nettracer3d 0.6.8__py3-none-any.whl → 0.7.0__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,7 +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
+ #from nettracer3d import segmenter_GPU <--- couldn't get this faster than CPU ¯\_(ツ)_/¯
29
29
 
30
30
 
31
31
 
@@ -818,6 +818,8 @@ class ImageViewerWindow(QMainWindow):
818
818
  link_nodes.triggered.connect(self.handle_link)
819
819
  delink_nodes = highlight_menu.addAction("Split Nodes")
820
820
  delink_nodes.triggered.connect(self.handle_split)
821
+ override_obj = highlight_menu.addAction("Override Channel with Selection")
822
+ override_obj.triggered.connect(self.handle_override)
821
823
  context_menu.addMenu(highlight_menu)
822
824
 
823
825
  # Create measure menu
@@ -1244,7 +1246,7 @@ class ImageViewerWindow(QMainWindow):
1244
1246
  except:
1245
1247
  pass
1246
1248
 
1247
- print(f"Found {len(filtered_df)} direct connections between nodes of ID {sort} and their neighbors (of any ID)")
1249
+ #print(f"Found {len(filtered_df)} direct connections between nodes of ID {sort} and their neighbors (of any ID)")
1248
1250
 
1249
1251
  if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
1250
1252
  self.mini_overlay = True
@@ -1662,7 +1664,9 @@ class ImageViewerWindow(QMainWindow):
1662
1664
  print(f"An error has occurred: {e}")
1663
1665
 
1664
1666
 
1665
-
1667
+ def handle_override(self):
1668
+ dialog = OverrideDialog(self)
1669
+ dialog.exec()
1666
1670
 
1667
1671
 
1668
1672
 
@@ -1970,6 +1974,28 @@ class ImageViewerWindow(QMainWindow):
1970
1974
  points.append((x, y))
1971
1975
  return points
1972
1976
 
1977
+ def get_current_mouse_position(self):
1978
+ # Get the main application's current mouse position
1979
+ cursor_pos = QCursor.pos()
1980
+
1981
+ # Convert global screen coordinates to canvas widget coordinates
1982
+ canvas_pos = self.canvas.mapFromGlobal(cursor_pos)
1983
+
1984
+ # Check if the position is within the canvas bounds
1985
+ if not (0 <= canvas_pos.x() < self.canvas.width() and
1986
+ 0 <= canvas_pos.y() < self.canvas.height()):
1987
+ return 0, 0 # Mouse is outside of the matplotlib canvas
1988
+
1989
+ # Convert from canvas widget coordinates to matplotlib data coordinates
1990
+ x = canvas_pos.x()
1991
+ y = canvas_pos.y()
1992
+
1993
+ # Transform display coordinates to data coordinates
1994
+ inv = self.ax.transData.inverted()
1995
+ data_coords = inv.transform((x, y))
1996
+
1997
+ return data_coords[0], data_coords[1]
1998
+
1973
1999
  def on_mouse_press(self, event):
1974
2000
  """Handle mouse press events."""
1975
2001
  if event.inaxes != self.ax:
@@ -2661,8 +2687,8 @@ class ImageViewerWindow(QMainWindow):
2661
2687
  degree_dist_action.triggered.connect(self.show_degree_dist_dialog)
2662
2688
  neighbor_id_action = stats_menu.addAction("Identity Distribution of Neighbors")
2663
2689
  neighbor_id_action.triggered.connect(self.show_neighbor_id_dialog)
2664
- random_action = stats_menu.addAction("Generate Equivalent Random Network")
2665
- random_action.triggered.connect(self.show_random_dialog)
2690
+ ripley_action = stats_menu.addAction("Clustering Analysis")
2691
+ ripley_action.triggered.connect(self.show_ripley_dialog)
2666
2692
  vol_action = stats_menu.addAction("Calculate Volumes")
2667
2693
  vol_action.triggered.connect(self.volumes)
2668
2694
  rad_action = stats_menu.addAction("Calculate Radii")
@@ -2681,6 +2707,13 @@ class ImageViewerWindow(QMainWindow):
2681
2707
  id_code_action = overlay_menu.addAction("Code Identities")
2682
2708
  id_code_action.triggered.connect(lambda: self.show_code_dialog(sort = 'Identity'))
2683
2709
 
2710
+ rand_menu = analysis_menu.addMenu("Randomize")
2711
+ random_action = rand_menu.addAction("Generate Equivalent Random Network")
2712
+ random_action.triggered.connect(self.show_random_dialog)
2713
+ random_nodes = rand_menu.addAction("Scramble Nodes (Centroids)")
2714
+ random_nodes.triggered.connect(self.show_randnode_dialog)
2715
+
2716
+
2684
2717
 
2685
2718
  # Process menu
2686
2719
  process_menu = menubar.addMenu("Process")
@@ -2958,8 +2991,11 @@ class ImageViewerWindow(QMainWindow):
2958
2991
 
2959
2992
  def show_type_dialog(self):
2960
2993
  """Show the type dialog"""
2961
- dialog = TypeDialog(self)
2962
- dialog.exec()
2994
+ try:
2995
+ dialog = TypeDialog(self)
2996
+ dialog.exec()
2997
+ except:
2998
+ pass
2963
2999
 
2964
3000
  def show_skeletonize_dialog(self):
2965
3001
  """show the skeletonize dialog"""
@@ -3472,8 +3508,8 @@ class ImageViewerWindow(QMainWindow):
3472
3508
  nii_img = nib.load(filename)
3473
3509
  # Get data and transpose to match TIFF orientation
3474
3510
  # If X needs to become Z, we move axis 2 (X) to position 0 (Z)
