nettracer3d 1.0.2__py3-none-any.whl → 1.0.4__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.
@@ -3933,7 +3934,7 @@ class Network_3D:
3933
3934
  """
3934
3935
  self._nodes, num_nodes = label_objects(nodes, structure_3d)
3935
3936
 
3936
- def combine_nodes(self, root_nodes, other_nodes, other_ID, identity_dict, root_ID = None, centroids = False):
3937
+ def combine_nodes(self, root_nodes, other_nodes, other_ID, identity_dict, root_ID = None, centroids = False, down_factor = None):
3937
3938
 
3938
3939
  """Internal method to merge two labelled node arrays into one"""
3939
3940
 
@@ -3944,7 +3945,10 @@ class Network_3D:
3944
3945
  max_val = np.max(root_nodes)
3945
3946
  other_nodes[:] = np.where(mask, other_nodes + max_val, 0)
3946
3947
  if centroids:
3947
- new_dict = network_analysis._find_centroids(other_nodes)
3948
+ new_dict = network_analysis._find_centroids(other_nodes, down_factor = down_factor)
3949
+ if down_factor is not None:
3950
+ for item in new_dict:
3951
+ new_dict[item] = down_factor * new_dict[item]
3948
3952
  self.node_centroids.update(new_dict)
3949
3953
 
3950
3954
  if root_ID is not None:
@@ -3984,7 +3988,7 @@ class Network_3D:
3984
3988
 
3985
3989
  return nodes, identity_dict
3986
3990
 
3987
- def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes", centroids = False):
3991
+ def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes", centroids = False, down_factor = None):
3988
3992
  """
3989
3993
  Merges the self._nodes attribute with alternate labelled node images. The alternate nodes can be inputted as a string for a filepath to a tif,
3990
3994
  or as a directory address containing only tif images, which will merge the _nodes attribute with all tifs in the folder. The _node_identities attribute
@@ -4005,19 +4009,21 @@ class Network_3D:
4005
4009
  identity_dict = {} #A dictionary to deliniate the node identities
4006
4010
 
4007
4011
  if centroids:
4008
- self.node_centroids = network_analysis._find_centroids(self._nodes)
4009
-
4012
+ self.node_centroids = network_analysis._find_centroids(self._nodes, down_factor = down_factor)
4013
+ if down_factor is not None:
4014
+ for item in self.node_centroids:
4015
+ self.node_centroids[item] = down_factor * self.node_centroids[item]
4010
4016
 
4011
4017
  try: #Try presumes the input is a tif
4012
4018
  addn_nodes = tifffile.imread(addn_nodes_name) #If not this will fail and activate the except block
4013
4019
 
4014
4020
  if label_nodes is True:
4015
4021
  addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
4016
- node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids) #This method stacks labelled arrays
4022
+ node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor) #This method stacks labelled arrays
4017
4023
  num_nodes = np.max(node_labels)
4018
4024
 
4019
4025
  else: #If nodes already labelled
4020
- node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids)
4026
+ node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_name, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor)
4021
4027
  num_nodes = int(np.max(node_labels))
4022
4028
 
4023
4029
  except: #Exception presumes the input is a directory containing multiple tifs, to allow multi-node stackage.
@@ -4035,15 +4041,15 @@ class Network_3D:
4035
4041
  if label_nodes is True:
4036
4042
  addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
4037
4043
  if i == 0:
4038
- node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
4044
+ node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor)
4039
4045
  else:
4040
- node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
4046
+ node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids, down_factor = down_factor)
4041
4047
 
4042
4048
  else:
4043
4049
  if i == 0:
4044
- node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
4050
+ node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids, down_factor = down_factor)
4045
4051
  else:
4046
- node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
4052
+ node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids, down_factor = down_factor)
4047
4053
  except Exception as e:
4048
4054
  print("Could not open additional nodes, verify they are being inputted correctly...")
4049
4055
 
@@ -6049,57 +6055,94 @@ class Network_3D:
6049
6055
  self.node_identities = modify_dict
6050
6056
 
6051
6057
 
6052
- 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):
6053
-
6054
- 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):
6055
6059
 
6060
+ def distribute_points_uniformly(n, shape, z_scale, xy_scale, num, is_2d=False, mask=None):
6056
6061
  from scipy.spatial import KDTree
