nettracer3d 0.6.6__py3-none-any.whl → 0.6.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.
@@ -25,6 +25,7 @@ import multiprocessing as mp
25
25
  from concurrent.futures import ThreadPoolExecutor
26
26
  from functools import partial
27
27
  from nettracer3d import segmenter
28
+ #from nettracer3d import segmenter_GPU
28
29
 
29
30
 
30
31
 
@@ -147,6 +148,11 @@ class ImageViewerWindow(QMainWindow):
147
148
  3: None
148
149
  } #For storing thresholding information
149
150
 
151
+ self.radii_dict = {
152
+ 0: None,
153
+ 1: None
154
+ }
155
+
150
156
  self.original_shape = None #For undoing resamples
151
157
 
152
158
  # Create control panel
@@ -1312,7 +1318,10 @@ class ImageViewerWindow(QMainWindow):
1312
1318
  info_dict['Centroid'] = my_network.node_centroids[label]
1313
1319
 
1314
1320
  if self.volume_dict[0] is not None:
1315
- info_dict['Volume'] = self.volume_dict[0][label]
1321
+ info_dict['Volume (Scaled)'] = self.volume_dict[0][label]
1322
+
1323
+ if self.radii_dict[0] is not None:
1324
+ info_dict['Max Radius (Scaled)'] = self.radii_dict[0][label]
1316
1325
 
1317
1326
 
1318
1327
  elif sort == 'edge':
@@ -1327,7 +1336,10 @@ class ImageViewerWindow(QMainWindow):
1327
1336
  info_dict['Centroid'] = my_network.edge_centroids[label]
1328
1337
 
1329
1338
  if self.volume_dict[1] is not None:
1330
- info_dict['Volume'] = self.volume_dict[1][label]
1339
+ info_dict['Volume (Scaled)'] = self.volume_dict[1][label]
1340
+
1341
+ if self.radii_dict[1] is not None:
1342
+ info_dict['~Radius (Scaled)'] = self.radii_dict[1][label]
1331
1343
 
1332
1344
  self.format_for_upperright_table(info_dict, title = f'Info on Object')
1333
1345
 
@@ -2485,7 +2497,7 @@ class ImageViewerWindow(QMainWindow):
2485
2497
  # Process menu
2486
2498
  process_menu = menubar.addMenu("Process")
2487
2499
  calculate_menu = process_menu.addMenu("Calculate")
2488
- calc_all_action = calculate_menu.addAction("Calculate All (Find Node-Edge-Node Network)")
2500
+ calc_all_action = calculate_menu.addAction("Calculate Connectivity Network (Find Node-Edge-Node Network)")
2489
2501
  calc_all_action.triggered.connect(self.show_calc_all_dialog)
2490
2502
  calc_prox_action = calculate_menu.addAction("Calculate Proximity Network (connect nodes by distance)")
2491
2503
  calc_prox_action.triggered.connect(self.show_calc_prox_dialog)
@@ -2551,8 +2563,8 @@ class ImageViewerWindow(QMainWindow):
2551
2563
  idoverlay_action.triggered.connect(self.show_idoverlay_dialog)
2552
2564
  coloroverlay_action = overlay_menu.addAction("Color Nodes (or Edges)")
2553
2565
  coloroverlay_action.triggered.connect(self.show_coloroverlay_dialog)
2554
- searchoverlay_action = overlay_menu.addAction("Show Search Regions")
2555
- searchoverlay_action.triggered.connect(self.show_search_dialog)
2566
+ #searchoverlay_action = overlay_menu.addAction("Show Search Regions")
2567
+ #searchoverlay_action.triggered.connect(self.show_search_dialog)
2556
2568
  shuffle_action = overlay_menu.addAction("Shuffle")
2557
2569
  shuffle_action.triggered.connect(self.show_shuffle_dialog)
2558
2570
  arbitrary_action = image_menu.addAction("Select Objects")
@@ -2887,6 +2899,20 @@ class ImageViewerWindow(QMainWindow):
2887
2899
  elif trumper == '-':
2888
2900
  for key, value in my_dict.items():
2889
2901
  my_dict[key] = value[0]
2902
+ elif trumper == '/':
2903
+ new_dict = {}
2904
+ max_val = max(my_dict.keys()) + 1
2905
+ for key, value in my_dict.items():
2906
+ new_dict[key] = f'{value[0]}'
2907
+ if len(value) > 1:
2908
+ for i in range(1, len(value)):
2909
+ new_dict[max_val] = f'{value[i]}'
2910
+ try:
2911
+ my_network.node_centroids[max_val] = my_network.node_centroids[key]
2912
+ except:
2913
+ pass
2914
+ max_val += 1
2915
+ return new_dict
2890
2916
  else:
2891
2917
  for thing in my_dict:
2892
2918
  val = my_dict[thing]
@@ -2925,12 +2951,16 @@ class ImageViewerWindow(QMainWindow):
2925
2951
  'Multiple IDs Detected',
2926
2952
  'The node identities appear to contain multiple ids per node in a list.\n'
2927
2953
  'If you desire one node ID to trump all others, enter it here.\n'
2928
- '(Enter "-" to have the first IDs trump all others or press x to skip)'
2954
+ '(Enter "-" to have the first IDs trump all others)\n'
2955
+ '(Enter "/" to have multi-ID nodes be split into many nodes sharing a centroid)\n'
2956
+ '(Close this window to continue with multi-ID nodes)'
2929
2957
  )
2930
2958
  if not ok or trump_value.strip() == '':
2931
2959
  trump_value = None
2932
2960
  elif trump_value.upper() == '-':
2933
2961
  trump_value = '-'
2962
+ elif trump_value.upper() == "/":
2963
+ trump_value = '/'
2934
2964
  my_network.node_identities = uncork(my_network.node_identities, trump_value)
2935
2965
  else:
2936
2966
  trump_value = None
@@ -3038,76 +3068,78 @@ class ImageViewerWindow(QMainWindow):
3038
3068
  "",
3039
3069
  QFileDialog.Option.ShowDirsOnly
3040
3070
  )
3041
- self.reset(nodes = True, network = True, xy_scale = 1, z_scale = 1, edges = True, search_region = True, network_overlay = True, id_overlay = True)
3042
-
3043
-
3044
- my_network.assemble(directory)
3045
3071
 