3475
- data = nii_img.get_fdata()
3476
- self.channel_data[channel_index] = np.transpose(data, (2, 1, 0))
3511
+ arraydata = nii_img.get_fdata()
3512
+ self.channel_data[channel_index] = np.transpose(arraydata, (2, 1, 0))
3477
3513
 
3478
3514
  elif file_extension in ['jpg', 'jpeg', 'png']:
3479
3515
  from PIL import Image
@@ -4128,10 +4164,18 @@ class ImageViewerWindow(QMainWindow):
4128
4164
  dialog = NeighborIdentityDialog(self)
4129
4165
  dialog.exec()
4130
4166
 
4167
+ def show_ripley_dialog(self):
4168
+ dialog = RipleyDialog(self)
4169
+ dialog.exec()
4170
+
4131
4171
  def show_random_dialog(self):
4132
4172
  dialog = RandomDialog(self)
4133
4173
  dialog.exec()
4134
4174
 
4175
+ def show_randnode_dialog(self):
4176
+ dialog = RandNodeDialog(self)
4177
+ dialog.exec()
4178
+
4135
4179
  def show_rad_dialog(self):
4136
4180
  dialog = RadDialog(self)
4137
4181
  dialog.exec()
@@ -4908,10 +4952,10 @@ class PropertiesDialog(QDialog):
4908
4952
  self.id_overlay.setChecked(self.check_checked(my_network.id_overlay))
4909
4953
  layout.addRow("Overlay 2 Status", self.id_overlay)
4910
4954
 
4911
- #self.search_region = QPushButton("search region")
4912
- #self.search_region.setCheckable(True)
4913
- #self.search_region.setChecked(self.check_checked(my_network.search_region))
4914
- #layout.addRow("Node Search Region Status", self.search_region)
4955
+ self.search_region = QPushButton("search region")
4956
+ self.search_region.setCheckable(True)
4957
+ self.search_region.setChecked(self.check_checked(my_network.search_region))
4958
+ layout.addRow("Node Search Region Status", self.search_region)
4915
4959
 
4916
4960
  self.network = QPushButton("Network")
4917
4961
  self.network.setCheckable(True)
@@ -4950,10 +4994,10 @@ class PropertiesDialog(QDialog):
4950
4994
  edges = not self.edges.isChecked()
4951
4995
  network_overlay = not self.network_overlay.isChecked()
4952
4996
  id_overlay = not self.id_overlay.isChecked()
4953
- #search_region = not self.search_region.isChecked()
4997
+ search_region = not self.search_region.isChecked()
4954
4998
  network = not self.network.isChecked()
4955
4999
 
4956
- 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)
5000
+ self.parent().reset(nodes = nodes, edges = edges, search_region = search_region, network_overlay = network_overlay, id_overlay = id_overlay, network = network, xy_scale = xy_scale, z_scale = z_scale)
4957
5001
 
4958
5002
  self.accept()
4959
5003
 
@@ -5948,16 +5992,128 @@ class NeighborIdentityDialog(QDialog):
5948
5992
 
5949
5993
 
5950
5994
 
5995
+ class RipleyDialog(QDialog):
5996
+
5997
+ def __init__(self, parent=None):
5998
+
5999
+ super().__init__(parent)
6000
+ self.setWindowTitle(f"Find Ripley's H Function From Centroids")
6001
+ self.setModal(True)
6002
+
6003
+ layout = QFormLayout(self)
6004
+
6005
+ if my_network.node_identities is not None:
6006
+ self.root = QComboBox()
6007
+ self.root.addItems(list(set(my_network.node_identities.values())))
6008
+ self.root.setCurrentIndex(0)
6009
+ layout.addRow("Root Identity to Search for Neighbors", self.root)
6010
+ else:
6011
+ self.root = None
6012
+
6013
+ if my_network.node_identities is not None:
6014
+ self.targ = QComboBox()
6015
+ self.targ.addItems(list(set(my_network.node_identities.values())))
6016
+ self.targ.setCurrentIndex(0)
6017
+ layout.addRow("Targ Identity to be Searched For", self.targ)
6018
+ else:
6019
+ self.targ = None
6020
+
6021
+ self.distance = QLineEdit("5")
6022
+ layout.addRow("Bucket Distance for Searching For Clusters (automatically scaled by xy and z scales):", self.distance)
6023
+
6024
+
6025
+ self.proportion = QLineEdit("0.5")
6026
+ layout.addRow("Proportion of image to search? (0-1, high vals increase border artifacts): ", self.proportion)
6027
+
6028
+ self.edgecorrect = QPushButton("Border Correction")
6029
+ self.edgecorrect.setCheckable(True)
6030
+ self.edgecorrect.setChecked(False)
6031
+ layout.addRow("Use Border Correction (Extrapolate for points beyond the border):", self.edgecorrect)
6032
+
6033
+ self.ignore = QPushButton("Ignore Border Roots")
6034
+ self.ignore.setCheckable(True)
6035
+ self.ignore.setChecked(False)
6036
+ layout.addRow("Exclude Root Nodes Near Borders?:", self.ignore)
6037
+
6038
+ # Add Run button
6039
+ run_button = QPushButton("Get Ripley's H")
6040
+ run_button.clicked.connect(self.ripley)
6041
+ layout.addWidget(run_button)
6042
+
6043
+ def ripley(self):
6044
+
6045
+ try:
6046
+
6047
+ if my_network.node_centroids is None:
6048
+ self.parent().show_centroid_dialog()
6049
+
6050
+ try:
6051
+ root = self.root.currentText()
6052
+ except:
6053
+ root = None
6054
+
6055
+ try:
6056
+ targ = self.targ.currentText()
6057
+ except:
6058
+ targ = None
6059
+
6060
+ try:
6061
+ distance = float(self.distance.text())
6062
+ except:
6063
+ return
6064
+
6065
+
6066
+ try:
6067
+ proportion = abs(float(self.proportion.text()))
6068
+ except:
6069
+ proportion = 0.5
6070
+
6071
+ if proportion > 1 or proportion <= 0:
6072
+ print("Utilizing proportion = 0.5")
6073
+ proportion = 0.5
5951
6074
 
