nettracer3d 0.6.4__py3-none-any.whl → 0.6.6__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.
@@ -1399,55 +1399,128 @@ class ImageViewerWindow(QMainWindow):
1399
1399
  print(f"An error has occured: {e}")
1400
1400
 
1401
1401
  def handle_seperate(self):
1402
-
1402
+ print("Note: I search each selected label one at a time and then split it with the ndimage.label method which uses C but still has to search the entire array each time, I may be a very slow with big operations :)")
1403
1403
  try:
1404
-
1404
+ # Handle nodes
1405
1405
  if len(self.clicked_values['nodes']) > 0:
1406
- self.create_highlight_overlay(node_indices = self.clicked_values['nodes'])
1407
- max_val = np.max(my_network.nodes)
1408
- self.highlight_overlay, num = n3d.label_objects(self.highlight_overlay)
1409
-
1410
- node_bools = self.highlight_overlay != 0
1411
- new_max = num + max_val
1412
- self.highlight_overlay = self.highlight_overlay + max_val
1413
- self.highlight_overlay = self.highlight_overlay * node_bools
1414
- if new_max < 256:
1415
- dtype = np.uint8
1416
- elif new_max < 65536:
1417
- dtype = np.uint16
1418
- else:
1419
- dtype = np.uint32
1420
-
1421
- self.highlight_overlay = self.highlight_overlay.astype(dtype)
1422
- my_network.nodes = my_network.nodes + self.highlight_overlay
1406
+ self.create_highlight_overlay(node_indices=self.clicked_values['nodes'])
1407
+ max_val = np.max(my_network.nodes) + 1
1408
+
1409
+ # Create a boolean mask for highlighted values
1410
+ self.highlight_overlay = self.highlight_overlay != 0
1411
+
1412
+ # Create array with just the highlighted values
1413
+ highlighted_nodes = self.highlight_overlay * my_network.nodes
1414
+
1415
+ # Get unique values in the highlighted regions (excluding 0)
1416
+ vals = list(np.unique(highlighted_nodes))
1417
+ if vals[0] == 0:
1418
+ del vals[0]
1419
+
1420
+ # Process each value separately
1421
+ for val in vals:
1422
+ # Create a mask for this value
1423
+ val_mask = my_network.nodes == val
1424
+
1425
+ # Create an array without this value
1426
+ temp = my_network.nodes - (val_mask * val)
1427
+
1428
+ # Label the connected components for this value
1429
+ labeled_mask, num_components = n3d.label_objects(val_mask)
1430
+
1431
+ if num_components > 1:
1432
+ # Set appropriate dtype based on max value
1433
+ if max_val + num_components < 256:
1434
+ dtype = np.uint8
1435
+ elif max_val + num_components < 65536:
1436
+ dtype = np.uint16
1437
+ labeled_mask = labeled_mask.astype(dtype)
1438
+ temp = temp.astype(dtype)
1439
+ else:
1440
+ dtype = np.uint32
1441
+ labeled_mask = labeled_mask.astype(dtype)
1442
+ temp = temp.astype(dtype)
1443
+
1444
+ # Add new labels to the temporary array
1445
+ mask_nonzero = labeled_mask != 0
1446
+ labeled_mask = labeled_mask + max_val - 1 # -1 because we'll restore the first component
1447
+ labeled_mask = labeled_mask * mask_nonzero
1448
+
1449
+ # Restore original value for first component
1450
+ first_component = labeled_mask == max_val
1451
+ labeled_mask = labeled_mask - (first_component * (max_val - val))
1452
+
1453
+ # Add labeled components back to the array
1454
+ my_network.nodes = temp + labeled_mask
1455
+
1456
+ # Update max value for next iteration
1457
+ max_val += num_components - 1 # -1 because we kept one original label
1458
+
1423
1459
  self.load_channel(0, my_network.nodes, True)
1424
-
1460
+
1461
+ # Handle edges
1425
1462
  if len(self.clicked_values['edges']) > 0:
1426
- self.create_highlight_overlay(edge_indices = self.clicked_values['edges'])
1427
- max_val = np.max(my_network.edges)
1428
- self.highlight_overlay, num = n3d.label_objects(self.highlight_overlay)
1429
- node_bools = self.highlight_overlay != 0
1430
- new_max = num + max_val
1431
-
1432
- self.highlight_overlay = self.highlight_overlay + max_val
1433
- self.highlight_overlay = self.highlight_overlay * node_bools
1434
- if new_max < 256:
1435
- dtype = np.uint8
1436
- elif new_max < 65536:
1437
- dtype = np.uint16
1438
- else:
1439
- dtype = np.uint32
1440
-
1441
- self.highlight_overlay = self.highlight_overlay.astype(dtype)
1442
- my_network.edges = my_network.edges + self.highlight_overlay
1463
+ self.create_highlight_overlay(edge_indices=self.clicked_values['edges'])
1464
+ max_val = np.max(my_network.edges) + 1
1465
+
1466
+ # Create a boolean mask for highlighted values
1467
+ self.highlight_overlay = self.highlight_overlay != 0
1468
+
1469
+ # Create array with just the highlighted values
1470
+ highlighted_edges = self.highlight_overlay * my_network.edges
1471
+
1472
+ # Get unique values in the highlighted regions (excluding 0)
1473
+ vals = list(np.unique(highlighted_edges))
1474
+ if vals[0] == 0:
1475
+ del vals[0]
1476
+
1477
+ # Process each value separately
1478
+ for val in vals:
1479
+ # Create a mask for this value
1480
+ val_mask = my_network.edges == val
1481
+
1482
+ # Create an array without this value
1483
+ temp = my_network.edges - (val_mask * val)
1484
+
1485
+ # Label the connected components for this value
1486
+ labeled_mask, num_components = n3d.label_objects(val_mask)
1487
+
1488
+ if num_components > 1:
1489
+ # Set appropriate dtype based on max value
1490
+ if max_val + num_components < 256:
1491
+ dtype = np.uint8
1492
+ elif max_val + num_components < 65536:
1493
+ dtype = np.uint16
1494
+ labeled_mask = labeled_mask.astype(dtype)
1495
+ temp = temp.astype(dtype)
1496
+ else:
1497
+ dtype = np.uint32
1498
+ labeled_mask = labeled_mask.astype(dtype)
1499
+ temp = temp.astype(dtype)
1500
+
1501
+ # Add new labels to the temporary array
1502
+ mask_nonzero = labeled_mask != 0
1503
+ labeled_mask = labeled_mask + max_val - 1 # -1 because we'll restore the first component
1504
+ labeled_mask = labeled_mask * mask_nonzero
1505
+
1506
+ # Restore original value for first component
1507
+ first_component = labeled_mask == max_val
1508
+ labeled_mask = labeled_mask - (first_component * (max_val - val))
1509
+
1510
+ # Add labeled components back to the array
1511
+ my_network.edges = temp + labeled_mask
1512
+
1513
+ # Update max value for next iteration
1514
+ max_val += num_components - 1 # -1 because we kept one original label
1515
+
1443
1516
  self.load_channel(1, my_network.edges, True)
1517
+
1444
1518
  self.highlight_overlay = None
1445
1519
  self.update_display()
1446
1520
  print("Network is not updated automatically, please recompute if necessary. Identities are not automatically updated.")
1447
1521
  self.show_centroid_dialog()
1448
-
1449
1522
  except Exception as e:
1450
- print(f"Error seperating: {e}")
1523
+ print(f"Error separating: {e}")
1451
1524
 
1452
1525
 
1453
1526
 
@@ -2392,6 +2465,8 @@ class ImageViewerWindow(QMainWindow):
2392
2465
  random_action.triggered.connect(self.show_random_dialog)
2393
2466
  vol_action = stats_menu.addAction("Calculate Volumes")
2394
2467
  vol_action.triggered.connect(self.volumes)
2468
+ rad_action = stats_menu.addAction("Calculate Radii")
2469
+ rad_action.triggered.connect(self.show_rad_dialog)
2395
2470
  inter_action = stats_menu.addAction("Calculate Node < > Edge Interaction")
2396
2471
  inter_action.triggered.connect(self.show_interaction_dialog)