3046
- # Load image channels
3047
- try:
3048
- self.load_channel(0, my_network.nodes, True)
3049
- except Exception as e:
3050
- print(e)
3051
- try:
3052
- self.load_channel(1, my_network.edges, True)
3053
- except Exception as e:
3054
- print(e)
3055
- try:
3056
- self.load_channel(2, my_network.network_overlay, True)
3057
- except Exception as e:
3058
- print(e)
3059
- try:
3060
- self.load_channel(3, my_network.id_overlay, True)
3061
- except Exception as e:
3062
- print(e)
3072
+ if directory != "":
3063
3073
 
3064
- # Update slider range based on new data
3065
- for channel in self.channel_data:
3066
- if channel is not None:
3067
- self.slice_slider.setEnabled(True)
3068
- self.slice_slider.setMinimum(0)
3069
- self.slice_slider.setMaximum(channel.shape[0] - 1)
3070
- self.slice_slider.setValue(0)
3071
- self.current_slice = 0
3072
- break
3074
+ self.reset(network = True, xy_scale = 1, z_scale = 1, edges = True, network_overlay = True, id_overlay = True)
3073
3075
 
3074
- # Display network_lists in the network table
3075
- # Create empty DataFrame for network table if network_lists is None
3076
- if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
3077
- empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
3078
- model = PandasModel(empty_df)
3079
- self.network_table.setModel(model)
3080
- else:
3081
- model = PandasModel(my_network.network_lists)
3082
- self.network_table.setModel(model)
3083
- # Adjust column widths to content
3084
- for column in range(model.columnCount(None)):
3085
- self.network_table.resizeColumnToContents(column)
3076
+ my_network.assemble(directory)
3086
3077
 
3087
- if hasattr(my_network, 'node_centroids') and my_network.node_centroids is not None:
3078
+ # Load image channels
3088
3079
  try:
3089
- self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
3080
+ self.load_channel(0, my_network.nodes, True)
3090
3081
  except Exception as e:
3091
- print(f"Error loading node centroid table: {e}")
3092
-
3093
- if hasattr(my_network, 'edge_centroids') and my_network.edge_centroids is not None:
3082
+ print(e)
3094
3083
  try:
3095
- self.format_for_upperright_table(my_network.edge_centroids, 'EdgeID', ['Z', 'Y', 'X'], 'Edge Centroids')
3084
+ self.load_channel(1, my_network.edges, True)
3096
3085
  except Exception as e:
3097
- print(f"Error loading edge centroid table: {e}")
3098
-
3099
- if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
3086
+ print(e)
3100
3087
  try:
3101
- self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
3088
+ self.load_channel(2, my_network.network_overlay, True)
3102
3089
  except Exception as e:
3103
- print(f"Error loading node identity table: {e}")
3104
-
3105
-
3106
- if hasattr(my_network, 'communities') and my_network.communities is not None:
3090
+ print(e)
3107
3091
  try:
3108
- self.format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
3092
+ self.load_channel(3, my_network.id_overlay, True)
3109
3093
  except Exception as e:
3110
- print(f"Error loading node community table: {e}")
3094
+ print(e)
3095
+
3096
+ # Update slider range based on new data
3097
+ for channel in self.channel_data:
3098
+ if channel is not None:
3099
+ self.slice_slider.setEnabled(True)
3100
+ self.slice_slider.setMinimum(0)
3101
+ self.slice_slider.setMaximum(channel.shape[0] - 1)
3102
+ self.slice_slider.setValue(0)
3103
+ self.current_slice = 0
3104
+ break
3105
+
3106
+ # Display network_lists in the network table
3107
+ # Create empty DataFrame for network table if network_lists is None
3108
+ if not hasattr(my_network, 'network_lists') or my_network.network_lists is None:
3109
+ empty_df = pd.DataFrame(columns=['Node 1A', 'Node 1B', 'Edge 1C'])
3110
+ model = PandasModel(empty_df)
3111
+ self.network_table.setModel(model)
3112
+ else:
3113
+ model = PandasModel(my_network.network_lists)
3114
+ self.network_table.setModel(model)
3115
+ # Adjust column widths to content
3116
+ for column in range(model.columnCount(None)):
3117
+ self.network_table.resizeColumnToContents(column)
3118
+
3119
+ if hasattr(my_network, 'node_centroids') and my_network.node_centroids is not None:
3120
+ try:
3121
+ self.format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
3122
+ except Exception as e:
3123
+ print(f"Error loading node centroid table: {e}")
3124
+
3125
+ if hasattr(my_network, 'edge_centroids') and my_network.edge_centroids is not None:
3126
+ try:
3127
+ self.format_for_upperright_table(my_network.edge_centroids, 'EdgeID', ['Z', 'Y', 'X'], 'Edge Centroids')
3128
+ except Exception as e:
3129
+ print(f"Error loading edge centroid table: {e}")
3130
+
3131
+ if hasattr(my_network, 'node_identities') and my_network.node_identities is not None:
3132
+ try:
3133
+ self.format_for_upperright_table(my_network.node_identities, 'NodeID', 'Identity', 'Node Identities')
3134
+ except Exception as e:
3135
+ print(f"Error loading node identity table: {e}")
3136
+
3137
+
3138
+ if hasattr(my_network, 'communities') and my_network.communities is not None:
3139
+ try:
3140
+ self.format_for_upperright_table(my_network.communities, 'NodeID', 'Community', 'Node Communities')
3141
+ except Exception as e:
3142
+ print(f"Error loading node community table: {e}")
3111
3143
 
3112
3144
  except Exception as e:
3113
3145
  QMessageBox.critical(
@@ -3471,9 +3503,11 @@ class ImageViewerWindow(QMainWindow):
3471
3503
 
3472
3504
  if edges:
3473
3505
  self.delete_channel(1, False)
3474
-
3475
- if search_region:
3476
- my_network.search_region = None
3506
+ try:
3507
+ if search_region:
3508
+ my_network.search_region = None
3509
+ except:
3510
+ pass
3477
3511
 
3478
3512
  if network_overlay:
3479
3513
  self.delete_channel(2, False)
@@ -3483,8 +3517,7 @@ class ImageViewerWindow(QMainWindow):
3483
3517
 
3484
3518
 
3485
3519
 
3486
- def save_network_3d(self, asbool = True):
3487
-
3520
+ def save_network_3d(self, asbool=True):
3488
3521
  try:
3489
3522
  if asbool: # Save As
3490
3523
  # First let user select parent directory
@@ -3494,25 +3527,28 @@ class ImageViewerWindow(QMainWindow):
3494
3527
  "",
3495
3528
  QFileDialog.Option.ShowDirsOnly
3496
3529
  )
