nettracer3d 0.7.2__py3-none-any.whl → 0.7.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.
nettracer3d/nettracer.py CHANGED
@@ -2913,6 +2913,7 @@ class Network_3D:
2913
2913
 
2914
2914
  if file_path is not None:
2915
2915
  self._node_centroids = network_analysis.read_centroids_to_dict(file_path)
2916
+ self._node_centroids = self.clear_null(self._node_centroids)
2916
2917
  print("Succesfully loaded node centroids")
2917
2918
  return
2918
2919
 
@@ -2922,10 +2923,12 @@ class Network_3D:
2922
2923
  if item == 'node_centroids.xlsx' or item == 'node_centroids.csv':
2923
2924
  if directory is not None:
2924
2925
  self._node_centroids = network_analysis.read_centroids_to_dict(f'{directory}/{item}')
2926
+ self._node_centroids = self.clear_null(self._node_centroids)
2925
2927
  print("Succesfully loaded node centroids")
2926
2928
  return
2927
2929
  else:
2928
2930
  self._node_centroids = network_analysis.read_centroids_to_dict(item)
2931
+ self._node_centroids = self.clear_null(self._node_centroids)
2929
2932
  print("Succesfully loaded node centroids")
2930
2933
  return
2931
2934
 
@@ -2941,6 +2944,7 @@ class Network_3D:
2941
2944
 
2942
2945
  if file_path is not None:
2943
2946
  self._node_identities = network_analysis.read_excel_to_singval_dict(file_path)
2947
+ self._node_identities = self.clear_null(self._node_identities)
2944
2948
  print("Succesfully loaded node identities")
2945
2949
  return
2946
2950
 
@@ -2950,10 +2954,12 @@ class Network_3D:
2950
2954
  if item == 'node_identities.xlsx' or item == 'node_identities.csv':
2951
2955
  if directory is not None:
2952
2956
  self._node_identities = network_analysis.read_excel_to_singval_dict(f'{directory}/{item}')
2957
+ self._node_identities = self.clear_null(self._node_identities)
2953
2958
  print("Succesfully loaded node identities")
2954
2959
  return
2955
2960
  else:
2956
2961
  self._node_identities = network_analysis.read_excel_to_singval_dict(item)
2962
+ self._node_identities = self.clear_null(self._node_identities)
2957
2963
  print("Succesfully loaded node identities")
2958
2964
  return
2959
2965
 
@@ -2968,7 +2974,8 @@ class Network_3D:
2968
2974
  """
2969
2975
 
2970
2976
  if file_path is not None:
2971
- self._node_identities = network_analysis.read_excel_to_singval_dict(file_path)
2977
+ self._communities = network_analysis.read_excel_to_singval_dict(file_path)
2978
+ self._communities = self.clear_null(self._communities)
2972
2979
  print("Succesfully loaded communities")
2973
2980
  return
2974
2981
 
@@ -2978,15 +2985,23 @@ class Network_3D:
2978
2985
  if item == 'node_communities.xlsx' or item == 'node_communities.csv':
2979
2986
  if directory is not None:
2980
2987
  self._communities = network_analysis.read_excel_to_singval_dict(f'{directory}/{item}')
2988
+ self._communities = self.clear_null(self._communities)
2981
2989
  print("Succesfully loaded communities")
2982
2990
  return
2983
2991
  else:
2984
2992
  self._communities = network_analysis.read_excel_to_singval_dict(item)
2993
+ self._communities = self.clear_null(self._communities)
2985
2994
  print("Succesfully loaded communities")
2986
2995
  return
2987
2996
 
2988
2997
  print("Could not find communities. They must be in the specified directory and named 'node_communities.xlsx'")
2989
2998
 
2999
+ def clear_null(self, some_dict):
3000
+
3001
+ if some_dict == {}:
3002
+ some_dict = None
3003
+ return some_dict
3004
+
2990
3005
  def load_edge_centroids(self, directory = None, file_path = None):
2991
3006
  """
2992
3007
  Can be called on a Network_3D object to load a .xlsx into the edge_centroids property as a dictionary. It will look for a file called 'edge_centroids.xlsx' in the specified directory,
@@ -2997,6 +3012,7 @@ class Network_3D:
2997
3012
 
2998
3013
  if file_path is not None:
2999
3014
  self._edge_centroids = network_analysis.read_centroids_to_dict(file_path)
3015
+ self._edge_centroids = self.clear_null(self._edge_centroids)
3000
3016
  print("Succesfully loaded edge centroids")
3001
3017
  return
3002
3018
 
@@ -3006,10 +3022,12 @@ class Network_3D:
3006
3022
  if item == 'edge_centroids.xlsx' or item == 'edge_centroids.csv':
3007
3023
  if directory is not None:
3008
3024
  self._edge_centroids = network_analysis.read_centroids_to_dict(f'{directory}/{item}')
3025
+ self._edge_centroids = self.clear_null(self._edge_centroids)
3009
3026
  print("Succesfully loaded edge centroids")
3010
3027
  return
3011
3028
  else:
3012
3029
  self._edge_centroids = network_analysis.read_centroids_to_dict(item)
