nettracer3d 1.0.7__py3-none-any.whl → 1.0.8__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/nettracer.py +1 -1
- nettracer3d/nettracer_gui.py +43 -40
- nettracer3d/segmenter.py +67 -25
- nettracer3d/segmenter_GPU.py +67 -29
- nettracer3d/stats.py +861 -0
- {nettracer3d-1.0.7.dist-info → nettracer3d-1.0.8.dist-info}/METADATA +4 -4
- {nettracer3d-1.0.7.dist-info → nettracer3d-1.0.8.dist-info}/RECORD +11 -10
- {nettracer3d-1.0.7.dist-info → nettracer3d-1.0.8.dist-info}/WHEEL +0 -0
- {nettracer3d-1.0.7.dist-info → nettracer3d-1.0.8.dist-info}/entry_points.txt +0 -0
- {nettracer3d-1.0.7.dist-info → nettracer3d-1.0.8.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-1.0.7.dist-info → nettracer3d-1.0.8.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer.py
CHANGED
|
@@ -804,7 +804,7 @@ def threshold(arr, proportion, custom_rad = None):
|
|
|
804
804
|
|
|
805
805
|
threshold_index = int(len(sorted_values) * proportion)
|
|
806
806
|
threshold_value = sorted_values[threshold_index]
|
|
807
|
-
print(f"Thresholding as if smallest_radius
|
|
807
|
+
print(f"Thresholding as if smallest_radius was assigned {threshold_value}")
|
|
808
808
|
|
|
809
809
|
|
|
810
810
|
mask = arr > threshold_value
|
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -36,6 +36,7 @@ from threading import Lock
|
|
|
36
36
|
from scipy import ndimage
|
|
37
37
|
import os
|
|
38
38
|
from . import painting
|
|
39
|
+
from . import stats as net_stats
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
|
|
@@ -425,8 +426,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
425
426
|
self.canvas.mpl_connect('button_release_event', self.on_mouse_release)
|
|
426
427
|
self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
|
|
427
428
|
|
|
428
|
-
#self.canvas.mpl_connect('button_press_event', self.on_mouse_click)
|
|
429
|
-
|
|
430
429
|
# Initialize measurement tracking
|
|
431
430
|
self.measurement_points = [] # List to store point pairs
|
|
432
431
|
self.angle_measurements = [] # NEW: List to store angle trios
|
|
@@ -879,6 +878,24 @@ class ImageViewerWindow(QMainWindow):
|
|
|
879
878
|
elif self.scroll_direction > 0 and new_value <= self.slice_slider.maximum():
|
|
880
879
|
self.slice_slider.setValue(new_value)
|
|
881
880
|
|
|
881
|
+
def evaluate_mini(self, mode = 'nodes'):
|
|
882
|
+
if mode == 'nodes':
|
|
883
|
+
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
884
|
+
self.mini_overlay = True
|
|
885
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
886
|
+
else:
|
|
887
|
+
self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
888
|
+
elif mode == 'edges':
|
|
889
|
+
|
|
890
|
+
if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
|
|
891
|
+
self.mini_overlay = True
|
|
892
|
+
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
893
|
+
else:
|
|
894
|
+
self.create_highlight_overlay(
|
|
895
|
+
node_indices=self.clicked_values['nodes'],
|
|
896
|
+
edge_indices=self.clicked_values['edges']
|
|
897
|
+
)
|
|
898
|
+
|
|
882
899
|
def create_highlight_overlay(self, node_indices=None, edge_indices=None, overlay1_indices = None, overlay2_indices = None, bounds = False):
|
|
883
900
|
"""
|
|
884
901
|
Create a binary overlay highlighting specific nodes and/or edges using parallel processing.
|
|
@@ -1705,28 +1722,12 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1705
1722
|
if edges:
|
|
1706
1723
|
edge_indices = filtered_df.iloc[:, 2].unique().tolist()
|
|
1707
1724
|
self.clicked_values['edges'] = edge_indices
|
|
1708
|
-
|
|
1709
|
-
if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
|
|
1710
|
-
self.mini_overlay = True
|
|
1711
|
-
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1712
|
-
else:
|
|
1713
|
-
self.create_highlight_overlay(
|
|
1714
|
-
node_indices=self.clicked_values['nodes'],
|
|
1715
|
-
edge_indices=self.clicked_values['edges']
|
|
1716
|
-
)
|
|
1725
|
+
self.evaluate_mini(mode = 'edges')
|
|
1717
1726
|
else:
|
|
1718
|
-
|
|
1719
|
-
self.mini_overlay = True
|
|
1720
|
-
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1721
|
-
else:
|
|
1722
|
-
self.create_highlight_overlay(
|
|
1723
|
-
node_indices=self.clicked_values['nodes'],
|
|
1724
|
-
edge_indices = self.clicked_values['edges']
|
|
1725
|
-
)
|
|
1726
|
-
|
|
1727
|
+
self.evaluate_mini()
|
|
1727
1728
|
|
|
1728
1729
|
except Exception as e:
|
|
1729
|
-
print(f"Error
|
|
1730
|
+
print(f"Error showing neighbors: {e}")
|
|
1730
1731
|
|
|
1731
1732
|
|
|
1732
1733
|
def handle_show_component(self, edges = False, nodes = True):
|
|
@@ -1797,23 +1798,10 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1797
1798
|
if edges:
|
|
1798
1799
|
edge_indices = filtered_df.iloc[:, 2].unique().tolist()
|
|
1799
1800
|
self.clicked_values['edges'] = edge_indices
|
|
1800
|
-
|
|
1801
|
-
self.mini_overlay = True
|
|
1802
|
-
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1803
|
-
else:
|
|
1804
|
-
self.create_highlight_overlay(
|
|
1805
|
-
node_indices=self.clicked_values['nodes'],
|
|
1806
|
-
edge_indices=edge_indices
|
|
1807
|
-
)
|
|
1801
|
+
self.evaluate_mini(mode = 'edges')
|
|
1808
1802
|
else:
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
1812
|
-
else:
|
|
1813
|
-
self.create_highlight_overlay(
|
|
1814
|
-
node_indices = self.clicked_values['nodes'],
|
|
1815
|
-
edge_indices = self.clicked_values['edges']
|
|
1816
|
-
)
|
|
1803
|
+
self.evaluate_mini()
|
|
1804
|
+
|
|
1817
1805
|
|
|
1818
1806
|
except Exception as e:
|
|
1819
1807
|
|
|
@@ -2640,9 +2628,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2640
2628
|
self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
|
|
2641
2629
|
self.needs_mini = False
|
|
2642
2630
|
else:
|
|
2643
|
-
self.
|
|
2631
|
+
self.evaluate_mini()
|
|
2644
2632
|
else:
|
|
2645
|
-
self.
|
|
2633
|
+
self.evaluate_mini()
|
|
2646
2634
|
|
|
2647
2635
|
|
|
2648
2636
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
@@ -4569,6 +4557,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4569
4557
|
allstats_action.triggered.connect(self.stats)
|
|
4570
4558
|
histos_action = stats_menu.addAction("Network Statistic Histograms")
|
|
4571
4559
|
histos_action.triggered.connect(self.histos)
|
|
4560
|
+
sig_action = stats_menu.addAction("Significance Testing")
|
|
4561
|
+
sig_action.triggered.connect(self.sig_test)
|
|
4572
4562
|
radial_action = stats_menu.addAction("Radial Distribution Analysis")
|
|
4573
4563
|
radial_action.triggered.connect(self.show_radial_dialog)
|
|
4574
4564
|
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
@@ -4913,6 +4903,16 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4913
4903
|
except Exception as e:
|
|
4914
4904
|
print(f"Error creating histogram selector: {e}")
|
|
4915
4905
|
|
|
4906
|
+
def sig_test(self):
|
|
4907
|
+
# Get the existing QApplication instance
|
|
4908
|
+
app = QApplication.instance()
|
|
4909
|
+
|
|
4910
|
+
# Create the statistical GUI window without starting a new event loop
|
|
4911
|
+
stats_window = net_stats.main(app)
|
|
4912
|
+
|
|
4913
|
+
# Keep a reference so it doesn't get garbage collected
|
|
4914
|
+
self.stats_window = stats_window
|
|
4915
|
+
|
|
4916
4916
|
def volumes(self):
|
|
4917
4917
|
|
|
4918
4918
|
|
|
@@ -10616,6 +10616,9 @@ class MotherDialog(QDialog):
|
|
|
10616
10616
|
|
|
10617
10617
|
except Exception as e:
|
|
10618
10618
|
|
|
10619
|
+
import traceback
|
|
10620
|
+
print(traceback.format_exc())
|
|
10621
|
+
|
|
10619
10622
|
print(f"Error finding mothers: {e}")
|
|
10620
10623
|
|
|
10621
10624
|
|
|
@@ -12360,7 +12363,7 @@ class ThresholdWindow(QMainWindow):
|
|
|
12360
12363
|
button_layout.addWidget(run_button)
|
|
12361
12364
|
|
|
12362
12365
|
# Add Cancel button for external dialog use
|
|
12363
|
-
cancel_button = QPushButton("Cancel/Skip")
|
|
12366
|
+
cancel_button = QPushButton("Cancel/Skip (Retains Selection)")
|
|
12364
12367
|
cancel_button.clicked.connect(self.cancel_processing)
|
|
12365
12368
|
button_layout.addWidget(cancel_button)
|
|
12366
12369
|
|
nettracer3d/segmenter.py
CHANGED
|
@@ -1181,12 +1181,22 @@ class InteractiveSegmenter:
|
|
|
1181
1181
|
(x[0][2] - curr_x) ** 2))
|
|
1182
1182
|
return nearest[0]
|
|
1183
1183
|
else:
|
|
1184
|
-
# 3D chunks:
|
|
1185
|
-
nearest
|
|
1184
|
+
# 3D chunks: find chunks on nearest Z-plane, then closest in X/Y
|
|
1185
|
+
# First find the nearest Z-plane among available chunks
|
|
1186
|
+
nearest_z = min(unprocessed_chunks,
|
|
1187
|
+
key=lambda x: abs(x[1]['center'][0] - curr_z))[1]['center'][0]
|
|
1188
|
+
|
|
1189
|
+
# Get all chunks on that nearest Z-plane
|
|
1190
|
+
nearest_z_chunks = [chunk for chunk in unprocessed_chunks
|
|
1191
|
+
if chunk[1]['center'][0] == nearest_z]
|
|
1192
|
+
|
|
1193
|
+
# From those chunks, find closest in X/Y
|
|
1194
|
+
nearest = min(nearest_z_chunks,
|
|
1186
1195
|
key=lambda x: sum((a - b) ** 2 for a, b in
|
|
1187
|
-
zip(x[1]['center'], (
|
|
1196
|
+
zip(x[1]['center'][1:], (curr_y, curr_x))))
|
|
1197
|
+
|
|
1188
1198
|
return nearest[0]
|
|
1189
|
-
|
|
1199
|
+
|
|
1190
1200
|
return None
|
|
1191
1201
|
|
|
1192
1202
|
while True:
|
|
@@ -1284,12 +1294,14 @@ class InteractiveSegmenter:
|
|
|
1284
1294
|
|
|
1285
1295
|
return foreground_features, background_features
|
|
1286
1296
|
|
|
1287
|
-
def compute_3d_chunks(self, chunk_size=None):
|
|
1297
|
+
def compute_3d_chunks(self, chunk_size=None, thickness=49):
|
|
1288
1298
|
"""
|
|
1289
|
-
Compute 3D chunks with consistent logic across all operations.
|
|
1299
|
+
Compute 3D chunks as rectangular prisms with consistent logic across all operations.
|
|
1300
|
+
Creates chunks that are thin in Z and square-like in X/Y dimensions.
|
|
1290
1301
|
|
|
1291
1302
|
Args:
|
|
1292
|
-
chunk_size: Optional chunk size, otherwise uses dynamic calculation
|
|
1303
|
+
chunk_size: Optional chunk size for volume calculation, otherwise uses dynamic calculation
|
|
1304
|
+
thickness: Z-dimension thickness of chunks (default: 9)
|
|
1293
1305
|
|
|
1294
1306
|
Returns:
|
|
1295
1307
|
list: List of chunk coordinates [z_start, z_end, y_start, y_end, x_start, x_end]
|
|
@@ -1313,27 +1325,57 @@ class InteractiveSegmenter:
|
|
|
1313
1325
|
except:
|
|
1314
1326
|
depth, height, width, rgb = self.image_3d.shape
|
|
1315
1327
|
|
|
1316
|
-
# Calculate chunk
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1328
|
+
# Calculate target volume per chunk (same as original cube)
|
|
1329
|
+
target_volume = chunk_size ** 3
|
|
1330
|
+
|
|
1331
|
+
# Calculate XY side length based on thickness and target volume
|
|
1332
|
+
# Volume = thickness * xy_side * xy_side
|
|
1333
|
+
# So xy_side = sqrt(volume / thickness)
|
|
1334
|
+
xy_side = int(np.sqrt(target_volume / thickness))
|
|
1335
|
+
xy_side = max(1, xy_side) # Ensure minimum size of 1
|
|
1320
1336
|
|
|
1321
|
-
#
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
np.arange(y_chunks) * chunk_size,
|
|
1325
|
-
np.arange(x_chunks) * chunk_size,
|
|
1326
|
-
indexing='ij'
|
|
1327
|
-
)).reshape(3, -1).T
|
|
1337
|
+
# Calculate actual chunk dimensions for grid calculation
|
|
1338
|
+
z_chunk_size = thickness
|
|
1339
|
+
xy_chunk_size = xy_side
|
|
1328
1340
|
|
|
1329
|
-
#
|
|
1341
|
+
# Calculate number of chunks in each dimension
|
|
1342
|
+
z_chunks = (depth + z_chunk_size - 1) // z_chunk_size
|
|
1343
|
+
y_chunks = (height + xy_chunk_size - 1) // xy_chunk_size
|
|
1344
|
+
x_chunks = (width + xy_chunk_size - 1) // xy_chunk_size
|
|
1345
|
+
|
|
1346
|
+
# Calculate actual chunk sizes to distribute remainder evenly
|
|
1347
|
+
# This ensures all chunks are roughly the same size
|
|
1348
|
+
z_sizes = np.full(z_chunks, depth // z_chunks)
|
|
1349
|
+
z_remainder = depth % z_chunks
|
|
1350
|
+
z_sizes[:z_remainder] += 1
|
|
1351
|
+
|
|
1352
|
+
y_sizes = np.full(y_chunks, height // y_chunks)
|
|
1353
|
+
y_remainder = height % y_chunks
|
|
1354
|
+
y_sizes[:y_remainder] += 1
|
|
1355
|
+
|
|
1356
|
+
x_sizes = np.full(x_chunks, width // x_chunks)
|
|
1357
|
+
x_remainder = width % x_chunks
|
|
1358
|
+
x_sizes[:x_remainder] += 1
|
|
1359
|
+
|
|
1360
|
+
# Calculate cumulative positions
|
|
1361
|
+
z_positions = np.concatenate([[0], np.cumsum(z_sizes)])
|
|
1362
|
+
y_positions = np.concatenate([[0], np.cumsum(y_sizes)])
|
|
1363
|
+
x_positions = np.concatenate([[0], np.cumsum(x_sizes)])
|
|
1364
|
+
|
|
1365
|
+
# Generate all chunk coordinates
|
|
1330
1366
|
chunks = []
|
|
1331
|
-
for
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1367
|
+
for z_idx in range(z_chunks):
|
|
1368
|
+
for y_idx in range(y_chunks):
|
|
1369
|
+
for x_idx in range(x_chunks):
|
|
1370
|
+
z_start = z_positions[z_idx]
|
|
1371
|
+
z_end = z_positions[z_idx + 1]
|
|
1372
|
+
y_start = y_positions[y_idx]
|
|
1373
|
+
y_end = y_positions[y_idx + 1]
|
|
1374
|
+
x_start = x_positions[x_idx]
|
|
1375
|
+
x_end = x_positions[x_idx + 1]
|
|
1376
|
+
|
|
1377
|
+
coords = [z_start, z_end, y_start, y_end, x_start, x_end]
|
|
1378
|
+
chunks.append(coords)
|
|
1337
1379
|
|
|
1338
1380
|
return chunks
|
|
1339
1381
|
|
nettracer3d/segmenter_GPU.py
CHANGED
|
@@ -1055,19 +1055,18 @@ class InteractiveSegmenter:
|
|
|
1055
1055
|
self.realtimechunks = chunk_dict
|
|
1056
1056
|
print("Ready!")
|
|
1057
1057
|
|
|
1058
|
-
def compute_3d_chunks(self, chunk_size=None):
|
|
1058
|
+
def compute_3d_chunks(self, chunk_size=None, thickness=49):
|
|
1059
1059
|
"""
|
|
1060
|
-
Compute 3D chunks with consistent logic across all operations
|
|
1060
|
+
Compute 3D chunks as rectangular prisms with consistent logic across all operations.
|
|
1061
|
+
Creates chunks that are thin in Z and square-like in X/Y dimensions.
|
|
1061
1062
|
|
|
1062
1063
|
Args:
|
|
1063
|
-
chunk_size: Optional chunk size, otherwise uses dynamic calculation
|
|
1064
|
+
chunk_size: Optional chunk size for volume calculation, otherwise uses dynamic calculation
|
|
1065
|
+
thickness: Z-dimension thickness of chunks (default: 9)
|
|
1064
1066
|
|
|
1065
1067
|
Returns:
|
|
1066
1068
|
list: List of chunk coordinates [z_start, z_end, y_start, y_end, x_start, x_end]
|
|
1067
1069
|
"""
|
|
1068
|
-
import cupy as cp
|
|
1069
|
-
import multiprocessing
|
|
1070
|
-
|
|
1071
1070
|
# Use consistent chunk size calculation
|
|
1072
1071
|
if chunk_size is None:
|
|
1073
1072
|
if hasattr(self, 'master_chunk') and self.master_chunk is not None:
|
|
@@ -1075,10 +1074,10 @@ class InteractiveSegmenter:
|
|
|
1075
1074
|
else:
|
|
1076
1075
|
# Dynamic calculation (same as segmentation)
|
|
1077
1076
|
total_cores = multiprocessing.cpu_count()
|
|
1078
|
-
total_volume =
|
|
1077
|
+
total_volume = np.prod(self.image_3d.shape)
|
|
1079
1078
|
target_volume_per_chunk = total_volume / (total_cores * 4)
|
|
1080
1079
|
|
|
1081
|
-
chunk_size = int(
|
|
1080
|
+
chunk_size = int(np.cbrt(target_volume_per_chunk))
|
|
1082
1081
|
chunk_size = max(16, min(chunk_size, min(self.image_3d.shape) // 2))
|
|
1083
1082
|
chunk_size = ((chunk_size + 7) // 16) * 16
|
|
1084
1083
|
|
|
@@ -1087,28 +1086,57 @@ class InteractiveSegmenter:
|
|
|
1087
1086
|
except:
|
|
1088
1087
|
depth, height, width, rgb = self.image_3d.shape
|
|
1089
1088
|
|
|
1090
|
-
# Calculate chunk
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1089
|
+
# Calculate target volume per chunk (same as original cube)
|
|
1090
|
+
target_volume = chunk_size ** 3
|
|
1091
|
+
|
|
1092
|
+
# Calculate XY side length based on thickness and target volume
|
|
1093
|
+
# Volume = thickness * xy_side * xy_side
|
|
1094
|
+
# So xy_side = sqrt(volume / thickness)
|
|
1095
|
+
xy_side = int(np.sqrt(target_volume / thickness))
|
|
1096
|
+
xy_side = max(1, xy_side) # Ensure minimum size of 1
|
|
1097
|
+
|
|
1098
|
+
# Calculate actual chunk dimensions for grid calculation
|
|
1099
|
+
z_chunk_size = thickness
|
|
1100
|
+
xy_chunk_size = xy_side
|
|
1101
|
+
|
|
1102
|
+
# Calculate number of chunks in each dimension
|
|
1103
|
+
z_chunks = (depth + z_chunk_size - 1) // z_chunk_size
|
|
1104
|
+
y_chunks = (height + xy_chunk_size - 1) // xy_chunk_size
|
|
1105
|
+
x_chunks = (width + xy_chunk_size - 1) // xy_chunk_size
|
|
1106
|
+
|
|
1107
|
+
# Calculate actual chunk sizes to distribute remainder evenly
|
|
1108
|
+
# This ensures all chunks are roughly the same size
|
|
1109
|
+
z_sizes = np.full(z_chunks, depth // z_chunks)
|
|
1110
|
+
z_remainder = depth % z_chunks
|
|
1111
|
+
z_sizes[:z_remainder] += 1
|
|
1094
1112
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
cp.arange(y_chunks) * chunk_size,
|
|
1099
|
-
cp.arange(x_chunks) * chunk_size,
|
|
1100
|
-
indexing='ij'
|
|
1101
|
-
)).reshape(3, -1).T
|
|
1113
|
+
y_sizes = np.full(y_chunks, height // y_chunks)
|
|
1114
|
+
y_remainder = height % y_chunks
|
|
1115
|
+
y_sizes[:y_remainder] += 1
|
|
1102
1116
|
|
|
1117
|
+
x_sizes = np.full(x_chunks, width // x_chunks)
|
|
1118
|
+
x_remainder = width % x_chunks
|
|
1119
|
+
x_sizes[:x_remainder] += 1
|
|
1103
1120
|
|
|
1104
|
-
#
|
|
1121
|
+
# Calculate cumulative positions
|
|
1122
|
+
z_positions = np.concatenate([[0], np.cumsum(z_sizes)])
|
|
1123
|
+
y_positions = np.concatenate([[0], np.cumsum(y_sizes)])
|
|
1124
|
+
x_positions = np.concatenate([[0], np.cumsum(x_sizes)])
|
|
1125
|
+
|
|
1126
|
+
# Generate all chunk coordinates
|
|
1105
1127
|
chunks = []
|
|
1106
|
-
for
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1128
|
+
for z_idx in range(z_chunks):
|
|
1129
|
+
for y_idx in range(y_chunks):
|
|
1130
|
+
for x_idx in range(x_chunks):
|
|
1131
|
+
z_start = z_positions[z_idx]
|
|
1132
|
+
z_end = z_positions[z_idx + 1]
|
|
1133
|
+
y_start = y_positions[y_idx]
|
|
1134
|
+
y_end = y_positions[y_idx + 1]
|
|
1135
|
+
x_start = x_positions[x_idx]
|
|
1136
|
+
x_end = x_positions[x_idx + 1]
|
|
1137
|
+
|
|
1138
|
+
coords = [z_start, z_end, y_start, y_end, x_start, x_end]
|
|
1139
|
+
chunks.append(coords)
|
|
1112
1140
|
|
|
1113
1141
|
return chunks
|
|
1114
1142
|
|
|
@@ -1196,10 +1224,20 @@ class InteractiveSegmenter:
|
|
|
1196
1224
|
(x[0][2] - curr_x) ** 2))
|
|
1197
1225
|
return nearest[0]
|
|
1198
1226
|
else:
|
|
1199
|
-
# 3D chunks:
|
|
1200
|
-
nearest
|
|
1227
|
+
# 3D chunks: find chunks on nearest Z-plane, then closest in X/Y
|
|
1228
|
+
# First find the nearest Z-plane among available chunks
|
|
1229
|
+
nearest_z = min(unprocessed_chunks,
|
|
1230
|
+
key=lambda x: abs(x[1]['center'][0] - curr_z))[1]['center'][0]
|
|
1231
|
+
|
|
1232
|
+
# Get all chunks on that nearest Z-plane
|
|
1233
|
+
nearest_z_chunks = [chunk for chunk in unprocessed_chunks
|
|
1234
|
+
if chunk[1]['center'][0] == nearest_z]
|
|
1235
|
+
|
|
1236
|
+
# From those chunks, find closest in X/Y
|
|
1237
|
+
nearest = min(nearest_z_chunks,
|
|
1201
1238
|
key=lambda x: sum((a - b) ** 2 for a, b in
|
|
1202
|
-
zip(x[1]['center'], (
|
|
1239
|
+
zip(x[1]['center'][1:], (curr_y, curr_x))))
|
|
1240
|
+
|
|
1203
1241
|
return nearest[0]
|
|
1204
1242
|
|
|
1205
1243
|
return None
|