@@ -2038,6 +2038,7 @@ def gui_createControlsToolbar(self):
20382038
20392039 brushEraserToolBar.addWidget(QLabel(' '))
20402040 self.brushAutoFillCheckbox = QCheckBox('Auto-fill holes')
2041+ self.brushAutoFillCheckbox.setTristate(False)
20412042 self.brushAutoFillAction = brushEraserToolBar.addWidget(
20422043 self.brushAutoFillCheckbox
20432044 )
@@ -5424,7 +5425,7 @@ def gui_mousePressEventImg2(self, event: QGraphicsSceneMouseEvent):
54245425 return
54255426
54265427 if editID.assignNewID:
5427- self.assignNewIDfromClickedID(ID, event)
5428+ self.assignNewIDfromClickedID(ID, event, shift=shift )
54285429 return
54295430
54305431 if not self.doNotAskAgainExistingID:
@@ -5815,7 +5816,7 @@ def gui_mouseDragEventImg1(self, event):
58155816 return
58165817
58175818 posData = self.data[self.pos_i]
5818- Y, X = self.get_2Dlab(posData.lab).shape
5819+ Y, X = self.get_2Dlab(posData.lab, force_z=False ).shape
58195820 xdata, ydata = int(x), int(y)
58205821 if not myutils.is_in_bounds(xdata, ydata, X, Y):
58215822 return
@@ -5825,7 +5826,7 @@ def gui_mouseDragEventImg1(self, event):
58255826
58265827 # Brush dragging mouse --> keep brushing
58275828 elif self.isMouseDragImg1 and self.brushButton.isChecked():
5828- lab_2D = self.get_2Dlab(posData.lab)
5829+ lab_2D = self.get_2Dlab(posData.lab, force_z=False )
58295830
58305831 # t1 = time.perf_counter()
58315832
@@ -5862,7 +5863,7 @@ def gui_mouseDragEventImg1(self, event):
58625863
58635864 # t5 = time.perf_counter()
58645865
5865- lab2D = self.get_2Dlab(posData.lab)
5866+ lab2D = self.get_2Dlab(posData.lab, force_z=False )
58665867 brushMask = np.logical_and(
58675868 lab2D[diskSlice] == posData.brushID, diskMask
58685869 )
@@ -5887,7 +5888,7 @@ def gui_mouseDragEventImg1(self, event):
58875888 # Eraser dragging mouse --> keep erasing
58885889 elif self.isMouseDragImg1 and self.eraserButton.isChecked():
58895890 posData = self.data[self.pos_i]
5890- lab_2D = self.get_2Dlab(posData.lab)
5891+ lab_2D = self.get_2Dlab(posData.lab, force_z=False )
58915892 rrPoly, ccPoly = self.getPolygonBrush((y, x), Y, X)
58925893
58935894 ymin, xmin, ymax, xmax, diskMask = self.getDiskMask(xdata, ydata)
@@ -5975,18 +5976,28 @@ def gui_mouseDragEventImg1(self, event):
59755976 self.zoomRectItem.setSize((w, h))
59765977
59775978 # @exec_time
5978- def fillHolesID(self, ID, sender='brush'):
5979+ def fillHolesID(self, ID, sender='brush', enabled=None ):
59795980 posData = self.data[self.pos_i]
59805981 if sender == 'brush':
5981- if not self.brushAutoFillCheckbox.isChecked():
5982+ if enabled is None:
5983+ enabled = self.brushAutoFillCheckbox.isChecked()
5984+
5985+ if not enabled:
5986+ return False
5987+
5988+ if not self.brushButton.isChecked():
59825989 return False
59835990
5984- lab2D = self.get_2Dlab(posData.lab)
5991+ lab2D = self.get_2Dlab(posData.lab, force_z=False )
59855992 mask = lab2D == ID
59865993 filledMask = scipy.ndimage.binary_fill_holes(mask)
5987- lab2D[filledMask] = ID
5994+ newFilledMask = np.logical_and(filledMask, ~mask)
5995+ if not np.any(newFilledMask):
5996+ return False
59885997
5989- self.set_2Dlab(lab2D)
5998+ # Apply only newly filled pixels to avoid rewriting the full 3D
5999+ # stack when editing in projection mode.
6000+ self.applyBrushMask(newFilledMask, ID)
59906001 return True
59916002 return False
59926003
@@ -6715,7 +6726,7 @@ def gui_mouseDragEventImg2(self, event):
67156726 if mode == 'Viewer':
67166727 return
67176728
6718- Y, X = self.get_2Dlab(posData.lab).shape
6729+ Y, X = self.get_2Dlab(posData.lab, force_z=False ).shape
67196730 x, y = event.pos().x(), event.pos().y()
67206731 xdata, ydata = int(x), int(y)
67216732 if not myutils.is_in_bounds(xdata, ydata, X, Y):
@@ -6724,7 +6735,7 @@ def gui_mouseDragEventImg2(self, event):
67246735 # Eraser dragging mouse --> keep erasing
67256736 if self.isMouseDragImg2 and self.eraserButton.isChecked():
67266737 posData = self.data[self.pos_i]
6727- lab_2D = self.get_2Dlab(posData.lab)
6738+ lab_2D = self.get_2Dlab(posData.lab, force_z=False )
67286739 Y, X = lab_2D.shape
67296740 x, y = event.pos().x(), event.pos().y()
67306741 xdata, ydata = int(x), int(y)
@@ -6755,7 +6766,7 @@ def gui_mouseDragEventImg2(self, event):
67556766 # Brush paint dragging mouse --> keep painting
67566767 if self.isMouseDragImg2 and self.brushButton.isChecked():
67576768 posData = self.data[self.pos_i]
6758- lab_2D = self.get_2Dlab(posData.lab)
6769+ lab_2D = self.get_2Dlab(posData.lab, force_z=False )
67596770 Y, X = lab_2D.shape
67606771 x, y = event.pos().x(), event.pos().y()
67616772 xdata, ydata = int(x), int(y)
@@ -6795,7 +6806,7 @@ def gui_mouseReleaseEventImg2(self, event):
67956806 if mode == 'Viewer':
67966807 return
67976808
6798- Y, X = self.get_2Dlab(posData.lab).shape
6809+ Y, X = self.get_2Dlab(posData.lab, force_z=False ).shape
67996810 try:
68006811 x, y = event.pos().x(), event.pos().y()
68016812 except Exception as e:
@@ -6826,7 +6837,7 @@ def gui_mouseReleaseEventImg2(self, event):
68266837 elif self.mergeIDsButton.isChecked():
68276838 x, y = event.pos().x(), event.pos().y()
68286839 xdata, ydata = int(x), int(y)
6829- lab2D = self.get_2Dlab(posData.lab)
6840+ lab2D = self.get_2Dlab(posData.lab, force_z=False )
68306841 ID = lab2D[ydata, xdata]
68316842 if ID == 0:
68326843 nearest_ID = core.nearest_nonzero_2D(
@@ -6911,7 +6922,7 @@ def gui_mouseReleaseEventImg1(self, event):
69116922 if mode == 'Viewer':
69126923 return
69136924
6914- Y, X = self.get_2Dlab(posData.lab).shape
6925+ Y, X = self.get_2Dlab(posData.lab, force_z=False ).shape
69156926 x, y = event.pos().x(), event.pos().y()
69166927 xdata, ydata = int(x), int(y)
69176928 if not myutils.is_in_bounds(xdata, ydata, X, Y):
@@ -9677,7 +9688,6 @@ def applyEditID(
96779688 self, clickedID, currentIDs, oldIDnewIDMapper, clicked_x, clicked_y, shift=False, doPropagateUnvisited=False
96789689 ):
96799690 posData = self.data[self.pos_i]
9680-
96819691 # Ask to propagate change to all future visited frames
96829692 key = 'Edit ID'
96839693 askAction = self.askHowFutureFramesActions[key]
@@ -11639,7 +11649,8 @@ def brushAutoHideToggled(self, checked):
1163911649
1164011650 def brushReleased(self):
1164111651 posData = self.data[self.pos_i]
11642- self.fillHolesID(posData.brushID, sender='brush')
11652+ do_auto_fill = self.brushAutoFillCheckbox.isChecked()
11653+ self.fillHolesID(posData.brushID, sender='brush', enabled=do_auto_fill)
1164311654
1164411655 # Update data (rp, etc)
1164511656
@@ -21553,13 +21564,13 @@ def applyBrushMask(self, mask, ID, toLocalSlice=None):
2155321564 posData.lab[mask] = ID
2155421565
2155521566 def assignNewIDfromClickedID(
21556- self, clickedID: int, event: QGraphicsSceneMouseEvent
21567+ self, clickedID: int, event: QGraphicsSceneMouseEvent, shift: bool = False
2155721568 ):
2155821569 posData = self.data[self.pos_i]
2155921570 x, y = event.pos().x(), event.pos().y()
2156021571 newID = self.setBrushID(return_val=True)
2156121572 mapper = [(clickedID, newID)]
21562- self.applyEditID(clickedID, posData.IDs.copy(), mapper, x, y)
21573+ self.applyEditID(clickedID, posData.IDs.copy(), mapper, x, y, shift=shift )
2156321574
2156421575 def set_2Dlab(self, lab2D, lab3D=None):
2156521576 posData = self.data[self.pos_i]
0 commit comments