5952
6075
 
6076
+ edgecorrect = self.edgecorrect.isChecked()
5953
6077
 
6078
+ ignore = self.ignore.isChecked()
6079
+
6080
+ if my_network.nodes is not None:
6081
+
6082
+ if my_network.nodes.shape[0] == 1:
6083
+ bounds = (np.array([0, 0]), np.array([my_network.nodes.shape[2], my_network.nodes.shape[1]]))
6084
+ else:
6085
+ bounds = (np.array([0, 0, 0]), np.array([my_network.nodes.shape[2], my_network.nodes.shape[1], my_network.nodes.shape[0]]))
6086
+ else:
6087
+ bounds = None
6088
+
6089
+ r_vals, k_vals, h_vals = my_network.get_ripley(root, targ, distance, edgecorrect, bounds, ignore, proportion)
6090
+
6091
+ k_dict = dict(zip(r_vals, k_vals))
6092
+ h_dict = dict(zip(r_vals, h_vals))
6093
+
6094
+
6095
+ self.parent().format_for_upperright_table(k_dict, metric='Radius (scaled)', value='L Value', title="Ripley's K")
6096
+ self.parent().format_for_upperright_table(h_dict, metric='Radius (scaled)', value='L Normed', title="Ripley's H")
6097
+
6098
+
6099
+ self.accept()
6100
+
6101
+ except Exception as e:
6102
+ QMessageBox.critical(
6103
+ self,
6104
+ "Error:",
6105
+ f"Failed to preform cluster analysis: {str(e)}"
6106
+ )
6107
+ import traceback
6108
+ print(traceback.format_exc())
6109
+ print(f"Error: {e}")
5954
6110
 
5955
6111
  class RandomDialog(QDialog):
5956
6112
 
5957
6113
  def __init__(self, parent=None):
5958
6114
 
5959
6115
  super().__init__(parent)
5960
- self.setWindowTitle("Degree Distribution Parameters")
6116
+ self.setWindowTitle("Random Parameters")
5961
6117
  self.setModal(True)
5962
6118
 
5963
6119
  layout = QFormLayout(self)
@@ -5990,6 +6146,79 @@ class RandomDialog(QDialog):
5990
6146
 
5991
6147
  self.accept()
5992
6148
 
6149
+ class RandNodeDialog(QDialog):
6150
+
6151
+ def __init__(self, parent=None):
6152
+
6153
+ super().__init__(parent)
6154
+ self.setWindowTitle("Random Node Parameters")
6155
+ self.setModal(True)
6156
+ layout = QFormLayout(self)
6157
+
6158
+
6159
+ self.mode = QComboBox()
6160
+ self.mode.addItems(["Anywhere", "Within Dimensional Bounds of Nodes", "Within Masked Bounds of Edges", "Within Masked Bounds of Overlay1", "Within Masked Bounds of Overlay2"])
6161
+ self.mode.setCurrentIndex(0)
6162
+ layout.addRow("Mode", self.mode)
6163
+
6164
+ # Add Run button
6165
+ run_button = QPushButton("Get Random Nodes (Will go in Nodes)")
6166
+ run_button.clicked.connect(self.random)
6167
+ layout.addWidget(run_button)
6168
+
6169
+ def random(self):
6170
+
6171
+ try:
6172
+
6173
+ if my_network.node_centroids is None:
6174
+ self.parent().show_centroid_dialog()
6175
+
6176
+ bounds = None
6177
+ mask = None
6178
+
6179
+ mode = self.mode.currentIndex()
6180
+
6181
+ if mode == 0 and not (my_network.nodes is None and my_network.edges is None and my_network.network_overlay is None and my_network.id_overlay is None):
6182
+ pass
6183
+ elif mode == 1 or (my_network.nodes is None and my_network.edges is None and my_network.network_overlay is None and my_network.id_overlay is None):
6184
+ print("HELLO")
6185
+ # Convert string labels to integers if necessary
6186
+ if any(isinstance(k, str) for k in my_network.node_centroids.keys()):
6187
+ label_map = {label: idx for idx, label in enumerate(my_network.node_centroids.keys())}
6188
+ my_network.node_centroids = {label_map[k]: v for k, v in my_network.node_centroids.items()}
6189
+
6190
+ # Convert centroids to array and keep track of labels
6191
+ labels = np.array(list(my_network.node_centroids.keys()), dtype=np.uint32)
6192
+ centroid_points = np.array([my_network.node_centroids[label] for label in labels])
6193
+
6194
+ # Calculate shape if not provided
6195
+ max_coords = centroid_points.max(axis=0)
6196
+ max_shape = tuple(max_coord + 1 for max_coord in max_coords)
6197
+ min_coords = centroid_points.min(axis=0)
6198
+ min_shape = tuple(min_coord + 1 for min_coord in min_coords)
6199
+ bounds = (min_shape, max_shape)
6200
+ else:
6201
+ mask = n3d.binarize(self.parent().channel_data[mode - 1])
6202
+
6203
+ centroids, array = my_network.random_nodes(bounds = bounds, mask = mask)
6204
+
6205
+ if my_network.nodes is not None:
6206
+ try:
6207
+ self.parent().load_channel(0, array, data = True)
6208
+ except:
6209
+ pass
6210
+
6211
+ self.parent().format_for_upperright_table(my_network.node_centroids, 'NodeID', ['Z', 'Y', 'X'], 'Node Centroids')
6212
+
6213
+ except Exception as e:
6214
+ QMessageBox.critical(
6215
+ self,
6216
+ "Error:",
6217
+ f"Failed to randomize: {str(e)}"
6218
+ )
6219
+ print(f"Error: {e}")
6220
+
6221
+
5993
6222
  class RadDialog(QDialog):
