import os, sys, datetime, functools, operator
# from time import time, gmtime, strftime
reload(sys)
sys.setdefaultencoding('utf8')

import ij
from collections import defaultdict
from ij import IJ, WindowManager
from ij.gui import Plot
from ij.io import OpenDialog
from java.io import File
from java.nio.file import Files
from java.awt.event import ActionListener, MouseAdapter, WindowAdapter, FocusEvent, KeyEvent, ActionEvent
from javax.swing import JPanel, JComboBox, JLabel, JFrame, JCheckBox, JButton, JRadioButton, ButtonGroup, JFormattedTextField, BorderFactory, JOptionPane, InputVerifier, JMenuBar, Box, ImageIcon, JScrollPane
from java.text import DecimalFormat
from java.awt import GridLayout, BorderLayout, FlowLayout, Dimension, Image, Toolkit, Color
from java.beans import PropertyChangeListener
from ij.plugin.frame import RoiManager

if os.path.split(os.path.dirname(sys.argv[0]))[-1] == '3D cyclorama':
	# Deployed mode
	path = os.path.join(IJ.getDirectory("plugins"), '3Dcyclorama')
else:
	# Developer mode
	IJ.log("Running in developer mode")
	path = os.path.abspath(os.path.dirname(sys.argv[0]))
sys.path.append(path)

from cycloramaUtilities import *

class paneProperyChangeListener(PropertyChangeListener):
	def __init__(self, label, propertyDict):
		self.name = label
		self.properties = propertyDict
	def propertyChange(self, e):
		self.properties[self.name] = e.getSource().getValue()
		if self.name == 'maxSpanLen':
			f = getField('interpolateContoursPane', 'spanStep')
			if f:
				self.properties['maxNoSteps'] = int(self.properties[self.name]/f.getValue())
		elif self.name == 'spanStep':
			f = getField('interpolateContoursPane', 'maxSpanLen')
			if f:
				self.properties['maxNoSteps'] = int(f.getValue()/self.properties[self.name])
		elif self.name == 'nPoints':
			spanSearchWinClass = getField('interpolateContoursPane', 'spanSearchWinClass')
			if not spanSearchWinClass is None:
				spanSearchWinClass.setSelectedIndex(spanSearchWinClass.getSelectedIndex()) # Fires actionPerformed event
class paneActionPerformedListener(ActionListener):
	def __init__(self, name, propertyDict):
		self.name = name
		self.properties = propertyDict
	def actionPerformed(self, e):
		global scanStack
		if isinstance(e.getSource(), JCheckBox):
			if self.name == "showContours":
				if not scanStack:
					JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack and compute boundary contours first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
					e.getSource().setSelected(False)
					self.properties[self.name] = e.getSource().isSelected()
				elif not scanStack.contours:
					JOptionPane.showMessageDialog(JFrame('') , "Please compute boundary contours first.", "Error: no boundary contours found!", JOptionPane.ERROR_MESSAGE);
					e.getSource().setSelected(False)
					self.properties[self.name] = e.getSource().isSelected()
				else:
					self.properties[self.name] = e.getSource().isSelected()
					scanStack.impSlices.updateAndDraw()
			else:
				self.properties[self.name] = e.getSource().isSelected()
		elif isinstance(e.getSource(), JRadioButton):
			global allFrames, loadScanStackProperties, boundaryContoursProperties, interpolateContoursProperties, computeMappingProperties, computeCycloramaProperties, analyzeProperties, dynamicProperties

			if self.name == 'Previous' and e.getSource().isSelected():
				parametersPath = os.path.join(path, 'previousCycloramaParameters.txt')
				dynamicProperties['parameters'] = 'Previous'
			elif self.name == 'Default' and e.getSource().isSelected():
				parametersPath = os.path.join(path, 'defaultCycloramaParameters.txt')
				dynamicProperties['parameters'] = 'Default'
				dynamicProperties['roiSet'] = dynamicProperties['inType'] = dynamicProperties['outType']  = ''

			loadParameters(parametersPath)
			refreshPanes()
			dynamicProperties['noSlices'] = loadScanStackProperties['noSlices']
			field = getField('loadScanStackPane', 'noSlices')
			if field:
				field.setValue(dynamicProperties['noSlices'])

			if computeMappingProperties["cycloramaMode"] == 'idx':
				dynamicProperties['cycloramaMode'] = 'Linear'
			elif computeMappingProperties["cycloramaMode"] == 'DTW':
				dynamicProperties['cycloramaMode'] = 'DTW'
			field = getField('mappingPane', 'cycloramaMode')
			if field:
				field.setSelectedIndex(field.getClientProperty('itemList').index(dynamicProperties['cycloramaMode']))

		elif isinstance(e.getSource(), JComboBox):
			global boundaryContoursProperties, interpolateContoursProperties, computeMappingProperties, dynamicProperties
			self.properties[self.name] = e.getSource().getSelectedItem()
			if self.name == 'spanSearchWinClass':
				if self.properties[self.name] == 'Narrow':
					interpolateContoursProperties['spanSearchWin'] = max(3, int(boundaryContoursProperties['nPoints']/10))
				elif self.properties[self.name] == 'Intermediate':
					interpolateContoursProperties['spanSearchWin'] = max(3, int(boundaryContoursProperties['nPoints']/5))
				elif self.properties[self.name] == 'Wide':
					interpolateContoursProperties['spanSearchWin'] = max(3, int(boundaryContoursProperties['nPoints']/3))
				spanSearchWinClass = getField('interpolateContoursPane', 'spanSearchWin').setValue(interpolateContoursProperties['spanSearchWin'])
			if self.name == 'cycloramaMode':
				if self.properties[self.name] == 'Linear':
					computeMappingProperties["cycloramaMode"] = 'idx'
				elif self.properties[self.name] == 'DTW':
					computeMappingProperties["cycloramaMode"] = 'DTW'
			if self.name == 'geometry' and scanStack:
				scanStack.geometry = self.properties[self.name].lower()
			if self.name == 'inType' or self.name == 'outType':
				boundaryContoursProperties[self.name] = self.properties[self.name]
			if self.name == 'roiSet':
				if scanStack:
					roiSetPath = os.path.join(scanStack.path, self.properties[self.name])
					isValid, roiIDs, roiLengths = checkRoiSetValidity(roiSetPath)
					updateContourTypeFields(roiIDs, roiLengths)
						# if isValid:
						# TODO: Handle invalid
