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.
@@ -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 (Weighted avg by community size)")
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
- G = my_network.network
2864
+ try:
2841
2865
 
2842
- shortest_path_lengths = dict(nx.all_pairs_shortest_path_length(G))
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
- # Express frequency distribution as a percentage (ignoring path lengths of 0)
2856
- freq_percent = 100 * path_lengths[1:] / path_lengths[1:].sum()
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
- # Plot the frequency distribution (ignoring path lengths of 0) as a percentage
2859
- fig, ax = plt.subplots(figsize=(15, 8))
2860
- ax.bar(np.arange(1, diameter + 1), height=freq_percent)
2861
- ax.set_title(
2862
- "Distribution of shortest path length in G", fontdict={"size": 35}, loc="center"
2863
- )
2864
- ax.set_xlabel("Shortest Path Length", fontdict={"size": 22})
2865
- ax.set_ylabel("Frequency (%)", fontdict={"size": 22})
2866
-
2867
- plt.show()
2868
- freq_dict = {freq: length for length, freq in enumerate(freq_percent, start=1)}
2869
- self.format_for_upperright_table(freq_dict, metric='Frequency (%)', value='Shortest Path Length', title="Distribution of shortest path length in G")
2870
-
2871
- degree_centrality = nx.centrality.degree_centrality(G)
2872
- plt.figure(figsize=(15, 8))
2873
- plt.hist(degree_centrality.values(), bins=25)
2874
- plt.xticks(ticks=[0, 0.025, 0.05, 0.1, 0.15, 0.2]) # set the x axis ticks
2875
- plt.title("Degree Centrality Histogram ", fontdict={"size": 35}, loc="center")
2876
- plt.xlabel("Degree Centrality", fontdict={"size": 20})
2877
- plt.ylabel("Counts", fontdict={"size": 20})
2878
- plt.show()
2879
- self.format_for_upperright_table(degree_centrality, metric='Node', value='Degree Centrality', title="Degree Centrality Table")
2880
-
2881
-
2882
- betweenness_centrality = nx.centrality.betweenness_centrality(
2883
- G
2884
- )
2885
- plt.figure(figsize=(15, 8))
2886
- plt.hist(betweenness_centrality.values(), bins=100)
2887
- plt.xticks(ticks=[0, 0.02, 0.1, 0.2, 0.3, 0.4, 0.5]) # set the x axis ticks
2888
- plt.title("Betweenness Centrality Histogram ", fontdict={"size": 35}, loc="center")
2889
- plt.xlabel("Betweenness Centrality", fontdict={"size": 20})
2890
- plt.ylabel("Counts", fontdict={"size": 20})
2891
- plt.show()
2892
- self.format_for_upperright_table(betweenness_centrality, metric='Node', value='Betweenness Centrality', title="Betweenness Centrality Table")
2893
-
2894
-
2895
- closeness_centrality = nx.centrality.closeness_centrality(
2896
- G
2897
- )
2898
- plt.figure(figsize=(15, 8))
2899
- plt.hist(closeness_centrality.values(), bins=60)
2900
- plt.title("Closeness Centrality Histogram ", fontdict={"size": 35}, loc="center")
2901
- plt.xlabel("Closeness Centrality", fontdict={"size": 20})
2902
- plt.ylabel("Counts", fontdict={"size": 20})
2903
- plt.show()
2904
- self.format_for_upperright_table(closeness_centrality, metric='Node', value='Closeness Centrality', title="Closeness Centrality Table")
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
- eigenvector_centrality = nx.centrality.eigenvector_centrality(
2908
- G
2909
- )
2910
- plt.figure(figsize=(15, 8))
2911
- plt.hist(eigenvector_centrality.values(), bins=60)
2912
- plt.xticks(ticks=[0, 0.01, 0.02, 0.04, 0.06, 0.08]) # set the x axis ticks
2913
- plt.title("Eigenvector Centrality Histogram ", fontdict={"size": 35}, loc="center")
2914
- plt.xlabel("Eigenvector Centrality", fontdict={"size": 20})
2915
- plt.ylabel("Counts", fontdict={"size": 20})
2916
- plt.show()
2917
- self.format_for_upperright_table(eigenvector_centrality, metric='Node', value='Eigenvector Centrality', title="Eigenvector Centrality Table")
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
- clusters = nx.clustering(G)
2922
- plt.figure(figsize=(15, 8))
2923
- plt.hist(clusters.values(), bins=50)
2924
- plt.title("Clustering Coefficient Histogram ", fontdict={"size": 35}, loc="center")
2925
- plt.xlabel("Clustering Coefficient", fontdict={"size": 20})
2926
- plt.ylabel("Counts", fontdict={"size": 20})
2927
- plt.show()
2928
- self.format_for_upperright_table(clusters, metric='Node', value='Clustering Coefficient', title="Clustering Coefficient Table")
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
- bridges = list(nx.bridges(G))
2931
- self.format_for_upperright_table(bridges, metric = 'Node Pair', title="Bridges")
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
- if my_network.communities is None:
4276
- self.show_partition_dialog()
4405
+ dialog = ComIdDialog(self)
4406
+ dialog.exec()
4277
4407
 
4278
- if my_network.communities is None:
4279
- return
4408
+ def handle_com_neighbor(self):
4280
4409
 
4281
- info = my_network.community_id_info()
4410
+ dialog = ComNeighborDialog(self)
4411
+ dialog.exec()
4282
4412
 
4283
- self.format_for_upperright_table(info, 'Node Identity Type', 'Weighted Proportion in Communities', 'Weighted Average of Community Makeup')
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("cubic")
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 None
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
- my_network.nodes = my_network.centroid_array()
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
- self.auto.setChecked(True)
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 Branches")
10001
+ self.fix = QPushButton("Auto-Correct 1")
9493
10002
  self.fix.setCheckable(True)
9494
10003
  self.fix.setChecked(False)
9495
- correction_layout.addWidget(QLabel("Attempt to auto-correct branch labels:"), 0, 0)
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.fix_branches(temp_network.nodes, temp_network.network, temp_network.communities, fix_val)
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().channel_data[2] = my_network.network_overlay
10473
- self.parent().channel_data[3] = my_network.id_overlay
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