nettracer3d 0.7.5__py3-none-any.whl → 0.7.7__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/community_extractor.py +13 -29
- nettracer3d/excelotron.py +1669 -0
- nettracer3d/modularity.py +6 -9
- nettracer3d/neighborhoods.py +223 -0
- nettracer3d/nettracer.py +314 -13
- nettracer3d/nettracer_gui.py +682 -131
- nettracer3d/proximity.py +89 -9
- nettracer3d/smart_dilate.py +20 -15
- {nettracer3d-0.7.5.dist-info → nettracer3d-0.7.7.dist-info}/METADATA +4 -8
- nettracer3d-0.7.7.dist-info/RECORD +23 -0
- {nettracer3d-0.7.5.dist-info → nettracer3d-0.7.7.dist-info}/WHEEL +1 -1
- nettracer3d-0.7.5.dist-info/RECORD +0 -21
- {nettracer3d-0.7.5.dist-info → nettracer3d-0.7.7.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.7.5.dist-info → nettracer3d-0.7.7.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.7.5.dist-info → nettracer3d-0.7.7.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer_gui.py
CHANGED
|
@@ -5,7 +5,7 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QG
|
|
|
5
5
|
QFormLayout, QLineEdit, QPushButton, QFileDialog,
|
|
6
6
|
QLabel, QComboBox, QMessageBox, QTableView, QInputDialog,
|
|
7
7
|
QMenu, QTabWidget, QGroupBox)
|
|
8
|
-
from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer, QThread, pyqtSignal)
|
|
8
|
+
from PyQt6.QtCore import (QPoint, Qt, QAbstractTableModel, QTimer, QThread, pyqtSignal, QObject, QCoreApplication)
|
|
9
9
|
import numpy as np
|
|
10
10
|
import time
|
|
11
11
|
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
|
@@ -26,6 +26,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
26
26
|
from functools import partial
|
|
27
27
|
from nettracer3d import segmenter
|
|
28
28
|
from nettracer3d import segmenter_GPU
|
|
29
|
+
from nettracer3d import excelotron
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
|
|
@@ -409,6 +410,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
409
410
|
self.mini_overlay = False # If the program is currently drawing the overlay by frame this will be true
|
|
410
411
|
self.mini_overlay_data = None #Actual data for mini overlay
|
|
411
412
|
self.mini_thresh = (500*500*500) # Array volume to start using mini overlays for
|
|
413
|
+
self.shape = None
|
|
414
|
+
|
|
415
|
+
self.excel_manager = ExcelotronManager(self)
|
|
416
|
+
self.excel_manager.data_received.connect(self.handle_excel_data)
|
|
417
|
+
self.prev_coms = None
|
|
412
418
|
|
|
413
419
|
def start_left_scroll(self):
|
|
414
420
|
"""Start scrolling left when left arrow is pressed."""
|
|
@@ -1448,7 +1454,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1448
1454
|
|
|
1449
1455
|
print("Note, this method is a tad slow...")
|
|
1450
1456
|
|
|
1451
|
-
def separate_nontouching_objects(input_array):
|
|
1457
|
+
def separate_nontouching_objects(input_array, max_val = 0):
|
|
1452
1458
|
"""
|
|
1453
1459
|
Efficiently separate non-touching objects in a labeled array.
|
|
1454
1460
|
|
|
@@ -1483,7 +1489,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1483
1489
|
|
|
1484
1490
|
# Step 3: Create a new output array with unique labels for each connected component
|
|
1485
1491
|
output_array = np.zeros_like(input_array)
|
|
1486
|
-
next_label = 1
|
|
1492
|
+
next_label = 1 + max_val
|
|
1487
1493
|
|
|
1488
1494
|
# Map of (original_label, connected_component) -> new_unique_label
|
|
1489
1495
|
unique_label_map = {}
|
|
@@ -1512,9 +1518,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1512
1518
|
|
|
1513
1519
|
# Get non-highlighted part of the array
|
|
1514
1520
|
non_highlighted = my_network.nodes * (~self.highlight_overlay)
|
|
1521
|
+
|
|
1522
|
+
if (highlighted_nodes==non_highlighted).all():
|
|
1523
|
+
max_val = 0
|
|
1524
|
+
else:
|
|
1525
|
+
max_val = np.max(non_highlighted)
|
|
1515
1526
|
|
|
1516
1527
|
# Process highlighted part
|
|
1517
|
-
processed_highlights = separate_nontouching_objects(highlighted_nodes)
|
|
1528
|
+
processed_highlights = separate_nontouching_objects(highlighted_nodes, max_val)
|
|
1518
1529
|
|
|
1519
1530
|
# Combine back with non-highlighted parts
|
|
1520
1531
|
my_network.nodes = non_highlighted + processed_highlights
|
|
@@ -1534,9 +1545,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1534
1545
|
|
|
1535
1546
|
# Get non-highlighted part of the array
|
|
1536
1547
|
non_highlighted = my_network.edges * (~self.highlight_overlay)
|
|
1548
|
+
|
|
1549
|
+
if (highlighted_nodes==non_highlighted).all():
|
|
1550
|
+
max_val = 0
|
|
1551
|
+
else:
|
|
1552
|
+
max_val = np.max(non_highlighted)
|
|
1537
1553
|
|
|
1538
1554
|
# Process highlighted part
|
|
1539
|
-
processed_highlights = separate_nontouching_objects(highlighted_edges)
|
|
1555
|
+
processed_highlights = separate_nontouching_objects(highlighted_edges, max_val)
|
|
1540
1556
|
|
|
1541
1557
|
# Combine back with non-highlighted parts
|
|
1542
1558
|
my_network.edges = non_highlighted + processed_highlights
|
|
@@ -1545,7 +1561,7 @@ class ImageViewerWindow(QMainWindow):
|
|
|
1545
1561
|
|
|
1546
1562
|
self.highlight_overlay = None
|
|
1547
1563
|
self.update_display()
|
|
1548
|
-
print("Network is not updated automatically, please recompute if necessary. Identities are not automatically updated.")
|
|
1564
|
+
print("Network is not updated automatically, please recompute if necessary - this method has a high chance of disrupting the network. Identities are not automatically updated.")
|
|
1549
1565
|
self.show_centroid_dialog()
|
|
1550
1566
|
except Exception as e:
|
|
1551
1567
|
print(f"Error separating: {e}")
|
|
@@ -2670,6 +2686,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2670
2686
|
load_action.triggered.connect(lambda checked, ch=i: self.load_channel(ch))
|
|
2671
2687
|
load_action = load_menu.addAction("Load Network")
|
|
2672
2688
|
load_action.triggered.connect(self.load_network)
|
|
2689
|
+
load_action = load_menu.addAction("Load From Excel Helper")
|
|
2690
|
+
load_action.triggered.connect(self.launch_excelotron)
|
|
2673
2691
|
misc_menu = load_menu.addMenu("Load Misc Properties")
|
|
2674
2692
|
load_action = misc_menu.addAction("Load Node IDs")
|
|
2675
2693
|
load_action.triggered.connect(lambda: self.load_misc('Node Identities'))
|
|
@@ -2686,10 +2704,14 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2686
2704
|
network_menu = analysis_menu.addMenu("Network")
|
|
2687
2705
|
netshow_action = network_menu.addAction("Show Network")
|
|
2688
2706
|
netshow_action.triggered.connect(self.show_netshow_dialog)
|
|
2689
|
-
partition_action = network_menu.addAction("Community Partition +Generic Community Stats")
|
|
2707
|
+
partition_action = network_menu.addAction("Community Partition + Generic Community Stats")
|
|
2690
2708
|
partition_action.triggered.connect(self.show_partition_dialog)
|
|
2691
|
-
com_identity_action = network_menu.addAction("Identity Makeup of Network Communities (
|
|
2709
|
+
com_identity_action = network_menu.addAction("Identity Makeup of Network Communities (and UMAP)")
|
|
2692
2710
|
com_identity_action.triggered.connect(self.handle_com_id)
|
|
2711
|
+
com_neighbor_action = network_menu.addAction("Convert Network Communities into Neighborhoods?")
|
|
2712
|
+
com_neighbor_action.triggered.connect(self.handle_com_neighbor)
|
|
2713
|
+
com_cell_action = network_menu.addAction("Create Communities Based on Cuboidal Proximity Cells?")
|
|
2714
|
+
com_cell_action.triggered.connect(self.handle_com_cell)
|
|
2693
2715
|
stats_menu = analysis_menu.addMenu("Stats")
|
|
2694
2716
|
allstats_action = stats_menu.addAction("Calculate Generic Network Stats")
|
|
2695
2717
|
allstats_action.triggered.connect(self.stats)
|
|
@@ -2758,6 +2780,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2758
2780
|
thresh_action.triggered.connect(self.show_thresh_dialog)
|
|
2759
2781
|
mask_action = image_menu.addAction("Mask Channel")
|
|
2760
2782
|
mask_action.triggered.connect(self.show_mask_dialog)
|
|
2783
|
+
crop_action = image_menu.addAction("Crop Channels")
|
|
2784
|
+
crop_action.triggered.connect(self.show_crop_dialog)
|
|
2761
2785
|
type_action = image_menu.addAction("Channel dtype")
|
|
2762
2786
|
type_action.triggered.connect(self.show_type_dialog)
|
|
2763
2787
|
skeletonize_action = image_menu.addAction("Skeletonize")
|
|
@@ -2837,99 +2861,103 @@ class ImageViewerWindow(QMainWindow):
|
|
|
2837
2861
|
|
|
2838
2862
|
"""from networkx documentation"""
|
|
2839
2863
|
|
|
2840
|
-
|
|
2864
|
+
try:
|
|
2841
2865
|
|
|
2842
|
-
|
|
2843
|
-
diameter = max(nx.eccentricity(G, sp=shortest_path_lengths).values())
|
|
2844
|
-
# We know the maximum shortest path length (the diameter), so create an array
|
|
2845
|
-
# to store values from 0 up to (and including) diameter
|
|
2846
|
-
path_lengths = np.zeros(diameter + 1, dtype=int)
|
|
2866
|
+
G = my_network.network
|
|
2847
2867
|
|
|
2868
|
+
shortest_path_lengths = dict(nx.all_pairs_shortest_path_length(G))
|
|
2869
|
+
diameter = max(nx.eccentricity(G, sp=shortest_path_lengths).values())
|
|
2870
|
+
# We know the maximum shortest path length (the diameter), so create an array
|
|
2871
|
+
# to store values from 0 up to (and including) diameter
|
|
2872
|
+
path_lengths = np.zeros(diameter + 1, dtype=int)
|
|
2848
2873
|
|
|
2849
2874
|
|
|
2850
|
-
# Extract the frequency of shortest path lengths between two nodes
|
|
2851
|
-
for pls in shortest_path_lengths.values():
|
|
2852
|
-
pl, cnts = np.unique(list(pls.values()), return_counts=True)
|
|
2853
|
-
path_lengths[pl] += cnts
|
|
2854
2875
|
|
|
2855
|
-
|
|
2856
|
-
|
|
2876
|
+
# Extract the frequency of shortest path lengths between two nodes
|
|
2877
|
+
for pls in shortest_path_lengths.values():
|
|
2878
|
+
pl, cnts = np.unique(list(pls.values()), return_counts=True)
|
|
2879
|
+
path_lengths[pl] += cnts
|
|
2857
2880
|
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2881
|
+
# Express frequency distribution as a percentage (ignoring path lengths of 0)
|
|
2882
|
+
freq_percent = 100 * path_lengths[1:] / path_lengths[1:].sum()
|
|
2883
|
+
|
|
2884
|
+
# Plot the frequency distribution (ignoring path lengths of 0) as a percentage
|
|
2885
|
+
fig, ax = plt.subplots(figsize=(15, 8))
|
|
2886
|
+
ax.bar(np.arange(1, diameter + 1), height=freq_percent)
|
|
2887
|
+
ax.set_title(
|
|
2888
|
+
"Distribution of shortest path length in G", fontdict={"size": 35}, loc="center"
|
|
2889
|
+
)
|
|
2890
|
+
ax.set_xlabel("Shortest Path Length", fontdict={"size": 22})
|
|
2891
|
+
ax.set_ylabel("Frequency (%)", fontdict={"size": 22})
|
|
2892
|
+
|
|
2893
|
+
plt.show()
|
|
2894
|
+
freq_dict = {freq: length for length, freq in enumerate(freq_percent, start=1)}
|
|
2895
|
+
self.format_for_upperright_table(freq_dict, metric='Frequency (%)', value='Shortest Path Length', title="Distribution of shortest path length in G")
|
|
2896
|
+
|
|
2897
|
+
degree_centrality = nx.centrality.degree_centrality(G)
|
|
2898
|
+
plt.figure(figsize=(15, 8))
|
|
2899
|
+
plt.hist(degree_centrality.values(), bins=25)
|
|
2900
|
+
plt.xticks(ticks=[0, 0.025, 0.05, 0.1, 0.15, 0.2]) # set the x axis ticks
|
|
2901
|
+
plt.title("Degree Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2902
|
+
plt.xlabel("Degree Centrality", fontdict={"size": 20})
|
|
2903
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2904
|
+
plt.show()
|
|
2905
|
+
self.format_for_upperright_table(degree_centrality, metric='Node', value='Degree Centrality', title="Degree Centrality Table")
|
|
2906
|
+
|
|
2907
|
+
|
|
2908
|
+
betweenness_centrality = nx.centrality.betweenness_centrality(
|
|
2909
|
+
G
|
|
2910
|
+
)
|
|
2911
|
+
plt.figure(figsize=(15, 8))
|
|
2912
|
+
plt.hist(betweenness_centrality.values(), bins=100)
|
|
2913
|
+
plt.xticks(ticks=[0, 0.02, 0.1, 0.2, 0.3, 0.4, 0.5]) # set the x axis ticks
|
|
2914
|
+
plt.title("Betweenness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2915
|
+
plt.xlabel("Betweenness Centrality", fontdict={"size": 20})
|
|
2916
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2917
|
+
plt.show()
|
|
2918
|
+
self.format_for_upperright_table(betweenness_centrality, metric='Node', value='Betweenness Centrality', title="Betweenness Centrality Table")
|
|
2919
|
+
|
|
2920
|
+
|
|
2921
|
+
closeness_centrality = nx.centrality.closeness_centrality(
|
|
2922
|
+
G
|
|
2923
|
+
)
|
|
2924
|
+
plt.figure(figsize=(15, 8))
|
|
2925
|
+
plt.hist(closeness_centrality.values(), bins=60)
|
|
2926
|
+
plt.title("Closeness Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2927
|
+
plt.xlabel("Closeness Centrality", fontdict={"size": 20})
|
|
2928
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2929
|
+
plt.show()
|
|
2930
|
+
self.format_for_upperright_table(closeness_centrality, metric='Node', value='Closeness Centrality', title="Closeness Centrality Table")
|
|
2905
2931
|
|
|
2906
2932
|
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2933
|
+
eigenvector_centrality = nx.centrality.eigenvector_centrality(
|
|
2934
|
+
G
|
|
2935
|
+
)
|
|
2936
|
+
plt.figure(figsize=(15, 8))
|
|
2937
|
+
plt.hist(eigenvector_centrality.values(), bins=60)
|
|
2938
|
+
plt.xticks(ticks=[0, 0.01, 0.02, 0.04, 0.06, 0.08]) # set the x axis ticks
|
|
2939
|
+
plt.title("Eigenvector Centrality Histogram ", fontdict={"size": 35}, loc="center")
|
|
2940
|
+
plt.xlabel("Eigenvector Centrality", fontdict={"size": 20})
|
|
2941
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2942
|
+
plt.show()
|
|
2943
|
+
self.format_for_upperright_table(eigenvector_centrality, metric='Node', value='Eigenvector Centrality', title="Eigenvector Centrality Table")
|
|
2918
2944
|
|
|
2919
2945
|
|
|
2920
2946
|
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2947
|
+
clusters = nx.clustering(G)
|
|
2948
|
+
plt.figure(figsize=(15, 8))
|
|
2949
|
+
plt.hist(clusters.values(), bins=50)
|
|
2950
|
+
plt.title("Clustering Coefficient Histogram ", fontdict={"size": 35}, loc="center")
|
|
2951
|
+
plt.xlabel("Clustering Coefficient", fontdict={"size": 20})
|
|
2952
|
+
plt.ylabel("Counts", fontdict={"size": 20})
|
|
2953
|
+
plt.show()
|
|
2954
|
+
self.format_for_upperright_table(clusters, metric='Node', value='Clustering Coefficient', title="Clustering Coefficient Table")
|
|
2929
2955
|
|
|
2930
|
-
|
|
2931
|
-
|
|
2956
|
+
bridges = list(nx.bridges(G))
|
|
2957
|
+
self.format_for_upperright_table(bridges, metric = 'Node Pair', title="Bridges")
|
|
2932
2958
|
|
|
2959
|
+
except Exception as e:
|
|
2960
|
+
print(f"Error generating histograms: {e}")
|
|
2933
2961
|
|
|
2934
2962
|
def volumes(self):
|
|
2935
2963
|
|
|
@@ -3121,6 +3149,11 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3121
3149
|
dialog = MaskDialog(self)
|
|
3122
3150
|
dialog.exec()
|
|
3123
3151
|
|
|
3152
|
+
def show_crop_dialog(self):
|
|
3153
|
+
"""Show the crop dialog"""
|
|
3154
|
+
dialog = CropDialog(self)
|
|
3155
|
+
dialog.exec()
|
|
3156
|
+
|
|
3124
3157
|
def show_type_dialog(self):
|
|
3125
3158
|
"""Show the type dialog"""
|
|
3126
3159
|
try:
|
|
@@ -3539,6 +3572,103 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3539
3572
|
f"Failed to load network: {str(e)}"
|
|
3540
3573
|
)
|
|
3541
3574
|
|
|
3575
|
+
def launch_excelotron(self):
|
|
3576
|
+
"""Method to launch Excelotron - call this from a button or menu"""
|
|
3577
|
+
self.excel_manager.launch()
|
|
3578
|
+
|
|
3579
|
+
def close_excelotron(self):
|
|
3580
|
+
"""Method to close Excelotron"""
|
|
3581
|
+
self.excel_manager.close()
|
|
3582
|
+
|
|
3583
|
+
def handle_excel_data(self, data_dict, property_name):
|
|
3584
|
+
"""Handle data received from Excelotron"""
|
|
3585
|
+
print(f"Received data for property: {property_name}")
|
|
3586
|
+
print(f"Data keys: {list(data_dict.keys())}")
|
|
3587
|
+
|
|
3588
|
+
if property_name == 'Node Centroids':
|
|
3589
|
+
|
|
3590
|
+
try:
|
|
3591
|
+
|
|
3592
|
+
ys = data_dict['Y']
|
|
3593
|
+
xs = data_dict['X']
|
|
3594
|
+
if 'Numerical IDs' in data_dict:
|
|
3595
|
+
nodes = data_dict['Numerical IDs']
|
|
3596
|
+
else:
|
|
3597
|
+
nodes = np.arange(1, len(ys) + 1)
|
|
3598
|
+
|
|
3599
|
+
|
|
3600
|
+
if 'Z' in data_dict:
|
|
3601
|
+
zs = data_dict['Z']
|
|
3602
|
+
else:
|
|
3603
|
+
zs = np.zeros(len(ys))
|
|
3604
|
+
|
|
3605
|
+
centroids = {}
|
|
3606
|
+
|
|
3607
|
+
for i in range(len(nodes)):
|
|
3608
|
+
|
|
3609
|
+
centroids[nodes[i]] = [int(zs[i]), int(ys[i]), int(xs[i])]
|
|
3610
|
+
|
|
3611
|
+
my_network.node_centroids = centroids
|
|
3612
|
+
|
|
3613
|
+
self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
3614
|
+
|
|
3615
|
+
print("Centroids succesfully set")
|
|
3616
|
+
|
|
3617
|
+
except Exception as e:
|
|
3618
|
+
print(f"Error: {e}")
|
|
3619
|
+
|
|
3620
|
+
elif property_name == 'Node Identities':
|
|
3621
|
+
|
|
3622
|
+
try:
|
|
3623
|
+
|
|
3624
|
+
idens = data_dict['Identity Column']
|
|
3625
|
+
|
|
3626
|
+
if 'Numerical IDs' in data_dict:
|
|
3627
|
+
nodes = data_dict['Numerical IDs']
|
|
3628
|
+
else:
|
|
3629
|
+
nodes = np.arange(1, len(idens) + 1)
|
|
3630
|
+
|
|
3631
|
+
identities = {}
|
|
3632
|
+
|
|
3633
|
+
|
|
3634
|
+
for i in range(len(nodes)):
|
|
3635
|
+
|
|
3636
|
+
identities[nodes[i]] = str(idens[i])
|
|
3637
|
+
|
|
3638
|
+
my_network.node_identities = identities
|
|
3639
|
+
|
|
3640
|
+
self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', title = 'Node Identities')
|
|
3641
|
+
|
|
3642
|
+
print("Identities succesfully set")
|
|
3643
|
+
|
|
3644
|
+
except Exception as e:
|
|
3645
|
+
print(f"Error: {e}")
|
|
3646
|
+
|
|
3647
|
+
elif property_name == 'Node Communities':
|
|
3648
|
+
|
|
3649
|
+
try:
|
|
3650
|
+
|
|
3651
|
+
coms = data_dict['Community Identifier']
|
|
3652
|
+
|
|
3653
|
+
if 'Numerical IDs' in data_dict:
|
|
3654
|
+
nodes = data_dict['Numerical IDs']
|
|
3655
|
+
else:
|
|
3656
|
+
nodes = np.arange(1, len(coms) + 1)
|
|
3657
|
+
|
|
3658
|
+
communities = {}
|
|
3659
|
+
|
|
3660
|
+
for i in range(len(nodes)):
|
|
3661
|
+
|
|
3662
|
+
communities[nodes[i]] = [str(coms[i])]
|
|
3663
|
+
|
|
3664
|
+
my_network.communities = communities
|
|
3665
|
+
|
|
3666
|
+
self.format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID', title = 'Community Partition')
|
|
3667
|
+
|
|
3668
|
+
print("Communities succesfully set")
|
|
3669
|
+
|
|
3670
|
+
except Exception as e:
|
|
3671
|
+
print(f"Error: {e}")
|
|
3542
3672
|
|
|
3543
3673
|
|
|
3544
3674
|
def set_active_channel(self, index):
|
|
@@ -3769,6 +3899,8 @@ class ImageViewerWindow(QMainWindow):
|
|
|
3769
3899
|
except:
|
|
3770
3900
|
pass
|
|
3771
3901
|
|
|
3902
|
+
self.shape = self.channel_data[channel_index].shape
|
|
3903
|
+
|
|
3772
3904
|
self.update_display(reset_resize = reset_resize)
|
|
3773
3905
|
|
|
3774
3906
|
|
|
@@ -4269,19 +4401,19 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4269
4401
|
dialog.exec()
|
|
4270
4402
|
|
|
4271
4403
|
def handle_com_id(self):
|
|
4272
|
-
if my_network.node_identities is None:
|
|
4273
|
-
print("Node identities must be set")
|
|
4274
4404
|
|
|
4275
|
-
|
|
4276
|
-
|
|
4405
|
+
dialog = ComIdDialog(self)
|
|
4406
|
+
dialog.exec()
|
|
4277
4407
|
|
|
4278
|
-
|
|
4279
|
-
return
|
|
4408
|
+
def handle_com_neighbor(self):
|
|
4280
4409
|
|
|
4281
|
-
|
|
4410
|
+
dialog = ComNeighborDialog(self)
|
|
4411
|
+
dialog.exec()
|
|
4282
4412
|
|
|
4283
|
-
|
|
4413
|
+
def handle_com_cell(self):
|
|
4284
4414
|
|
|
4415
|
+
dialog = ComCellDialog(self)
|
|
4416
|
+
dialog.exec()
|
|
4285
4417
|
|
|
4286
4418
|
def show_radial_dialog(self):
|
|
4287
4419
|
dialog = RadialDialog(self)
|
|
@@ -4332,6 +4464,21 @@ class ImageViewerWindow(QMainWindow):
|
|
|
4332
4464
|
dialog = CodeDialog(self, sort = sort)
|
|
4333
4465
|
dialog.exec()
|
|
4334
4466
|
|
|
4467
|
+
def closeEvent(self, event):
|
|
4468
|
+
"""Override closeEvent to close all windows when main window closes"""
|
|
4469
|
+
|
|
4470
|
+
# Close all Qt windows
|
|
4471
|
+
QApplication.closeAllWindows()
|
|
4472
|
+
|
|
4473
|
+
# Close all matplotlib figures
|
|
4474
|
+
plt.close('all')
|
|
4475
|
+
|
|
4476
|
+
# Accept the close event
|
|
4477
|
+
event.accept()
|
|
4478
|
+
|
|
4479
|
+
# Force quit the application
|
|
4480
|
+
QCoreApplication.quit()
|
|
4481
|
+
|
|
4335
4482
|
|
|
4336
4483
|
|
|
4337
4484
|
#TABLE RELATED:
|
|
@@ -5528,10 +5675,15 @@ class Show3dDialog(QDialog):
|
|
|
5528
5675
|
layout.addRow("Downsample Factor (Optional to speed up display):", self.downsample)
|
|
5529
5676
|
|
|
5530
5677
|
# Network Overlay checkbox (default True)
|
|
5531
|
-
self.cubic = QPushButton("
|
|
5678
|
+
self.cubic = QPushButton("Cubic")
|
|
5532
5679
|
self.cubic.setCheckable(True)
|
|
5533
5680
|
self.cubic.setChecked(False)
|
|
5534
5681
|
layout.addRow("Use cubic downsample (Slower but preserves shape better potentially)?", self.cubic)
|
|
5682
|
+
|
|
5683
|
+
self.box = QPushButton("Box")
|
|
5684
|
+
self.box.setCheckable(True)
|
|
5685
|
+
self.box.setChecked(False)
|
|
5686
|
+
layout.addRow("Include bounding box?", self.box)
|
|
5535
5687
|
|
|
5536
5688
|
# Add Run button
|
|
5537
5689
|
run_button = QPushButton("Show 3D")
|
|
@@ -5550,6 +5702,7 @@ class Show3dDialog(QDialog):
|
|
|
5550
5702
|
downsample = None
|
|
5551
5703
|
|
|
5552
5704
|
cubic = self.cubic.isChecked()
|
|
5705
|
+
box = self.box.isChecked()
|
|
5553
5706
|
|
|
5554
5707
|
if cubic:
|
|
5555
5708
|
order = 3
|
|
@@ -5582,7 +5735,7 @@ class Show3dDialog(QDialog):
|
|
|
5582
5735
|
arrays_3d.append(self.parent().highlight_overlay)
|
|
5583
5736
|
colors.append(color_template[4])
|
|
5584
5737
|
|
|
5585
|
-
n3d.show_3d(arrays_3d, arrays_4d, down_factor = downsample, order = order, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale, colors = colors)
|
|
5738
|
+
n3d.show_3d(arrays_3d, arrays_4d, down_factor = downsample, order = order, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale, colors = colors, box = box)
|
|
5586
5739
|
|
|
5587
5740
|
self.accept()
|
|
5588
5741
|
|
|
@@ -5958,7 +6111,7 @@ class PartitionDialog(QDialog):
|
|
|
5958
6111
|
dostats = self.stats.isChecked()
|
|
5959
6112
|
|
|
5960
6113
|
try:
|
|
5961
|
-
seed = int(self.seed.text()) if self.seed.text() else
|
|
6114
|
+
seed = int(self.seed.text()) if self.seed.text() else 42
|
|
5962
6115
|
except:
|
|
5963
6116
|
seed = None
|
|
5964
6117
|
|
|
@@ -5979,6 +6132,184 @@ class PartitionDialog(QDialog):
|
|
|
5979
6132
|
except Exception as e:
|
|
5980
6133
|
print(f"Error creating communities: {e}")
|
|
5981
6134
|
|
|
6135
|
+
class ComIdDialog(QDialog):
|
|
6136
|
+
|
|
6137
|
+
def __init__(self, parent=None):
|
|
6138
|
+
|
|
6139
|
+
super().__init__(parent)
|
|
6140
|
+
self.setWindowTitle("Select Mode")
|
|
6141
|
+
self.setModal(True)
|
|
6142
|
+
|
|
6143
|
+
layout = QFormLayout(self)
|
|
6144
|
+
|
|
6145
|
+
self.mode = QComboBox()
|
|
6146
|
+
self.mode.addItems(["Average Identities Per Community", "Weighted Average Identity of All Communities", ])
|
|
6147
|
+
self.mode.setCurrentIndex(0)
|
|
6148
|
+
layout.addRow("Mode", self.mode)
|
|
6149
|
+
|
|
6150
|
+
# umap checkbox (default True)
|
|
6151
|
+
self.umap = QPushButton("UMAP")
|
|
6152
|
+
self.umap.setCheckable(True)
|
|
6153
|
+
self.umap.setChecked(True)
|
|
6154
|
+
layout.addRow("Generate UMAP?:", self.umap)
|
|
6155
|
+
|
|
6156
|
+
# weighted checkbox (default True)
|
|
6157
|
+
self.label = QPushButton("Label")
|
|
6158
|
+
self.label.setCheckable(True)
|
|
6159
|
+
self.label.setChecked(False)
|
|
6160
|
+
layout.addRow("If using above - label UMAP points?:", self.label)
|
|
6161
|
+
|
|
6162
|
+
|
|
6163
|
+
# Add Run button
|
|
6164
|
+
run_button = QPushButton("Get Community ID Info")
|
|
6165
|
+
run_button.clicked.connect(self.run)
|
|
6166
|
+
layout.addWidget(run_button)
|
|
6167
|
+
|
|
6168
|
+
def run(self):
|
|
6169
|
+
|
|
6170
|
+
try:
|
|
6171
|
+
|
|
6172
|
+
if my_network.node_identities is None:
|
|
6173
|
+
print("Node identities must be set")
|
|
6174
|
+
|
|
6175
|
+
if my_network.communities is None:
|
|
6176
|
+
self.parent().show_partition_dialog()
|
|
6177
|
+
|
|
6178
|
+
if my_network.communities is None:
|
|
6179
|
+
return
|
|
6180
|
+
|
|
6181
|
+
mode = self.mode.currentIndex()
|
|
6182
|
+
|
|
6183
|
+
umap = self.umap.isChecked()
|
|
6184
|
+
label = self.label.isChecked()
|
|
6185
|
+
|
|
6186
|
+
if mode == 1:
|
|
6187
|
+
|
|
6188
|
+
info = my_network.community_id_info()
|
|
6189
|
+
|
|
6190
|
+
self.parent().format_for_upperright_table(info, 'Node Identity Type', 'Weighted Proportion in Communities', 'Weighted Average of Community Makeup')
|
|
6191
|
+
|
|
6192
|
+
else:
|
|
6193
|
+
|
|
6194
|
+
info, names = my_network.community_id_info_per_com(umap = umap, label = label)
|
|
6195
|
+
|
|
6196
|
+
self.parent().format_for_upperright_table(info, 'Community', names, 'Average of Community Makeup')
|
|
6197
|
+
|
|
6198
|
+
self.accept()
|
|
6199
|
+
|
|
6200
|
+
except Exception as e:
|
|
6201
|
+
|
|
6202
|
+
print(f"Error: {e}")
|
|
6203
|
+
|
|
6204
|
+
|
|
6205
|
+
|
|
6206
|
+
class ComNeighborDialog(QDialog):
|
|
6207
|
+
|
|
6208
|
+
def __init__(self, parent=None):
|
|
6209
|
+
|
|
6210
|
+
super().__init__(parent)
|
|
6211
|
+
self.setWindowTitle("Reassign Communities Based on Identity Similarity?")
|
|
6212
|
+
self.setModal(True)
|
|
6213
|
+
|
|
6214
|
+
layout = QFormLayout(self)
|
|
6215
|
+
|
|
6216
|
+
self.neighborcount = QLineEdit("5")
|
|
6217
|
+
layout.addRow("Num Neighborhoods:", self.neighborcount)
|
|
6218
|
+
|
|
6219
|
+
self.seed = QLineEdit("")
|
|
6220
|
+
layout.addRow("Clustering Seed:", self.seed)
|
|
6221
|
+
|
|
6222
|
+
self.limit = QLineEdit("")
|
|
6223
|
+
layout.addRow("Min Community Size to be grouped (Smaller communities will be placed in neighborhood 0 - does not apply if empty)", self.limit)
|
|
6224
|
+
|
|
6225
|
+
# Add Run button
|
|
6226
|
+
run_button = QPushButton("Get Neighborhoods (Note this overwrites current communities - save your coms first)")
|
|
6227
|
+
run_button.clicked.connect(self.run)
|
|
6228
|
+
layout.addWidget(run_button)
|
|
6229
|
+
|
|
6230
|
+
def run(self):
|
|
6231
|
+
|
|
6232
|
+
try:
|
|
6233
|
+
|
|
6234
|
+
if my_network.node_identities is None:
|
|
6235
|
+
print("Node identities must be set")
|
|
6236
|
+
|
|
6237
|
+
if my_network.communities is None:
|
|
6238
|
+
self.parent().show_partition_dialog()
|
|
6239
|
+
|
|
6240
|
+
if my_network.communities is None:
|
|
6241
|
+
return
|
|
6242
|
+
|
|
6243
|
+
seed = float(self.seed.text()) if self.seed.text().strip() else 42
|
|
6244
|
+
|
|
6245
|
+
limit = int(self.limit.text()) if self.limit.text().strip() else None
|
|
6246
|
+
|
|
6247
|
+
|
|
6248
|
+
neighborcount = int(self.neighborcount.text()) if self.neighborcount.text().strip() else 5
|
|
6249
|
+
|
|
6250
|
+
if self.parent().prev_coms is None:
|
|
6251
|
+
|
|
6252
|
+
self.parent().prev_coms = copy.deepcopy(my_network.communities)
|
|
6253
|
+
my_network.assign_neighborhoods(seed, neighborcount, limit = limit)
|
|
6254
|
+
else:
|
|
6255
|
+
my_network.assign_neighborhoods(seed, neighborcount, limit = limit, prev_coms = self.parent().prev_coms)
|
|
6256
|
+
|
|
6257
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'NeighborhoodID', title = 'Neighborhood Partition')
|
|
6258
|
+
|
|
6259
|
+
print("Neighborhoods have been assigned to communities based on similarity")
|
|
6260
|
+
|
|
6261
|
+
self.accept()
|
|
6262
|
+
|
|
6263
|
+
except Exception as e:
|
|
6264
|
+
|
|
6265
|
+
print(f"Error assigning neighborhoods: {e}")
|
|
6266
|
+
|
|
6267
|
+
class ComCellDialog(QDialog):
|
|
6268
|
+
|
|
6269
|
+
def __init__(self, parent=None):
|
|
6270
|
+
|
|
6271
|
+
super().__init__(parent)
|
|
6272
|
+
self.setWindowTitle("Assign Communities Based on Proximity Within Cuboidal Cells?")
|
|
6273
|
+
self.setModal(True)
|
|
6274
|
+
|
|
6275
|
+
layout = QFormLayout(self)
|
|
6276
|
+
|
|
6277
|
+
self.size = QLineEdit("")
|
|
6278
|
+
layout.addRow("Cell Size:", self.size)
|
|
6279
|
+
|
|
6280
|
+
# Add Run button
|
|
6281
|
+
run_button = QPushButton("Get Neighborhoods (Note this overwrites current communities - save your coms first)")
|
|
6282
|
+
run_button.clicked.connect(self.run)
|
|
6283
|
+
layout.addWidget(run_button)
|
|
6284
|
+
|
|
6285
|
+
def run(self):
|
|
6286
|
+
|
|
6287
|
+
try:
|
|
6288
|
+
|
|
6289
|
+
size = int(self.size.text()) if self.size.text().strip() else None
|
|
6290
|
+
|
|
6291
|
+
if size is None:
|
|
6292
|
+
return
|
|
6293
|
+
|
|
6294
|
+
if my_network.node_centroids is None:
|
|
6295
|
+
self.parent().show_centroid_dialog()
|
|
6296
|
+
if my_network.node_centroids is None:
|
|
6297
|
+
return
|
|
6298
|
+
|
|
6299
|
+
my_network.community_cells(size = size)
|
|
6300
|
+
|
|
6301
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'CommunityID')
|
|
6302
|
+
|
|
6303
|
+
self.accept()
|
|
6304
|
+
|
|
6305
|
+
except Exception as e:
|
|
6306
|
+
|
|
6307
|
+
print(f"Error: {e}")
|
|
6308
|
+
|
|
6309
|
+
|
|
6310
|
+
|
|
6311
|
+
|
|
6312
|
+
|
|
5982
6313
|
|
|
5983
6314
|
|
|
5984
6315
|
class RadialDialog(QDialog):
|
|
@@ -7302,7 +7633,7 @@ class SLabelDialog(QDialog):
|
|
|
7302
7633
|
try:
|
|
7303
7634
|
|
|
7304
7635
|
# Update both the display data and the network object
|
|
7305
|
-
binary_array = sdl.smart_label(binary_array, label_array, directory = None, GPU = GPU, predownsample = down_factor)
|
|
7636
|
+
binary_array = sdl.smart_label(binary_array, label_array, directory = None, GPU = GPU, predownsample = down_factor, remove_template = True)
|
|
7306
7637
|
|
|
7307
7638
|
label_array = sdl.invert_array(label_array)
|
|
7308
7639
|
|
|
@@ -7419,12 +7750,101 @@ class ThresholdDialog(QDialog):
|
|
|
7419
7750
|
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
7420
7751
|
return msg.exec() == QMessageBox.StandardButton.Yes
|
|
7421
7752
|
|
|
7753
|
+
class ExcelotronManager(QObject):
|
|
7754
|
+
# Signal to emit when data is received from Excelotron
|
|
7755
|
+
data_received = pyqtSignal(dict, str) # dictionary, property_name
|
|
7756
|
+
|
|
7757
|
+
def __init__(self, parent=None):
|
|
7758
|
+
super().__init__(parent)
|
|
7759
|
+
self.excelotron_window = None
|
|
7760
|
+
self.last_data = None
|
|
7761
|
+
self.last_property = None
|
|
7762
|
+
|
|
7763
|
+
def launch(self):
|
|
7764
|
+
"""Launch the Excelotron window"""
|
|
7765
|
+
|
|
7766
|
+
if self.excelotron_window is None:
|
|
7767
|
+
ExcelGUIClass = excelotron.main(standalone=False)
|
|
7768
|
+
self.excelotron_window = ExcelGUIClass()
|
|
7769
|
+
self.excelotron_window.data_exported.connect(self._on_data_exported)
|
|
7770
|
+
# Connect to both close event and destroyed signal
|
|
7771
|
+
self.excelotron_window.destroyed.connect(self._on_window_destroyed)
|
|
7772
|
+
self.excelotron_window.closeEvent = self._create_close_handler(self.excelotron_window.closeEvent)
|
|
7773
|
+
self.excelotron_window.show()
|
|
7774
|
+
else:
|
|
7775
|
+
self.excelotron_window.raise_()
|
|
7776
|
+
self.excelotron_window.activateWindow()
|
|
7777
|
+
|
|
7778
|
+
def _create_close_handler(self, original_close_event):
|
|
7779
|
+
"""Create a close event handler that cleans up properly"""
|
|
7780
|
+
def close_handler(event):
|
|
7781
|
+
self._cleanup_window()
|
|
7782
|
+
original_close_event(event)
|
|
7783
|
+
return close_handler
|
|
7784
|
+
|
|
7785
|
+
def close(self):
|
|
7786
|
+
"""Close the Excelotron window"""
|
|
7787
|
+
if self.excelotron_window is not None:
|
|
7788
|
+
self.excelotron_window.close()
|
|
7789
|
+
self._cleanup_window()
|
|
7790
|
+
|
|
7791
|
+
def _cleanup_window(self):
|
|
7792
|
+
"""Properly cleanup the window reference"""
|
|
7793
|
+
if self.excelotron_window is not None:
|
|
7794
|
+
try:
|
|
7795
|
+
# Disconnect all signals to prevent issues
|
|
7796
|
+
self.excelotron_window.data_exported.disconnect()
|
|
7797
|
+
self.excelotron_window.destroyed.disconnect()
|
|
7798
|
+
except:
|
|
7799
|
+
pass # Ignore if already disconnected
|
|
7800
|
+
|
|
7801
|
+
# Schedule for deletion
|
|
7802
|
+
self.excelotron_window.deleteLater()
|
|
7803
|
+
self.excelotron_window = None
|
|
7804
|
+
|
|
7805
|
+
def is_open(self):
|
|
7806
|
+
"""Check if Excelotron window is open"""
|
|
7807
|
+
is_open = self.excelotron_window is not None
|
|
7808
|
+
return is_open
|
|
7809
|
+
|
|
7810
|
+
def _on_data_exported(self, data_dict, property_name):
|
|
7811
|
+
"""Internal slot to handle data from Excelotron"""
|
|
7812
|
+
self.last_data = data_dict
|
|
7813
|
+
self.last_property = property_name
|
|
7814
|
+
# Re-emit the signal for parent to handle
|
|
7815
|
+
self.data_received.emit(data_dict, property_name)
|
|
7816
|
+
|
|
7817
|
+
def _on_window_destroyed(self):
|
|
7818
|
+
"""Handle when the Excelotron window is destroyed/closed"""
|
|
7819
|
+
self.excelotron_window = None
|
|
7820
|
+
|
|
7821
|
+
def get_last_data(self):
|
|
7822
|
+
"""Get the last exported data"""
|
|
7823
|
+
return self.last_data, self.last_property
|
|
7422
7824
|
|
|
7423
7825
|
class MachineWindow(QMainWindow):
|
|
7424
7826
|
|
|
7425
7827
|
def __init__(self, parent=None, GPU = False):
|
|
7426
7828
|
super().__init__(parent)
|
|
7427
7829
|
|
|
7830
|
+
if self.parent().active_channel == 0:
|
|
7831
|
+
if self.parent().channel_data[0] is not None:
|
|
7832
|
+
try:
|
|
7833
|
+
active_data = self.parent().channel_data[0]
|
|
7834
|
+
act_channel = 0
|
|
7835
|
+
except:
|
|
7836
|
+
active_data = self.parent().channel_data[1]
|
|
7837
|
+
act_channel = 1
|
|
7838
|
+
else:
|
|
7839
|
+
active_data = self.parent().channel_data[1]
|
|
7840
|
+
act_channel = 1
|
|
7841
|
+
|
|
7842
|
+
try:
|
|
7843
|
+
array1 = np.zeros_like(active_data).astype(np.uint8)
|
|
7844
|
+
except:
|
|
7845
|
+
print("No data in nodes channel")
|
|
7846
|
+
return
|
|
7847
|
+
|
|
7428
7848
|
self.setWindowTitle("Threshold")
|
|
7429
7849
|
|
|
7430
7850
|
# Create central widget and layout
|
|
@@ -7446,25 +7866,6 @@ class MachineWindow(QMainWindow):
|
|
|
7446
7866
|
|
|
7447
7867
|
self.parent().pen_button.setEnabled(False)
|
|
7448
7868
|
|
|
7449
|
-
|
|
7450
|
-
if self.parent().active_channel == 0:
|
|
7451
|
-
if self.parent().channel_data[0] is not None:
|
|
7452
|
-
try:
|
|
7453
|
-
active_data = self.parent().channel_data[0]
|
|
7454
|
-
act_channel = 0
|
|
7455
|
-
except:
|
|
7456
|
-
active_data = self.parent().channel_data[1]
|
|
7457
|
-
act_channel = 1
|
|
7458
|
-
else:
|
|
7459
|
-
active_data = self.parent().channel_data[1]
|
|
7460
|
-
act_channel = 1
|
|
7461
|
-
|
|
7462
|
-
try:
|
|
7463
|
-
array1 = np.zeros_like(active_data).astype(np.uint8)
|
|
7464
|
-
except:
|
|
7465
|
-
print("No data in nodes channel")
|
|
7466
|
-
return
|
|
7467
|
-
|
|
7468
7869
|
array3 = np.zeros_like(active_data).astype(np.uint8)
|
|
7469
7870
|
self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
|
|
7470
7871
|
|
|
@@ -8849,6 +9250,91 @@ class MaskDialog(QDialog):
|
|
|
8849
9250
|
except Exception as e:
|
|
8850
9251
|
print(f"Error masking: {e}")
|
|
8851
9252
|
|
|
9253
|
+
class CropDialog(QDialog):
|
|
9254
|
+
|
|
9255
|
+
def __init__(self, parent=None):
|
|
9256
|
+
|
|
9257
|
+
try:
|
|
9258
|
+
|
|
9259
|
+
super().__init__(parent)
|
|
9260
|
+
self.setWindowTitle("Crop Image?")
|
|
9261
|
+
self.setModal(True)
|
|
9262
|
+
|
|
9263
|
+
layout = QFormLayout(self)
|
|
9264
|
+
|
|
9265
|
+
self.xmin = QLineEdit("0")
|
|
9266
|
+
layout.addRow("X Min", self.xmin)
|
|
9267
|
+
|
|
9268
|
+
self.xmax = QLineEdit(f"{self.parent().shape[2]}")
|
|
9269
|
+
layout.addRow("X Max", self.xmax)
|
|
9270
|
+
|
|
9271
|
+
self.ymin = QLineEdit("0")
|
|
9272
|
+
layout.addRow("Y Min", self.ymin)
|
|
9273
|
+
|
|
9274
|
+
self.ymax = QLineEdit(f"{self.parent().shape[1]}")
|
|
9275
|
+
layout.addRow("Y Max", self.ymax)
|
|
9276
|
+
|
|
9277
|
+
self.zmin = QLineEdit("0")
|
|
9278
|
+
layout.addRow("Z Min", self.zmin)
|
|
9279
|
+
|
|
9280
|
+
self.zmax = QLineEdit(f"{self.parent().shape[0]}")
|
|
9281
|
+
layout.addRow("Z Max", self.zmax)
|
|
9282
|
+
|
|
9283
|
+
# Add Run button
|
|
9284
|
+
run_button = QPushButton("Run")
|
|
9285
|
+
run_button.clicked.connect(self.run)
|
|
9286
|
+
layout.addRow(run_button)
|
|
9287
|
+
|
|
9288
|
+
except:
|
|
9289
|
+
pass
|
|
9290
|
+
|
|
9291
|
+
def run(self):
|
|
9292
|
+
|
|
9293
|
+
try:
|
|
9294
|
+
|
|
9295
|
+
xmin = int(self.xmin.text()) if self.xmin.text() else 0
|
|
9296
|
+
ymin = int(self.ymin.text()) if self.ymin.text() else 0
|
|
9297
|
+
zmin = int(self.zmin.text()) if self.zmin.text() else 0
|
|
9298
|
+
xmax = int(self.xmax.text()) if self.xmax.text() else self.parent().shape[2]
|
|
9299
|
+
ymax = int(self.ymax.text()) if self.xmax.text() else self.parent().shape[1]
|
|
9300
|
+
zmax = int(self.zmax.text()) if self.xmax.text() else self.parent().shape[0]
|
|
9301
|
+
|
|
9302
|
+
args = xmin, ymin, zmin, xmax, ymax, zmax
|
|
9303
|
+
|
|
9304
|
+
for i, array in enumerate(self.parent().channel_data):
|
|
9305
|
+
|
|
9306
|
+
if array is None:
|
|
9307
|
+
|
|
9308
|
+
continue
|
|
9309
|
+
|
|
9310
|
+
else:
|
|
9311
|
+
|
|
9312
|
+
array = self.reslice_3d_array(array, args)
|
|
9313
|
+
|
|
9314
|
+
self.parent().load_channel(i, array, data = True)
|
|
9315
|
+
|
|
9316
|
+
self.accept()
|
|
9317
|
+
|
|
9318
|
+
except Exception as e:
|
|
9319
|
+
|
|
9320
|
+
print(f"Error cropping: {e}")
|
|
9321
|
+
|
|
9322
|
+
|
|
9323
|
+
|
|
9324
|
+
|
|
9325
|
+
|
|
9326
|
+
|
|
9327
|
+
|
|
9328
|
+
|
|
9329
|
+
def reslice_3d_array(self, array, args):
|
|
9330
|
+
"""Internal method used for the secondary algorithm to reslice subarrays around nodes."""
|
|
9331
|
+
|
|
9332
|
+
x_start, y_start, z_start, x_end, y_end, z_end = args
|
|
9333
|
+
|
|
9334
|
+
# Reslice the array
|
|
9335
|
+
array = array[z_start:z_end+1, y_start:y_end+1, x_start:x_end+1]
|
|
9336
|
+
|
|
9337
|
+
return array
|
|
8852
9338
|
|
|
8853
9339
|
|
|
8854
9340
|
class TypeDialog(QDialog):
|
|
@@ -9212,6 +9698,12 @@ class CentroidNodeDialog(QDialog):
|
|
|
9212
9698
|
|
|
9213
9699
|
layout = QFormLayout(self)
|
|
9214
9700
|
|
|
9701
|
+
# Add mode selection dropdown
|
|
9702
|
+
self.mode_selector = QComboBox()
|
|
9703
|
+
self.mode_selector.addItems(["Starting at 0", "Starting at Min Centroids (will transpose centroids)"])
|
|
9704
|
+
self.mode_selector.setCurrentIndex(0) # Default to Mode 1
|
|
9705
|
+
layout.addRow("Execution Mode:", self.mode_selector)
|
|
9706
|
+
|
|
9215
9707
|
# Add Run button
|
|
9216
9708
|
run_button = QPushButton("Run Node Generation? (Will override current nodes). Note it is presumed your nodes begin at 1, not 0.")
|
|
9217
9709
|
run_button.clicked.connect(self.run_nodes)
|
|
@@ -9241,7 +9733,18 @@ class CentroidNodeDialog(QDialog):
|
|
|
9241
9733
|
)
|
|
9242
9734
|
return
|
|
9243
9735
|
|
|
9244
|
-
|
|
9736
|
+
mode = self.mode_selector.currentIndex()
|
|
9737
|
+
|
|
9738
|
+
if mode == 0:
|
|
9739
|
+
|
|
9740
|
+
my_network.nodes = my_network.centroid_array()
|
|
9741
|
+
|
|
9742
|
+
else:
|
|
9743
|
+
|
|
9744
|
+
my_network.nodes, my_network.centroids = my_network.centroid_array(clip = True)
|
|
9745
|
+
|
|
9746
|
+
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
9747
|
+
|
|
9245
9748
|
|
|
9246
9749
|
self.parent().load_channel(0, channel_data = my_network.nodes, data = True)
|
|
9247
9750
|
|
|
@@ -9324,7 +9827,13 @@ class GenNodesDialog(QDialog):
|
|
|
9324
9827
|
# Auto checkbox
|
|
9325
9828
|
self.auto = QPushButton("Auto")
|
|
9326
9829
|
self.auto.setCheckable(True)
|
|
9327
|
-
|
|
9830
|
+
try:
|
|
9831
|
+
if my_network.edges.shape[0] == 1:
|
|
9832
|
+
self.auto.setChecked(False)
|
|
9833
|
+
else:
|
|
9834
|
+
self.auto.setChecked(True)
|
|
9835
|
+
except:
|
|
9836
|
+
self.auto.setChecked(True)
|
|
9328
9837
|
rec_layout.addWidget(QLabel("Attempt to Auto Correct Skeleton Looping:"), 1, 0)
|
|
9329
9838
|
rec_layout.addWidget(self.auto, 1, 1)
|
|
9330
9839
|
|
|
@@ -9489,10 +9998,10 @@ class BranchDialog(QDialog):
|
|
|
9489
9998
|
correction_layout = QGridLayout()
|
|
9490
9999
|
|
|
9491
10000
|
# Branch Fix checkbox
|
|
9492
|
-
self.fix = QPushButton("Auto-Correct
|
|
10001
|
+
self.fix = QPushButton("Auto-Correct 1")
|
|
9493
10002
|
self.fix.setCheckable(True)
|
|
9494
10003
|
self.fix.setChecked(False)
|
|
9495
|
-
correction_layout.addWidget(QLabel("
|
|
10004
|
+
correction_layout.addWidget(QLabel("Auto-Correct Branches by Collapsing Busy Neighbors: "), 0, 0)
|
|
9496
10005
|
correction_layout.addWidget(self.fix, 0, 1)
|
|
9497
10006
|
|
|
9498
10007
|
# Fix value
|
|
@@ -9504,6 +10013,12 @@ class BranchDialog(QDialog):
|
|
|
9504
10013
|
self.seed = QLineEdit('')
|
|
9505
10014
|
correction_layout.addWidget(QLabel("Random seed for auto correction (int - optional):"), 2, 0)
|
|
9506
10015
|
correction_layout.addWidget(self.seed, 2, 1)
|
|
10016
|
+
|
|
10017
|
+
self.fix2 = QPushButton("Auto-Correct 2")
|
|
10018
|
+
self.fix2.setCheckable(True)
|
|
10019
|
+
self.fix2.setChecked(True)
|
|
10020
|
+
correction_layout.addWidget(QLabel("Auto-Correct Branches by Collapsing Internal Labels: "), 3, 0)
|
|
10021
|
+
correction_layout.addWidget(self.fix2, 3, 1)
|
|
9507
10022
|
|
|
9508
10023
|
correction_group.setLayout(correction_layout)
|
|
9509
10024
|
main_layout.addWidget(correction_group)
|
|
@@ -9573,6 +10088,7 @@ class BranchDialog(QDialog):
|
|
|
9573
10088
|
GPU = self.GPU.isChecked()
|
|
9574
10089
|
cubic = self.cubic.isChecked()
|
|
9575
10090
|
fix = self.fix.isChecked()
|
|
10091
|
+
fix2 = self.fix2.isChecked()
|
|
9576
10092
|
fix_val = float(self.fix_val.text()) if self.fix_val.text() else None
|
|
9577
10093
|
seed = int(self.seed.text()) if self.seed.text() else None
|
|
9578
10094
|
|
|
@@ -9589,6 +10105,25 @@ class BranchDialog(QDialog):
|
|
|
9589
10105
|
|
|
9590
10106
|
output = n3d.label_branches(my_network.edges, nodes = my_network.nodes, bonus_array = original_array, GPU = GPU, down_factor = down_factor, arrayshape = original_shape)
|
|
9591
10107
|
|
|
10108
|
+
if fix2:
|
|
10109
|
+
|
|
10110
|
+
temp_network = n3d.Network_3D(nodes = output)
|
|
10111
|
+
|
|
10112
|
+
max_val = np.max(temp_network.nodes)
|
|
10113
|
+
|
|
10114
|
+
background = temp_network.nodes == 0
|
|
10115
|
+
|
|
10116
|
+
background = background * max_val
|
|
10117
|
+
|
|
10118
|
+
temp_network.nodes = temp_network.nodes + background
|
|
10119
|
+
|
|
10120
|
+
del background
|
|
10121
|
+
|
|
10122
|
+
temp_network.morph_proximity(search = [3,3], fastdil = True) #Detect network of nearby branches
|
|
10123
|
+
|
|
10124
|
+
output = n3d.fix_branches(output, temp_network.network, max_val)
|
|
10125
|
+
|
|
10126
|
+
|
|
9592
10127
|
if fix:
|
|
9593
10128
|
|
|
9594
10129
|
temp_network = n3d.Network_3D(nodes = output)
|
|
@@ -9597,7 +10132,7 @@ class BranchDialog(QDialog):
|
|
|
9597
10132
|
|
|
9598
10133
|
temp_network.community_partition(weighted = False, style = 1, dostats = False, seed = seed) #Find communities with louvain, unweighted params
|
|
9599
10134
|
|
|
9600
|
-
targs = n3d.
|
|
10135
|
+
targs = n3d.fix_branches_network(temp_network.nodes, temp_network.network, temp_network.communities, fix_val)
|
|
9601
10136
|
|
|
9602
10137
|
temp_network.com_to_node(targs)
|
|
9603
10138
|
|
|
@@ -9784,6 +10319,11 @@ class ModifyDialog(QDialog):
|
|
|
9784
10319
|
self.revid.setCheckable(True)
|
|
9785
10320
|
self.revid.setChecked(False)
|
|
9786
10321
|
layout.addRow("Remove Unassigned IDs from Centroid List?:", self.revid)
|
|
10322
|
+
|
|
10323
|
+
self.remove = QPushButton("Remove Missing")
|
|
10324
|
+
self.remove.setCheckable(True)
|
|
10325
|
+
self.remove.setChecked(False)
|
|
10326
|
+
layout.addRow("Remove Any Nodes Not in Nodes Channel From Properties?:", self.remove)
|
|
9787
10327
|
|
|
9788
10328
|
# trunk checkbox (default false)
|
|
9789
10329
|
self.trunk = QPushButton("Remove Trunk")
|
|
@@ -9862,6 +10402,7 @@ class ModifyDialog(QDialog):
|
|
|
9862
10402
|
prune = self.prune.isChecked()
|
|
9863
10403
|
isolate = self.isolate.isChecked()
|
|
9864
10404
|
comcollapse = self.comcollapse.isChecked()
|
|
10405
|
+
remove = self.remove.isChecked()
|
|
9865
10406
|
|
|
9866
10407
|
|
|
9867
10408
|
if isolate and my_network.node_identities is not None:
|
|
@@ -9875,6 +10416,21 @@ class ModifyDialog(QDialog):
|
|
|
9875
10416
|
pass
|
|
9876
10417
|
|
|
9877
10418
|
|
|
10419
|
+
if remove:
|
|
10420
|
+
my_network.purge_properties()
|
|
10421
|
+
try:
|
|
10422
|
+
self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
|
|
10423
|
+
except:
|
|
10424
|
+
pass
|
|
10425
|
+
try:
|
|
10426
|
+
self.parent().format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
|
|
10427
|
+
except:
|
|
10428
|
+
pass
|
|
10429
|
+
try:
|
|
10430
|
+
self.parent().format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
|
|
10431
|
+
except:
|
|
10432
|
+
pass
|
|
10433
|
+
|
|
9878
10434
|
|
|
9879
10435
|
if edgeweight:
|
|
9880
10436
|
my_network.remove_edge_weights()
|
|
@@ -10469,14 +11025,9 @@ class ProxDialog(QDialog):
|
|
|
10469
11025
|
my_network.id_overlay = my_network.draw_node_indices(directory=directory)
|
|
10470
11026
|
|
|
10471
11027
|
# Update channel data
|
|
10472
|
-
self.parent().
|
|
10473
|
-
self.parent().
|
|
11028
|
+
self.parent().load_channel(2, channel_data = my_network.network_overlay, data = True)
|
|
11029
|
+
self.parent().load_channel(3, channel_data = my_network.id_overlay, data = True)
|
|
10474
11030
|
|
|
10475
|
-
# Enable the overlay channel buttons
|
|
10476
|
-
self.parent().channel_buttons[2].setEnabled(True)
|
|
10477
|
-
self.parent().channel_buttons[3].setEnabled(True)
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
11031
|
self.parent().update_display()
|
|
10481
11032
|
self.accept()
|
|
10482
11033
|
|