nettracer3d 0.6.9__tar.gz → 0.7.0__tar.gz
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-0.6.9/src/nettracer3d.egg-info → nettracer3d-0.7.0}/PKG-INFO +9 -7
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/README.md +8 -6
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/pyproject.toml +1 -1
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/morphology.py +2 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/nettracer.py +176 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/nettracer_gui.py +238 -5
- nettracer3d-0.7.0/src/nettracer3d/proximity.py +649 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/segmenter.py +86 -3
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/smart_dilate.py +4 -4
- {nettracer3d-0.6.9 → nettracer3d-0.7.0/src/nettracer3d.egg-info}/PKG-INFO +9 -7
- nettracer3d-0.6.9/src/nettracer3d/proximity.py +0 -325
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/LICENSE +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/setup.cfg +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/community_extractor.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/network_analysis.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/node_draw.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/run.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d/simple_network.py +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d.egg-info/entry_points.txt +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d.egg-info/requires.txt +0 -0
- {nettracer3d-0.6.9 → nettracer3d-0.7.0}/src/nettracer3d.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
|
|
5
5
|
Author-email: Liam McLaughlin <liamm@wustl.edu>
|
|
6
6
|
Project-URL: Documentation, https://nettracer3d.readthedocs.io/en/latest/
|
|
@@ -73,12 +73,14 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
73
73
|
|
|
74
74
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
75
75
|
|
|
76
|
-
-- Version 0.
|
|
76
|
+
-- Version 0.7.0 Updates --
|
|
77
77
|
|
|
78
|
-
1.
|
|
78
|
+
1. Added new function in 'Analyze -> Stats -> Cluster Analysis'
|
|
79
|
+
* This function allows the user to create a ripley's K or H function to compare the relative clustering of two types of nodes, or of one type of node vs itself.
|
|
79
80
|
|
|
80
|
-
2. Added new
|
|
81
|
+
2. Added new function in 'Analyze -> Randomize -> Scramble Nodes'
|
|
82
|
+
* This function randomly rearranges the node (centroids) for comparison with other centroid-using methods, as a possible way to demonstrate non-random behavior.
|
|
83
|
+
* The randomize menu is likewise new and the 'Generate Equivalent Random Network' method was moved there.
|
|
81
84
|
|
|
82
|
-
3.
|
|
83
|
-
|
|
84
|
-
4. Now specifies python 3.11.
|
|
85
|
+
3. Bug fixes.
|
|
86
|
+
* Importantly fixed a bug with dt-based dilation not working in 2D, which I had accidentally introduced recently.
|
|
@@ -34,12 +34,14 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
|
|
|
34
34
|
|
|
35
35
|
NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
|
|
36
36
|
|
|
37
|
-
-- Version 0.
|
|
37
|
+
-- Version 0.7.0 Updates --
|
|
38
38
|
|
|
39
|
-
1.
|
|
39
|
+
1. Added new function in 'Analyze -> Stats -> Cluster Analysis'
|
|
40
|
+
* This function allows the user to create a ripley's K or H function to compare the relative clustering of two types of nodes, or of one type of node vs itself.
|
|
40
41
|
|
|
41
|
-
2. Added new
|
|
42
|
+
2. Added new function in 'Analyze -> Randomize -> Scramble Nodes'
|
|
43
|
+
* This function randomly rearranges the node (centroids) for comparison with other centroid-using methods, as a possible way to demonstrate non-random behavior.
|
|
44
|
+
* The randomize menu is likewise new and the 'Generate Equivalent Random Network' method was moved there.
|
|
42
45
|
|
|
43
|
-
3.
|
|
44
|
-
|
|
45
|
-
4. Now specifies python 3.11.
|
|
46
|
+
3. Bug fixes.
|
|
47
|
+
* Importantly fixed a bug with dt-based dilation not working in 2D, which I had accidentally introduced recently.
|
|
@@ -518,6 +518,7 @@ def compute_distance_transform_distance_GPU(nodes, sampling = [1,1,1]):
|
|
|
518
518
|
is_pseudo_3d = nodes.shape[0] == 1
|
|
519
519
|
if is_pseudo_3d:
|
|
520
520
|
nodes = cp.squeeze(nodes) # Convert to 2D for processing
|
|
521
|
+
del sampling[0]
|
|
521
522
|
|
|
522
523
|
# Compute the distance transform on the GPU
|
|
523
524
|
distance = cpx.distance_transform_edt(nodes, sampling = sampling)
|
|
@@ -533,6 +534,7 @@ def compute_distance_transform_distance(nodes, sampling = [1,1,1]):
|
|
|
533
534
|
is_pseudo_3d = nodes.shape[0] == 1
|
|
534
535
|
if is_pseudo_3d:
|
|
535
536
|
nodes = np.squeeze(nodes) # Convert to 2D for processing
|
|
537
|
+
del sampling[0]
|
|
536
538
|
|
|
537
539
|
# Fallback to CPU if there's an issue with GPU computation
|
|
538
540
|
distance = ndimage.distance_transform_edt(nodes, sampling = sampling)
|
|
@@ -4431,6 +4431,101 @@ class Network_3D:
|
|
|
4431
4431
|
return neighborhood_dict, proportion_dict, title1, title2, densities
|
|
4432
4432
|
|
|
4433
4433
|
|
|
4434
|
+
def get_ripley(self, root = None, targ = None, distance = 1, edgecorrect = True, bounds = None, ignore_dims = False, proportion = 0.5):
|
|
4435
|
+
|
|
4436
|
+
|
|
4437
|
+
if root is None or targ is None: #Self clustering in this case
|
|
4438
|
+
roots = self._node_centroids.values()
|
|
4439
|
+
targs = self._node_centroids.values()
|
|
4440
|
+
else:
|
|
4441
|
+
roots = []
|
|
4442
|
+
targs = []
|
|
4443
|
+
|
|
4444
|
+
for node, nodeid in self.node_identities.items(): #Otherwise we need to pull out this info
|
|
4445
|
+
if nodeid == root:
|
|
4446
|
+
roots.append(self._node_centroids[node])
|
|
4447
|
+
elif nodeid == targ:
|
|
4448
|
+
targs.append(self._node_centroids[node])
|
|
4449
|
+
|
|
4450
|
+
rooties = proximity.convert_centroids_to_array(roots, xy_scale = self.xy_scale, z_scale = self.z_scale)
|
|
4451
|
+
targs = proximity.convert_centroids_to_array(roots, xy_scale = self.xy_scale, z_scale = self.z_scale)
|
|
4452
|
+
points_array = np.vstack((rooties, targs))
|
|
4453
|
+
del rooties
|
|
4454
|
+
|
|
4455
|
+
try:
|
|
4456
|
+
if self.nodes.shape[0] == 1:
|
|
4457
|
+
dim = 2
|
|
4458
|
+
else:
|
|
4459
|
+
dim = 3
|
|
4460
|
+
except:
|
|
4461
|
+
dim = 2
|
|
4462
|
+
for centroid in self.node_centroids.values():
|
|
4463
|
+
if centroid[0] != 0:
|
|
4464
|
+
dim = 3
|
|
4465
|
+
break
|
|
4466
|
+
|
|
4467
|
+
|
|
4468
|
+
if ignore_dims:
|
|
4469
|
+
|
|
4470
|
+
factor = 0.25
|
|
4471
|
+
|
|
4472
|
+
|
|
4473
|
+
if bounds is None:
|
|
4474
|
+
if dim == 2:
|
|
4475
|
+
min_coords = np.array([0,0])
|
|
4476
|
+
else:
|
|
4477
|
+
min_coords = np.array([0,0,0])
|
|
4478
|
+
max_coords = np.max(points_array, axis=0)
|
|
4479
|
+
max_coords = np.flip(max_coords)
|
|
4480
|
+
bounds = (min_coords, max_coords)
|
|
4481
|
+
else:
|
|
4482
|
+
min_coords, max_coords = bounds
|
|
4483
|
+
|
|
4484
|
+
dim_list = max_coords - min_coords
|
|
4485
|
+
|
|
4486
|
+
new_list = []
|
|
4487
|
+
|
|
4488
|
+
|
|
4489
|
+
if dim == 3:
|
|
4490
|
+
for centroid in roots:
|
|
4491
|
+
|
|
4492
|
+
if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor) and ((centroid[0] - min_coords[2]) > dim_list[2] * factor) and ((max_coords[2] - centroid[0]) > dim_list[2] * factor):
|
|
4493
|
+
new_list.append(centroid)
|
|
4494
|
+
#print(f"dim_list: {dim_list}, centroid: {centroid}, min_coords: {min_coords}, max_coords: {max_coords}")
|
|
4495
|
+
else:
|
|
4496
|
+
for centroid in roots:
|
|
4497
|
+
|
|
4498
|
+
if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor):
|
|
4499
|
+
new_list.append(centroid)
|
|
4500
|
+
|
|
4501
|
+
roots = new_list
|
|
4502
|
+
print(f"Utilizing {len(roots)} root points. Note that low n values are unstable.")
|
|
4503
|
+
is_subset = True
|
|
4504
|
+
else:
|
|
4505
|
+
is_subset = False
|
|
4506
|
+
|
|
4507
|
+
|
|
4508
|
+
|
|
4509
|
+
|
|
4510
|
+
roots = proximity.convert_centroids_to_array(roots, xy_scale = self.xy_scale, z_scale = self.z_scale)
|
|
4511
|
+
|
|
4512
|
+
|
|
4513
|
+
if dim == 2:
|
|
4514
|
+
roots = proximity.convert_augmented_array_to_points(roots)
|
|
4515
|
+
targs = proximity.convert_augmented_array_to_points(targs)
|
|
4516
|
+
|
|
4517
|
+
r_vals = proximity.generate_r_values(points_array, distance, bounds = bounds, dim = dim, max_proportion=proportion)
|
|
4518
|
+
|
|
4519
|
+
k_vals = proximity.optimized_ripleys_k(roots, targs, r_vals, bounds=bounds, edge_correction=edgecorrect, dim = dim, is_subset = is_subset)
|
|
4520
|
+
|
|
4521
|
+
h_vals = proximity.compute_ripleys_h(k_vals, r_vals, dim)
|
|
4522
|
+
|
|
4523
|
+
proximity.plot_ripley_functions(r_vals, k_vals, h_vals, dim)
|
|
4524
|
+
|
|
4525
|
+
return r_vals, k_vals, h_vals
|
|
4526
|
+
|
|
4527
|
+
|
|
4528
|
+
|
|
4434
4529
|
|
|
4435
4530
|
#Morphological stats or network linking:
|
|
4436
4531
|
|
|
@@ -4490,6 +4585,87 @@ class Network_3D:
|
|
|
4490
4585
|
|
|
4491
4586
|
return array
|
|
4492
4587
|
|
|
4588
|
+
|
|
4589
|
+
|
|
4590
|
+
def random_nodes(self, bounds = None, mask = None):
|
|
4591
|
+
|
|
4592
|
+
if self.nodes is not None:
|
|
4593
|
+
try:
|
|
4594
|
+
self.nodes = np.zeros_like(self.nodes)
|
|
4595
|
+
except:
|
|
4596
|
+
pass
|
|
4597
|
+
|
|
4598
|
+
|
|
4599
|
+
if mask is not None:
|
|
4600
|
+
coords = np.argwhere(mask != 0)
|
|
4601
|
+
else:
|
|
4602
|
+
if bounds is not None:
|
|
4603
|
+
(z1, y1, x1), (z2, y2, x2) = bounds
|
|
4604
|
+
z1, y1, x1 = int(z1), int(y1), int(x1)
|
|
4605
|
+
z2, y2, x2 = int(z2), int(y2), int(x2)
|
|
4606
|
+
z_range = np.arange(z1, z2 + 1)
|
|
4607
|
+
y_range = np.arange(y1, y2 + 1)
|
|
4608
|
+
x_range = np.arange(x1, x2 + 1)
|
|
4609
|
+
z_grid, y_grid, x_grid = np.meshgrid(z_range, y_range, x_range, indexing='ij')
|
|
4610
|
+
del z_range
|
|
4611
|
+
del y_range
|
|
4612
|
+
del x_range
|
|
4613
|
+
coords = np.stack([z_grid.flatten(), y_grid.flatten(), x_grid.flatten()], axis=1)
|
|
4614
|
+
del z_grid
|
|
4615
|
+
del y_grid
|
|
4616
|
+
del x_grid
|
|
4617
|
+
else:
|
|
4618
|
+
shape = ()
|
|
4619
|
+
try:
|
|
4620
|
+
shape = self.nodes.shape
|
|
4621
|
+
except:
|
|
4622
|
+
try:
|
|
4623
|
+
shape = self.edges.shape
|
|
4624
|
+
except:
|
|
4625
|
+
try:
|
|
4626
|
+
shape = self._network_overlay.shape
|
|
4627
|
+
except:
|
|
4628
|
+
try:
|
|
4629
|
+
shape = self._id_overlay.shape
|
|
4630
|
+
except:
|
|
4631
|
+
pass
|
|
4632
|
+
|
|
4633
|
+
ranges = [np.arange(s) for s in shape]
|
|
4634
|
+
|
|
4635
|
+
# Create meshgrid
|
|
4636
|
+
mesh = np.meshgrid(*ranges, indexing='ij')
|
|
4637
|
+
del ranges
|
|
4638
|
+
|
|
4639
|
+
# Stack and reshape
|
|
4640
|
+
coords = np.stack(mesh, axis=-1).reshape(-1, len(shape))
|
|
4641
|
+
del mesh
|
|
4642
|
+
|
|
4643
|
+
if len(coords) < len(self.node_centroids):
|
|
4644
|
+
print(f"Warning: Only {len(coords)} positions available for {len(self.node_centroids)} labels")
|
|
4645
|
+
|
|
4646
|
+
new_centroids = {}
|
|
4647
|
+
|
|
4648
|
+
# Generate random indices without replacement
|
|
4649
|
+
available_count = min(len(coords), len(self.node_centroids))
|
|
4650
|
+
rand_indices = np.random.choice(len(coords), available_count, replace=False)
|
|
4651
|
+
|
|
4652
|
+
# Assign random positions to labels
|
|
4653
|
+
for i, label in enumerate(self.node_centroids.keys()):
|
|
4654
|
+
if i < len(rand_indices):
|
|
4655
|
+
centroid = coords[rand_indices[i]]
|
|
4656
|
+
new_centroids[label] = centroid
|
|
4657
|
+
z, y, x = centroid
|
|
4658
|
+
try:
|
|
4659
|
+
self.nodes[z, y, x] = label
|
|
4660
|
+
except:
|
|
4661
|
+
pass
|
|
4662
|
+
|
|
4663
|
+
# Update the centroids dictionary
|
|
4664
|
+
self.node_centroids = new_centroids
|
|
4665
|
+
|
|
4666
|
+
return self.node_centroids, self._nodes
|
|
4667
|
+
|
|
4668
|
+
|
|
4493
4669
|
def community_id_info(self):
|
|
4494
4670
|
def invert_dict(d):
|
|
4495
4671
|
inverted = {}
|
|
@@ -1246,7 +1246,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1246
1246
|
except:
|
|
1247
1247
|
pass
|
|
1248
1248
|
|
|
1249
|
-
print(f"Found {len(filtered_df)} direct connections between nodes of ID {sort} and their neighbors (of any ID)")
|
|
1249
|
+
#print(f"Found {len(filtered_df)} direct connections between nodes of ID {sort} and their neighbors (of any ID)")
|
|
1250
1250
|
|
|
1251
1251
|
if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
|
|
1252
1252
|
self.mini_overlay = True
|
|
@@ -2687,8 +2687,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2687
2687
|
degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
|
|
2688
2688
|
neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
|
|
2689
2689
|
neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
|
|
2690
|
-
|
|
2691
|
-
|
|
2690
|
+
ripley_action = stats_menu.addAction("Clustering Analysis")
|
|
2691
|
+
ripley_action.triggered.connect(self.show_ripley_dialog)
|
|
2692
2692
|
vol_action = stats_menu.addAction("Calculate Volumes")
|
|
2693
2693
|
vol_action.triggered.connect(self.volumes)
|
|
2694
2694
|
rad_action = stats_menu.addAction("Calculate Radii")
|
|
@@ -2707,6 +2707,13 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2707
2707
|
id_code_action = overlay_menu.addAction("Code Identities")
|
|
2708
2708
|
id_code_action.triggered.connect(lambda: self.show_code_dialog(sort = 'Identity'))
|
|
2709
2709
|
|
|
2710
|
+
rand_menu = analysis_menu.addMenu("Randomize")
|
|
2711
|
+
random_action = rand_menu.addAction("Generate Equivalent Random Network")
|
|
2712
|
+
random_action.triggered.connect(self.show_random_dialog)
|
|
2713
|
+
random_nodes = rand_menu.addAction("Scramble Nodes (Centroids)")
|
|
2714
|
+
random_nodes.triggered.connect(self.show_randnode_dialog)
|
|
2715
|
+
|
|
2716
|
+
|
|
2710
2717
|
|
|
2711
2718
|
# Process menu
|
|
2712
2719
|
process_menu = menubar.addMenu("Process")
|
|
@@ -3501,8 +3508,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3501
3508
|
nii_img = nib.load(filename)
|
|
3502
3509
|
# Get data and transpose to match TIFF orientation
|
|
3503
3510
|
# If X needs to become Z, we move axis 2 (X) to position 0 (Z)
|
|
3504
|
-
|
|
3505
|
-
self.channel_data[channel_index] = np.transpose(
|
|
3511
|
+
arraydata = nii_img.get_fdata()
|
|
3512
|
+
self.channel_data[channel_index] = np.transpose(arraydata, (2, 1, 0))
|
|
3506
3513
|
|
|
3507
3514
|
elif file_extension in ['jpg', 'jpeg', 'png']:
|
|
3508
3515
|
from PIL import Image
|
|
@@ -4157,10 +4164,18 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4157
4164
|
dialog = NeighborIdentityDialog(self)
|
|
4158
4165
|
dialog.exec()
|
|
4159
4166
|
|
|
4167
|
+
def show_ripley_dialog(self):
|
|
4168
|
+
dialog = RipleyDialog(self)
|
|
4169
|
+
dialog.exec()
|
|
4170
|
+
|
|
4160
4171
|
def show_random_dialog(self):
|
|
4161
4172
|
dialog = RandomDialog(self)
|
|
4162
4173
|
dialog.exec()
|
|
4163
4174
|
|
|
4175
|
+
def show_randnode_dialog(self):
|
|
4176
|
+
dialog = RandNodeDialog(self)
|
|
4177
|
+
dialog.exec()
|
|
4178
|
+
|
|
4164
4179
|
def show_rad_dialog(self):
|
|
4165
4180
|
dialog = RadDialog(self)
|
|
4166
4181
|
dialog.exec()
|
|
@@ -5977,9 +5992,121 @@ class NeighborIdentityDialog(QDialog):
|
|
|
5977
5992
|
|
|
5978
5993
|
|
|
5979
5994
|
|
|
5995
|
+
class RipleyDialog(QDialog):
|
|
5996
|
+
|
|
5997
|
+
def __init__(self, parent=None):
|
|
5998
|
+
|
|
5999
|
+
super().__init__(parent)
|
|
6000
|
+
self.setWindowTitle(f"Find Ripley's H Function From Centroids")
|
|
6001
|
+
self.setModal(True)
|
|
6002
|
+
|
|
6003
|
+
layout = QFormLayout(self)
|
|
6004
|
+
|
|
6005
|
+
if my_network.node_identities is not None:
|
|
6006
|
+
self.root = QComboBox()
|
|
6007
|
+
self.root.addItems(list(set(my_network.node_identities.values())))
|
|
6008
|
+
self.root.setCurrentIndex(0)
|
|
6009
|
+
layout.addRow("Root Identity to Search for Neighbors", self.root)
|
|
6010
|
+
else:
|
|
6011
|
+
self.root = None
|
|
6012
|
+
|
|
6013
|
+
if my_network.node_identities is not None:
|
|
6014
|
+
self.targ = QComboBox()
|
|
6015
|
+
self.targ.addItems(list(set(my_network.node_identities.values())))
|
|
6016
|
+
self.targ.setCurrentIndex(0)
|
|
6017
|
+
layout.addRow("Targ Identity to be Searched For", self.targ)
|
|
6018
|
+
else:
|
|
6019
|
+
self.targ = None
|
|
6020
|
+
|
|
6021
|
+
self.distance = QLineEdit("5")
|
|
6022
|
+
layout.addRow("Bucket Distance for Searching For Clusters (automatically scaled by xy and z scales):", self.distance)
|
|
6023
|
+
|
|
6024
|
+
|
|
6025
|
+
self.proportion = QLineEdit("0.5")
|
|
6026
|
+
layout.addRow("Proportion of image to search? (0-1, high vals increase border artifacts): ", self.proportion)
|
|
6027
|
+
|
|
6028
|
+
self.edgecorrect = QPushButton("Border Correction")
|
|
6029
|
+
self.edgecorrect.setCheckable(True)
|
|
6030
|
+
self.edgecorrect.setChecked(False)
|
|
6031
|
+
layout.addRow("Use Border Correction (Extrapolate for points beyond the border):", self.edgecorrect)
|
|
6032
|
+
|
|
6033
|
+
self.ignore = QPushButton("Ignore Border Roots")
|
|
6034
|
+
self.ignore.setCheckable(True)
|
|
6035
|
+
self.ignore.setChecked(False)
|
|
6036
|
+
layout.addRow("Exclude Root Nodes Near Borders?:", self.ignore)
|
|
6037
|
+
|
|
6038
|
+
# Add Run button
|
|
6039
|
+
run_button = QPushButton("Get Ripley's H")
|
|
6040
|
+
run_button.clicked.connect(self.ripley)
|
|
6041
|
+
layout.addWidget(run_button)
|
|
6042
|
+
|
|
6043
|
+
def ripley(self):
|
|
6044
|
+
|
|
6045
|
+
try:
|
|
6046
|
+
|
|
6047
|
+
if my_network.node_centroids is None:
|
|
6048
|
+
self.parent().show_centroid_dialog()
|
|
6049
|
+
|
|
6050
|
+
try:
|
|
6051
|
+
root = self.root.currentText()
|
|
6052
|
+
except:
|
|
6053
|
+
root = None
|
|
6054
|
+
|
|
6055
|
+
try:
|
|
6056
|
+
targ = self.targ.currentText()
|
|
6057
|
+
except:
|
|
6058
|
+
targ = None
|
|
6059
|
+
|
|
6060
|
+
try:
|
|
6061
|
+
distance = float(self.distance.text())
|
|
6062
|
+
except:
|
|
6063
|
+
return
|
|
6064
|
+
|
|
6065
|
+
|
|
6066
|
+
try:
|
|
6067
|
+
proportion = abs(float(self.proportion.text()))
|
|
6068
|
+
except:
|
|
6069
|
+
proportion = 0.5
|
|
6070
|
+
|
|
6071
|
+
if proportion > 1 or proportion <= 0:
|
|
6072
|
+
print("Utilizing proportion = 0.5")
|
|
6073
|
+
proportion = 0.5
|
|
6074
|
+
|
|
6075
|
+
|
|
6076
|
+
edgecorrect = self.edgecorrect.isChecked()
|
|
6077
|
+
|
|
6078
|
+
ignore = self.ignore.isChecked()
|
|
6079
|
+
|
|
6080
|
+
if my_network.nodes is not None:
|
|
6081
|
+
|
|
6082
|
+
if my_network.nodes.shape[0] == 1:
|
|
6083
|
+
bounds = (np.array([0, 0]), np.array([my_network.nodes.shape[2], my_network.nodes.shape[1]]))
|
|
6084
|
+
else:
|
|
6085
|
+
bounds = (np.array([0, 0, 0]), np.array([my_network.nodes.shape[2], my_network.nodes.shape[1], my_network.nodes.shape[0]]))
|
|
6086
|
+
else:
|
|
6087
|
+
bounds = None
|
|
6088
|
+
|
|
6089
|
+
r_vals, k_vals, h_vals = my_network.get_ripley(root, targ, distance, edgecorrect, bounds, ignore, proportion)
|
|
6090
|
+
|
|
6091
|
+
k_dict = dict(zip(r_vals, k_vals))
|
|
6092
|
+
h_dict = dict(zip(r_vals, h_vals))
|
|
6093
|
+
|
|
6094
|
+
|
|
6095
|
+
self.parent().format_for_upperright_table(k_dict, metric='Radius (scaled)', value='L Value', title="Ripley's K")
|
|
6096
|
+
self.parent().format_for_upperright_table(h_dict, metric='Radius (scaled)', value='L Normed', title="Ripley's H")
|
|
5980
6097
|
|
|
5981
6098
|
|
|
6099
|
+
self.accept()
|
|
5982
6100
|
|
|
6101
|
+
except Exception as e:
|
|
6102
|
+
QMessageBox.critical(
|
|
6103
|
+
self,
|
|
6104
|
+
"Error:",
|
|
6105
|
+
f"Failed to preform cluster analysis: {str(e)}"
|
|
6106
|
+
)
|
|
6107
|
+
import traceback
|
|
6108
|
+
print(traceback.format_exc())
|
|
6109
|
+
print(f"Error: {e}")
|
|
5983
6110
|
|
|
5984
6111
|
class RandomDialog(QDialog):
|
|
5985
6112
|
|
|
@@ -6019,6 +6146,79 @@ class RandomDialog(QDialog):
|
|
|
6019
6146
|
|
|
6020
6147
|
self.accept()
|
|
6021
6148
|
|
|
6149
|
+
class RandNodeDialog(QDialog):
|
|
6150
|
+
|
|
6151
|
+
def __init__(self, parent=None):
|
|
6152
|
+
|
|
6153
|
+
super().__init__(parent)
|
|
6154
|
+
self.setWindowTitle("Random Node Parameters")
|
|
6155
|
+
self.setModal(True)
|
|
6156
|
+
layout = QFormLayout(self)
|
|
6157
|
+
|
|
6158
|
+
|
|
6159
|
+
self.mode = QComboBox()
|
|
6160
|
+
self.mode.addItems(["Anywhere", "Within Dimensional Bounds of Nodes", "Within Masked Bounds of Edges", "Within Masked Bounds of Overlay1", "Within Masked Bounds of Overlay2"])
|
|
6161
|
+
self.mode.setCurrentIndex(0)
|
|
6162
|
+
layout.addRow("Mode", self.mode)
|
|
6163
|
+
|
|
6164
|
+
# Add Run button
|
|
6165
|
+
run_button = QPushButton("Get Random Nodes (Will go in Nodes)")
|
|
6166
|
+
run_button.clicked.connect(self.random)
|
|
6167
|
+
layout.addWidget(run_button)
|
|
6168
|
+
|
|
6169
|
+
def random(self):
|
|
6170
|
+
|
|
6171
|
+
try:
|
|
6172
|
+
|
|
6173
|
+
if my_network.node_centroids is None:
|
|
6174
|
+
self.parent().show_centroid_dialog()
|
|
6175
|
+
|
|
6176
|
+
bounds = None
|
|
6177
|
+
mask = None
|
|
6178
|
+
|
|
6179
|
+
mode = self.mode.currentIndex()
|
|
6180
|
+
|
|
6181
|
+
if mode == 0 and not (my_network.nodes is None and my_network.edges is None and my_network.network_overlay is None and my_network.id_overlay is None):
|
|
6182
|
+
pass
|
|
6183
|
+
elif mode == 1 or (my_network.nodes is None and my_network.edges is None and my_network.network_overlay is None and my_network.id_overlay is None):
|
|
6184
|
+
print("HELLO")
|
|
6185
|
+
# Convert string labels to integers if necessary
|
|
6186
|
+
if any(isinstance(k, str) for k in my_network.node_centroids.keys()):
|
|
6187
|
+
label_map = {label: idx for idx, label in enumerate(my_network.node_centroids.keys())}
|
|
6188
|
+
my_network.node_centroids = {label_map[k]: v for k, v in my_network.node_centroids.items()}
|
|
6189
|
+
|
|
6190
|
+
# Convert centroids to array and keep track of labels
|
|
6191
|
+
labels = np.array(list(my_network.node_centroids.keys()), dtype=np.uint32)
|
|
6192
|
+
centroid_points = np.array([my_network.node_centroids[label] for label in labels])
|
|
6193
|
+
|
|
6194
|
+
# Calculate shape if not provided
|
|
6195
|
+
max_coords = centroid_points.max(axis=0)
|
|
6196
|
+
max_shape = tuple(max_coord + 1 for max_coord in max_coords)
|
|
6197
|
+
min_coords = centroid_points.min(axis=0)
|
|
6198
|
+
min_shape = tuple(min_coord + 1 for min_coord in min_coords)
|
|
6199
|
+
bounds = (min_shape, max_shape)
|
|
6200
|
+
else:
|
|
6201
|
+
mask = n3d.binarize(self.parent().channel_data[mode - 1])
|
|
6202
|
+
|
|
6203
|
+
centroids, array = my_network.random_nodes(bounds = bounds, mask = mask)
|
|
6204
|
+
|
|
6205
|
+
if my_network.nodes is not None:
|
|
6206
|
+
try:
|
|
6207
|
+
self.parent().load_channel(0, array, data = True)
|
|
6208
|
+
except:
|
|
6209
|
+
pass
|
|
6210
|
+
|
|
6211
|
+
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
6212
|
+
|
|
6213
|
+
except Exception as e:
|
|
6214
|
+
QMessageBox.critical(
|
|
6215
|
+
self,
|
|
6216
|
+
"Error:",
|
|
6217
|
+
f"Failed to randomize: {str(e)}"
|
|
6218
|
+
)
|
|
6219
|
+
print(f"Error: {e}")
|
|
6220
|
+
|
|
6221
|
+
|
|
6022
6222
|
class RadDialog(QDialog):
|
|
6023
6223
|
|
|
6024
6224
|
def __init__(self, parent=None):
|
|
@@ -7208,8 +7408,14 @@ class MachineWindow(QMainWindow):
|
|
|
7208
7408
|
train_quick.clicked.connect(lambda: self.train_model(speed=True))
|
|
7209
7409
|
train_detailed = QPushButton("Train More Detailed Model")
|
|
7210
7410
|
train_detailed.clicked.connect(lambda: self.train_model(speed=False))
|
|
7411
|
+
save = QPushButton("Save Model")
|
|
7412
|
+
save.clicked.connect(self.save_model)
|
|
7413
|
+
load = QPushButton("Load Model")
|
|
7414
|
+
load.clicked.connect(self.load_model)
|
|
7211
7415
|
training_layout.addWidget(train_quick)
|
|
7212
7416
|
training_layout.addWidget(train_detailed)
|
|
7417
|
+
training_layout.addWidget(save)
|
|
7418
|
+
training_layout.addWidget(load)
|
|
7213
7419
|
training_group.setLayout(training_layout)
|
|
7214
7420
|
|
|
7215
7421
|
# Group 4: Segmentation Options
|
|
@@ -7280,6 +7486,33 @@ class MachineWindow(QMainWindow):
|
|
|
7280
7486
|
except:
|
|
7281
7487
|
pass
|
|
7282
7488
|
|
|
7489
|
+
def save_model(self):
|
|
7490
|
+
|
|
7491
|
+
filename, _ = QFileDialog.getSaveFileName(
|
|
7492
|
+
self,
|
|
7493
|
+
f"Save Model As",
|
|
7494
|
+
"", # Default directory
|
|
7495
|
+
"numpy data (*.npz);;All Files (*)" # File type filter
|
|
7496
|
+
)
|
|
7497
|
+
|
|
7498
|
+
if filename: # Only proceed if user didn't cancel
|
|
7499
|
+
# If user didn't type an extension, add .tif
|
|
7500
|
+
if not filename.endswith(('.npz')):
|
|
7501
|
+
filename += '.npz'
|
|
7502
|
+
|
|
7503
|
+
self.segmenter.save_model(filename, self.parent().channel_data[2])
|
|
7504
|
+
|
|
7505
|
+
def load_model(self):
|
|
7506
|
+
|
|
7507
|
+
filename, _ = QFileDialog.getOpenFileName(
|
|
7508
|
+
self,
|
|
7509
|
+
f"Load Model",
|
|
7510
|
+
"",
|
|
7511
|
+
"numpy data (*.npz)"
|
|
7512
|
+
)
|
|
7513
|
+
|
|
7514
|
+
self.segmenter.load_model(filename)
|
|
7515
|
+
self.trained = True
|
|
7283
7516
|
|
|
7284
7517
|
def toggle_two(self):
|
|
7285
7518
|
if self.two.isChecked():
|