2397
2472
  overlay_menu = analysis_menu.addMenu("Data/Overlays")
@@ -3312,8 +3387,7 @@ class ImageViewerWindow(QMainWindow):
3312
3387
 
3313
3388
 
3314
3389
  except Exception as e:
3315
- import traceback
3316
- print(traceback.format_exc())
3390
+
3317
3391
  if not data:
3318
3392
  from PyQt6.QtWidgets import QMessageBox
3319
3393
  QMessageBox.critical(
@@ -3825,6 +3899,9 @@ class ImageViewerWindow(QMainWindow):
3825
3899
  dialog = RandomDialog(self)
3826
3900
  dialog.exec()
3827
3901
 
3902
+ def show_rad_dialog(self):
3903
+ dialog = RadDialog(self)
3904
+ dialog.exec()
3828
3905
 
3829
3906
  def show_interaction_dialog(self):
3830
3907
  dialog = InteractionDialog(self)
@@ -4241,16 +4318,21 @@ class CustomTableView(QTableView):
4241
4318
  self.parent.clicked_values['edges'] = []
4242
4319
  self.parent.clicked_values['nodes'].append(value)
4243
4320
 
4244
- # Highlight the value in both tables if it exists
4245
- self.highlight_value_in_table(self.parent.network_table, value, column)
4246
- self.highlight_value_in_table(self.parent.selection_table, value, column)
4321
+ try:
4322
+ # Highlight the value in both tables if it exists
4323
+ self.highlight_value_in_table(self.parent.network_table, value, column)
4324
+ self.highlight_value_in_table(self.parent.selection_table, value, column)
4325
+ except:
4326
+ pass
4247
4327
  else:
4248
4328
  print(f"Node {value} not found in centroids dictionary")
4249
4329
 
4250
4330
  elif column == 2: # Third column is edges
4331
+ if my_network.edge_centroids is None:
4332
+ self.parent.show_centroid_dialog()
4333
+
4251
4334
  if value in my_network.edge_centroids:
4252
- if my_network.edge_centroids is None:
4253
- self.parent.show_centroid_dialog()
4335
+
4254
4336
  # Get centroid coordinates (Z, Y, X)
4255
4337
  centroid = my_network.edge_centroids[value]
4256
4338
  # Set the active channel to edges (1)
@@ -4271,9 +4353,12 @@ class CustomTableView(QTableView):
4271
4353
  self.parent.clicked_values['edges'] = []
4272
4354
  self.parent.clicked_values['edges'].append(value)
4273
4355
 
4274
- # Highlight the value in both tables if it exists
4275
- self.highlight_value_in_table(self.parent.network_table, value, column)
4276
- self.highlight_value_in_table(self.parent.selection_table, value, column)
4356
+ try:
4357
+ # Highlight the value in both tables if it exists
4358
+ self.highlight_value_in_table(self.parent.network_table, value, column)
4359
+ self.highlight_value_in_table(self.parent.selection_table, value, column)
4360
+ except:
4361
+ pass
4277
4362
  else:
4278
4363
  print(f"Edge {value} not found in centroids dictionary")
4279
4364
  else: #If highlighting paired elements
@@ -4905,6 +4990,52 @@ class ArbitraryDialog(QDialog):
4905
4990
  continue
4906
4991
 
4907
4992
  return processed_values
4993
+
4994
+ def handle_find_action(self, mode, value):
4995
+ """Handle the Find action."""
4996
+
4997
+ # Determine if we're looking for a node or edge
4998
+ if mode == 0:
4999
+
5000
+ if my_network.node_centroids is None:
5001
+ self.parent().show_centroid_dialog()
5002
+
5003
+ if value in my_network.node_centroids:
5004
+ # Get centroid coordinates (Z, Y, X)
5005
+ centroid = my_network.node_centroids[value]
5006
+ # Set the active channel to nodes (0)
5007
+ self.parent().set_active_channel(0)
5008
+ # Toggle on the nodes channel if it's not already visible
5009
+ if not self.parent().channel_visible[0]:
5010
+ self.parent().channel_buttons[0].setChecked(True)
5011
+ self.parent().toggle_channel(0)
5012
+ # Navigate to the Z-slice
5013
+ self.parent().slice_slider.setValue(int(centroid[0]))
5014
+ print(f"Found node {value} at Z-slice {centroid[0]}")
5015
+
5016
+ else:
5017
+ print(f"Node {value} not found in centroids dictionary")
5018
+
5019
+ else: # edges
5020
+ if my_network.edge_centroids is None:
5021
+ self.parent().show_centroid_dialog()
5022
+
5023
+ if value in my_network.edge_centroids:
5024
+
5025
+ # Get centroid coordinates (Z, Y, X)
5026
+ centroid = my_network.edge_centroids[value]
5027
+ # Set the active channel to edges (1)
5028
+ self.parent().set_active_channel(1)
5029
+ # Toggle on the edges channel if it's not already visible
5030
+ if not self.parent().channel_visible[1]:
5031
+ self.parent().channel_buttons[1].setChecked(True)
5032
+ self.parent().toggle_channel(1)
5033
+ # Navigate to the Z-slice
5034
+ self.parent().slice_slider.setValue(int(centroid[0]))
5035
+ print(f"Found edge {value} at Z-slice {centroid[0]}")
5036
+
5037
+ else:
5038
+ print(f"Edge {value} not found in centroids dictionary")
4908
5039
 
4909
5040
  def process_selections(self):
4910
5041
  """Process the selection and deselection inputs."""
@@ -4940,11 +5071,21 @@ class ArbitraryDialog(QDialog):
4940
5071
  except:
4941
5072
  pass #Forgive mistakes
4942
5073
 
4943
- for item in select_list:
4944
- try:
4945
- self.parent().clicked_values[mode].append(item)
4946
- except:
4947
- pass
5074
+ select_list.reverse()
5075
+
5076
+ self.parent().clicked_values[mode].extend(select_list)
5077
+
5078
+ select_list.reverse()
5079
+
5080
+ try:
5081
+ if mode == 'nodes':
5082
+ self.handle_find_action(0, select_list[0])
5083
+ self.parent().handle_info(sort = 'node')
5084
+ elif mode == 'edges':
5085
+ self.handle_find_action(1, select_list[0])
5086
+ self.parent().handle_info(sort = 'edge')
5087
+ except:
5088
+ pass
4948
5089
 
4949
5090
  self.parent().clicked_values[mode] = list(set(self.parent().clicked_values[mode]))
4950
5091
 
@@ -4964,6 +5105,8 @@ class ArbitraryDialog(QDialog):
4964
5105
 
4965
5106
  except Exception as e:
4966
5107
  QMessageBox.critical(self, "Error", f"Error processing selections: {str(e)}")
5108
+ import traceback
5109
+ print(traceback.format_exc())
4967
5110
 
4968
5111
  class Show3dDialog(QDialog):
4969
5112
  def __init__(self, parent=None):
@@ -5257,8 +5400,10 @@ class ShuffleDialog(QDialog):
5257
5400
 
5258
5401
  try:
5259
5402
  if accepted_mode == 4:
5260
-
5261
- self.parent().highlight_overlay = n3d.binarize(target_data)
5403
+ try:
5404
+ self.parent().highlight_overlay = n3d.binarize(target_data)
5405
+ except:
5406
+ self.parent().highlight_overay = None
5262
5407
  else:
5263
5408
  self.parent().load_channel(accepted_mode, channel_data = target_data, data = True)
5264
5409
  except:
@@ -5271,11 +5416,12 @@ class ShuffleDialog(QDialog):
5271
5416
  self.parent().highlight_overlay = n3d.binarize(active_data)
5272
5417
  except:
5273
5418
  self.parent().highlight_overlay = None
5419
+ else:
5420
+ self.parent().load_channel(accepted_target, channel_data = active_data, data = True)
5274
5421
  except:
5275
5422
  pass
5276
5423
 
5277
- else:
5278
- self.parent().load_channel(accepted_target, channel_data = active_data, data = True)
5424
+
5279
5425
 
5280
5426
 
5281
5427
  self.parent().update_display()
@@ -5604,6 +5750,50 @@ class RandomDialog(QDialog):
5604
5750
 
5605
5751
  self.accept()
5606
5752
 
5753
+ class RadDialog(QDialog):
5754
+
5755
+ def __init__(self, parent=None):
5756
+
5757
+ super().__init__(parent)
5758
+ self.setWindowTitle("Obtain Radii of Active Image? (Returns Largest Radius for Each Labeled Object)")
5759
+ self.setModal(True)
5760
+
5761
+ layout = QFormLayout(self)
5762
+
5763
+ # GPU checkbox (default False)
5764
+ self.GPU = QPushButton("GPU")
5765
+ self.GPU.setCheckable(True)
5766
+ self.GPU.setChecked(False)
5767
+ layout.addRow("Use GPU:", self.GPU)
5768
+
5769
+
5770
+ # Add Run button
5771
+ run_button = QPushButton("Calculate")
5772
+ run_button.clicked.connect(self.rads)
5773
+ layout.addWidget(run_button)
5774
+
5775
+ def rads(self):
5776
+
5777
+ try:
5778
+ GPU = self.GPU.isChecked()
5779
+
5780
+ active_data = self.parent().channel_data[self.parent().active_channel]
5781
+
5782
+ radii = n3d.estimate_object_radii(active_data, gpu=GPU)
5783
+
5784
+ for key, val in radii.items():
5785
+
5786
+ radii[key] = [val, val * (my_network.xy_scale**2) * my_network.z_scale]
5787
+
5788
+ self.parent().format_for_upperright_table(radii, title = '~Radii of Objects', metric='ObjectID', value=['Largest Radius (Voxels)', 'Largest Radius (Scaled)'])
5789
+
5790
+ self.accept()
5791
+
5792
+ except Exception as e:
5793
+ print(f"Error: {e}")
5794
+
5795
+
5796
+
5607
5797
 
5608
5798
 
5609
5799
  class InteractionDialog(QDialog):
@@ -6318,7 +6508,7 @@ class SLabelDialog(QDialog):
6318
6508
  # GPU checkbox (default True)
6319
6509
  self.GPU = QPushButton("GPU")
6320
6510
  self.GPU.setCheckable(True)
6321
- self.GPU.setChecked(True)
6511
+ self.GPU.setChecked(False)
6322
6512
  layout.addRow("Use GPU:", self.GPU)
6323
6513
 
6324
6514
  self.down_factor = QLineEdit("")
@@ -6558,16 +6748,16 @@ class MachineWindow(QMainWindow):
6558
6748
  self.GPU.setChecked(False)
6559
6749
  self.GPU.clicked.connect(self.toggle_GPU)
6560
6750
  self.use_gpu = False
6561
- self.two = QPushButton("Train By 2D Slice Patterns (Cheaper - CPU only)")
6751
+ self.two = QPushButton("Train By 2D Slice Patterns")
6562
6752
  self.two.setCheckable(True)
6563
- self.two.setChecked(True)
6753
+ self.two.setChecked(False)
6564
6754
  self.two.clicked.connect(self.toggle_two)
6565
- self.use_two = True
6755
+ self.use_two = False
6566
6756
  self.three = QPushButton("Train by 3D Patterns")
6567
6757
  self.three.setCheckable(True)
6568
- self.three.setChecked(False)
6758
+ self.three.setChecked(True)
6569
6759
  self.three.clicked.connect(self.toggle_three)
6570
- processing_layout.addWidget(self.GPU)
6760
+ #processing_layout.addWidget(self.GPU) [Decided to hold off on this until its more robust]
6571
6761
  processing_layout.addWidget(self.two)
6572
6762
  processing_layout.addWidget(self.three)
6573
6763
  processing_group.setLayout(processing_layout)
@@ -6600,7 +6790,7 @@ class MachineWindow(QMainWindow):
6600
6790
  full_button.clicked.connect(self.segment)
6601
6791
  segmentation_layout.addWidget(seg_button)
6602
6792
  #segmentation_layout.addWidget(self.pause_button) # <--- for some reason the segmenter preview is still running even when killed, may be regenerating itself somewhere. May or may not actually try to resolve this because this feature isnt that necessary.
6603
- segmentation_layout.addWidget(self.lock_button)
6793
+ #segmentation_layout.addWidget(self.lock_button) # Also turned this off
6604
6794
  segmentation_layout.addWidget(full_button)
6605
6795
  segmentation_group.setLayout(segmentation_layout)
6606
6796
 
@@ -7387,7 +7577,7 @@ class SmartDilateDialog(QDialog):
7387
7577
  # GPU checkbox (default True)
7388
7578
  self.GPU = QPushButton("GPU")
7389
7579
  self.GPU.setCheckable(True)
7390
- self.GPU.setChecked(True)
7580
+ self.GPU.setChecked(False)
7391
7581
  layout.addRow("Use GPU:", self.GPU)
7392
7582
 
7393
7583
  self.down_factor = QLineEdit("")
@@ -7615,6 +7805,18 @@ class HoleDialog(QDialog):
7615
7805
 
7616
7806
  layout = QFormLayout(self)
7617
7807
 
7808
+ # auto checkbox (default True)
7809
+ self.headon = QPushButton("Head-on")
7810
+ self.headon.setCheckable(True)
7811
+ self.headon.setChecked(False)
7812
+ layout.addRow("Only Use 2D Slicing Dimension:", self.headon)
7813
+
7814
+ # auto checkbox (default True)
7815
+ self.borders = QPushButton("Borders")
7816
+ self.borders.setCheckable(True)
7817
+ self.borders.setChecked(True)
7818
+ layout.addRow("Fill Small Holes Along Borders:", self.borders)
7819
+
7618
7820
  # Add Run button
7619
7821
  run_button = QPushButton("Run Fill Holes")
7620
7822
  run_button.clicked.connect(self.run_holes)
@@ -7628,10 +7830,15 @@ class HoleDialog(QDialog):
7628
7830
  active_data = self.parent().channel_data[self.parent().active_channel]
7629
7831
  if active_data is None:
7630
7832
  raise ValueError("No active image selected")
7833
+
7834
+ borders = self.borders.isChecked()
7835
+ headon = self.headon.isChecked()
7631
7836
 
7632
7837
  # Call dilate method with parameters
7633
7838
  result = n3d.fill_holes_3d(
7634
- active_data
7839
+ active_data,
7840
+ head_on = headon,
7841
+ fill_borders = borders
7635
7842
  )
7636
7843
 
7637
7844
  self.parent().load_channel(self.parent().active_channel, result, True)
@@ -7889,7 +8096,7 @@ class WatershedDialog(QDialog):
7889
8096
  # GPU checkbox (default True)
7890
8097
  self.gpu = QPushButton("GPU")
7891
8098
  self.gpu.setCheckable(True)
7892
- self.gpu.setChecked(True)
8099
+ self.gpu.setChecked(False)
7893
8100
  layout.addRow("Use GPU:", self.gpu)
7894
8101
 
7895
8102
  # Smallest radius (empty by default)
@@ -8758,15 +8965,23 @@ class CentroidDialog(QDialog):
8758
8965
  my_network.save_edge_centroids(directory = directory)
8759
8966
 
8760
8967
  elif chan == 0:
8761
- my_network.calculate_node_centroids(
8762
- down_factor = downsample
8763
- )
8764
- my_network.save_node_centroids(directory = directory)
8968
+ try:
8969
+ my_network.calculate_node_centroids(
8970
+ down_factor = downsample
8971
+ )
8972
+ my_network.save_node_centroids(directory = directory)
8973
+ except:
8974
+ pass
8765
8975
 
8766
- my_network.calculate_edge_centroids(
8767
- down_factor = downsample
8768
- )
8769
- my_network.save_edge_centroids(directory = directory)
8976
+ try:
8977
+
8978
+ my_network.calculate_edge_centroids(
8979
+ down_factor = downsample
8980
+ )
8981
+ my_network.save_edge_centroids(directory = directory)
8982
+
8983
+ except:
8984
+ pass
8770
8985
 
8771
8986
  if hasattr(my_network, 'node_centroids') and my_network.node_centroids is not None:
8772
8987
  try: