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.
- {nettracer3d-0.9.8/src/nettracer3d.egg-info → nettracer3d-1.0.0}/PKG-INFO +4 -3
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/README.md +3 -2
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/pyproject.toml +1 -1
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/nettracer_gui.py +194 -12
- {nettracer3d-0.9.8 → nettracer3d-1.0.0/src/nettracer3d.egg-info}/PKG-INFO +4 -3
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/LICENSE +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/setup.cfg +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/__init__.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/cellpose_manager.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/community_extractor.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/excelotron.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/modularity.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/morphology.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/neighborhoods.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/nettracer.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/network_analysis.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/network_draw.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/node_draw.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/painting.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/proximity.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/run.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/segmenter.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/segmenter_GPU.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/simple_network.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d/smart_dilate.py +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/entry_points.txt +0 -0
- {nettracer3d-0.9.8 → nettracer3d-1.0.0}/src/nettracer3d.egg-info/requires.txt +0 -0
- {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.
|
|
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.
|
|
113
|
+
-- Version 1.0.0 Updates --
|
|
114
114
|
|
|
115
|
-
*
|
|
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.
|
|
68
|
+
-- Version 1.0.0 Updates --
|
|
69
69
|
|
|
70
|
-
*
|
|
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
|
|
@@ -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("⤴")
|
|
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
|
-
#
|
|
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
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
113
|
+
-- Version 1.0.0 Updates --
|
|
114
114
|
|
|
115
|
-
*
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|