class buttonClickedListener(ActionListener):
	def __init__(self, name):
		self.name = name
	def actionPerformed(self, e):
		global scanStack, mapping, path, pathAndTimestamp, logPath, roiman, globalProperties, loadScanStackProperties, boundaryContoursProperties, interpolateContoursProperties, computeMappingProperties, computeCycloramaProperties, analyzeProperties, dynamicProperties, roiMan
		if self.name == "loadScanStack":
			scanStack = None
			closeOpenImages()

			od = OpenDialog("Please select scan directory")
			if od.getPath():
				scanStackPath = od.getDirectory()
				if loadROIsetFromDir(scanStackPath):
					roiSetPath = os.path.join(scanStackPath, dynamicProperties['roiSet'])
				else:
					roiSetPath = None

				scanStack = ScanStack(scanStackPath, 1, \
									loadScanStackProperties["baseSlice"], \
									type='',
									geometry = boundaryContoursProperties['geometry'].lower(),
									roiSetPath = roiSetPath)


				noImgInDir = 0
				for InFile in File(scanStackPath).listFiles():
					FileType = Files.probeContentType(InFile.toPath())
					if FileType and FileType.split('/')[0] == 'image':
						noImgInDir += 1
				if dynamicProperties["noSlices"] == 0:
					loadScanStackProperties["noSlices"] = noImgInDir
				else:
					loadScanStackProperties["noSlices"] = min(dynamicProperties["noSlices"], noImgInDir)
				field = getField('loadScanStackPane', 'noSlices')
				if field:
					field.setValue(loadScanStackProperties["noSlices"])

				if loadScanStackProperties["virtual"]:
					scanStack.loadScanStack(noSlices=loadScanStackProperties["noSlices"], virtual=" use")
				else:
					scanStack.loadScanStack(noSlices=loadScanStackProperties["noSlices"])

				scanStack.impSlices.addImageListener(imageUpdadeListener(scanStack.impSlices))
				scanStack.impSlices.getWindow().addWindowListener(windowCloseListener('impSlices'))

				readCalibration()
				IJ.run("Brightness/Contrast...")
			# else:
			# 	JOptionPane.showMessageDialog(JFrame('') , "ScanStack loading failed.", "Error!", JOptionPane.ERROR_MESSAGE)

		if self.name == "computeAll" or self.name == "computeBoundaryContours":
			if not scanStack:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE)
				return
			elif dynamicProperties['roiSet']=='' or dynamicProperties['roiSet']==None:
				JOptionPane.showMessageDialog(JFrame('') , "Please load a valid ROI set.", "Error: roiSet not found!", JOptionPane.ERROR_MESSAGE);
			else:
				field = getField('interpolateContoursPane', 'testInterpolationParams')
				if field:
					field.setEnabled(True)
				scanStack.rotAxis = Point2D(int(boundaryContoursProperties["rotAxisX"]), int(boundaryContoursProperties["rotAxisY"]))
				scanStack.sliceStep = loadScanStackProperties['sliceStep']
				scanStack.roiSetPath = os.path.join(scanStack.path, dynamicProperties['roiSet'])
				isValid, roiIDs, roiLengths = checkRoiSetValidity(scanStack.roiSetPath, suppressMsg=True)
				IJ.log("Selecting roiSet: " + scanStack.roiSetPath)
				roiMan.reset()
				quitAns = 0
				if not roiLengths[boundaryContoursProperties["inType"]] < roiLengths[boundaryContoursProperties["outType"]]:
					quitAns = JOptionPane.showConfirmDialog(None, "<html><p style='text-align:center;'>Inner/outer contours are possibly swapped. Consider swapping the contour IDs.<br>Would you like to keep current selection and continue?</p></html>", "Warning: Inner contours are longer than outer contours!", JOptionPane.YES_NO_OPTION);

				if dynamicProperties['sliceStep'] > scanStack.size :
					field = getField('boundaryContoursPane', 'sliceStep')
					if field:
						field.setValue(max(int(scanStack.size), 1))
						IJ.log('Warning: requested interpolation interval is larger than the scanStack size. Setting interpolation interval equal to {:d}.'.format(field.getValue()))

				if boundaryContoursProperties['nPoints'] < boundaryContoursProperties['smoothingFactor']:
					field = getField('boundaryContoursPane', 'smoothingFactor')
					if field:
						IJ.log('Warning: requested smoothing factor is larger than the contour length. Setting smoothing factor equal to contour length.')
						field.setValue(min(boundaryContoursProperties['nPoints'], boundaryContoursProperties['smoothingFactor']))

				if not quitAns:
					scanStack.computeBoundaryContours(apply=True, \
													nPoints=boundaryContoursProperties["nPoints"], \
													maxLoadedThreads=globalProperties["maxLoadedThreads"], \
													smoothingFactor=boundaryContoursProperties["smoothingFactor"], \
													method=boundaryContoursProperties["method"], \
													inType=boundaryContoursProperties["inType"], \
													outType=boundaryContoursProperties["outType"])

		if self.name == "computeAll" or self.name == "interpolateContours":
			if not scanStack:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack and compute boundary contours first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
			elif scanStack.impSlices.getStack().isVirtual():
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack as NOT virtual.", "Error: scanStack is virtual!", JOptionPane.ERROR_MESSAGE);
			elif not scanStack.contours:
				JOptionPane.showMessageDialog(JFrame('') , "Please compute boundary contours first.", "Error: no boundary contours found!", JOptionPane.ERROR_MESSAGE);
			else:
				if boundaryContoursProperties['nPoints'] < interpolateContoursProperties['spanSearchWin']:
					field = getField('interpolateContoursPane', 'spanSearchWin')
					if field:
						IJ.log('Warning: requested search window is larger than the contour length. Setting search window equal to contour length.')
						field.setValue(min(boundaryContoursProperties['nPoints'], interpolateContoursProperties['spanSearchWin']))

				scanStack.interpolateContours(depthLevels=interpolateContoursProperties["depthLevels"], \
															width=boundaryContoursProperties["nPoints"], \
															spanStep=interpolateContoursProperties["spanStep"], \
															maxSpanLen=interpolateContoursProperties["maxNoSteps"], \
															spanSearchWin=interpolateContoursProperties["spanSearchWin"], \
															minSpanVariance=interpolateContoursProperties["minSpanVariance"], \
															rigidity=interpolateContoursProperties["rigidity"], \
															smoothingFactor=boundaryContoursProperties["smoothingFactor"], \
															maxLoadedThreads=globalProperties["maxLoadedThreads"])
				if interpolateContoursProperties["saveContours"]:
					scanStack.saveContours()
					IJ.log("Contours saved at: " + scanStack.contoursSavedTo)
				scanStack.impSlices.updateAndDraw()

		if self.name == "computeAll" or self.name == "computeCyclorama":
			if not scanStack:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack, compute boundary contours and then compute contours first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
			elif not scanStack.contours:
				JOptionPane.showMessageDialog(JFrame('') , "Please compute boundary contours and then compute contours first.", "Error: no boundary contours found!", JOptionPane.ERROR_MESSAGE);
			elif not len(scanStack.contours)==interpolateContoursProperties["depthLevels"]:
				JOptionPane.showMessageDialog(JFrame('') , "Please  compute contours first.", "Error: no intermediate contours found!", JOptionPane.ERROR_MESSAGE);
			else:
				if computeMappingProperties["interpolatedDepthLevels"]==0 or computeMappingProperties["interpolatedDepthLevels"]==None:
					interpolatedDepthLevels = None
				else:
					interpolatedDepthLevels = computeMappingProperties["interpolatedDepthLevels"]

				field = getField('interpolateContoursPane', 'testInterpolationParams')
				if field:
					field.setEnabled(False)
				IJ.log("Computing mapping with mode" + computeMappingProperties["cycloramaMode"])
				mapping = scanStack.computeMapping(interpolatedDepthLevels=interpolatedDepthLevels, \
													maxLoadedThreads=globalProperties["maxLoadedThreads"], \
													 mode=computeMappingProperties["cycloramaMode"])

				if computeMappingProperties["saveMapping"]:
					pathAndTimestamp = mapping.save(scanStack.cycloramaPath)
				else:
					pathAndTimestamp = None

				scanStack.sampleScanStack(mapping, maxLoadedThreads=globalProperties["maxLoadedThreads"])
				cycloramaFile = None
				if computeCycloramaProperties["saveCyclorama"]:
					cycloramaFile = scanStack.saveCyclorama(mapping)

				scanStack.impPlanarStack.show()
				IJ.run("Brightness/Contrast...")
				logPath = scanStack.saveLog(cycloramaFile=cycloramaFile, pathAndTimestamp=pathAndTimestamp, times=None)

		if self.name == "computeAll" or self.name == "computeVolume":
			if not scanStack:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack, compute boundary contours first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
			elif not scanStack.contours:
				JOptionPane.showMessageDialog(JFrame('') , "Please compute boundary contours first.", "Error: no boundary contours found!", JOptionPane.ERROR_MESSAGE);
			else:
				IJ.log("Computing slice volumes...")
				if readCalibration():
					totVol, sliceVolumes = scanStack.measureVolume(analyzeProperties["startSlice"], analyzeProperties["endSlice"], analyzeProperties["pixelSize"])
					if analyzeProperties["saveVolumes"]:
						saveVolumes(totVol, sliceVolumes)

					IJ.log("Total volume of {0:d}  slices: {1:2.2E} ".format(len(sliceVolumes), totVol) + analyzeProperties["pixelUnits"] + '^3')
					plt = Plot("Volume histogram", "Volume [" +analyzeProperties["pixelUnits"] +"^3]", "Number of slices")
					plt.addHistogram(sliceVolumes, analyzeProperties["volHistBinsWidth"])
					plt.show()
				else:
					raise ValueError("Pixel-size not isotropic. Volume measurement aborted.")

		if self.name == "computeAll" or self.name == "computeThickness":
			if not scanStack:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack, compute boundary contours and then compute contours first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
			elif not scanStack.spanLengths:
				JOptionPane.showMessageDialog(JFrame('') , "Please compute contours first.", "Error: no spans found!", JOptionPane.ERROR_MESSAGE);
			else:
				IJ.log("Computing local thickness...")
				if readCalibration():
					locThicknesses = scanStack.measureSpanLengths(analyzeProperties["startSlice"], analyzeProperties["endSlice"], analyzeProperties["pixelSize"])
					data = [locThicknesses[k] for k in locThicknesses.keys()]
					data = functools.reduce(operator.iconcat, data, [])

					if analyzeProperties["saveThickness"]:
						saveThickness(locThicknesses)

					plt = Plot("Local thickness histogram", "Local thickness [" +analyzeProperties["pixelUnits"] +"]", "Number of spans")
					plt.addHistogram(data, analyzeProperties["thickHistBinsWidth"])
					plt.show()
				else:
					raise ValueError("Pixel-size not isotropic. Local thickness measurement aborted.")

		if self.name == "computeAll":
			saveParameters(os.path.join(scanStack.cycloramaPath, 'cycloramaParameters.txt'))

		if self.name == "showMasks":
			if not scanStack:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack and compute boundary contours first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
			elif not scanStack.contours:
				JOptionPane.showMessageDialog(JFrame('') , "Please compute boundary contours first.", "Error: no boundary contours found!", JOptionPane.ERROR_MESSAGE);
			else:
				impMasks = scanStack.getMasksImp()
				maskStack = impMasks.getStack()
				for i in range(1, maskStack.getSize()+1):
					maskStack.getProcessor(i).invert()
				impMasks.show()
				# impMasks.addImageListener(imageUpdadeListener(impMasks, purgeScanStack=False))
				# impMasks.removeImageListener(impMasks.getWindow().getListeners(WindowAdapter))
				# impMasks.getWindow().addWindowListener(windowCloseListener('impMasks', closeOnExit=False, saveOnExit=False))

		if self.name == "testInterpolationParams":
			if not scanStack:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack and compute boundary contours first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
			elif not scanStack.contours:
				JOptionPane.showMessageDialog(JFrame('') , "Please compute boundary contours first.", "Error: no boundary contours found!", JOptionPane.ERROR_MESSAGE);
			else:
				if boundaryContoursProperties['nPoints'] < interpolateContoursProperties['spanSearchWin']:
					field = getField('interpolateContoursPane', 'spanSearchWin')
					if field:
						IJ.log('Warning: requested search window is larger than the contour length. Setting search window equal to contour length.')
						field.setValue(min(boundaryContoursProperties['nPoints'], interpolateContoursProperties['spanSearchWin']))
				# TODO: Remove backend code from here
				showContours = getField('interpolateContoursPane', 'showContours')
				if showContours:
					showContours.setSelected(False)
					dynamicProperties['showContours'] = False
				roiMan = RoiManager().getInstance() or RoiManager()
				roiMan.reset()
				tmpScanStack = ScanStack('')
				tmpScanStack.geometry = scanStack.geometry
				z = scanStack.impSlices.getSlice()
				tmpScanStack.rotAxis = scanStack.rotAxis
				tmpScanStack.size = scanStack.size
				tmpScanStack.impSlices = scanStack.impSlices
				depthLevels = interpolateContoursProperties["depthLevels"]
				for idx, depth in enumerate(tmpScanStack.tools.linspace(0, 1, depthLevels, inclusive=True)):
					if depth==0.0 or depth==1.0:
						depth = int(depth)
						tmpScanStack.contours[depth] = {}
						tmpScanStack.contours[depth][z] = Contour(PointList(scanStack.contours[depth][z].getPoints()), pos=tmpScanStack.rotAxis, depthPos=depth, align=tmpScanStack.geometry=='tube')
					else:
						tmpScanStack.contours[depth] = {}
				tmpScanStack.checkContourInterpolationParameters(depthLevels=depthLevels, \
															z=z,
															width=boundaryContoursProperties["nPoints"], \
															spanStep=interpolateContoursProperties["spanStep"], \
															maxSpanLen=interpolateContoursProperties["maxNoSteps"], \
															spanSearchWin=interpolateContoursProperties["spanSearchWin"], \
															minSpanVariance=interpolateContoursProperties["minSpanVariance"], \
															rigidity=interpolateContoursProperties["rigidity"], \
															smoothingFactor=boundaryContoursProperties["smoothingFactor"])
				roiMan.runCommand("Show All")

		if self.name == "selectAOR":
			if scanStack is None:
				JOptionPane.showMessageDialog(JFrame('') , "Please load scanStack first.", "Error: no scanStack found!", JOptionPane.ERROR_MESSAGE);
			else:
				win = scanStack.impSlices.getWindow()
				if win:
					rX = getField('boundaryContoursPane', 'rotAxisX')
					rY = getField('boundaryContoursPane', 'rotAxisY')
					if rX and rY:
						rX.setEnabled(False)
						rY.setEnabled(False)
					win.getCanvas().addMouseListener(mouseClickListener(rX, rY))
