Skip to content

Commit 04aeed9

Browse files
committed
feat: allow drawing and clearing objects in a freehand region
1 parent 5c769c8 commit 04aeed9

9 files changed

+2369
-1001
lines changed

cellacdc/docs/source/tooltips.rst

+7
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@
237237
:height: 16px
238238
:width: 16px
239239

240+
.. |drawClearRegionAction| image:: https://raw.githubusercontent.com/SchmollerLab/Cell_ACDC/3dcf5611281c35e3cf8b7676ca7c00c9b17ee8e7/cellacdc/resources/icons/clear_freehand_region.svg
241+
:target: https://github.com/SchmollerLab/Cell_ACDC/blob/main/cellacdc/resources/icons/clear_freehand_region.svg
242+
:alt: drawClearRegionAction icon
243+
:height: 16px
244+
:width: 16px
245+
240246
.. |addDelPolyLineRoiAction| image:: https://raw.githubusercontent.com/SchmollerLab/Cell_ACDC/3dcf5611281c35e3cf8b7676ca7c00c9b17ee8e7/cellacdc/resources/icons/addDelPolyLineRoi.svg
241247
:target: https://github.com/SchmollerLab/Cell_ACDC/blob/main/cellacdc/resources/icons/addDelPolyLineRoi.svg
242248
:alt: addDelPolyLineRoiAction icon
@@ -431,6 +437,7 @@ Edit tools: Segmentation and tracking
431437
* Add custom poly-line deletion ROI. Every ID touched by the ROI will be automatically deleted.
432438
* Moving and reshaping the ROI will restore deleted IDs if they are not touched by it anymore.
433439
* To delete the ROI ``right-click on it --> remove``.
440+
* **Clear freehand region (** |drawClearRegionAction| **):** Draw a freehand region and clear all objects present in the region. Once activated, additional options will appear in a new toolbar.
434441
* **Delete bordering objects (** |delBorderObjAction| **):** Remove segmented objects touching the border of the image.
435442
* **Repeat tracking (** |repeatTrackingAction| **"Shift+T"):** Repeat tracking on current frame. Tracking method can be changed in ``Tracking --> Select real-time tracking algorithm``
436443
* **Manual tracking (** |manualTrackingButton| **"T"):** Select ID to track and right-click on an object to assign that ID.

cellacdc/gui.py

+125-14
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,10 @@ def gui_createToolBars(self):
20672067
)
20682068
self.addDelPolyLineRoiAction.roiType = 'polyline'
20692069

2070+
self.drawClearRegionAction = editToolBar.addWidget(
2071+
self.drawClearRegionButton
2072+
)
2073+
20702074
editToolBar.addAction(self.delBorderObjAction)
20712075

20722076
self.addDelRoiAction.toolbar = editToolBar
@@ -2896,6 +2900,15 @@ def gui_createControlsToolbar(self):
28962900
self.copyLostObjToolbar.setVisible(False)
28972901
self.controlToolBars.append(self.copyLostObjToolbar)
28982902

2903+
# Copy lost object contour toolbar
2904+
self.drawClearRegionToolbar = widgets.DrawClearRegionToolbar(
2905+
"Draw freehand region and clear objects controls", self
2906+
)
2907+
2908+
self.addToolBar(Qt.TopToolBarArea, self.drawClearRegionToolbar)
2909+
self.drawClearRegionToolbar.setVisible(False)
2910+
self.controlToolBars.append(self.drawClearRegionToolbar)
2911+
28992912
# Empty toolbar to avoid weird ranges on image when showing the
29002913
# other toolbars --> placeholder
29012914
placeHolderToolbar = widgets.ToolBar("Place holder", self)
@@ -3396,15 +3409,20 @@ def gui_createActions(self):
33963409
self.addDelRoiAction.roiType = 'rect'
33973410
self.addDelRoiAction.setIcon(QIcon(":addDelRoi.svg"))
33983411

3399-
34003412
self.addDelPolyLineRoiButton = QToolButton(self)
34013413
self.addDelPolyLineRoiButton.setCheckable(True)
34023414
self.addDelPolyLineRoiButton.setIcon(QIcon(":addDelPolyLineRoi.svg"))
34033415

34043416
self.checkableButtons.append(self.addDelPolyLineRoiButton)
34053417
self.LeftClickButtons.append(self.addDelPolyLineRoiButton)
3418+
3419+
self.drawClearRegionButton = QToolButton(self)
3420+
self.drawClearRegionButton.setCheckable(True)
3421+
self.drawClearRegionButton.setIcon(QIcon(":clear_freehand_region.svg"))
3422+
3423+
self.checkableButtons.append(self.drawClearRegionButton)
3424+
self.LeftClickButtons.append(self.drawClearRegionButton)
34063425

3407-
34083426
self.delBorderObjAction = QAction(self)
34093427
self.delBorderObjAction.setIcon(QIcon(":delBorderObj.svg"))
34103428

@@ -3743,6 +3761,7 @@ def gui_connectEditActions(self):
37433761
self.curvToolButton.toggled.connect(self.curvTool_cb)
37443762
self.wandToolButton.toggled.connect(self.wand_cb)
37453763
self.labelRoiButton.toggled.connect(self.labelRoi_cb)
3764+
self.drawClearRegionButton.toggled.connect(self.drawClearRegion_cb)
37463765
self.reInitCcaAction.triggered.connect(self.reInitCca)
37473766
self.moveLabelToolButton.toggled.connect(self.moveLabelButtonToggled)
37483767
self.editCcaToolAction.triggered.connect(
@@ -6040,7 +6059,7 @@ def gui_mouseDragEventImg1(self, event):
60406059
mode = str(self.modeComboBox.currentText())
60416060
if mode == 'Viewer':
60426061
return
6043-
6062+
60446063
posData = self.data[self.pos_i]
60456064
Y, X = self.get_2Dlab(posData.lab).shape
60466065
xdata, ydata = int(x), int(y)
@@ -6189,6 +6208,10 @@ def gui_mouseDragEventImg1(self, event):
61896208
self.labelRoiItem.setSize((w, h))
61906209
elif self.labelRoiIsFreeHandRadioButton.isChecked():
61916210
self.freeRoiItem.addPoint(xdata, ydata)
6211+
6212+
# Draw freehand clear region --> draw region
6213+
elif self.isMouseDragImg1 and self.drawClearRegionButton.isChecked():
6214+
self.freeRoiItem.addPoint(xdata, ydata)
61926215

61936216
# @exec_time
61946217
def fillHolesID(self, ID, sender='brush'):
@@ -7454,6 +7477,10 @@ def gui_mouseReleaseEventImg1(self, event):
74547477

74557478
self.clickedOnBud = False
74567479
self.BudMothTempLine.setData([], [])
7480+
7481+
elif self.isMouseDragImg1 and self.drawClearRegionButton.isChecked():
7482+
self.freeRoiItem.closeCurve()
7483+
self.clearObjsFreehandRegion()
74577484

74587485
def gui_clickedDelRoi(self, event, left_click, right_click):
74597486
posData = self.data[self.pos_i]
@@ -7587,7 +7614,7 @@ def gui_mousePressEventImg1(self, event):
75877614
)
75887615
findNextMotherButtonON = self.findNextMotherButton.isChecked()
75897616
unknownLineageButtonON = self.unknownLineageButton.isChecked()
7590-
7617+
drawClearRegionON = self.drawClearRegionButton.isChecked()
75917618

75927619
# Check if right-click on segment of polyline roi to add segment
75937620
segments = self.gui_getHoveredSegmentsPolyLineRoi()
@@ -7613,7 +7640,7 @@ def gui_mousePressEventImg1(self, event):
76137640
and not curvToolON and not eraserON and not rulerON
76147641
and not wandON and not polyLineRoiON and not labelRoiON
76157642
and not middle_click and not keepObjON and not separateON
7616-
and not manualBackgroundON
7643+
and not manualBackgroundON and not drawClearRegionON
76177644
and addPointsByClickingButton is None
76187645
)
76197646
if isPanImageClick:
@@ -7680,69 +7707,77 @@ def gui_mousePressEventImg1(self, event):
76807707
and not brushON and not dragImgLeft and not eraserON
76817708
and not polyLineRoiON and not labelRoiON
76827709
and addPointsByClickingButton is None
7683-
and not manualBackgroundON
7710+
and not manualBackgroundON and not canDrawClearRegion
76847711
)
76857712
canBrush = (
76867713
brushON and not curvToolON and not rulerON
76877714
and not dragImgLeft and not eraserON and not wandON
76887715
and not labelRoiON and not manualBackgroundON
7689-
and addPointsByClickingButton is None
7716+
and addPointsByClickingButton is None and not canDrawClearRegion
76907717
)
76917718
canErase = (
76927719
eraserON and not curvToolON and not rulerON
76937720
and not dragImgLeft and not brushON and not wandON
76947721
and not polyLineRoiON and not labelRoiON
76957722
and addPointsByClickingButton is None
7696-
and not manualBackgroundON
7723+
and not manualBackgroundON and not canDrawClearRegion
76977724
)
76987725
canRuler = (
76997726
rulerON and not curvToolON and not brushON
77007727
and not dragImgLeft and not brushON and not wandON
77017728
and not polyLineRoiON and not labelRoiON
77027729
and addPointsByClickingButton is None
7703-
and not manualBackgroundON
7730+
and not manualBackgroundON and not canDrawClearRegion
77047731
)
77057732
canWand = (
77067733
wandON and not curvToolON and not brushON
77077734
and not dragImgLeft and not brushON and not rulerON
77087735
and not polyLineRoiON and not labelRoiON
77097736
and addPointsByClickingButton is None
7710-
and not manualBackgroundON
7737+
and not manualBackgroundON and not canDrawClearRegion
77117738
)
77127739
canPolyLine = (
77137740
polyLineRoiON and not wandON and not curvToolON and not brushON
77147741
and not dragImgLeft and not brushON and not rulerON
77157742
and not labelRoiON and not manualBackgroundON
77167743
and addPointsByClickingButton is None
7744+
and not canDrawClearRegion
77177745
)
77187746
canLabelRoi = (
77197747
labelRoiON and not wandON and not curvToolON and not brushON
77207748
and not dragImgLeft and not brushON and not rulerON
77217749
and not polyLineRoiON and not keepObjON
77227750
and addPointsByClickingButton is None
7723-
and not manualBackgroundON
7751+
and not manualBackgroundON and not canDrawClearRegion
77247752
)
77257753
canKeep = (
77267754
keepObjON and not wandON and not curvToolON and not brushON
77277755
and not dragImgLeft and not brushON and not rulerON
77287756
and not polyLineRoiON and not labelRoiON
77297757
and addPointsByClickingButton is None
7730-
and not manualBackgroundON
7758+
and not manualBackgroundON and not canDrawClearRegion
77317759
)
77327760
canAddPoint = (
77337761
self.togglePointsLayerAction.isChecked()
77347762
and addPointsByClickingButton is not None and not wandON
77357763
and not curvToolON and not brushON
77367764
and not dragImgLeft and not brushON and not rulerON
77377765
and not polyLineRoiON and not labelRoiON and not keepObjON
7738-
and not manualBackgroundON
7766+
and not manualBackgroundON and not canDrawClearRegion
77397767
)
77407768
canAddManualBackgroundObj = (
77417769
manualBackgroundON and not wandON and not curvToolON and not brushON
77427770
and not dragImgLeft and not brushON and not rulerON
77437771
and not polyLineRoiON and not labelRoiON
77447772
and addPointsByClickingButton is None
7745-
and not keepObjON
7773+
and not keepObjON and not canDrawClearRegion
7774+
)
7775+
canDrawClearRegion = (
7776+
drawClearRegionON and not wandON and not curvToolON and not brushON
7777+
and not dragImgLeft and not brushON and not rulerON
7778+
and not labelRoiON and not manualBackgroundON
7779+
and addPointsByClickingButton is None
7780+
and not polyLineRoiON
77467781
)
77477782

77487783
# Enable dragging of the image window or the scalebar
@@ -7913,6 +7948,13 @@ def gui_mousePressEventImg1(self, event):
79137948
self.addClickedPoint(action, x, y, id)
79147949
self.drawPointsLayers(computePointsLayers=False)
79157950

