nettracer3d 1.0.8__py3-none-any.whl → 1.0.9__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.

Potentially problematic release.


This version of nettracer3d might be problematic. Click here for more details.

@@ -461,8 +461,6 @@ class ImageViewerWindow(QMainWindow):
461
461
  self.last_paint_pos = None
462
462
 
463
463
  self.resume = False
464
-
465
- self.hold_update = False
466
464
  self._first_pan_done = False
467
465
 
468
466
 
@@ -878,23 +876,20 @@ class ImageViewerWindow(QMainWindow):
878
876
  elif self.scroll_direction > 0 and new_value <= self.slice_slider.maximum():
879
877
  self.slice_slider.setValue(new_value)
880
878
 
881
- def evaluate_mini(self, mode = 'nodes'):
882
- if mode == 'nodes':
883
- if self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2] > self.mini_thresh:
884
- self.mini_overlay = True
885
- self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
886
- else:
887
- self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
888
- elif mode == 'edges':
889
879
 
890
- if self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2] > self.mini_thresh:
891
- self.mini_overlay = True
892
- self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
893
- else:
894
- self.create_highlight_overlay(
895
- node_indices=self.clicked_values['nodes'],
896
- edge_indices=self.clicked_values['edges']
897
- )
880
+ def confirm_mini_thresh(self):
881
+
882
+ if self.shape[0] * self.shape[1] * self.shape[2] > self.mini_thresh:
883
+ self.mini_overlay = True
884
+ return True
885
+ else:
886
+ return False
887
+
888
+ def evaluate_mini(self, mode = 'nodes'):
889
+ if self.confirm_mini_thresh():
890
+ self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
891
+ else:
892
+ self.create_highlight_overlay(node_indices=self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
898
893
 
899
894
  def create_highlight_overlay(self, node_indices=None, edge_indices=None, overlay1_indices = None, overlay2_indices = None, bounds = False):
900
895
  """
@@ -1021,13 +1016,13 @@ class ImageViewerWindow(QMainWindow):
1021
1016
 
1022
1017
  # Combine results
1023
1018
  if node_overlay is not None:
1024
- self.highlight_overlay = np.maximum(self.highlight_overlay, node_overlay)
1019
+ self.highlight_overlay = np.maximum(self.highlight_overlay, node_overlay).astype(np.uint8)
1025
1020
  if edge_overlay is not None:
1026
- self.highlight_overlay = np.maximum(self.highlight_overlay, edge_overlay)
1021
+ self.highlight_overlay = np.maximum(self.highlight_overlay, edge_overlay).astype(np.uint8)
1027
1022
  if overlay1_overlay is not None:
1028
- self.highlight_overlay = np.maximum(self.highlight_overlay, overlay1_overlay)
1023
+ self.highlight_overlay = np.maximum(self.highlight_overlay, overlay1_overlay).astype(np.uint8)
1029
1024
  if overlay2_overlay is not None:
1030
- self.highlight_overlay = np.maximum(self.highlight_overlay, overlay2_overlay)
1025
+ self.highlight_overlay = np.maximum(self.highlight_overlay, overlay2_overlay).astype(np.uint8)
1031
1026
 
1032
1027
  # Update display
1033
1028
  self.update_display(preserve_zoom=(current_xlim, current_ylim))
@@ -1036,6 +1031,9 @@ class ImageViewerWindow(QMainWindow):
1036
1031
 
1037
1032
  """Highlight overlay generation method specific for the segmenter interactive mode"""
1038
1033
 
1034
+ self.mini_overlay_data = None
1035
+ self.highlight_overlay = None
1036
+
1039
1037
  def process_chunk_bounds(chunk_data, indices_to_check):
1040
1038
  """Process a single chunk of the array to create highlight mask"""
1041
1039
  mask = (chunk_data >= indices_to_check[0]) & (chunk_data <= indices_to_check[1])
@@ -1308,6 +1306,9 @@ class ImageViewerWindow(QMainWindow):
1308
1306
  select_nodes = select_all_menu.addAction("Nodes")
1309
1307
  select_both = select_all_menu.addAction("Nodes + Edges")
1310
1308
  select_edges = select_all_menu.addAction("Edges")
1309
+ select_net_nodes = select_all_menu.addAction("Nodes in Network")
1310
+ select_net_both = select_all_menu.addAction("Nodes + Edges in Network")
1311
+ select_net_edges = select_all_menu.addAction("Edges in Network")
1311
1312
  context_menu.addMenu(select_all_menu)
1312
1313
 
1313
1314
  if len(self.clicked_values['nodes']) > 0 or len(self.clicked_values['edges']) > 0:
@@ -1382,6 +1383,9 @@ class ImageViewerWindow(QMainWindow):
1382
1383
  select_nodes.triggered.connect(lambda: self.handle_select_all(edges = False, nodes = True))
1383
1384
  select_both.triggered.connect(lambda: self.handle_select_all(edges = True))
1384
1385
  select_edges.triggered.connect(lambda: self.handle_select_all(edges = True, nodes = False))
1386
+ select_net_nodes.triggered.connect(lambda: self.handle_select_all(edges = False, nodes = True, network = True))
1387
+ select_net_both.triggered.connect(lambda: self.handle_select_all(edges = True, network = True))
1388
+ select_net_edges.triggered.connect(lambda: self.handle_select_all(edges = True, nodes = False, network = True))
1385
1389
  if self.highlight_overlay is not None or self.mini_overlay_data is not None:
1386
1390
  highlight_select = context_menu.addAction("Add highlight in network selection")
1387
1391
  highlight_select.triggered.connect(self.handle_highlight_select)
@@ -2066,12 +2070,15 @@ class ImageViewerWindow(QMainWindow):
2066
2070
 
2067
2071
 
2068
2072
 
2069
- def handle_select_all(self, nodes = True, edges = False):
2073
+ def handle_select_all(self, nodes = True, edges = False, network = False):
2070
2074
 
2071
2075
  try:
2072
2076
 
2073
2077
  if nodes:
2074
- nodes = list(np.unique(my_network.nodes))
2078
+ if not network:
2079
+ nodes = list(np.unique(my_network.nodes))
2080
+ else:
2081
+ nodes = list(set(my_network.network_lists[0] + my_network.network_lists[1]))
2075
2082
  if nodes[0] == 0:
2076
2083
  del nodes[0]
2077
2084
  num = (self.channel_data[0].shape[0] * self.channel_data[0].shape[1] * self.channel_data[0].shape[2])
@@ -2079,7 +2086,10 @@ class ImageViewerWindow(QMainWindow):
2079
2086
  else:
2080
2087
  nodes = []
2081
2088
  if edges:
2082
- edges = list(np.unique(my_network.edges))
2089
+ if not network:
2090
+ edges = list(np.unique(my_network.edges))
2091
+ else:
2092
+ edges = my_network.network_lists[2]
2083
2093
  num = (self.channel_data[1].shape[0] * self.channel_data[1].shape[1] * self.channel_data[1].shape[2])
2084
2094
  if edges[0] == 0:
2085
2095
  del edges[0]
@@ -2202,7 +2212,7 @@ class ImageViewerWindow(QMainWindow):
2202
2212
  except:
2203
2213
  pass
2204
2214
 
2205
- self.format_for_upperright_table(info_dict, title = f'Info on Object')
2215
+ self.format_for_upperright_table(info_dict, title = f'Info on Object', sort = False)
2206
2216
 
2207
2217
  except:
2208
2218
  pass
@@ -3695,17 +3705,23 @@ class ImageViewerWindow(QMainWindow):
3695
3705
 
3696
3706
  # Add highlight overlays if they exist (with downsampling)
3697
3707
  if self.mini_overlay and self.highlight and self.machine_window is None:
3698
- display_overlay = crop_and_downsample_image(self.mini_overlay_data, y_min_padded, y_max_padded, x_min_padded, x_max_padded, downsample_factor)
3699
- highlight_rgba = self.create_highlight_rgba(display_overlay, yellow=True)
3700
- composite = self.blend_layers(composite, highlight_rgba)
3708
+ try:
3709
+ display_overlay = crop_and_downsample_image(self.mini_overlay_data, y_min_padded, y_max_padded, x_min_padded, x_max_padded, downsample_factor)
3710
+ highlight_rgba = self.create_highlight_rgba(display_overlay, yellow=True)
3711
+ composite = self.blend_layers(composite, highlight_rgba)
3712
+ except:
3713
+ pass
3701
3714
  elif self.highlight_overlay is not None and self.highlight:
3702
- highlight_slice = self.highlight_overlay[self.current_slice]
3703
- display_highlight = crop_and_downsample_image(highlight_slice, y_min_padded, y_max_padded, x_min_padded, x_max_padded, downsample_factor)
3704
- if self.machine_window is None:
3705
- highlight_rgba = self.create_highlight_rgba(display_highlight, yellow=True)
3706
- else:
3707
- highlight_rgba = self.create_highlight_rgba(display_highlight, yellow=False)
3708
- composite = self.blend_layers(composite, highlight_rgba)
3715
+ try:
3716
+ highlight_slice = self.highlight_overlay[self.current_slice]
3717
+ display_highlight = crop_and_downsample_image(highlight_slice, y_min_padded, y_max_padded, x_min_padded, x_max_padded, downsample_factor)
3718
+ if self.machine_window is None:
3719
+ highlight_rgba = self.create_highlight_rgba(display_highlight, yellow=True)
3720
+ else:
3721
+ highlight_rgba = self.create_highlight_rgba(display_highlight, yellow=False)
3722
+ composite = self.blend_layers(composite, highlight_rgba)
3723
+ except:
3724
+ pass
3709
3725
 
3710
3726
  # Convert to 0-255 range for display
3711
3727
  return (composite * 255).astype(np.uint8)
@@ -4517,6 +4533,8 @@ class ImageViewerWindow(QMainWindow):
4517
4533
  for i in range(4):
4518
4534
  load_action = load_menu.addAction(f"Load {self.channel_names[i]}")
4519
4535
  load_action.triggered.connect(lambda checked, ch=i: self.load_channel(ch))
4536
+ load_action = load_menu.addAction("Load Full-Sized Highlight Overlay")
4537
+ load_action.triggered.connect(lambda: self.load_channel(channel_index = 4, load_highlight = True))
4520
4538
  load_action = load_menu.addAction("Load Network")
4521
4539
  load_action.triggered.connect(self.load_network)
4522
4540
  load_action = load_menu.addAction("Load From Excel Helper")
@@ -4770,7 +4788,10 @@ class ImageViewerWindow(QMainWindow):
4770
4788
  # Invalid input - reset to default
4771
4789
  self.downsample_factor = 1
4772
4790
 
4773
- self.throttle = self.shape[1] * self.shape[2] > 3000 * 3000 * self.downsample_factor
4791
+ try:
4792
+ self.throttle = self.shape[1] * self.shape[2] > 3000 * 3000 * self.downsample_factor
4793
+ except:
4794
+ self.throttle = False
4774
4795
 
4775
4796
  # Optional: Trigger display update if you want immediate effect
4776
4797
  if update:
@@ -4938,7 +4959,7 @@ class ImageViewerWindow(QMainWindow):
4938
4959
 
4939
4960
 
4940
4961
 
4941
- def format_for_upperright_table(self, data, metric='Metric', value='Value', title=None):
4962
+ def format_for_upperright_table(self, data, metric='Metric', value='Value', title=None, sort = True):
4942
4963
  """
4943
4964
  Format dictionary or list data for display in upper right table.
4944
4965
 
@@ -5024,11 +5045,12 @@ class ImageViewerWindow(QMainWindow):
5024
5045
  table = CustomTableView(self)
5025
5046
  table.setModel(PandasModel(df))
5026
5047
 
5027
- try:
5028
- first_column_name = table.model()._data.columns[0]
5029
- table.sort_table(first_column_name, ascending=True)
5030
- except:
5031
- pass
5048
+ if sort:
5049
+ try:
5050
+ first_column_name = table.model()._data.columns[0]
5051
+ table.sort_table(first_column_name, ascending=True)
5052
+ except:
5053
+ pass
5032
5054
 
5033
5055
  # Add to tabbed widget
5034
5056
  if title is None:
@@ -5620,6 +5642,7 @@ class ImageViewerWindow(QMainWindow):
5620
5642
  self.last_saved = os.path.dirname(directory)
5621
5643
  self.last_save_name = directory
5622
5644
 
5645
+ self.channel_data = [None] * 5
5623
5646
  if directory != "":
5624
5647
 
5625
5648
  self.reset(network = True, xy_scale = 1, z_scale = 1, nodes = True, edges = True, network_overlay = True, id_overlay = True, update = False)
@@ -5700,6 +5723,8 @@ class ImageViewerWindow(QMainWindow):
5700
5723
  f"Failed to load Network 3D Object: {str(e)}"
5701
5724
  )
