diff --git a/dataset.py b/dataset.py index c86fa7badef946236754d133dd503ffa457d5a6d..6b49705fe7f67191c1443000131afbf0526e9e77 100644 --- a/dataset.py +++ b/dataset.py @@ -74,6 +74,18 @@ class DataSet(object): self.pshift = None # shift of raman scan position relative to image center self.seedpoints = np.array([]) self.seeddeletepoints = np.array([]) + self.detectParams = {'points': np.array([[50,0],[100,200],[200,255]]), + 'contrastcurve': True, + 'blurRadius': 9, + 'threshold': 0.2, + 'maxholebrightness': 0.5, + 'erodeconvexdefects': 0, + 'minparticlearea': 20, + 'minparticledistance': 20, + 'measurefrac': 1, + 'compactness': 0.1, + 'seedRad': 3} + self.ramanpoints = [] self.particlecontours = [] self.particlestats = [] @@ -104,8 +116,8 @@ class DataSet(object): from opticalscan import loadAndPasteImage # try to load png and check for detection contours - recreatefullimage = recreatefullimage or not os.path.exists(self.getLegacyImageName()) - if not recreatefullimage: + buggyimage = recreatefullimage + if not buggyimage and os.path.exists(self.getLegacyImageName()): img = cv2imread_fix(self.getLegacyImageName()) Nc = len(self.particlecontours) if Nc>0: @@ -113,12 +125,12 @@ class DataSet(object): contpixels = img[contour[:,0,1],contour[:,0,0]] if np.all(contpixels[:,1]==255) and np.all(contpixels[:,2]==0) \ and np.all(contpixels[:,0]==0): - recreatefullimage = True - if not recreatefullimage: + buggyimage = True + if not buggyimage: cv2imwrite_fix(self.getImageName(), img) del img - if recreatefullimage: + if buggyimage: print("recreating fullimage from grid data") imgdata = None zvalimg = None @@ -248,4 +260,12 @@ class DataSet(object): def save(self): saveData(self, self.fname) + + +if __name__ == '__main__': + dset = loadData(r'C:\Users\brandt\Desktop\20180723DemoTZW\20180723DemoTZW.pkl') + print(dset.detectParams) +# dset.seedpoints = np.array([]) +# dset.seeddeletepoints = np.array([]) +# dset.save() \ No newline at end of file diff --git a/detectionview.py b/detectionview.py index f78ae50407064836420f3102207b5dd92833eb64..590edf9751abca0b16b250e224c80539dbe86ff4 100755 --- a/detectionview.py +++ b/detectionview.py @@ -32,7 +32,10 @@ class HistWidget(QtWidgets.QWidget): super().__init__(parent) self.updateCallbacks(histcallback, curvecallback) - self.points = np.array([[50,0],[100,150],[200,255]]) + if parent.dataset is None: + self.points = np.array([[50,0],[100,220],[200,255]]) + else: + self.points = parent.dataset.detectParams['points'] self.fig = plt.Figure() self.canvas = FigureCanvasQTAgg(self.fig) @@ -151,12 +154,12 @@ class ImageView(QtWidgets.QLabel): p0 = np.array([p0]) if len(self.seedpoints)>0: arr = np.array(self.seedpoints) - d = np.linalg.norm(arr-p0, axis=1) + d = np.linalg.norm(arr[:, :2]-p0, axis=1) ind = d>self.seedradius self.seedpoints = arr[ind,:].tolist() if len(self.seeddeletepoints)>0: arr = np.array(self.seeddeletepoints) - d = np.linalg.norm(arr-p0, axis=1) + d = np.linalg.norm(arr[:, :2]-p0, axis=1) ind = d>self.seedradius self.seeddeletepoints = arr[ind,:].tolist() @@ -170,9 +173,9 @@ class ImageView(QtWidgets.QLabel): self.drag = "add" p0 = event.pos() if self.drag=="add": - self.seedpoints.append([p0.x(),p0.y()]) + self.seedpoints.append([p0.x(),p0.y(),self.seedradius]) elif self.drag=="delete": - self.seeddeletepoints.append([p0.x(),p0.y()]) + self.seeddeletepoints.append([p0.x(),p0.y(),self.seedradius]) else: self.removeSeeds([p0.x(),p0.y()]) @@ -183,9 +186,9 @@ class ImageView(QtWidgets.QLabel): if self.drag: p0 = event.pos() if self.drag=="add": - self.seedpoints.append([p0.x(),p0.y()]) + self.seedpoints.append([p0.x(),p0.y(),self.seedradius]) elif self.drag=="delete": - self.seeddeletepoints.append([p0.x(),p0.y()]) + self.seeddeletepoints.append([p0.x(),p0.y(),self.seedradius]) else: self.removeSeeds([p0.x(),p0.y()]) self.update() @@ -205,6 +208,10 @@ class ImageView(QtWidgets.QLabel): self.overlay = None def updateSeedPoints(self, seedpoints=[], seeddeletepoints=[]): +# if len(seedpoints) > 0 and len(self.seedpoints) > 0: +# print(seedpoints[0, :], self.seedpoints[0, :]) +# else: +# print('else...', len(seedpoints), len(self.seedpoints)) self.seedpoints = seedpoints self.seeddeletepoints = seeddeletepoints @@ -231,6 +238,7 @@ class ImageView(QtWidgets.QLabel): self.overlay = pix def paintEvent(self, event): + painter = QtGui.QPainter(self) painter.drawPixmap(0, 0, self.pixmap()) painter.setOpacity(self.alpha) @@ -247,16 +255,16 @@ class ImageView(QtWidgets.QLabel): painter.drawEllipse(p[0]-2, p[1]-2, 5, 5) if self.showseedpoints: - r = self.seedradius - painter.setPen(QtCore.Qt.magenta) - painter.setBrush(QtCore.Qt.magenta) - for p in self.seeddeletepoints: - painter.drawEllipse(p[0]-r, p[1]-r, 2*r, 2*r) - painter.setPen(QtCore.Qt.white) painter.setBrush(QtCore.Qt.white) for p in self.seedpoints: - painter.drawEllipse(p[0]-r, p[1]-r, 2*r, 2*r) + painter.drawEllipse(p[0]-p[2], p[1]-p[2], 2*p[2], 2*p[2]) + + painter.setPen(QtCore.Qt.magenta) #I think it is more useful when the deletpoints override the seedpoints + painter.setBrush(QtCore.Qt.magenta) + + for p in self.seeddeletepoints: + painter.drawEllipse(p[0]-p[2], p[1]-p[2], 2*p[2], 2*p[2]) class ParticleDetectionView(QtWidgets.QWidget): imageUpdate = QtCore.pyqtSignal(name='imageUpdate') @@ -264,10 +272,10 @@ class ParticleDetectionView(QtWidgets.QWidget): def __init__(self, img, dataset, parent=None): super().__init__(parent, QtCore.Qt.Window) - self.dataset = dataset + self.dataset = self.verifySeedpoints(dataset) self.img = img self.imgclip = 0,0,0,0 - self.seg = Segmentation() + self.seg = Segmentation(self.dataset) self.thread = None vbox = QtWidgets.QVBoxLayout() @@ -294,6 +302,7 @@ class ParticleDetectionView(QtWidgets.QWidget): group = QtWidgets.QGroupBox("Detection settings", self) grid = QtWidgets.QGridLayout() self.parameters = [] + #create editable parameters: for i, p in enumerate(self.seg.parlist): label, colstretch = None, 1 if p.name == "points": @@ -370,7 +379,38 @@ class ParticleDetectionView(QtWidgets.QWidget): self.setLayout(hbox) self.setWindowTitle("Particle Detection") + + def saveDetectParams(self, ds=None): + if ds is not None: + for param in self.parameters: + if param[1] == 'points': + print(param[0].value()) + try: #is it a spinbox or the histWidget? Read out the value + ds.detectParams[param[1]] = param[0].value() + except: #otherwise checkbox -> take its state + ds.detectParams[param[1]] = param[0].isChecked() + ds.detectParams['seedRad'] = self.seedradiusedit.value() + ds.save() + + def verifySeedpoints(self, dataset): + seedpoints = dataset.seedpoints + seeddeletepoints = dataset.seeddeletepoints + + if len(seedpoints) > 0: #points are present + if seedpoints.shape[1] == 2: #old entries with only x,y coordinates + radii = np.ones((seedpoints.shape[0], 1))*3 + dataset.seedpoints = np.hstack((seedpoints, radii)) + else: + dataset.seedpoints = np.array([]) + if len(seeddeletepoints) > 0: #points are present + if seeddeletepoints.shape[1] == 2: #old entries with only x,y coordinates + radii = np.ones((seeddeletepoints.shape[0], 1))*3 + dataset.seeddeletepoints = np.hstack((seeddeletepoints, radii)) + else: + dataset.seeddeletepoints = np.array([]) + return dataset + @QtCore.pyqtSlot() def seedChanged(self): seedradius = self.seedradiusedit.value() @@ -380,20 +420,21 @@ class ParticleDetectionView(QtWidgets.QWidget): ind &= (arr0[:,1]>n1-seedradius)&(arr0[:,1]<=n2+seedradius) arr0 = arr0[~ind,:] else: - arr0 = arr0.reshape(0,2) + arr0 = arr0.reshape(0,3) + if arr1.shape[0]>0: - arr1 += p0 + arr1[:,:2] += p0 else: - arr1 = arr1.reshape(0,2) + arr1 = arr1.reshape(0,3) return np.concatenate((arr0, arr1), axis=0) if self.dataset is not None: n1,n2,m1,m2 = self.imgclip p0 = np.array([[m1,n1]], dtype=np.int32) - self.dataset.seedpoints = clipdata(self.dataset.seedpoints, np.array(self.imglabel.seedpoints, dtype=np.int32), p0, n1, n2, m1, m2) + self.dataset.seeddeletepoints = clipdata(self.dataset.seeddeletepoints, np.array(self.imglabel.seeddeletepoints, dtype=np.int32), p0, n1, n2, m1, m2) @@ -402,23 +443,35 @@ class ParticleDetectionView(QtWidgets.QWidget): @QtCore.pyqtSlot() def updateImageSeeds(self): if self.dataset is not None: - seedradius = self.seedradiusedit.value() +# seedradius = self.seedradiusedit.value() n1,n2,m1,m2 = self.imgclip p0 = np.array([[m1,n1]], dtype=np.int32) seedpoints = [] seeddeletepoints = [] + arr1 = self.dataset.seedpoints - if arr1.shape[0]>0: - ind = (arr1[:,0]>m1-seedradius)&(arr1[:,0]<=m2+seedradius) - ind &= (arr1[:,1]>n1-seedradius)&(arr1[:,1]<=n2+seedradius) - if np.any(ind): - seedpoints = (arr1[ind,:]-p0).tolist() + #what seeds are actually in image view? + for point in arr1: #Josef says: I replaced the commented logic with the one right here below, as the old one somehow did not work.... The for-loop might become slow at some point?? + if point[0] > (m1-point[2]) and point[0] <= (m2+point[2]) and point[1] > (n1-point[2]) and point[1] <= (n2+point[2]): + seedpoints.append([point[0] - p0[0][0], point[1] - p0[0][1], point[2]]) +# if arr1.shape[0]>0: +# ind = (arr1[:,0]>m1-seedradius)&(arr1[:,0]<=m2+seedradius) +# ind &= (arr1[:,1]>n1-seedradius)&(arr1[:,1]<=n2+seedradius) +# if np.any(ind): +# arr1[ind, :2] -= p0 +# seedpoints = arr1.tolist() +# arr2 = self.dataset.seeddeletepoints - if arr2.shape[0]>0: - ind = (arr2[:,0]>m1-seedradius)&(arr2[:,0]<=m2+seedradius) - ind &= (arr2[:,1]>n1-seedradius)&(arr2[:,1]<=n2+seedradius) - if np.any(ind): - seeddeletepoints = (arr2[ind,:]-p0).tolist() + for point in arr2: #Josef says: I replaced the commented logic with the one right here below, as the old one somehow did not work.... The for-loop might become slow at some point?? + if point[0] > (m1-point[2]) and point[0] <= (m2+point[2]) and point[1] > (n1-point[2]) and point[1] <= (n2+point[2]): + seeddeletepoints.append([point[0] - p0[0][0], point[1] - p0[0][1], point[2]]) +# if arr2.shape[0]>0: +# ind = (arr2[:,0]>m1-seedradius)&(arr2[:,0]<=m2+seedradius) +# ind &= (arr2[:,1]>n1-seedradius)&(arr2[:,1]<=n2+seedradius) +# if np.any(ind): +# arr2[ind, :2] -= p0 +# seeddeletepoints = arr2.tolist() + self.imglabel.updateSeedPoints(seedpoints, seeddeletepoints) def closeEvent(self, event): @@ -435,6 +488,12 @@ class ParticleDetectionView(QtWidgets.QWidget): dp = self.p0-event.pos() self.lastmove = (self.lastcenter[0]+dp.x(),self.lastcenter[1]+dp.y()) self.setImageCenter(self.lastmove) + + def wheelEvent(self, event): + if event.angleDelta().y() > 0: + self.seedradiusedit.setValue(self.seedradiusedit.value()+1) + else: + self.seedradiusedit.setValue(self.seedradiusedit.value()-1) def mouseReleaseEvent(self, event): if self.drag: @@ -486,6 +545,7 @@ class ParticleDetectionView(QtWidgets.QWidget): self.updateImageSeeds() def detectShow(self, showname): + self.saveDetectParams(self.dataset) img = self.subimg.copy() kwargs = {} for ui, name, valuefunc in self.parameters: @@ -534,6 +594,7 @@ class ParticleDetectionView(QtWidgets.QWidget): @QtCore.pyqtSlot() def detectParticles(self): + self.saveDetectParams(self.dataset) if self.thread is not None and self.thread.is_alive(): self.cancelThread() return @@ -596,7 +657,8 @@ if __name__ == "__main__": from dataset import DataSet from time import time - fname = r"D:\Projekte\Mikroplastik\Waferreinigung_Kerzenfilter\WaferMilliQH118\fullimage.tif" +# fname = r"D:\Projekte\Mikroplastik\Waferreinigung_Kerzenfilter\WaferMilliQH118\fullimage.tif" + fname = r"C:\Users\brandt\Desktop\20180723DemoTZW\fullimage.tif" app = QtWidgets.QApplication(sys.argv) t2 = time() @@ -605,8 +667,8 @@ if __name__ == "__main__": print("OpenCV read:", t3-t2) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - view = ParticleDetectionView(img, None) - ds = DataSet("dummy") + ds = DataSet("dummy") + view = ParticleDetectionView(img, ds, None) view.setDataSet(ds) view.show() app.exec_() \ No newline at end of file diff --git a/segmentation.py b/segmentation.py index 8b111a8f4368c669643be218278da428097740e6..2ca9d98a1faacf240edcff37b59fdfcfc55c53be 100644 --- a/segmentation.py +++ b/segmentation.py @@ -41,22 +41,36 @@ class Parameter(object): self.show = show class Segmentation(object): - def __init__(self): + def __init__(self, dataset=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, + 'blurRadius': 9, + 'threshold': 0.2, + 'maxholebrightness': 0.5, + 'erodeconvexdefects': 0, + 'minparticlearea': 20, + 'minparticledistance': 20, + 'measurefrac': 1, + 'compactness': 0.1, + 'seedRad': 3} self.initialParameters() def initialParameters(self): - parlist = [Parameter("points", np.ndarray, np.array([[20,0],[50,100],[200,255]]), helptext="Curve contrast"), - Parameter("contrastcurve", np.bool, True, helptext="Contrast curve", show=True), - Parameter("blurRadius", int, 9, 3, 99, 1, 2, helptext="Blur radius", show=True), - Parameter("threshold", float, .2, .01, .9, 2, .02, helptext="Basic threshold", show=True), - Parameter("maxholebrightness", float, 0.5, 0, 1, 2, 0.02, helptext="Close holes brighter than..", show = True), - Parameter("erodeconvexdefects", int, 0, 0, 20, helptext="Erode convex defects", show=True), - Parameter("minparticlearea", int, 20, 10, 1000, 0, 50, helptext="Min. particle pixel area", show=False), - Parameter("minparticledistance", int, 20, 10, 1000, 0, 5, helptext="Min. distance between particles", show=False), - Parameter("measurefrac", float, 1, 0, 1, 2, stepsize = 0.05, helptext="measure fraction of particles", show=False), + parlist = [Parameter("points", np.ndarray, self.detectParams['points'], helptext="Curve contrast"), + Parameter("contrastcurve", np.bool, self.detectParams['contrastcurve'], helptext="Contrast curve", show=True), + 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("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("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("sure_fg", None, helptext="Show sure foreground", show=True), - Parameter("compactness", float, 0.1, 0, 1, 2, 0.05, helptext="watershed compactness", show=False), + 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), ] # make each parameter accessible via self.name @@ -279,10 +293,6 @@ class Segmentation(object): if return_step=="contrastcurve": return gray, 0 # image blur for noise-reduction - if self.blurRadius%2 != 1: - self.blurRadius += 1 - print('blur Radius was an even number, incremented blur Radius by 1') - blur = cv2.medianBlur(gray, self.blurRadius) blur = np.uint8(blur*(255/blur.max())) if return_step=="blurRadius": return blur, 0 @@ -330,27 +340,26 @@ class Segmentation(object): sure_fg = self.getSureForeground(erthresh, self.minparticledistance, self.minparticlearea) - # modify sure_fg with seedpoints and deletepoints + sure_bg = cv2.dilate(erthresh, np.ones((5, 5)), iterations = 1) + sure_bg = self.closeHoles(sure_bg) + + sure_bg = cv2.dilate(thresh, np.ones((5, 5)), iterations = 1) + sure_bg = self.closeHoles(sure_bg) + + # modify sure_fg and sure_bg with seedpoints and deletepoints if len(deletepoints)>0: h, w = sure_fg.shape[:2] mask = np.zeros((h+2, w+2), np.uint8) for p in np.int32(deletepoints): - if p[0] >= 0 and p[1] >= 0: - cv2.floodFill(sure_fg, mask, tuple(p), 0) - else: - print('skipped del point at {}'.format(p)) - for p in np.int32(deletepoints): - cv2.circle(sure_fg, tuple(p), int(seedradius), 0, -1) + if p[0] > 0 and p[1] > 0: + cv2.floodFill(sure_fg, mask, tuple([p[0], p[1]]), 0) for p in np.int32(seedpoints): - cv2.circle(sure_fg, tuple(p), int(seedradius), 1, -1) - - sure_bg = cv2.dilate(erthresh, np.ones((5, 5)), iterations = 1) - sure_bg = self.closeHoles(sure_bg) - print("sure_fg, sure_bg") + cv2.circle(sure_fg, tuple([p[0], p[1]]), int(p[2]), 1, -1) + for p in np.int32(deletepoints): + cv2.circle(sure_fg, tuple([p[0], p[1]]), int(p[2]), 0, -1) + cv2.circle(sure_bg, tuple([p[0], p[1]]), int(p[2]), 0, -1) - sure_bg = cv2.dilate(thresh, np.ones((5, 5)), iterations = 1) - sure_bg = self.closeHoles(sure_bg) print("sure_fg, sure_bg") if self.cancelcomputation: return None, None, None