nettracer3d 0.9.8__tar.gz → 1.0.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.

Files changed (30) hide show
  1. {nettracer3d-0.9.8/src/nettracer3d.egg-info → nettracer3d-1.0.0}/PKG-INFO +4 -3
  2. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/README.md +3 -2
  3. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/pyproject.toml +1 -1
  4. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/nettracer_gui.py +194 -12
  5. {nettracer3d-0.9.8 → nettracer3d-1.0.0/src/nettracer3d.egg-info}/PKG-INFO +4 -3
  6. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/LICENSE +0 -0
  7. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/setup.cfg +0 -0
  8. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/__init__.py +0 -0
  9. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/cellpose_manager.py +0 -0
  10. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/community_extractor.py +0 -0
  11. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/excelotron.py +0 -0
  12. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/modularity.py +0 -0
  13. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/morphology.py +0 -0
  14. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/neighborhoods.py +0 -0
  15. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/nettracer.py +0 -0
  16. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/network_analysis.py +0 -0
  17. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/network_draw.py +0 -0
  18. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/node_draw.py +0 -0
  19. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/painting.py +0 -0
  20. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/proximity.py +0 -0
  21. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/run.py +0 -0
  22. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/segmenter.py +0 -0
  23. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/segmenter_GPU.py +0 -0
  24. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/simple_network.py +0 -0
  25. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/smart_dilate.py +0 -0
  26. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
  27. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
  28. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/entry_points.txt +0 -0
  29. {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/requires.txt +0 -0
  30. {nettracer3d-0.9.8 → nettracer3d-1.0.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.9.8
3
+ Version: 1.0.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/
@@ -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 0.9.8 Updates --
113
+ -- Version 1.0.0 Updates --
114
114
 
115
- * Some minor bug fixes
115
+ * The 'network selection' table is now auto-populated when using the multiple-identity selector, and when using the node thresholder.
116
+ * And other minor adjustments/bug fixes
@@ -65,6 +65,7 @@ McLaughlin, L., Zhang, B., Sharma, S. et al. Three dimensional multiscalar neuro
65
65
 
66
66
  NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
67
67
 
68
- -- Version 0.9.8 Updates --
68
+ -- Version 1.0.0 Updates --
69
69
 
70
- * Some minor bug fixes
70
+ * The 'network selection' table is now auto-populated when using the multiple-identity selector, and when using the node thresholder.
71
+ * And other minor adjustments/bug fixes
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nettracer3d"
3
- version = "0.9.8"
3
+ version = "1.0.0"
4
4
  authors = [
5
5
  { name="Liam McLaughlin", email="liamm@wustl.edu" },
6
6
  ]
@@ -466,6 +466,102 @@ class ImageViewerWindow(QMainWindow):
466
466
  self.hold_update = False
467
467
  self._first_pan_done = False
468
468
 
469
+
470
+ def load_file(self):
471
+ """Load CSV or Excel file and convert to dictionary format."""
472
+ try:
473
+ # Open file dialog
474
+ file_filter = "Spreadsheet Files (*.csv *.xlsx);;CSV Files (*.csv);;Excel Files (*.xlsx)"
475
+ filename, _ = QFileDialog.getOpenFileName(
476
+ self,
477
+ "Load File",
478
+ "",
479
+ file_filter
480
+ )
481
+
482
+ if not filename:
483
+ return
484
+
485
+ # Read the file
486
+ if filename.endswith('.csv'):
487
+ df = pd.read_csv(filename)
488
+ elif filename.endswith('.xlsx'):
489
+ df = pd.read_excel(filename)
490
+ else:
491
+ QMessageBox.warning(self, "Error", "Please select a CSV or Excel file.")
492
+ return
493
+
494
+ if df.empty:
495
+ QMessageBox.warning(self, "Error", "The file appears to be empty.")
496
+ return
497
+
498
+ # Extract headers
499
+ headers = df.columns.tolist()
500
+ if len(headers) < 1:
501
+ QMessageBox.warning(self, "Error", "File must have at least 1 column.")
502
+ return
503
+
504
+ # Extract filename without extension for title
505
+ import os
506
+ title = os.path.splitext(os.path.basename(filename))[0]
507
+
508
+ if len(headers) == 1:
509
+ # Single column: pass header to metric, column data as list to data, nothing to value
510
+ metric = headers[0]
511
+ data = df.iloc[:, 0].tolist() # First column as list
512
+ value = None
513
+
514
+ self.format_for_upperright_table(
515
+ data=data,
516
+ metric=metric,
517
+ value=value,
518
+ title=title
519
+ )
520
+ else:
521
+ # Multiple columns: create dictionary as before
522
+ # First column header (for metric parameter)
523
+ metric = headers[0]
524
+
525
+ # Remaining headers (for value parameter)
526
+ value = headers[1:]
527
+
528
+ # Create dictionary
529
+ data_dict = {}
530
+
531
+ for index, row in df.iterrows():
532
+ key = row.iloc[0] # First column value as key
533
+
534
+ if len(headers) == 2:
535
+ # If only 2 columns, store single value
536
+ data_dict[key] = row.iloc[1]
537
+ else:
538
+ # If more than 2 columns, store as list
539
+ data_dict[key] = row.iloc[1:].tolist()
540
+
541
+ if len(value) == 1:
542
+ value = value[0]
543
+
544
+ # Call the parent method
545
+ self.format_for_upperright_table(
546
+ data=data_dict,
547
+ metric=metric,
548
+ value=value,
549
+ title=title
550
+ )
551
+
552
+ QMessageBox.information(
553
+ self,
554
+ "Success",
555
+ f"File '{title}' loaded successfully with {len(df)} entries."
556
+ )
557
+
558
+ except Exception as e:
559
+ QMessageBox.critical(
560
+ self,
561
+ "Error",
562
+ f"Failed to load file: {str(e)}"
563
+ )
564
+
469
565
  def popup_canvas(self):
470
566
  """Pop the canvas out into its own window"""
471
567
  if hasattr(self, 'popup_window') and self.popup_window.isVisible():
@@ -931,7 +1027,6 @@ class ImageViewerWindow(QMainWindow):
931
1027
 
932
1028
  """Highlight overlay generation method specific for the segmenter interactive mode"""
933
1029
 
934
-
935
1030
  def process_chunk_bounds(chunk_data, indices_to_check):
936
1031
  """Process a single chunk of the array to create highlight mask"""
937
1032
  mask = (chunk_data >= indices_to_check[0]) & (chunk_data <= indices_to_check[1])
@@ -1010,6 +1105,30 @@ class ImageViewerWindow(QMainWindow):
1010
1105
  current_ylim = self.ax.get_ylim()
1011
1106
  self.update_display_pan_mode(current_xlim, current_ylim)
1012
1107
 
1108
+ if my_network.network is not None:
1109
+ try:
1110
+ if self.active_channel == 0:
1111
+
1112
+ # Get the existing DataFrame from the model
1113
+ original_df = self.network_table.model()._data
1114
+
1115
+ # Create mask for rows where one column is any original node AND the other column is any neighbor
1116
+ mask = (
1117
+ (original_df.iloc[:, 0].isin(indices)) &
1118
+ (original_df.iloc[:, 1].isin(indices)))
1119
+
1120
+ # Filter the DataFrame to only include direct connections
1121
+ filtered_df = original_df[mask].copy()
1122
+
1123
+ # Create new model with filtered DataFrame and update selection table
1124
+ new_model = PandasModel(filtered_df)
1125
+ self.selection_table.setModel(new_model)
1126
+
1127
+ # Switch to selection table
1128
+ self.selection_button.click()
1129
+ except:
1130
+ pass
1131
+
1013
1132
 
1014
1133
 
1015
1134
  def create_mini_overlay(self, node_indices = None, edge_indices = None):
@@ -3872,6 +3991,30 @@ class ImageViewerWindow(QMainWindow):
3872
3991
  if len(self.clicked_values['edges']):
3873
3992
  self.highlight_value_in_tables(self.clicked_values['edges'][-1])
3874
3993
  self.handle_info('edge')
3994
+
3995
+ if len(self.clicked_values['nodes']) > 0 or len(self.clicked_values['edges']) > 0: # Check if we have any nodes selected
3996
+
3997
+ old_nodes = copy.deepcopy(self.clicked_values['nodes'])
3998
+
3999
+ # Get the existing DataFrame from the model
4000
+ original_df = self.network_table.model()._data
4001
+
4002
+ # Create mask for rows where one column is any original node AND the other column is any neighbor
4003
+ mask = (
4004
+ ((original_df.iloc[:, 0].isin(self.clicked_values['nodes'])) &
4005
+ (original_df.iloc[:, 1].isin(self.clicked_values['nodes']))) |
4006
+ (original_df.iloc[:, 2].isin(self.clicked_values['edges']))
4007
+ )
4008
+
4009
+ # Filter the DataFrame to only include direct connections
4010
+ filtered_df = original_df[mask].copy()
4011
+
4012
+ # Create new model with filtered DataFrame and update selection table
4013
+ new_model = PandasModel(filtered_df)
4014
+ self.selection_table.setModel(new_model)
4015
+
4016
+ # Switch to selection table
4017
+ self.selection_button.click()
3875
4018
 
3876
4019
  elif not self.selecting and self.selection_start: # If we had a click but never started selection
3877
4020
  # Handle as a normal click
@@ -4418,7 +4561,7 @@ class ImageViewerWindow(QMainWindow):
4418
4561
 
4419
4562
 
4420
4563
  # Add after your other buttons
4421
- self.popup_button = QPushButton("⤴") # or "🔗" or "⤴"
4564
+ self.popup_button = QPushButton("⤴")
4422
4565
  self.popup_button.setFixedSize(40, 40)
4423
4566
  self.popup_button.setToolTip("Pop out canvas")
4424
4567
  self.popup_button.clicked.connect(self.popup_canvas)
@@ -4433,6 +4576,12 @@ class ImageViewerWindow(QMainWindow):
4433
4576
  cam_button.setStyleSheet("font-size: 24px;")
4434
4577
  cam_button.clicked.connect(self.snap)
4435
4578
  corner_layout.addWidget(cam_button)
4579
+
4580
+ load_button = QPushButton("📁")
4581
+ load_button.setFixedSize(40, 40)
4582
+ load_button.setStyleSheet("font-size: 24px;")
4583
+ load_button.clicked.connect(self.load_file)
4584
+ corner_layout.addWidget(load_button)
4436
4585
 
4437
4586
  # Set as corner widget
4438
4587
  menubar.setCornerWidget(corner_widget, Qt.Corner.TopRightCorner)
@@ -6073,8 +6222,6 @@ class ImageViewerWindow(QMainWindow):
6073
6222
  if self.resume:
6074
6223
  self.machine_window.segmentation_worker.resume()
6075
6224
  self.resume = False
6076
- if self.prev_down != self.downsample_factor:
6077
- self.validate_downsample_input(text = self.prev_down)
6078
6225
 
6079
6226
  if self.static_background is not None:
6080
6227
  # Your existing virtual strokes conversion logic
@@ -6131,8 +6278,6 @@ class ImageViewerWindow(QMainWindow):
6131
6278
  for img in list(self.ax.get_images()):
6132
6279
  img.remove()
6133
6280
  # Clear measurement points
6134
- for artist in self.measurement_artists:
6135
- artist.remove()
6136
6281
  self.measurement_artists.clear()
6137
6282
 
6138
6283
  # Determine the current view bounds (either from preserve_zoom or current state)
@@ -6347,9 +6492,10 @@ class ImageViewerWindow(QMainWindow):
6347
6492
  if reset_resize:
6348
6493
  self.resizing = False
6349
6494
 
6350
- # Use draw_idle for better performance
6495
+ # draw_idle
6351
6496
  self.canvas.draw_idle()
6352
6497
 
6498
+
6353
6499
  except Exception as e:
6354
6500
  pass
6355
6501
  #import traceback
@@ -6655,15 +6801,19 @@ class CustomTableView(QTableView):
6655
6801
  desc_action.triggered.connect(lambda checked, c=col: self.sort_table(c, ascending=False))
6656
6802
 
6657
6803
  # Different menus for top and bottom tables
6658
- if self in self.parent.data_table: # Top table
6804
+ if self.is_top_table: # Use the flag instead of checking membership
6659
6805
  save_menu = context_menu.addMenu("Save As")
6660
6806
  save_csv = save_menu.addAction("CSV")
6661
6807
  save_excel = save_menu.addAction("Excel")
6808
+
6809
+ if self.model() and len(self.model()._data.columns) == 2:
6810
+ thresh_action = context_menu.addAction("Use to Threshold Nodes")
6811
+ thresh_action.triggered.connect(lambda: self.thresh(self.create_threshold_dict()))
6812
+
6662
6813
  close_action = context_menu.addAction("Close All")
6663
-
6664
6814
  close_action.triggered.connect(self.close_all)
6665
6815
 
6666
- # Connect the actions
6816
+ # Connect the save actions
6667
6817
  save_csv.triggered.connect(lambda: self.save_table_as('csv'))
6668
6818
  save_excel.triggered.connect(lambda: self.save_table_as('xlsx'))
6669
6819
  else: # Bottom tables
@@ -6707,6 +6857,38 @@ class CustomTableView(QTableView):
6707
6857
  cursor_pos = QCursor.pos()
6708
6858
  context_menu.exec(cursor_pos)
6709
6859
 
6860
+
6861
+
6862
+ def thresh(self, special_dict):
6863
+ try:
6864
+ self.parent.special_dict = special_dict
6865
+ thresh_window = ThresholdWindow(self.parent, 4)
6866
+ thresh_window.show()
6867
+ except:
6868
+ pass
6869
+
6870
+ def create_threshold_dict(self):
6871
+ try:
6872
+ """Create a dictionary from the 2-column table data."""
6873
+ if not self.model() or not hasattr(self.model(), '_data'):
6874
+ return {}
6875
+
6876
+ df = self.model()._data
6877
+ if len(df.columns) != 2:
6878
+ return {}
6879
+
6880
+ # Create dictionary: {column_0_value: column_1_value}
6881
+ threshold_dict = {}
6882
+ for index, row in df.iterrows():
6883
+ key = row.iloc[0] # Column 0 value
6884
+ value = row.iloc[1] # Column 1 value
6885
+ threshold_dict[int(key)] = float(value)
6886
+
6887
+ return threshold_dict
6888
+ except:
6889
+ pass
6890
+
6891
+
6710
6892
  def sort_table(self, column, ascending=True):
6711
6893
  """Sort the table by the specified column."""
6712
6894
  try:
@@ -12970,7 +13152,7 @@ class GenNodesDialog(QDialog):
12970
13152
 
12971
13153
  if my_network.edges is None and my_network.nodes is not None:
12972
13154
  self.parent().load_channel(1, my_network.nodes, data = True)
12973
- self.parent().delete_channel(0, True)
13155
+ self.parent().delete_channel(0, False)
12974
13156
  # Get directory (None if empty)
12975
13157
  #directory = self.directory.text() if self.directory.text() else None
12976
13158
 
@@ -13202,7 +13384,7 @@ class BranchDialog(QDialog):
13202
13384
 
13203
13385
  if my_network.edges is None and my_network.nodes is not None:
13204
13386
  self.parent().load_channel(1, my_network.nodes, data = True)
13205
- self.parent().delete_channel(0, True)
13387
+ self.parent().delete_channel(0, False)
13206
13388
 
13207
13389
  original_shape = my_network.edges.shape
13208
13390
  original_array = copy.deepcopy(my_network.edges)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nettracer3d
3
- Version: 0.9.8
3
+ Version: 1.0.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/
@@ -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 0.9.8 Updates --
113
+ -- Version 1.0.0 Updates --
114
114
 
115
- * Some minor bug fixes
115
+ * The 'network selection' table is now auto-populated when using the multiple-identity selector, and when using the node thresholder.
116
+ * And other minor adjustments/bug fixes
File without changes
File without changes