5702
5725
 
5726
+
5727
+
5703
5728
  def load_network(self):
5704
5729
  """Load in the network from a .xlsx (need to add .csv support)"""
5705
5730
 
@@ -5940,12 +5965,41 @@ class ImageViewerWindow(QMainWindow):
5940
5965
  msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
5941
5966
  return msg.exec() == QMessageBox.StandardButton.Yes
5942
5967
 
5943
- def load_channel(self, channel_index, channel_data=None, data=False, assign_shape = True, preserve_zoom = None, end_paint = False, begin_paint = False, color = False):
5968
+ def get_scaling_metadata_only(self, filename):
5969
+ # This only reads headers/metadata, not image data
5970
+ with tifffile.TiffFile(filename) as tif:
5971
+ x_scale = y_scale = z_scale = unit = None
5972
+
5973
+ # ImageJ metadata (very lightweight)
5974
+ if hasattr(tif, 'imagej_metadata') and tif.imagej_metadata:
5975
+ metadata = tif.imagej_metadata
5976
+ z_scale = metadata.get('spacing')
5977
+ unit = metadata.get('unit')
5978
+
5979
+ # TIFF tags (also lightweight - just header info)
5980
+ page = tif.pages[0] # This doesn't load image data
5981
+ tags = page.tags
5982
+
5983
+ if 'XResolution' in tags:
5984
+ x_res = tags['XResolution'].value
5985
+ x_scale = x_res[1] / x_res[0] if isinstance(x_res, tuple) else 1.0 / x_res
5986
+
5987
+ if 'YResolution' in tags:
5988
+ y_res = tags['YResolution'].value
5989
+ y_scale = y_res[1] / y_res[0] if isinstance(y_res, tuple) else 1.0 / y_res
5990
+
5991
+ if x_scale is None:
5992
+ x_scale = 1
5993
+ if z_scale is None:
5994
+ z_scale = 1
5995
+
5996
+ return x_scale, z_scale
5997
+
5998
+ def load_channel(self, channel_index, channel_data=None, data=False, assign_shape = True, preserve_zoom = None, end_paint = False, begin_paint = False, color = False, load_highlight = False):
5944
5999
  """Load a channel and enable active channel selection if needed."""