3030
+ self._edge_centroids = self.clear_null(self._edge_centroids)
3013
3031
  print("Succesfully loaded edge centroids")
3014
3032
  return
3015
3033
 
@@ -3427,10 +3445,11 @@ class Network_3D:
3427
3445
  self.calculate_search_region(search, GPU = GPU, fast_dil = fast_dil, GPU_downsample = GPU_downsample)
3428
3446
  #self._nodes = None # I originally put this here to micromanage RAM a little bit (it writes it to disk so I wanted to purge it from mem briefly but now idt thats necessary and I'd rather give it flexibility when lacking write permissions)
3429
3447
  search = None
3430
- try:
3431
- self.save_search_region(directory)
3432
- except:
3433
- pass
3448
+ if directory is not None:
3449
+ try:
3450
+ self.save_search_region(directory)
3451
+ except:
3452
+ pass
3434
3453
 
3435
3454
  self.calculate_edges(edges, diledge = diledge, inners = inners, hash_inner_edges = hash_inners, search = search, remove_edgetrunk = remove_trunk, GPU = GPU, fast_dil = fast_dil, skeletonized = skeletonize)
3436
3455
  del edges
@@ -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 <--- couldn't get this faster than CPU ¯\_(ツ)_/¯
28
+ from nettracer3d import segmenter_GPU
29
29
 
30
30
 
31
31
 
@@ -2870,67 +2870,72 @@ class ImageViewerWindow(QMainWindow):
2870
2870
  return val
2871
2871
  return val
2872
2872
 
2873
- if isinstance(data, (list, tuple, np.ndarray)):
2874
- # Handle list input - create single column DataFrame
2875
- df = pd.DataFrame({
2876
- metric: [convert_to_numeric(val) for val in data]
2877
- })
2878
-
2879
- # Format floating point numbers
2880
- df[metric] = df[metric].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
2881
-
2882
- else: # Dictionary input
2883
- # Get sample value to determine structure
2884
- sample_value = next(iter(data.values()))
2885
- is_multi_value = isinstance(sample_value, (list, tuple, np.ndarray))
2886
-
2887
- if is_multi_value:
2888
- # Handle multi-value case
2889
- if isinstance(value, str):
2890
- # If single string provided for multi-values, generate numbered headers
2891
- n_cols = len(sample_value)
2892
- value_headers = [f"{value}_{i+1}" for i in range(n_cols)]
2893
- else:
2894
- # Use provided list of headers
2895
- value_headers = value
2896
- if len(value_headers) != len(sample_value):
2897
- raise ValueError("Number of headers must match number of values per key")
2898
-
2899
- # Create lists for each column
2900
- dict_data = {metric: list(data.keys())}
2901
- for i, header in enumerate(value_headers):
2902
- # Convert values to numeric when possible before adding to DataFrame
2903
- dict_data[header] = [convert_to_numeric(data[key][i]) for key in data.keys()]
2904
-
2905
- df = pd.DataFrame(dict_data)
2906
-
2907
- # Format floating point numbers in all value columns
2908
- for header in value_headers:
2909
- df[header] = df[header].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
2910
-
2911
- else:
2912
- # Single-value case
2873
+ try:
2874
+
2875
+ if isinstance(data, (list, tuple, np.ndarray)):
2876
+ # Handle list input - create single column DataFrame
2913
2877
  df = pd.DataFrame({
2914
- metric: data.keys(),
2915
- value: [convert_to_numeric(val) for val in data.values()]
2878
+ metric: [convert_to_numeric(val) for val in data]
2916
2879
  })
2917
2880
 
2918
2881
  # Format floating point numbers
2919
- df[value] = df[value].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
2920
-
2921
- # Create new table
2922
- table = CustomTableView(self)
2923
- table.setModel(PandasModel(df))
2924
-
2925
- # Add to tabbed widget
2926
- if title is None:
2927
- self.tabbed_data.add_table(f"{metric} Analysis", table)
2928
- else:
2929
- self.tabbed_data.add_table(f"{title}", table)
2930
-
2931
- # Adjust column widths to content
2932
- for column in range(table.model().columnCount(None)):
2933
- table.resizeColumnToContents(column)
2882
+ df[metric] = df[metric].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
2883
+
2884
+ else: # Dictionary input
2885
+ # Get sample value to determine structure
2886
+ sample_value = next(iter(data.values()))
2887
+ is_multi_value = isinstance(sample_value, (list, tuple, np.ndarray))
2888
+
2889
+ if is_multi_value:
2890
+ # Handle multi-value case
2891
+ if isinstance(value, str):
2892
+ # If single string provided for multi-values, generate numbered headers
2893
+ n_cols = len(sample_value)
2894
+ value_headers = [f"{value}_{i+1}" for i in range(n_cols)]
2895
+ else:
2896
+ # Use provided list of headers
2897
+ value_headers = value
2898
+ if len(value_headers) != len(sample_value):
2899
+ raise ValueError("Number of headers must match number of values per key")
2900
+
2901
+ # Create lists for each column
2902
+ dict_data = {metric: list(data.keys())}
2903
+ for i, header in enumerate(value_headers):
2904
+ # Convert values to numeric when possible before adding to DataFrame
2905
+ dict_data[header] = [convert_to_numeric(data[key][i]) for key in data.keys()]
2906
+
2907
+ df = pd.DataFrame(dict_data)
2908
+
2909
+ # Format floating point numbers in all value columns
2910
+ for header in value_headers:
2911
+ df[header] = df[header].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
2912
+
2913
+ else:
2914
+ # Single-value case
2915
+ df = pd.DataFrame({
2916
+ metric: data.keys(),
2917
+ value: [convert_to_numeric(val) for val in data.values()]
2918
+ })
2919
+
2920
+ # Format floating point numbers
2921
+ df[value] = df[value].apply(lambda x: f"{x:.3f}" if isinstance(x, (float, np.float64)) else str(x))
2922
+
2923
+ # Create new table
2924
+ table = CustomTableView(self)
2925
+ table.setModel(PandasModel(df))
2926
+
2927
+ # Add to tabbed widget
2928
+ if title is None:
2929
+ self.tabbed_data.add_table(f"{metric} Analysis", table)
2930
+ else:
2931
+ self.tabbed_data.add_table(f"{title}", table)
2932
+
2933
+ # Adjust column widths to content
2934
+ for column in range(table.model().columnCount(None)):
2935
+ table.resizeColumnToContents(column)
2936
+
2937
+ except:
2938
+ pass
2934
2939
 
