nettracer3d 1.2.7__py3-none-any.whl → 1.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nettracer3d might be problematic. Click here for more details.
- nettracer3d/branch_stitcher.py +245 -142
- nettracer3d/community_extractor.py +3 -2
- nettracer3d/endpoint_joiner.py +286 -0
- nettracer3d/filaments.py +348 -106
- nettracer3d/histos.py +1182 -0
- nettracer3d/modularity.py +14 -96
- nettracer3d/neighborhoods.py +3 -2
- nettracer3d/nettracer.py +296 -82
- nettracer3d/nettracer_gui.py +2275 -2770
- nettracer3d/network_analysis.py +28 -9
- nettracer3d/network_graph_widget.py +2267 -0
- nettracer3d/painting.py +158 -298
- nettracer3d/segmenter.py +1 -1
- nettracer3d/segmenter_GPU.py +0 -1
- nettracer3d/simple_network.py +4 -4
- nettracer3d/smart_dilate.py +19 -7
- nettracer3d/tutorial.py +77 -26
- {nettracer3d-1.2.7.dist-info → nettracer3d-1.3.6.dist-info}/METADATA +50 -18
- nettracer3d-1.3.6.dist-info/RECORD +32 -0
- {nettracer3d-1.2.7.dist-info → nettracer3d-1.3.6.dist-info}/WHEEL +1 -1
- nettracer3d-1.2.7.dist-info/RECORD +0 -29
- {nettracer3d-1.2.7.dist-info → nettracer3d-1.3.6.dist-info}/entry_points.txt +0 -0
- {nettracer3d-1.2.7.dist-info → nettracer3d-1.3.6.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-1.2.7.dist-info → nettracer3d-1.3.6.dist-info}/top_level.txt +0 -0
nettracer3d/painting.py
CHANGED
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
from PyQt6.QtWidgets import QApplication, QMainWindow
|
|
2
|
-
|
|
2
|
+
from PyQt6.QtCore import Qt
|
|
3
|
+
import pyqtgraph as pg
|
|
3
4
|
import copy
|
|
4
5
|
import numpy as np
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class PaintManager(QMainWindow):
|
|
8
|
-
def __init__(self, parent
|
|
9
|
+
def __init__(self, parent=None):
|
|
9
10
|
super().__init__(parent)
|
|
10
11
|
self.resume = False
|
|
11
12
|
|
|
12
13
|
# Initialize stroke tracking storage once
|
|
13
14
|
if parent is not None:
|
|
14
15
|
if not hasattr(parent, 'completed_paint_strokes'):
|
|
15
|
-
parent.completed_paint_strokes = []
|
|
16
|
+
parent.completed_paint_strokes = []
|
|
16
17
|
if not hasattr(parent, 'current_stroke_points'):
|
|
17
|
-
parent.current_stroke_points = []
|
|
18
|
+
parent.current_stroke_points = []
|
|
18
19
|
if not hasattr(parent, 'current_stroke_type'):
|
|
19
|
-
parent.current_stroke_type = None
|
|
20
|
+
parent.current_stroke_type = None
|
|
20
21
|
|
|
21
|
-
#
|
|
22
|
-
if not hasattr(parent, '
|
|
23
|
-
parent.
|
|
24
|
-
if not hasattr(parent, '
|
|
25
|
-
parent.
|
|
26
|
-
if not hasattr(parent, 'current_operation'):
|
|
27
|
-
parent.current_operation = []
|
|
28
|
-
if not hasattr(parent, 'current_operation_type'):
|
|
29
|
-
parent.current_operation_type = None
|
|
22
|
+
# PyQtGraph visual items
|
|
23
|
+
if not hasattr(parent, 'virtual_paint_items'):
|
|
24
|
+
parent.virtual_paint_items = [] # Store all visual items
|
|
25
|
+
if not hasattr(parent, 'current_paint_items'):
|
|
26
|
+
parent.current_paint_items = [] # Current stroke visuals
|
|
30
27
|
|
|
31
28
|
def get_line_points(self, x0, y0, x1, y1):
|
|
32
29
|
"""Get all points in a line between (x0,y0) and (x1,y1) using Bresenham's algorithm."""
|
|
@@ -60,27 +57,19 @@ class PaintManager(QMainWindow):
|
|
|
60
57
|
return points
|
|
61
58
|
|
|
62
59
|
def initiate_paint_session(self, channel, current_xlim, current_ylim):
|
|
63
|
-
# Create static background (same as selection rectangle)
|
|
64
|
-
|
|
65
60
|
if self.parent().machine_window is not None:
|
|
66
61
|
if self.parent().machine_window.segmentation_worker is not None:
|
|
67
62
|
if not self.parent().machine_window.segmentation_worker._paused:
|
|
68
63
|
self.resume = True
|
|
69
64
|
self.parent().machine_window.segmentation_worker.pause()
|
|
70
65
|
|
|
71
|
-
|
|
72
66
|
if not self.parent().channel_visible[channel]:
|
|
73
67
|
self.parent().channel_visible[channel] = True
|
|
74
|
-
|
|
75
|
-
# Capture the background once
|
|
76
|
-
self.parent().static_background = self.parent().canvas.copy_from_bbox(self.parent().ax.bbox)
|
|
77
68
|
|
|
78
69
|
if self.resume:
|
|
79
70
|
self.parent().machine_window.segmentation_worker.resume()
|
|
80
71
|
self.resume = False
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
|
|
84
73
|
def start_virtual_paint_session(self, channel, current_xlim, current_ylim):
|
|
85
74
|
"""Start a virtual paint session that doesn't modify arrays until the end."""
|
|
86
75
|
self.parent().painting = True
|
|
@@ -90,7 +79,7 @@ class PaintManager(QMainWindow):
|
|
|
90
79
|
if not self.parent().channel_visible[channel]:
|
|
91
80
|
self.parent().channel_visible[channel] = True
|
|
92
81
|
|
|
93
|
-
# Initialize stroke tracking storage
|
|
82
|
+
# Initialize stroke tracking storage
|
|
94
83
|
if not hasattr(self.parent(), 'completed_paint_strokes'):
|
|
95
84
|
self.parent().completed_paint_strokes = []
|
|
96
85
|
if not hasattr(self.parent(), 'current_stroke_points'):
|
|
@@ -98,50 +87,53 @@ class PaintManager(QMainWindow):
|
|
|
98
87
|
if not hasattr(self.parent(), 'current_stroke_type'):
|
|
99
88
|
self.parent().current_stroke_type = None
|
|
100
89
|
|
|
101
|
-
# Initialize
|
|
102
|
-
if not hasattr(self.parent(), '
|
|
103
|
-
self.parent().
|
|
104
|
-
if not hasattr(self.parent(), '
|
|
105
|
-
self.parent().
|
|
106
|
-
if not hasattr(self.parent(), 'current_operation'):
|
|
107
|
-
self.parent().current_operation = []
|
|
108
|
-
if not hasattr(self.parent(), 'current_operation_type'):
|
|
109
|
-
self.parent().current_operation_type = None
|
|
90
|
+
# Initialize PyQtGraph visual storage
|
|
91
|
+
if not hasattr(self.parent(), 'virtual_paint_items'):
|
|
92
|
+
self.parent().virtual_paint_items = []
|
|
93
|
+
if not hasattr(self.parent(), 'current_paint_items'):
|
|
94
|
+
self.parent().current_paint_items = []
|
|
110
95
|
|
|
111
96
|
def reset_all_paint_storage(self):
|
|
112
|
-
"""Reset all paint storage
|
|
97
|
+
"""Reset all paint storage."""
|
|
98
|
+
# Clear visual items from view
|
|
99
|
+
if hasattr(self.parent(), 'virtual_paint_items'):
|
|
100
|
+
for item in self.parent().virtual_paint_items:
|
|
101
|
+
try:
|
|
102
|
+
self.parent().view.removeItem(item)
|
|
103
|
+
except:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
if hasattr(self.parent(), 'current_paint_items'):
|
|
107
|
+
for item in self.parent().current_paint_items:
|
|
108
|
+
try:
|
|
109
|
+
self.parent().view.removeItem(item)
|
|
110
|
+
except:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
113
|
self.parent().completed_paint_strokes = []
|
|
114
114
|
self.parent().current_stroke_points = []
|
|
115
115
|
self.parent().current_stroke_type = None
|
|
116
|
-
self.parent().
|
|
117
|
-
self.parent().
|
|
118
|
-
self.parent().current_operation = []
|
|
119
|
-
self.parent().current_operation_type = None
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
self.parent().virtual_paint_items = []
|
|
117
|
+
self.parent().current_paint_items = []
|
|
122
118
|
|
|
123
119
|
def add_virtual_paint_point(self, x, y, brush_size, erase=False, foreground=True):
|
|
124
|
-
"""Add a single paint point to the virtual layer."""
|
|
120
|
+
"""Add a single paint point to the virtual layer using PyQtGraph."""
|
|
125
121
|
|
|
126
122
|
# Determine operation type and visual properties
|
|
127
123
|
if erase:
|
|
128
|
-
paint_color =
|
|
129
|
-
alpha = 0.5
|
|
124
|
+
paint_color = (0, 0, 0) # Black for erase
|
|
130
125
|
operation_type = 'erase'
|
|
131
126
|
else:
|
|
132
127
|
if self.parent().machine_window is not None:
|
|
133
128
|
if foreground:
|
|
134
|
-
paint_color =
|
|
135
|
-
alpha = 0.7
|
|
129
|
+
paint_color = (0, 255, 0) # Green for foreground (value 1)
|
|
136
130
|
else:
|
|
137
|
-
paint_color =
|
|
138
|
-
alpha = 0.7
|
|
131
|
+
paint_color = (255, 0, 0) # Red for background (value 2)
|
|
139
132
|
else:
|
|
140
|
-
paint_color =
|
|
141
|
-
alpha = 0.7
|
|
133
|
+
paint_color = (255, 255, 255) # White for normal paint
|
|
142
134
|
operation_type = 'draw'
|
|
143
|
-
|
|
144
|
-
# Store the operation data
|
|
135
|
+
|
|
136
|
+
# Store the operation data
|
|
145
137
|
operation_data = {
|
|
146
138
|
'x': x,
|
|
147
139
|
'y': y,
|
|
@@ -153,229 +145,156 @@ class PaintManager(QMainWindow):
|
|
|
153
145
|
'threedthresh': getattr(self.parent(), 'threedthresh', 1)
|
|
154
146
|
}
|
|
155
147
|
|
|
156
|
-
# Add to stroke tracking
|
|
148
|
+
# Add to stroke tracking
|
|
157
149
|
if self.parent().current_stroke_type != operation_type:
|
|
158
|
-
# Finish previous stroke if switching between draw/erase
|
|
159
150
|
self.finish_current_stroke()
|
|
160
151
|
self.parent().current_stroke_type = operation_type
|
|
161
152
|
|
|
162
153
|
self.parent().current_stroke_points.append(operation_data)
|
|
163
154
|
|
|
164
|
-
# Create visual circle
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
self.parent().
|
|
175
|
-
'circle': circle,
|
|
176
|
-
'data': operation_data
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
self.parent().ax.add_patch(circle)
|
|
155
|
+
# Create visual circle using ScatterPlotItem
|
|
156
|
+
scatter = pg.ScatterPlotItem(
|
|
157
|
+
[x], [y],
|
|
158
|
+
size=brush_size,
|
|
159
|
+
pen=pg.mkPen(paint_color, width=1),
|
|
160
|
+
brush=pg.mkBrush(*paint_color, 127) # 50% alpha
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Add to view
|
|
164
|
+
self.parent().view.addItem(scatter)
|
|
165
|
+
self.parent().current_paint_items.append(scatter)
|
|
180
166
|
|
|
181
167
|
def finish_current_stroke(self):
|
|
182
168
|
"""Finish the current stroke and add it to completed strokes."""
|
|
183
169
|
if not self.parent().current_stroke_points:
|
|
184
170
|
return
|
|
185
|
-
|
|
186
|
-
# Store the completed stroke with its type
|
|
171
|
+
|
|
172
|
+
# Store the completed stroke with its type AND visual items
|
|
187
173
|
stroke_data = {
|
|
188
174
|
'points': self.parent().current_stroke_points.copy(),
|
|
189
|
-
'type': self.parent().current_stroke_type
|
|
175
|
+
'type': self.parent().current_stroke_type,
|
|
176
|
+
'visual_items': self.parent().current_paint_items.copy() # Store visual items with stroke
|
|
190
177
|
}
|
|
191
178
|
|
|
192
179
|
self.parent().completed_paint_strokes.append(stroke_data)
|
|
193
180
|
|
|
194
|
-
#
|
|
181
|
+
# Move current visual items to completed
|
|
182
|
+
self.parent().virtual_paint_items.extend(self.parent().current_paint_items)
|
|
183
|
+
self.parent().current_paint_items = []
|
|
184
|
+
|
|
185
|
+
# Clear current stroke data
|
|
195
186
|
self.parent().current_stroke_points = []
|
|
196
187
|
self.parent().current_stroke_type = None
|
|
197
188
|
|
|
189
|
+
def undo_last_virtual_stroke(self):
|
|
190
|
+
"""Undo the most recent virtual paint stroke (not yet converted to data)."""
|
|
191
|
+
|
|
192
|
+
# First try to undo the current stroke in progress
|
|
193
|
+
if hasattr(self.parent(), 'current_stroke_points') and self.parent().current_stroke_points:
|
|
194
|
+
# Remove visual items for current stroke
|
|
195
|
+
if hasattr(self.parent(), 'current_paint_items'):
|
|
196
|
+
for item in self.parent().current_paint_items:
|
|
197
|
+
try:
|
|
198
|
+
self.parent().view.removeItem(item)
|
|
199
|
+
except:
|
|
200
|
+
pass
|
|
201
|
+
self.parent().current_paint_items = []
|
|
202
|
+
|
|
203
|
+
# Clear current stroke data
|
|
204
|
+
self.parent().current_stroke_points = []
|
|
205
|
+
self.parent().current_stroke_type = None
|
|
206
|
+
return True # Successfully undid current stroke
|
|
207
|
+
|
|
208
|
+
# If no current stroke, undo the most recent completed stroke
|
|
209
|
+
if hasattr(self.parent(), 'completed_paint_strokes') and self.parent().completed_paint_strokes:
|
|
210
|
+
# Get the last completed stroke
|
|
211
|
+
last_stroke = self.parent().completed_paint_strokes.pop()
|
|
212
|
+
|
|
213
|
+
# Remove its visual items from the view
|
|
214
|
+
visual_items = last_stroke.get('visual_items', [])
|
|
215
|
+
for item in visual_items:
|
|
216
|
+
try:
|
|
217
|
+
self.parent().view.removeItem(item)
|
|
218
|
+
# Also remove from virtual_paint_items list
|
|
219
|
+
if item in self.parent().virtual_paint_items:
|
|
220
|
+
self.parent().virtual_paint_items.remove(item)
|
|
221
|
+
except:
|
|
222
|
+
pass
|
|
223
|
+
|
|
224
|
+
return True # Successfully undid completed stroke
|
|
225
|
+
|
|
226
|
+
# Nothing to undo
|
|
227
|
+
return False
|
|
228
|
+
|
|
198
229
|
def add_virtual_paint_stroke(self, x, y, brush_size, erase=False, foreground=True):
|
|
199
|
-
"""Add a paint stroke
|
|
200
|
-
# Just add the current point for visual display (no interpolation yet)
|
|
230
|
+
"""Add a paint stroke."""
|
|
201
231
|
self.add_virtual_paint_point(x, y, brush_size, erase, foreground)
|
|
202
|
-
|
|
203
|
-
# Store the last position for data conversion later
|
|
204
232
|
self.parent().last_virtual_pos = (x, y)
|
|
205
233
|
|
|
206
234
|
def connect_virtual_paint_points(self):
|
|
207
|
-
"""Connect points with lines matching the
|
|
208
|
-
|
|
209
|
-
if not hasattr(self.parent(), 'current_operation') or len(self.parent().current_operation) < 2:
|
|
235
|
+
"""Connect points with lines matching the brush size."""
|
|
236
|
+
if not hasattr(self.parent(), 'current_stroke_points') or len(self.parent().current_stroke_points) < 2:
|
|
210
237
|
return
|
|
211
238
|
|
|
212
|
-
|
|
213
|
-
existing_points = self.parent().current_operation.copy()
|
|
214
|
-
point_data = [item['data'] for item in existing_points if 'data' in item]
|
|
239
|
+
point_data = self.parent().current_stroke_points
|
|
215
240
|
|
|
216
241
|
if len(point_data) < 2:
|
|
217
242
|
return
|
|
218
243
|
|
|
219
|
-
# Get visual properties
|
|
244
|
+
# Get visual properties from first point
|
|
220
245
|
first_data = point_data[0]
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
# Convert brush size from data coordinates to points for linewidth
|
|
224
|
-
# Get the transformation from data to display coordinates
|
|
225
|
-
ax = self.parent().ax
|
|
226
|
-
|
|
227
|
-
# Transform two points to see the scaling
|
|
228
|
-
p1_data = [0, 0]
|
|
229
|
-
p2_data = [brush_size_data, 0] # One brush_size unit away
|
|
230
|
-
|
|
231
|
-
p1_display = ax.transData.transform(p1_data)
|
|
232
|
-
p2_display = ax.transData.transform(p2_data)
|
|
233
|
-
|
|
234
|
-
# Calculate pixels per data unit
|
|
235
|
-
pixels_per_data_unit = abs(p2_display[0] - p1_display[0])
|
|
236
|
-
|
|
237
|
-
# Convert to points (matplotlib uses 72 points per inch, figure.dpi pixels per inch)
|
|
238
|
-
fig = ax.figure
|
|
239
|
-
points_per_pixel = 72.0 / fig.dpi
|
|
240
|
-
brush_size_points = pixels_per_data_unit * points_per_pixel
|
|
246
|
+
brush_size = first_data['brush_size']
|
|
241
247
|
|
|
242
248
|
if first_data['erase']:
|
|
243
|
-
line_color =
|
|
244
|
-
alpha = 0.5
|
|
249
|
+
line_color = (0, 0, 0) # Black
|
|
245
250
|
else:
|
|
246
251
|
if self.parent().machine_window is not None:
|
|
247
252
|
if first_data['foreground']:
|
|
248
|
-
line_color =
|
|
249
|
-
alpha = 0.7
|
|
253
|
+
line_color = (0, 255, 0) # Green
|
|
250
254
|
else:
|
|
251
|
-
line_color =
|
|
252
|
-
alpha = 0.7
|
|
255
|
+
line_color = (255, 0, 0) # Red
|
|
253
256
|
else:
|
|
254
|
-
line_color =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
alpha=alpha,
|
|
271
|
-
linewidths=brush_size_points, # Now in points, matching circles
|
|
272
|
-
animated=True)
|
|
273
|
-
self.parent().ax.add_collection(lc)
|
|
274
|
-
|
|
275
|
-
# Add the line collection as a visual-only element
|
|
276
|
-
self.parent().current_operation.append({
|
|
277
|
-
'line_collection': lc,
|
|
278
|
-
'is_connection_visual': True
|
|
279
|
-
})
|
|
257
|
+
line_color = (255, 255, 255) # White
|
|
258
|
+
|
|
259
|
+
# Create line segments
|
|
260
|
+
x_coords = [p['x'] for p in point_data]
|
|
261
|
+
y_coords = [p['y'] for p in point_data]
|
|
262
|
+
|
|
263
|
+
# Create connected line
|
|
264
|
+
line = pg.PlotDataItem(
|
|
265
|
+
x_coords, y_coords,
|
|
266
|
+
pen=pg.mkPen(color=line_color, width=brush_size)
|
|
267
|
+
)
|
|
268
|
+
line.setOpacity(0.5)
|
|
269
|
+
|
|
270
|
+
# Add to view
|
|
271
|
+
self.parent().view.addItem(line)
|
|
272
|
+
self.parent().current_paint_items.append(line)
|
|
280
273
|
|
|
281
274
|
def finish_current_virtual_operation(self):
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if not self.parent().current_operation:
|
|
285
|
-
return
|
|
286
|
-
|
|
287
|
-
# Filter out connection visuals from the operation before storing
|
|
288
|
-
data_items = []
|
|
289
|
-
visual_items = []
|
|
290
|
-
|
|
291
|
-
for item in self.parent().current_operation:
|
|
292
|
-
if item.get('is_connection_visual', False):
|
|
293
|
-
visual_items.append(item)
|
|
294
|
-
else:
|
|
295
|
-
data_items.append(item)
|
|
296
|
-
|
|
297
|
-
# Only store the data items for this specific stroke
|
|
298
|
-
if data_items:
|
|
299
|
-
if self.parent().current_operation_type == 'draw':
|
|
300
|
-
self.parent().virtual_draw_operations.append(data_items)
|
|
301
|
-
elif self.parent().current_operation_type == 'erase':
|
|
302
|
-
self.parent().virtual_erase_operations.append(data_items)
|
|
303
|
-
|
|
304
|
-
# Clean up visual items that are connection-only
|
|
305
|
-
for item in visual_items:
|
|
306
|
-
try:
|
|
307
|
-
if 'line_collection' in item:
|
|
308
|
-
item['line_collection'].remove()
|
|
309
|
-
elif 'line' in item:
|
|
310
|
-
item['line'].remove()
|
|
311
|
-
except:
|
|
312
|
-
pass
|
|
313
|
-
|
|
314
|
-
self.parent().current_operation = []
|
|
315
|
-
self.parent().current_operation_type = None
|
|
316
|
-
|
|
317
|
-
def update_virtual_paint_display(self):
|
|
318
|
-
"""Update display with virtual paint strokes - handles different object types."""
|
|
319
|
-
if not hasattr(self.parent(), 'static_background') or self.parent().static_background is None:
|
|
320
|
-
return
|
|
321
|
-
|
|
322
|
-
# Restore the clean background
|
|
323
|
-
self.parent().canvas.restore_region(self.parent().static_background)
|
|
324
|
-
|
|
325
|
-
# Draw all completed operations
|
|
326
|
-
for operation_list in [self.parent().virtual_draw_operations, self.parent().virtual_erase_operations]:
|
|
327
|
-
for operation in operation_list:
|
|
328
|
-
for item in operation:
|
|
329
|
-
self._draw_virtual_item(item)
|
|
330
|
-
|
|
331
|
-
# Draw current operation being painted
|
|
332
|
-
if hasattr(self.parent(), 'current_operation'):
|
|
333
|
-
for item in self.parent().current_operation:
|
|
334
|
-
self._draw_virtual_item(item)
|
|
335
|
-
|
|
336
|
-
# Blit everything at once
|
|
337
|
-
self.parent().canvas.blit(self.parent().ax.bbox)
|
|
338
|
-
|
|
339
|
-
def _draw_virtual_item(self, item):
|
|
340
|
-
"""Helper method to draw different types of virtual paint items."""
|
|
341
|
-
try:
|
|
342
|
-
# Skip items that are marked as visual-only connections
|
|
343
|
-
if item.get('is_connection_visual', False):
|
|
344
|
-
if 'line' in item:
|
|
345
|
-
self.parent().ax.draw_artist(item['line'])
|
|
346
|
-
elif 'line_collection' in item:
|
|
347
|
-
self.parent().ax.draw_artist(item['line_collection'])
|
|
348
|
-
elif 'circle' in item:
|
|
349
|
-
self.parent().ax.draw_artist(item['circle'])
|
|
350
|
-
elif 'line' in item:
|
|
351
|
-
self.parent().ax.draw_artist(item['line'])
|
|
352
|
-
elif 'line_collection' in item:
|
|
353
|
-
self.parent().ax.draw_artist(item['line_collection'])
|
|
354
|
-
except Exception as e:
|
|
355
|
-
# Skip items that can't be drawn (might have been removed)
|
|
356
|
-
pass
|
|
275
|
+
"""Finish the current operation."""
|
|
276
|
+
self.finish_current_stroke()
|
|
357
277
|
|
|
358
278
|
def convert_virtual_strokes_to_data(self):
|
|
359
|
-
"""Convert each stroke separately to actual array data
|
|
279
|
+
"""Convert each stroke separately to actual array data."""
|
|
360
280
|
|
|
361
281
|
# Finish the current stroke first
|
|
362
282
|
self.finish_current_stroke()
|
|
363
283
|
|
|
364
|
-
# Process
|
|
284
|
+
# Process completed strokes
|
|
365
285
|
for stroke in self.parent().completed_paint_strokes:
|
|
366
286
|
stroke_points = stroke['points']
|
|
367
|
-
stroke_type = stroke['type']
|
|
368
287
|
|
|
369
288
|
if len(stroke_points) == 0:
|
|
370
289
|
continue
|
|
371
|
-
|
|
372
|
-
# Apply interpolation within this stroke
|
|
290
|
+
|
|
291
|
+
# Apply interpolation within this stroke
|
|
373
292
|
last_pos = None
|
|
374
293
|
for point_data in stroke_points:
|
|
375
294
|
current_pos = (point_data['x'], point_data['y'])
|
|
376
295
|
|
|
377
296
|
if last_pos is not None:
|
|
378
|
-
# Interpolate between consecutive points
|
|
297
|
+
# Interpolate between consecutive points
|
|
379
298
|
points = self.get_line_points(last_pos[0], last_pos[1], current_pos[0], current_pos[1])
|
|
380
299
|
for px, py in points:
|
|
381
300
|
self.paint_at_position_vectorized(
|
|
@@ -403,74 +322,25 @@ class PaintManager(QMainWindow):
|
|
|
403
322
|
|
|
404
323
|
last_pos = current_pos
|
|
405
324
|
|
|
406
|
-
# Clean up
|
|
407
|
-
for
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
item['circle'].remove()
|
|
413
|
-
elif 'line_collection' in item:
|
|
414
|
-
item['line_collection'].remove()
|
|
415
|
-
elif 'line' in item:
|
|
416
|
-
item['line'].remove()
|
|
417
|
-
except:
|
|
418
|
-
pass
|
|
325
|
+
# Clean up visual elements
|
|
326
|
+
for item in self.parent().virtual_paint_items:
|
|
327
|
+
try:
|
|
328
|
+
self.parent().view.removeItem(item)
|
|
329
|
+
except:
|
|
330
|
+
pass
|
|
419
331
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
elif 'line_collection' in item:
|
|
426
|
-
item['line_collection'].remove()
|
|
427
|
-
elif 'line' in item:
|
|
428
|
-
item['line'].remove()
|
|
429
|
-
except:
|
|
430
|
-
pass
|
|
332
|
+
for item in self.parent().current_paint_items:
|
|
333
|
+
try:
|
|
334
|
+
self.parent().view.removeItem(item)
|
|
335
|
+
except:
|
|
336
|
+
pass
|
|
431
337
|
|
|
432
|
-
# Reset
|
|
338
|
+
# Reset storage
|
|
433
339
|
self.parent().completed_paint_strokes = []
|
|
434
340
|
self.parent().current_stroke_points = []
|
|
435
341
|
self.parent().current_stroke_type = None
|
|
436
|
-
self.parent().
|
|
437
|
-
self.parent().
|
|
438
|
-
self.parent().current_operation = []
|
|
439
|
-
self.parent().current_operation_type = None
|
|
440
|
-
|
|
441
|
-
def end_virtual_paint_session(self):
|
|
442
|
-
"""Convert virtual paint to actual array modifications when exiting paint mode."""
|
|
443
|
-
if not hasattr(self.parent(), 'virtual_paint_strokes'):
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
# Now apply all the virtual strokes to the actual arrays
|
|
447
|
-
for stroke in self.parent().virtual_paint_strokes:
|
|
448
|
-
for circle in stroke:
|
|
449
|
-
center = circle.center
|
|
450
|
-
radius = circle.radius
|
|
451
|
-
is_erase = circle.get_facecolor()[0] == 0 # Black = erase
|
|
452
|
-
|
|
453
|
-
# Apply to actual array
|
|
454
|
-
self.paint_at_position_vectorized(
|
|
455
|
-
int(center[0]), int(center[1]),
|
|
456
|
-
erase=is_erase,
|
|
457
|
-
channel=self.paint_channel,
|
|
458
|
-
brush_size=int(radius * 2)
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
# Remove the virtual circle
|
|
462
|
-
circle.remove()
|
|
463
|
-
|
|
464
|
-
# Clean up virtual paint data
|
|
465
|
-
self.virtual_paint_strokes = []
|
|
466
|
-
self.current_stroke = []
|
|
467
|
-
|
|
468
|
-
# Reset background
|
|
469
|
-
self.static_background = None
|
|
470
|
-
self.painting = False
|
|
471
|
-
|
|
472
|
-
# Full refresh to show final result
|
|
473
|
-
self.update_display()
|
|
342
|
+
self.parent().virtual_paint_items = []
|
|
343
|
+
self.parent().current_paint_items = []
|
|
474
344
|
|
|
475
345
|
def paint_at_position_vectorized(self, center_x, center_y, erase=False, channel=2,
|
|
476
346
|
slice_idx=None, brush_size=None, threed=None,
|
|
@@ -478,46 +348,37 @@ class PaintManager(QMainWindow):
|
|
|
478
348
|
"""Vectorized paint operation for better performance."""
|
|
479
349
|
if self.parent().channel_data[channel] is None:
|
|
480
350
|
return
|
|
481
|
-
|
|
482
|
-
# Use provided parameters or fall back to instance variables
|
|
483
351
|
slice_idx = slice_idx if slice_idx is not None else self.parent().current_slice
|
|
484
352
|
brush_size = brush_size if brush_size is not None else getattr(self.parent(), 'brush_size', 5)
|
|
485
353
|
threed = threed if threed is not None else getattr(self.parent(), 'threed', False)
|
|
486
354
|
threedthresh = threedthresh if threedthresh is not None else getattr(self.parent(), 'threedthresh', 1)
|
|
487
355
|
|
|
488
|
-
# Handle 3D painting
|
|
356
|
+
# Handle 3D painting
|
|
489
357
|
if threed and threedthresh > 1:
|
|
490
358
|
half_range = (threedthresh - 1) // 2
|
|
491
359
|
low = max(0, slice_idx - half_range)
|
|
492
360
|
high = min(self.parent().channel_data[channel].shape[0] - 1, slice_idx + half_range)
|
|
493
361
|
|
|
494
|
-
|
|
495
362
|
for i in range(low, high + 1):
|
|
496
|
-
|
|
497
|
-
# Recursive call for each slice, but with threed=False to avoid infinite recursion
|
|
498
363
|
self.paint_at_position_vectorized(
|
|
499
364
|
center_x, center_y,
|
|
500
365
|
erase=erase,
|
|
501
366
|
channel=channel,
|
|
502
|
-
slice_idx=i,
|
|
367
|
+
slice_idx=i,
|
|
503
368
|
brush_size=brush_size,
|
|
504
|
-
threed=False,
|
|
369
|
+
threed=False,
|
|
505
370
|
threedthresh=1,
|
|
506
371
|
foreground=foreground,
|
|
507
372
|
machine_window=machine_window
|
|
508
373
|
)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
return # Exit early, recursive calls handle everything
|
|
512
|
-
|
|
513
|
-
# Regular 2D painting (single slice)
|
|
374
|
+
return
|
|
514
375
|
|
|
515
376
|
# Determine paint value
|
|
516
377
|
if erase:
|
|
517
378
|
val = 0
|
|
518
379
|
elif machine_window is None:
|
|
519
380
|
try:
|
|
520
|
-
val = self.parent().min_max[channel][1]
|
|
381
|
+
val = max(1, self.parent().min_max[channel][1])
|
|
521
382
|
except:
|
|
522
383
|
val = 255
|
|
523
384
|
elif foreground:
|
|
@@ -535,15 +396,14 @@ class PaintManager(QMainWindow):
|
|
|
535
396
|
x_max = min(width, center_x + radius + 1)
|
|
536
397
|
|
|
537
398
|
if y_min >= y_max or x_min >= x_max:
|
|
538
|
-
return
|
|
399
|
+
return
|
|
539
400
|
|
|
540
|
-
# Create coordinate grids
|
|
401
|
+
# Create coordinate grids
|
|
541
402
|
y_coords, x_coords = np.mgrid[y_min:y_max, x_min:x_max]
|
|
542
403
|
|
|
543
|
-
# Calculate distances
|
|
404
|
+
# Calculate distances and create mask
|
|
544
405
|
distances_sq = (x_coords - center_x) ** 2 + (y_coords - center_y) ** 2
|
|
545
406
|
mask = distances_sq <= radius ** 2
|
|
546
407
|
|
|
547
|
-
# Paint on this
|
|
548
|
-
|
|
408
|
+
# Paint on this slice
|
|
549
409
|
self.parent().channel_data[channel][slice_idx][y_min:y_max, x_min:x_max][mask] = val
|
nettracer3d/segmenter.py
CHANGED
|
@@ -1380,7 +1380,7 @@ class InteractiveSegmenter:
|
|
|
1380
1380
|
return chunks
|
|
1381
1381
|
|
|
1382
1382
|
def train_batch(self, foreground_array, speed=True, use_gpu=False, use_two=False, mem_lock=False, saving=False):
|
|
1383
|
-
"""
|
|
1383
|
+
"""Train model for batch of arrays"""
|
|
1384
1384
|
|
|
1385
1385
|
if not saving:
|
|
1386
1386
|
print("Training model...")
|