6057
-
6058
6062
  if n <= 1:
6059
6063
  return 0
6060
-
6061
- # Calculate total positions and sampling step
6062
- total_positions = np.prod(shape)
6063
- if n >= total_positions:
6064
- # If we want more points than positions, just return scaled unit distance
6065
- return xy_scale if is_2d else min(z_scale, xy_scale)
6066
6064
 
6067
- # Create uniformly spaced indices
6068
- indices = np.linspace(0, total_positions - 1, n, dtype=int)
6069
-
6070
- # Convert flat indices to coordinates
6071
- coords = []
6072
- for idx in indices:
6073
- 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)
6074
6095
  if len(shape) == 3:
6075
- # Apply scaling: [z, y, x] with respective scales
6076
- scaled_coord = [coord[0] * z_scale, coord[1] * xy_scale, coord[2] * xy_scale]
6077
- elif len(shape) == 2:
6078
- # Apply scaling: [y, x] with xy_scale
6079
- scaled_coord = [coord[0] * xy_scale, coord[1] * xy_scale]
6080
- coords.append(scaled_coord)
6081
-
6082
- 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]
6083
6132
 
6084
6133
  # Build KDTree
6085
6134
  tree = KDTree(coords)
6086
6135
 
6087
- # Pick a point near the middle of the array
6088
- middle_idx = len(coords) // 2
6089
- query_point = coords[middle_idx]
6090
-
6091
6136
  # Find the num+1 nearest neighbors (including the point itself)
6092
6137
  distances, indices = tree.query(query_point, k=num+1)
6093
6138
 
6094
6139
  # Exclude the point itself (distance 0) and get the actual neighbors
6095
6140
  neighbor_distances = distances[1:num+1]
6096
-
6097
6141
  if num == n:
6098
6142
  neighbor_distances[-1] = neighbor_distances[-2]
6099
6143
 
6100
6144
  avg_distance = np.mean(neighbor_distances)
6101
6145
 
6102
-
6103
6146
  return avg_distance
6104
6147
 
6105
6148
  do_borders = not centroids
@@ -6162,11 +6205,11 @@ class Network_3D:
6162
6205
  if heatmap:
6163
6206
  root_set = []
6164
6207
  compare_set = []
6165
- if root is None:
6166
-
6167
- root_set = list(self.node_centroids.keys())
6208
+ if root is None and not do_borders:
6168
6209
  compare_set = root_set
6169
- else:
6210
+ if not do_borders:
6211
+ root_set = list(self.node_centroids.keys())
6212
+ elif self.node_identities is not None:
6170
6213
  for node, iden in self.node_identities.items():
6171
6214
 
6172
6215
  if iden == root:
@@ -6194,7 +6237,8 @@ class Network_3D:
6194
6237
  targ = [targ]
6195
6238
 
6196
6239
  compare_set_neigh = approx_boundaries(self.nodes, targ, self.node_identities, keep_labels = False)
6197
- 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)
6198
6242
 
6199
6243
  if quant:
6200
6244
  try:
@@ -6236,7 +6280,15 @@ class Network_3D:
6236
6280
  else:
6237
6281
  is_2d = False
6238
6282
 
6239
- 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)
6240
6292
 
6241
6293
  node_intensity = {}
6242
6294
  import math
@@ -6244,7 +6296,6 @@ class Network_3D:
6244
6296
 
6245
6297
  for node in root_set:
6246
6298
  node_intensity[node] = math.log(pred/output[node])
6247
- #print(output[node])
6248
6299
  node_centroids[node] = self.node_centroids[node]
6249
6300
 
6250
6301
  if numpy:
@@ -4,7 +4,7 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QG
4
4
  QHBoxLayout, QSlider, QMenuBar, QMenu, QDialog,
5
5
  QFormLayout, QLineEdit, QPushButton, QFileDialog,
6
6
  QLabel, QComboBox, QMessageBox, QTableView, QInputDialog,
7
- QMenu, QTabWidget, QGroupBox)
7
+ QMenu, QTabWidget, QGroupBox, QCheckBox)
8
8
  from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer, QThread, pyqtSignal, QObject, QCoreApplication, QEvent, QEventLoop)
9
9
  import numpy as np
10
10
  import time
@@ -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)
@@ -5471,7 +5473,6 @@ class ImageViewerWindow(QMainWindow):
5471
5473
 