7951+
elif left_click and canDrawClearRegion:
7952+
x, y = event.pos().x(), event.pos().y()
7953+
xdata, ydata = int(x), int(y)
7954+
self.freeRoiItem.addPoint(xdata, ydata)
7955+
7956+
self.isMouseDragImg1 = True
7957+
79167958
elif left_click and canRuler or canPolyLine:
79177959
x, y = event.pos().x(), event.pos().y()
79187960
xdata, ydata = int(x), int(y)
@@ -12638,6 +12680,7 @@ def connectLeftClickButtons(self):
1263812680
self.eraserButton.toggled.connect(self.Eraser_cb)
1263912681
self.wandToolButton.toggled.connect(self.wand_cb)
1264012682
self.labelRoiButton.toggled.connect(self.labelRoi_cb)
12683+
self.drawClearRegionButton.toggled.connect(self.drawClearRegion_cb)
1264112684
self.expandLabelToolButton.toggled.connect(self.expandLabelCallback)
1264212685
self.addDelPolyLineRoiButton.toggled.connect(self.addDelPolyLineRoi_cb)
1264312686
self.manualBackgroundButton.toggled.connect(self.manualBackground_cb)
@@ -12872,6 +12915,26 @@ def labelRoiTrangeCheckboxToggled(self, checked):
1287212915
self.labelRoiStartFrameNoSpinbox.setValue(posData.frame_i+1)
1287312916
self.labelRoiStopFrameNoSpinbox.setValue(posData.SizeT)
1287412917

12918+
def drawClearRegion_cb(self, checked):
12919+
posData = self.data[self.pos_i]
12920+
if checked:
12921+
self.disconnectLeftClickButtons()
12922+
self.uncheckLeftClickButtons(self.drawClearRegionButton)
12923+
self.connectLeftClickButtons()
12924+
12925+
self.drawClearRegionToolbar.setVisible(checked)
12926+
12927+
if not self.isSegm3D:
12928+
self.drawClearRegionToolbar.setZslicesControlEnabled(False)
12929+
return
12930+
12931+
if not checked:
12932+
return
12933+
12934+
self.drawClearRegionToolbar.setZslicesControlEnabled(
12935+
True, SizeZ=posData.SizeZ
12936+
)
12937+
1287512938
def labelRoi_cb(self, checked):
1287612939
posData = self.data[self.pos_i]
1287712940
if checked:
@@ -12936,6 +12999,54 @@ def labelRoi_cb(self, checked):
1293612999
self.ax1.removeItem(self.labelRoiItem)
1293713000
self.updateLabelRoiCircularCursor(None, None, False)
1293813001

13002+
def clearObjsFreehandRegion(self):
13003+
self.logger.info('Clearing objects inside freehand region...')
13004+
13005+
# Store undo state before modifying stuff
13006+
self.storeUndoRedoStates(False, storeImage=False, storeOnlyZoom=True)
13007+
13008+
posData = self.data[self.pos_i]
13009+
zRange = None
13010+
if self.isSegm3D:
13011+
z_slice = self.z_lab()
13012+
zRange = self.drawClearRegionToolbar.zRange(z_slice, posData.SizeZ)
13013+
13014+
regionSlice = self.freeRoiItem.slice(zRange=zRange)
13015+
mask = self.freeRoiItem.mask()
13016+
13017+
regionLab = posData.lab[..., *regionSlice].copy()
13018+
regionLab[..., ~mask] = 0
13019+
13020+
clearBorders = (
13021+
self.drawClearRegionToolbar
13022+
.clearOnlyEnclosedObjsRadioButton
13023+
.isChecked()
13024+
)
13025+
if clearBorders:
13026+
regionLab = skimage.segmentation.clear_border(regionLab)
13027+
13028+
regionRp = skimage.measure.regionprops(regionLab)
13029+
clearIDs = [obj.label for obj in regionRp]
13030+
13031+
if not clearIDs:
13032+
if clearBorders:
13033+
self.logger.warning(
13034+
'None of the objects in the freehand region are '
13035+
'fully enclosed'
13036+
)
13037+
else:
13038+
self.logger.warning(
13039+
'None of the objects are touching the freehand region'
13040+
)
13041+
return
13042+
13043+
self.deleteIDmiddleClick(clearIDs, False, False)
13044+
self.update_cca_df_deletedIDs(posData, clearIDs)
13045+
13046+
self.freeRoiItem.clear()
13047+
13048+
self.updateAllImages()
13049+
1293913050
def labelRoiWorkerFinished(self):
1294013051
self.logger.info('Magic labeller closed.')
1294113052
worker = self.labelRoiActiveWorkers.pop(-1)

0 commit comments

Comments
 (0)