class windowCloseListener(WindowAdapter):
	def __init__(self, name):
		self.name = name
	def windowClosing(self, e):
		global scanStack, allFrames

		if self.name == 'impSlices':
			scanStack = None
			return

		for f in allFrames:
			f.dispose()

		saveParameters(os.path.join(path, 'previousCycloramaParameters.txt'))
class imageUpdadeListener(ij.ImageListener):
	def __init__(self, imp):
		self.imp = imp
	def imageUpdated(self, imp):
		global scanStack, dynamicProperties
		hiddenRoiMan = RoiManager(True)
		if dynamicProperties["showContours"] and scanStack.impSlices:
			z = scanStack.impSlices.getSlice()
			for contDepth in scanStack.contours.keys():
				# TODO: Make this dodge error when contour is not found
				roi = scanStack.contours[contDepth][z].getContourRoi()
				hiddenRoiMan.add(scanStack.impSlices, roi, z)
			hiddenRoiMan.runCommand(scanStack.impSlices, "Show All")
		else:
			hiddenRoiMan.runCommand(scanStack.impSlices, "Show None")
	def imageClosed(self, imp):
		global scanStack
		self.imp.removeImageListener(self)
		if scanStack:
			scanStack.impSlices.addImageListener(imageUpdadeListener(scanStack.impSlices))
	def imageOpened(self, imp):
		pass
class mouseClickListener(MouseAdapter):
	def __init__(self, rX, rY):
		self.rX = rX
		self.rY = rY
	def mouseClicked(self, event):
		global boundaryContoursProperties
		canvas = event.getSource()
		imp = canvas.getImage()
		if imp == scanStack.impSlices:
			# z = imp.getSlice()
			clickedPoint = canvas.getCursorLoc()
			boundaryContoursProperties["rotAxisX"] = int(clickedPoint.getX())
			boundaryContoursProperties["rotAxisY"] = int(clickedPoint.getY())
			self.rX.setValue(boundaryContoursProperties["rotAxisX"])
			self.rY.setValue(boundaryContoursProperties["rotAxisY"])
			self.rX.setEnabled(True)
			self.rY.setEnabled(True)
class txtVerifier(InputVerifier):
	def __init__(self, name, charType, minV):
		self.name = name
		self.charType = charType
		self.minV = minV
	def verify(self, field):
		if field.isEditValid():
			field.commitEdit()
		else:
			field.setValue(field.getValue())
		if self.charType == 'int':
			v = int(float(field.getValue()))
		elif self.charType == 'float':
			v = float(field.getValue())
		elif self.charType == 'txt':
			v = field.getValue()
		field.setValue(v)

		if self.name == 'noSlices' and v == 0:
			return True

		if (self.charType == 'int' or self.charType == 'float') and v < self.minV:
			if v < self.minV:
				JOptionPane.showMessageDialog(JFrame('') , "Expecting a value greater or equal to {:d}".format(self.minV), "Input value error!", JOptionPane.ERROR_MESSAGE)
				field.setValue(self.minV)
				return False
		else:
			return True
def mean(l):
	if len(l)>0:
		s = 0
		for v in l:
			s +=v
		return s/len(l)
	else:
		return 0
def getField(frameName, fieldName):
	global allFrames
	if not allFrames is None:
		for f in allFrames:
			if f.getName() == frameName:
				for p in f.getContentPane().getComponents():
					if isinstance(p, JPanel):
						for c in p.getComponents():
							try:
								if c.getClientProperty('name') == fieldName:
									return c
							except:
								pass
	return None
def closeOpenImages():
	wm = WindowManager
	imageIDs = wm.getIDList()
	if not imageIDs is None:
		for imID in imageIDs:
			imp = wm.getImage(imID)
			imp.close()
def checkRoiSetValidity(roiSetPath, suppressMsg=False):
	global loadScanStackProperties, roiMan
	# Check validity
	# hiddenRoiMan = RoiManager(True)
	roiMan.reset()
	roiMan.runCommand("Open", roiSetPath)
	sliceRois = defaultdict(lambda : [])
	allTypes = []
	allZ = []
	roiLengths = defaultdict(lambda : [])
	for idx in range(0, roiMan.getCount()):
		name = roiMan.getName(idx).split("-")
		z = int(name[0])
		typ = int(name[1])
		roiLengths[typ].append(roiMan.getRoi(idx).getFloatPolygon().getLength(False))
		allTypes.append(typ)
		allZ.append(z)
		sliceRois[z].append(typ)
	for typ in roiLengths.keys():
		roiLengths[typ] = mean(roiLengths[typ])

	scanStackRange = range(loadScanStackProperties['baseSlice'], loadScanStackProperties['baseSlice']+loadScanStackProperties['noSlices'])
	zRange = range(min(allZ), max(allZ)+1)
	if not set(scanStackRange).issubset(set(zRange)):
		if not suppressMsg:
			JOptionPane.showMessageDialog(JFrame(''), \
			"<html><p style='text-align:center;'>The selected roiSet does not cover the range of scanStack slices:\
			<br>roiSet range: [{0:d}, {1:d}]<br>\
			scanStack range: [{2:d}, {3:d}]</p><br>\
			<p style='text-align:center;'>Select a roiSet that covers the range of the loaded slices in the scanStack:<br>\
			First slice &le; {2:d}<br>\
			Last slice &ge; {3:d}</p> </html>".format(zRange[0], zRange[-1], scanStackRange[0], scanStackRange[-1]),\
			"Error: invalid roiSet!", JOptionPane.ERROR_MESSAGE);
		return False, [''], {}
	roiIDs = list(set(allTypes))
	invalidList = []
	isValid = True
	for z in sliceRois.keys():
		if not sliceRois[z] == roiIDs:
			invalidList.append(z)
			isValid = False
	if isValid:
		return True, roiIDs, roiLengths
	else:
		if not suppressMsg:
			JOptionPane.showMessageDialog(JFrame(''), \
			"<html><p style='text-align:center;'>The selected roiSet does not include matching contour IDs for all scanStack slices:\
			<br>Found contour IDs: {0:s}<br> \
			Invalid slices: {1:s} </p><br> \
			<p>Select a roiSet with the same set of contour IDs for each of the defined slices.</p></html>".format(sorted(roiIDs), sorted(invalidList)), \
			"Error: invalid roiSet!", JOptionPane.ERROR_MESSAGE);
		return False, [''], {}