5472
5474
  elif sort == 'Merge Nodes':
5473
5475
  try:
5474
-
5475
5476
  if my_network.nodes is None:
5476
5477
  QMessageBox.critical(
5477
5478
  self,
@@ -5479,72 +5480,118 @@ class ImageViewerWindow(QMainWindow):
5479
5480
  "Please load your first set of nodes into the 'Nodes' channel first"
5480
5481
  )
5481
5482
  return
5482
-
5483
5483
  if len(np.unique(my_network.nodes)) < 3:
5484
5484
  self.show_label_dialog()
5485
-
5486
- # First ask user what they want to select
5487
- msg = QMessageBox()
5488
- msg.setWindowTitle("Selection Type")
5489
- msg.setText("Would you like to select a TIFF file or a directory?")
5490
- tiff_button = msg.addButton("TIFF File", QMessageBox.ButtonRole.AcceptRole)
5491
- dir_button = msg.addButton("Directory", QMessageBox.ButtonRole.AcceptRole)
5492
- msg.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
5493
-
5494
- msg.exec()
5495
-
5496
- # Also if they want centroids:
5497
- msg2 = QMessageBox()
5498
- msg2.setWindowTitle("Selection Type")
5499
- msg2.setText("Would you like to compute node centroids for each image prior to merging?")
5500
- yes_button = msg2.addButton("Yes", QMessageBox.ButtonRole.AcceptRole)
5501
- no_button = msg2.addButton("No", QMessageBox.ButtonRole.AcceptRole)
5502
- msg2.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
5503
-
5504
- msg2.exec()
5505
-
5506
- if msg2.clickedButton() == yes_button:
5507
- centroids = True
5508
- else:
5509
- centroids = False
5510
-
5511
- if msg.clickedButton() == tiff_button:
5512
- # Code for selecting TIFF files
5513
- filename, _ = QFileDialog.getOpenFileName(
5514
- self,
5515
- "Select TIFF file",
5516
- "",
5517
- "TIFF files (*.tiff *.tif)"
5518
- )
5519
- if filename:
5520
- selected_path = filename
5521
-
5522
- elif msg.clickedButton() == dir_button:
5523
- # Code for selecting directories
5524
- dialog = QFileDialog(self)
5525
- dialog.setOption(QFileDialog.Option.DontUseNativeDialog)
5526
- dialog.setOption(QFileDialog.Option.ReadOnly)
5527
- dialog.setFileMode(QFileDialog.FileMode.Directory)
5528
- dialog.setViewMode(QFileDialog.ViewMode.Detail)
5529
-
5530
- if dialog.exec() == QFileDialog.DialogCode.Accepted:
5531
- selected_path = dialog.directory().absolutePath()
5532
-
5533
- my_network.merge_nodes(selected_path, root_id = self.node_name, centroids = centroids)
5534
- self.load_channel(0, my_network.nodes, True)
5535
-
5536
-
5537
- if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
5485
+
5486
+ # Create custom dialog
5487
+ dialog = QDialog(self)
5488
+ dialog.setWindowTitle("Merge Nodes Configuration")
5489
+ dialog.setModal(True)
5490
+ dialog.resize(400, 200)
5491
+
5492
+ layout = QVBoxLayout(dialog)
5493
+
5494
+ # Selection type
5495
+ type_layout = QHBoxLayout()
5496
+ type_label = QLabel("Selection Type:")
5497
+ type_combo = QComboBox()
5498
+ type_combo.addItems(["TIFF File", "Directory"])
5499
+ type_layout.addWidget(type_label)
5500
+ type_layout.addWidget(type_combo)
5501
+ layout.addLayout(type_layout)
5502
+
5503
+ # Centroids checkbox
5504
+ centroids_layout = QHBoxLayout()
5505
+ centroids_check = QCheckBox("Compute node centroids for each image prior to merging")
5506
+ centroids_layout.addWidget(centroids_check)
5507
+ layout.addLayout(centroids_layout)
5508
+
5509
+ # Down factor for centroid calculation
5510
+ down_factor_layout = QHBoxLayout()
5511
+ down_factor_label = QLabel("Down Factor (for centroid calculation downsampling):")
5512
+ down_factor_edit = QLineEdit()
5513
+ down_factor_edit.setText("1") # Default value
5514
+ down_factor_edit.setPlaceholderText("Enter down factor (e.g., 1, 2, 4)")
5515
+ down_factor_layout.addWidget(down_factor_label)
5516
+ down_factor_layout.addWidget(down_factor_edit)
5517
+ layout.addLayout(down_factor_layout)
5518
+
5519
+ # Buttons
5520
+ button_layout = QHBoxLayout()
5521
+ accept_button = QPushButton("Accept")
5522
+ cancel_button = QPushButton("Cancel")
5523
+ button_layout.addWidget(accept_button)
5524
+ button_layout.addWidget(cancel_button)
5525
+ layout.addLayout(button_layout)
5526
+
5527
+ # Connect buttons
5528
+ accept_button.clicked.connect(dialog.accept)
5529
+ cancel_button.clicked.connect(dialog.reject)
5530
+
5531
+ # Execute dialog
5532
+ if dialog.exec() == QDialog.DialogCode.Accepted:
5533
+ # Get values from dialog
5534
+ selection_type = type_combo.currentText()
5535
+ centroids = centroids_check.isChecked()
5536
+
5537
+ # Validate and get down_factor
5538
5538
  try:
5539
- self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
5540
- except Exception as e:
5541
- print(f"Error loading node identity table: {e}")
5542
- if centroids:
5543
- self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
5544
-
5545
-
5539
+ down_factor = int(down_factor_edit.text())
5540
+ if down_factor <= 0:
5541
+ raise ValueError("Down factor must be positive")
5542
+ except ValueError as e:
5543
+ QMessageBox.critical(
5544
+ self,
5545
+ "Invalid Input",
5546
+ f"Invalid down factor: {str(e)}"
5547
+ )
5548
+ return
5549
+
5550
+ # Handle file/directory selection based on combo box choice
5551
+ if selection_type == "TIFF File":
5552
+ filename, _ = QFileDialog.getOpenFileName(
5553
+ self,
5554
+ "Select TIFF file",
5555
+ "",
5556
+ "TIFF files (*.tiff *.tif)"
5557
+ )
5558
+ if filename:
5559
+ selected_path = filename
5560
+ else:
5561
+ return # User cancelled file selection
5562
+ else: # Directory
5563
+ file_dialog = QFileDialog(self)
5564
+ file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog)
5565
+ file_dialog.setOption(QFileDialog.Option.ReadOnly)
5566
+ file_dialog.setFileMode(QFileDialog.FileMode.Directory)
5567
+ file_dialog.setViewMode(QFileDialog.ViewMode.Detail)
5568
+ if file_dialog.exec() == QFileDialog.DialogCode.Accepted:
5569
+ selected_path = file_dialog.directory().absolutePath()
5570
+ else:
5571
+ return # User cancelled directory selection
5572
+
5573
+ if down_factor == 1:
5574
+ down_factor = None
5575
+ # Call merge_nodes with all parameters
5576
+ my_network.merge_nodes(
5577
+ selected_path,
5578
+ root_id=self.node_name,
5579
+ centroids=centroids,
5580
+ down_factor=down_factor
5581
+ )
5582
+
5583
+ self.load_channel(0, my_network.nodes, True)
5584
+
5585
+ if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
5586
+ try:
5587
+ self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
5588
+ except Exception as e:
5589
+ print(f"Error loading node identity table: {e}")
5590
+
5591
+ if centroids:
5592
+ self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
5593
+
5546
5594
  except Exception as e:
5547
-
5548
5595
  QMessageBox.critical(
5549
5596
  self,
5550
5597
  "Error Merging",
@@ -5567,7 +5614,8 @@ class ImageViewerWindow(QMainWindow):
5567
5614
  )
5568
5615
 
5569
5616
  self.last_load = directory
5570
-
5617
+ self.last_saved = os.path.dirname(directory)
5618
+ self.last_save_name = directory
5571
5619
 
5572
5620
  if directory != "":
5573
5621
 
@@ -5649,8 +5697,6 @@ class ImageViewerWindow(QMainWindow):
5649
5697
  f"Failed to load Network 3D Object: {str(e)}"
5650
5698
  )
5651
5699
 
5652
-
5653
-
5654
5700
  def load_network(self):
5655
5701
  """Load in the network from a .xlsx (need to add .csv support)"""
5656
5702
 
@@ -8993,7 +9039,7 @@ class ComNeighborDialog(QDialog):
8993
9039
 
8994
9040
  mode = self.mode.currentIndex()
8995
9041
 
8996
- 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
8997
9043
 
8998
9044
  limit = int(self.limit.text()) if self.limit.text().strip() else None
8999
9045
 
@@ -9190,6 +9236,11 @@ class NearNeighDialog(QDialog):
9190
9236
  self.numpy.setChecked(False)
9191
9237
  self.numpy.clicked.connect(self.toggle_map)
9192
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)
9193
9244
 
9194
9245
  main_layout.addWidget(heatmap_group)
9195
9246
 
@@ -9281,35 +9332,48 @@ class NearNeighDialog(QDialog):
9281
9332
  root = None
9282
9333
  targ = None
9283
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
+
9284
9346
  heatmap = self.map.isChecked()
9285
9347
  threed = self.threed.isChecked()
9286
9348
  numpy = self.numpy.isChecked()
9287
9349
  num = int(self.num.text()) if self.num.text().strip() else 1
9288
9350
  quant = self.quant.isChecked()
9289
9351
  centroids = self.centroids.isChecked()
9352
+
9290
9353
  if not centroids:
9354
+ print("Using 1 nearest neighbor due to not using centroids")
9291
9355
  num = 1
9292
9356
 
9293
9357
  if root is not None and targ is not None:
9294
9358
  title = f"Nearest {num} Neighbor(s) Distance of {targ} from {root}"
9295
- header = f"Shortest Distance to Closest {num} {targ}(s)"
9359
+ header = f"Avg Shortest Distance to Closest {num} {targ}(s)"
9296
9360
  header2 = f"{root} Node ID"
9297
9361
  header3 = f'Theoretical Uniform Distance to Closest {num} {targ}(s)'
9298
9362
  else:
9299
9363
  title = f"Nearest {num} Neighbor(s) Distance Between Nodes"
9300
- header = f"Shortest Distance to Closest {num} Nodes"
9364
+ header = f"Avg Shortest Distance to Closest {num} Nodes"
9301
9365
  header2 = "Root Node ID"
9302
9366
  header3 = f'Simulated Theoretical Uniform Distance to Closest {num} Nodes'
9303
9367
 
9304
- if centroids and my_network.node_centroids is None:
9368
+ if my_network.node_centroids is None:
9305
9369
  self.parent().show_centroid_dialog()
9306
9370
  if my_network.node_centroids is None:
9307
9371
  return
9308
9372
 
9309
9373
  if not numpy:
9310
- 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)
9311
9375
  else:
9312
- 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)
9313
9377
  self.parent().load_channel(3, overlay, data = True, preserve_zoom = (self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
9314
9378
 
9315
9379
  if quant_overlay is not None:
@@ -10064,9 +10128,33 @@ class ViolinDialog(QDialog):
10064
10128
 
10065
10129
  return df_normalized
10066
10130
 
10131
+ def show_in_table(self, df, metric, title):
10132
+
10133
+ # Create new table
10134
+ table = CustomTableView(self.parent())
10135
+ table.setModel(PandasModel(df))
10136
+
10137
+ try:
10138
+ first_column_name = table.model()._data.columns[0]
10139
+ table.sort_table(first_column_name, ascending=True)
10140
+ except:
10141
+ pass
10142
+
10143
+ # Add to tabbed widget
10144
+ if title is None:
10145
+ self.parent().tabbed_data.add_table(f"{metric} Analysis", table)
10146
+ else:
10147
+ self.parent().tabbed_data.add_table(f"{title}", table)
10148
+
10149
+
10150
+
10151
+ # Adjust column widths to content
10152
+ for column in range(table.model().columnCount(None)):
10153
+ table.resizeColumnToContents(column)
10154
+
10067
10155
  def run(self):
10068
10156
 
10069
- def df_to_dict_by_rows(df, row_indices):
10157
+ def df_to_dict_by_rows(df, row_indices, title):
10070
10158
  """
10071
10159
  Convert a pandas DataFrame to a dictionary by selecting specific rows.
10072
10160
  No normalization - dataframe is already normalized.
@@ -10095,6 +10183,10 @@ class ViolinDialog(QDialog):
10095
10183
  for column in masked_df.columns:
10096
10184
  result_dict[column] = masked_df[column].tolist()
10097
10185
 
10186
+ masked_df.insert(0, "NodeIDs", row_indices)
10187
+ self.show_in_table(masked_df, metric = "NodeID", title = title)
10188
+
10189
+
10098
10190
  return result_dict
10099
10191
 
10100
10192
  from . import neighborhoods
@@ -10112,10 +10204,10 @@ class ViolinDialog(QDialog):
10112
10204
  if iden in parse:
10113
10205
  iden_list.append(item)
10114
10206
  except:
10115
- if iden == item:
10207
+ if (iden == my_network.node_identities[item]):
10116
10208
  iden_list.append(item)
10117
10209
 
10118
- violin_dict = df_to_dict_by_rows(self.df, iden_list)
10210
+ violin_dict = df_to_dict_by_rows(self.df, iden_list, f"Z-Score-like Channel Intensities of Identity {iden}, {len(iden_list)} Nodes")
10119
10211
 
10120
10212
  neighborhoods.create_violin_plots(violin_dict, graph_title=f"Z-Score-like Channel Intensities of Identity {iden}, {len(iden_list)} Nodes")
10121
10213
 
@@ -10124,11 +10216,11 @@ class ViolinDialog(QDialog):
10124
10216
 
10125
10217
  com = self.coms.currentText()
10126
10218
 
10127
- com_dict = n3d.invert_dict(my_network.communities) # Fixed: should be communities
10219
+ com_dict = n3d.invert_dict(my_network.communities)
10128
10220
 
10129
10221
  com_list = com_dict[int(com)]
10130
10222
 
10131
- violin_dict = df_to_dict_by_rows(self.df, com_list)
10223
+ violin_dict = df_to_dict_by_rows(self.df, com_list, f"Z-Score-like Channel Intensities of Community/Neighborhood {com}, {len(com_list)} Nodes")
10132
10224
 
10133
10225
  neighborhoods.create_violin_plots(violin_dict, graph_title=f"Z-Score-like Channel Intensities of Community/Neighborhood {com}, {len(com_list)} Nodes")
10134
10226
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nettracer3d
3
- Version: 1.0.2
3
+ Version: 1.0.4
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,7 +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.2 Updates --
113
+ -- Version 1.0.4 Updates --
114
114
 
115
- * Minor fixes
116
- * Added ability to generate violin plots using the table generated from merging node identities, showing the relative expression of markers for multiple channels for the nodes belonging to some channel or community/neighborhood
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=dZR8wOFUDNmfsn1M615FKbg1n4C_RhtwLh9vtjCryBg,270737
9
- nettracer3d/nettracer_gui.py,sha256=m1V7S1YMw17YBgcAdE3lRmCbvoBsTZyUl7dOmQ6N6OI,661601
8
+ nettracer3d/nettracer.py,sha256=6r5aGtmiHxGkfCh5lJuREAoloZ3WKvcaRAD7kmWXedk,273957
9
+ nettracer3d/nettracer_gui.py,sha256=0QzoDa6o2z-iVZzM3v4tePFV-tvw-lkD_24tQ0fZ1sU,666134
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.2.dist-info/licenses/LICENSE,sha256=jnNT-yBeIAKAHpYthPvLeqCzJ6nSurgnKmloVnfsjCI,764
21
- nettracer3d-1.0.2.dist-info/METADATA,sha256=1LixOhZlmUtLcP9OrX6WK_mS0M2tuzo3tjGbw4FZrEE,7218
22
- nettracer3d-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- nettracer3d-1.0.2.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
24
- nettracer3d-1.0.2.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
25
- nettracer3d-1.0.2.dist-info/RECORD,,
20
+ nettracer3d-1.0.4.dist-info/licenses/LICENSE,sha256=jnNT-yBeIAKAHpYthPvLeqCzJ6nSurgnKmloVnfsjCI,764
21
+ nettracer3d-1.0.4.dist-info/METADATA,sha256=VMs_Lcaa3xnnLTrror42GmRUJBdzIcyGk0sMtJZZ9Eo,7120
22
+ nettracer3d-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ nettracer3d-1.0.4.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
24
+ nettracer3d-1.0.4.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
25
+ nettracer3d-1.0.4.dist-info/RECORD,,