nettracer3d 1.0.2__py3-none-any.whl → 1.0.3__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.
nettracer3d/nettracer.py CHANGED
@@ -3933,7 +3933,7 @@ class Network_3D:
3933
3933
  """
3934
3934
  self._nodes, num_nodes = label_objects(nodes, structure_3d)
3935
3935
 
3936
- def combine_nodes(self, root_nodes, other_nodes, other_ID, identity_dict, root_ID = None, centroids = False):
3936
+ def combine_nodes(self, root_nodes, other_nodes, other_ID, identity_dict, root_ID = None, centroids = False, down_factor = None):
3937
3937
 
3938
3938
  """Internal method to merge two labelled node arrays into one"""
3939
3939
 
@@ -3944,7 +3944,10 @@ class Network_3D:
3944
3944
  max_val = np.max(root_nodes)
3945
3945
  other_nodes[:] = np.where(mask, other_nodes + max_val, 0)
3946
3946
  if centroids:
3947
- new_dict = network_analysis._find_centroids(other_nodes)
3947
+ new_dict = network_analysis._find_centroids(other_nodes, down_factor = down_factor)
3948
+ if down_factor is not None:
3949
+ for item in new_dict:
3950
+ new_dict[item] = down_factor * new_dict[item]
3948
3951
  self.node_centroids.update(new_dict)
3949
3952
 
3950
3953
  if root_ID is not None:
@@ -3984,7 +3987,7 @@ class Network_3D:
3984
3987
 
3985
3988
  return nodes, identity_dict
3986
3989
 
3987
- def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes", centroids = False):
3990
+ def merge_nodes(self, addn_nodes_name, label_nodes = True, root_id = "Root_Nodes", centroids = False, down_factor = None):
3988
3991
  """
3989
3992
  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
3993
  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 +4008,21 @@ class Network_3D:
4005
4008
  identity_dict = {} #A dictionary to deliniate the node identities
4006
4009
 
4007
4010
  if centroids:
4008
- self.node_centroids = network_analysis._find_centroids(self._nodes)
4009
-
4011
+ self.node_centroids = network_analysis._find_centroids(self._nodes, down_factor = down_factor)
4012
+ if down_factor is not None:
4013
+ for item in self.node_centroids:
4014
+ self.node_centroids[item] = down_factor * self.node_centroids[item]
4010
4015
 
4011
4016
  try: #Try presumes the input is a tif
4012
4017
  addn_nodes = tifffile.imread(addn_nodes_name) #If not this will fail and activate the except block
4013
4018
 
4014
4019
  if label_nodes is True:
4015
4020
  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
4021
+ 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
4022
  num_nodes = np.max(node_labels)
4018
4023
 
4019
4024
  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)
4025
+ 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
4026
  num_nodes = int(np.max(node_labels))
4022
4027
 
4023
4028
  except: #Exception presumes the input is a directory containing multiple tifs, to allow multi-node stackage.
@@ -4035,15 +4040,15 @@ class Network_3D:
4035
4040
  if label_nodes is True:
4036
4041
  addn_nodes, num_nodes2 = label_objects(addn_nodes) # Label the node objects. Note this presumes no overlap between node masks.
4037
4042
  if i == 0:
4038
- node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
4043
+ 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
4044
  else:
4040
- node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
4045
+ node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids, down_factor = down_factor)
4041
4046
 
4042
4047
  else:
4043
4048
  if i == 0:
4044
- node_labels, identity_dict = self.combine_nodes(self._nodes, addn_nodes, addn_nodes_ID, identity_dict, nodes_name, centroids = centroids)
4049
+ 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
4050
  else:
4046
- node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids)
4051
+ node_labels, identity_dict = self.combine_nodes(node_labels, addn_nodes, addn_nodes_ID, identity_dict, centroids = centroids, down_factor = down_factor)
4047
4052
  except Exception as e:
4048
4053
  print("Could not open additional nodes, verify they are being inputted correctly...")
4049
4054
 
@@ -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
@@ -5471,7 +5471,6 @@ class ImageViewerWindow(QMainWindow):
5471
5471
 
5472
5472
  elif sort == 'Merge Nodes':
5473
5473
  try:
5474
-
5475
5474
  if my_network.nodes is None:
5476
5475
  QMessageBox.critical(
5477
5476
  self,
@@ -5479,72 +5478,118 @@ class ImageViewerWindow(QMainWindow):
5479
5478
  "Please load your first set of nodes into the 'Nodes' channel first"
5480
5479
  )
5481
5480
  return
5482
-
5483
5481
  if len(np.unique(my_network.nodes)) < 3:
5484
5482
  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:
5483
+
5484
+ # Create custom dialog
5485
+ dialog = QDialog(self)
5486
+ dialog.setWindowTitle("Merge Nodes Configuration")
5487
+ dialog.setModal(True)
5488
+ dialog.resize(400, 200)
5489
+
5490
+ layout = QVBoxLayout(dialog)
5491
+
5492
+ # Selection type
5493
+ type_layout = QHBoxLayout()
5494
+ type_label = QLabel("Selection Type:")
5495
+ type_combo = QComboBox()
5496
+ type_combo.addItems(["TIFF File", "Directory"])
5497
+ type_layout.addWidget(type_label)
5498
+ type_layout.addWidget(type_combo)
5499
+ layout.addLayout(type_layout)
5500
+
5501
+ # Centroids checkbox
5502
+ centroids_layout = QHBoxLayout()
5503
+ centroids_check = QCheckBox("Compute node centroids for each image prior to merging")
5504
+ centroids_layout.addWidget(centroids_check)
5505
+ layout.addLayout(centroids_layout)
5506
+
5507
+ # Down factor for centroid calculation
5508
+ down_factor_layout = QHBoxLayout()
5509
+ down_factor_label = QLabel("Down Factor (for centroid calculation downsampling):")
5510
+ down_factor_edit = QLineEdit()
5511
+ down_factor_edit.setText("1") # Default value
5512
+ down_factor_edit.setPlaceholderText("Enter down factor (e.g., 1, 2, 4)")
5513
+ down_factor_layout.addWidget(down_factor_label)
5514
+ down_factor_layout.addWidget(down_factor_edit)
5515
+ layout.addLayout(down_factor_layout)
5516
+
5517
+ # Buttons
5518
+ button_layout = QHBoxLayout()
5519
+ accept_button = QPushButton("Accept")
5520
+ cancel_button = QPushButton("Cancel")
5521
+ button_layout.addWidget(accept_button)
5522
+ button_layout.addWidget(cancel_button)
5523
+ layout.addLayout(button_layout)
5524
+
5525
+ # Connect buttons
5526
+ accept_button.clicked.connect(dialog.accept)
5527
+ cancel_button.clicked.connect(dialog.reject)
5528
+
5529
+ # Execute dialog
5530
+ if dialog.exec() == QDialog.DialogCode.Accepted:
5531
+ # Get values from dialog
5532
+ selection_type = type_combo.currentText()
5533
+ centroids = centroids_check.isChecked()
5534
+
5535
+ # Validate and get down_factor
5538
5536
  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
-
5537
+ down_factor = int(down_factor_edit.text())
5538
+ if down_factor <= 0:
5539
+ raise ValueError("Down factor must be positive")
5540
+ except ValueError as e:
5541
+ QMessageBox.critical(
5542
+ self,
5543
+ "Invalid Input",
5544
+ f"Invalid down factor: {str(e)}"
5545
+ )
5546
+ return
5547
+
5548
+ # Handle file/directory selection based on combo box choice
5549
+ if selection_type == "TIFF File":
5550
+ filename, _ = QFileDialog.getOpenFileName(
5551
+ self,
5552
+ "Select TIFF file",
5553
+ "",
5554
+ "TIFF files (*.tiff *.tif)"
5555
+ )
5556
+ if filename:
5557
+ selected_path = filename
5558
+ else:
5559
+ return # User cancelled file selection
5560
+ else: # Directory
5561
+ file_dialog = QFileDialog(self)
5562
+ file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog)
5563
+ file_dialog.setOption(QFileDialog.Option.ReadOnly)
5564
+ file_dialog.setFileMode(QFileDialog.FileMode.Directory)
5565
+ file_dialog.setViewMode(QFileDialog.ViewMode.Detail)
5566
+ if file_dialog.exec() == QFileDialog.DialogCode.Accepted:
5567
+ selected_path = file_dialog.directory().absolutePath()
5568
+ else:
5569
+ return # User cancelled directory selection
5570
+
5571
+ if down_factor == 1:
5572
+ down_factor = None
5573
+ # Call merge_nodes with all parameters
5574
+ my_network.merge_nodes(
5575
+ selected_path,
5576
+ root_id=self.node_name,
5577
+ centroids=centroids,
5578
+ down_factor=down_factor
5579
+ )
5580
+
5581
+ self.load_channel(0, my_network.nodes, True)
5582
+
5583
+ if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
5584
+ try:
5585
+ self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
5586
+ except Exception as e:
5587
+ print(f"Error loading node identity table: {e}")
5588
+
5589
+ if centroids:
5590
+ self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
5591
+
5546
5592
  except Exception as e:
5547
-
5548
5593
  QMessageBox.critical(
5549
5594
  self,
5550
5595
  "Error Merging",
@@ -10064,9 +10109,33 @@ class ViolinDialog(QDialog):
10064
10109
 
10065
10110
  return df_normalized
10066
10111
 
10112
+ def show_in_table(self, df, metric, title):
10113
+
10114
+ # Create new table
10115
+ table = CustomTableView(self.parent())
10116
+ table.setModel(PandasModel(df))
10117
+
10118
+ try:
10119
+ first_column_name = table.model()._data.columns[0]
10120
+ table.sort_table(first_column_name, ascending=True)
10121
+ except:
10122
+ pass
10123
+
10124
+ # Add to tabbed widget
10125
+ if title is None:
10126
+ self.parent().tabbed_data.add_table(f"{metric} Analysis", table)
10127
+ else:
10128
+ self.parent().tabbed_data.add_table(f"{title}", table)
10129
+
10130
+
10131
+
10132
+ # Adjust column widths to content
10133
+ for column in range(table.model().columnCount(None)):
10134
+ table.resizeColumnToContents(column)
10135
+
10067
10136
  def run(self):
10068
10137
 
10069
- def df_to_dict_by_rows(df, row_indices):
10138
+ def df_to_dict_by_rows(df, row_indices, title):
10070
10139
  """
10071
10140
  Convert a pandas DataFrame to a dictionary by selecting specific rows.
10072
10141
  No normalization - dataframe is already normalized.
@@ -10095,6 +10164,10 @@ class ViolinDialog(QDialog):
10095
10164
  for column in masked_df.columns:
10096
10165
  result_dict[column] = masked_df[column].tolist()
10097
10166
 
10167
+ masked_df.insert(0, "NodeIDs", row_indices)
10168
+ self.show_in_table(masked_df, metric = "NodeID", title = title)
10169
+
10170
+
10098
10171
  return result_dict
10099
10172
 
10100
10173
  from . import neighborhoods
@@ -10112,10 +10185,10 @@ class ViolinDialog(QDialog):
10112
10185
  if iden in parse:
10113
10186
  iden_list.append(item)
10114
10187
  except:
10115
- if iden == item:
10188
+ if (iden == my_network.node_identities[item]):
10116
10189
  iden_list.append(item)
10117
10190
 
10118
- violin_dict = df_to_dict_by_rows(self.df, iden_list)
10191
+ 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
10192
 
10120
10193
  neighborhoods.create_violin_plots(violin_dict, graph_title=f"Z-Score-like Channel Intensities of Identity {iden}, {len(iden_list)} Nodes")
10121
10194
 
@@ -10124,11 +10197,11 @@ class ViolinDialog(QDialog):
10124
10197
 
10125
10198
  com = self.coms.currentText()
10126
10199
 
10127
- com_dict = n3d.invert_dict(my_network.communities) # Fixed: should be communities
10200
+ com_dict = n3d.invert_dict(my_network.communities)
10128
10201
 
10129
10202
  com_list = com_dict[int(com)]
10130
10203
 
10131
- violin_dict = df_to_dict_by_rows(self.df, com_list)
10204
+ 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
10205
 
10133
10206
  neighborhoods.create_violin_plots(violin_dict, graph_title=f"Z-Score-like Channel Intensities of Community/Neighborhood {com}, {len(com_list)} Nodes")
10134
10207
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nettracer3d
3
- Version: 1.0.2
3
+ Version: 1.0.3
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,6 @@ 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.3 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
@@ -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=_K_npg50qrXU1kj8NeFnlQY0oRMnD_PyvNi23hoQDow,271318
9
+ nettracer3d/nettracer_gui.py,sha256=3nUjgPi2H-OKqBB7PP35kQVXLJNzPJN9wx9Ay_9pt50,665218
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.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,,