def updateContourTypeFields(roiIDs, roiLengths=None):
	global boundaryContoursProperties
	# Need to keep these because the comboBox updates the global boundaryContoursProperties
	# every time a new item is added and the original values are being overwritten.
	tmpInType = boundaryContoursProperties['inType']
	tmpOutType = boundaryContoursProperties['outType']
	inTypeComboBox = getField('boundaryContoursPane', 'inType')
	if inTypeComboBox:
		inTypeComboBox.removeAllItems()
		for id in roiIDs:
			inTypeComboBox.addItem(id)
		if roiLengths:
			inTypeComboBox.setSelectedIndex(roiIDs.index(min(roiLengths, key=roiLengths.get)))
		elif tmpInType in roiIDs:
			inTypeComboBox.setSelectedIndex(roiIDs.index(tmpInType))
		else:
			inTypeComboBox.setSelectedIndex(0)
	outTypeComboBox = getField('boundaryContoursPane', 'outType')
	if outTypeComboBox:
		outTypeComboBox.removeAllItems()
		for id in roiIDs:
			outTypeComboBox.addItem(id)
		if roiLengths:
			outTypeComboBox.setSelectedIndex(roiIDs.index(max(roiLengths, key=roiLengths.get)))
		elif tmpOutType in roiIDs:
			outTypeComboBox.setSelectedIndex(roiIDs.index(tmpOutType))
		else:
			outTypeComboBox.setSelectedIndex(0)
def loadROIsetFromDir(dirPath=None):
	global dynamicProperties, boundaryContoursProperties, roiMan

	roiSetComboBox = getField('boundaryContoursPane', 'roiSet')
	if roiSetComboBox:
		roiSetComboBox.removeAllItems()
		roiSetList = []
		if not dirPath is None:
			for f in os.listdir(dirPath):
				if f.endswith(".zip"):
					roiSetPath = os.path.join(dirPath, f)
					roiSetList.append(f)
					roiSetComboBox.addItem(f)
			roiMan.reset()
			roiIDs = ['']
			if roiSetList:
				for f in roiSetList:
					dynamicProperties['roiSet'] = f
					roiSetPath = os.path.join(dirPath, f)
					IJ.log("Loading roiSet: " + roiSetPath)
					roiSetComboBox.setSelectedIndex(roiSetList.index(f))

					isValid, roiIDs, roiLengths = checkRoiSetValidity(roiSetPath, suppressMsg=True)
					if isValid:
						updateContourTypeFields(roiIDs, roiLengths)
						return True
				updateContourTypeFields(roiIDs)
				JOptionPane.showMessageDialog(JFrame('') , "No valid ROI set found in scanStack directory.", "Error: could not load roiSet", JOptionPane.ERROR_MESSAGE);
				return False
			else:
				updateContourTypeFields(roiIDs)
				JOptionPane.showMessageDialog(JFrame('') , "No ROI set found in scanStack directory.", "Error: could not load roiSet", JOptionPane.ERROR_MESSAGE);
				return False
		else:
			dynamicProperties['roiSet'] = ''
			roiSetList = ['']
			roiSetComboBox.addItem('')
			roiSetComboBox.setSelectedIndex(roiSetList.index(''))
def readCalibration():
	global scanStack, analyzeProperties
	cal = scanStack.impSlices.getCalibration()
	if cal.pixelDepth == cal.pixelWidth == cal.pixelHeight:
		analyzeProperties["pixelUnits"] = cal.getUnits()
		analyzeProperties["startSlice"] = 1
		analyzeProperties["endSlice"] = scanStack.size
		analyzeProperties["pixelSize"] = cal.pixelDepth
		return True
	else:
		return False
def saveParameters(parametersPath=None):
	global scanStack, globalProperties, loadScanStackProperties, boundaryContoursProperties, interpolateContoursProperties, computeMappingProperties, computeCycloramaProperties, analyzeProperties

	parameters = {
	"globalProperties":globalProperties,
	"loadScanStackProperties":loadScanStackProperties,
	"boundaryContoursProperties":boundaryContoursProperties,
	"interpolateContoursProperties":interpolateContoursProperties,
	"computeMappingProperties":computeMappingProperties,
	"computeCycloramaProperties":computeCycloramaProperties,
	"analyzeProperties": analyzeProperties
	}

	with open(parametersPath, 'w') as f:
		f.write(json.dumps(parameters, indent=4, sort_keys=True, separators=(',', ': '), ensure_ascii=False))
def loadParameters(parametersPath):
	global globalProperties, loadScanStackProperties, boundaryContoursProperties, interpolateContoursProperties, computeMappingProperties, computeCycloramaProperties, analyzeProperties

	IJ.log('Loading parameters from: ' + parametersPath)
	with open(parametersPath, 'r') as f:
		parameters = json.load(f)

	globalProperties = parameters["globalProperties"]
	loadScanStackProperties = parameters["loadScanStackProperties"]
	boundaryContoursProperties = parameters["boundaryContoursProperties"]
	interpolateContoursProperties = parameters["interpolateContoursProperties"]
	computeMappingProperties = parameters["computeMappingProperties"]
	computeCycloramaProperties = parameters["computeCycloramaProperties"]
	analyzeProperties = parameters["analyzeProperties"]
def initialise():
	global allFrames, path, logPath, scanStack, dynamicProperties, computeMappingProperties, loadScanStackProperties, interpolateContoursProperties, boundaryContoursProperties, pathAndTimestamp, roiMan
	logPath = None
	scanStack = None
	allFrames = []

	dynamicProperties = {'roiSet':'', 'parameters':'Previous', 'inType':'', 'outType':'', 'showContours':False}
	if computeMappingProperties['cycloramaMode'] == 'idx':
		dynamicProperties['cycloramaMode'] = 'Linear'
	elif computeMappingProperties['cycloramaMode'] == 'DTW':
		dynamicProperties['cycloramaMode'] = 'DTW'
	dynamicProperties['noSlices'] = loadScanStackProperties['noSlices']
	dynamicProperties['sliceStep'] = loadScanStackProperties['sliceStep']

	boundaryContoursProperties["method"] = "load"
	loadScanStackProperties['contourDefinition'] = 'Load ROIset'
	pathAndTimestamp = None
	IJ.log("\\Clear")
	roiMan = RoiManager().getInstance() or RoiManager()
