Skip to content

Commit

Permalink
feat: allow drawing and clearing objects in a freehand region
Browse files Browse the repository at this point in the history
  • Loading branch information
ElpadoCan committed Feb 11, 2025
1 parent 5c769c8 commit 04aeed9
Show file tree
Hide file tree
Showing 9 changed files with 2,369 additions and 1,001 deletions.
7 changes: 7 additions & 0 deletions cellacdc/docs/source/tooltips.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@
:height: 16px
:width: 16px

.. |drawClearRegionAction| image:: https://raw.githubusercontent.com/SchmollerLab/Cell_ACDC/3dcf5611281c35e3cf8b7676ca7c00c9b17ee8e7/cellacdc/resources/icons/clear_freehand_region.svg
:target: https://github.com/SchmollerLab/Cell_ACDC/blob/main/cellacdc/resources/icons/clear_freehand_region.svg
:alt: drawClearRegionAction icon
:height: 16px
:width: 16px

.. |addDelPolyLineRoiAction| image:: https://raw.githubusercontent.com/SchmollerLab/Cell_ACDC/3dcf5611281c35e3cf8b7676ca7c00c9b17ee8e7/cellacdc/resources/icons/addDelPolyLineRoi.svg
:target: https://github.com/SchmollerLab/Cell_ACDC/blob/main/cellacdc/resources/icons/addDelPolyLineRoi.svg
:alt: addDelPolyLineRoiAction icon
Expand Down Expand Up @@ -431,6 +437,7 @@ Edit tools: Segmentation and tracking
* Add custom poly-line deletion ROI. Every ID touched by the ROI will be automatically deleted.
* Moving and reshaping the ROI will restore deleted IDs if they are not touched by it anymore.
* To delete the ROI ``right-click on it --> remove``.
* **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.
* **Delete bordering objects (** |delBorderObjAction| **):** Remove segmented objects touching the border of the image.
* **Repeat tracking (** |repeatTrackingAction| **"Shift+T"):** Repeat tracking on current frame. Tracking method can be changed in ``Tracking --> Select real-time tracking algorithm``
* **Manual tracking (** |manualTrackingButton| **"T"):** Select ID to track and right-click on an object to assign that ID.
Expand Down
139 changes: 125 additions & 14 deletions cellacdc/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,10 @@ def gui_createToolBars(self):
)
self.addDelPolyLineRoiAction.roiType = 'polyline'

self.drawClearRegionAction = editToolBar.addWidget(
self.drawClearRegionButton
)

editToolBar.addAction(self.delBorderObjAction)

self.addDelRoiAction.toolbar = editToolBar
Expand Down Expand Up @@ -2896,6 +2900,15 @@ def gui_createControlsToolbar(self):
self.copyLostObjToolbar.setVisible(False)
self.controlToolBars.append(self.copyLostObjToolbar)

# Copy lost object contour toolbar
self.drawClearRegionToolbar = widgets.DrawClearRegionToolbar(
"Draw freehand region and clear objects controls", self
)

self.addToolBar(Qt.TopToolBarArea, self.drawClearRegionToolbar)
self.drawClearRegionToolbar.setVisible(False)
self.controlToolBars.append(self.drawClearRegionToolbar)

# Empty toolbar to avoid weird ranges on image when showing the
# other toolbars --> placeholder
placeHolderToolbar = widgets.ToolBar("Place holder", self)
Expand Down Expand Up @@ -3396,15 +3409,20 @@ def gui_createActions(self):
self.addDelRoiAction.roiType = 'rect'
self.addDelRoiAction.setIcon(QIcon(":addDelRoi.svg"))


self.addDelPolyLineRoiButton = QToolButton(self)
self.addDelPolyLineRoiButton.setCheckable(True)
self.addDelPolyLineRoiButton.setIcon(QIcon(":addDelPolyLineRoi.svg"))

self.checkableButtons.append(self.addDelPolyLineRoiButton)
self.LeftClickButtons.append(self.addDelPolyLineRoiButton)

self.drawClearRegionButton = QToolButton(self)
self.drawClearRegionButton.setCheckable(True)
self.drawClearRegionButton.setIcon(QIcon(":clear_freehand_region.svg"))

self.checkableButtons.append(self.drawClearRegionButton)
self.LeftClickButtons.append(self.drawClearRegionButton)


self.delBorderObjAction = QAction(self)
self.delBorderObjAction.setIcon(QIcon(":delBorderObj.svg"))

Expand Down Expand Up @@ -3743,6 +3761,7 @@ def gui_connectEditActions(self):
self.curvToolButton.toggled.connect(self.curvTool_cb)
self.wandToolButton.toggled.connect(self.wand_cb)
self.labelRoiButton.toggled.connect(self.labelRoi_cb)
self.drawClearRegionButton.toggled.connect(self.drawClearRegion_cb)
self.reInitCcaAction.triggered.connect(self.reInitCca)
self.moveLabelToolButton.toggled.connect(self.moveLabelButtonToggled)
self.editCcaToolAction.triggered.connect(
Expand Down Expand Up @@ -6040,7 +6059,7 @@ def gui_mouseDragEventImg1(self, event):
mode = str(self.modeComboBox.currentText())
if mode == 'Viewer':
return

posData = self.data[self.pos_i]
Y, X = self.get_2Dlab(posData.lab).shape
xdata, ydata = int(x), int(y)
Expand Down Expand Up @@ -6189,6 +6208,10 @@ def gui_mouseDragEventImg1(self, event):
self.labelRoiItem.setSize((w, h))
elif self.labelRoiIsFreeHandRadioButton.isChecked():
self.freeRoiItem.addPoint(xdata, ydata)

# Draw freehand clear region --> draw region
elif self.isMouseDragImg1 and self.drawClearRegionButton.isChecked():
self.freeRoiItem.addPoint(xdata, ydata)

# @exec_time
def fillHolesID(self, ID, sender='brush'):
Expand Down Expand Up @@ -7454,6 +7477,10 @@ def gui_mouseReleaseEventImg1(self, event):

self.clickedOnBud = False
self.BudMothTempLine.setData([], [])

elif self.isMouseDragImg1 and self.drawClearRegionButton.isChecked():
self.freeRoiItem.closeCurve()
self.clearObjsFreehandRegion()

def gui_clickedDelRoi(self, event, left_click, right_click):
posData = self.data[self.pos_i]
Expand Down Expand Up @@ -7587,7 +7614,7 @@ def gui_mousePressEventImg1(self, event):
)
findNextMotherButtonON = self.findNextMotherButton.isChecked()
unknownLineageButtonON = self.unknownLineageButton.isChecked()

drawClearRegionON = self.drawClearRegionButton.isChecked()

# Check if right-click on segment of polyline roi to add segment
segments = self.gui_getHoveredSegmentsPolyLineRoi()
Expand All @@ -7613,7 +7640,7 @@ def gui_mousePressEventImg1(self, event):
and not curvToolON and not eraserON and not rulerON
and not wandON and not polyLineRoiON and not labelRoiON
and not middle_click and not keepObjON and not separateON
and not manualBackgroundON
and not manualBackgroundON and not drawClearRegionON
and addPointsByClickingButton is None
)
if isPanImageClick:
Expand Down Expand Up @@ -7680,69 +7707,77 @@ def gui_mousePressEventImg1(self, event):
and not brushON and not dragImgLeft and not eraserON
and not polyLineRoiON and not labelRoiON
and addPointsByClickingButton is None
and not manualBackgroundON
and not manualBackgroundON and not canDrawClearRegion
)
canBrush = (
brushON and not curvToolON and not rulerON
and not dragImgLeft and not eraserON and not wandON
and not labelRoiON and not manualBackgroundON
and addPointsByClickingButton is None
and addPointsByClickingButton is None and not canDrawClearRegion
)
canErase = (
eraserON and not curvToolON and not rulerON
and not dragImgLeft and not brushON and not wandON
and not polyLineRoiON and not labelRoiON
and addPointsByClickingButton is None
and not manualBackgroundON
and not manualBackgroundON and not canDrawClearRegion
)
canRuler = (
rulerON and not curvToolON and not brushON
and not dragImgLeft and not brushON and not wandON
and not polyLineRoiON and not labelRoiON
and addPointsByClickingButton is None
and not manualBackgroundON
and not manualBackgroundON and not canDrawClearRegion
)
canWand = (
wandON and not curvToolON and not brushON
and not dragImgLeft and not brushON and not rulerON
and not polyLineRoiON and not labelRoiON
and addPointsByClickingButton is None
and not manualBackgroundON
and not manualBackgroundON and not canDrawClearRegion
)
canPolyLine = (
polyLineRoiON and not wandON and not curvToolON and not brushON
and not dragImgLeft and not brushON and not rulerON
and not labelRoiON and not manualBackgroundON
and addPointsByClickingButton is None
and not canDrawClearRegion
)
canLabelRoi = (
labelRoiON and not wandON and not curvToolON and not brushON
and not dragImgLeft and not brushON and not rulerON
and not polyLineRoiON and not keepObjON
and addPointsByClickingButton is None
and not manualBackgroundON
and not manualBackgroundON and not canDrawClearRegion
)
canKeep = (
keepObjON and not wandON and not curvToolON and not brushON
and not dragImgLeft and not brushON and not rulerON
and not polyLineRoiON and not labelRoiON
and addPointsByClickingButton is None
and not manualBackgroundON
and not manualBackgroundON and not canDrawClearRegion
)
canAddPoint = (
self.togglePointsLayerAction.isChecked()
and addPointsByClickingButton is not None and not wandON
and not curvToolON and not brushON
and not dragImgLeft and not brushON and not rulerON
and not polyLineRoiON and not labelRoiON and not keepObjON
and not manualBackgroundON
and not manualBackgroundON and not canDrawClearRegion
)
canAddManualBackgroundObj = (
manualBackgroundON and not wandON and not curvToolON and not brushON
and not dragImgLeft and not brushON and not rulerON
and not polyLineRoiON and not labelRoiON
and addPointsByClickingButton is None
and not keepObjON
and not keepObjON and not canDrawClearRegion
)
canDrawClearRegion = (
drawClearRegionON and not wandON and not curvToolON and not brushON
and not dragImgLeft and not brushON and not rulerON
and not labelRoiON and not manualBackgroundON
and addPointsByClickingButton is None
and not polyLineRoiON
)

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

