Skip to content

Commit 8d97c75

Browse files
committed
fix: gh workflow and fill holes brush
1 parent 4f13044 commit 8d97c75

3 files changed

Lines changed: 54 additions & 24 deletions

File tree

.github/workflows/build_cython_extensions.yml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ jobs:
5656

5757
- uses: actions/download-artifact@v6
5858
with:
59+
pattern: precompiled-*
60+
merge-multiple: true
5961
path: artifacts/
6062

6163
- name: Flatten artifacts into precompiled directory
@@ -68,17 +70,33 @@ jobs:
6870
echo "Total files:"
6971
find cellacdc/precompiled/ -type f | grep -E '\.(pyd|so)$' | wc -l
7072
73+
- name: Validate artifact count
74+
run: |
75+
EXPECTED_COUNT=12
76+
FILE_COUNT=$(find cellacdc/precompiled/ -type f \( -name "*.pyd" -o -name "*.so" \) | wc -l)
77+
echo "Expected files: $EXPECTED_COUNT"
78+
echo "Found files: $FILE_COUNT"
79+
if [ "$FILE_COUNT" -lt "$EXPECTED_COUNT" ]; then
80+
echo "Missing binaries: expected at least $EXPECTED_COUNT, found $FILE_COUNT"
81+
exit 1
82+
fi
83+
7184
- name: Commit precompiled binaries
7285
run: |
7386
git config user.name "github-actions[bot]"
7487
git config user.email "github-actions[bot]@users.noreply.github.com"
75-
git add -A cellacdc/precompiled/
88+
git add -f cellacdc/precompiled/*.pyd cellacdc/precompiled/*.so
89+
git add cellacdc/precompiled/__init__.py
7690
FILE_COUNT=$(find cellacdc/precompiled/ -type f \( -name "*.pyd" -o -name "*.so" \) | wc -l)
7791
echo "Files to commit: $FILE_COUNT"
7892
if [ "$FILE_COUNT" -eq 0 ]; then
7993
echo "No precompiled files found, skipping commit"
8094
exit 0
8195
fi
82-
git commit -m "ci: update precompiled Cython extensions [skip ci]" || echo "No changes to commit"
83-
git pull --rebase origin ${{ github.ref_name }} || true
84-
git push origin HEAD:${{ github.ref_name }} || echo "Nothing to push"
96+
if git diff --cached --quiet; then
97+
echo "No changes to commit"
98+
exit 0
99+
fi
100+
git commit -m "ci: update precompiled Cython extensions [skip ci]"
101+
git pull --rebase origin ${{ github.ref_name }}
102+
git push origin HEAD:${{ github.ref_name }}

cellacdc/gui.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -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]

cellacdc/precompiled_functions.pyx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# precompiled_functions.pyx
22
# cython: boundscheck=False, wraparound=False, cdivision=True
3+
# rand change to trigger gh actions: 1
34
import numpy as np
45
cimport numpy as np
56
from libc.limits cimport UINT_MAX

0 commit comments

Comments
 (0)