def saveVolumes(totVol, sliceVolumes):
	global scanStack, analyzeProperties, boundaryContoursProperties
	timestamp = datetime.datetime.today().strftime('%Y%m%d%H%M')
	volumesPath = os.path.join(scanStack.path, "Analysis")
	if not os.path.exists(volumesPath):
		os.mkdir(volumesPath)
	inType = boundaryContoursProperties["inType"]
	outType = boundaryContoursProperties["outType"]
	volumesPath = os.path.join(volumesPath, timestamp+"_in"+str(inType)+"-out"+str(outType)+"_volumes.txt")
	strVol = {
		"scanStackPath": scanStack.path,
		"startSlice": loadScanStackProperties["baseSlice"] + analyzeProperties["startSlice"] - 1,
		"endSlice": loadScanStackProperties["baseSlice"] + analyzeProperties["endSlice"] - 1,
		"totalVolume:": totVol,
		"pixelUnits:": analyzeProperties["pixelUnits"] ,
		"pixelSize": analyzeProperties["pixelSize"],
		"sliceVolumes": sliceVolumes
	}
	with open(volumesPath, 'w') as f:
		f.write(json.dumps(strVol, indent=4, sort_keys=True, separators=(',', ': '), ensure_ascii=False))
def saveThickness(locThicknesses):
	global scanStack, analyzeProperties, boundaryContoursProperties
	timestamp = datetime.datetime.today().strftime('%Y%m%d%H%M')
	thicknessesPath = os.path.join(scanStack.path, "Analysis")
	if not os.path.exists(thicknessesPath):
		os.mkdir(thicknessesPath)
	inType = boundaryContoursProperties["inType"]
	outType = boundaryContoursProperties["outType"]
	thicknessesPath = os.path.join(thicknessesPath, timestamp+"_in"+str(inType)+"-out"+str(outType)+"_thickness.txt")
	strThick = {
		"scanStackPath": scanStack.path,
		"startSlice": analyzeProperties["startSlice"],
		"endSlice": analyzeProperties["endSlice"],
		"pixelUnits:": analyzeProperties["pixelUnits"] ,
		"pixelSize": analyzeProperties["pixelSize"],
		"localThicknesses": locThicknesses
	}
	with open(thicknessesPath, 'w') as f:
		f.write(json.dumps(strThick, indent=4, sort_keys=True, separators=(',', ': '), ensure_ascii=False))

def createButton(pane, name, labelText, toolTip=''):
	button = JButton(labelText)
	button.putClientProperty('name', name)
	if toolTip:
		button.setToolTipText("<html><p style='width: 200px';>{:s}</p></html>".format(toolTip))
	button.addActionListener(buttonClickedListener(name))
	pane.add(button)
def createTextBox(fieldPane, labelPane, propertyDict, name, labelText, noColumns=3, charType='int', minV=1, txtToolTip='', labelToolTip=''):
	if charType=='txt':
		field = JFormattedTextField()
	elif charType=='int' or charType=='float':
		field = JFormattedTextField(DecimalFormat())
	field.putClientProperty('name', name)
	field.setValue(propertyDict[name])
	field.setColumns(noColumns)
	field.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT)
	field.addPropertyChangeListener(paneProperyChangeListener(name, propertyDict))
	field.setInputVerifier(txtVerifier(name, charType, minV))
	label = JLabel(labelText)
	label.setLabelFor(field)
	if txtToolTip:
		field.setToolTipText("<html><p style='width: 200px';>{:s}</p></html>".format(txtToolTip))
	if labelToolTip:
		label.setToolTipText("<html><p style='width: 200px';>{:s}</p></html>".format(labelToolTip))
	labelPane.add(label)
	fieldPane.add(field)
	return field
def createCheckBox(fieldPane, labelPane, propertyDict, name, labelText, labelToolTip=''):
	field = JCheckBox("", propertyDict[name])
	field.putClientProperty('name', name)
	field.addActionListener(paneActionPerformedListener(name, propertyDict))
	label = JLabel(labelText)
	label.setLabelFor(field)
	if labelToolTip:
		label.setToolTipText("<html><p style='width: 200px';>{:s}</p></html>".format(labelToolTip))
	labelPane.add(label)
	fieldPane.add(field)
def createComboBox(fieldPane, labelPane, propertyDict, name, labelText, items, prototypeText=None, txtToolTip='', labelToolTip=''):
	field = JComboBox(items)
	field.putClientProperty('name', name)
	field.putClientProperty('itemList', items)
	if prototypeText:
		field.setPrototypeDisplayValue(prototypeText);
	field.setSelectedIndex(items.index(propertyDict[name]))
	field.addActionListener(paneActionPerformedListener(name, propertyDict))
	label = JLabel(labelText)
	label.setLabelFor(field)
	if txtToolTip:
		field.setToolTipText("<html><p style='width: 200px';>{:s}</p></html>".format(txtToolTip))
	if labelToolTip:
		label.setToolTipText("<html><p style='width: 200px';>{:s}</p></html>".format(labelToolTip))
	labelPane.add(label)
	fieldPane.add(field)
	return field
def createRadioButtons(fieldPane, labelPane, propertyDict, name, labelText, items, default=""):
	field = ButtonGroup()
	options = []
	for i, item in enumerate(items):
		option = JRadioButton(item)
		if not propertyDict is None:
			option.addActionListener(paneActionPerformedListener(item, propertyDict))
		if item==default:
			option.setSelected(True)
		field.add(option)
		options.append(option)
		if i==0:
			label = JLabel(labelText)
		else:
			label = JLabel("")
		labelPane.add(label)
	# fieldPane.setLayout(FlowLayout())
	for option in options:
		fieldPane.add(option)
def createEmptyLabel(fieldPane, labelPane):
	labelPane.add(JLabel(''))
	fieldPane.add(JLabel(''))
def packFrame(frame, labelPane, fieldPane, bottomPane, name):
	labelPane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20))
	fieldPane.setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 20))
	frame.add(labelPane, BorderLayout.WEST)
	frame.add(fieldPane, BorderLayout.EAST)
	frame.add(bottomPane, BorderLayout.SOUTH)
	frame.pack()
	frame.addWindowListener(windowCloseListener(name))
	floatFrame(frame)
	frame.setVisible(True)
	return frame
def floatFrame(frame, spacer=5):
	global allFrames
	if allFrames:
		pos = allFrames[-1].getLocation()
		x, y = int(pos.getX()), int(pos.getY())
		w, h = allFrames[-1].getWidth(), max(map(JFrame.getHeight, allFrames))
		if len(allFrames) % 3:
			frame.setLocation(x+w+spacer, y)
		else:
			frame.setLocation(0, y+h+spacer)
	else:
		frame.setLocation(spacer, spacer)
def createLoadScanStackPane():
	global allFrames, loadScanStackProperties, boundaryContoursProperties, globalProperties, dynamicProperties
	frame = JFrame("1: Load scan stack")
	frame.setName('loadScanStackPane')
	fieldPane = JPanel(GridLayout(0,1))
	labelPane = JPanel(GridLayout(0,1))
	bottomPane = JPanel(GridLayout(0,1))

	createRadioButtons(fieldPane, labelPane, dynamicProperties, "parametersFile", "Parameters", ["Previous", "Default"], dynamicProperties['parameters'])
	createEmptyLabel(fieldPane, labelPane)
	createTextBox(fieldPane, labelPane, loadScanStackProperties, "baseSlice", "First slice", 4, \
	labelToolTip="The slice number of the entire image stack to be loaded as the first slice of the scanStack.", \
	txtToolTip='Positive integer.')
	createTextBox(fieldPane, labelPane, dynamicProperties, "noSlices", "Number of slices", 4, minV=22, \
	labelToolTip="Total number of slices to load in the scanStack.", \
	txtToolTip='Integer greater or equal to 22. Set to 0 to load all images in the selected directory.')
	createCheckBox(fieldPane, labelPane, loadScanStackProperties, "virtual", "Open as virtual stack")
	createEmptyLabel(fieldPane, labelPane)
	createTextBox(fieldPane, labelPane, globalProperties, "maxLoadedThreads", "Max number of threads", \
	labelToolTip='Number concurrent computational threads to use.', \
	txtToolTip='Positive integer.')
	createButton(bottomPane, 'loadScanStack', 'Load scanStack', 'Load an image stack depicting the tube that needs unrolling.')
	createButton(bottomPane, 'computeAll', 'Compute all', 'Perform all operations of panels 1 to 5 with the current parameters.')

	return packFrame(frame, labelPane, fieldPane, bottomPane, 'loadScanStack')
def createBoundaryContoursPane():
	global allFrames, dynamicProperties, boundaryContoursProperties, loadScanStackProperties, scanStack, roiSetComboBox
	frame = JFrame("2: Boundary contours")
	frame.setName('boundaryContoursPane')
	fieldPane = JPanel(GridLayout(0,1))
	labelPane = JPanel(GridLayout(0,1))
	bottomPane = JPanel(GridLayout(0,1))

	createComboBox(fieldPane, labelPane, dynamicProperties, "roiSet", "ROI set", [''], 'cycloramaRoiSet.zip', \
	labelToolTip='Fiji/ImageJ ROI set (create ROIs using the ROI manager, rename to <sliceNo>-<contour ID>-<some No>, select all ROIs and save as *.zip). At least one pair of ROIs required on each slice where ROIs are defined. Define ROIs on a handful of slices (5 to 20 is plausible).', \
	txtToolTip='ROI sets found in the scanStack directory. When selected, the ROI set is loaded in the ROI manager for examination.')
	createComboBox(fieldPane, labelPane, dynamicProperties, 'inType', 'Inner contour ID', [''], '10', \
	labelToolTip='ROI id of the ROIs that represent inner contours.', \
	txtToolTip='ROI ids found in the selected ROI set.')
	createComboBox(fieldPane, labelPane, dynamicProperties, 'outType', 'Outer contour ID', [''], '10', \
	labelToolTip='ROI id of the ROIs that represent outer contours.', \
	txtToolTip='ROI ids found in the selected ROI set.')
	if not scanStack is None:
		loadROIsetFromDir(scanStack.path)
	else:
		loadROIsetFromDir()
	createEmptyLabel(fieldPane, labelPane)

	createComboBox(fieldPane, labelPane, boundaryContoursProperties, 'geometry', 'Geometry', ['Tube', 'Sheet'], \
	labelToolTip='Geometry of the object that needs to be unrolled/flattened', \
	txtToolTip='Select Tube to unroll tubular structures or Sheet to flatten sheets and scrolls.')
	createEmptyLabel(fieldPane, labelPane)

	createTextBox(fieldPane, labelPane, boundaryContoursProperties, "nPoints", "Contour length", minV=10, \
	labelToolTip='Number of points used to represent each of the boundary and intermediate contours.', \
	txtToolTip='Positive integer. A value around 50 to 100 and no more than a few hundrents is plausible.')
	createTextBox(fieldPane, labelPane, boundaryContoursProperties, "smoothingFactor", "Smoothing factor", 2, \
	labelToolTip='Window size (number of points) used to smoothen the contours in a moving average filter.', \
	txtToolTip='Positive integer, smaller than the contour length. Set to 1 to avoid smoothing.')
	createTextBox(fieldPane, labelPane, dynamicProperties, "sliceStep", "Interpolation interval", 2, \
	labelToolTip='Number of slices in between spline points used to interpolate the contours through the scanStack.', \
	txtToolTip='Positive integer, smaller than the number of slices. Reduce for samples changing radically from slice to slice.')
	createEmptyLabel(fieldPane, labelPane)
	createTextBox(fieldPane, labelPane, boundaryContoursProperties, "rotAxisX", "Rotation axis X", minV=0, \
	labelToolTip='Location (pixels) of the axis of rotation in the horizontal direction.', \
	txtToolTip='Non-negative integer with zero at the leftmost side of the image.')
	createTextBox(fieldPane, labelPane, boundaryContoursProperties, "rotAxisY", "Rotation axis Y", minV=0, \
	labelToolTip='Location (pixels) of the axis of rotation in the vertical direction.', \
	txtToolTip='Non-negative integer with zero at the top of the image.')
	# TODO: Make this consistent to the rest
	selectAORbutton = JButton('Select')
	selectAORbutton.setToolTipText("<html><p style='width: 200px';>{:s}</p></html>".format('Click on the scanStack to interactively select the rotation axis.'))
	fieldPane.add(selectAORbutton)
	labelPane.add(JLabel(''))
	selectAORbutton.addActionListener(buttonClickedListener("selectAOR"))
	createButton(bottomPane, 'computeBoundaryContours', 'Compute boundary contours', 'Interpolate the boundary contours (inner and outer) across the scanStack slices to compute the boundary contours on all slices.')
	createButton(bottomPane, 'showMasks', 'Show masks', 'Show the mask of the annulus, i.e. the space enclosed between the boundary contours (image with white in the annulus and zero elsewhere).')

	return packFrame(frame, labelPane, fieldPane, bottomPane, 'computeBoundaryContours')
def createInterpolateContoursPane():
	global allFrames, interpolateContoursProperties, scanStack
	frame = JFrame("3: Intermediate contours")
	frame.setName('interpolateContoursPane')
	fieldPane = JPanel(GridLayout(0,1))
	labelPane = JPanel(GridLayout(0,1))
	bottomPane = JPanel(GridLayout(0,1))

	createTextBox(fieldPane, labelPane, interpolateContoursProperties, "depthLevels", "Number of contours", minV=2, \
	labelToolTip='Number of contours (including the boundaries) to compute the mapping upon.', \
	txtToolTip='Positive integer greater or equal to 2. A value of 5 to 10 is plausible.')
	createTextBox(fieldPane, labelPane, interpolateContoursProperties, "spanSearchWin", "Search window", minV=3,\
	labelToolTip='Number of points across the boundary contours to consider when computing the spans. Relative to the contour length, this defines the immediate neighbourhood of points that affect the span shape.', \
	txtToolTip="Positive integer, greater than 2 and smaller than the contour length. Reduce if the spans do not reach the outer contour. Increase if the span shape does not capture the sample's shape.")
	createComboBox(fieldPane, labelPane, interpolateContoursProperties, "spanSearchWinClass", '', ['Narrow', 'Intermediate', 'Wide'], \
	txtToolTip='Select Narrow, Intermediate or Wide window for incremental locality for the computation of each span, i.e. Narrow only considers points within the intermediate neighbourhood, while wide considers a much broader area.')
	createEmptyLabel(fieldPane, labelPane)
	createTextBox(fieldPane, labelPane, interpolateContoursProperties, "maxSpanLen", "Max span length", \
	labelToolTip='The maximum allowed length in pixels of the spans (electric field lines) beginning from the inner contour and reaching the outer contour.', \
	txtToolTip='Positive integer compareable but (1.5 times) larger than the thickest part of the sample in pixels.')
	createTextBox(fieldPane, labelPane, interpolateContoursProperties, "minSpanVariance", "Span straightness", \
	labelToolTip='The minimum allowed variance of the points making up the span. Increase if the spans curl into a single point. Decrease if the spans are too straight or no spans appear.', \
	txtToolTip='Positive integer. A value of 20 to 100 is plausible.')
	createTextBox(fieldPane, labelPane, interpolateContoursProperties, "rigidity", "Span rigidity", charType='float', minV=0, \
	labelToolTip='Defines how stiff the spans are. Reduce to allow the spans to be more curvy. Increase for straighter spans.', \
	txtToolTip='Positive real number. A value of 1 is plausible.')
	createTextBox(fieldPane, labelPane, interpolateContoursProperties, "spanStep", "Span step", minV=2, \
	labelToolTip='The length of individual straight line segments steps that make up each span. Decrease for smoother spans. Increase only if necessary to reduce computational effort when the max span length is too large.', \
	txtToolTip='Positive integer greater than 1. A value of 2 is advisable.')
	createEmptyLabel(fieldPane, labelPane)
	createCheckBox(fieldPane, labelPane, interpolateContoursProperties, "saveContours", "Save contours")
	createCheckBox(fieldPane, labelPane, dynamicProperties, "showContours", "Show contours", \
	labelToolTip='Overlay the currently computed (boundary and intermediate) contours on the scanStack. This disgards the spans and contours shown on the active slices when clicking test parameters.')
	createButton(bottomPane, 'testInterpolationParams', 'Test parameters', 'Computes the spans and intermediate contours for the active slice only. Use this to validate the selected parameters before computing the contours of all slices.')
	createButton(bottomPane, 'interpolateContours', 'Compute contours', 'Computes the (spans and) intermediate contours for all slices. Use test parameters to validate the selected parameters before this.')

	return packFrame(frame, labelPane, fieldPane, bottomPane, 'computeCyclorama')
def computeMappingPane():
	global allFrames, computeMappingProperties, computeCycloramaProperties
	frame = JFrame("4: Mapping and cyclorama")
	frame.setName('mappingPane')
	fieldPane = JPanel(GridLayout(0,1))
	labelPane = JPanel(GridLayout(0,1))
	bottomPane = JPanel(GridLayout(0,1))

	createTextBox(fieldPane, labelPane, computeMappingProperties, "interpolatedDepthLevels", "Number of depth levels", minV=0, \
	labelToolTip='The number of slices of the 3D cyclorama. That is the total number of contours resulting after mapping and interpolating the contours computed beforehand.', \
	txtToolTip='Non-negative integer up to the maximum thickness of the sample in pixels. Set to 0 to avoid interpolation.')
	createComboBox(fieldPane, labelPane, dynamicProperties, "cycloramaMode", "Mapping mode", ["Linear", "DTW"], \
	labelToolTip='Defines the type of deformation to impose in order to pair points on the deformed tube to poins on the 3D cyclorama (rectangular cuboid).', \
	txtToolTip='The linear mode distributes the deformation evenly throughout the 3D cyclorama. The dynamic time warping (DTW) mode collects the deformation on a single column.')
	createEmptyLabel(fieldPane, labelPane)
	createCheckBox(fieldPane, labelPane, computeMappingProperties, "saveMapping", "Save mapping")
	createCheckBox(fieldPane, labelPane, computeCycloramaProperties, "saveCyclorama", "Save cyclorama")
	createButton(bottomPane, 'computeCyclorama', 'Compute mapping and cyclorama', 'Computes the mapping between the contours (defined on the deformed tube) and the cuboid that is the 3D cyclorama. Then it interpolates the mapped space with the to create the selected depth levels.')

	return packFrame(frame, labelPane, fieldPane, bottomPane, 'computeMapping')
def analyzePane():
	global allFrames, analyzeProperties
	frame = JFrame("5: Measurements")
	frame.setName('analyzePane')
	fieldPane = JPanel(GridLayout(0,1))
	labelPane = JPanel(GridLayout(0,1))
	bottomPane = JPanel(GridLayout(0,1))

	createTextBox(fieldPane, labelPane, analyzeProperties, "volHistBinsWidth", "Volume hist bins width", noColumns=5, \
	labelToolTip='The width in image calibration unit of each of the bins used to make the volume histogram.', \
	txtToolTip='Positive integer. Reduce for more grannularity.')
	createTextBox(fieldPane, labelPane, analyzeProperties, "thickHistBinsWidth", "Thickness hist bins width", noColumns=5, \
	labelToolTip='The width in image calibration unit of each of the bins used to make the local thickness histogram.', \
	txtToolTip='Positive integer. Reduce for more grannularity.')
	createEmptyLabel(fieldPane, labelPane)
	createCheckBox(fieldPane, labelPane, analyzeProperties, "saveVolumes", "Save volumes")
	createCheckBox(fieldPane, labelPane, analyzeProperties, "saveThickness", "Save local thickness")
	createButton(bottomPane, 'computeVolume', 'Slice volumes histogram', 'Shows a histogram of the volumes of the annulus enclosed by the boundary contours on each slice.')
	createButton(bottomPane, 'computeThickness', 'Local thickness histogram', 'Shows a histogram of the local thicknesss (length of spans) computed on each slice.')

	return packFrame(frame, labelPane, fieldPane, bottomPane, 'analyze')

def createPanes():
	global allFrames
	allFrames = []
	allFrames.append(createLoadScanStackPane())
	allFrames.append(createBoundaryContoursPane())
	allFrames.append(createInterpolateContoursPane())
	allFrames.append(computeMappingPane())
	allFrames.append(analyzePane())
# TODO: Consider replacing refreshpanes with value updating without discarding the frame
def refreshPanes():
	global allFrames
	for f in allFrames:
		f.dispose()
	allFrames = []
	allFrames.append(createLoadScanStackPane())
	allFrames.append(createBoundaryContoursPane())
	allFrames.append(createInterpolateContoursPane())
	allFrames.append(computeMappingPane())
	allFrames.append(analyzePane())

loadParameters(os.path.join(path, 'previousCycloramaParameters.txt'))
initialise()
createPanes()