3497
-
3498
- if parent_dir: # If user didn't cancel
3499
- # Prompt user for new folder name
3500
- new_folder_name, ok = QInputDialog.getText(
3501
- self,
3502
- "New Folder",
3503
- "Enter name for new output folder:"
3504
- )
3530
+ if not parent_dir: # If user canceled the directory selection
3531
+ return # Exit the method early
3532
+
3533
+ # Prompt user for new folder name
3534
+ new_folder_name, ok = QInputDialog.getText(
3535
+ self,
3536
+ "New Folder",
3537
+ "Enter name for new output folder:"
3538
+ )
3505
3539
 
3540
+ # Check if user canceled the folder name dialog
3541
+ if not ok or not new_folder_name:
3542
+ return # Exit the method early
3543
+
3506
3544
  else: # Save
3507
3545
  parent_dir = None # Let the backend handle default save location
3508
3546
 
3509
3547
  # Call appropriate save method
3510
- if parent_dir is not None or not asbool: # Proceed if we have a filename OR if it's a regular save
3511
- if asbool:
3512
- my_network.dump(parent_dir = parent_dir, name = new_folder_name)
3513
- else:
3514
- my_network.dump(name = 'my_network')
3515
-
3548
+ if asbool:
3549
+ my_network.dump(parent_dir=parent_dir, name=new_folder_name)
3550
+ else:
3551
+ my_network.dump(name='my_network')
3516
3552
 
3517
3553
  except Exception as e:
3518
3554
  QMessageBox.critical(
@@ -3689,6 +3725,11 @@ class ImageViewerWindow(QMainWindow):
3689
3725
  else:
3690
3726
  # Regular channel processing with colormap
3691
3727
  # Calculate brightness/contrast limits from entire volume
3728
+ if self.min_max[channel][0] == None:
3729
+ self.min_max[channel][0] = np.min(channel)
3730
+ if self.min_max[channel][1] == None:
3731
+ self.min_max[channel][1] = np.max(channel)
3732
+
3692
3733
  img_min = self.min_max[channel][0]
3693
3734
  img_max = self.min_max[channel][1]
3694
3735
 
@@ -3699,6 +3740,9 @@ class ImageViewerWindow(QMainWindow):
3699
3740
  else:
3700
3741
  vmin = img_min + (img_max - img_min) * self.channel_brightness[channel]['min']
3701
3742
  vmax = img_min + (img_max - img_min) * self.channel_brightness[channel]['max']
3743
+
3744
+
3745
+
3702
3746
 
3703
3747
  # Normalize the image safely
3704
3748
  if vmin == vmax:
@@ -3827,7 +3871,8 @@ class ImageViewerWindow(QMainWindow):
3827
3871
  self.canvas.draw()
3828
3872
 
3829
3873
  except:
3830
- pass
3874
+ import traceback
3875
+ print(traceback.format_exc())
3831
3876
 
3832
3877
  def update_display_slice(self, channel, preserve_zoom=None):
3833
3878
  """Ultra minimal update that only changes the paint channel's data"""
@@ -4675,10 +4720,10 @@ class PropertiesDialog(QDialog):
4675
4720
  self.id_overlay.setChecked(self.check_checked(my_network.id_overlay))
4676
4721
  layout.addRow("Overlay 2 Status", self.id_overlay)
4677
4722
 
4678
- self.search_region = QPushButton("search region")
4679
- self.search_region.setCheckable(True)
4680
- self.search_region.setChecked(self.check_checked(my_network.search_region))
4681
- layout.addRow("Node Search Region Status", self.search_region)
4723
+ #self.search_region = QPushButton("search region")
4724
+ #self.search_region.setCheckable(True)
4725
+ #self.search_region.setChecked(self.check_checked(my_network.search_region))
4726
+ #layout.addRow("Node Search Region Status", self.search_region)
4682
4727
 
4683
4728
  self.network = QPushButton("Network")
4684
4729
  self.network.setCheckable(True)
@@ -4717,10 +4762,10 @@ class PropertiesDialog(QDialog):
4717
4762
  edges = not self.edges.isChecked()
4718
4763
  network_overlay = not self.network_overlay.isChecked()
4719
4764
  id_overlay = not self.id_overlay.isChecked()
4720
- search_region = not self.search_region.isChecked()
4765
+ #search_region = not self.search_region.isChecked()
4721
4766
  network = not self.network.isChecked()
4722
4767
 
4723
- self.parent().reset(nodes = nodes, edges = edges, network_overlay = network_overlay, id_overlay = id_overlay, search_region = search_region, network = network, xy_scale = xy_scale, z_scale = z_scale)
4768
+ self.parent().reset(nodes = nodes, edges = edges, network_overlay = network_overlay, id_overlay = id_overlay, network = network, xy_scale = xy_scale, z_scale = z_scale)
4724
4769
 
4725
4770
  self.accept()
4726
4771
 
@@ -5670,6 +5715,11 @@ class NeighborIdentityDialog(QDialog):
5670
5715
  self.search = QLineEdit("")
5671
5716
  layout.addRow("Search Radius (Ignore if using network):", self.search)
5672
5717
 
5718
+ self.fastdil = QPushButton("Fast Dilate")
5719
+ self.fastdil.setCheckable(True)
5720
+ self.fastdil.setChecked(False)
5721
+ layout.addRow("(If not using network) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
5722
+
5673
5723
  # Add Run button
5674
5724
  run_button = QPushButton("Get Neighborhood Identity Distribution")
5675
5725
  run_button.clicked.connect(self.neighborids)
@@ -5690,8 +5740,10 @@ class NeighborIdentityDialog(QDialog):
5690
5740
 
5691
5741
  search = float(self.search.text()) if self.search.text().strip() else 0
5692
5742
 
5743
+ fastdil = self.fastdil.isChecked()
5744
+
5693
5745
 
5694
- result, result2, title1, title2, densities = my_network.neighborhood_identities(root = root, directory = directory, mode = mode, search = search)
5746
+ result, result2, title1, title2, densities = my_network.neighborhood_identities(root = root, directory = directory, mode = mode, search = search, fastdil = fastdil)
5695
5747
 
5696
5748
  self.parent().format_for_upperright_table(result, 'Node Identity', 'Amount', title = title1)
5697
5749
  self.parent().format_for_upperright_table(result2, 'Node Identity', 'Proportion', title = title2)
@@ -5761,10 +5813,10 @@ class RadDialog(QDialog):
5761
5813
  layout = QFormLayout(self)
5762
5814
 
5763
5815
  # GPU checkbox (default False)
5764
- self.GPU = QPushButton("GPU")
5765
- self.GPU.setCheckable(True)
5766
- self.GPU.setChecked(False)
5767
- layout.addRow("Use GPU:", self.GPU)
5816
+ #self.GPU = QPushButton("GPU")
5817
+ #self.GPU.setCheckable(True)
5818
+ #self.GPU.setChecked(False)
5819
+ #layout.addRow("Use GPU:", self.GPU)
5768
5820
 
5769
5821
 
5770
5822
  # Add Run button
@@ -5775,17 +5827,18 @@ class RadDialog(QDialog):
5775
5827
  def rads(self):
5776
5828
 
5777
5829
  try:
5778
- GPU = self.GPU.isChecked()
5830
+ #GPU = self.GPU.isChecked() # <- I can never get these to be faster than parallel CPU *shrugs*
5779
5831
 
5780
5832
  active_data = self.parent().channel_data[self.parent().active_channel]
5781
5833
 
5782
- radii = n3d.estimate_object_radii(active_data, gpu=GPU)
5834
+ radii = n3d.estimate_object_radii(active_data, gpu=False, xy_scale = my_network.xy_scale, z_scale = my_network.z_scale)
5783
5835
 
5784
- for key, val in radii.items():
5785
-
5786
- radii[key] = [val, val * (my_network.xy_scale**2) * my_network.z_scale]
5836
+ if self.parent().active_channel == 0:
5837
+ self.parent().radii_dict[0] = radii
5838
+ elif self.parent().active_channel == 1:
5839
+ self.parent().radii_dict[1] = radii
5787
5840
 
5788
- self.parent().format_for_upperright_table(radii, title = '~Radii of Objects', metric='ObjectID', value=['Largest Radius (Voxels)', 'Largest Radius (Scaled)'])
5841
+ self.parent().format_for_upperright_table(radii, title = 'Largest Radii of Objects', metric='ObjectID', value='Largest Radius (Scaled)')
5789
5842
 
5790
5843
  self.accept()
5791
5844
 
@@ -5818,6 +5871,11 @@ class InteractionDialog(QDialog):
5818
5871
  self.mode_selector.setCurrentIndex(0) # Default to Mode 1
5819
5872
  layout.addRow("Execution Mode:", self.mode_selector)
5820
5873
 
5874
+ self.fastdil = QPushButton("Fast Dilate")
5875
+ self.fastdil.setCheckable(True)
5876
+ self.fastdil.setChecked(False)
5877
+ layout.addRow("(If not using network) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
5878
+
5821
5879
  # Add Run button
5822
5880
  run_button = QPushButton("Calculate")
5823
5881
  run_button.clicked.connect(self.interaction)
@@ -5833,8 +5891,11 @@ class InteractionDialog(QDialog):
5833
5891
  node_search = float(self.node_search.text()) if self.node_search.text() else 0
5834
5892
  except ValueError:
5835
5893
  node_search = 0
5894
+
5895
+
5896
+ fastdil = self.fastdil.isChecked()
5836
5897
 
5837
- result = my_network.interactions(search = node_search, cores = accepted_mode)
5898
+ result = my_network.interactions(search = node_search, cores = accepted_mode, fastdil = fastdil)
5838
5899
 
5839
5900
  self.parent().format_for_upperright_table(result, 'Node ID', ['Volume of Nearby Edge (Scaled)', 'Volume of Search Region'], title = 'Node/Edge Interactions')
5840
5901
 
@@ -5842,6 +5903,9 @@ class InteractionDialog(QDialog):
5842
5903
 
5843
5904
  except Exception as e:
5844
5905
 
5906
+ import traceback
5907
+ print(traceback.format_exc())
5908
+
5845
5909
  print(f"Error finding interactions: {e}")
5846
5910
 
5847
5911
 
@@ -6198,6 +6262,15 @@ class ResizeDialog(QDialog):
6198
6262
  undo_button.clicked.connect(lambda: self.run_resize(undo = True))
6199
6263
  layout.addRow(undo_button)
6200
6264
 
6265
+ if my_network.xy_scale != my_network.z_scale:
6266
+ norm_button_upsize = QPushButton(f"Normalize Scaling with Upsample")
6267
+ norm_button_upsize.clicked.connect(lambda: self.run_resize(upsize = True, special = True))
6268
+ layout.addRow(norm_button_upsize)
6269
+
6270
+ norm_button_downsize = QPushButton("Normalize Scaling with Downsample")
6271
+ norm_button_downsize.clicked.connect(lambda: self.run_resize(upsize = False, special = True))
6272
+ layout.addRow(norm_button_downsize)
6273
+
6201
6274
  run_button = QPushButton("Run Resize")
6202
6275
  run_button.clicked.connect(self.run_resize)
6203
6276
  layout.addRow(run_button)
@@ -6209,7 +6282,7 @@ class ResizeDialog(QDialog):
6209
6282
  self.xsize.setText("1")
6210
6283
  self.ysize.setText("1")
6211
6284
 
6212
- def run_resize(self, undo = False):
6285
+ def run_resize(self, undo = False, upsize = True, special = False):
6213
6286
  try:
6214
6287
  self.parent().resizing = True
6215
6288
  # Get parameters
@@ -6224,7 +6297,27 @@ class ResizeDialog(QDialog):
6224
6297
  return
6225
6298
 
6226
6299
  resize = resize if resize is not None else (zsize, ysize, xsize)
6227
-
6300
+
6301
+ if special:
6302
+ if upsize:
6303
+ if (my_network.z_scale > my_network.xy_scale):
6304
+ # Z dimension needs to be stretched
6305
+ resize = [my_network.z_scale/my_network.xy_scale, 1, 1] # Scale factor for [z, y, x]
6306
+ cardinal = my_network.xy_scale
6307
+ elif (my_network.xy_scale > my_network.z_scale):
6308
+ # XY dimensions need to be stretched
6309
+ resize = [1, my_network.xy_scale/my_network.z_scale, my_network.xy_scale/my_network.z_scale] # Scale factor for [z, y, x]
6310
+ cardinal = my_network.z_scale
6311
+ else:
6312
+ if (my_network.z_scale > my_network.xy_scale):
6313
+ # XY dimension needs to be shrunk
6314
+ resize = [1, my_network.xy_scale/my_network.z_scale, my_network.xy_scale/my_network.z_scale] # Scale factor for [z, y, x]
6315
+ cardinal = my_network.z_scale
6316
+ elif (my_network.xy_scale > my_network.z_scale):
6317
+ # Z dimensions need to be shrunk
6318
+ resize = [my_network.z_scale/my_network.xy_scale, 1, 1] # Scale factor for [z, y, x]
6319
+ cardinal = my_network.xy_scale
6320
+
6228
6321
  # Get the shape from whichever array exists
6229
6322
  array_shape = None
6230
6323
  if my_network.nodes is not None:
@@ -6306,14 +6399,18 @@ class ResizeDialog(QDialog):
6306
6399
  self.parent().slice_slider.setMaximum(channel.shape[0] - 1)
6307
6400
  break
6308
6401
 
6309
- if isinstance(resize, (int, float)):
6310
- my_network.xy_scale = my_network.xy_scale/resize
6311
- my_network.z_scale = my_network.z_scale/resize
6312
- print("xy_scales and z_scales have been adjusted per resample. Check image -> properties to manually reset them to 1 if desired.")
6402
+ if not special:
6403
+ if isinstance(resize, (int, float)):
6404
+ my_network.xy_scale = my_network.xy_scale/resize
6405
+ my_network.z_scale = my_network.z_scale/resize
6406
+ print("xy_scales and z_scales have been adjusted per resample. Check image -> properties to manually reset them to 1 if desired.")
6407
+ else:
6408
+ my_network.xy_scale = my_network.xy_scale/resize[1]
6409
+ my_network.z_scale = my_network.z_scale/resize[0]
6410
+ print("xy_scales and z_scales have been adjusted per resample. Check image -> properties to manually reset them to 1 if desired. Note that xy_scale will not correspond if you made your XY plane a non-square.")
6313
6411
  else:
6314
- my_network.xy_scale = my_network.xy_scale/resize[1]
6315
- my_network.z_scale = my_network.z_scale/resize[0]
6316
- print("xy_scales and z_scales have been adjusted per resample. Check image -> properties to manually reset them to 1 if desired. Note that xy_scale will not correspond if you made your XY plane a non-square.")
6412
+ my_network.xy_scale = cardinal
6413
+ my_network.z_scale = cardinal
6317
6414
 
6318
6415
  try:
6319
6416
  if my_network.node_centroids is not None:
@@ -6587,9 +6684,14 @@ class ThresholdDialog(QDialog):
6587
6684
 
6588
6685
  # Add ML button
6589
6686
  ML = QPushButton("Machine Learning")
6590
- ML.clicked.connect(self.start_ml)
6687
+ ML.clicked.connect(lambda: self.start_ml(GPU = False))
6591
6688
  layout.addRow(ML)
6592
6689
 
6690
+ # Add ML button
6691
+ #ML2 = QPushButton("Machine Learning (GPU)")
6692
+ #ML2.clicked.connect(lambda: self.start_ml(GPU = True))
6693
+ #layout.addRow(ML2)
6694
+
6593
6695
 
6594
6696
  def thresh_mode(self):
6595
6697
 
@@ -6614,7 +6716,7 @@ class ThresholdDialog(QDialog):
6614
6716
  except:
6615
6717
  pass
6616
6718
 
6617
- def start_ml(self):
6719
+ def start_ml(self, GPU = False):
6618
6720
 
6619
6721
 
6620
6722
  if self.parent().channel_data[2] is not None or self.parent().channel_data[3] is not None or self.parent().highlight_overlay is not None:
@@ -6630,10 +6732,16 @@ class ThresholdDialog(QDialog):
6630
6732
  )
6631
6733
  return
6632
6734
 
6735
+ try:
6736
+ import cupy as cp
6737
+ except:
6738
+ print("Cupy import failed, using CPU version")
6739
+ GPU = False
6740
+
6633
6741
  if self.parent().mini_overlay_data is not None:
6634
6742
  self.parent().mini_overlay_data = None
6635
6743
 
6636
- self.parent().machine_window = MachineWindow(self.parent())
6744
+ self.parent().machine_window = MachineWindow(self.parent(), GPU = GPU)
6637
6745
  self.parent().machine_window.show() # Non-modal window
6638
6746
  self.accept()
6639
6747
 
@@ -6650,7 +6758,7 @@ class ThresholdDialog(QDialog):
6650
6758
 
6651
6759
  class MachineWindow(QMainWindow):
6652
6760
 
6653
- def __init__(self, parent=None):
6761
+ def __init__(self, parent=None, GPU = False):
6654
6762
  super().__init__(parent)
6655
6763
 
6656
6764
  self.setWindowTitle("Threshold")
@@ -6743,11 +6851,8 @@ class MachineWindow(QMainWindow):
6743
6851
  # Group 2: Processing Options (GPU)
6744
6852
  processing_group = QGroupBox("Processing Options")
6745
6853
  processing_layout = QHBoxLayout()
6746
- self.GPU = QPushButton("GPU (Beta)")
6747
- self.GPU.setCheckable(True)
6748
- self.GPU.setChecked(False)
6749
- self.GPU.clicked.connect(self.toggle_GPU)
6750
- self.use_gpu = False
6854
+
6855
+ self.use_gpu = GPU
6751
6856
  self.two = QPushButton("Train By 2D Slice Patterns")
6752
6857
  self.two.setCheckable(True)
6753
6858
  self.two.setChecked(False)
@@ -6796,7 +6901,8 @@ class MachineWindow(QMainWindow):
6796
6901
 
6797
6902
  # Add all groups to main layout
6798
6903
  main_layout.addWidget(drawing_group)
6799
- main_layout.addWidget(processing_group)
6904
+ if not GPU:
6905
+ main_layout.addWidget(processing_group)
6800
6906
  main_layout.addWidget(training_group)
6801
6907
  main_layout.addWidget(segmentation_group)
6802
6908
 
@@ -6806,10 +6912,16 @@ class MachineWindow(QMainWindow):
6806
6912
  self.trained = False
6807
6913
  self.previewing = False
6808
6914
 
6915
+ if not GPU:
6916
+ self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=False)
6917
+ else:
6918
+ self.segmenter = segmenter_GPU.InteractiveSegmenter(active_data)
6809
6919
 
6810
- self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=True)
6811
6920
  self.segmentation_worker = None
