nettracer3d 0.7.6__py3-none-any.whl → 0.7.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/proximity.py CHANGED
@@ -9,6 +9,7 @@ import multiprocessing as mp
9
9
  import pandas as pd
10
10
  import matplotlib.pyplot as plt
11
11
  from typing import Dict, Union, Tuple, List, Optional
12
+ from collections import defaultdict
12
13
 
13
14
 
14
15
  # Related to morphological border searching:
@@ -142,15 +143,17 @@ def find_shared_value_pairs(input_dict):
142
143
 
143
144
  #Related to kdtree centroid searching:
144
145
 
145
- def populate_array(centroids):
146
+ def populate_array(centroids, clip=False):
146
147
  """
147
148
  Create a 3D array from centroid coordinates.
148
149
 
149
150
  Args:
150
151
  centroids: Dictionary where keys are object IDs and values are [z,y,x] coordinates
152
+ clip: Boolean, if True, transpose all centroids so minimum values become 0
151
153
 
152
154
  Returns:
153
- 3D numpy array where values are object IDs at their centroid locations
155
+ If clip=False: 3D numpy array where values are object IDs at their centroid locations
156
+ If clip=True: Tuple of (3D numpy array, dictionary with clipped centroids)
154
157
  """
155
158
  # Input validation
156
159
  if not centroids:
@@ -163,21 +166,39 @@ def populate_array(centroids):
163
166
  min_coords = coords.min(axis=0)
164
167
  max_coords = coords.max(axis=0)
165
168
 
166
- # Check for negative coordinates
167
- if np.any(min_coords < 0):
169
+ # Check for negative coordinates only if not clipping
170
+ if not clip and np.any(min_coords < 0):
168
171
  raise ValueError("Negative coordinates found in centroids")
169
172
 
173
+ # Apply clipping if requested
174
+ clipped_centroids = {}
175
+ if clip:
176
+ # Transpose all coordinates so minimum becomes 0
177
+ coords = coords - min_coords
178
+ max_coords = max_coords - min_coords
179
+ min_coords = np.zeros_like(min_coords)
180
+
181
+ # Create dictionary with clipped centroids
182
+ for i, obj_id in enumerate(centroids.keys()):
183
+ clipped_centroids[obj_id] = coords[i].tolist()
184
+
170
185
  # Create array
171
186
  array = np.zeros((max_coords[0] + 1,
172
187
  max_coords[1] + 1,
173
188
  max_coords[2] + 1), dtype=int)
174
189
 
175
- # Populate array with rounded coordinates
176
- for obj_id, coord in centroids.items():
177
- z, y, x = np.round([coord[0], coord[1], coord[2]]).astype(int)
190
+ # Populate array with (possibly clipped) rounded coordinates
191
+ for i, (obj_id, coord) in enumerate(centroids.items()):
192
+ if clip:
193
+ z, y, x = coords[i] # Use pre-computed clipped coordinates
194
+ else:
195
+ z, y, x = np.round([coord[0], coord[1], coord[2]]).astype(int)
178
196
  array[z, y, x] = obj_id
179
197
 
180
- return array
198
+ if clip:
199
+ return array, clipped_centroids
200
+ else:
201
+ return array
181
202
 
182
203
  def find_neighbors_kdtree(radius, centroids=None, array=None, targets=None):
183
204
  # Get coordinates of nonzero points
@@ -646,4 +667,63 @@ def plot_ripley_functions(r_values, k_values, h_values, dimension=2, figsize=(12
646
667
 
647
668
  plt.tight_layout()
648
669
  plt.show()
649
- #plt.clf()
670
+ #plt.clf()
671
+
672
+
673
+
674
+
675
+ def partition_objects_into_cells(object_centroids, cell_size):
676
+ """
677
+ Partition objects into 3D grid cells based on their centroids.
678
+
679
+ Args:
680
+ object_centroids (dict): Dictionary with object labels as keys and [z,y,x] coordinates as values
681
+ cell_size (tuple or int): Size of each cell. If int, creates cubic cells. If tuple, (z_size, y_size, x_size)
682
+
683
+ Returns:
684
+ dict: Dictionary with cell numbers as keys and lists of object labels as values
685
+ """
686
+
687
+ if not object_centroids:
688
+ return {}
689
+
690
+ # Handle cell_size input
691
+ if isinstance(cell_size, (int, float)):
692
+ cell_size = (cell_size, cell_size, cell_size)
693
+ elif len(cell_size) == 1:
694
+ cell_size = (cell_size[0], cell_size[0], cell_size[0])
695
+
696
+ # Extract centroids and find bounds
697
+ centroids = np.array(list(object_centroids.values()))
698
+ labels = list(object_centroids.keys())
699
+
700
+ # Find the bounding box of all centroids
701
+ min_coords = np.min(centroids, axis=0) # [min_z, min_y, min_x]
702
+ max_coords = np.max(centroids, axis=0) # [max_z, max_y, max_x]
703
+
704
+ # Calculate number of cells in each dimension
705
+ dimensions = max_coords - min_coords
706
+ num_cells = np.ceil(dimensions / np.array(cell_size)).astype(int)
707
+
708
+ # Initialize result dictionary
709
+ cell_assignments = defaultdict(list)
710
+
711
+ # Assign each object to a cell
712
+ for i, (label, centroid) in enumerate(object_centroids.items()):
713
+ # Calculate which cell this centroid belongs to
714
+ relative_pos = np.array(centroid) - min_coords
715
+ cell_indices = np.floor(relative_pos / np.array(cell_size)).astype(int)
716
+
717
+ # Ensure indices don't exceed bounds (handles edge cases)
718
+ cell_indices = np.minimum(cell_indices, num_cells - 1)
719
+ cell_indices = np.maximum(cell_indices, 0)
720
+
721
+ # Convert 3D cell indices to a single cell number
722
+ cell_number = (cell_indices[0] * num_cells[1] * num_cells[2] +
723
+ cell_indices[1] * num_cells[2] +
724
+ cell_indices[2])
725
+
726
+ cell_assignments[int(cell_number)].append(int(label))
727
+
728
+ # Convert defaultdict to regular dict and sort keys
729
+ return dict(sorted(cell_assignments.items()))
@@ -108,6 +108,8 @@ def dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z):
108
108
 
109
109
  num_cores = mp.cpu_count()
110
110
 
111
+
112
+
111
113
  with ThreadPoolExecutor(max_workers=num_cores) as executor:
112
114
  futures = {executor.submit(process_slice, z): z for z in range(tiff_array.shape[0])}
113
115
 
@@ -246,14 +248,14 @@ def invert_array(array):
246
248
  return np.logical_not(array).astype(np.uint8)
247
249
 
248
250
  def process_chunk(start_idx, end_idx, nodes, ring_mask, nearest_label_indices):
249
- nodes_chunk = nodes[start_idx:end_idx]
250
- ring_mask_chunk = ring_mask[start_idx:end_idx]
251
+ nodes_chunk = nodes[:, start_idx:end_idx, :]
252
+ ring_mask_chunk = ring_mask[:, start_idx:end_idx, :]
251
253
  dilated_nodes_with_labels_chunk = np.copy(nodes_chunk)
252
254
  ring_indices = np.argwhere(ring_mask_chunk)
253
255
 
254
256
  for index in ring_indices:
255
257
  z, y, x = index
256
- nearest_z, nearest_y, nearest_x = nearest_label_indices[:, z + start_idx, y, x]
258
+ nearest_z, nearest_y, nearest_x = nearest_label_indices[:, z, y + start_idx, x]
257
259
  try: #There was an index error here once on the highest val of the second axis. I could not understand why because it usually doesnt hence the try block.
258
260
  dilated_nodes_with_labels_chunk[z, y, x] = nodes[nearest_z, nearest_y, nearest_x]
259
261
  except:
@@ -332,10 +334,10 @@ def smart_dilate(nodes, dilate_xy, dilate_z, directory = None, GPU = True, fast_
332
334
 
333
335
  # Step 5: Process in parallel chunks using ThreadPoolExecutor
334
336
  num_cores = mp.cpu_count() # Use all available CPU cores
335
- chunk_size = nodes.shape[0] // num_cores # Divide the array into chunks along the z-axis
337
+ chunk_size = nodes.shape[1] // num_cores # Divide the array into chunks along the y-axis
336
338
 
337
339
  with ThreadPoolExecutor(max_workers=num_cores) as executor:
338
- args_list = [(i * chunk_size, (i + 1) * chunk_size if i != num_cores - 1 else nodes.shape[0], nodes, ring_mask, nearest_label_indices) for i in range(num_cores)]
340
+ args_list = [(i * chunk_size, (i + 1) * chunk_size if i != num_cores - 1 else nodes.shape[1], nodes, ring_mask, nearest_label_indices) for i in range(num_cores)]
339
341
  results = list(executor.map(lambda args: process_chunk(*args), args_list))
340
342
 
341
343
  del ring_mask
@@ -343,7 +345,7 @@ def smart_dilate(nodes, dilate_xy, dilate_z, directory = None, GPU = True, fast_
343
345
  del nearest_label_indices
344
346
 
345
347
  # Combine results from chunks
346
- dilated_nodes_with_labels = np.concatenate(results, axis=0)
348
+ dilated_nodes_with_labels = np.concatenate(results, axis=1)
347
349
 
348
350
 
349
351
  if (dilated_nodes_with_labels.shape[1] < original_shape[1]) and fast_dil: #If downsample was used, upsample output
@@ -368,7 +370,7 @@ def round_to_odd(number):
368
370
  rounded -= 1
369
371
  return rounded
370
372
 
371
- def smart_label(binary_array, label_array, directory = None, GPU = True, predownsample = None):
373
+ def smart_label(binary_array, label_array, directory = None, GPU = True, predownsample = None, remove_template = False):
372
374
 
373
375
  original_shape = binary_array.shape
374
376
 
@@ -419,12 +421,11 @@ def smart_label(binary_array, label_array, directory = None, GPU = True, predown
419
421
  break
420
422
  except cp.cuda.memory.OutOfMemoryError:
421
423
  down_factor += 1
422
- binary_mask = binary_array #Need this for later to stamp out the correct output
423
424
  binary_core = binarize(small_array)
424
425
  label_array = small_array
425
- binary_array = nettracer.downsample(binary_array, downsample_needed)
426
- binary_array = nettracer.dilate_3D_old(binary_array)
427
- ring_mask = binary_array & invert_array(binary_core)
426
+ binary_small = nettracer.downsample(binary_array, downsample_needed)
427
+ binary_small = nettracer.dilate_3D_old(binary_small)
428
+ ring_mask = binary_small & invert_array(binary_core)
428
429
 
429
430
  else:
430
431
  goto_except = 1/0
@@ -440,18 +441,21 @@ def smart_label(binary_array, label_array, directory = None, GPU = True, predown
440
441
 
441
442
  # Step 5: Process in parallel chunks using ThreadPoolExecutor
442
443
  num_cores = mp.cpu_count() # Use all available CPU cores
443
- chunk_size = label_array.shape[0] // num_cores # Divide the array into chunks along the z-axis
444
+ chunk_size = label_array.shape[1] // num_cores # Divide the array into chunks along the z-axis
445
+
444
446
 
445
447
  with ThreadPoolExecutor(max_workers=num_cores) as executor:
446
- args_list = [(i * chunk_size, (i + 1) * chunk_size if i != num_cores - 1 else label_array.shape[0], label_array, ring_mask, nearest_label_indices) for i in range(num_cores)]
448
+ args_list = [(i * chunk_size, (i + 1) * chunk_size if i != num_cores - 1 else label_array.shape[1], label_array, ring_mask, nearest_label_indices) for i in range(num_cores)]
447
449
  results = list(executor.map(lambda args: process_chunk(*args), args_list))
448
450
 
449
451
  # Combine results from chunks
450
- dilated_nodes_with_labels = np.concatenate(results, axis=0)
452
+ dilated_nodes_with_labels = np.concatenate(results, axis=1)
451
453
 
452
454
  if label_array.shape[1] < original_shape[1]: #If downsample was used, upsample output
453
455
  dilated_nodes_with_labels = nettracer.upsample_with_padding(dilated_nodes_with_labels, downsample_needed, original_shape)
454
- dilated_nodes_with_labels = dilated_nodes_with_labels * binary_mask
456
+ dilated_nodes_with_labels = dilated_nodes_with_labels * binary_array
457
+ elif remove_template:
458
+ dilated_nodes_with_labels = dilated_nodes_with_labels * binary_array
455
459
 
456
460
  if string_bool:
457
461
  if directory is not None:
@@ -465,6 +469,7 @@ def smart_label(binary_array, label_array, directory = None, GPU = True, predown
465
469
  except Exception as e:
466
470
  print(f"Could not save search region file to active directory")
467
471
 
472
+
468
473
  return dilated_nodes_with_labels
469
474
 
470
475
  def compute_distance_transform_GPU(nodes, return_dists = False, sampling = [1, 1, 1]):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nettracer3d
3
- Version: 0.7.6
3
+ Version: 0.7.8
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/
@@ -28,6 +28,7 @@ Requires-Dist: PyQt6
28
28
  Requires-Dist: scikit-learn
29
29
  Requires-Dist: nibabel
30
30
  Requires-Dist: setuptools
31
+ Requires-Dist: umap-learn
31
32
  Provides-Extra: cuda11
32
33
  Requires-Dist: cupy-cuda11x; extra == "cuda11"
33
34
  Provides-Extra: cuda12
@@ -72,6 +73,14 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
72
73
 
73
74
  NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
74
75
 
75
- -- Version 0.7.6 Updates --
76
+ -- Version 0.7.8 (and 0.7.7) Updates --
76
77
 
77
78
  * Bug Fixes
79
+ * Added the excel helper loader for better automated loading from QuPath exports specifically
80
+ * Added the ability to cluster communities into broader neighborhoods (with KMeans) based on their compositions.
81
+ * Added heatmap and UMAP graph displays based on community compositions.
82
+ * Added the ability to show heatmaps of nodes based on their density within their communities
83
+ * Added the ability to cluster nodes into communities based on spatial grouping in arbitrarily-sized cells (rather than just using the network)
84
+ * Added function to crop the current image
85
+ * More options under 'Modify Network'
86
+ * 'Show 3D' method now can render a bounding box.
@@ -0,0 +1,23 @@
1
+ nettracer3d/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ nettracer3d/community_extractor.py,sha256=BrONCLRLYUdMfLe_018AQi0k0J7xwVahc_lmsOO5Pwo,23086
3
+ nettracer3d/excelotron.py,sha256=lS5vnpoOGZWp7fdqVpTPqeC-mUKrfwDrWHfx4PQ7Uzg,71384
4
+ nettracer3d/modularity.py,sha256=O9OeKbjD3v6gSFz9K2GzP6LsxlpQaPfeJbM1pyIEigw,21788
5
+ nettracer3d/morphology.py,sha256=jyDjYzrZ4LvI5jOyw8DLsxmo-i5lpqHsejYpW7Tq7Mo,19786
6
+ nettracer3d/neighborhoods.py,sha256=kkKR8m6Gjw34cDd_mytAIwLxqvuNBtQb2hU4JuBY9pI,12301
7
+ nettracer3d/nettracer.py,sha256=M1KFIPg7WCzm8BXQOreuEVhgjg0PpLKRg4Y88DyVuK8,225843
8
+ nettracer3d/nettracer_gui.py,sha256=WTcN-tOk4vzDLhnVN4un5PEkC90GDp3sxZZhgLifMOM,468787
9
+ nettracer3d/network_analysis.py,sha256=h-5yzUWdE0hcWYy8wcBA5LV1bRhdqiMnKbQLrRzb1Sw,41443
10
+ nettracer3d/network_draw.py,sha256=F7fw6Pcf4qWOhdKwLmhwqWdschbDlHzwCVolQC9imeU,14117
11
+ nettracer3d/node_draw.py,sha256=k3sCTfUCJs3aH1C1q1gTNxDz9EAQbBd1hsUIJajxRx8,9823
12
+ nettracer3d/proximity.py,sha256=5n8qxqxmmMtq5bqVpSkqw3EefuZIyGdLybVs18D3ZNg,27996
13
+ nettracer3d/run.py,sha256=xYeaAc8FCx8MuzTGyL3NR3mK7WZzffAYAH23bNRZYO4,127
14
+ nettracer3d/segmenter.py,sha256=BD9vxnblDKXfmR8hLP_iVqqfVngH6opz4Q7V6sxc2KM,60062
15
+ nettracer3d/segmenter_GPU.py,sha256=Fqr0Za6X2ss4rfaziqOhvhBfbGDPHkHw6fVxs39lZaU,53862
16
+ nettracer3d/simple_network.py,sha256=Ft_81VhVQ3rqoXvuYnsckXuxCcQSJfakhOfkFaclxZY,9340
17
+ nettracer3d/smart_dilate.py,sha256=DOEOQq9ig6-AO4MpqAG0CqrGDFqw5_UBeqfSedqHk28,25933
18
+ nettracer3d-0.7.8.dist-info/licenses/LICENSE,sha256=gM207DhJjWrxLuEWXl0Qz5ISbtWDmADfjHp3yC2XISs,888
19
+ nettracer3d-0.7.8.dist-info/METADATA,sha256=GFylr691RVFXgRMrrEhOEW6ySL5vUW-8-bK8qQ4hr9A,4797
20
+ nettracer3d-0.7.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ nettracer3d-0.7.8.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
22
+ nettracer3d-0.7.8.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
23
+ nettracer3d-0.7.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,21 +0,0 @@
1
- nettracer3d/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- nettracer3d/community_extractor.py,sha256=vzckLJfubplcQUAIr4UNgezxUJIdeFmH6CTWk8jYjOw,23739
3
- nettracer3d/modularity.py,sha256=1Qg8Vc7Cl8nkkJxz_Z8tNR8hK1b-ZrFrMVUl4SKGLY8,21825
4
- nettracer3d/morphology.py,sha256=jyDjYzrZ4LvI5jOyw8DLsxmo-i5lpqHsejYpW7Tq7Mo,19786
5
- nettracer3d/nettracer.py,sha256=2xAImidzKkYEA_5Ja18v9vMUYgFUEwJB8v8-QCKfi8A,213593
6
- nettracer3d/nettracer_gui.py,sha256=7L0xeR4aENPppT9YQcc7U8EPYUQMq3pfREP_iWEzawk,436234
7
- nettracer3d/network_analysis.py,sha256=h-5yzUWdE0hcWYy8wcBA5LV1bRhdqiMnKbQLrRzb1Sw,41443
8
- nettracer3d/network_draw.py,sha256=F7fw6Pcf4qWOhdKwLmhwqWdschbDlHzwCVolQC9imeU,14117
9
- nettracer3d/node_draw.py,sha256=k3sCTfUCJs3aH1C1q1gTNxDz9EAQbBd1hsUIJajxRx8,9823
10
- nettracer3d/proximity.py,sha256=nlVBXzJ6r84TlP8UaLcdamWifYn-jfVIF0uB-56k_Js,24752
11
- nettracer3d/run.py,sha256=xYeaAc8FCx8MuzTGyL3NR3mK7WZzffAYAH23bNRZYO4,127
12
- nettracer3d/segmenter.py,sha256=BD9vxnblDKXfmR8hLP_iVqqfVngH6opz4Q7V6sxc2KM,60062
13
- nettracer3d/segmenter_GPU.py,sha256=Fqr0Za6X2ss4rfaziqOhvhBfbGDPHkHw6fVxs39lZaU,53862
14
- nettracer3d/simple_network.py,sha256=Ft_81VhVQ3rqoXvuYnsckXuxCcQSJfakhOfkFaclxZY,9340
15
- nettracer3d/smart_dilate.py,sha256=69z9Bn8xtA7rkhcVpqd1PxRSxxRFnIQse9lc2-LU4TU,25879
16
- nettracer3d-0.7.6.dist-info/licenses/LICENSE,sha256=gM207DhJjWrxLuEWXl0Qz5ISbtWDmADfjHp3yC2XISs,888
17
- nettracer3d-0.7.6.dist-info/METADATA,sha256=YO0kmThsnywdo9NJhnGxQFvmAkUdhMlEKHS-gFgCRvY,4093
18
- nettracer3d-0.7.6.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
19
- nettracer3d-0.7.6.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
20
- nettracer3d-0.7.6.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
21
- nettracer3d-0.7.6.dist-info/RECORD,,