5945
6000
 
5946
6001
  try:
5947
6002
 
5948
- self.hold_update = True
5949
6003
  if not data: # For solo loading
5950
6004
  filename, _ = QFileDialog.getOpenFileName(
5951
6005
  self,
@@ -5965,6 +6019,13 @@ class ImageViewerWindow(QMainWindow):
5965
6019
  try:
5966
6020
  if file_extension in ['tif', 'tiff']:
5967
6021
  import tifffile
6022
+ self.channel_data[channel_index] = None
6023
+ if (self.channel_data[0] is None and self.channel_data[1] is None) and (channel_index == 0 or channel_index == 1):
6024
+ try:
6025
+ my_network.xy_scale, my_network.z_scale = self.get_scaling_metadata_only(filename)
6026
+ print(f"xy_scale property set to {my_network.xy_scale}; z_scale property set to {my_network.z_scale}")
6027
+ except:
6028
+ pass
5968
6029
  self.channel_data[channel_index] = tifffile.imread(filename)
5969
6030
 
5970
6031
  elif file_extension == 'nii':
@@ -6070,51 +6131,52 @@ class ImageViewerWindow(QMainWindow):
6070
6131
  my_network.id_overlay = self.channel_data[channel_index]
6071
6132
 
6072
6133
  # Enable the channel button
6073
- self.channel_buttons[channel_index].setEnabled(True)
6074
- self.delete_buttons[channel_index].setEnabled(True)
6134
+ if channel_index != 4:
6135
+ self.channel_buttons[channel_index].setEnabled(True)
6136
+ self.delete_buttons[channel_index].setEnabled(True)
6075
6137
 
6076
6138
 
6077
- # Enable active channel selector if this is the first channel loaded
6078
- if not self.active_channel_combo.isEnabled():
6079
- self.active_channel_combo.setEnabled(True)
6139
+ # Enable active channel selector if this is the first channel loaded
6140
+ if not self.active_channel_combo.isEnabled():
6141
+ self.active_channel_combo.setEnabled(True)
6080
6142
 
6081
- # Update slider range if this is the first channel loaded
6082
- try:
6083
- if len(self.channel_data[channel_index].shape) == 3 or len(self.channel_data[channel_index].shape) == 4:
6084
- if not self.slice_slider.isEnabled():
6085
- self.slice_slider.setEnabled(True)
6086
- self.slice_slider.setMinimum(0)
6087
- self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
6088
- if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
6089
- self.current_slice = self.slice_slider.value()
6143
+ # Update slider range if this is the first channel loaded
6144
+ try:
6145
+ if len(self.channel_data[channel_index].shape) == 3 or len(self.channel_data[channel_index].shape) == 4:
6146
+ if not self.slice_slider.isEnabled():
6147
+ self.slice_slider.setEnabled(True)
6148
+ self.slice_slider.setMinimum(0)
6149
+ self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
6150
+ if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
6151
+ self.current_slice = self.slice_slider.value()
6152
+ else:
6153
+ self.slice_slider.setValue(0)
6154
+ self.current_slice = 0
6090
6155
  else:
6091
- self.slice_slider.setValue(0)
6092
- self.current_slice = 0
6156
+ self.slice_slider.setEnabled(True)
6157
+ self.slice_slider.setMinimum(0)
6158
+ self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
6159
+ if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
6160
+ self.current_slice = self.slice_slider.value()
6161
+ else:
6162
+ self.current_slice = 0
6163
+ self.slice_slider.setValue(0)
6093
6164
  else:
6094
- self.slice_slider.setEnabled(True)
6095
- self.slice_slider.setMinimum(0)
6096
- self.slice_slider.setMaximum(self.channel_data[channel_index].shape[0] - 1)
6097
- if self.slice_slider.value() < self.channel_data[channel_index].shape[0] - 1:
6098
- self.current_slice = self.slice_slider.value()
6099
- else:
6100
- self.current_slice = 0
6101
- self.slice_slider.setValue(0)
6102
- else:
6103
- self.slice_slider.setEnabled(False)
6104
- except:
6105
- pass
6165
+ self.slice_slider.setEnabled(False)
6166
+ except:
6167
+ pass
6106
6168
 
6107
-
6108
- # If this is the first channel loaded, make it active
6109
- if all(not btn.isEnabled() for btn in self.channel_buttons[:channel_index]):
6110
- self.set_active_channel(channel_index)
6169
+
6170
+ # If this is the first channel loaded, make it active
6171
+ if all(not btn.isEnabled() for btn in self.channel_buttons[:channel_index]):
6172
+ self.set_active_channel(channel_index)
6111
6173
 
6112
- if not self.channel_buttons[channel_index].isChecked():
6113
- self.channel_buttons[channel_index].click()
6174
+ if not self.channel_buttons[channel_index].isChecked():
6175
+ self.channel_buttons[channel_index].click()
6114
6176
 
6115
- self.min_max[channel_index][0] = np.min(self.channel_data[channel_index])
6116
- self.min_max[channel_index][1] = np.max(self.channel_data[channel_index])
6117
- self.volume_dict[channel_index] = None #reset volumes
6177
+ self.min_max[channel_index][0] = np.min(self.channel_data[channel_index])
6178
+ self.min_max[channel_index][1] = np.max(self.channel_data[channel_index])
6179
+ self.volume_dict[channel_index] = None #reset volumes
6118
6180
 
6119
6181
  try:
6120
6182
  if assign_shape: #keep original shape tracked to undo resampling.
@@ -6138,7 +6200,6 @@ class ImageViewerWindow(QMainWindow):
6138
6200
 
6139
6201
  self.img_height, self.img_width = self.shape[1], self.shape[2]
6140
6202
  self.original_ylim, self.original_xlim = (self.shape[1] + 0.5, -0.5), (-0.5, self.shape[2] - 0.5)
6141
- #print(self.original_xlim)
6142
6203
 
6143
6204
  self.completed_paint_strokes = [] #Reset pending paint operations
6144
6205
  self.current_stroke_points = []
@@ -6148,6 +6209,12 @@ class ImageViewerWindow(QMainWindow):
6148
6209
  self.current_operation = []
6149
6210
  self.current_operation_type = None
6150
6211
 
6212
+ if load_highlight:
6213
+ self.highlight_overlay = n3d.binarize(self.channel_data[4].astype(np.uint8))
6214
+ self.mini_overlay_data = None
6215
+ self.mini_overlay = False
6216
+ self.channel_data[4] = None
6217
+
6151
6218
  if self.pan_mode:
6152
6219
  self.pan_button.click()
6153
6220
  if self.show_channels:
@@ -6156,7 +6223,6 @@ class ImageViewerWindow(QMainWindow):
6156
6223
  elif not end_paint:
6157
6224
 
6158
6225
  self.update_display(reset_resize = reset_resize, preserve_zoom = preserve_zoom)
6159
-
6160
6226
 
6161
6227
  except Exception as e:
6162
6228
 
@@ -6165,7 +6231,7 @@ class ImageViewerWindow(QMainWindow):
6165
6231
  QMessageBox.critical(
6166
6232
  self,
6167
6233
  "Error Loading File",
6168
- f"Failed to load tiff file: {str(e)}"
6234
+ f"Failed to load file: {str(e)}"
6169
6235
  )
6170
6236
 
6171
6237
  def delete_channel(self, channel_index, called = True, update = True):
@@ -6394,15 +6460,13 @@ class ImageViewerWindow(QMainWindow):
6394
6460
  self.pm.convert_virtual_strokes_to_data()
6395
6461
  self.current_slice = slice_value
6396
6462
  if self.preview:
6463
+ self.highlight_overlay = None
6464
+ self.mini_overlay_data = None
6465
+ self.mini_overlay = False
6397
6466
  self.create_highlight_overlay_slice(self.targs, bounds=self.bounds)
6398
6467
  elif self.mini_overlay == True: #If we are rendering the highlight overlay for selected values one at a time.
6399
6468
  self.create_mini_overlay(node_indices = self.clicked_values['nodes'], edge_indices = self.clicked_values['edges'])
6400
- if not self.hold_update:
6401
- self.update_display(preserve_zoom=view_settings)
6402
- else:
6403
- self.hold_update = False
6404
- #if self.machine_window is not None:
6405
- #self.machine_window.poke_segmenter()
6469
+ self.update_display(preserve_zoom=view_settings)
6406
6470
  if self.pan_mode:
6407
6471
  self.pan_button.click()
6408
6472
  self.pending_slice = None
@@ -6419,7 +6483,6 @@ class ImageViewerWindow(QMainWindow):
6419
6483
  self.channel_brightness[channel_index]['max'] = max_val / 65535
6420
6484
  self.update_display(preserve_zoom = (current_xlim, current_ylim))
6421
6485
 
6422
-
6423
6486
  def update_display(self, preserve_zoom=None, dims=None, called=False, reset_resize=False, skip=False):
6424
6487
  """Optimized display update with view-based cropping for performance."""
6425
6488
  try:
@@ -6645,7 +6708,7 @@ class ImageViewerWindow(QMainWindow):
6645
6708
  # Handle preview, overlays, and measurements (apply cropping here too)
6646
6709
 
6647
6710
  # Overlay handling (optimized with cropping and downsampling)
6648
- if self.mini_overlay and self.highlight and self.machine_window is None:
6711
+ if self.mini_overlay and self.highlight and self.machine_window is None and not self.preview:
6649
6712
  highlight_cmap = LinearSegmentedColormap.from_list('highlight', [(0, 0, 0, 0), (1, 1, 0, 1)])
6650
6713
  display_overlay = crop_and_downsample_image(
6651
6714
  self.mini_overlay_data, y_min_padded, y_max_padded,
@@ -6774,8 +6837,6 @@ class ImageViewerWindow(QMainWindow):
6774
6837
  #print(traceback.format_exc())
6775
6838
 
6776
6839
 
6777
-
6778
-
6779
6840
  def get_channel_image(self, channel):
6780
6841
  """Find the matplotlib image object for a specific channel."""
6781
6842
  if not hasattr(self.ax, 'images'):
@@ -10028,6 +10089,22 @@ class InteractionDialog(QDialog):
10028
10089
  self.mode_selector.setCurrentIndex(0) # Default to Mode 1
10029
10090
  layout.addRow("Execution Mode:", self.mode_selector)
10030
10091
 
10092
+ self.length = QPushButton("Return Lengths")
10093
+ self.length.setCheckable(True)
10094
+ self.length.setChecked(False)
10095
+ layout.addRow("(Will Skeletonize the Edge Mirror and use that to calculate adjacent length of edges, as opposed to default volumes):", self.length)
10096
+
10097
+ self.auto = QPushButton("Auto")
10098
+ self.auto.setCheckable(True)
10099
+ try:
10100
+ if self.parent().shape[0] == 1:
10101
+ self.auto.setChecked(False)
10102
+ else:
10103
+ self.auto.setChecked(True)
10104
+ except:
10105
+ self.auto.setChecked(False)
10106
+ layout.addRow("(If Above): Attempt to Auto Correct Skeleton Looping:", self.auto)
10107
+
10031
10108
  self.fastdil = QPushButton("Fast Dilate")
10032
10109
  self.fastdil.setCheckable(True)
10033
10110
  self.fastdil.setChecked(False)
@@ -10051,10 +10128,16 @@ class InteractionDialog(QDialog):
10051
10128
 
10052
10129
 
10053
10130
  fastdil = self.fastdil.isChecked()
10131
+ length = self.length.isChecked()
10132
+ auto = self.auto.isChecked()
10054
10133
 
10055
- result = my_network.interactions(search = node_search, cores = accepted_mode, fastdil = fastdil)
10134
+ result = my_network.interactions(search = node_search, cores = accepted_mode, skele = length, length = length, auto = auto, fastdil = fastdil)
10135
+
10136
+ if not length:
10137
+ self.parent().format_for_upperright_table(result, 'Node ID', ['Volume of Nearby Edge (Scaled)', 'Volume of Search Region (Scaled)'], title = 'Node/Edge Interactions')
10138
+ else:
10139
+ self.parent().format_for_upperright_table(result, 'Node ID', ['~Length of Nearby Edge (Scaled)', 'Volume of Search Region (Scaled)'], title = 'Node/Edge Interactions')
10056
10140
 
10057
- self.parent().format_for_upperright_table(result, 'Node ID', ['Volume of Nearby Edge (Scaled)', 'Volume of Search Region'], title = 'Node/Edge Interactions')
10058
10141
 
10059
10142
  self.accept()
10060
10143
 
@@ -11306,12 +11389,11 @@ class ThresholdDialog(QDialog):
11306
11389
  print("Error - please calculate network first")
11307
11390
  return
11308
11391
 
11309
- if self.parent().mini_overlay_data is not None:
11310
- self.parent().mini_overlay_data = None
11311
-
11312
11392
  thresh_window = ThresholdWindow(self.parent(), accepted_mode)
11313
11393
  thresh_window.show() # Non-modal window
11314
11394
  self.highlight_overlay = None
11395
+ #self.mini_overlay = False
11396
+ self.mini_overlay_data = None
11315
11397
  self.accept()
11316
11398
  except:
11317
11399
  import traceback
@@ -12285,16 +12367,23 @@ class ThresholdWindow(QMainWindow):
12285
12367
  self.parent().bounds = False
12286
12368
 
12287
12369
  elif accepted_mode == 0:
12288
- targ_shape = self.parent().channel_data[self.parent().active_channel].shape
12289
- if (targ_shape[0] + targ_shape[1] + targ_shape[2]) > 2500: #Take a simpler histogram on big arrays
12290
- temp_max = np.max(self.parent().channel_data[self.parent().active_channel])
12291
- temp_min = np.min(self.parent().channel_data[self.parent().active_channel])
12292
- temp_array = n3d.downsample(self.parent().channel_data[self.parent().active_channel], 5)
12293
- self.histo_list = temp_array.flatten().tolist()
12294
- self.histo_list.append(temp_min)
12295
- self.histo_list.append(temp_max)
12296
- else: #Otherwise just use full array data
12297
- self.histo_list = self.parent().channel_data[self.parent().active_channel].flatten().tolist()
12370
+ data = self.parent().channel_data[self.parent().active_channel]
12371
+ nonzero_data = data[data != 0]
12372
+
12373
+ if nonzero_data.size > 578009537:
12374
+ # For large arrays, use numpy histogram directly
12375
+ counts, bin_edges = np.histogram(nonzero_data, bins= min(int(np.sqrt(nonzero_data.size)), 500), density=False)
12376
+ # Store min/max separately if needed elsewhere
12377
+ self.data_min = np.min(nonzero_data)
12378
+ self.data_max = np.max(nonzero_data)
12379
+ self.histo_list = [self.data_min, self.data_max]
12380
+ else:
12381
+ # For smaller arrays, can still use histogram method for consistency
12382
+ counts, bin_edges = np.histogram(nonzero_data, bins='auto', density=False)
12383
+ self.data_min = np.min(nonzero_data)
12384
+ self.data_max = np.max(nonzero_data)
12385
+ self.histo_list = [self.data_min, self.data_max]
12386
+
12298
12387
  self.bounds = True
12299
12388
  self.parent().bounds = True
12300
12389
 
@@ -12307,16 +12396,26 @@ class ThresholdWindow(QMainWindow):
12307
12396
  layout.addWidget(self.canvas)
12308
12397
 
12309
12398
  # Pre-compute histogram with numpy
12310
- counts, bin_edges = np.histogram(self.histo_list, bins=50)
12311
- bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
12399
+ if accepted_mode != 0:
12400
+ counts, bin_edges = np.histogram(self.histo_list, bins=50)
12401
+ bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
12402
+ # Store histogram bounds
12403
+ if self.bounds:
12404
+ self.data_min = 0
12405
+ else:
12406
+ self.data_min = min(self.histo_list)
12407
+ self.data_max = max(self.histo_list)
12408
+ else:
12409
+ bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
12410
+ bin_width = bin_edges[1] - bin_edges[0]
12312
12411
 
12313
12412
  # Plot pre-computed histogram
12314
12413
  self.ax = fig.add_subplot(111)
12315
12414
  self.ax.bar(bin_centers, counts, width=bin_edges[1] - bin_edges[0], alpha=0.5)
12316
12415
 
12317
12416
  # Add vertical lines for thresholds
12318
- self.min_line = self.ax.axvline(min(self.histo_list), color='r')
12319
- self.max_line = self.ax.axvline(max(self.histo_list), color='b')
12417
+ self.min_line = self.ax.axvline(self.data_min, color='r')
12418
+ self.max_line = self.ax.axvline(self.data_max, color='b')
12320
12419
 
12321
12420
  # Connect events for dragging
12322
12421
  self.canvas.mpl_connect('button_press_event', self.on_press)
@@ -12324,13 +12423,6 @@ class ThresholdWindow(QMainWindow):
12324
12423
  self.canvas.mpl_connect('button_release_event', self.on_release)
12325
12424
 
12326
12425
  self.dragging = None
12327
-
12328
- # Store histogram bounds
12329
- if self.bounds:
12330
- self.data_min = 0
12331
- else:
12332
- self.data_min = min(self.histo_list)
12333
- self.data_max = max(self.histo_list)
12334
12426
 
12335
12427
  # Create form layout for inputs
12336
12428
  form_layout = QFormLayout()
@@ -15285,31 +15377,81 @@ class HistogramSelector(QWidget):
15285
15377
  """)
15286
15378
  layout.addWidget(button)
15287
15379
 
15380
+
15288
15381
  def shortest_path_histogram(self):
15289
15382
  try:
15290
- shortest_path_lengths = dict(nx.all_pairs_shortest_path_length(self.G))
15291
- diameter = max(nx.eccentricity(self.G, sp=shortest_path_lengths).values())
15292
- path_lengths = np.zeros(diameter + 1, dtype=int)
15293
-
15294
- for pls in shortest_path_lengths.values():
15295
- pl, cnts = np.unique(list(pls.values()), return_counts=True)
15383
+ # Check if graph has multiple disconnected components
15384
+ components = list(nx.connected_components(self.G))
15385
+
15386
+ if len(components) > 1:
15387
+ print(f"Warning: Graph has {len(components)} disconnected components. Computing shortest paths within each component separately.")
15388
+
15389
+ # Initialize variables to collect data from all components
15390
+ all_path_lengths = []
15391
+ max_diameter = 0
15392
+
15393
+ # Process each component separately
15394
+ for i, component in enumerate(components):
15395
+ subgraph = self.G.subgraph(component)
15396
+
15397
+ if len(component) < 2:
15398
+ # Skip single-node components (no paths to compute)
15399
+ continue
15400
+
15401
+ # Compute shortest paths for this component
15402
+ shortest_path_lengths = dict(nx.all_pairs_shortest_path_length(subgraph))
15403
+ component_diameter = max(nx.eccentricity(subgraph, sp=shortest_path_lengths).values())
15404
+ max_diameter = max(max_diameter, component_diameter)
15405
+
15406
+ # Collect path lengths from this component
15407
+ for pls in shortest_path_lengths.values():
15408
+ all_path_lengths.extend(list(pls.values()))
15409
+
15410
+ # Remove self-paths (length 0) and create histogram
15411
+ all_path_lengths = [pl for pl in all_path_lengths if pl > 0]
15412
+
15413
+ if not all_path_lengths:
15414
+ print("No paths found across components (only single-node components)")
15415
+ return
15416
+
15417
+ # Create combined histogram
15418
+ path_lengths = np.zeros(max_diameter + 1, dtype=int)
15419
+ pl, cnts = np.unique(all_path_lengths, return_counts=True)
15296
15420
  path_lengths[pl] += cnts
15297
-
15421
+
15422
+ title_suffix = f" (across {len(components)} components)"
15423
+
15424
+ else:
15425
+ # Single component
15426
+ shortest_path_lengths = dict(nx.all_pairs_shortest_path_length(self.G))
15427
+ diameter = max(nx.eccentricity(self.G, sp=shortest_path_lengths).values())
15428
+ path_lengths = np.zeros(diameter + 1, dtype=int)
15429
+ for pls in shortest_path_lengths.values():
15430
+ pl, cnts = np.unique(list(pls.values()), return_counts=True)
15431
+ path_lengths[pl] += cnts
15432
+ max_diameter = diameter
15433
+ title_suffix = ""
15434
+
15435
+ # Generate visualization and results (same for both cases)
15298
15436
  freq_percent = 100 * path_lengths[1:] / path_lengths[1:].sum()
15299
-
15300
15437
  fig, ax = plt.subplots(figsize=(15, 8))
15301
- ax.bar(np.arange(1, diameter + 1), height=freq_percent)
15438
+ ax.bar(np.arange(1, max_diameter + 1), height=freq_percent)
15302
15439
  ax.set_title(
15303
- "Distribution of shortest path length in G", fontdict={"size": 35}, loc="center"
15440
+ f"Distribution of shortest path length in G{title_suffix}",
15441
+ fontdict={"size": 35}, loc="center"
15304
15442
  )
15305
15443
  ax.set_xlabel("Shortest Path Length", fontdict={"size": 22})
15306
15444
  ax.set_ylabel("Frequency (%)", fontdict={"size": 22})
15307
15445
  plt.show()
15308
15446
 
15309
15447
  freq_dict = {freq: length for length, freq in enumerate(freq_percent, start=1)}
15310
- self.network_analysis.format_for_upperright_table(freq_dict, metric='Frequency (%)',
15311
- value='Shortest Path Length',
15312
- title="Distribution of shortest path length in G")
15448
+ self.network_analysis.format_for_upperright_table(
15449
+ freq_dict,
15450
+ metric='Frequency (%)',
15451
+ value='Shortest Path Length',
15452
+ title=f"Distribution of shortest path length in G{title_suffix}"
15453
+ )
15454
+
15313
15455
  except Exception as e:
15314
15456
  print(f"Error generating shortest path histogram: {e}")
15315
15457
 
@@ -15328,20 +15470,62 @@ class HistogramSelector(QWidget):
15328
15470
  title="Degree Centrality Table")
15329
15471
  except Exception as e:
15330
15472
  print(f"Error generating degree centrality histogram: {e}")
15331
-
15473
+
15332
15474
  def betweenness_centrality_histogram(self):
15333
15475
  try:
15334
- betweenness_centrality = nx.centrality.betweenness_centrality(self.G)
15476
+ # Check if graph has multiple disconnected components
15477
+ components = list(nx.connected_components(self.G))
15478
+
15479
+ if len(components) > 1:
15480
+ print(f"Warning: Graph has {len(components)} disconnected components. Computing betweenness centrality within each component separately.")
15481
+
15482
+ # Initialize dictionary to collect betweenness centrality from all components
15483
+ combined_betweenness_centrality = {}
15484
+
15485
+ # Process each component separately
15486
+ for i, component in enumerate(components):
15487
+ if len(component) < 2:
15488
+ # For single-node components, betweenness centrality is 0
15489
+ for node in component:
15490
+ combined_betweenness_centrality[node] = 0.0
15491
+ continue
15492
+
15493
+ # Create subgraph for this component
15494
+ subgraph = self.G.subgraph(component)
15495
+
15496
+ # Compute betweenness centrality for this component
15497
+ component_betweenness = nx.centrality.betweenness_centrality(subgraph)
15498
+
15499
+ # Add to combined results
15500
+ combined_betweenness_centrality.update(component_betweenness)
15501
+
15502
+ betweenness_centrality = combined_betweenness_centrality
15503
+ title_suffix = f" (across {len(components)} components)"
15504
+
15505
+ else:
15506
+ # Single component
15507
+ betweenness_centrality = nx.centrality.betweenness_centrality(self.G)
15508
+ title_suffix = ""
15509
+
15510
+ # Generate visualization and results (same for both cases)
15335
15511
  plt.figure(figsize=(15, 8))
15336
15512
  plt.hist(betweenness_centrality.values(), bins=100)
15337
15513
  plt.xticks(ticks=[0, 0.02, 0.1, 0.2, 0.3, 0.4, 0.5])
15338
- plt.title("Betweenness Centrality Histogram ", fontdict={"size": 35}, loc="center")
15514
+ plt.title(
15515
+ f"Betweenness Centrality Histogram{title_suffix}",
15516
+ fontdict={"size": 35}, loc="center"
15517
+ )
15339
15518
  plt.xlabel("Betweenness Centrality", fontdict={"size": 20})
15340
15519
  plt.ylabel("Counts", fontdict={"size": 20})
15341
15520
  plt.show()
15342
- self.network_analysis.format_for_upperright_table(betweenness_centrality, metric='Node',
15343
- value='Betweenness Centrality',
15344
- title="Betweenness Centrality Table")
15521
+
15522
+ self.network_analysis.format_for_upperright_table(
15523
+ betweenness_centrality,
15524
+ metric='Node',
15525
+ value='Betweenness Centrality',
15526
+ title=f"Betweenness Centrality Table{title_suffix}"
15527
+ )
15528
+
15345
15529
  except Exception as e:
15346
15530
  print(f"Error generating betweenness centrality histogram: {e}")
15347
15531
 
@@ -15394,7 +15578,27 @@ class HistogramSelector(QWidget):
15394
15578
  def bridges_analysis(self):
15395
15579
  try:
15396
15580
  bridges = list(nx.bridges(self.G))
15397
- self.network_analysis.format_for_upperright_table(bridges, metric='Node Pair',
15581
+ try:
15582
+ # Get the existing DataFrame from the model
15583
+ original_df = self.network_analysis.network_table.model()._data
15584
+
15585
+ # Create boolean mask
15586
+ mask = pd.Series([False] * len(original_df))
15587
+
15588
+ for u, v in bridges:
15589
+ # Check for both (u,v) and (v,u) orientations
15590
+ bridge_mask = (
15591
+ ((original_df.iloc[:, 0] == u) & (original_df.iloc[:, 1] == v)) |
15592
+ ((original_df.iloc[:, 0] == v) & (original_df.iloc[:, 1] == u))
15593
+ )
15594
+ mask |= bridge_mask
15595
+ # Filter the DataFrame to only include bridge connections
15596
+ filtered_df = original_df[mask].copy()
15597
+ df_dict = {i: row.tolist() for i, row in enumerate(filtered_df.values)}
15598
+ self.network_analysis.format_for_upperright_table(df_dict, metric='Bridge ID', value = ['NodeA', 'NodeB', 'EdgeC'],
15599
+ title="Bridges")
15600
+ except:
15601
+ self.network_analysis.format_for_upperright_table(bridges, metric='Node Pair',
15398
15602
  title="Bridges")
15399
15603
  except Exception as e:
15400
15604
  print(f"Error generating bridges analysis: {e}")
@@ -15421,7 +15625,7 @@ class HistogramSelector(QWidget):
15421
15625
  def node_connectivity_histogram(self):
15422
15626
  """Local node connectivity - minimum number of nodes that must be removed to disconnect neighbors"""
15423
15627
  try:
15424
- if self.G.number_of_nodes() > 500: # Skip for large networks (computationally expensive)
15628
+ if self.G.number_of_nodes() > 500:
15425
15629
  print("Note this analysis may be slow for large network (>500 nodes)")
15426
15630
  #return
15427
15631
 
@@ -15499,7 +15703,7 @@ class HistogramSelector(QWidget):
15499
15703
  def load_centrality_histogram(self):
15500
15704
  """Load centrality - fraction of shortest paths passing through each node"""
15501
15705
  try:
15502
- if self.G.number_of_nodes() > 1000: # Skip for very large networks
15706
+ if self.G.number_of_nodes() > 1000:
15503
15707
  print("Note this analysis may be slow for large network (>1000 nodes)")
15504
15708
  #return
15505
15709
 
@@ -15518,21 +15722,67 @@ class HistogramSelector(QWidget):
15518
15722
  def communicability_centrality_histogram(self):
15519
15723
  """Communicability centrality - based on communicability between nodes"""
15520
15724
  try:
15521
- if self.G.number_of_nodes() > 500: # Skip for large networks (memory intensive)
15725
+ if self.G.number_of_nodes() > 500:
15522
15726
  print("Note this analysis may be slow for large network (>500 nodes)")
15523
15727
  #return
15728
+
15729
+ # Check if graph has multiple disconnected components
15730
+ components = list(nx.connected_components(self.G))
15731
+
15732
+ if len(components) > 1:
15733
+ print(f"Warning: Graph has {len(components)} disconnected components. Computing communicability centrality within each component separately.")
15734
+
15735
+ # Initialize dictionary to collect communicability centrality from all components
15736
+ combined_comm_centrality = {}
15737
+
15738
+ # Process each component separately
15739
+ for i, component in enumerate(components):
15740
+ if len(component) < 2:
15741
+ # For single-node components, communicability betweenness centrality is 0
15742
+ for node in component:
15743
+ combined_comm_centrality[node] = 0.0
15744
+ continue
15745
+
15746
+ # Create subgraph for this component
15747
+ subgraph = self.G.subgraph(component)
15748
+
15749
+ # Compute communicability betweenness centrality for this component
15750
+ try:
15751
+ component_comm_centrality = nx.communicability_betweenness_centrality(subgraph)
15752
+ # Add to combined results
15753
+ combined_comm_centrality.update(component_comm_centrality)
15754
+ except Exception as comp_e:
15755
+ print(f"Error computing communicability centrality for component {i+1}: {comp_e}")
15756
+ # Set centrality to 0 for nodes in this component if computation fails
15757
+ for node in component:
15758
+ combined_comm_centrality[node] = 0.0
15759
+
15760
+ comm_centrality = combined_comm_centrality
15761
+ title_suffix = f" (across {len(components)} components)"
15524
15762
 
15525
- # Use the correct function name - it's in the communicability module
15526
- comm_centrality = nx.communicability_betweenness_centrality(self.G)
15763
+ else:
15764
+ # Single component
15765
+ comm_centrality = nx.communicability_betweenness_centrality(self.G)
15766
+ title_suffix = ""
15767
+
15768
+ # Generate visualization and results (same for both cases)
15527
15769
  plt.figure(figsize=(15, 8))
15528
15770
  plt.hist(comm_centrality.values(), bins=50, alpha=0.7)
15529
- plt.title("Communicability Betweenness Centrality Distribution", fontdict={"size": 35}, loc="center")
15771
+ plt.title(
15772
+ f"Communicability Betweenness Centrality Distribution{title_suffix}",
15773
+ fontdict={"size": 35}, loc="center"
15774
+ )
15530
15775
  plt.xlabel("Communicability Betweenness Centrality", fontdict={"size": 20})
15531
15776
  plt.ylabel("Frequency", fontdict={"size": 20})
15532
15777
  plt.show()
15533
- self.network_analysis.format_for_upperright_table(comm_centrality, metric='Node',
15534
- value='Communicability Betweenness Centrality',
15535
- title="Communicability Betweenness Centrality Table")
15778
+
15779
+ self.network_analysis.format_for_upperright_table(
15780
+ comm_centrality,
15781
+ metric='Node',
15782
+ value='Communicability Betweenness Centrality',
15783
+ title=f"Communicability Betweenness Centrality Table{title_suffix}"
15784
+ )
15785
+
15536
15786
  except Exception as e:
15537
15787
  print(f"Error generating communicability betweenness centrality histogram: {e}")
15538
15788
 
@@ -15555,20 +15805,67 @@ class HistogramSelector(QWidget):
15555
15805
  def current_flow_betweenness_histogram(self):
15556
15806
  """Current flow betweenness - models network as electrical circuit"""
15557
15807
  try:
15558
- if self.G.number_of_nodes() > 500: # Skip for large networks (computationally expensive)
15808
+ if self.G.number_of_nodes() > 500:
15559
15809
  print("Note this analysis may be slow for large network (>500 nodes)")
15560
15810
  #return
15811
+
15812
+ # Check if graph has multiple disconnected components
15813
+ components = list(nx.connected_components(self.G))
15814
+
15815
+ if len(components) > 1:
15816
+ print(f"Warning: Graph has {len(components)} disconnected components. Computing current flow betweenness centrality within each component separately.")
15561
15817
 
15562
- current_flow = nx.current_flow_betweenness_centrality(self.G)
15818
+ # Initialize dictionary to collect current flow betweenness from all components
15819
+ combined_current_flow = {}
15820
+
15821
+ # Process each component separately
15822
+ for i, component in enumerate(components):
15823
+ if len(component) < 2:
15824
+ # For single-node components, current flow betweenness centrality is 0
15825
+ for node in component:
15826
+ combined_current_flow[node] = 0.0
15827
+ continue
15828
+
15829
+ # Create subgraph for this component
15830
+ subgraph = self.G.subgraph(component)
15831
+
15832
+ # Compute current flow betweenness centrality for this component
15833
+ try:
15834
+ component_current_flow = nx.current_flow_betweenness_centrality(subgraph)
15835
+ # Add to combined results
15836
+ combined_current_flow.update(component_current_flow)
15837
+ except Exception as comp_e:
15838
+ print(f"Error computing current flow betweenness for component {i+1}: {comp_e}")
15839
+ # Set centrality to 0 for nodes in this component if computation fails
15840
+ for node in component:
15841
+ combined_current_flow[node] = 0.0
15842
+
15843
+ current_flow = combined_current_flow
15844
+ title_suffix = f" (across {len(components)} components)"
15845
+
15846
+ else:
15847
+ # Single component
15848
+ current_flow = nx.current_flow_betweenness_centrality(self.G)
15849
+ title_suffix = ""
15850
+
15851
+ # Generate visualization and results (same for both cases)
15563
15852
  plt.figure(figsize=(15, 8))
15564
15853
  plt.hist(current_flow.values(), bins=50, alpha=0.7)
15565
- plt.title("Current Flow Betweenness Centrality Distribution", fontdict={"size": 35}, loc="center")
15854
+ plt.title(
15855
+ f"Current Flow Betweenness Centrality Distribution{title_suffix}",
15856
+ fontdict={"size": 35}, loc="center"
15857
+ )
15566
15858
  plt.xlabel("Current Flow Betweenness Centrality", fontdict={"size": 20})
15567
15859
  plt.ylabel("Frequency", fontdict={"size": 20})
15568
15860
  plt.show()
15569
- self.network_analysis.format_for_upperright_table(current_flow, metric='Node',
15570
- value='Current Flow Betweenness',
15571
- title="Current Flow Betweenness Table")
15861
+
15862
+ self.network_analysis.format_for_upperright_table(
15863
+ current_flow,
15864
+ metric='Node',
15865
+ value='Current Flow Betweenness',
15866
+ title=f"Current Flow Betweenness Table{title_suffix}"
15867
+ )
15868
+
15572
15869
  except Exception as e:
15573
15870
  print(f"Error generating current flow betweenness histogram: {e}")
15574
15871