2935
2940
 
2936
2941
  def show_watershed_dialog(self):
@@ -7412,7 +7417,11 @@ class MachineWindow(QMainWindow):
7412
7417
  self.three.setCheckable(True)
7413
7418
  self.three.setChecked(True)
7414
7419
  self.three.clicked.connect(self.toggle_three)
7415
- #processing_layout.addWidget(self.GPU) [Decided to hold off on this until its more robust]
7420
+ self.GPU = QPushButton("GPU")
7421
+ self.GPU.setCheckable(True)
7422
+ self.GPU.setChecked(False)
7423
+ self.GPU.clicked.connect(self.toggle_GPU)
7424
+ processing_layout.addWidget(self.GPU)
7416
7425
  processing_layout.addWidget(self.two)
7417
7426
  processing_layout.addWidget(self.three)
7418
7427
  processing_group.setLayout(processing_layout)
@@ -7550,6 +7559,35 @@ class MachineWindow(QMainWindow):
7550
7559
  self.two.setChecked(True)
7551
7560
  self.use_two = True
7552
7561
 
7562
+ def toggle_GPU(self):
7563
+
7564
+ if self.parent().active_channel == 0:
7565
+ if self.parent().channel_data[0] is not None:
7566
+ try:
7567
+ active_data = self.parent().channel_data[0]
7568
+ act_channel = 0
7569
+ except:
7570
+ active_data = self.parent().channel_data[1]
7571
+ act_channel = 1
7572
+ else:
7573
+ active_data = self.parent().channel_data[1]
7574
+ act_channel = 1
7575
+
7576
+ if self.GPU.isChecked():
7577
+
7578
+ try:
7579
+ self.segmenter = segmenter_GPU.InteractiveSegmenter(active_data)
7580
+ print("Using GPU")
7581
+ except:
7582
+ self.GPU.setChecked(False)
7583
+ print("Could not detect GPU")
7584
+ import traceback
7585
+ traceback.print_exc()
7586
+
7587
+ else:
7588
+ self.segmenter = segmenter.InteractiveSegmenter(active_data, use_gpu=False)
7589
+ print("Using CPU")
7590
+
7553
7591
 
7554
7592
 
7555
7593
  def toggle_foreground(self):
@@ -7836,18 +7874,12 @@ class MachineWindow(QMainWindow):
7836
7874
  self.parent().highlight_overlay = array3 #Clear this out for the segmenter to use
7837
7875
 
7838
7876
  print("Segmenting entire volume with model...")
7839
- foreground_coords, background_coords = self.segmenter.segment_volume(gpu = self.use_gpu)
7877
+ #foreground_coords, background_coords = self.segmenter.segment_volume(array = self.parent().highlight_overlay)
7878
+ self.parent().highlight_overlay = self.segmenter.segment_volume(array = self.parent().highlight_overlay)
7840
7879
 
7841
7880
  # Clean up when done
7842
7881
  self.segmenter.cleanup()
7843
7882
 
7844
- fg_array = np.array(list(foreground_coords))
7845
- if len(fg_array) > 0: # Check if we have any foreground coordinates
7846
- # Unpack into separate coordinate arrays
7847
- z_coords, y_coords, x_coords = fg_array[:, 0], fg_array[:, 1], fg_array[:, 2]
7848
- # Assign values in a single vectorized operation
7849
- self.parent().highlight_overlay[z_coords, y_coords, x_coords] = 255
7850
-
7851
7883
  self.parent().load_channel(3, self.parent().highlight_overlay, True)
7852
7884
 
7853
7885
  # Not exactly sure why we need all this but the channel buttons weren't loading like they normally do when load_channel() is called: