nettracer3d 0.7.3__py3-none-any.whl → 0.7.5__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.
@@ -0,0 +1,1286 @@
1
+ import numpy as np
2
+ #try:
3
+ #import torch
4
+ #except:
5
+ #pass
6
+ import cupy as cp
7
+ import cupyx.scipy.ndimage as cpx
8
+ #try:
9
+ #from cuml.ensemble import RandomForestClassifier as cuRandomForestClassifier
10
+ #except:
11
+ #pass
12
+ import concurrent.futures
13
+ from concurrent.futures import ThreadPoolExecutor
14
+ import threading
15
+ from scipy import ndimage
16
+ import multiprocessing
17
+ from sklearn.ensemble import RandomForestClassifier
18
+ from collections import defaultdict
19
+
20
+ class InteractiveSegmenter:
21
+ def __init__(self, image_3d):
22
+ image_3d = cp.asarray(image_3d)
23
+ self.image_3d = image_3d
24
+ self.patterns = []
25
+
26
+ self.model = RandomForestClassifier(
27
+ n_estimators=100,
28
+ n_jobs=-1,
29
+ max_depth=None
30
+ )
31
+
32
+ self.feature_cache = None
33
+ self.lock = threading.Lock()
34
+ self._currently_segmenting = None
35
+ self.use_gpu = True
36
+
37
+ # Current position attributes
38
+ self.current_z = None
39
+ self.current_x = None
40
+ self.current_y = None
41
+
42
+ self.realtimechunks = None
43
+ self.current_speed = False
44
+
45
+ # Tracking if we're using 2d or 3d segs
46
+ self.use_two = False
47
+ self.two_slices = []
48
+ self.speed = True
49
+ self.cur_gpu = False
50
+ self.map_slice = None
51
+ self.prev_z = None
52
+ self.previewing = False
53
+
54
+ # flags to track state
55
+ self._currently_processing = False
56
+ self._skip_next_update = False
57
+ self._last_processed_slice = None
58
+ self.mem_lock = False
59
+
60
+ #Adjustable feature map params:
61
+ self.alphas = [1,2,4,8]
62
+ self.windows = 10
63
+ self.dogs = [(1, 2), (2, 4), (4, 8)]
64
+ self.master_chunk = 49
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
+
206
+ def process_chunk(self, chunk_coords):
207
+ """Process a chunk staying in CuPy as much as possible"""
208
+
209
+ foreground_coords = [] # Keep as list of CuPy coordinates
210
+ background_coords = []
211
+
212
+ if self.previewing or not self.use_two:
213
+
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()
241
+
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
+
268
+ else:
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
339
+
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)
343
+
344
+ # Create meshgrid
345
+ y_coords, x_coords = cp.meshgrid(y_range, x_range, indexing='ij')
346
+
347
+ # Calculate total size
348
+ total_size = len(y_range) * len(x_range)
349
+
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
+ ))
356
+
357
+ return slice_coords
358
+
359
+ def compute_feature_maps_gpu(self, image_3d=None):
360
+ """Compute feature maps using GPU with CuPy"""
361
+ import cupy as cp
362
+ import cupyx.scipy.ndimage as cupy_ndimage
363
+
364
+ features = []
365
+ if image_3d is None:
366
+ image_3d = self.image_3d # Assuming this is already a cupy array
367
+
368
+ original_shape = image_3d.shape
369
+
370
+ # Gaussian smoothing at different scales
371
+ for sigma in self.alphas:
372
+ smooth = cupy_ndimage.gaussian_filter(image_3d, sigma)
373
+ features.append(smooth)
374
+
375
+ # Difference of Gaussians
376
+ for (s1, s2) in self.dogs:
377
+ g1 = cupy_ndimage.gaussian_filter(image_3d, s1)
378
+ g2 = cupy_ndimage.gaussian_filter(image_3d, s2)
379
+ dog = g1 - g2
380
+ features.append(dog)
381
+
382
+ # Gradient computations using cupyx
383
+ gx = cupy_ndimage.sobel(image_3d, axis=2, mode='reflect') # x direction
384
+ gy = cupy_ndimage.sobel(image_3d, axis=1, mode='reflect') # y direction
385
+ gz = cupy_ndimage.sobel(image_3d, axis=0, mode='reflect') # z direction
386
+
387
+ # Gradient magnitude
388
+ gradient_magnitude = cp.sqrt(gx**2 + gy**2 + gz**2)
389
+ features.append(gradient_magnitude)
390
+
391
+ # Verify shapes
392
+ for i, feat in enumerate(features):
393
+ if feat.shape != original_shape:
394
+ feat_adjusted = cp.expand_dims(feat, axis=0)
395
+ if feat_adjusted.shape != original_shape:
396
+ raise ValueError(f"Feature {i} has shape {feat.shape}, expected {original_shape}")
397
+ features[i] = feat_adjusted
398
+
399
+ return cp.stack(features, axis=-1)
400
+
401
+ def compute_deep_feature_maps_gpu(self, image_3d=None):
402
+ """Compute feature maps using GPU"""
403
+ import cupy as cp
404
+ import cupyx.scipy.ndimage as cupy_ndimage
405
+
406
+ features = []
407
+ if image_3d is None:
408
+ image_3d = self.image_3d # Assuming this is already a cupy array
409
+ original_shape = image_3d.shape
410
+
411
+ # Gaussian and DoG using cupyx
412
+ for sigma in self.alphas:
413
+ smooth = cupy_ndimage.gaussian_filter(image_3d, sigma)
414
+ features.append(smooth)
415
+
416
+ # Difference of Gaussians
417
+ for (s1, s2) in self.dogs:
418
+ g1 = cupy_ndimage.gaussian_filter(image_3d, s1)
419
+ g2 = cupy_ndimage.gaussian_filter(image_3d, s2)
420
+ dog = g1 - g2
421
+ features.append(dog)
422
+
423
+ # Local statistics using cupyx's convolve
424
+ window_size = self.windows
425
+ kernel = cp.ones((window_size, window_size, window_size)) / (window_size**3)
426
+
427
+ # Local mean
428
+ local_mean = cupy_ndimage.convolve(image_3d, kernel, mode='reflect')
429
+ features.append(local_mean)
430
+
431
+ # Local variance
432
+ mean = cp.mean(image_3d)
433
+ local_var = cupy_ndimage.convolve((image_3d - mean)**2, kernel, mode='reflect')
434
+ features.append(local_var)
435
+
436
+ # Gradient computations using cupyx
437
+ gx = cupy_ndimage.sobel(image_3d, axis=2, mode='reflect')
438
+ gy = cupy_ndimage.sobel(image_3d, axis=1, mode='reflect')
439
+ gz = cupy_ndimage.sobel(image_3d, axis=0, mode='reflect')
440
+
441
+ # Gradient magnitude
442
+ gradient_magnitude = cp.sqrt(gx**2 + gy**2 + gz**2)
443
+ features.append(gradient_magnitude)
444
+
445
+ # Second-order gradients
446
+ gxx = cupy_ndimage.sobel(gx, axis=2, mode='reflect')
447
+ gyy = cupy_ndimage.sobel(gy, axis=1, mode='reflect')
448
+ gzz = cupy_ndimage.sobel(gz, axis=0, mode='reflect')
449
+
450
+ # Laplacian (sum of second derivatives)
451
+ laplacian = gxx + gyy + gzz
452
+ features.append(laplacian)
453
+
454
+ # Hessian determinant
455
+ hessian_det = gxx * gyy * gzz
456
+ features.append(hessian_det)
457
+
458
+ # Verify shapes
459
+ for i, feat in enumerate(features):
460
+ if feat.shape != original_shape:
461
+ feat_adjusted = cp.expand_dims(feat, axis=0)
462
+ if feat_adjusted.shape != original_shape:
463
+ raise ValueError(f"Feature {i} has shape {feat.shape}, expected {original_shape}")
464
+ features[i] = feat_adjusted
465
+
466
+ return cp.stack(features, axis=-1)
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
+
620
+ def segment_volume(self, array, chunk_size=None, gpu=True):
621
+ """Segment volume using parallel processing of chunks with vectorized chunk creation"""
622
+
623
+ array = cp.asarray(array) # Ensure CuPy array
624
+
625
+ self.realtimechunks = None
626
+ self.map_slice = None
627
+ chunk_size = self.master_chunk
628
+
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
676
+
677
+ print("Chunking data...")
678
+
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
714
+
715
+ # Process chunks
716
+ print("Segmenting chunks...")
717
+
718
+ for i, chunk in enumerate(chunks):
719
+ # Process chunk - returns CuPy arrays of coordinates
720
+ fore_coords, _ = self.process_chunk(chunk)
721
+
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:
726
+ # Direct indexing with CuPy arrays
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()
743
+
744
+ print(f"Processed {i+1}/{len(chunks)} chunks")
745
+
746
+ # Clean up GPU memory
747
+ cp.get_default_memory_pool().free_all_blocks()
748
+
749
+ # Convert to NumPy at the very end for return
750
+ return cp.asnumpy(array)
751
+
752
+ def update_position(self, z=None, x=None, y=None):
753
+ """Update current position for chunk prioritization with safeguards"""
754
+
755
+ # Check if we should skip this update
756
+ if hasattr(self, '_skip_next_update') and self._skip_next_update:
757
+ self._skip_next_update = False
758
+ return
759
+
760
+ # Store the previous z-position if not set
761
+ if not hasattr(self, 'prev_z') or self.prev_z is None:
762
+ self.prev_z = z
763
+
764
+ # Check if currently processing - if so, only update position but don't trigger map_slice changes
765
+ if hasattr(self, '_currently_processing') and self._currently_processing:
766
+ self.current_z = z
767
+ self.current_x = x
768
+ self.current_y = y
769
+ self.prev_z = z
770
+ return
771
+
772
+ # Update current positions
773
+ self.current_z = z
774
+ self.current_x = x
775
+ self.current_y = y
776
+
777
+ # Only clear map_slice if z changes and we're not already generating a new one
778
+ if self.current_z != self.prev_z:
779
+ # Instead of setting to None, check if we already have it in the cache
780
+ if hasattr(self, 'feature_cache') and self.feature_cache is not None:
781
+ if self.current_z not in self.feature_cache:
782
+ self.map_slice = None
783
+ self._currently_segmenting = None
784
+
785
+ # Update previous z
786
+ self.prev_z = z
787
+
788
+
789
+ def get_realtime_chunks(self, chunk_size=49):
790
+
791
+ # Determine if we need to chunk XY planes
792
+ small_dims = (self.image_3d.shape[1] <= chunk_size and
793
+ self.image_3d.shape[2] <= chunk_size)
794
+ few_z = self.image_3d.shape[0] <= 100 # arbitrary threshold
795
+
796
+ # If small enough, each Z is one chunk
797
+ if small_dims and few_z:
798
+ chunk_size_xy = max(self.image_3d.shape[1], self.image_3d.shape[2])
799
+ else:
800
+ chunk_size_xy = chunk_size
801
+
802
+ # Calculate chunks for XY plane
803
+ y_chunks = (self.image_3d.shape[1] + chunk_size_xy - 1) // chunk_size_xy
804
+ x_chunks = (self.image_3d.shape[2] + chunk_size_xy - 1) // chunk_size_xy
805
+
806
+ # Populate chunk dictionary
807
+ chunk_dict = {}
808
+
809
+ # Create chunks for each Z plane
810
+ for z in range(self.image_3d.shape[0]):
811
+ if small_dims:
812
+
813
+ chunk_dict[(z, 0, 0)] = {
814
+ 'coords': [0, self.image_3d.shape[1], 0, self.image_3d.shape[2]],
815
+ 'processed': False,
816
+ 'z': z
817
+ }
818
+ else:
819
+ # Multiple chunks per Z
820
+ for y_chunk in range(y_chunks):
821
+ for x_chunk in range(x_chunks):
822
+ y_start = y_chunk * chunk_size_xy
823
+ x_start = x_chunk * chunk_size_xy
824
+ y_end = min(y_start + chunk_size_xy, self.image_3d.shape[1])
825
+ x_end = min(x_start + chunk_size_xy, self.image_3d.shape[2])
826
+
827
+ chunk_dict[(z, y_start, x_start)] = {
828
+ 'coords': [y_start, y_end, x_start, x_end],
829
+ 'processed': False,
830
+ 'z': z
831
+ }
832
+
833
+ self.realtimechunks = chunk_dict
834
+
835
+ print("Ready!")
836
+
837
+
838
+ def segment_volume_realtime(self, gpu=True):
839
+ """Segment volume in realtime using CuPy for GPU acceleration"""
840
+ import cupy as cp
841
+
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
849
+
850
+ if self.realtimechunks is None:
851
+ self.get_realtime_chunks()
852
+ else:
853
+ for chunk_pos in self.realtimechunks: # chunk_pos is the (z, y_start, x_start) tuple
854
+ self.realtimechunks[chunk_pos]['processed'] = False
855
+
856
+ chunk_dict = self.realtimechunks
857
+
858
+ def get_nearest_unprocessed_chunk(self):
859
+ """Get nearest unprocessed chunk prioritizing current Z"""
860
+ curr_z = self.current_z if self.current_z is not None else self.image_3d.shape[0] // 2
861
+ curr_y = self.current_y if self.current_y is not None else self.image_3d.shape[1] // 2
862
+ curr_x = self.current_x if self.current_x is not None else self.image_3d.shape[2] // 2
863
+
864
+ # First try to find chunks at current Z
865
+ current_z_chunks = [(pos, info) for pos, info in chunk_dict.items()
866
+ if pos[0] == curr_z and not info['processed']]
867
+
868
+ if current_z_chunks:
869
+ # Find nearest chunk in current Z plane using the chunk positions from the key
870
+ nearest = min(current_z_chunks,
871
+ key=lambda x: ((x[0][1] - curr_y) ** 2 +
872
+ (x[0][2] - curr_x) ** 2))
873
+ return nearest[0]
874
+
875
+ # If no chunks at current Z, find nearest Z with available chunks
876
+ available_z = sorted(
877
+ [(pos[0], pos) for pos, info in chunk_dict.items()
878
+ if not info['processed']],
879
+ key=lambda x: abs(x[0] - curr_z)
880
+ )
881
+
882
+ if available_z:
883
+ target_z = available_z[0][0]
884
+ # Find nearest chunk in target Z plane
885
+ z_chunks = [(pos, info) for pos, info in chunk_dict.items()
886
+ if pos[0] == target_z and not info['processed']]
887
+ nearest = min(z_chunks,
888
+ key=lambda x: ((x[0][1] - curr_y) ** 2 +
889
+ (x[0][2] - curr_x) ** 2))
890
+ return nearest[0]
891
+
892
+ return None
893
+
894
+ while True:
895
+ # Find nearest unprocessed chunk using class attributes
896
+ chunk_idx = get_nearest_unprocessed_chunk(self)
897
+ if chunk_idx is None:
898
+ break
899
+
900
+ # Process the chunk directly
901
+ chunk = chunk_dict[chunk_idx]
902
+ chunk['processed'] = True
903
+ coords = chunk['coords']
904
+
905
+ # Use CuPy for meshgrid
906
+ coords_array = cp.stack(cp.meshgrid(
907
+ cp.array([chunk['z']]),
908
+ cp.arange(coords[0], coords[1]),
909
+ cp.arange(coords[2], coords[3]),
910
+ indexing='ij'
911
+ )).reshape(3, -1).T
912
+
913
+ # Convert to CPU for further processing - add cp.asnumpy() here
914
+ coords = list(map(tuple, cp.asnumpy(coords_array)))
915
+
916
+ # Process the chunk directly based on whether GPU is available
917
+ fore, back = self.process_chunk(coords)
918
+
919
+ # Yield the results
920
+ yield cp.asnumpy(fore), cp.asnumpy(back)
921
+
922
+
923
+ def cleanup(self):
924
+ """Clean up GPU memory"""
925
+ import cupy as cp
926
+
927
+ try:
928
+ # Force garbage collection first
929
+ import gc
930
+ gc.collect()
931
+
932
+ # Clean up CuPy memory pools
933
+ mempool = cp.get_default_memory_pool()
934
+ pinned_mempool = cp.get_default_pinned_memory_pool()
935
+
936
+ # Print memory usage before cleanup (optional)
937
+ # print(f"Used GPU memory: {mempool.used_bytes() / 1024**2:.2f} MB")
938
+
939
+ # Free all blocks
940
+ mempool.free_all_blocks()
941
+ pinned_mempool.free_all_blocks()
942
+
943
+ # Print memory usage after cleanup (optional)
944
+ # print(f"Used GPU memory after cleanup: {mempool.used_bytes() / 1024**2:.2f} MB")
945
+
946
+ except Exception as e:
947
+ print(f"Warning: Could not clean up GPU memory: {e}")
948
+
949
+ def train_batch(self, foreground_array, speed=True, use_gpu=True, use_two=False, mem_lock=False, saving = False):
950
+ """Train directly on foreground and background arrays using GPU acceleration"""
951
+ import cupy as cp
952
+
953
+ if not saving:
954
+ print("Training model...")
955
+
956
+ self.speed = speed
957
+ self.cur_gpu = use_gpu
958
+ self.realtimechunks = None # dump ram
959
+
960
+ self.mem_lock = mem_lock
961
+
962
+ self.model = RandomForestClassifier(
963
+ n_estimators=100,
964
+ n_jobs=-1,
965
+ max_depth=None
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:
1033
+
1034
+ box_size = self.master_chunk
1035
+
1036
+ # Memory-efficient approach: compute features only for necessary subarrays
1037
+ foreground_features = []
1038
+ background_features = []
1039
+
1040
+ # Convert foreground_array to CuPy array
1041
+ foreground_array_gpu = cp.asarray(foreground_array)
1042
+
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))])
1191
+
1192
+ # Train the model
1193
+ try:
1194
+ self.model.fit(X, y)
1195
+ except Exception as e:
1196
+ print(f"Error during model training: {e}")
1197
+ import traceback
1198
+
1199
+ self.current_speed = speed
1200
+
1201
+ # Clean up GPU memory
1202
+ cp.get_default_memory_pool().free_all_blocks()
1203
+
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
+