6812
6921
 
6922
+ self.fore_button.click()
6923
+ self.fore_button.click()
6924
+
6813
6925
  def toggle_lock(self):
6814
6926
 
6815
6927
  self.mem_lock = self.lock_button.isChecked()
@@ -6835,12 +6947,6 @@ class MachineWindow(QMainWindow):
6835
6947
  pass
6836
6948
 
6837
6949
 
6838
-
6839
- def toggle_GPU(self):
6840
-
6841
-
6842
- self.use_gpu = self.GPU.isChecked()
6843
-
6844
6950
  def toggle_two(self):
6845
6951
  if self.two.isChecked():
6846
6952
  # If button two is checked, ensure button three is unchecked
@@ -6918,6 +7024,8 @@ class MachineWindow(QMainWindow):
6918
7024
  self.trained = True
6919
7025
  except Exception as e:
6920
7026
  print("Error training. Perhaps you forgot both foreground and background markers? I need both!")
7027
+ import traceback
7028
+ traceback.print_exc()
6921
7029
  except MemoryError:
6922
7030
  QMessageBox.critical(
6923
7031
  self,
@@ -7580,6 +7688,12 @@ class SmartDilateDialog(QDialog):
7580
7688
  self.GPU.setChecked(False)
7581
7689
  layout.addRow("Use GPU:", self.GPU)
7582
7690
 
7691
+ # dt checkbox (default False)
7692
+ self.predt = QPushButton("Pre-DT")
7693
+ self.predt.setCheckable(True)
7694
+ self.predt.setChecked(False)
7695
+ layout.addRow("Use Distance Transform for Predilation (Better at Large Dilations):", self.predt)
7696
+
7583
7697
  self.down_factor = QLineEdit("")
7584
7698
  layout.addRow("Internal Downsample for GPU (if needed):", self.down_factor)
7585
7699
 
@@ -7594,11 +7708,12 @@ class SmartDilateDialog(QDialog):
7594
7708
 
7595
7709
  GPU = self.GPU.isChecked()
7596
7710
  down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
7711
+ predt = not self.predt.isChecked()
7597
7712
  active_data, amount, xy_scale, z_scale = self.params
7598
7713
 
7599
7714
  dilate_xy, dilate_z = n3d.dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
7600
7715
 
7601
- result = sdl.smart_dilate(active_data, dilate_xy, dilate_z, GPU = GPU, predownsample = down_factor)
7716
+ result = sdl.smart_dilate(active_data, dilate_xy, dilate_z, GPU = GPU, predownsample = down_factor, fast_dil = predt, use_dt_dil_amount = amount, xy_scale = xy_scale, z_scale = z_scale)
7602
7717
 
7603
7718
  self.parent().load_channel(self.parent().active_channel, result, True)
7604
7719
  self.accept()
@@ -7634,7 +7749,7 @@ class DilateDialog(QDialog):
7634
7749
 
7635
7750
  # Add mode selection dropdown
7636
7751
  self.mode_selector = QComboBox()
7637
- self.mode_selector.addItems(["Binary Dilation", "Preserve Labels (slower)", "Recursive Binary Dilation (Use if the dilation radius is much larger than your objects)"])
7752
+ self.mode_selector.addItems(["Pseudo3D Binary Kernels (For Fast, small dilations)", "Preserve Labels (slower)", "Distance Transform-Based (Slower but more accurate at larger dilations)"])
7638
7753
  self.mode_selector.setCurrentIndex(0) # Default to Mode 1
7639
7754
  layout.addRow("Execution Mode:", self.mode_selector)
7640
7755
 
@@ -7684,18 +7799,17 @@ class DilateDialog(QDialog):
7684
7799
  return
7685
7800
 
7686
7801
  if accepted_mode == 2:
7687
- recursive = True
7802
+ result = n3d.dilate_3D_dt(active_data, amount, xy_scaling = xy_scale, z_scaling = z_scale)
7688
7803
  else:
7689
- recursive = False
7690
7804
 
7691
- # Call dilate method with parameters
7692
- result = n3d.dilate(
7693
- active_data,
7694
- amount,
7695
- xy_scale = xy_scale,
7696
- z_scale = z_scale,
7697
- recursive = recursive
7698
- )
7805
+ # Call dilate method with parameters
7806
+ result = n3d.dilate(
7807
+ active_data,
7808
+ amount,
7809
+ xy_scale = xy_scale,
7810
+ z_scale = z_scale)
7811
+
7812
+ result = result * 255
7699
7813
 
7700
7814
  # Update both the display data and the network object
7701
7815
  self.parent().load_channel(self.parent().active_channel, result, True)
@@ -7739,6 +7853,12 @@ class ErodeDialog(QDialog):
7739
7853
  self.z_scale = QLineEdit(z_scale)
7740
7854
  layout.addRow("z_scale:", self.z_scale)
7741
7855
 
7856
+ # Add mode selection dropdown
7857
+ self.mode_selector = QComboBox()
7858
+ self.mode_selector.addItems(["Pseudo3D Binary Kernels (For Fast, small erosions)", "Distance Transform-Based (Slower but more accurate at larger dilations)"])
7859
+ self.mode_selector.setCurrentIndex(0) # Default to Mode 1
7860
+ layout.addRow("Execution Mode:", self.mode_selector)
7861
+
7742
7862
  # Add Run button
7743
7863
  run_button = QPushButton("Run Erode")
7744
7864
  run_button.clicked.connect(self.run_erode)
@@ -7769,6 +7889,8 @@ class ErodeDialog(QDialog):
7769
7889
  z_scale = float(self.z_scale.text()) if self.z_scale.text() else 1
7770
7890
  except ValueError:
7771
7891
  z_scale = 1
7892
+
7893
+ mode = self.mode_selector.currentIndex()
7772
7894
 
7773
7895
  # Get the active channel data from parent
7774
7896
  active_data = self.parent().channel_data[self.parent().active_channel]
@@ -7781,6 +7903,7 @@ class ErodeDialog(QDialog):
7781
7903
  amount,
7782
7904
  xy_scale = xy_scale,
7783
7905
  z_scale = z_scale,
7906
+ mode = mode
7784
7907
  )