elif left_click and canDrawClearRegion:
x, y = event.pos().x(), event.pos().y()
xdata, ydata = int(x), int(y)
self.freeRoiItem.addPoint(xdata, ydata)

self.isMouseDragImg1 = True

elif left_click and canRuler or canPolyLine:
x, y = event.pos().x(), event.pos().y()
xdata, ydata = int(x), int(y)
Expand Down Expand Up @@ -12638,6 +12680,7 @@ def connectLeftClickButtons(self):
self.eraserButton.toggled.connect(self.Eraser_cb)
self.wandToolButton.toggled.connect(self.wand_cb)
self.labelRoiButton.toggled.connect(self.labelRoi_cb)
self.drawClearRegionButton.toggled.connect(self.drawClearRegion_cb)
self.expandLabelToolButton.toggled.connect(self.expandLabelCallback)
self.addDelPolyLineRoiButton.toggled.connect(self.addDelPolyLineRoi_cb)
self.manualBackgroundButton.toggled.connect(self.manualBackground_cb)
Expand Down Expand Up @@ -12872,6 +12915,26 @@ def labelRoiTrangeCheckboxToggled(self, checked):
self.labelRoiStartFrameNoSpinbox.setValue(posData.frame_i+1)
self.labelRoiStopFrameNoSpinbox.setValue(posData.SizeT)

def drawClearRegion_cb(self, checked):
posData = self.data[self.pos_i]
if checked:
self.disconnectLeftClickButtons()
self.uncheckLeftClickButtons(self.drawClearRegionButton)
self.connectLeftClickButtons()

self.drawClearRegionToolbar.setVisible(checked)

if not self.isSegm3D:
self.drawClearRegionToolbar.setZslicesControlEnabled(False)
return

if not checked:
return

self.drawClearRegionToolbar.setZslicesControlEnabled(
True, SizeZ=posData.SizeZ
)

def labelRoi_cb(self, checked):
posData = self.data[self.pos_i]
if checked:
Expand Down Expand Up @@ -12936,6 +12999,54 @@ def labelRoi_cb(self, checked):
self.ax1.removeItem(self.labelRoiItem)
self.updateLabelRoiCircularCursor(None, None, False)

def clearObjsFreehandRegion(self):
self.logger.info('Clearing objects inside freehand region...')

# Store undo state before modifying stuff
self.storeUndoRedoStates(False, storeImage=False, storeOnlyZoom=True)

posData = self.data[self.pos_i]
zRange = None
if self.isSegm3D:
z_slice = self.z_lab()
zRange = self.drawClearRegionToolbar.zRange(z_slice, posData.SizeZ)

regionSlice = self.freeRoiItem.slice(zRange=zRange)
mask = self.freeRoiItem.mask()

regionLab = posData.lab[..., *regionSlice].copy()
regionLab[..., ~mask] = 0

clearBorders = (
self.drawClearRegionToolbar
.clearOnlyEnclosedObjsRadioButton
.isChecked()
)
if clearBorders:
regionLab = skimage.segmentation.clear_border(regionLab)

regionRp = skimage.measure.regionprops(regionLab)
clearIDs = [obj.label for obj in regionRp]

if not clearIDs:
if clearBorders:
self.logger.warning(
'None of the objects in the freehand region are '
'fully enclosed'
)
else:
self.logger.warning(
'None of the objects are touching the freehand region'
)
return

self.deleteIDmiddleClick(clearIDs, False, False)
self.update_cca_df_deletedIDs(posData, clearIDs)

self.freeRoiItem.clear()

self.updateAllImages()

def labelRoiWorkerFinished(self):
self.logger.info('Magic labeller closed.')
worker = self.labelRoiActiveWorkers.pop(-1)
Expand Down
Loading

0 comments on commit 04aeed9

Please sign in to comment.