nettracer3d 0.5.2__py3-none-any.whl → 0.5.4__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.
@@ -397,6 +397,8 @@ class ImageViewerWindow(QMainWindow):
397
397
  # Initialize highlight overlay
398
398
  self.highlight_overlay = None
399
399
  self.highlight_bounds = None # Store bounds for positioning
400
+ self.mini_overlay = False # If the program is currently drawing the overlay by frame this will be true
401
+ self.mini_thresh = (500*500*500) # Array volume to start using mini overlays for
400
402
 
401
403
  def start_left_scroll(self):
402
404
  """Start scrolling left when left arrow is pressed."""
@@ -443,6 +445,8 @@ class ImageViewerWindow(QMainWindow):
443
445
  edge_indices (list): List of edge indices to highlight
444
446
  """
445
447
 
448
+ self.mini_overlay = False #If this method is ever being called, it means we are rendering the entire overlay so mini overlay needs to reset.
449
+
446
450
  def process_chunk(chunk_data, indices_to_check):
447
451
  """Process a single chunk of the array to create highlight mask"""
448
452
  mask = np.isin(chunk_data, indices_to_check)
@@ -463,6 +467,9 @@ class ImageViewerWindow(QMainWindow):
463
467
  if overlay1_indices is not None:
464
468
  if 0 in overlay1_indices:
465
469
  overlay1_indices.remove(0)
470
+ if overlay2_indices is not None:
471
+ if 0 in overlay2_indices:
472
+ overlay2_indices.remove(0)
466
473
 
467
474
  if node_indices is None:
468
475
  node_indices = []
@@ -628,6 +635,110 @@ class ImageViewerWindow(QMainWindow):
628
635
  # Update display
629
636
  self.update_display(preserve_zoom=(current_xlim, current_ylim), called = True)
630
637
 
638
+ def create_mini_overlay(self, node_indices = None, edge_indices = None):
639
+
640
+ """
641
+ Create a binary overlay highlighting specific nodes and/or edges using parallel processing, one slice at a time for efficiency.
642
+
643
+ Args:
644
+ node_indices (list): List of node indices to highlight
645
+ edge_indices (list): List of edge indices to highlight
646
+ """
647
+
648
+ def process_chunk(chunk_data, indices_to_check):
649
+ """Process a single chunk of the array to create highlight mask"""
650
+ mask = np.isin(chunk_data, indices_to_check)
651
+ return mask * 255
652
+
653
+
654
+ if node_indices is not None:
655
+ if 0 in node_indices:
656
+ node_indices.remove(0)
657
+ if edge_indices is not None:
658
+ if 0 in edge_indices:
659
+ edge_indices.remove(0)
660
+
661
+
662
+ if node_indices is None:
663
+ node_indices = []
664
+ if edge_indices is None:
665
+ edge_indices = []
666
+
667
+
668
+ current_xlim = self.ax.get_xlim() if hasattr(self, 'ax') and self.ax.get_xlim() != (0, 1) else None
669
+ current_ylim = self.ax.get_ylim() if hasattr(self, 'ax') and self.ax.get_ylim() != (0, 1) else None
670
+
671
+ if not node_indices and not edge_indices:
672
+ self.highlight_overlay = None
673
+ self.update_display(preserve_zoom=(current_xlim, current_ylim))
674
+ return
675
+
676
+ # Get the shape of the full array from any existing channel
677
+ for channel in self.channel_data:
678
+ if channel is not None:
679
+ full_shape = channel.shape
680
+ break
681
+ else:
682
+ return # No valid channels to get shape from
683
+
684
+ # Initialize full-size overlay
685
+ if self.highlight_overlay is None:
686
+ self.highlight_overlay = np.zeros(full_shape, dtype=np.uint8)
687
+
688
+ # Get number of CPU cores
689
+ num_cores = mp.cpu_count()
690
+
691
+ # Calculate chunk size along y-axis
692
+ chunk_size = full_shape[1] // num_cores
693
+ if chunk_size < 1:
694
+ chunk_size = 1
695
+
696
+ def process_channel(channel_data, indices, array_shape):
697
+ if channel_data is None or not indices:
698
+ return None
699
+
700
+ # Create chunks
701
+ chunks = []
702
+ for i in range(0, array_shape[1], chunk_size):
703
+ end = min(i + chunk_size, array_shape[1])
704
+ chunks.append(channel_data[i:end, :])
705
+
706
+ # Process chunks in parallel using ThreadPoolExecutor
707
+ process_func = partial(process_chunk, indices_to_check=indices)
708
+
709
+
710
+ with ThreadPoolExecutor(max_workers=num_cores) as executor:
711
+ chunk_results = list(executor.map(process_func, chunks))
712
+
713
+ # Reassemble the chunks
714
+ return np.concatenate(chunk_results, axis=0)
715
+
716
+ # Process nodes and edges in parallel using multiprocessing
717
+ with ThreadPoolExecutor(max_workers=num_cores) as executor:
718
+ try:
719
+ slice_node = self.channel_data[0][self.current_slice, :, :] #This is the only major difference to the big highlight... we are only looking at this
720
+ future_nodes = executor.submit(process_channel, slice_node, node_indices, full_shape)
721
+ node_overlay = future_nodes.result()
722
+ except:
723
+ node_overlay = None
724
+ try:
725
+ slice_edge = self.channel_data[1][self.current_slice, :, :]
726
+ future_edges = executor.submit(process_channel, slice_edge, edge_indices, full_shape)
727
+ edge_overlay = future_edges.result()
728
+ except:
729
+ edge_overlay = None
730
+
731
+ # Combine results
732
+ self.highlight_overlay[self.current_slice, :, :] = np.zeros_like(self.highlight_overlay[self.current_slice, :, :])
733
+ if node_overlay is not None:
734
+ self.highlight_overlay[self.current_slice, :, :] = np.maximum(self.highlight_overlay[self.current_slice, :, :], node_overlay)
735
+ if edge_overlay is not None:
736
+ self.highlight_overlay[self.current_slice, :, :] = np.maximum(self.highlight_overlay[self.current_slice, :, :], edge_overlay)
737
+
738
+
739
+ # Update display
740
+ self.update_display(preserve_zoom=(current_xlim, current_ylim))
741
+
631
742
 
632
743
 
633
744
 
@@ -890,14 +1001,23 @@ class ImageViewerWindow(QMainWindow):
890
1001
  edge_indices = filtered_df.iloc[:, 2].unique().tolist()
891
1002
  self.clicked_values['edges'] = edge_indices
892
1003
 
893
- self.create_highlight_overlay(
894
- node_indices=self.clicked_values['nodes'],
895
- edge_indices=self.clicked_values['edges']
896
- )
1004
+ if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
1005
+ self.mini_overlay = True
1006
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
1007
+ else:
1008
+ self.create_highlight_overlay(
1009
+ node_indices=self.clicked_values['nodes'],
1010
+ edge_indices=self.clicked_values['edges']
1011
+ )
897
1012
  else:
898
- self.create_highlight_overlay(
899
- node_indices=self.clicked_values['nodes']
900
- )
1013
+ if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
1014
+ self.mini_overlay = True
1015
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
1016
+ else:
1017
+ self.create_highlight_overlay(
1018
+ node_indices=self.clicked_values['nodes'],
1019
+ edge_indices = self.clicked_values['edges']
1020
+ )
901
1021
 
902
1022
 
903
1023
  except Exception as e:
@@ -972,14 +1092,23 @@ class ImageViewerWindow(QMainWindow):
972
1092
  if edges:
973
1093
  edge_indices = filtered_df.iloc[:, 2].unique().tolist()
974
1094
  self.clicked_values['edges'] = edge_indices
975
- self.create_highlight_overlay(
976
- node_indices=self.clicked_values['nodes'],
977
- edge_indices=edge_indices
978
- )
1095
+ if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
1096
+ self.mini_overlay = True
1097
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
1098
+ else:
1099
+ self.create_highlight_overlay(
1100
+ node_indices=self.clicked_values['nodes'],
1101
+ edge_indices=edge_indices
1102
+ )
979
1103
  else:
980
- self.create_highlight_overlay(
981
- node_indices = self.clicked_values['nodes']
982
- )
1104
+ if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
1105
+ self.mini_overlay = True
1106
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
1107
+ else:
1108
+ self.create_highlight_overlay(
1109
+ node_indices = self.clicked_values['nodes'],
1110
+ edge_indices = self.clicked_values['edges']
1111
+ )
983
1112
 
984
1113
  except Exception as e:
985
1114
 
@@ -1043,15 +1172,24 @@ class ImageViewerWindow(QMainWindow):
1043
1172
  if edges:
1044
1173
  edge_indices = filtered_df.iloc[:, 2].unique().tolist()
1045
1174
  self.clicked_values['edges'] = edge_indices
1046
- self.create_highlight_overlay(
1047
- node_indices=nodes,
1048
- edge_indices=edge_indices
1049
- )
1175
+ if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
1176
+ self.mini_overlay = True
1177
+ self.create_mini_overlay(node_indices = nodes, edge_indices = edge_indices)
1178
+ else:
1179
+ self.create_highlight_overlay(
1180
+ node_indices=nodes,
1181
+ edge_indices=edge_indices
1182
+ )
1050
1183
  self.clicked_values['nodes'] = nodes
1051
1184
  else:
1052
- self.create_highlight_overlay(
1053
- node_indices = nodes
1054
- )
1185
+ if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
1186
+ self.mini_overlay = True
1187
+ self.create_mini_overlay(node_indices = nodes, edge_indices = self.clicked_values['edges'])
1188
+ else:
1189
+ self.create_highlight_overlay(
1190
+ node_indices = nodes,
1191
+ edge_indices = self.clicked_values['edges']
1192
+ )
1055
1193
  self.clicked_values['nodes'] = nodes
1056
1194
 
1057
1195
  except Exception as e:
@@ -1095,9 +1233,15 @@ class ImageViewerWindow(QMainWindow):
1095
1233
 
1096
1234
  print(f"Found {len(filtered_df)} direct connections between nodes of ID {sort} and their neighbors (of any ID)")
1097
1235
 
1098
- self.create_highlight_overlay(
1099
- node_indices= nodes
1100
- )
1236
+ if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
1237
+ self.mini_overlay = True
1238
+ self.create_mini_overlay(node_indices = nodes, edge_indices = self.clicked_values['edges'])
1239
+ else:
1240
+ self.create_highlight_overlay(
1241
+ node_indices = nodes,
1242
+ edge_indices = self.clicked_values['edges']
1243
+ )
1244
+ self.clicked_values['nodes'] = nodes
1101
1245
 
1102
1246
  except Exception as e:
1103
1247
  print(f"Error showing identities: {e}")
@@ -1286,7 +1430,7 @@ class ImageViewerWindow(QMainWindow):
1286
1430
  self.load_channel(1, my_network.edges, True)
1287
1431
  self.highlight_overlay = None
1288
1432
  self.update_display()
1289
- print("Network is not updated automatically, please recompute if necesarry. Identities are not automatically updated.")
1433
+ print("Network is not updated automatically, please recompute if necessary. Identities are not automatically updated.")
1290
1434
  self.show_centroid_dialog()
1291
1435
 
1292
1436
  except Exception as e:
@@ -1315,7 +1459,7 @@ class ImageViewerWindow(QMainWindow):
1315
1459
 
1316
1460
 
1317
1461
  if len(self.clicked_values['edges']) > 0:
1318
- self.create_highlight_overlay(node_indices = self.clicked_values['edges'])
1462
+ self.create_highlight_overlay(edge_indices = self.clicked_values['edges'])
1319
1463
  mask = self.highlight_overlay == 0
1320
1464
  my_network.edges = my_network.edges * mask
1321
1465
  self.load_channel(1, my_network.edges, True)
@@ -1393,8 +1537,6 @@ class ImageViewerWindow(QMainWindow):
1393
1537
 
1394
1538
  pairs = list(combinations(nodes, 2))
1395
1539
 
1396
- print(pairs)
1397
-
1398
1540
 
1399
1541
  for i in range(len(my_network.network_lists[0]) - 1, -1, -1):
1400
1542
  print((my_network.network_lists[0][i], my_network.network_lists[1][i]))
@@ -1891,7 +2033,11 @@ class ImageViewerWindow(QMainWindow):
1891
2033
  self.clicked_values['nodes'].extend(selected_values)
1892
2034
  # Remove duplicates while preserving order
1893
2035
  self.clicked_values['nodes'] = list(dict.fromkeys(self.clicked_values['nodes']))
1894
- self.create_highlight_overlay(node_indices=self.clicked_values['nodes'])
2036
+ if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
2037
+ self.mini_overlay = True
2038
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
2039
+ else:
2040
+ self.create_highlight_overlay(node_indices=self.clicked_values['nodes'])
1895
2041
 
1896
2042
  # Try to highlight the last selected value in tables
1897
2043
  if self.clicked_values['nodes']:
@@ -1903,7 +2049,11 @@ class ImageViewerWindow(QMainWindow):
1903
2049
  self.clicked_values['edges'].extend(selected_values)
1904
2050
  # Remove duplicates while preserving order
1905
2051
  self.clicked_values['edges'] = list(dict.fromkeys(self.clicked_values['edges']))
1906
- self.create_highlight_overlay(edge_indices=self.clicked_values['edges'])
2052
+ if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
2053
+ self.mini_overlay = True
2054
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
2055
+ else:
2056
+ self.create_highlight_overlay(edge_indices=self.clicked_values['edges'])
1907
2057
 
1908
2058
  # Try to highlight the last selected value in tables
1909
2059
  if self.clicked_values['edges']:
@@ -2144,9 +2294,17 @@ class ImageViewerWindow(QMainWindow):
2144
2294
 
2145
2295
  # Highlight the clicked element in the image using the stored lists
2146
2296
  if self.active_channel == 0 and (starting_vals['nodes']) != (self.clicked_values['nodes']):
2147
- self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices=self.clicked_values['edges'])
2297
+ if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
2298
+ self.mini_overlay = True
2299
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
2300
+ else:
2301
+ self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices=self.clicked_values['edges'])
2148
2302
  elif self.active_channel == 1 and starting_vals['edges'] != self.clicked_values['edges']:
2149
- self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices=self.clicked_values['edges'])
2303
+ if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
2304
+ self.mini_overlay = True
2305
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
2306
+ else:
2307
+ self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices=self.clicked_values['edges'])
2150
2308
 
2151
2309
 
2152
2310
  except IndexError:
@@ -2298,6 +2456,8 @@ class ImageViewerWindow(QMainWindow):
2298
2456
  netoverlay_action.triggered.connect(self.show_netoverlay_dialog)
2299
2457
  idoverlay_action = overlay_menu.addAction("Create ID Overlay")
2300
2458
  idoverlay_action.triggered.connect(self.show_idoverlay_dialog)
2459
+ coloroverlay_action = overlay_menu.addAction("Color Nodes (or Edges)")
2460
+ coloroverlay_action.triggered.connect(self.show_coloroverlay_dialog)
2301
2461
  searchoverlay_action = overlay_menu.addAction("Show Search Regions")
2302
2462
  searchoverlay_action.triggered.connect(self.show_search_dialog)
2303
2463
  shuffle_action = overlay_menu.addAction("Shuffle")
@@ -2578,6 +2738,11 @@ class ImageViewerWindow(QMainWindow):
2578
2738
  dialog = IdOverlayDialog(self)
2579
2739
  dialog.exec()
2580
2740
 
2741
+ def show_coloroverlay_dialog(self):
2742
+ """show the color overlay dialog"""
2743
+ dialog = ColorOverlayDialog(self)
2744
+ dialog.exec()
2745
+
2581
2746
  def show_search_dialog(self):
2582
2747
  """Show the search dialog"""
2583
2748
  dialog = SearchOverlayDialog(self)
@@ -2954,14 +3119,46 @@ class ImageViewerWindow(QMainWindow):
2954
3119
 
2955
3120
  try:
2956
3121
  if not data: # For solo loading
2957
- import tifffile
2958
3122
  filename, _ = QFileDialog.getOpenFileName(
2959
3123
  self,
2960
3124
  f"Load Channel {channel_index + 1}",
2961
3125
  "",
2962
- "TIFF Files (*.tif *.tiff)"
3126
+ "Image Files (*.tif *.tiff *.nii *.jpg *.jpeg *.png)"
2963
3127
  )
2964
- self.channel_data[channel_index] = tifffile.imread(filename)
3128
+
3129
+ if not filename:
3130
+ return
3131
+
3132
+ file_extension = filename.lower().split('.')[-1]
3133
+
3134
+ try:
3135
+ if file_extension in ['tif', 'tiff']:
3136
+ import tifffile
3137
+ self.channel_data[channel_index] = tifffile.imread(filename)
3138
+
3139
+ elif file_extension == 'nii':
3140
+ import nibabel as nib
3141
+ nii_img = nib.load(filename)
3142
+ # Get data and transpose to match TIFF orientation
3143
+ # If X needs to become Z, we move axis 2 (X) to position 0 (Z)
3144
+ data = nii_img.get_fdata()
3145
+ self.channel_data[channel_index] = np.transpose(data, (2, 1, 0))
3146
+
3147
+ elif file_extension in ['jpg', 'jpeg', 'png']:
3148
+ from PIL import Image
3149
+
3150
+ with Image.open(filename) as img:
3151
+ # Convert directly to numpy array, keeping color if present
3152
+ self.channel_data[channel_index] = np.array(img)
3153
+
3154
+ # Debug info to check shape
3155
+ print(f"Loaded image shape: {self.channel_data[channel_index].shape}")
3156
+
3157
+ except ImportError as e:
3158
+ QMessageBox.critical(self, "Error", f"Required library not installed: {str(e)}")
3159
+ except Exception as e:
3160
+ QMessageBox.critical(self, "Error", f"Error loading image: {str(e)}")
3161
+
2965
3162
 
2966
3163
  if len(self.channel_data[channel_index].shape) == 2: # handle 2d data
2967
3164
  self.channel_data[channel_index] = np.expand_dims(self.channel_data[channel_index], axis=0)
@@ -2969,24 +3166,33 @@ class ImageViewerWindow(QMainWindow):
2969
3166
  else:
2970
3167
  self.channel_data[channel_index] = channel_data
2971
3168
 
2972
- if len(self.channel_data[channel_index].shape) == 3: # potentially 2D RGB
2973
- if self.channel_data[channel_index].shape[-1] in (3, 4): # last dim is 3 or 4
2974
- if self.confirm_rgb_dialog():
2975
- # User confirmed it's 2D RGB, expand to 4D
2976
- self.channel_data[channel_index] = np.expand_dims(self.channel_data[channel_index], axis=0)
2977
-
2978
- if len(self.channel_data[channel_index].shape) == 4 and (channel_index == 0 or channel_index == 1):
2979
- self.channel_data[channel_index] = self.reduce_rgb_dimension(self.channel_data[channel_index])
3169
+ try:
3170
+ if len(self.channel_data[channel_index].shape) == 3: # potentially 2D RGB
3171
+ if self.channel_data[channel_index].shape[-1] in (3, 4): # last dim is 3 or 4
3172
+ if self.confirm_rgb_dialog():
3173
+ # User confirmed it's 2D RGB, expand to 4D
3174
+ self.channel_data[channel_index] = np.expand_dims(self.channel_data[channel_index], axis=0)
3175
+ except:
3176
+ pass
3177
+
3178
+ try:
3179
+ if len(self.channel_data[channel_index].shape) == 4 and (channel_index == 0 or channel_index == 1):
3180
+ self.channel_data[channel_index] = self.reduce_rgb_dimension(self.channel_data[channel_index])
3181
+ except:
3182
+ pass
2980
3183
 
2981
3184
  reset_resize = False
2982
3185
 
2983
3186
  for i in range(4): #Try to ensure users don't load in different sized arrays
2984
3187
  if self.channel_data[i] is None or i == channel_index or data:
2985
3188
  if self.highlight_overlay is not None: #Make sure highlight overlay is always the same shape as new images
2986
- if self.channel_data[i].shape[:3] != self.highlight_overlay.shape:
2987
- self.resizing = True
2988
- reset_resize = True
2989
- self.highlight_overlay = None
3189
+ try:
3190
+ if self.channel_data[i].shape[:3] != self.highlight_overlay.shape:
3191
+ self.resizing = True
3192
+ reset_resize = True
3193
+ self.highlight_overlay = None
3194
+ except:
3195
+ pass
2990
3196
  continue
2991
3197
  else:
2992
3198
  old_shape = self.channel_data[i].shape[:3] #Ask user to resize images that are shaped differently
@@ -3015,27 +3221,30 @@ class ImageViewerWindow(QMainWindow):
3015
3221
  self.active_channel_combo.setEnabled(True)
3016
3222
 
3017
3223
  # Update slider range if this is the first channel loaded
3018
- if len(self.channel_data[channel_index].shape) == 3 or len(self.channel_data[channel_index].shape) == 4:
3019
- if not self.slice_slider.isEnabled():
3020
- self.slice_slider.setEnabled(True)
3021
- self.slice_slider.setMinimum(0)
3022
- self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
3023
- if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
3024
- self.current_slice = self.slice_slider.value()
3224
+ try:
3225
+ if len(self.channel_data[channel_index].shape) == 3 or len(self.channel_data[channel_index].shape) == 4:
3226
+ if not self.slice_slider.isEnabled():
3227
+ self.slice_slider.setEnabled(True)
3228
+ self.slice_slider.setMinimum(0)
3229
+ self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
3230
+ if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
3231
+ self.current_slice = self.slice_slider.value()
3232
+ else:
3233
+ self.slice_slider.setValue(0)
3234
+ self.current_slice = 0
3025
3235
  else:
3026
- self.slice_slider.setValue(0)
3027
- self.current_slice = 0
3236
+ self.slice_slider.setEnabled(True)
3237
+ self.slice_slider.setMinimum(0)
3238
+ self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
3239
+ if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
3240
+ self.current_slice = self.slice_slider.value()
3241
+ else:
3242
+ self.current_slice = 0
3243
+ self.slice_slider.setValue(0)
3028
3244
  else:
3029
- self.slice_slider.setEnabled(True)
3030
- self.slice_slider.setMinimum(0)
3031
- self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
3032
- if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
3033
- self.current_slice = self.slice_slider.value()
3034
- else:
3035
- self.current_slice = 0
3036
- self.slice_slider.setValue(0)
3037
- else:
3038
- self.slice_slider.setEnabled(False)
3245
+ self.slice_slider.setEnabled(False)
3246
+ except:
3247
+ pass
3039
3248
 
3040
3249
 
3041
3250
  # If this is the first channel loaded, make it active
@@ -3048,19 +3257,24 @@ class ImageViewerWindow(QMainWindow):
3048
3257
  self.min_max[channel_index][1] = np.max(self.channel_data[channel_index])
3049
3258
  self.volume_dict[channel_index] = None #reset volumes
3050
3259
 
3051
- if assign_shape: #keep original shape tracked to undo resampling.
3052
- if self.original_shape is None:
3053
- self.original_shape = self.channel_data[channel_index].shape
3054
- elif self.original_shape[0] < self.channel_data[channel_index].shape[0] or self.original_shape[1] < self.channel_data[channel_index].shape[1] or self.original_shape[2] < self.channel_data[channel_index].shape[2]:
3055
- self.original_shape = self.channel_data[channel_index].shape
3056
- if len(self.original_shape) == 4:
3057
- self.original_shape = (self.original_shape[0], self.original_shape[1], self.original_shape[2])
3260
+ try:
3261
+ if assign_shape: #keep original shape tracked to undo resampling.
3262
+ if self.original_shape is None:
3263
+ self.original_shape = self.channel_data[channel_index].shape
3264
+ elif self.original_shape[0] < self.channel_data[channel_index].shape[0] or self.original_shape[1] < self.channel_data[channel_index].shape[1] or self.original_shape[2] < self.channel_data[channel_index].shape[2]:
3265
+ self.original_shape = self.channel_data[channel_index].shape
3266
+ if len(self.original_shape) == 4:
3267
+ self.original_shape = (self.original_shape[0], self.original_shape[1], self.original_shape[2])
3268
+ except:
3269
+ pass
3058
3270
 
3059
3271
  self.update_display(reset_resize = reset_resize)
3060
3272
 
3061
3273
 
3062
3274
 
3063
3275
  except Exception as e:
3276
+ import traceback
3277
+ print(traceback.format_exc())
3064
3278
  if not data:
3065
3279
  from PyQt6.QtWidgets import QMessageBox
3066
3280
  QMessageBox.critical(
@@ -3225,6 +3439,8 @@ class ImageViewerWindow(QMainWindow):
3225
3439
  elif ch_index == 3:
3226
3440
  my_network.save_id_overlay(filename=filename)
3227
3441
  elif ch_index == 4:
3442
+ if self.mini_overlay == True:
3443
+ self.create_highlight_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
3228
3444
  if filename == None:
3229
3445
  filename = "Highlighted_Element.tif"
3230
3446
  tifffile.imwrite(f"{filename}", self.highlight_overlay)
@@ -3269,6 +3485,8 @@ class ImageViewerWindow(QMainWindow):
3269
3485
  if self.pending_slice is not None:
3270
3486
  slice_value, view_settings = self.pending_slice
3271
3487
  self.current_slice = slice_value
3488
+ if self.mini_overlay == True: #If we are rendering the highlight overlay for selected values one at a time.
3489
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
3272
3490
  self.update_display(preserve_zoom=view_settings)
3273
3491
  self.pending_slice = None
3274
3492
 
@@ -3931,7 +4149,13 @@ class CustomTableView(QTableView):
3931
4149
  # Navigate to the Z-slice
3932
4150
  self.parent.slice_slider.setValue(int(centroid[0]))
3933
4151
  print(f"Found node {value} at Z-slice {centroid[0]}")
3934
- self.parent.create_highlight_overlay(node_indices=[value])
4152
+ 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:
4153
+ self.parent.mini_overlay = True
4154
+ self.parent.create_mini_overlay(node_indices = [value])
4155
+ else:
4156
+ self.parent.create_highlight_overlay(node_indices=[value])
4157
+ self.parent.clicked_values['nodes'] = []
4158
+ self.parent.clicked_values['edges'] = []
3935
4159
  self.parent.clicked_values['nodes'].append(value)
3936
4160
 
3937
4161
  # Highlight the value in both tables if it exists
@@ -3955,7 +4179,13 @@ class CustomTableView(QTableView):
3955
4179
  # Navigate to the Z-slice
3956
4180
  self.parent.slice_slider.setValue(int(centroid[0]))
3957
4181
  print(f"Found edge {value} at Z-slice {centroid[0]}")
3958
- self.parent.create_highlight_overlay(edge_indices=[value])
4182
+ 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:
4183
+ self.parent.mini_overlay = True
4184
+ self.parent.create_mini_overlay(edge_indices = [value])
4185
+ else:
4186
+ self.parent.create_highlight_overlay(edge_indices=[value])
4187
+ self.parent.clicked_values['nodes'] = []
4188
+ self.parent.clicked_values['edges'] = []
3959
4189
  self.parent.clicked_values['edges'].append(value)
3960
4190
 
3961
4191
  # Highlight the value in both tables if it exists
@@ -3983,12 +4213,24 @@ class CustomTableView(QTableView):
3983
4213
  self.parent.slice_slider.setValue(int(centroid1[0]))
3984
4214
  print(f"Found node pair {value[0]} and {value[1]} at Z-slices {centroid1[0]} and {centroid2[0]}, respectively")
3985
4215
  try:
3986
- self.parent.create_highlight_overlay(node_indices=[int(value[0]), int(value[1])], edge_indices = int(value[2]))
4216
+ 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:
4217
+ self.parent.mini_overlay = True
4218
+ self.parent.create_mini_overlay(node_indices=[int(value[0]), int(value[1])], edge_indices = int(value[2]))
4219
+ else:
4220
+ self.parent.create_highlight_overlay(node_indices=[int(value[0]), int(value[1])], edge_indices = int(value[2]))
4221
+ self.parent.clicked_values['nodes'] = []
4222
+ self.parent.clicked_values['edges'] = []
3987
4223
  self.parent.clicked_values['edges'].append(value[2])
3988
4224
  self.parent.clicked_values['nodes'].append(value[0])
3989
4225
  self.parent.clicked_values['nodes'].append(value[1])
3990
4226
  except:
3991
- self.parent.create_highlight_overlay(node_indices=[int(value[0]), int(value[1])])
4227
+ 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:
4228
+ self.parent.mini_overlay = True
4229
+ self.parent.create_mini_overlay(node_indices=[int(value[0]), int(value[1])])
4230
+ else:
4231
+ self.parent.create_highlight_overlay(node_indices=[int(value[0]), int(value[1])])
4232
+ self.parent.clicked_values['nodes'] = []
4233
+ self.parent.clicked_values['edges'] = []
3992
4234
  self.parent.clicked_values['nodes'].append(value[0])
3993
4235
  self.parent.clicked_values['nodes'].append(value[1])
3994
4236
 
@@ -4534,6 +4776,8 @@ class Show3dDialog(QDialog):
4534
4776
  arrays_4d.append(channel)
4535
4777
 
4536
4778
  if self.parent().highlight_overlay is not None:
4779
+ if self.parent().mini_overlay == True:
4780
+ self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
4537
4781
  arrays_3d.append(self.parent().highlight_overlay)
4538
4782
  colors.append(color_template[4])
4539
4783
 
@@ -4668,6 +4912,45 @@ class IdOverlayDialog(QDialog):
4668
4912
 
4669
4913
  self.accept()
4670
4914
 
4915
+ class ColorOverlayDialog(QDialog):
4916
+
4917
+ def __init__(self, parent=None):
4918
+
4919
+ super().__init__(parent)
4920
+ self.setWindowTitle("Generate Node (or Edge) -> Color Overlay?")
4921
+ self.setModal(True)
4922
+
4923
+ layout = QFormLayout(self)
4924
+
4925
+ self.down_factor = QLineEdit("")
4926
+ layout.addRow("down_factor (for speeding up overlay generation - optional):", self.down_factor)
4927
+
4928
+ # Add Run button
4929
+ run_button = QPushButton("Generate (Will go to Overlay 2)")
4930
+ run_button.clicked.connect(self.coloroverlay)
4931
+ layout.addWidget(run_button)
4932
+
4933
+ def coloroverlay(self):
4934
+
4935
+ down_factor = float(self.down_factor.text()) if self.down_factor.text().strip() else None
4936
+
4937
+ if self.parent().active_channel == 0:
4938
+ mode = 0
4939
+ self.sort = 'Node'
4940
+ else:
4941
+ mode = 1
4942
+ self.sort = 'Edge'
4943
+
4944
+
4945
+ result, legend = my_network.node_to_color(down_factor = down_factor, mode = mode)
4946
+
4947
+ self.parent().format_for_upperright_table(legend, f'{self.sort} Id', f'Encoding Val: {self.sort}', 'Legend')
4948
+
4949
+
4950
+ self.parent().load_channel(3, channel_data = result, data = True)
4951
+
4952
+ self.accept()
4953
+
4671
4954
 
4672
4955
  class ShuffleDialog(QDialog):
4673
4956
 
@@ -4708,11 +4991,15 @@ class ShuffleDialog(QDialog):
4708
4991
  accepted_target = self.target_selector.currentIndex()
4709
4992
 
4710
4993
  if accepted_mode == 4:
4994
+ if self.parent().mini_overlay == True:
4995
+ self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
4711
4996
  active_data = self.parent().highlight_overlay
4712
4997
  else:
4713
4998
  active_data = self.parent().channel_data[accepted_mode]
4714
4999
 
4715
5000
  if accepted_target == 4:
5001
+ if self.parent().mini_overlay == True:
5002
+ self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
4716
5003
  target_data = self.parent().highlight_overlay
4717
5004
  else:
4718
5005
  target_data = self.parent().channel_data[accepted_target]
@@ -6814,6 +7101,8 @@ class MaskDialog(QDialog):
6814
7101
  output_target = self.output_selector.currentIndex()
6815
7102
 
6816
7103
  if accepted_mode == 4:
7104
+ if self.parent().mini_overlay == True:
7105
+ self.parent().create_highlight_overlay(node_indices = self.parent().clicked_values['nodes'], edge_indices = self.parent().clicked_values['edges'])
6817
7106
  active_data = self.parent().highlight_overlay
6818
7107
  else:
6819
7108
  active_data = self.parent().channel_data[accepted_mode]
@@ -6916,11 +7205,16 @@ class WatershedDialog(QDialog):
6916
7205
  self.directory.setPlaceholderText("Leave empty for None")
6917
7206
  layout.addRow("Output Directory:", self.directory)
6918
7207
 
6919
- active_shape = self.parent().channel_data[self.parent().active_channel].shape[0]
7208
+ try:
6920
7209
 
6921
- if active_shape == 1:
6922
- self.default = 0.2
6923
- else:
7210
+ active_shape = self.parent().channel_data[self.parent().active_channel].shape[0]
7211
+
7212
+ if active_shape == 1:
7213
+ self.default = 0.2
7214
+ else:
7215
+ self.default = 0.05
7216
+
7217
+ except:
6924
7218
  self.default = 0.05
6925
7219
 
6926
7220
 
@@ -6949,6 +7243,9 @@ class WatershedDialog(QDialog):
6949
7243
  self.predownsample2.setPlaceholderText("Leave empty for None")
6950
7244
  layout.addRow("Smart Label GPU Downsample:", self.predownsample2)
6951
7245
 
7246
+ layout.addRow("Note:", QLabel(f"If the optimal proportion watershed output is still labeling spatially seperated objects with the same label, try right placing the result in nodes or edges\nthen right click the image and choose 'select all', followed by right clicking and 'selection' -> 'split non-touching labels'."))
7247
+
7248
+
6952
7249
  # Add Run button
6953
7250
  run_button = QPushButton("Run Watershed")
6954
7251
  run_button.clicked.connect(self.run_watershed)