7785
7908
 
7786
7909
 
@@ -8182,8 +8305,7 @@ class WatershedDialog(QDialog):
8182
8305
  self.accept()
8183
8306
 
8184
8307
  except Exception as e:
8185
- import traceback
8186
- print(traceback.format_exc())
8308
+
8187
8309
  QMessageBox.critical(
8188
8310
  self,
8189
8311
  "Error",
@@ -8343,20 +8465,9 @@ class GenNodesDialog(QDialog):
8343
8465
  layout = QFormLayout(self)
8344
8466
  self.called = called
8345
8467
 
8346
- self.branch_removal = QLineEdit("0")
8347
- layout.addRow("Skeleton Voxel Branch Length to Remove (int) (Compensates for spines off medial axis):", self.branch_removal)
8348
-
8349
- self.comp_dil = QLineEdit("0")
8350
- layout.addRow("Voxel distance to merge nearby nodes (Int - compensates for multi-branch identification along thick branch regions):", self.comp_dil)
8351
-
8352
- self.max_vol = QLineEdit("0")
8353
- layout.addRow("Maximum Voxel Volume of Vertices to Retain (int - Compensates for skeleton looping - occurs before any node merging - the smallest objects are always 27 voxels):", self.max_vol)
8354
-
8355
- # auto checkbox (default True)
8356
- self.auto = QPushButton("Auto")
8357
- self.auto.setCheckable(True)
8358
- self.auto.setChecked(False)
8359
- layout.addRow("Attempt to Auto Correct Skeleton Looping:", self.auto)
8468
+ self.directory = QLineEdit()
8469
+ self.directory.setPlaceholderText("Leave empty to save in active dir")
8470
+ layout.addRow("Output Directory:", self.directory)
8360
8471
 
8361
8472
  if not down_factor:
8362
8473
  down_factor = None
@@ -8371,16 +8482,33 @@ class GenNodesDialog(QDialog):
8371
8482
  self.down_factor = down_factor[0]
8372
8483
  self.cubic = down_factor[1]
8373
8484
 
8374
- self.directory = QLineEdit()
8375
- self.directory.setPlaceholderText("Leave empty to save in active dir")
8376
- layout.addRow("Output Directory:", self.directory)
8485
+ self.branch_removal = QLineEdit("0")
8486
+ layout.addRow("Skeleton Voxel Branch Length to Remove (int) (Compensates for spines off medial axis):", self.branch_removal)
8487
+
8488
+ self.max_vol = QLineEdit("0")
8489
+ layout.addRow("Maximum Voxel Volume of Vertices to Retain (int - Compensates for skeleton looping - occurs before any node merging - the smallest objects are always 27 voxels):", self.max_vol)
8490
+
8491
+ self.comp_dil = QLineEdit("0")
8492
+ layout.addRow("Voxel distance to merge nearby nodes (Int - compensates for multi-branch identification along thick branch regions):", self.comp_dil)
8493
+
8494
+ self.fast_dil = QPushButton("Fast-Dil")
8495
+ self.fast_dil.setCheckable(True)
8496
+ self.fast_dil.setChecked(True)
8497
+ layout.addRow("(If using above) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fast_dil)
8498
+
8499
+ # auto checkbox (default True)
8500
+ self.auto = QPushButton("Auto")
8501
+ self.auto.setCheckable(True)
8502
+ self.auto.setChecked(True)
8503
+ layout.addRow("Attempt to Auto Correct Skeleton Looping:", self.auto)
8504
+
8377
8505
 
8378
8506
  # retain checkbox (default True)
8379
8507
  if not called:
8380
8508
  self.retain = QPushButton("Retain")
8381
8509
  self.retain.setCheckable(True)
8382
8510
  self.retain.setChecked(True)
8383
- layout.addRow("Retain Original Edges? (Will be moved to overlay 2):", self.retain)
8511
+ #layout.addRow("Retain Original Edges? (Will be moved to overlay 2):", self.retain)
8384
8512
  else:
8385
8513
  self.retain = False
8386
8514
 
@@ -8436,6 +8564,8 @@ class GenNodesDialog(QDialog):
8436
8564
 
8437
8565
  auto = self.auto.isChecked()
8438
8566
 
8567
+ fastdil = self.fast_dil.isChecked()
8568
+
8439
8569
 
8440
8570
  if auto:
8441
8571
  my_network.edges = n3d.skeletonize(my_network.edges)
@@ -8449,7 +8579,8 @@ class GenNodesDialog(QDialog):
8449
8579
  comp_dil=comp_dil,
8450
8580
  down_factor=down_factor,
8451
8581
  order = order,
8452
- return_skele = True
8582
+ return_skele = True,
8583
+ fastdil = fastdil
8453
8584
 
8454
8585
  )
8455
8586
 
@@ -8476,7 +8607,7 @@ class GenNodesDialog(QDialog):
8476
8607
 
8477
8608
  self.parent().load_channel(0, channel_data = result, data = True)
8478
8609
 
8479
- if retain:
8610
+ if retain and self.called:
8480
8611
  self.parent().load_channel(3, channel_data = my_network.edges, data = True)
8481
8612
 
8482
8613
 
@@ -8487,6 +8618,9 @@ class GenNodesDialog(QDialog):
8487
8618
 
8488
8619
  except Exception as e:
8489
8620
 
8621
+ import traceback
8622
+ print(traceback.format_exc())
8623
+
8490
8624
 
8491
8625
  QMessageBox.critical(
8492
8626
  self,
@@ -8581,7 +8715,7 @@ class BranchDialog(QDialog):
8581
8715
 
8582
8716
  temp_network = n3d.Network_3D(nodes = output)
8583
8717
 
8584
- temp_network.morph_proximity(search = 1) #Detect network of nearby branches
8718
+ temp_network.morph_proximity(search = [3,3], fastdil = True) #Detect network of nearby branches
8585
8719
 
8586
8720
  temp_network.community_partition(weighted = False, style = 1, dostats = False) #Find communities with louvain, unweighted params
8587
8721
 
@@ -9013,8 +9147,6 @@ class CentroidDialog(QDialog):
9013
9147
  class CalcAllDialog(QDialog):
9014
9148
  # Class variables to store previous settings
9015
9149
  prev_directory = ""
9016
- prev_xy_scale = "1"
9017
- prev_z_scale = "1"
9018
9150
  prev_search = ""
9019
9151
  prev_diledge = ""
9020
9152
  prev_down_factor = ""
@@ -9024,7 +9156,7 @@ class CalcAllDialog(QDialog):
9024
9156
  prev_gpu = True
9025
9157
  prev_label_nodes = True
9026
9158
  prev_inners = True
9027
- prev_skeletonize = False
9159
+ prev_fastdil = False
9028
9160
  prev_overlays = False
9029
9161
  prev_updates = True
9030
9162
 
@@ -9041,10 +9173,10 @@ class CalcAllDialog(QDialog):
9041
9173
  layout.addRow("Output Directory:", self.directory)
9042
9174
 
9043
9175
  # Load previous values for all inputs
9044
- self.xy_scale = QLineEdit(self.prev_xy_scale)
9176
+ self.xy_scale = QLineEdit(f'{my_network.xy_scale}')
9045
9177
  layout.addRow("xy_scale:", self.xy_scale)
9046
9178
 
9047
- self.z_scale = QLineEdit(self.prev_z_scale)
9179
+ self.z_scale = QLineEdit(f'{my_network.z_scale}')
9048
9180
  layout.addRow("z_scale:", self.z_scale)
9049
9181
 
9050
9182
  self.search = QLineEdit(self.prev_search)
@@ -9087,10 +9219,10 @@ class CalcAllDialog(QDialog):
9087
9219
  self.inners.setChecked(self.prev_inners)
9088
9220
  layout.addRow("Use Inner Edges:", self.inners)
9089
9221
 
9090
- self.skeletonize = QPushButton("Skeletonize")
9091
- self.skeletonize.setCheckable(True)
9092
- self.skeletonize.setChecked(self.prev_skeletonize)
9093
- layout.addRow("Skeletonize Edges:", self.skeletonize)
9222
+ self.fastdil = QPushButton("Fast Dilate")
9223
+ self.fastdil.setCheckable(True)
9224
+ self.fastdil.setChecked(self.prev_fastdil)
9225
+ layout.addRow("Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
9094
9226
 
9095
9227
  self.overlays = QPushButton("Overlays")
9096
9228
  self.overlays.setCheckable(True)
@@ -9161,7 +9293,7 @@ class CalcAllDialog(QDialog):
9161
9293
  gpu = self.gpu.isChecked()
9162
9294
  label_nodes = self.label_nodes.isChecked()
9163
9295
  inners = self.inners.isChecked()
9164
- skeletonize = self.skeletonize.isChecked()
9296
+ fastdil = self.fastdil.isChecked()
9165
9297
  overlays = self.overlays.isChecked()
9166
9298
  update = self.update.isChecked()
9167
9299
 
@@ -9184,13 +9316,11 @@ class CalcAllDialog(QDialog):
9184
9316
  GPU=gpu,
9185
9317
  label_nodes=label_nodes,
9186
9318
  inners=inners,
9187
- skeletonize=skeletonize
9319
+ fast_dil=fastdil
9188
9320
  )
9189
9321
 
9190
9322
  # Store current values as previous values
9191
9323
  CalcAllDialog.prev_directory = self.directory.text()
9192
- CalcAllDialog.prev_xy_scale = self.xy_scale.text()
9193
- CalcAllDialog.prev_z_scale = self.z_scale.text()
9194
9324
  CalcAllDialog.prev_search = self.search.text()
9195
9325
  CalcAllDialog.prev_diledge = self.diledge.text()
9196
9326
  CalcAllDialog.prev_down_factor = self.down_factor.text()
@@ -9200,22 +9330,22 @@ class CalcAllDialog(QDialog):
9200
9330
  CalcAllDialog.prev_gpu = self.gpu.isChecked()
9201
9331
  CalcAllDialog.prev_label_nodes = self.label_nodes.isChecked()
9202
9332
  CalcAllDialog.prev_inners = self.inners.isChecked()
9203
- CalcAllDialog.prev_skeletonize = self.skeletonize.isChecked()
9333
+ CalcAllDialog.prev_fastdil = self.fastdil.isChecked()
9204
9334
  CalcAllDialog.prev_overlays = self.overlays.isChecked()
9205
9335
  CalcAllDialog.prev_updates = self.update.isChecked()
9206
9336
 
9207
9337
 
9208
9338
  # Update both the display data and the network object
9209
9339
  if update:
9210
- self.parent().channel_data[0] = my_network.nodes
9211
- self.parent().channel_data[1] = my_network.edges
9340
+ self.parent().load_channel(0, my_network.nodes, True)
9341
+ self.parent().load_channel(1, my_network.edges, True)
9212
9342
  else:
9213
9343
  my_network.nodes = temp_nodes.copy()
9214
9344
  del temp_nodes
9215
9345
  my_network.edges = temp_edges.copy()
9216
9346
  del temp_edges
9217
- self.parent().channel_data[0] = my_network.nodes
9218
- self.parent().channel_data[1] = my_network.edges
9347
+ self.parent().load_channel(0, my_network.nodes, True)
9348
+ self.parent().load_channel(1, my_network.edges, True)
9219
9349
 
9220
9350
 
9221
9351
  # Then handle overlays
@@ -9228,8 +9358,8 @@ class CalcAllDialog(QDialog):
9228
9358
  my_network.id_overlay = my_network.draw_node_indices(directory=directory)
9229
9359
 
9230
9360
  # Update channel data
9231
- self.parent().channel_data[2] = my_network.network_overlay
9232
- self.parent().channel_data[3] = my_network.id_overlay
9361
+ self.parent().load_channel(2, my_network.network_overlay, True)
9362
+ self.parent().load_channel(3, my_network.id_overlay, True)
9233
9363
 
9234
9364
  # Enable the overlay channel buttons
9235
9365
  self.parent().channel_buttons[2].setEnabled(True)
@@ -9315,6 +9445,11 @@ class ProxDialog(QDialog):
9315
9445
  self.mode_selector.setCurrentIndex(0) # Default to Mode 1
9316
9446
  layout.addRow("Execution Mode:", self.mode_selector)
9317
9447
 
9448
+ self.fastdil = QPushButton("Fast Dilate")
9449
+ self.fastdil.setCheckable(True)
9450
+ self.fastdil.setChecked(False)
9451
+ layout.addRow("(If using morphological) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
9452
+
9318
9453
  if my_network.node_identities is not None:
9319
9454
  self.id_selector = QComboBox()
9320
9455
  # Add all options from id dictionary
@@ -9378,7 +9513,8 @@ class ProxDialog(QDialog):
9378
9513
  except ValueError:
9379
9514
  search = None
9380
9515
 
9381
- overlays = self.overlays.isChecked()
9516
+ overlays = self.overlays.isChecked()
9517
+ fastdil = self.fastdil.isChecked()
9382
9518
 
9383
9519
  my_network.xy_scale = xy_scale
9384
9520
  my_network.z_scale = z_scale
@@ -9389,7 +9525,7 @@ class ProxDialog(QDialog):
9389
9525
  my_network.nodes, _ = n3d.label_objects(my_network.nodes)
9390
9526
  if my_network.node_centroids is None:
9391
9527
  self.parent().show_centroid_dialog()
9392
- my_network.morph_proximity(search = search, targets = targets)
9528
+ my_network.morph_proximity(search = search, targets = targets, fastdil = fastdil)
9393
9529
 
9394
9530
  self.parent().load_channel(0, channel_data = my_network.nodes, data = True)
9395
9531
  elif mode == 0: