diff --git a/detectionview.py b/detectionview.py index 92358361f76a959c33d9656c6859e2926b46e0a9..923d3a9e0dca49eeff97adeb150b44d90208d68e 100755 --- a/detectionview.py +++ b/detectionview.py @@ -275,7 +275,7 @@ class ParticleDetectionView(QtWidgets.QWidget): self.dataset = self.verifySeedpoints(dataset) self.img = img self.imgclip = 0,0,0,0 - self.seg = Segmentation(self.dataset) + self.seg = Segmentation(self.dataset, self) self.thread = None self.view = parent @@ -303,10 +303,12 @@ class ParticleDetectionView(QtWidgets.QWidget): group = QtWidgets.QGroupBox("Detection settings", self) grid = QtWidgets.QGridLayout() self.parameters = [] + checkBoxesToLink = {} + # create editable parameters: for i, p in enumerate(self.seg.parlist): label, colstretch = None, 1 - if p.name == "points": + if p.name == "contrastCurve": paramui = HistWidget(lambda : self.seg.calculateHist(self.seg.convert2Gray(self.subimg)), self.seg.calculateHistFunction, self) valuefunc = makeValueLambda(paramui.value) @@ -316,6 +318,8 @@ class ParticleDetectionView(QtWidgets.QWidget): paramui.setChecked(p.value) valuefunc = makeValueLambda(paramui.isChecked) colstretch = 2 + if p.linkedParameter is not None: + checkBoxesToLink[paramui] = p.linkedParameter elif p.dtype == int or p.dtype == float: label = QtWidgets.QLabel(p.helptext, self) if p.dtype == int: @@ -334,18 +338,44 @@ class ParticleDetectionView(QtWidgets.QWidget): elif p.dtype is None: label = QtWidgets.QLabel(p.helptext, self) paramui = None + if paramui is not None: - self.parameters.append([paramui, p.name, valuefunc]) + self.parameters.append([paramui, p.name, valuefunc, None]) if colstretch == 1: grid.addWidget(paramui, i, 1, QtCore.Qt.AlignLeft) else: - grid.addWidget(paramui, i, 0, 1, 2, QtCore.Qt.AlignLeft) + grid.addWidget(paramui, i, 0, 1, 2, QtCore.Qt.AlignLeft) + + if label is not None: grid.addWidget(label, i, 0, QtCore.Qt.AlignLeft) + if p.show is True: pshow = QtWidgets.QPushButton(">", self) pshow.released.connect(makeShowLambda(p.name)) grid.addWidget(pshow, i, 2, QtCore.Qt.AlignRight) + + if paramui is not None: + self.parameters[-1][3] = pshow + + + + + #link checkboxes to other parameters: + def makeEnableLambda(checkbox, parameter): + return lambda: parameter.setEnabled(checkbox.isChecked()) + + for box in checkBoxesToLink: + linkedName = checkBoxesToLink[box] + for p in self.parameters: + if p[1] == linkedName: + p[0].setEnabled(box.isChecked()) + box.stateChanged.connect(makeEnableLambda(box, p[0])) #the actual control element + if p[3] is not None: #the "show" box, if present + box.stateChanged.connect(makeEnableLambda(box, p[3])) + p[3].setEnabled(box.isChecked()) + + label = QtWidgets.QLabel("Seed radius", self) grid.addWidget(label, i+1, 0, QtCore.Qt.AlignLeft) @@ -384,7 +414,7 @@ class ParticleDetectionView(QtWidgets.QWidget): def saveDetectParams(self, ds=None): if ds is not None: for param in self.parameters: - if param[1] == 'points': + if param[1] == 'contrastCurve': print(param[0].value()) try: # is it a spinbox or the histWidget? Read out the value ds.detectParams[param[1]] = param[0].value() @@ -535,7 +565,7 @@ class ParticleDetectionView(QtWidgets.QWidget): self.saveDetectParams(self.dataset) img = self.subimg.copy() kwargs = {} - for ui, name, valuefunc in self.parameters: + for ui, name, valuefunc, showbtn in self.parameters: kwargs[name] = valuefunc() self.seg.setParameters(**kwargs) seedradius = self.seedradiusedit.value() @@ -579,6 +609,9 @@ class ParticleDetectionView(QtWidgets.QWidget): self.pdetectsub.setEnabled(True) self.pclear.setEnabled(True) + def raiseWarning(self, warning): + QtWidgets.QMessageBox.critical(self, "Warning", warning) + @QtCore.pyqtSlot() def detectParticles(self): self.saveDetectParams(self.dataset) @@ -615,7 +648,7 @@ class ParticleDetectionView(QtWidgets.QWidget): if self.dataset is not None: seedpoints = self.dataset.seedpoints deletepoints = self.dataset.seeddeletepoints - for ui, name, valuefunc in self.parameters: + for ui, name, valuefunc, showbtn in self.parameters: kwargs[name] = valuefunc() seedradius = self.seedradiusedit.value() self.seg.setParameters(**kwargs) diff --git a/segmentation.py b/segmentation.py index 00edda256bcd9951a92779f9c2deb253470fe3cd..26f70ec452036f359fc2a5ea73ae0bebb2e53062 100644 --- a/segmentation.py +++ b/segmentation.py @@ -30,7 +30,7 @@ from random import random class Parameter(object): def __init__(self, name, dtype, value=None, minval=None, maxval=None, - decimals=0, stepsize=1, helptext=None, show=False): + decimals=0, stepsize=1, helptext=None, show=False, linkedParameter=None): self.name = name self.dtype = dtype self.value = value @@ -39,36 +39,50 @@ class Parameter(object): self.stepsize = stepsize self.helptext = helptext self.show = show + self.linkedParameter = linkedParameter class Segmentation(object): - def __init__(self, dataset=None): + def __init__(self, dataset=None, parent=None): self.cancelcomputation = False - if dataset is not None: - self.detectParams = dataset.detectParams - else: - self.detectParams = {'points': np.array([[50,0],[100,200],[200,255]]), - 'contrastcurve': True, + self.parent = parent + self.defaultParams = {'contrastCurve': np.array([[50,0],[100,200],[200,255]]), + 'activateContrastCurve': True, 'blurRadius': 9, - 'threshold': 0.2, + 'activateLowThresh': True, + 'lowThresh': 0.2, + 'activateUpThresh': False, + 'upThresh': 0.5, 'maxholebrightness': 0.5, - 'erodeconvexdefects': 0, +# 'erodeconvexdefects': 0, 'minparticlearea': 20, 'minparticledistance': 20, + 'closeBackground': True, 'measurefrac': 1, 'compactness': 0., 'seedRad': 3} - self.initialParameters() + if dataset is not None: + self.detectParams = dataset.detectParams + for key in self.defaultParams: + if key not in self.detectParams: + self.detectParams[key] = self.defaultParams[key] + else: + self.detectParams = self.defaultParams + self.initializeParameters() - def initialParameters(self): - parlist = [Parameter("points", np.ndarray, self.detectParams['points'], helptext="Curve contrast"), - Parameter("contrastcurve", np.bool, self.detectParams['contrastcurve'], helptext="Contrast curve", show=True), + def initializeParameters(self): + parlist = [Parameter("contrastCurve", np.ndarray, self.detectParams['contrastCurve'], helptext="Curve contrast"), + Parameter("activateContrastCurve", np.bool, self.detectParams['activateContrastCurve'], helptext="activate Contrast curve", show=True, linkedParameter='contrastCurve'), Parameter("blurRadius", int, self.detectParams['blurRadius'], 3, 99, 1, 2, helptext="Blur radius", show=True), - Parameter("threshold", float, self.detectParams['threshold'], .01, .9, 2, .02, helptext="Basic threshold", show=True), + Parameter("activateLowThresh", np.bool, self.detectParams['activateThresh2'], helptext="activate lower threshold", show=False, linkedParameter='lowThresh'), + Parameter("lowThresh", float, self.detectParams['lowThresh'], .01, .9, 2, .02, helptext="Lower threshold", show=True), + Parameter("activateUpThresh", np.bool, self.detectParams['activateThresh2'], helptext="activate upper threshold", show=False, linkedParameter='upThresh'), + Parameter("upThresh", float, self.detectParams['upThresh'], .01, 1.0, 2, .02, helptext="Upper threshold", show=True), Parameter("maxholebrightness", float, self.detectParams['maxholebrightness'], 0, 1, 2, 0.02, helptext="Close holes brighter than..", show = True), - Parameter("erodeconvexdefects", int, self.detectParams['erodeconvexdefects'], 0, 20, helptext="Erode convex defects", show=True), +# Parameter("erodeconvexdefects", int, self.detectParams['erodeconvexdefects'], 0, 20, helptext="Erode convex defects", show=True), #TODO: Consider removing it entirely. It is usually not used... Parameter("minparticlearea", int, self.detectParams['minparticlearea'], 10, 1000, 0, 50, helptext="Min. particle pixel area", show=False), Parameter("minparticledistance", int, self.detectParams['minparticledistance'], 10, 1000, 0, 5, helptext="Min. distance between particles", show=False), Parameter("measurefrac", float, self.detectParams['measurefrac'], 0, 1, 2, stepsize = 0.05, helptext="measure fraction of particles", show=False), + Parameter("closeBackground", np.bool, self.detectParams['closeBackground'], helptext="close holes in sure background", show=False), Parameter("sure_fg", None, helptext="Show sure foreground", show=True), Parameter("compactness", float, self.detectParams['compactness'], 0, 1, 2, 0.05, helptext="watershed compactness", show=False), Parameter("watershed", None, helptext="Show watershed markers", show=True), @@ -284,15 +298,16 @@ class Segmentation(object): # convert to gray image and do histrogram normalization gray = self.convert2Gray(img) print("gray") - if self.contrastcurve: - xi, arr = self.calculateHistFunction(self.points) + + if self.activateContrastCurve: + xi, arr = self.calculateHistFunction(self.contrastCurve) gray = arr[gray] print("contrast curve") if self.cancelcomputation: return None, None, None # return even if inactive! - if return_step=="contrastcurve": return gray, 0 + if return_step=="activateContrastCurve": return gray, 0 # image blur for noise-reduction blur = cv2.medianBlur(gray, self.blurRadius) @@ -303,11 +318,36 @@ class Segmentation(object): return None, None, None # thresholding - thresh = cv2.threshold(blur, int(255*self.threshold), 255, cv2.THRESH_BINARY)[1] - if return_step=="threshold": return thresh, 0 - print("threshold") - if self.cancelcomputation: + if self.activateLowThresh and not self.activateUpThresh: + thresh = cv2.threshold(blur, int(255*self.lowThresh), 255, cv2.THRESH_BINARY)[1] + if return_step=="lowThresh": return thresh, 0 + print("lower threshold") + if self.cancelcomputation: + return None, None, None + + elif self.activateLowThresh and self.activateUpThresh: + lowerLimit, upperLimit = np.round(self.lowThresh*255), np.round(self.upThresh*255) + thresh = np.zeros_like(blur) + thresh[np.where(np.logical_and(blur >= lowerLimit, blur <= upperLimit))] = 255 + + if return_step=="lowThresh" or return_step=="upThresh": return thresh, 0 + print("between threshold") + if self.cancelcomputation: + return None, None, None + + elif not self.activateLowThresh and self.activateUpThresh: + thresh = np.zeros_like(blur) + thresh[np.where(blur <= np.round(self.upThresh*255))] = 255 + if return_step=="upThresh": return thresh, 0 + print("upper threshold") + if self.cancelcomputation: + return None, None, None + else: #no checkbox checked + if self.parent is not None: + self.parent.raiseWarning('No thresholding method selected!\nAborted detection..') + print('NO THRESHOLDING SELECTED!') return None, None, None + #close holes darkter than self.max_brightness self.closeBrightHoles(thresh, blur, self.maxholebrightness) @@ -317,20 +357,20 @@ class Segmentation(object): if self.cancelcomputation: return None, None, None - if self.erodeconvexdefects>0: - erthresh = self.erodeConvexDefects(thresh, self.erodeconvexdefects) ##ist erthresh hier eigentlich notwendig? Können wir bei Bedarf nicht einfach "thresh" überschreiben, anstatt noch ein großes Bild in den Speicher zu laden? - else: - erthresh = thresh - print("erodeconvexdefects") - if self.cancelcomputation: - return None, None, None +# if self.erodeconvexdefects>0: #TODO: Consider removing +# thresh = self.erodeConvexDefects(thresh, self.erodeconvexdefects) ##ist erthresh hier eigentlich notwendig? Können wir bei Bedarf nicht einfach "thresh" überschreiben, anstatt noch ein großes Bild in den Speicher zu laden? +# else: +# erthresh = thresh +# print("erodeconvexdefects") +# if self.cancelcomputation: +# return None, None, None # return even if inactive! - if return_step=="erodeconvexdefects": - if self.erodeconvexdefects > 0: return erthresh, 0 - else: return thresh, 0 +# if return_step=="erodeconvexdefects": +# if self.erodeconvexdefects > 0: return thresh, 0 +# else: return thresh, 0 - dist_transform = cv2.distanceTransform(erthresh, cv2.DIST_L2,5) + dist_transform = cv2.distanceTransform(thresh, cv2.DIST_L2,5) print("distanceTransform") if self.cancelcomputation: return None, None, None @@ -339,13 +379,11 @@ class Segmentation(object): '''the peak_local_max function takes the min distance between peaks. Unfortunately, that means that individual particles smaller than that distance are consequently disregarded. Hence, we need a connectec_components approach''' - sure_fg = self.getSureForeground(erthresh, self.minparticledistance, self.minparticlearea) - - sure_bg = cv2.dilate(erthresh, np.ones((5, 5)), iterations = 1) - sure_bg = self.closeHoles(sure_bg) + sure_fg = self.getSureForeground(thresh, self.minparticledistance, self.minparticlearea) sure_bg = cv2.dilate(thresh, np.ones((5, 5)), iterations = 1) - sure_bg = self.closeHoles(sure_bg) + if self.closeBackground: + sure_bg = self.closeHoles(sure_bg) # modify sure_fg and sure_bg with seedpoints and deletepoints if len(deletepoints)>0: