nettracer3d 0.7.4__py3-none-any.whl → 0.7.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.
- nettracer3d/community_extractor.py +17 -39
- nettracer3d/modularity.py +41 -264
- nettracer3d/nettracer.py +7 -94
- nettracer3d/nettracer_gui.py +422 -205
- nettracer3d/network_analysis.py +0 -178
- nettracer3d/segmenter.py +9 -56
- nettracer3d/segmenter_GPU.py +856 -181
- nettracer3d/simple_network.py +2 -150
- {nettracer3d-0.7.4.dist-info → nettracer3d-0.7.6.dist-info}/METADATA +3 -6
- nettracer3d-0.7.6.dist-info/RECORD +21 -0
- {nettracer3d-0.7.4.dist-info → nettracer3d-0.7.6.dist-info}/WHEEL +1 -1
- nettracer3d/segmenter - Copy.py +0 -2097
- nettracer3d-0.7.4.dist-info/RECORD +0 -22
- {nettracer3d-0.7.4.dist-info → nettracer3d-0.7.6.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.4.dist-info → nettracer3d-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.4.dist-info → nettracer3d-0.7.6.dist-info}/top_level.txt +0 -0
nettracer3d/segmenter_GPU.py
CHANGED
|
@@ -32,6 +32,7 @@ class InteractiveSegmenter:
|
|
|
32
32
|
self.feature_cache = None
|
|
33
33
|
self.lock = threading.Lock()
|
|
34
34
|
self._currently_segmenting = None
|
|
35
|
+
self.use_gpu = True
|
|
35
36
|
|
|
36
37
|
# Current position attributes
|
|
37
38
|
self.current_z = None
|
|
@@ -62,67 +63,298 @@ class InteractiveSegmenter:
|
|
|
62
63
|
self.dogs = [(1, 2), (2, 4), (4, 8)]
|
|
63
64
|
self.master_chunk = 49
|
|
64
65
|
|
|
66
|
+
#Data when loading prev model:
|
|
67
|
+
self.previous_foreground = None
|
|
68
|
+
self.previous_background = None
|
|
69
|
+
self.previous_z_fore = None
|
|
70
|
+
self.previous_z_back = None
|
|
71
|
+
|
|
72
|
+
def segment_slice_chunked(self, slice_z, block_size=49):
|
|
73
|
+
"""
|
|
74
|
+
A completely standalone method to segment a single z-slice in chunks
|
|
75
|
+
with improved safeguards.
|
|
76
|
+
"""
|
|
77
|
+
# Check if we're already processing this slice
|
|
78
|
+
if self._currently_processing and self._currently_processing == slice_z:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# Set processing flag with the slice we're processing
|
|
82
|
+
self._currently_processing = slice_z
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
# First attempt to get the feature map
|
|
86
|
+
feature_map = None
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
if slice_z in self.feature_cache:
|
|
90
|
+
feature_map = self.feature_cache[slice_z]
|
|
91
|
+
elif hasattr(self, 'map_slice') and self.map_slice is not None and slice_z == self.current_z:
|
|
92
|
+
feature_map = self.map_slice
|
|
93
|
+
else:
|
|
94
|
+
# Generate new feature map
|
|
95
|
+
try:
|
|
96
|
+
feature_map = self.get_feature_map_slice(slice_z, self.current_speed, False)
|
|
97
|
+
self.map_slice = feature_map
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error generating feature map: {e}")
|
|
100
|
+
import traceback
|
|
101
|
+
traceback.print_exc()
|
|
102
|
+
return # Exit if we can't generate the feature map
|
|
103
|
+
except:
|
|
104
|
+
# Generate new feature map
|
|
105
|
+
try:
|
|
106
|
+
feature_map = self.get_feature_map_slice(slice_z, self.current_speed, False)
|
|
107
|
+
self.map_slice = feature_map
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"Error generating feature map: {e}")
|
|
110
|
+
import traceback
|
|
111
|
+
traceback.print_exc()
|
|
112
|
+
return # Exit if we can't generate the feature map
|
|
113
|
+
|
|
114
|
+
# Check that we have a valid feature map
|
|
115
|
+
if feature_map is None:
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Get dimensions of the slice
|
|
119
|
+
y_size, x_size = self.image_3d.shape[1], self.image_3d.shape[2]
|
|
120
|
+
chunk_count = 0
|
|
121
|
+
|
|
122
|
+
# Determine if feature_map is a CuPy array
|
|
123
|
+
is_cupy_array = hasattr(feature_map, 'get')
|
|
124
|
+
|
|
125
|
+
# Process in blocks for chunked feedback
|
|
126
|
+
for y_start in range(0, y_size, block_size):
|
|
127
|
+
if self._currently_processing != slice_z:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
for x_start in range(0, x_size, block_size):
|
|
131
|
+
if self._currently_processing != slice_z:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
y_end = min(y_start + block_size, y_size)
|
|
135
|
+
x_end = min(x_start + block_size, x_size)
|
|
136
|
+
|
|
137
|
+
# Create coordinates and features for this block
|
|
138
|
+
coords = []
|
|
139
|
+
features_list = []
|
|
140
|
+
|
|
141
|
+
for y in range(y_start, y_end):
|
|
142
|
+
for x in range(x_start, x_end):
|
|
143
|
+
coords.append((slice_z, y, x))
|
|
144
|
+
features_list.append(feature_map[y, x])
|
|
145
|
+
|
|
146
|
+
# Convert features to NumPy properly based on type
|
|
147
|
+
if is_cupy_array:
|
|
148
|
+
# If feature_map is a CuPy array, we need to extract a CuPy array
|
|
149
|
+
# from the list and then convert it to NumPy
|
|
150
|
+
try:
|
|
151
|
+
# Create a CuPy array from the list of feature vectors
|
|
152
|
+
features_array = cp.stack(features_list)
|
|
153
|
+
# Convert to NumPy explicitly using .get()
|
|
154
|
+
features = features_array.get()
|
|
155
|
+
except Exception as e:
|
|
156
|
+
print(f"Error converting features to NumPy: {e}")
|
|
157
|
+
# Fallback: convert each feature individually
|
|
158
|
+
features = []
|
|
159
|
+
for feat in features_list:
|
|
160
|
+
if hasattr(feat, 'get'):
|
|
161
|
+
features.append(feat.get())
|
|
162
|
+
else:
|
|
163
|
+
features.append(feat)
|
|
164
|
+
else:
|
|
165
|
+
# If it's already a NumPy array, we can use it directly
|
|
166
|
+
features = features_list
|
|
167
|
+
|
|
168
|
+
# Skip empty blocks
|
|
169
|
+
if not coords:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Predict
|
|
173
|
+
try:
|
|
174
|
+
try:
|
|
175
|
+
predictions = self.model.predict(features)
|
|
176
|
+
except ValueError:
|
|
177
|
+
self.feature_cache = None
|
|
178
|
+
self.map_slice = None
|
|
179
|
+
return None, None
|
|
180
|
+
|
|
181
|
+
# Split results
|
|
182
|
+
foreground = set()
|
|
183
|
+
background = set()
|
|
184
|
+
|
|
185
|
+
for coord, pred in zip(coords, predictions):
|
|
186
|
+
if pred:
|
|
187
|
+
foreground.add(coord)
|
|
188
|
+
else:
|
|
189
|
+
background.add(coord)
|
|
190
|
+
|
|
191
|
+
# Yield this chunk
|
|
192
|
+
chunk_count += 1
|
|
193
|
+
yield foreground, background
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f"Error processing chunk: {e}")
|
|
197
|
+
import traceback
|
|
198
|
+
traceback.print_exc()
|
|
199
|
+
|
|
200
|
+
finally:
|
|
201
|
+
# Only clear if we're still processing the same slice
|
|
202
|
+
# (otherwise, another slice might have taken over)
|
|
203
|
+
if self._currently_processing == slice_z:
|
|
204
|
+
self._currently_processing = None
|
|
205
|
+
|
|
65
206
|
def process_chunk(self, chunk_coords):
|
|
66
207
|
"""Process a chunk staying in CuPy as much as possible"""
|
|
67
208
|
|
|
68
209
|
foreground_coords = [] # Keep as list of CuPy coordinates
|
|
69
210
|
background_coords = []
|
|
211
|
+
|
|
212
|
+
if self.previewing or not self.use_two:
|
|
70
213
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
214
|
+
if self.realtimechunks is None:
|
|
215
|
+
z_min, z_max = chunk_coords[0], chunk_coords[1]
|
|
216
|
+
y_min, y_max = chunk_coords[2], chunk_coords[3]
|
|
217
|
+
x_min, x_max = chunk_coords[4], chunk_coords[5]
|
|
218
|
+
|
|
219
|
+
# Create meshgrid using CuPy - already good
|
|
220
|
+
z_range = cp.arange(z_min, z_max)
|
|
221
|
+
y_range = cp.arange(y_min, y_max)
|
|
222
|
+
x_range = cp.arange(x_min, x_max)
|
|
223
|
+
|
|
224
|
+
# More efficient way to create coordinates
|
|
225
|
+
chunk_coords_array = cp.stack(cp.meshgrid(
|
|
226
|
+
z_range, y_range, x_range, indexing='ij'
|
|
227
|
+
)).reshape(3, -1).T
|
|
228
|
+
|
|
229
|
+
# Keep as CuPy array instead of converting to list
|
|
230
|
+
chunk_coords_gpu = chunk_coords_array
|
|
231
|
+
else:
|
|
232
|
+
# Convert list to CuPy array once
|
|
233
|
+
chunk_coords_gpu = cp.array(chunk_coords)
|
|
234
|
+
z_coords = chunk_coords_gpu[:, 0]
|
|
235
|
+
y_coords = chunk_coords_gpu[:, 1]
|
|
236
|
+
x_coords = chunk_coords_gpu[:, 2]
|
|
237
|
+
|
|
238
|
+
z_min, z_max = cp.min(z_coords).item(), cp.max(z_coords).item()
|
|
239
|
+
y_min, y_max = cp.min(y_coords).item(), cp.max(y_coords).item()
|
|
240
|
+
x_min, x_max = cp.min(x_coords).item(), cp.max(x_coords).item()
|
|
85
241
|
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
242
|
+
# Extract subarray - already good
|
|
243
|
+
subarray = self.image_3d[z_min:z_max+1, y_min:y_max+1, x_min:x_max+1]
|
|
244
|
+
|
|
245
|
+
# Compute features
|
|
246
|
+
if self.speed:
|
|
247
|
+
feature_map = self.compute_feature_maps_gpu(subarray)
|
|
248
|
+
else:
|
|
249
|
+
feature_map = self.compute_deep_feature_maps_gpu(subarray)
|
|
250
|
+
|
|
251
|
+
# Extract features more efficiently
|
|
252
|
+
local_coords = chunk_coords_gpu.copy()
|
|
253
|
+
local_coords[:, 0] -= z_min
|
|
254
|
+
local_coords[:, 1] -= y_min
|
|
255
|
+
local_coords[:, 2] -= x_min
|
|
256
|
+
|
|
257
|
+
# Vectorized feature extraction
|
|
258
|
+
features_gpu = feature_map[local_coords[:, 0], local_coords[:, 1], local_coords[:, 2]]
|
|
259
|
+
|
|
260
|
+
features_cpu = cp.asnumpy(features_gpu)
|
|
261
|
+
predictions = self.model.predict(features_cpu)
|
|
262
|
+
|
|
263
|
+
# Keep coordinates as CuPy arrays
|
|
264
|
+
pred_mask = cp.array(predictions, dtype=bool)
|
|
265
|
+
foreground_coords = chunk_coords_gpu[pred_mask]
|
|
266
|
+
background_coords = chunk_coords_gpu[~pred_mask]
|
|
267
|
+
|
|
105
268
|
else:
|
|
106
|
-
|
|
269
|
+
# 2D implementation for GPU
|
|
270
|
+
foreground_coords = []
|
|
271
|
+
background_coords = []
|
|
272
|
+
|
|
273
|
+
# Check if chunk_coords is in the 2D format [z, y_start, y_end, x_start, x_end]
|
|
274
|
+
if len(chunk_coords) == 5:
|
|
275
|
+
z = chunk_coords[0]
|
|
276
|
+
y_start = chunk_coords[1]
|
|
277
|
+
y_end = chunk_coords[2]
|
|
278
|
+
x_start = chunk_coords[3]
|
|
279
|
+
x_end = chunk_coords[4]
|
|
280
|
+
|
|
281
|
+
# Generate coordinates for this slice or subchunk using the new function
|
|
282
|
+
coords_array = self.twodim_coords(z, y_start, y_end, x_start, x_end)
|
|
283
|
+
|
|
284
|
+
# Get the feature map for this z-slice
|
|
285
|
+
if self.feature_cache is None:
|
|
286
|
+
feature_map = self.get_feature_map_slice(z, self.speed, True) # Use GPU
|
|
287
|
+
elif z not in self.feature_cache and not self.previewing:
|
|
288
|
+
feature_map = self.get_feature_map_slice(z, self.speed, True) # Use GPU
|
|
289
|
+
elif (z not in self.feature_cache or self.feature_cache is None) and self.previewing:
|
|
290
|
+
feature_map = self.map_slice
|
|
291
|
+
if feature_map is None:
|
|
292
|
+
return [], []
|
|
293
|
+
else:
|
|
294
|
+
feature_map = self.feature_cache[z]
|
|
295
|
+
|
|
296
|
+
# Check if we have a valid feature map
|
|
297
|
+
if feature_map is None:
|
|
298
|
+
return [], []
|
|
299
|
+
|
|
300
|
+
# Extract y and x coordinates from the array
|
|
301
|
+
y_indices = coords_array[:, 1]
|
|
302
|
+
x_indices = coords_array[:, 2]
|
|
303
|
+
|
|
304
|
+
# Extract features using CuPy indexing
|
|
305
|
+
features_gpu = feature_map[y_indices, x_indices]
|
|
306
|
+
|
|
307
|
+
# Convert to NumPy for the model
|
|
308
|
+
features_cpu = features_gpu.get()
|
|
309
|
+
|
|
310
|
+
# Make predictions
|
|
311
|
+
predictions = self.model.predict(features_cpu)
|
|
312
|
+
|
|
313
|
+
# Create CuPy boolean mask from predictions
|
|
314
|
+
pred_mask = cp.array(predictions, dtype=bool)
|
|
315
|
+
|
|
316
|
+
# Split into foreground and background using the mask
|
|
317
|
+
fore_coords = coords_array[pred_mask]
|
|
318
|
+
back_coords = coords_array[~pred_mask]
|
|
319
|
+
|
|
320
|
+
return fore_coords, back_coords
|
|
321
|
+
|
|
322
|
+
return foreground_coords, background_coords
|
|
323
|
+
|
|
324
|
+
def twodim_coords(self, z, y_start, y_end, x_start, x_end):
|
|
325
|
+
"""
|
|
326
|
+
Generate 2D coordinates for a z-slice using CuPy for GPU acceleration.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
z (int): Z-slice index
|
|
330
|
+
y_start (int): Start index for y dimension
|
|
331
|
+
y_end (int): End index for y dimension
|
|
332
|
+
x_start (int): Start index for x dimension
|
|
333
|
+
x_end (int): End index for x dimension
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
CuPy array of coordinates in format (z, y, x)
|
|
337
|
+
"""
|
|
338
|
+
import cupy as cp
|
|
107
339
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
local_coords[:, 1] -= y_min
|
|
112
|
-
local_coords[:, 2] -= x_min
|
|
340
|
+
# Create ranges for y and x dimensions
|
|
341
|
+
y_range = cp.arange(y_start, y_end, dtype=int)
|
|
342
|
+
x_range = cp.arange(x_start, x_end, dtype=int)
|
|
113
343
|
|
|
114
|
-
#
|
|
115
|
-
|
|
344
|
+
# Create meshgrid
|
|
345
|
+
y_coords, x_coords = cp.meshgrid(y_range, x_range, indexing='ij')
|
|
116
346
|
|
|
117
|
-
|
|
118
|
-
|
|
347
|
+
# Calculate total size
|
|
348
|
+
total_size = len(y_range) * len(x_range)
|
|
119
349
|
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
350
|
+
# Stack coordinates with z values
|
|
351
|
+
slice_coords = cp.column_stack((
|
|
352
|
+
cp.full(total_size, z, dtype=int),
|
|
353
|
+
y_coords.ravel(),
|
|
354
|
+
x_coords.ravel()
|
|
355
|
+
))
|
|
124
356
|
|
|
125
|
-
return
|
|
357
|
+
return slice_coords
|
|
126
358
|
|
|
127
359
|
def compute_feature_maps_gpu(self, image_3d=None):
|
|
128
360
|
"""Compute feature maps using GPU with CuPy"""
|
|
@@ -233,6 +465,158 @@ class InteractiveSegmenter:
|
|
|
233
465
|
|
|
234
466
|
return cp.stack(features, axis=-1)
|
|
235
467
|
|
|
468
|
+
|
|
469
|
+
def compute_feature_maps_gpu_2d(self, z=None):
|
|
470
|
+
"""Compute feature maps for 2D images using GPU with CuPy"""
|
|
471
|
+
import cupy as cp
|
|
472
|
+
import cupyx.scipy.ndimage as cupy_ndimage
|
|
473
|
+
|
|
474
|
+
# Extract 2D slice if z is provided, otherwise use the image directly
|
|
475
|
+
if z is not None:
|
|
476
|
+
image_2d = cp.asarray(self.image_3d[z, :, :])
|
|
477
|
+
else:
|
|
478
|
+
# Assuming image_2d is already available or passed
|
|
479
|
+
image_2d = cp.asarray(self.image_2d)
|
|
480
|
+
|
|
481
|
+
original_shape = image_2d.shape
|
|
482
|
+
features = []
|
|
483
|
+
|
|
484
|
+
# Gaussian smoothing at different scales
|
|
485
|
+
for sigma in self.alphas:
|
|
486
|
+
smooth = cupy_ndimage.gaussian_filter(image_2d, sigma)
|
|
487
|
+
features.append(smooth)
|
|
488
|
+
|
|
489
|
+
# Difference of Gaussians
|
|
490
|
+
for (s1, s2) in self.dogs:
|
|
491
|
+
g1 = cupy_ndimage.gaussian_filter(image_2d, s1)
|
|
492
|
+
g2 = cupy_ndimage.gaussian_filter(image_2d, s2)
|
|
493
|
+
dog = g1 - g2
|
|
494
|
+
features.append(dog)
|
|
495
|
+
|
|
496
|
+
# Gradient computations for 2D
|
|
497
|
+
gx = cupy_ndimage.sobel(image_2d, axis=1, mode='reflect') # x direction
|
|
498
|
+
gy = cupy_ndimage.sobel(image_2d, axis=0, mode='reflect') # y direction
|
|
499
|
+
|
|
500
|
+
# Gradient magnitude (2D version - no z component)
|
|
501
|
+
gradient_magnitude = cp.sqrt(gx**2 + gy**2)
|
|
502
|
+
features.append(gradient_magnitude)
|
|
503
|
+
|
|
504
|
+
# Verify shapes
|
|
505
|
+
for i, feat in enumerate(features):
|
|
506
|
+
if feat.shape != original_shape:
|
|
507
|
+
# Check dimensionality and expand if needed
|
|
508
|
+
if len(feat.shape) < len(original_shape):
|
|
509
|
+
feat_adjusted = feat
|
|
510
|
+
missing_dims = len(original_shape) - len(feat.shape)
|
|
511
|
+
for _ in range(missing_dims):
|
|
512
|
+
feat_adjusted = cp.expand_dims(feat_adjusted, axis=0)
|
|
513
|
+
|
|
514
|
+
if feat_adjusted.shape != original_shape:
|
|
515
|
+
raise ValueError(f"Feature {i} has shape {feat.shape}, expected {original_shape}")
|
|
516
|
+
|
|
517
|
+
features[i] = feat_adjusted
|
|
518
|
+
|
|
519
|
+
# Stack features along a new dimension
|
|
520
|
+
result = cp.stack(features, axis=-1)
|
|
521
|
+
|
|
522
|
+
# Optional: Return as numpy array if needed
|
|
523
|
+
# result = cp.asnumpy(result)
|
|
524
|
+
|
|
525
|
+
return result
|
|
526
|
+
|
|
527
|
+
def compute_deep_feature_maps_gpu_2d(self, z=None):
|
|
528
|
+
"""Compute 2D feature maps using GPU with CuPy"""
|
|
529
|
+
import cupy as cp
|
|
530
|
+
import cupyx.scipy.ndimage as cupy_ndimage
|
|
531
|
+
|
|
532
|
+
# Extract 2D slice if z is provided, otherwise use the image directly
|
|
533
|
+
if z is not None:
|
|
534
|
+
image_2d = cp.asarray(self.image_3d[z, :, :])
|
|
535
|
+
else:
|
|
536
|
+
# Assuming image_2d is already available or passed
|
|
537
|
+
image_2d = cp.asarray(self.image_2d)
|
|
538
|
+
|
|
539
|
+
original_shape = image_2d.shape
|
|
540
|
+
features = []
|
|
541
|
+
|
|
542
|
+
# Stage 1: Compute all base features
|
|
543
|
+
|
|
544
|
+
# Gaussian smoothing
|
|
545
|
+
gaussian_results = {}
|
|
546
|
+
for sigma in self.alphas:
|
|
547
|
+
smooth = cupy_ndimage.gaussian_filter(image_2d, sigma)
|
|
548
|
+
gaussian_results[sigma] = smooth
|
|
549
|
+
features.append(smooth)
|
|
550
|
+
|
|
551
|
+
# Difference of Gaussians
|
|
552
|
+
for (s1, s2) in self.dogs:
|
|
553
|
+
g1 = cupy_ndimage.gaussian_filter(image_2d, s1)
|
|
554
|
+
g2 = cupy_ndimage.gaussian_filter(image_2d, s2)
|
|
555
|
+
dog = g1 - g2
|
|
556
|
+
features.append(dog)
|
|
557
|
+
|
|
558
|
+
# Local statistics using 2D kernel
|
|
559
|
+
window_size = self.windows
|
|
560
|
+
kernel = cp.ones((window_size, window_size)) / (window_size**2)
|
|
561
|
+
|
|
562
|
+
# Local mean
|
|
563
|
+
local_mean = cupy_ndimage.convolve(image_2d, kernel, mode='reflect')
|
|
564
|
+
features.append(local_mean)
|
|
565
|
+
|
|
566
|
+
# Local variance
|
|
567
|
+
mean = cp.mean(image_2d)
|
|
568
|
+
local_var = cupy_ndimage.convolve((image_2d - mean)**2, kernel, mode='reflect')
|
|
569
|
+
features.append(local_var)
|
|
570
|
+
|
|
571
|
+
# First-order gradients
|
|
572
|
+
gx = cupy_ndimage.sobel(image_2d, axis=1, mode='reflect') # x direction
|
|
573
|
+
gy = cupy_ndimage.sobel(image_2d, axis=0, mode='reflect') # y direction
|
|
574
|
+
|
|
575
|
+
# Gradient magnitude
|
|
576
|
+
gradient_magnitude = cp.sqrt(gx**2 + gy**2)
|
|
577
|
+
features.append(gradient_magnitude)
|
|
578
|
+
|
|
579
|
+
# Stage 2: Compute derived features
|
|
580
|
+
|
|
581
|
+
# Second-order gradients
|
|
582
|
+
gxx = cupy_ndimage.sobel(gx, axis=1, mode='reflect')
|
|
583
|
+
gyy = cupy_ndimage.sobel(gy, axis=0, mode='reflect')
|
|
584
|
+
|
|
585
|
+
# Cross derivatives for Hessian determinant
|
|
586
|
+
gxy = cupy_ndimage.sobel(gx, axis=0, mode='reflect')
|
|
587
|
+
gyx = cupy_ndimage.sobel(gy, axis=1, mode='reflect')
|
|
588
|
+
|
|
589
|
+
# Laplacian (sum of second derivatives)
|
|
590
|
+
laplacian = gxx + gyy
|
|
591
|
+
features.append(laplacian)
|
|
592
|
+
|
|
593
|
+
# Hessian determinant
|
|
594
|
+
hessian_det = gxx * gyy - gxy * gyx
|
|
595
|
+
features.append(hessian_det)
|
|
596
|
+
|
|
597
|
+
# Verify shapes
|
|
598
|
+
for i, feat in enumerate(features):
|
|
599
|
+
if feat.shape != original_shape:
|
|
600
|
+
# Check dimensionality and expand if needed
|
|
601
|
+
if len(feat.shape) < len(original_shape):
|
|
602
|
+
feat_adjusted = feat
|
|
603
|
+
missing_dims = len(original_shape) - len(feat.shape)
|
|
604
|
+
for _ in range(missing_dims):
|
|
605
|
+
feat_adjusted = cp.expand_dims(feat_adjusted, axis=0)
|
|
606
|
+
|
|
607
|
+
if feat_adjusted.shape != original_shape:
|
|
608
|
+
raise ValueError(f"Feature {i} has shape {feat.shape}, expected {original_shape}")
|
|
609
|
+
|
|
610
|
+
features[i] = feat_adjusted
|
|
611
|
+
|
|
612
|
+
# Stack all features along a new dimension
|
|
613
|
+
result = cp.stack(features, axis=-1)
|
|
614
|
+
|
|
615
|
+
# Optional: Return as numpy array if needed
|
|
616
|
+
# result = cp.asnumpy(result)
|
|
617
|
+
|
|
618
|
+
return result
|
|
619
|
+
|
|
236
620
|
def segment_volume(self, array, chunk_size=None, gpu=True):
|
|
237
621
|
"""Segment volume using parallel processing of chunks with vectorized chunk creation"""
|
|
238
622
|
|
|
@@ -242,53 +626,129 @@ class InteractiveSegmenter:
|
|
|
242
626
|
self.map_slice = None
|
|
243
627
|
chunk_size = self.master_chunk
|
|
244
628
|
|
|
245
|
-
|
|
246
|
-
|
|
629
|
+
def create_2d_chunks():
|
|
630
|
+
"""
|
|
631
|
+
Create chunks by z-slices for 2D processing.
|
|
632
|
+
Each chunk is a complete z-slice with all y,x coordinates,
|
|
633
|
+
unless the slice exceeds 262144 pixels, in which case it's divided into subchunks.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
List of chunks where each chunk contains the parameters needed for processing.
|
|
637
|
+
Format depends on subchunking:
|
|
638
|
+
- No subchunking: [y_dim, x_dim, z, total_pixels, None]
|
|
639
|
+
- Y subchunking: [y_dim, x_dim, z, None, ['y', start_y, end_y]]
|
|
640
|
+
- X subchunking: [y_dim, x_dim, z, None, ['x', start_x, end_x]]
|
|
641
|
+
"""
|
|
642
|
+
MAX_CHUNK_SIZE = 262144
|
|
643
|
+
chunks = []
|
|
644
|
+
|
|
645
|
+
for z in range(self.image_3d.shape[0]):
|
|
646
|
+
# Get the dimensions of this z-slice
|
|
647
|
+
y_dim = self.image_3d.shape[1]
|
|
648
|
+
x_dim = self.image_3d.shape[2]
|
|
649
|
+
total_pixels = y_dim * x_dim
|
|
650
|
+
|
|
651
|
+
# If the slice is small enough, do not subchunk
|
|
652
|
+
if total_pixels <= MAX_CHUNK_SIZE:
|
|
653
|
+
chunks.append([z, 0, y_dim, 0, x_dim]) # [z_start, y_start, y_end, x_start, x_end]
|
|
654
|
+
else:
|
|
655
|
+
# Determine which dimension to divide (the largest one)
|
|
656
|
+
largest_dim = 'y' if y_dim >= x_dim else 'x'
|
|
657
|
+
|
|
658
|
+
# Calculate how many divisions we need
|
|
659
|
+
num_divisions = int(cp.ceil(total_pixels / MAX_CHUNK_SIZE))
|
|
660
|
+
|
|
661
|
+
# Calculate the approx size of each division along the largest dimension
|
|
662
|
+
if largest_dim == 'y':
|
|
663
|
+
div_size = int(cp.ceil(y_dim / num_divisions))
|
|
664
|
+
# Create subchunks by dividing the y-dimension
|
|
665
|
+
for i in range(0, y_dim, div_size):
|
|
666
|
+
end_i = min(i + div_size, y_dim)
|
|
667
|
+
chunks.append([z, i, end_i, 0, x_dim]) # [z, y_start, y_end, x_start, x_end]
|
|
668
|
+
else: # largest_dim == 'x'
|
|
669
|
+
div_size = int(cp.ceil(x_dim / num_divisions))
|
|
670
|
+
# Create subchunks by dividing the x-dimension
|
|
671
|
+
for i in range(0, x_dim, div_size):
|
|
672
|
+
end_i = min(i + div_size, x_dim)
|
|
673
|
+
chunks.append([z, 0, y_dim, i, end_i]) # [z, y_start, y_end, x_start, x_end]
|
|
674
|
+
|
|
675
|
+
return chunks
|
|
247
676
|
|
|
248
|
-
|
|
249
|
-
z_chunks = (self.image_3d.shape[0] + chunk_size - 1) // chunk_size
|
|
250
|
-
y_chunks = (self.image_3d.shape[1] + chunk_size - 1) // chunk_size
|
|
251
|
-
x_chunks = (self.image_3d.shape[2] + chunk_size - 1) // chunk_size
|
|
677
|
+
print("Chunking data...")
|
|
252
678
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
679
|
+
if not self.use_two:
|
|
680
|
+
# 3D Processing - Create chunks for 3D volume
|
|
681
|
+
# Round to nearest multiple of 32 for better memory alignment
|
|
682
|
+
chunk_size = ((chunk_size + 15) // 32) * 32
|
|
683
|
+
|
|
684
|
+
# Calculate number of chunks in each dimension
|
|
685
|
+
z_chunks = (self.image_3d.shape[0] + chunk_size - 1) // chunk_size
|
|
686
|
+
y_chunks = (self.image_3d.shape[1] + chunk_size - 1) // chunk_size
|
|
687
|
+
x_chunks = (self.image_3d.shape[2] + chunk_size - 1) // chunk_size
|
|
688
|
+
|
|
689
|
+
# Create start indices for all chunks at once using CuPy
|
|
690
|
+
chunk_starts = cp.array(cp.meshgrid(
|
|
691
|
+
cp.arange(z_chunks) * chunk_size,
|
|
692
|
+
cp.arange(y_chunks) * chunk_size,
|
|
693
|
+
cp.arange(x_chunks) * chunk_size,
|
|
694
|
+
indexing='ij'
|
|
695
|
+
)).reshape(3, -1).T
|
|
696
|
+
|
|
697
|
+
chunks = []
|
|
698
|
+
for chunk_start_gpu in chunk_starts:
|
|
699
|
+
# Extract values from CuPy array
|
|
700
|
+
z_start = int(chunk_start_gpu[0]) # Convert to regular Python int
|
|
701
|
+
y_start = int(chunk_start_gpu[1])
|
|
702
|
+
x_start = int(chunk_start_gpu[2])
|
|
703
|
+
|
|
704
|
+
z_end = min(z_start + chunk_size, self.image_3d.shape[0])
|
|
705
|
+
y_end = min(y_start + chunk_size, self.image_3d.shape[1])
|
|
706
|
+
x_end = min(x_start + chunk_size, self.image_3d.shape[2])
|
|
707
|
+
|
|
708
|
+
coords = [z_start, z_end, y_start, y_end, x_start, x_end]
|
|
709
|
+
chunks.append(coords)
|
|
710
|
+
else:
|
|
711
|
+
# 2D Processing - Create chunks by z-slices
|
|
712
|
+
chunks = create_2d_chunks()
|
|
713
|
+
self.feature_cache = None # Reset feature cache for 2D processing
|
|
260
714
|
|
|
261
715
|
# Process chunks
|
|
262
716
|
print("Segmenting chunks...")
|
|
263
717
|
|
|
264
|
-
for i,
|
|
265
|
-
#
|
|
266
|
-
|
|
267
|
-
y_start = int(chunk_start_gpu[1])
|
|
268
|
-
x_start = int(chunk_start_gpu[2])
|
|
269
|
-
|
|
270
|
-
z_end = min(z_start + chunk_size, self.image_3d.shape[0])
|
|
271
|
-
y_end = min(y_start + chunk_size, self.image_3d.shape[1])
|
|
272
|
-
x_end = min(x_start + chunk_size, self.image_3d.shape[2])
|
|
273
|
-
|
|
274
|
-
coords = [z_start, z_end, y_start, y_end, x_start, x_end]
|
|
718
|
+
for i, chunk in enumerate(chunks):
|
|
719
|
+
# Process chunk - returns CuPy arrays of coordinates
|
|
720
|
+
fore_coords, _ = self.process_chunk(chunk)
|
|
275
721
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
722
|
+
if isinstance(fore_coords, list) and len(fore_coords) == 0:
|
|
723
|
+
# Skip empty results
|
|
724
|
+
pass
|
|
725
|
+
elif hasattr(fore_coords, 'shape') and len(fore_coords) > 0:
|
|
280
726
|
# Direct indexing with CuPy arrays
|
|
281
|
-
|
|
727
|
+
try:
|
|
728
|
+
array[fore_coords[:, 0], fore_coords[:, 1], fore_coords[:, 2]] = 255
|
|
729
|
+
except IndexError as e:
|
|
730
|
+
print(f"Index error when updating array: {e}")
|
|
731
|
+
# Fallback to a safer but slower approach
|
|
732
|
+
for coord in fore_coords:
|
|
733
|
+
try:
|
|
734
|
+
z, y, x = int(coord[0]), int(coord[1]), int(coord[2])
|
|
735
|
+
if 0 <= z < array.shape[0] and 0 <= y < array.shape[1] and 0 <= x < array.shape[2]:
|
|
736
|
+
array[z, y, x] = 255
|
|
737
|
+
except Exception as inner_e:
|
|
738
|
+
print(f"Error updating coordinate {coord}: {inner_e}")
|
|
739
|
+
|
|
740
|
+
# Memory management - release reference to chunk data
|
|
741
|
+
if i % 10 == 0: # Do periodic memory cleanup
|
|
742
|
+
cp.get_default_memory_pool().free_all_blocks()
|
|
282
743
|
|
|
283
|
-
print(f"Processed {i}/{len(
|
|
744
|
+
print(f"Processed {i+1}/{len(chunks)} chunks")
|
|
284
745
|
|
|
285
746
|
# Clean up GPU memory
|
|
286
747
|
cp.get_default_memory_pool().free_all_blocks()
|
|
287
748
|
|
|
288
|
-
#
|
|
749
|
+
# Convert to NumPy at the very end for return
|
|
289
750
|
return cp.asnumpy(array)
|
|
290
751
|
|
|
291
|
-
|
|
292
752
|
def update_position(self, z=None, x=None, y=None):
|
|
293
753
|
"""Update current position for chunk prioritization with safeguards"""
|
|
294
754
|
|
|
@@ -379,13 +839,13 @@ class InteractiveSegmenter:
|
|
|
379
839
|
"""Segment volume in realtime using CuPy for GPU acceleration"""
|
|
380
840
|
import cupy as cp
|
|
381
841
|
|
|
382
|
-
try:
|
|
383
|
-
from cuml.ensemble import RandomForestClassifier as cuRandomForestClassifier
|
|
384
|
-
gpu_ml_available = True
|
|
385
|
-
except:
|
|
386
|
-
print("Cannot find cuML, using CPU to segment instead...")
|
|
387
|
-
gpu_ml_available = False
|
|
388
|
-
gpu = False
|
|
842
|
+
#try:
|
|
843
|
+
#from cuml.ensemble import RandomForestClassifier as cuRandomForestClassifier
|
|
844
|
+
#gpu_ml_available = True
|
|
845
|
+
#except:
|
|
846
|
+
#print("Cannot find cuML, using CPU to segment instead...")
|
|
847
|
+
#gpu_ml_available = False
|
|
848
|
+
#gpu = False
|
|
389
849
|
|
|
390
850
|
if self.realtimechunks is None:
|
|
391
851
|
self.get_realtime_chunks()
|
|
@@ -486,11 +946,13 @@ class InteractiveSegmenter:
|
|
|
486
946
|
except Exception as e:
|
|
487
947
|
print(f"Warning: Could not clean up GPU memory: {e}")
|
|
488
948
|
|
|
489
|
-
def train_batch(self, foreground_array, speed=True, use_gpu=True, use_two=False, mem_lock=False):
|
|
949
|
+
def train_batch(self, foreground_array, speed=True, use_gpu=True, use_two=False, mem_lock=False, saving = False):
|
|
490
950
|
"""Train directly on foreground and background arrays using GPU acceleration"""
|
|
491
951
|
import cupy as cp
|
|
492
|
-
|
|
493
|
-
|
|
952
|
+
|
|
953
|
+
if not saving:
|
|
954
|
+
print("Training model...")
|
|
955
|
+
|
|
494
956
|
self.speed = speed
|
|
495
957
|
self.cur_gpu = use_gpu
|
|
496
958
|
self.realtimechunks = None # dump ram
|
|
@@ -502,110 +964,323 @@ class InteractiveSegmenter:
|
|
|
502
964
|
n_jobs=-1,
|
|
503
965
|
max_depth=None
|
|
504
966
|
)
|
|
967
|
+
|
|
968
|
+
if use_two:
|
|
969
|
+
|
|
970
|
+
#changed = [] #Track which slices need feature maps
|
|
971
|
+
|
|
972
|
+
if not self.use_two: #Clarifies if we need to redo feature cache for 2D
|
|
973
|
+
self.feature_cache = None
|
|
974
|
+
self.use_two = True
|
|
975
|
+
|
|
976
|
+
self.feature_cache = None #Decided this should reset, can remove this line to have it retain prev feature maps
|
|
977
|
+
self.two_slices = []
|
|
978
|
+
|
|
979
|
+
if self.feature_cache == None:
|
|
980
|
+
self.feature_cache = {}
|
|
981
|
+
|
|
982
|
+
foreground_array = cp.asarray(foreground_array)
|
|
983
|
+
|
|
984
|
+
# Get foreground coordinates and features
|
|
985
|
+
z_fore, y_fore, x_fore = cp.where(foreground_array == 1)
|
|
986
|
+
|
|
987
|
+
z_fore_cpu = cp.asnumpy(z_fore)
|
|
988
|
+
y_fore_cpu = cp.asnumpy(y_fore)
|
|
989
|
+
x_fore_cpu = cp.asnumpy(x_fore)
|
|
990
|
+
|
|
991
|
+
fore_coords = list(zip(z_fore_cpu, y_fore_cpu, x_fore_cpu))
|
|
992
|
+
|
|
993
|
+
# Get background coordinates and features
|
|
994
|
+
z_back, y_back, x_back = cp.where(foreground_array == 2)
|
|
995
|
+
|
|
996
|
+
z_back_cpu = cp.asnumpy(z_back)
|
|
997
|
+
y_back_cpu = cp.asnumpy(y_back)
|
|
998
|
+
x_back_cpu = cp.asnumpy(x_back)
|
|
999
|
+
|
|
1000
|
+
back_coords = list(zip(z_back_cpu, y_back_cpu, x_back_cpu))
|
|
1001
|
+
|
|
1002
|
+
foreground_features = []
|
|
1003
|
+
background_features = []
|
|
1004
|
+
|
|
1005
|
+
z_fores = self.organize_by_z(fore_coords)
|
|
1006
|
+
z_backs = self.organize_by_z(back_coords)
|
|
1007
|
+
slices = set(list(z_fores.keys()) + list(z_backs.keys()))
|
|
1008
|
+
|
|
1009
|
+
for z in slices:
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
current_map = self.get_feature_map_slice(z, speed, use_gpu)
|
|
1013
|
+
|
|
1014
|
+
if z in z_fores:
|
|
1015
|
+
|
|
1016
|
+
for y, x in z_fores[z]:
|
|
1017
|
+
# Get the feature vector for this foreground point
|
|
1018
|
+
feature_vector = current_map[y, x]
|
|
1019
|
+
|
|
1020
|
+
# Add to our collection
|
|
1021
|
+
foreground_features.append(cp.asnumpy(feature_vector))
|
|
1022
|
+
|
|
1023
|
+
if z in z_backs:
|
|
1024
|
+
|
|
1025
|
+
for y, x in z_backs[z]:
|
|
1026
|
+
# Get the feature vector for this foreground point
|
|
1027
|
+
feature_vector = current_map[y, x]
|
|
1028
|
+
|
|
1029
|
+
# Add to our collection
|
|
1030
|
+
background_features.append(cp.asnumpy(feature_vector))
|
|
1031
|
+
|
|
1032
|
+
else:
|
|
505
1033
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
# Memory-efficient approach: compute features only for necessary subarrays
|
|
509
|
-
foreground_features = []
|
|
510
|
-
background_features = []
|
|
511
|
-
|
|
512
|
-
# Convert foreground_array to CuPy array
|
|
513
|
-
foreground_array_gpu = cp.asarray(foreground_array)
|
|
514
|
-
|
|
515
|
-
# Find coordinates of foreground and background scribbles
|
|
516
|
-
z_fore = cp.argwhere(foreground_array_gpu == 1)
|
|
517
|
-
z_back = cp.argwhere(foreground_array_gpu == 2)
|
|
518
|
-
|
|
519
|
-
# Convert back to NumPy for compatibility with the rest of the code
|
|
520
|
-
z_fore_cpu = cp.asnumpy(z_fore)
|
|
521
|
-
z_back_cpu = cp.asnumpy(z_back)
|
|
522
|
-
|
|
523
|
-
# If no scribbles, return empty lists
|
|
524
|
-
if len(z_fore_cpu) == 0 and len(z_back_cpu) == 0:
|
|
525
|
-
return foreground_features, background_features
|
|
526
|
-
|
|
527
|
-
# Get dimensions of the input array
|
|
528
|
-
depth, height, width = foreground_array.shape
|
|
529
|
-
|
|
530
|
-
# Determine the minimum number of boxes needed to cover all scribbles
|
|
531
|
-
half_box = box_size // 2
|
|
532
|
-
|
|
533
|
-
# Step 1: Find the minimum set of boxes that cover all scribbles
|
|
534
|
-
# We'll divide the volume into a grid of boxes of size box_size
|
|
535
|
-
|
|
536
|
-
# Calculate how many boxes are needed in each dimension
|
|
537
|
-
z_grid_size = (depth + box_size - 1) // box_size
|
|
538
|
-
y_grid_size = (height + box_size - 1) // box_size
|
|
539
|
-
x_grid_size = (width + box_size - 1) // box_size
|
|
540
|
-
|
|
541
|
-
# Track which grid cells contain scribbles
|
|
542
|
-
grid_cells_with_scribbles = set()
|
|
543
|
-
|
|
544
|
-
# Map original coordinates to grid cells
|
|
545
|
-
for z, y, x in cp.vstack((z_fore_cpu, z_back_cpu)) if len(z_back_cpu) > 0 else z_fore_cpu:
|
|
546
|
-
grid_z = int(z // box_size)
|
|
547
|
-
grid_y = int(y // box_size)
|
|
548
|
-
grid_x = int(x // box_size)
|
|
549
|
-
grid_cells_with_scribbles.add((grid_z, grid_y, grid_x))
|
|
550
|
-
|
|
551
|
-
# Step 2: Process each grid cell that contains scribbles
|
|
552
|
-
for grid_z, grid_y, grid_x in grid_cells_with_scribbles:
|
|
553
|
-
# Calculate the boundaries of this grid cell
|
|
554
|
-
z_min = grid_z * box_size
|
|
555
|
-
y_min = grid_y * box_size
|
|
556
|
-
x_min = grid_x * box_size
|
|
1034
|
+
box_size = self.master_chunk
|
|
557
1035
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1036
|
+
# Memory-efficient approach: compute features only for necessary subarrays
|
|
1037
|
+
foreground_features = []
|
|
1038
|
+
background_features = []
|
|
561
1039
|
|
|
562
|
-
#
|
|
563
|
-
|
|
564
|
-
subarray2 = foreground_array_gpu[z_min:z_max, y_min:y_max, x_min:x_max]
|
|
1040
|
+
# Convert foreground_array to CuPy array
|
|
1041
|
+
foreground_array_gpu = cp.asarray(foreground_array)
|
|
565
1042
|
|
|
566
|
-
#
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
foreground_features
|
|
577
|
-
|
|
578
|
-
#
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
y
|
|
1043
|
+
# Find coordinates of foreground and background scribbles
|
|
1044
|
+
z_fore = cp.argwhere(foreground_array_gpu == 1)
|
|
1045
|
+
z_back = cp.argwhere(foreground_array_gpu == 2)
|
|
1046
|
+
|
|
1047
|
+
# Convert back to NumPy for compatibility with the rest of the code
|
|
1048
|
+
z_fore_cpu = cp.asnumpy(z_fore)
|
|
1049
|
+
z_back_cpu = cp.asnumpy(z_back)
|
|
1050
|
+
|
|
1051
|
+
# If no scribbles, return empty lists
|
|
1052
|
+
if len(z_fore_cpu) == 0 and len(z_back_cpu) == 0:
|
|
1053
|
+
return foreground_features, background_features
|
|
1054
|
+
|
|
1055
|
+
# Get dimensions of the input array
|
|
1056
|
+
depth, height, width = foreground_array.shape
|
|
1057
|
+
|
|
1058
|
+
# Determine the minimum number of boxes needed to cover all scribbles
|
|
1059
|
+
half_box = box_size // 2
|
|
1060
|
+
|
|
1061
|
+
# Step 1: Find the minimum set of boxes that cover all scribbles
|
|
1062
|
+
# We'll divide the volume into a grid of boxes of size box_size
|
|
1063
|
+
|
|
1064
|
+
# Calculate how many boxes are needed in each dimension
|
|
1065
|
+
z_grid_size = (depth + box_size - 1) // box_size
|
|
1066
|
+
y_grid_size = (height + box_size - 1) // box_size
|
|
1067
|
+
x_grid_size = (width + box_size - 1) // box_size
|
|
1068
|
+
|
|
1069
|
+
# Track which grid cells contain scribbles
|
|
1070
|
+
grid_cells_with_scribbles = set()
|
|
1071
|
+
|
|
1072
|
+
# Map original coordinates to grid cells
|
|
1073
|
+
for z, y, x in cp.vstack((z_fore_cpu, z_back_cpu)) if len(z_back_cpu) > 0 else z_fore_cpu:
|
|
1074
|
+
grid_z = int(z // box_size)
|
|
1075
|
+
grid_y = int(y // box_size)
|
|
1076
|
+
grid_x = int(x // box_size)
|
|
1077
|
+
grid_cells_with_scribbles.add((grid_z, grid_y, grid_x))
|
|
1078
|
+
|
|
1079
|
+
# Step 2: Process each grid cell that contains scribbles
|
|
1080
|
+
for grid_z, grid_y, grid_x in grid_cells_with_scribbles:
|
|
1081
|
+
# Calculate the boundaries of this grid cell
|
|
1082
|
+
z_min = grid_z * box_size
|
|
1083
|
+
y_min = grid_y * box_size
|
|
1084
|
+
x_min = grid_x * box_size
|
|
1085
|
+
|
|
1086
|
+
z_max = min(z_min + box_size, depth)
|
|
1087
|
+
y_max = min(y_min + box_size, height)
|
|
1088
|
+
x_max = min(x_min + box_size, width)
|
|
1089
|
+
|
|
1090
|
+
# Extract the subarray (assuming image_3d is already a CuPy array)
|
|
1091
|
+
subarray = self.image_3d[z_min:z_max, y_min:y_max, x_min:x_max]
|
|
1092
|
+
subarray2 = foreground_array_gpu[z_min:z_max, y_min:y_max, x_min:x_max]
|
|
1093
|
+
|
|
1094
|
+
# Compute features for this subarray
|
|
1095
|
+
if self.speed:
|
|
1096
|
+
subarray_features = self.compute_feature_maps_gpu(subarray)
|
|
1097
|
+
else:
|
|
1098
|
+
subarray_features = self.compute_deep_feature_maps_gpu(subarray)
|
|
1099
|
+
|
|
1100
|
+
# Extract foreground features using a direct mask comparison
|
|
1101
|
+
local_fore_coords = cp.argwhere(subarray2 == 1)
|
|
1102
|
+
for local_z, local_y, local_x in cp.asnumpy(local_fore_coords):
|
|
1103
|
+
feature = subarray_features[int(local_z), int(local_y), int(local_x)]
|
|
1104
|
+
foreground_features.append(cp.asnumpy(feature))
|
|
1105
|
+
|
|
1106
|
+
# Extract background features using a direct mask comparison
|
|
1107
|
+
local_back_coords = cp.argwhere(subarray2 == 2)
|
|
1108
|
+
for local_z, local_y, local_x in cp.asnumpy(local_back_coords):
|
|
1109
|
+
feature = subarray_features[int(local_z), int(local_y), int(local_x)]
|
|
1110
|
+
background_features.append(cp.asnumpy(feature))
|
|
1111
|
+
|
|
1112
|
+
if self.previous_foreground is not None:
|
|
1113
|
+
failed = True
|
|
1114
|
+
try:
|
|
1115
|
+
# Make sure foreground_features is a NumPy array before vstack
|
|
1116
|
+
if isinstance(foreground_features, list):
|
|
1117
|
+
foreground_features = np.array(foreground_features)
|
|
1118
|
+
|
|
1119
|
+
# Convert CuPy arrays to NumPy if necessary
|
|
1120
|
+
if hasattr(foreground_features, 'get'):
|
|
1121
|
+
foreground_features = foreground_features.get()
|
|
1122
|
+
|
|
1123
|
+
foreground_features = np.vstack([self.previous_foreground, foreground_features])
|
|
1124
|
+
failed = False
|
|
1125
|
+
except Exception as e:
|
|
1126
|
+
pass
|
|
1127
|
+
|
|
1128
|
+
try:
|
|
1129
|
+
# Make sure background_features is a NumPy array before vstack
|
|
1130
|
+
if isinstance(background_features, list):
|
|
1131
|
+
background_features = np.array(background_features)
|
|
1132
|
+
|
|
1133
|
+
# Convert CuPy arrays to NumPy if necessary
|
|
1134
|
+
if hasattr(background_features, 'get'):
|
|
1135
|
+
background_features = background_features.get()
|
|
1136
|
+
|
|
1137
|
+
background_features = np.vstack([self.previous_background, background_features])
|
|
1138
|
+
failed = False
|
|
1139
|
+
except Exception as e:
|
|
1140
|
+
pass
|
|
1141
|
+
try:
|
|
1142
|
+
# Ensure coordinate arrays are NumPy arrays
|
|
1143
|
+
if hasattr(z_fore_cpu, 'get'):
|
|
1144
|
+
z_fore_cpu = z_fore_cpu.get()
|
|
1145
|
+
if hasattr(self.previous_z_fore, 'get'):
|
|
1146
|
+
self.previous_z_fore = self.previous_z_fore.get()
|
|
1147
|
+
|
|
1148
|
+
z_fore_cpu = np.concatenate([self.previous_z_fore, z_fore_cpu])
|
|
1149
|
+
except Exception as e:
|
|
1150
|
+
pass
|
|
1151
|
+
try:
|
|
1152
|
+
# Ensure coordinate arrays are NumPy arrays
|
|
1153
|
+
if hasattr(z_back_cpu, 'get'):
|
|
1154
|
+
z_back_cpu = z_back_cpu.get()
|
|
1155
|
+
if hasattr(self.previous_z_back, 'get'):
|
|
1156
|
+
self.previous_z_back = self.previous_z_back.get()
|
|
1157
|
+
|
|
1158
|
+
z_back_cpu = np.concatenate([self.previous_z_back, z_back_cpu])
|
|
1159
|
+
except Exception as e:
|
|
1160
|
+
pass
|
|
1161
|
+
if failed:
|
|
1162
|
+
print("Could not combine new model with old loaded model. Perhaps you are trying to combine a quick model with a deep model? I cannot combine these...")
|
|
1163
|
+
|
|
1164
|
+
if saving:
|
|
1165
|
+
# Make sure to return NumPy arrays, not CuPy arrays
|
|
1166
|
+
if hasattr(foreground_features, 'get'):
|
|
1167
|
+
foreground_features = foreground_features.get()
|
|
1168
|
+
if hasattr(background_features, 'get'):
|
|
1169
|
+
background_features = background_features.get()
|
|
1170
|
+
if hasattr(z_fore_cpu, 'get'):
|
|
1171
|
+
z_fore_cpu = z_fore_cpu.get()
|
|
1172
|
+
if hasattr(z_back_cpu, 'get'):
|
|
1173
|
+
z_back_cpu = z_back_cpu.get()
|
|
1174
|
+
|
|
1175
|
+
return foreground_features, background_features, z_fore_cpu, z_back_cpu
|
|
1176
|
+
|
|
1177
|
+
# Make sure foreground_features and background_features are NumPy arrays
|
|
1178
|
+
if isinstance(foreground_features, list):
|
|
1179
|
+
foreground_features = np.array(foreground_features)
|
|
1180
|
+
elif hasattr(foreground_features, 'get'):
|
|
1181
|
+
foreground_features = foreground_features.get()
|
|
1182
|
+
|
|
1183
|
+
if isinstance(background_features, list):
|
|
1184
|
+
background_features = np.array(background_features)
|
|
1185
|
+
elif hasattr(background_features, 'get'):
|
|
1186
|
+
background_features = background_features.get()
|
|
1187
|
+
|
|
1188
|
+
# Combine features and labels for training
|
|
1189
|
+
X = np.vstack([foreground_features, background_features])
|
|
1190
|
+
y = np.hstack([np.ones(len(z_fore_cpu)), np.zeros(len(z_back_cpu))])
|
|
597
1191
|
|
|
598
1192
|
# Train the model
|
|
599
1193
|
try:
|
|
600
1194
|
self.model.fit(X, y)
|
|
601
1195
|
except Exception as e:
|
|
602
1196
|
print(f"Error during model training: {e}")
|
|
603
|
-
|
|
604
|
-
print(y)
|
|
1197
|
+
import traceback
|
|
605
1198
|
|
|
606
1199
|
self.current_speed = speed
|
|
607
1200
|
|
|
608
1201
|
# Clean up GPU memory
|
|
609
1202
|
cp.get_default_memory_pool().free_all_blocks()
|
|
610
1203
|
|
|
611
|
-
print("Done")
|
|
1204
|
+
print("Done")
|
|
1205
|
+
|
|
1206
|
+
|
|
1207
|
+
def save_model(self, file_name, foreground_array):
|
|
1208
|
+
|
|
1209
|
+
print("Saving model data")
|
|
1210
|
+
|
|
1211
|
+
foreground_features, background_features, z_fore, z_back = self.train_batch(foreground_array, speed = self.speed, use_gpu = self.use_gpu, use_two = self.use_two, mem_lock = self.mem_lock, saving = True)
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
np.savez(file_name,
|
|
1215
|
+
foreground_features=foreground_features,
|
|
1216
|
+
background_features=background_features,
|
|
1217
|
+
z_fore=z_fore,
|
|
1218
|
+
z_back=z_back,
|
|
1219
|
+
speed=self.speed,
|
|
1220
|
+
use_gpu=self.use_gpu,
|
|
1221
|
+
use_two=self.use_two,
|
|
1222
|
+
mem_lock=self.mem_lock)
|
|
1223
|
+
|
|
1224
|
+
print(f"Model data saved to {file_name}")
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def load_model(self, file_name):
|
|
1228
|
+
|
|
1229
|
+
print("Loading model data")
|
|
1230
|
+
|
|
1231
|
+
data = np.load(file_name)
|
|
1232
|
+
|
|
1233
|
+
# Unpack the arrays
|
|
1234
|
+
self.previous_foreground = data['foreground_features']
|
|
1235
|
+
self.previous_background = data['background_features']
|
|
1236
|
+
self.previous_z_fore = data['z_fore']
|
|
1237
|
+
self.previous_z_back = data['z_back']
|
|
1238
|
+
self.speed = bool(data['speed'])
|
|
1239
|
+
self.use_gpu = bool(data['use_gpu'])
|
|
1240
|
+
self.use_two = bool(data['use_two'])
|
|
1241
|
+
self.mem_lock = bool(data['mem_lock'])
|
|
1242
|
+
|
|
1243
|
+
X = np.vstack([self.previous_foreground, self.previous_background])
|
|
1244
|
+
y = np.hstack([np.ones(len(self.previous_z_fore)), np.zeros(len(self.previous_z_back))])
|
|
1245
|
+
|
|
1246
|
+
try:
|
|
1247
|
+
self.model.fit(X, y)
|
|
1248
|
+
except:
|
|
1249
|
+
print(X)
|
|
1250
|
+
print(y)
|
|
1251
|
+
|
|
1252
|
+
print("Done")
|
|
1253
|
+
|
|
1254
|
+
def get_feature_map_slice(self, z, speed, use_gpu):
|
|
1255
|
+
|
|
1256
|
+
if self._currently_segmenting is not None:
|
|
1257
|
+
return
|
|
1258
|
+
|
|
1259
|
+
if speed:
|
|
1260
|
+
output = self.compute_feature_maps_gpu_2d(z = z)
|
|
1261
|
+
|
|
1262
|
+
elif not speed:
|
|
1263
|
+
output = self.compute_deep_feature_maps_gpu_2d(z = z)
|
|
1264
|
+
|
|
1265
|
+
return output
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
|
|
1269
|
+
def organize_by_z(self, coordinates):
|
|
1270
|
+
"""
|
|
1271
|
+
Organizes a list of [z, y, x] coordinates into a dictionary of [y, x] coordinates grouped by z-value.
|
|
1272
|
+
|
|
1273
|
+
Args:
|
|
1274
|
+
coordinates: List of [z, y, x] coordinate lists
|
|
1275
|
+
|
|
1276
|
+
Returns:
|
|
1277
|
+
Dictionary with z-values as keys and lists of corresponding [y, x] coordinates as values
|
|
1278
|
+
"""
|
|
1279
|
+
z_dict = defaultdict(list)
|
|
1280
|
+
|
|
1281
|
+
for z, y, x in coordinates:
|
|
1282
|
+
z_dict[z].append((y, x))
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
return dict(z_dict) # Convert back to regular dict
|
|
1286
|
+
|