5994
6223
 
5995
6224
  def __init__(self, parent=None):
@@ -6001,10 +6230,10 @@ class RadDialog(QDialog):
6001
6230
  layout = QFormLayout(self)
6002
6231
 
6003
6232
  # GPU checkbox (default False)
6004
- #self.GPU = QPushButton("GPU")
6005
- #self.GPU.setCheckable(True)
6006
- #self.GPU.setChecked(False)
6007
- #layout.addRow("Use GPU:", self.GPU)
6233
+ self.GPU = QPushButton("GPU")
6234
+ self.GPU.setCheckable(True)
6235
+ self.GPU.setChecked(False)
6236
+ layout.addRow("Use GPU:", self.GPU)
6008
6237
 
6009
6238
 
6010
6239
  # Add Run button
@@ -6015,7 +6244,7 @@ class RadDialog(QDialog):
6015
6244
  def rads(self):
6016
6245
 
6017
6246
  try:
6018
- #GPU = self.GPU.isChecked() # <- I can never get these to be faster than parallel CPU *shrugs*
6247
+ GPU = self.GPU.isChecked() # <- I can never get these to be faster than parallel CPU *shrugs*
6019
6248
 
6020
6249
  active_data = self.parent().channel_data[self.parent().active_channel]
6021
6250
 
@@ -6042,7 +6271,7 @@ class InteractionDialog(QDialog):
6042
6271
  def __init__(self, parent=None):
6043
6272
 
6044
6273
  super().__init__(parent)
6045
- self.setWindowTitle("Partition Parameters")
6274
+ self.setWindowTitle("Interaction Parameters")
6046
6275
  self.setModal(True)
6047
6276
 
6048
6277
  layout = QFormLayout(self)
@@ -6062,7 +6291,7 @@ class InteractionDialog(QDialog):
6062
6291
  self.fastdil = QPushButton("Fast Dilate")
6063
6292
  self.fastdil.setCheckable(True)
6064
6293
  self.fastdil.setChecked(False)
6065
- layout.addRow("(If not using network) Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
6294
+ layout.addRow("Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.fastdil)
6066
6295
 
6067
6296
  # Add Run button
6068
6297
  run_button = QPushButton("Calculate")
@@ -6117,7 +6346,7 @@ class DegreeDialog(QDialog):
6117
6346
  layout.addRow("Execution Mode:", self.mode_selector)
6118
6347
 
6119
6348
  self.mask_limiter = QLineEdit("1")
6120
- layout.addRow("Masks smaller high degree proportion of nodes (ignore if only returning degrees)", self.mask_limiter)
6349
+ layout.addRow("Proportion of high degree nodes to keep (ignore if only returning degrees)", self.mask_limiter)
6121
6350
 
6122
6351
  self.down_factor = QLineEdit("1")
6123
6352
  layout.addRow("down_factor (for speeding up overlay generation - ignore if only returning degrees:", self.down_factor)
@@ -6662,6 +6891,116 @@ class ResizeDialog(QDialog):
6662
6891
  QMessageBox.critical(self, "Error", f"Failed to resize: {str(e)}")
6663
6892
 
6664
6893
 
6894
+ class OverrideDialog(QDialog):
6895
+ def __init__(self, parent=None):
6896
+ super().__init__(parent)
6897
+ self.setWindowTitle("Override Parameters")
6898
+ self.setModal(True)
6899
+
6900
+ layout = QFormLayout(self)
6901
+
6902
+ layout.addRow(QLabel("Use Highlight Overlay to Place Data From: "))
6903
+
6904
+ # Add mode selection dropdown
6905
+ self.mode_selector = QComboBox()
6906
+ self.mode_selector.addItems(["Nodes", "Edges"])
6907
+ self.mode_selector.setCurrentIndex(0) # Default to Mode 1
6908
+ layout.addRow("Overrider:", self.mode_selector)
6909
+
6910
+ layout.addRow(QLabel("To Override Corresponding Data In: "))
6911
+
6912
+ # Add mode selection dropdown
6913
+ self.target_selector = QComboBox()
6914
+ self.target_selector.addItems(["Nodes", "Edges", "Overlay 1", "Overlay 2"])
6915
+ self.target_selector.setCurrentIndex(0) # Default to Mode 1
6916
+ layout.addRow("To be overwritten:", self.target_selector)
6917
+
6918
+ layout.addRow(QLabel("Place output in: "))
6919
+
6920
+ # Add mode selection dropdown
6921
+ self.output_selector = QComboBox()
6922
+ self.output_selector.addItems(["Nodes", "Edges", "Overlay 1", "Overlay 2", "Highlight Overlay"])
6923
+ self.output_selector.setCurrentIndex(0) # Default to Mode 1
6924
+ layout.addRow("Output Location:", self.output_selector)
6925
+
6926
+ # Add Run button
6927
+ run_button = QPushButton("Override")
6928
+ run_button.clicked.connect(self.override)
6929
+ layout.addWidget(run_button)
6930
+
6931
+ def override(self):
6932
+
6933
+ try:
6934
+
6935
+ accepted_mode = self.mode_selector.currentIndex()
6936
+ accepted_target = self.target_selector.currentIndex()
6937
+ output_target = self.output_selector.currentIndex()
6938
+
6939
+ if accepted_mode == accepted_target:
6940
+ return
6941
+
6942
+ active_data = self.parent().channel_data[accepted_mode]
6943
+
6944
+ if accepted_mode == 0:
6945
+ self.parent().create_highlight_overlay(node_indices=self.parent().clicked_values['nodes'])
6946
+ else:
6947
+ self.parent().create_highlight_overlay(edge_indices=self.parent().clicked_values['edges'])
6948
+
6949
+ target_data = self.parent().channel_data[accepted_target]
6950
+
6951
+ if target_data is None:
6952
+ target_data = np.zeros_like(active_data)
6953
+
6954
+
6955
+
6956
+ try:
6957
+
6958
+ self.parent().highlight_overlay = self.parent().highlight_overlay > 0 #What we want in override image
6959
+ inv = n3d.invert_boolean(self.parent().highlight_overlay) #what we want to keep in target image
6960
+
6961
+ target_data = target_data * inv #Cut out what we don't want in target image
6962
+ max_val = np.max(target_data) #Ensure non-val overlap
6963
+ other_max = np.max(active_data)
6964
+ true_max = max_val + other_max
6965
+ if true_max < 256:
6966
+ dtype = np.uint8
6967
+ elif true_max < 65536:
6968
+ dtype = np.uint16
6969
+ else:
6970
+ dtype = np.uint32
6971
+
6972
+ active_data = active_data.astype(dtype)
6973
+
6974
+ active_data = active_data + max_val #Transpose override image
6975
+
6976
+ active_data = self.parent().highlight_overlay * active_data #Cut out what we want from old image image
6977
+
6978
+ target_data = target_data.astype(dtype)
6979
+
6980
+ target_data = target_data + active_data #Insert new selection
6981
+
6982
+ if output_target == 4:
6983
+
6984
+ self.parent().highlight_overlay = result
6985
+
6986
+ else:
6987
+
6988
+
6989
+ # Update both the display data and the network object
6990
+ self.parent().load_channel(output_target, channel_data = target_data, data = True)
6991
+
6992
+ self.parent().update_display()
6993
+
6994
+ self.accept()
6995
+
6996
+ except Exception as e:
6997
+ print(f"Error overriding: {e}")
6998
+
6999
+ except Exception as e:
7000
+ print(f"Error overriding: {e}")
7001
+
7002
+
7003
+
6665
7004
  class BinarizeDialog(QDialog):
6666
7005
  def __init__(self, parent=None):
6667
7006
  super().__init__(parent)
@@ -6923,7 +7262,7 @@ class ThresholdDialog(QDialog):
6923
7262
  try:
6924
7263
  import cupy as cp
6925
7264
  except:
6926
- print("Cupy import failed, using CPU version")
7265
+ #print("Cupy import failed, using CPU version")
6927
7266
  GPU = False
6928
7267
 
6929
7268
  if self.parent().mini_overlay_data is not None:
@@ -6983,8 +7322,12 @@ class MachineWindow(QMainWindow):
6983
7322
  active_data = self.parent().channel_data[1]
6984
7323
  act_channel = 1
6985
7324
 
7325
+ try:
7326
+ array1 = np.zeros_like(active_data).astype(np.uint8)
7327
+ except:
7328
+ print("No data in nodes channel")
7329
+ return
6986
7330
 
6987
- array1 = np.zeros_like(active_data).astype(np.uint8)
6988
7331
  array3 = np.zeros_like(active_data).astype(np.uint8)
6989
7332
  self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
6990
7333
 
@@ -7065,8 +7408,14 @@ class MachineWindow(QMainWindow):
7065
7408
  train_quick.clicked.connect(lambda: self.train_model(speed=True))
7066
7409
  train_detailed = QPushButton("Train More Detailed Model")
7067
7410
  train_detailed.clicked.connect(lambda: self.train_model(speed=False))
7411
+ save = QPushButton("Save Model")
7412
+ save.clicked.connect(self.save_model)
7413
+ load = QPushButton("Load Model")
7414
+ load.clicked.connect(self.load_model)
7068
7415
  training_layout.addWidget(train_quick)
7069
7416
  training_layout.addWidget(train_detailed)
7417
+ training_layout.addWidget(save)
7418
+ training_layout.addWidget(load)
7070
7419
  training_group.setLayout(training_layout)
7071
7420
 
7072
7421
  # Group 4: Segmentation Options
@@ -7137,6 +7486,33 @@ class MachineWindow(QMainWindow):
7137
7486
  except:
7138
7487
  pass
7139
7488
 
7489
+ def save_model(self):
7490
+
7491
+ filename, _ = QFileDialog.getSaveFileName(
7492
+ self,
7493
+ f"Save Model As",
7494
+ "", # Default directory
7495
+ "numpy data (*.npz);;All Files (*)" # File type filter
7496
+ )
7497
+
7498
+ if filename: # Only proceed if user didn't cancel
7499
+ # If user didn't type an extension, add .tif
7500
+ if not filename.endswith(('.npz')):
7501
+ filename += '.npz'
7502
+
7503
+ self.segmenter.save_model(filename, self.parent().channel_data[2])
7504
+
7505
+ def load_model(self):
7506
+
7507
+ filename, _ = QFileDialog.getOpenFileName(
7508
+ self,
7509
+ f"Load Model",
7510
+ "",
7511
+ "numpy data (*.npz)"
7512
+ )
7513
+
7514
+ self.segmenter.load_model(filename)
7515
+ self.trained = True
7140
7516
 
7141
7517
  def toggle_two(self):
7142
7518
  if self.two.isChecked():
@@ -7227,6 +7603,7 @@ class MachineWindow(QMainWindow):
7227
7603
  )
7228
7604
 
7229
7605
 
7606
+
7230
7607
  def start_segmentation(self):
7231
7608
 
7232
7609
  self.kill_segmentation()
@@ -7255,7 +7632,11 @@ class MachineWindow(QMainWindow):
7255
7632
  self.segmentation_worker.finished.connect(self.segmentation_finished)
7256
7633
  current_xlim = self.parent().ax.get_xlim()
7257
7634
  current_ylim = self.parent().ax.get_ylim()
7258
- self.segmenter.update_position(self.parent().current_slice, int((current_ylim[0] - current_ylim[1])/2), int((current_xlim[1] - current_xlim[0])/2))
7635
+ try:
7636
+ x, y = self.parent().get_current_mouse_position()
7637
+ except:
7638
+ x, y = 0, 0
7639
+ self.segmenter.update_position(self.parent().current_slice, x, y)
7259
7640
  self.segmentation_worker.start()
7260
7641
 
7261
7642
  def confirm_seg_dialog(self):
@@ -7321,7 +7702,12 @@ class MachineWindow(QMainWindow):
7321
7702
  current_xlim = self.parent().ax.get_xlim()
7322
7703
  current_ylim = self.parent().ax.get_ylim()
7323
7704
 
7324
- self.segmenter.update_position(self.parent().current_slice, int((current_ylim[0] - current_ylim[1])/2), int((current_xlim[1] - current_xlim[0])/2))
7705
+ try:
7706
+ x, y = self.parent().get_current_mouse_position()
7707
+ except:
7708
+ x, y = 0, 0
7709
+ self.segmenter.update_position(self.parent().current_slice, x, y)
7710
+
7325
7711
  if not self.parent().painting:
7326
7712
  # Only update if view limits are valid
7327
7713
  self.parent().update_display(preserve_zoom=(current_xlim, current_ylim))
@@ -7616,6 +8002,8 @@ class ThresholdWindow(QMainWindow):
7616
8002
  self.bounds = True
7617
8003
  self.parent().bounds = True
7618
8004
 
8005
+ self.chan = self.parent().active_channel
8006
+
7619
8007
 
7620
8008
  # Create matplotlib figure
7621
8009
  fig = Figure(figsize=(5, 4))
@@ -7684,6 +8072,45 @@ class ThresholdWindow(QMainWindow):
7684
8072
  self.parent().preview = False
7685
8073
  self.parent().targs = None
7686
8074
  self.parent().bounds = False
8075
+ try: # could probably be refactored but this just handles keeping the highlight elements if the user presses X
8076
+ if self.chan == 0:
8077
+ if not self.bounds:
8078
+ self.parent().clicked_values['nodes'] = self.get_values_in_range_all_vols(self.chan, float(self.min.text()), float(self.max.text()))
8079
+ else:
8080
+ vals = np.unique(self.parent().channel_data[self.chan])
8081
+ self.parent().clicked_values['nodes'] = (vals[(vals >= float(self.min.text())) & (vals <= float(self.max.text()))]).tolist()
8082
+
8083
+ if self.parent().channel_data[0].shape[0] * self.parent().channel_data[0].shape[1] * self.parent().channel_data[0].shape[2] > self.parent().mini_thresh:
8084
+ self.parent().mini_overlay = True
8085
+ self.parent().create_mini_overlay(node_indices = self.parent().clicked_values['nodes'])
8086
+ else:
8087
+ self.parent().create_highlight_overlay(
8088
+ node_indices=self.parent().clicked_values['nodes']
8089
+ )
8090
+ elif self.chan == 1:
8091
+ if not self.bounds:
8092
+ self.parent().clicked_values['edges'] = self.get_values_in_range_all_vols(self.chan, float(self.min.text()), float(self.max.text()))
8093
+ else:
8094
+ vals = np.unique(self.parent().channel_data[self.chan])
8095
+ self.parent().clicked_values['edges'] = (vals[(vals >= float(self.min.text())) & (vals <= float(self.max.text()))]).tolist()
8096
+
8097
+ if self.parent().channel_data[1].shape[0] * self.parent().channel_data[1].shape[1] * self.parent().channel_data[1].shape[2] > self.parent().mini_thresh:
8098
+ self.parent().mini_overlay = True
8099
+ self.parent().create_mini_overlay(edge_indices = self.parent().clicked_values['edges'])
8100
+ else:
8101
+ self.parent().create_highlight_overlay(
8102
+ node_indices=self.parent().clicked_values['edges']
8103
+ )
8104
+ except:
8105
+ pass
8106
+
8107
+
8108
+ def get_values_in_range_all_vols(self, chan, min_val, max_val):
8109
+ output = []
8110
+ for node, vol in self.parent().volume_dict[chan].items():
8111
+ if min_val <= vol <= max_val:
8112
+ output.append(node)
8113
+ return output
7687
8114
 
7688
8115
  def get_values_in_range(self, lst, min_val, max_val):
7689
8116
  values = [x for x in lst if min_val <= x <= max_val]
@@ -7882,10 +8309,10 @@ class SmartDilateDialog(QDialog):
7882
8309
  layout.addRow("Use GPU:", self.GPU)
7883
8310
 
7884
8311
  # dt checkbox (default False)
7885
- self.predt = QPushButton("Pre-DT")
8312
+ self.predt = QPushButton("Fast Dilation")
7886
8313
  self.predt.setCheckable(True)
7887
8314
  self.predt.setChecked(False)
7888
- layout.addRow("Use Distance Transform for Predilation (Better at Large Dilations):", self.predt)
8315
+ layout.addRow("Use Fast Dilation (Higher speed, less accurate with search regions much larger than nodes):", self.predt)
7889
8316
 
7890
8317
  self.down_factor = QLineEdit("")
7891
8318
  layout.addRow("Internal Downsample for GPU (if needed):", self.down_factor)
@@ -7901,7 +8328,7 @@ class SmartDilateDialog(QDialog):
7901
8328
 
7902
8329
  GPU = self.GPU.isChecked()
7903
8330
  down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
7904
- predt = not self.predt.isChecked()
8331
+ predt = self.predt.isChecked()
7905
8332
  active_data, amount, xy_scale, z_scale = self.params
7906
8333
 
7907
8334
  dilate_xy, dilate_z = n3d.dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
@@ -8000,7 +8427,8 @@ class DilateDialog(QDialog):
8000
8427
  active_data,
8001
8428
  amount,
8002
8429
  xy_scale = xy_scale,
8003
- z_scale = z_scale)
8430
+ z_scale = z_scale,
8431
+ fast_dil = True)
8004
8432
 
8005
8433
  result = result * 255
8006
8434
 
@@ -8250,6 +8678,7 @@ class MaskDialog(QDialog):
8250
8678
  print(f"Error masking: {e}")
8251
8679
 
8252
8680
 
8681
+
8253
8682
  class TypeDialog(QDialog):
8254
8683
 
8255
8684
  def __init__(self, parent=None):
@@ -8268,7 +8697,7 @@ class TypeDialog(QDialog):
8268
8697
 
8269
8698
  # Add mode selection dropdown
8270
8699
  self.mode_selector = QComboBox()
8271
- self.mode_selector.addItems(["8bit int", "16bit int", "32bit int", "32bit float", "64bit float"])
8700
+ self.mode_selector.addItems(["8bit uint", "16bit uint", "32bit uint", "32bit float", "64bit float"])
8272
8701
  self.mode_selector.setCurrentIndex(0) # Default to Mode 1
8273
8702
  layout.addRow("Change to?:", self.mode_selector)
8274
8703
 
@@ -8279,33 +8708,38 @@ class TypeDialog(QDialog):
8279
8708
 
8280
8709
  def run_type(self, active_data):
8281
8710
 
8282
- mode = self.mode_selector.currentIndex()
8711
+ try:
8283
8712
 
8284
- if mode == 0:
8713
+ mode = self.mode_selector.currentIndex()
8285
8714
 
8286
- active_data = active_data.astype(np.uint8)
8715
+ if mode == 0:
8287
8716
 
8288
- elif mode == 1:
8717
+ active_data = active_data.astype(np.uint8)
8289
8718
 
8290
- active_data = active_data.astype(np.uint16)
8719
+ elif mode == 1:
8291
8720
 
8292
- elif mode == 2:
8721
+ active_data = active_data.astype(np.uint16)
8293
8722
 
8294
- active_data = active_data.astype(np.uint32)
8723
+ elif mode == 2:
8295
8724
 
8296
- elif mode == 3:
8725
+ active_data = active_data.astype(np.uint32)
8297
8726
 
8298
- active_data = active_data.astype(np.float32)
8727
+ elif mode == 3:
8299
8728
 
8300
- elif mode == 4:
8729
+ active_data = active_data.astype(np.float32)
8301
8730
 
8302
- active_data = active_data.astype(np.float64)
8731
+ elif mode == 4:
8303
8732
 
8304
- self.parent().load_channel(self.active_chan, active_data, True)
8733
+ active_data = active_data.astype(np.float64)
8305
8734
 
8735
+ self.parent().load_channel(self.active_chan, active_data, True)
8306
8736
 
8307
- print(f"Channel {self.active_chan}) dtype now: {self.parent().channel_data[self.active_chan].dtype}")
8308
- self.accept()
8737
+
8738
+ print(f"Channel {self.active_chan}) dtype now: {self.parent().channel_data[self.active_chan].dtype}")
8739
+ self.accept()
8740
+
8741
+ except Exception as E:
8742
+ print(f"Error: {e}")
8309
8743
 
8310
8744
 
8311
8745
 
@@ -8423,7 +8857,7 @@ class WatershedDialog(QDialog):
8423
8857
  # Predownsample (empty by default)
8424
8858
  self.predownsample = QLineEdit()
8425
8859
  self.predownsample.setPlaceholderText("Leave empty for None")
8426
- layout.addRow("Smart Dilate GPU Downsample:", self.predownsample)
8860
+ layout.addRow("Kernel Obtainment GPU Downsample:", self.predownsample)
8427
8861
 
8428
8862
  # Predownsample2 (empty by default)
8429
8863
  self.predownsample2 = QLineEdit()
@@ -8658,9 +9092,9 @@ class GenNodesDialog(QDialog):
8658
9092
  layout = QFormLayout(self)
8659
9093
  self.called = called
8660
9094
 
8661
- self.directory = QLineEdit()
8662
- self.directory.setPlaceholderText("Leave empty to save in active dir")
8663
- layout.addRow("Output Directory:", self.directory)
9095
+ #self.directory = QLineEdit()
9096
+ #self.directory.setPlaceholderText("Leave empty to save in active dir")
9097
+ #layout.addRow("Output Directory:", self.directory)
8664
9098
 
8665
9099
  if not down_factor:
8666
9100
  down_factor = None
@@ -8670,7 +9104,7 @@ class GenNodesDialog(QDialog):
8670
9104
  self.cubic = QPushButton("Cubic Downsample")
8671
9105
  self.cubic.setCheckable(True)
8672
9106
  self.cubic.setChecked(False)
8673
- layout.addRow("(if downsampling): Use cubic dilation? (Slower but can preserve structure better)", self.cubic)
9107
+ layout.addRow("(if downsampling): Use cubic downsample? (Slower but can preserve structure better)", self.cubic)
8674
9108
  else:
8675
9109
  self.down_factor = down_factor[0]
8676
9110
  self.cubic = down_factor[1]
@@ -8714,7 +9148,7 @@ class GenNodesDialog(QDialog):
8714
9148
 
8715
9149
  try:
8716
9150
  # Get directory (None if empty)
8717
- directory = self.directory.text() if self.directory.text() else None
9151
+ #directory = self.directory.text() if self.directory.text() else None
8718
9152
 
8719
9153
  # Get branch_removal
8720
9154
  try:
@@ -8860,7 +9294,7 @@ class BranchDialog(QDialog):
8860
9294
  self.cubic = QPushButton("Cubic Downsample")
8861
9295
  self.cubic.setCheckable(True)
8862
9296
  self.cubic.setChecked(False)
8863
- layout.addRow("(if downsampling): Use cubic dilation? (Slower but can preserve structure better)", self.cubic)
9297
+ layout.addRow("(if downsampling): Use cubic downsample? (Slower but can preserve structure better)", self.cubic)
8864
9298
 
8865
9299
  # Add Run button
8866
9300
  run_button = QPushButton("Run Branch Label")
@@ -9283,20 +9717,23 @@ class CentroidDialog(QDialog):
9283
9717
  my_network.calculate_node_centroids(
9284
9718
  down_factor = downsample
9285
9719
  )
9286
- my_network.save_node_centroids(directory = directory)
9720
+ if directory:
9721
+ my_network.save_node_centroids(directory = directory)
9287
9722
 
9288
9723
  elif chan == 2:
9289
9724
  my_network.calculate_edge_centroids(
9290
9725
  down_factor = downsample
9291
9726
  )
9292
- my_network.save_edge_centroids(directory = directory)
9727
+ if directory:
9728
+ my_network.save_edge_centroids(directory = directory)
9293
9729
 
9294
9730
  elif chan == 0:
9295
9731
  try:
9296
9732
  my_network.calculate_node_centroids(
9297
9733
  down_factor = downsample
9298
9734
  )
9299
- my_network.save_node_centroids(directory = directory)
9735
+ if directory:
9736
+ my_network.save_node_centroids(directory = directory)
9300
9737
  except:
9301
9738
  pass
9302
9739
 
@@ -9305,7 +9742,8 @@ class CentroidDialog(QDialog):
9305
9742
  my_network.calculate_edge_centroids(
9306
9743
  down_factor = downsample
9307
9744
  )
9308
- my_network.save_edge_centroids(directory = directory)
9745
+ if directory:
9746
+ my_network.save_edge_centroids(directory = directory)
9309
9747
 
9310
9748
  except:
9311
9749
  pass
@@ -9346,7 +9784,7 @@ class CalcAllDialog(QDialog):
9346
9784
  prev_GPU_downsample = ""
9347
9785
  prev_other_nodes = ""
9348
9786
  prev_remove_trunk = ""
9349
- prev_gpu = True
9787
+ prev_gpu = False
9350
9788
  prev_label_nodes = True
9351
9789
  prev_inners = True
9352
9790
  prev_fastdil = False
@@ -9378,7 +9816,7 @@ class CalcAllDialog(QDialog):
9378
9816
 
9379
9817
  self.diledge = QLineEdit(self.prev_diledge)
9380
9818
  self.diledge.setPlaceholderText("Leave empty for None")
9381
- layout.addRow("Edge Reconnection Distance (int):", self.diledge)
9819
+ layout.addRow("Edge Reconnection Distance (float):", self.diledge)
9382
9820
 
9383
9821
  self.down_factor = QLineEdit(self.prev_down_factor)
9384
9822
  self.down_factor.setPlaceholderText("Leave empty for None")
@@ -9405,7 +9843,7 @@ class CalcAllDialog(QDialog):
9405
9843
  self.label_nodes = QPushButton("Label")
9406
9844
  self.label_nodes.setCheckable(True)
9407
9845
  self.label_nodes.setChecked(self.prev_label_nodes)
9408
- layout.addRow("Label Nodes:", self.label_nodes)
9846
+ layout.addRow("Re-Label Nodes (WARNING - OVERRIDES ANY CURRENT LABELS):", self.label_nodes)
9409
9847
 
9410
9848
  self.inners = QPushButton("Inner Edges")
9411
9849
  self.inners.setCheckable(True)
@@ -9744,13 +10182,13 @@ class ProxDialog(QDialog):
9744
10182
  return
9745
10183
 
9746
10184
  if populate:
9747
- my_network.nodes = my_network.kd_network(distance = search, targets = targets)
10185
+ my_network.nodes = my_network.kd_network(distance = search, targets = targets, make_array = True)
9748
10186
  self.parent().load_channel(0, channel_data = my_network.nodes, data = True)
9749
10187
  else:
9750
10188
  my_network.kd_network(distance = search, targets = targets)
9751
10189
 
9752
-
9753
- my_network.dump(directory = directory)
10190
+ if directory is not None:
10191
+ my_network.dump(directory = directory)
9754
10192
 
9755
10193
 
9756
10194
  # Then handle overlays