nettracer3d 1.0.3__py3-none-any.whl → 1.0.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.

Potentially problematic release.


This version of nettracer3d might be problematic. Click here for more details.

nettracer3d/nettracer.py CHANGED
@@ -1903,6 +1903,7 @@ def mirror_points_for_edge_correction(points_array, bounds, max_r, dim=3):
1903
1903
  all_points = np.vstack([all_points, mirrored_points])
1904
1904
 
1905
1905
  return all_points
1906
+
1906
1907
  def get_max_r_from_proportion(bounds, proportion):
1907
1908
  """
1908
1909
  Calculate max_r based on bounds and proportion, matching your generate_r_values logic.
@@ -6054,57 +6055,94 @@ class Network_3D:
6054
6055
  self.node_identities = modify_dict
6055
6056
 
6056
6057
 
6057
- def nearest_neighbors_avg(self, root, targ, xy_scale = 1, z_scale = 1, num = 1, heatmap = False, threed = True, numpy = False, quant = False, centroids = True):
6058
-
6059
- def distribute_points_uniformly(n, shape, z_scale, xy_scale, num, is_2d=False):
6058
+ def nearest_neighbors_avg(self, root, targ, xy_scale = 1, z_scale = 1, num = 1, heatmap = False, threed = True, numpy = False, quant = False, centroids = True, mask = None):
6060
6059
 
6060
+ def distribute_points_uniformly(n, shape, z_scale, xy_scale, num, is_2d=False, mask=None):
6061
6061
  from scipy.spatial import KDTree
6062
-
6063
6062
  if n <= 1:
6064
6063
  return 0
6065
-
6066
- # Calculate total positions and sampling step
6067
- total_positions = np.prod(shape)
6068
- if n >= total_positions:
6069
- # If we want more points than positions, just return scaled unit distance
6070
- return xy_scale if is_2d else min(z_scale, xy_scale)
6071
-
6072
- # Create uniformly spaced indices
6073
- indices = np.linspace(0, total_positions - 1, n, dtype=int)
6074
6064
 
6075
- # Convert flat indices to coordinates
6076
- coords = []
6077
- for idx in indices:
6078
- coord = np.unravel_index(idx, shape)
6065
+ if mask is not None:
6066
+ # Handle mask-based distribution
6067
+ # Find all valid positions where mask is True
6068
+ valid_positions = np.where(mask)
6069
+ total_valid_positions = len(valid_positions[0])
6070
+
6071
+ if total_valid_positions == 0:
6072
+ raise ValueError("No valid positions found in mask")
6073
+
6074
+ if n >= total_valid_positions:
6075
+ # If we want more points than valid positions, return scaled unit distance
6076
+ return xy_scale if is_2d else min(z_scale, xy_scale)
6077
+
6078
+ # Create uniformly spaced indices within valid positions
6079
+ valid_indices = np.linspace(0, total_valid_positions - 1, n, dtype=int)
6080
+
6081
+ # Convert to coordinates and apply scaling
6082
+ coords = []
6083
+ for idx in valid_indices:
6084
+ if len(shape) == 3:
6085
+ coord = (valid_positions[0][idx], valid_positions[1][idx], valid_positions[2][idx])
6086
+ scaled_coord = [coord[0] * z_scale, coord[1] * xy_scale, coord[2] * xy_scale]
6087
+ elif len(shape) == 2:
6088
+ coord = (valid_positions[0][idx], valid_positions[1][idx])
6089
+ scaled_coord = [coord[0] * xy_scale, coord[1] * xy_scale]
6090
+ coords.append(scaled_coord)
6091
+
6092
+ coords = np.array(coords)
6093
+
6094
+ # Find a good query point (closest to center of valid region)
6079
6095
  if len(shape) == 3:
6080
- # Apply scaling: [z, y, x] with respective scales
6081
- scaled_coord = [coord[0] * z_scale, coord[1] * xy_scale, coord[2] * xy_scale]
6082
- elif len(shape) == 2:
6083
- # Apply scaling: [y, x] with xy_scale
6084
- scaled_coord = [coord[0] * xy_scale, coord[1] * xy_scale]
6085
- coords.append(scaled_coord)
6086
-
6087
- coords = np.array(coords)
6096
+ center_pos = [np.mean(valid_positions[0]) * z_scale,
6097
+ np.mean(valid_positions[1]) * xy_scale,
6098
+ np.mean(valid_positions[2]) * xy_scale]
6099
+ else:
6100
+ center_pos = [np.mean(valid_positions[0]) * xy_scale,
6101
+ np.mean(valid_positions[1]) * xy_scale]
6102
+
6103
+ # Find point closest to center of valid region
6104
+ center_distances = np.sum((coords - center_pos)**2, axis=1)
6105
+ middle_idx = np.argmin(center_distances)
6106
+ query_point = coords[middle_idx]
6107
+
6108
+ else:
6109
+ # Original behavior when no mask is provided
6110
+ total_positions = np.prod(shape)
6111
+ if n >= total_positions:
6112
+ return xy_scale if is_2d else min(z_scale, xy_scale)
6113
+
6114
+ # Create uniformly spaced indices
6115
+ indices = np.linspace(0, total_positions - 1, n, dtype=int)
6116
+
6117
+ # Convert flat indices to coordinates
6118
+ coords = []
6119
+ for idx in indices:
6120
+ coord = np.unravel_index(idx, shape)
6121
+ if len(shape) == 3:
6122
+ scaled_coord = [coord[0] * z_scale, coord[1] * xy_scale, coord[2] * xy_scale]
6123
+ elif len(shape) == 2:
6124
+ scaled_coord = [coord[0] * xy_scale, coord[1] * xy_scale]
6125
+ coords.append(scaled_coord)
6126
+
6127
+ coords = np.array(coords)
6128
+
6129
+ # Pick a point near the middle of the array
6130
+ middle_idx = len(coords) // 2
6131
+ query_point = coords[middle_idx]
6088
6132
 
6089
6133
  # Build KDTree
6090
6134
  tree = KDTree(coords)
6091
6135
 
6092
- # Pick a point near the middle of the array
6093
- middle_idx = len(coords) // 2
6094
- query_point = coords[middle_idx]
6095
-
6096
6136
  # Find the num+1 nearest neighbors (including the point itself)
6097
6137
  distances, indices = tree.query(query_point, k=num+1)
6098
6138
 
6099
6139
  # Exclude the point itself (distance 0) and get the actual neighbors
6100
6140
  neighbor_distances = distances[1:num+1]
6101
-
6102
6141
  if num == n:
6103
6142
  neighbor_distances[-1] = neighbor_distances[-2]
6104
6143
 
6105
6144
  avg_distance = np.mean(neighbor_distances)
6106
6145
 
6107
-
6108
6146
  return avg_distance
6109
6147
 
6110
6148
  do_borders = not centroids
@@ -6167,11 +6205,11 @@ class Network_3D:
6167
6205
  if heatmap:
6168
6206
  root_set = []
6169
6207
  compare_set = []
6170
- if root is None:
6171
-
6172
- root_set = list(self.node_centroids.keys())
6208
+ if root is None and not do_borders:
6173
6209
  compare_set = root_set
6174
- else:
6210
+ if not do_borders:
6211
+ root_set = list(self.node_centroids.keys())
6212
+ elif self.node_identities is not None:
6175
6213
  for node, iden in self.node_identities.items():
6176
6214
 
6177
6215
  if iden == root:
@@ -6199,7 +6237,8 @@ class Network_3D:
6199
6237
  targ = [targ]
6200
6238
 
6201
6239
  compare_set_neigh = approx_boundaries(self.nodes, targ, self.node_identities, keep_labels = False)
6202
- avg, output = proximity.average_nearest_neighbor_distances(self.node_centroids, root_set_neigh, compare_set_neigh, xy_scale=self.xy_scale, z_scale=self.z_scale, num = num, do_borders = do_borders)
6240
+
6241
+ avg, output = proximity.average_nearest_neighbor_distances(self.node_centroids, root_set_neigh, compare_set_neigh, xy_scale=self.xy_scale, z_scale=self.z_scale, num = num, do_borders = do_borders)
6203
6242
 
6204
6243
  if quant:
6205
6244
  try:
@@ -6241,7 +6280,15 @@ class Network_3D:
6241
6280
  else:
6242
6281
  is_2d = False
6243
6282
 
6244
- pred = distribute_points_uniformly(len(compare_set), bounds, self.z_scale, self.xy_scale, num = num, is_2d = is_2d)
6283
+ if root_set == []:
6284
+ avail_nodes = np.unique(self.nodes)
6285
+ compare_set = list(avail_nodes)
6286
+ if 0 in compare_set:
6287
+ del compare_set[0]
6288
+ root_set = compare_set
6289
+ elif compare_set == []:
6290
+ compare_set = root_set
6291
+ pred = distribute_points_uniformly(len(compare_set), bounds, self.z_scale, self.xy_scale, num = num, is_2d = is_2d, mask = mask)
6245
6292
 
6246
6293
  node_intensity = {}
6247
6294
  import math
@@ -6249,7 +6296,6 @@ class Network_3D:
6249
6296
 
6250
6297
  for node in root_set:
6251
6298
  node_intensity[node] = math.log(pred/output[node])
6252
- #print(output[node])
6253
6299
  node_centroids[node] = self.node_centroids[node]
6254
6300
 
6255
6301
  if numpy:
@@ -1280,7 +1280,9 @@ class ImageViewerWindow(QMainWindow):
1280
1280
 
1281
1281
  if my_network.node_identities is not None:
1282
1282
  identity_menu = QMenu("Show Identity", self)
1283
- for item in set(my_network.node_identities.values()):
1283
+ idens = list(set(my_network.node_identities.values()))
1284
+ idens.sort()
1285
+ for item in idens:
1284
1286
  show_identity = identity_menu.addAction(f"ID: {item}")
1285
1287
  show_identity.triggered.connect(lambda checked, item=item: self.handle_show_identities(sort=item))
1286
1288
  context_menu.addMenu(identity_menu)
@@ -5612,7 +5614,8 @@ class ImageViewerWindow(QMainWindow):
5612
5614
  )
5613
5615
 
5614
5616
  self.last_load = directory
5615
-
5617
+ self.last_saved = os.path.dirname(directory)
5618
+ self.last_save_name = directory
5616
5619
 
5617
5620
  if directory != "":
5618
5621
 
@@ -5694,8 +5697,6 @@ class ImageViewerWindow(QMainWindow):
5694
5697
  f"Failed to load Network 3D Object: {str(e)}"
5695
5698
  )
5696
5699
 
5697
-
5698
-
5699
5700
  def load_network(self):
5700
5701
  """Load in the network from a .xlsx (need to add .csv support)"""
5701
5702
 
@@ -9038,7 +9039,7 @@ class ComNeighborDialog(QDialog):
9038
9039
 
9039
9040
  mode = self.mode.currentIndex()
9040
9041
 
9041
- seed = float(self.seed.text()) if self.seed.text().strip() else 42
9042
+ seed = int(self.seed.text()) if self.seed.text().strip() else 42
9042
9043
 
9043
9044
  limit = int(self.limit.text()) if self.limit.text().strip() else None
9044
9045
 
@@ -9235,6 +9236,11 @@ class NearNeighDialog(QDialog):
9235
9236
  self.numpy.setChecked(False)
9236
9237
  self.numpy.clicked.connect(self.toggle_map)
9237
9238
  heatmap_layout.addRow("Overlay:", self.numpy)
9239
+
9240
+ self.mode = QComboBox()
9241
+ self.mode.addItems(["Anywhere", "Within Masked Bounds of Edges", "Within Masked Bounds of Overlay1", "Within Masked Bounds of Overlay2"])
9242
+ self.mode.setCurrentIndex(0)
9243
+ heatmap_layout.addRow("For heatmap, measure theoretical point distribution how?", self.mode)
9238
9244
 
9239
9245
  main_layout.addWidget(heatmap_group)
9240
9246
 
@@ -9326,35 +9332,48 @@ class NearNeighDialog(QDialog):
9326
9332
  root = None
9327
9333
  targ = None
9328
9334
 
9335
+ mode = self.mode.currentIndex()
9336
+
9337
+ if mode == 0:
9338
+ mask = None
9339
+ else:
9340
+ try:
9341
+ mask = self.parent().channel_data[mode] != 0
9342
+ except:
9343
+ print("Could not binarize mask")
9344
+ mask = None
9345
+
9329
9346
  heatmap = self.map.isChecked()
9330
9347
  threed = self.threed.isChecked()
9331
9348
  numpy = self.numpy.isChecked()
9332
9349
  num = int(self.num.text()) if self.num.text().strip() else 1
9333
9350
  quant = self.quant.isChecked()
9334
9351
  centroids = self.centroids.isChecked()
9352
+
9335
9353
  if not centroids:
9354
+ print("Using 1 nearest neighbor due to not using centroids")
9336
9355
  num = 1
9337
9356
 
9338
9357
  if root is not None and targ is not None:
9339
9358
  title = f"Nearest {num} Neighbor(s) Distance of {targ} from {root}"
9340
- header = f"Shortest Distance to Closest {num} {targ}(s)"
9359
+ header = f"Avg Shortest Distance to Closest {num} {targ}(s)"
9341
9360
  header2 = f"{root} Node ID"
9342
9361
  header3 = f'Theoretical Uniform Distance to Closest {num} {targ}(s)'
9343
9362
  else:
9344
9363
  title = f"Nearest {num} Neighbor(s) Distance Between Nodes"
9345
- header = f"Shortest Distance to Closest {num} Nodes"
9364
+ header = f"Avg Shortest Distance to Closest {num} Nodes"
9346
9365
  header2 = "Root Node ID"
9347
9366
  header3 = f'Simulated Theoretical Uniform Distance to Closest {num} Nodes'
9348
9367
 
9349
- if centroids and my_network.node_centroids is None:
9368
+ if my_network.node_centroids is None:
9350
9369
  self.parent().show_centroid_dialog()
9351
9370
  if my_network.node_centroids is None:
9352
9371
  return
9353
9372
 
9354
9373
  if not numpy:
9355
- avg, output, quant_overlay, pred = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, quant = quant, centroids = centroids)
9374
+ avg, output, quant_overlay, pred = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, quant = quant, centroids = centroids, mask = mask)
9356
9375
  else:
9357
- avg, output, overlay, quant_overlay, pred = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, numpy = True, quant = quant, centroids = centroids)
9376
+ avg, output, overlay, quant_overlay, pred = my_network.nearest_neighbors_avg(root, targ, my_network.xy_scale, my_network.z_scale, num = num, heatmap = heatmap, threed = threed, numpy = True, quant = quant, centroids = centroids, mask = mask)
9358
9377
  self.parent().load_channel(3, overlay, data = True, preserve_zoom = (self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
9359
9378
 
9360
9379
  if quant_overlay is not None:
@@ -10068,7 +10087,7 @@ class ViolinDialog(QDialog):
10068
10087
  # Convert all remaining columns to float type (batch conversion)
10069
10088
  df_copy = df_copy.astype(float)
10070
10089
 
10071
- # First, calculate the centerpoint for each column by finding the median across all identity groups
10090
+ # First, calculate the centerpoint for each column by finding the min across all identity groups
10072
10091
  column_centerpoints = {}
10073
10092
 
10074
10093
  for column in df_copy.columns:
@@ -10079,7 +10098,7 @@ class ViolinDialog(QDialog):
10079
10098
  valid_nodes = [node for node in node_list if node in df_copy.index]
10080
10099
  if valid_nodes and ((str(identity) == str(column)) or str(identity) == f'{str(column)}+'):
10081
10100
  # Get the median value for this identity in this column
10082
- identity_min = df_copy.loc[valid_nodes, column].median()
10101
+ identity_min = df_copy.loc[valid_nodes, column].min()
10083
10102
  centerpoint = identity_min
10084
10103
  break # Found the match, no need to continue
10085
10104
 
@@ -10088,6 +10107,7 @@ class ViolinDialog(QDialog):
10088
10107
  column_centerpoints[column] = centerpoint
10089
10108
  else:
10090
10109
  # Fallback: if no matching identity, use column median
10110
+ print(f"Could not find {str(column)} in node identities. As a fallback, using the median of all values in this channel rather than the minimum of user-designated valid values.")
10091
10111
  column_centerpoints[column] = df_copy[column].median()
10092
10112
 
10093
10113
  # Now normalize each column using Z-score-like calculation with identity centerpoint
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nettracer3d
3
- Version: 1.0.3
3
+ Version: 1.0.5
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/
@@ -110,6 +110,7 @@ McLaughlin, L., Zhang, B., Sharma, S. et al. Three dimensional multiscalar neuro
110
110
 
111
111
  NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
112
112
 
113
- -- Version 1.0.3 Updates --
113
+ -- Version 1.0.4 Updates --
114
114
 
115
115
  * Some small bug fixes and adjustments
116
+ * Heatmap theoretical distances can now be calculated based on an area constrained within a binary mask.
@@ -5,8 +5,8 @@ nettracer3d/excelotron.py,sha256=aNof6k-DgMxVyFgsl3ltSCxG4vZW49cuvCBzfzhYhUY,750
5
5
  nettracer3d/modularity.py,sha256=pborVcDBvICB2-g8lNoSVZbIReIBlfeBmjFbPYmtq7Y,22443
6
6
  nettracer3d/morphology.py,sha256=jyDjYzrZ4LvI5jOyw8DLsxmo-i5lpqHsejYpW7Tq7Mo,19786
7
7
  nettracer3d/neighborhoods.py,sha256=lh4_zuTwgTq-u8VHy72buBfZf58FEJo9sH3IIBuZr68,52735
8
- nettracer3d/nettracer.py,sha256=_K_npg50qrXU1kj8NeFnlQY0oRMnD_PyvNi23hoQDow,271318
9
- nettracer3d/nettracer_gui.py,sha256=3nUjgPi2H-OKqBB7PP35kQVXLJNzPJN9wx9Ay_9pt50,665218
8
+ nettracer3d/nettracer.py,sha256=6r5aGtmiHxGkfCh5lJuREAoloZ3WKvcaRAD7kmWXedk,273957
9
+ nettracer3d/nettracer_gui.py,sha256=GannK0Yvq80I0Pz6HxVlfiow6lcLlmJAsisenUq2hPQ,666323
10
10
  nettracer3d/network_analysis.py,sha256=kBzsVaq4dZkMe0k-VGvQIUvM-tK0ZZ8bvb-wtsugZRQ,46150
11
11
  nettracer3d/network_draw.py,sha256=F7fw6Pcf4qWOhdKwLmhwqWdschbDlHzwCVolQC9imeU,14117
12
12
  nettracer3d/node_draw.py,sha256=kZcR1PekLg0riioNeGcALIXQyZ5PtHA_9MT6z7Zovdk,10401
@@ -17,9 +17,9 @@ nettracer3d/segmenter.py,sha256=aOO3PwZ2UeizEIFX7N8midwzTzbEDzgM2jQ7WTdbrUg,7067
17
17
  nettracer3d/segmenter_GPU.py,sha256=OUekQljLKPiC4d4hNZmqrRa9HSVQ6HcCnILiAfHE5Hg,78051
18
18
  nettracer3d/simple_network.py,sha256=dkG4jpc4zzdeuoaQobgGfL3PNo6N8dGKQ5hEEubFIvA,9947
19
19
  nettracer3d/smart_dilate.py,sha256=TvRUh6B4q4zIdCO1BWH-xgTdND5OUNmo99eyxG9oIAU,27145
20
- nettracer3d-1.0.3.dist-info/licenses/LICENSE,sha256=jnNT-yBeIAKAHpYthPvLeqCzJ6nSurgnKmloVnfsjCI,764
21
- nettracer3d-1.0.3.dist-info/METADATA,sha256=jXFL5Ol1kS56twkSbK7qat2uYIFrnw9E7fRwOcojqLs,7013
22
- nettracer3d-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- nettracer3d-1.0.3.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
24
- nettracer3d-1.0.3.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
25
- nettracer3d-1.0.3.dist-info/RECORD,,
20
+ nettracer3d-1.0.5.dist-info/licenses/LICENSE,sha256=jnNT-yBeIAKAHpYthPvLeqCzJ6nSurgnKmloVnfsjCI,764
21
+ nettracer3d-1.0.5.dist-info/METADATA,sha256=_0HLxnSBy83kTuLBk9HlSh_1-sQjoeJwJTXUtGdIEPw,7120
22
+ nettracer3d-1.0.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ nettracer3d-1.0.5.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
24
+ nettracer3d-1.0.5.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
25
+ nettracer3d-1.0.5.dist-info/RECORD,,