nettracer3d 0.9.7__py3-none-any.whl → 0.9.9__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_gui.py +157 -23
- {nettracer3d-0.9.7.dist-info → nettracer3d-0.9.9.dist-info}/METADATA +4 -4
- {nettracer3d-0.9.7.dist-info → nettracer3d-0.9.9.dist-info}/RECORD +7 -7
- {nettracer3d-0.9.7.dist-info → nettracer3d-0.9.9.dist-info}/WHEEL +0 -0
- {nettracer3d-0.9.7.dist-info → nettracer3d-0.9.9.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.9.7.dist-info → nettracer3d-0.9.9.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.9.7.dist-info → nettracer3d-0.9.9.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -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():
|
|
@@ -2494,6 +2590,12 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2494
2590
|
|
|
2495
2591
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
2496
2592
|
|
|
2593
|
+
if self.pan_mode:
|
|
2594
|
+
self.create_pan_background()
|
|
2595
|
+
current_xlim = self.ax.get_xlim()
|
|
2596
|
+
current_ylim = self.ax.get_ylim()
|
|
2597
|
+
self.update_display_pan_mode(current_xlim, current_ylim)
|
|
2598
|
+
|
|
2497
2599
|
|
|
2498
2600
|
def toggle_zoom_mode(self):
|
|
2499
2601
|
"""Toggle zoom mode on/off."""
|
|
@@ -2501,7 +2603,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2501
2603
|
|
|
2502
2604
|
if self.zoom_mode:
|
|
2503
2605
|
if self.pan_mode:
|
|
2504
|
-
self.
|
|
2606
|
+
self.update_display(preserve_zoom=(self.ax.get_xlim(), self.ax.get_ylim()))
|
|
2607
|
+
self.pan_mode = False
|
|
2608
|
+
self.pan_button.setChecked(False)
|
|
2505
2609
|
|
|
2506
2610
|
self.pen_button.setChecked(False)
|
|
2507
2611
|
self.brush_mode = False
|
|
@@ -2522,11 +2626,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2522
2626
|
current_xlim = self.ax.get_xlim()
|
|
2523
2627
|
current_ylim = self.ax.get_ylim()
|
|
2524
2628
|
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
2525
|
-
if self.pan_mode:
|
|
2526
|
-
current_xlim = self.ax.get_xlim()
|
|
2527
|
-
current_ylim = self.ax.get_ylim()
|
|
2528
|
-
self.update_display(preserve_zoom=(current_xlim, current_ylim))
|
|
2529
|
-
self.pan_mode = False
|
|
2530
2629
|
|
|
2531
2630
|
else:
|
|
2532
2631
|
if self.machine_window is None:
|
|
@@ -2605,7 +2704,9 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2605
2704
|
if self.brush_mode:
|
|
2606
2705
|
|
|
2607
2706
|
if self.pan_mode:
|
|
2608
|
-
self.
|
|
2707
|
+
self.update_display(preserve_zoom=(self.ax.get_xlim(), self.ax.get_ylim()))
|
|
2708
|
+
self.pan_mode = False
|
|
2709
|
+
self.pan_button.setChecked(False)
|
|
2609
2710
|
|
|
2610
2711
|
self.pm = painting.PaintManager(parent = self)
|
|
2611
2712
|
|
|
@@ -3594,8 +3695,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3594
3695
|
# Multi-color highlight for machine window
|
|
3595
3696
|
mask_1 = (highlight_data == 1)
|
|
3596
3697
|
mask_2 = (highlight_data == 2)
|
|
3597
|
-
rgba[mask_1] = [1, 1, 0, 0.
|
|
3598
|
-
rgba[mask_2] = [0, 0.7, 1, 0.
|
|
3698
|
+
rgba[mask_1] = [1, 1, 0, 0.3] # Yellow for 1
|
|
3699
|
+
rgba[mask_2] = [0, 0.7, 1, 0.3] # Blue for 2
|
|
3599
3700
|
|
|
3600
3701
|
return rgba
|
|
3601
3702
|
|
|
@@ -4428,6 +4529,12 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4428
4529
|
cam_button.setStyleSheet("font-size: 24px;")
|
|
4429
4530
|
cam_button.clicked.connect(self.snap)
|
|
4430
4531
|
corner_layout.addWidget(cam_button)
|
|
4532
|
+
|
|
4533
|
+
load_button = QPushButton("📁")
|
|
4534
|
+
load_button.setFixedSize(40, 40)
|
|
4535
|
+
load_button.setStyleSheet("font-size: 24px;")
|
|
4536
|
+
load_button.clicked.connect(self.load_file)
|
|
4537
|
+
corner_layout.addWidget(load_button)
|
|
4431
4538
|
|
|
4432
4539
|
# Set as corner widget
|
|
4433
4540
|
menubar.setCornerWidget(corner_widget, Qt.Corner.TopRightCorner)
|
|
@@ -6088,17 +6195,6 @@ class ImageViewerWindow(QMainWindow):
|
|
|
6088
6195
|
pass
|
|
6089
6196
|
self.static_background = None
|
|
6090
6197
|
|
|
6091
|
-
# Your existing machine_window logic
|
|
6092
|
-
if self.machine_window is None:
|
|
6093
|
-
try:
|
|
6094
|
-
self.channel_data[4][self.current_slice, :, :] = n3d.overlay_arrays_simple(
|
|
6095
|
-
self.channel_data[self.temp_chan][self.current_slice, :, :],
|
|
6096
|
-
self.channel_data[4][self.current_slice, :, :])
|
|
6097
|
-
self.load_channel(self.temp_chan, self.channel_data[4], data=True, end_paint=True)
|
|
6098
|
-
self.channel_data[4] = None
|
|
6099
|
-
self.channel_visible[4] = False
|
|
6100
|
-
except:
|
|
6101
|
-
pass
|
|
6102
6198
|
|
|
6103
6199
|
# Get dimensions
|
|
6104
6200
|
active_channels = [i for i in range(4) if self.channel_data[i] is not None]
|
|
@@ -6661,15 +6757,19 @@ class CustomTableView(QTableView):
|
|
|
6661
6757
|
desc_action.triggered.connect(lambda checked, c=col: self.sort_table(c, ascending=False))
|
|
6662
6758
|
|
|
6663
6759
|
# Different menus for top and bottom tables
|
|
6664
|
-
if self
|
|
6760
|
+
if self.is_top_table: # Use the flag instead of checking membership
|
|
6665
6761
|
save_menu = context_menu.addMenu("Save As")
|
|
6666
6762
|
save_csv = save_menu.addAction("CSV")
|
|
6667
6763
|
save_excel = save_menu.addAction("Excel")
|
|
6764
|
+
|
|
6765
|
+
if self.model() and len(self.model()._data.columns) == 2:
|
|
6766
|
+
thresh_action = context_menu.addAction("Use to Threshold Nodes")
|
|
6767
|
+
thresh_action.triggered.connect(lambda: self.thresh(self.create_threshold_dict()))
|
|
6768
|
+
|
|
6668
6769
|
close_action = context_menu.addAction("Close All")
|
|
6669
|
-
|
|
6670
6770
|
close_action.triggered.connect(self.close_all)
|
|
6671
6771
|
|
|
6672
|
-
# Connect the actions
|
|
6772
|
+
# Connect the save actions
|
|
6673
6773
|
save_csv.triggered.connect(lambda: self.save_table_as('csv'))
|
|
6674
6774
|
save_excel.triggered.connect(lambda: self.save_table_as('xlsx'))
|
|
6675
6775
|
else: # Bottom tables
|
|
@@ -6713,6 +6813,38 @@ class CustomTableView(QTableView):
|
|
|
6713
6813
|
cursor_pos = QCursor.pos()
|
|
6714
6814
|
context_menu.exec(cursor_pos)
|
|
6715
6815
|
|
|
6816
|
+
|
|
6817
|
+
|
|
6818
|
+
def thresh(self, special_dict):
|
|
6819
|
+
try:
|
|
6820
|
+
self.parent.special_dict = special_dict
|
|
6821
|
+
thresh_window = ThresholdWindow(self.parent, 4)
|
|
6822
|
+
thresh_window.show()
|
|
6823
|
+
except:
|
|
6824
|
+
pass
|
|
6825
|
+
|
|
6826
|
+
def create_threshold_dict(self):
|
|
6827
|
+
try:
|
|
6828
|
+
"""Create a dictionary from the 2-column table data."""
|
|
6829
|
+
if not self.model() or not hasattr(self.model(), '_data'):
|
|
6830
|
+
return {}
|
|
6831
|
+
|
|
6832
|
+
df = self.model()._data
|
|
6833
|
+
if len(df.columns) != 2:
|
|
6834
|
+
return {}
|
|
6835
|
+
|
|
6836
|
+
# Create dictionary: {column_0_value: column_1_value}
|
|
6837
|
+
threshold_dict = {}
|
|
6838
|
+
for index, row in df.iterrows():
|
|
6839
|
+
key = row.iloc[0] # Column 0 value
|
|
6840
|
+
value = row.iloc[1] # Column 1 value
|
|
6841
|
+
threshold_dict[int(key)] = float(value)
|
|
6842
|
+
|
|
6843
|
+
return threshold_dict
|
|
6844
|
+
except:
|
|
6845
|
+
pass
|
|
6846
|
+
|
|
6847
|
+
|
|
6716
6848
|
def sort_table(self, column, ascending=True):
|
|
6717
6849
|
"""Sort the table by the specified column."""
|
|
6718
6850
|
try:
|
|
@@ -10991,6 +11123,8 @@ class MachineWindow(QMainWindow):
|
|
|
10991
11123
|
def toggle_brush_mode(self):
|
|
10992
11124
|
"""Toggle brush mode on/off"""
|
|
10993
11125
|
self.parent().brush_mode = self.brush_button.isChecked()
|
|
11126
|
+
if self.parent().pan_mode:
|
|
11127
|
+
self.parent().update_display(preserve_zoom=(self.parent().ax.get_xlim(), self.parent().ax.get_ylim()))
|
|
10994
11128
|
|
|
10995
11129
|
if self.parent().brush_mode:
|
|
10996
11130
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nettracer3d
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.9
|
|
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 0.9.
|
|
113
|
+
-- Version 0.9.9 Updates --
|
|
114
114
|
|
|
115
|
-
*
|
|
116
|
-
*
|
|
115
|
+
* Tables can now be opened to the rightside upper widget if they are the right format.
|
|
116
|
+
* Similarly, tables that have the format node id column:numerical values can now be used liberally to threshold the nodes, meaning most outputs of network analysis can be used to threshold nodes.
|
|
@@ -6,7 +6,7 @@ nettracer3d/modularity.py,sha256=pborVcDBvICB2-g8lNoSVZbIReIBlfeBmjFbPYmtq7Y,224
|
|
|
6
6
|
nettracer3d/morphology.py,sha256=jyDjYzrZ4LvI5jOyw8DLsxmo-i5lpqHsejYpW7Tq7Mo,19786
|
|
7
7
|
nettracer3d/neighborhoods.py,sha256=iIaHU1COIdRtzRpAuIQKfLGLNKYFK3dL8Vb_EeJIlEA,46459
|
|
8
8
|
nettracer3d/nettracer.py,sha256=d9Mhz-pHox5OfGGUSTrkTBJzCWCrbQvClTu2ANt-jUE,267943
|
|
9
|
-
nettracer3d/nettracer_gui.py,sha256=
|
|
9
|
+
nettracer3d/nettracer_gui.py,sha256=ckTjhIutYwnh7qwvgCmcKs2DCCODKICsiCxd1sD77gA,633247
|
|
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-0.9.
|
|
21
|
-
nettracer3d-0.9.
|
|
22
|
-
nettracer3d-0.9.
|
|
23
|
-
nettracer3d-0.9.
|
|
24
|
-
nettracer3d-0.9.
|
|
25
|
-
nettracer3d-0.9.
|
|
20
|
+
nettracer3d-0.9.9.dist-info/licenses/LICENSE,sha256=jnNT-yBeIAKAHpYthPvLeqCzJ6nSurgnKmloVnfsjCI,764
|
|
21
|
+
nettracer3d-0.9.9.dist-info/METADATA,sha256=0T7rx2BBeAwH0NxEczmM0TeOaL6y0VmHMJlIDKkPu9g,7259
|
|
22
|
+
nettracer3d-0.9.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
nettracer3d-0.9.9.dist-info/entry_points.txt,sha256=Nx1rr_0QhJXDBHAQg2vcqCzLMKBzSHfwy3xwGkueVyc,53
|
|
24
|
+
nettracer3d-0.9.9.dist-info/top_level.txt,sha256=zsYy9rZwirfCEOubolhee4TyzqBAL5gSUeFMzhFTX8c,12
|
|
25
|
+
nettracer3d-0.9.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|