nettracer3d 0.6.3__tar.gz → 0.6.4__tar.gz

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.
Files changed (25) hide show
  1. {nettracer3d-0.6.3/src/nettracer3d.egg-info → nettracer3d-0.6.4}/PKG-INFO +10 -5
  2. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/README.md +9 -4
  3. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/pyproject.toml +1 -1
  4. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/nettracer.py +45 -12
  5. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/nettracer_gui.py +63 -23
  6. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/network_analysis.py +6 -6
  7. {nettracer3d-0.6.3 → nettracer3d-0.6.4/src/nettracer3d.egg-info}/PKG-INFO +10 -5
  8. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/LICENSE +0 -0
  9. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/setup.cfg +0 -0
  10. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/__init__.py +0 -0
  11. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/community_extractor.py +0 -0
  12. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/modularity.py +0 -0
  13. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/morphology.py +0 -0
  14. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/network_draw.py +0 -0
  15. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/node_draw.py +0 -0
  16. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/proximity.py +0 -0
  17. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/run.py +0 -0
  18. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/segmenter.py +0 -0
  19. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/simple_network.py +0 -0
  20. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d/smart_dilate.py +0 -0
  21. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d.egg-info/SOURCES.txt +0 -0
  22. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d.egg-info/dependency_links.txt +0 -0
  23. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d.egg-info/entry_points.txt +0 -0
  24. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d.egg-info/requires.txt +0 -0
  25. {nettracer3d-0.6.3 → nettracer3d-0.6.4}/src/nettracer3d.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nettracer3d
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
5
5
  Author-email: Liam McLaughlin <mclaughlinliam99@gmail.com>
6
6
  Project-URL: User_Tutorial, https://www.youtube.com/watch?v=cRatn5VTWDY
@@ -45,10 +45,15 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
45
45
 
46
46
  NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
47
47
 
48
- -- Version 0.6.3 updates --
48
+ -- Version 0.6.4 updates --
49
49
 
50
- 1. Fixed bug with the active channel indicator on the GUI not always updating when the active channel was changed by internal loading.
50
+ 1. Fixed bug with tabulated data in top right having a right click window corresponding to the bottom left.
51
51
 
52
- 2. Updated ram_lock mode in the segmenter to garbage collect better... again.
52
+ 2. Fixed bug when converting communities to nodes that let the same community connect to itself in the new network.
53
53
 
54
- 3. Added new function (Label neighborhoods). Find it in process -> image -> label neighborhoods. Allows a 3d labeled array to act as a kernel and extend its labels along a secondary binary array. This can be useful in evaluating nearest neighbors en-masse. The method behind this (smart label) was previously used internally for some methods (ie watershedding) but I thought I'd include it as an actual function since it has some more general uses.
54
+ 3. Removed attempted trendline fitting from degree distribution
55
+
56
+ 4. Added new feature to skeletonization (and corresponding branch labeler/gennodes)
57
+ Now you can have the program attempt to auto-correct 3D skeletonization loop artifacts through a method that just runs the 3d fill holes algo and then attempts to reskeletonize the output. This worked well in my own testing.
58
+
59
+ 5. Other minor fixes/improvements
@@ -8,10 +8,15 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
8
8
 
9
9
  NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
10
10
 
11
- -- Version 0.6.3 updates --
11
+ -- Version 0.6.4 updates --
12
12
 
13
- 1. Fixed bug with the active channel indicator on the GUI not always updating when the active channel was changed by internal loading.
13
+ 1. Fixed bug with tabulated data in top right having a right click window corresponding to the bottom left.
14
14
 
15
- 2. Updated ram_lock mode in the segmenter to garbage collect better... again.
15
+ 2. Fixed bug when converting communities to nodes that let the same community connect to itself in the new network.
16
16
 
17
- 3. Added new function (Label neighborhoods). Find it in process -> image -> label neighborhoods. Allows a 3d labeled array to act as a kernel and extend its labels along a secondary binary array. This can be useful in evaluating nearest neighbors en-masse. The method behind this (smart label) was previously used internally for some methods (ie watershedding) but I thought I'd include it as an actual function since it has some more general uses.
17
+ 3. Removed attempted trendline fitting from degree distribution
18
+
19
+ 4. Added new feature to skeletonization (and corresponding branch labeler/gennodes)
20
+ Now you can have the program attempt to auto-correct 3D skeletonization loop artifacts through a method that just runs the 3d fill holes algo and then attempts to reskeletonize the output. This worked well in my own testing.
21
+
22
+ 5. Other minor fixes/improvements
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nettracer3d"
3
- version = "0.6.3"
3
+ version = "0.6.4"
4
4
  authors = [
5
5
  { name="Liam McLaughlin", email="mclaughlinliam99@gmail.com" },
6
6
  ]
@@ -751,6 +751,8 @@ def fill_holes_3d(array):
751
751
 
752
752
  return holes_mask
753
753
 
754
+ print("Filling Holes...")
755
+
754
756
  array = binarize(array)
755
757
  inv_array = invert_array(array)
756
758
 
@@ -3270,7 +3272,10 @@ class Network_3D:
3270
3272
  self._xy_scale = xy_scale
3271
3273
  self._z_scale = z_scale
3272
3274
 
3273
- self.save_scaling(directory)
3275
+ try:
3276
+ self.save_scaling(directory)
3277
+ except:
3278
+ pass
3274
3279
 
3275
3280
  if search is None and ignore_search_region == False:
3276
3281
  search = 0
@@ -3286,29 +3291,51 @@ class Network_3D:
3286
3291
  if other_nodes is not None:
3287
3292
  self.merge_nodes(other_nodes, label_nodes)
3288
3293
 
3289
- self.save_nodes(directory)
3290
- self.save_node_identities(directory)
3294
+ try:
3295
+ self.save_nodes(directory)
3296
+ except:
3297
+ pass
3298
+ try:
3299
+ self.save_node_identities(directory)
3300
+ except:
3301
+ pass
3291
3302
 
3292
3303
  if not ignore_search_region:
3293
3304
  self.calculate_search_region(search, GPU = GPU, fast_dil = fast_dil, GPU_downsample = GPU_downsample)
3294
- self._nodes = None
3305
+ #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)
3295
3306
  search = None
3296
- self.save_search_region(directory)
3307
+ try:
3308
+ self.save_search_region(directory)
3309
+ except:
3310
+ pass
3297
3311
 
3298
3312
  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)
3299
3313
  del edges
3300
- self.save_edges(directory)
3314
+ try:
3315
+ self.save_edges(directory)
3316
+ except:
3317
+ pass
3301
3318
 
3302
3319
  self.calculate_network(search = search, ignore_search_region = ignore_search_region)
3303
- self.save_network(directory)
3320
+
3321
+ try:
3322
+ self.save_network(directory)
3323
+ except:
3324
+ pass
3304
3325
 
3305
3326
  if self._nodes is None:
3306
3327
  self.load_nodes(directory)
3307
3328
 
3308
3329
  self.calculate_node_centroids(down_factor)
3309
- self.save_node_centroids(directory)
3330
+ try:
3331
+ self.save_node_centroids(directory)
3332
+ except:
3333
+ pass
3310
3334
  self.calculate_edge_centroids(down_factor)
3311
- self.save_edge_centroids(directory)
3335
+ try:
3336
+ self.save_edge_centroids(directory)
3337
+ except:
3338
+ pass
3312
3339
 
3313
3340
 
3314
3341
  def draw_network(self, directory = None, down_factor = None, GPU = False):
@@ -3557,15 +3584,21 @@ class Network_3D:
3557
3584
  list1 = self._network_lists[0] #Get network lists to change
3558
3585
  list2 = self._network_lists[1]
3559
3586
  list3 = self._network_lists[2]
3587
+ return1 = []
3588
+ return2 = []
3589
+ return3 = []
3560
3590
 
3561
3591
  for i in range(len(list1)):
3562
3592
  list1[i] = self.communities[list1[i]] #Set node at network list spot to its community instead
3563
3593
  list2[i] = self.communities[list2[i]]
3564
- if list1[i] == list2[i]: #If the edge corresponding there joins different communities, it will not be set to 0
3565
- list3[i] = 0
3594
+ if list1[i] != list2[i]: #Avoid self - self connections
3595
+ return1.append(list1[i])
3596
+ return2.append(list2[i])
3597
+ return3.append(list3[i])
3598
+
3566
3599
 
3567
3600
 
3568
- self.network_lists = [list1, list2, list3]
3601
+ self.network_lists = [return1, return2, return3]
3569
3602
 
3570
3603
  if self._nodes is not None:
3571
3604
  self._nodes = update_array(self._nodes, inverted, targets = targets) #Set the array to match the new network
@@ -325,7 +325,7 @@ class ImageViewerWindow(QMainWindow):
325
325
  self.tabbed_data = TabbedDataWidget(self)
326
326
  right_layout.addWidget(self.tabbed_data)
327
327
  # Initialize data_table property to None - it will be set when tabs are added
328
- self.data_table = None
328
+ self.data_table = []
329
329
 
330
330
  # Create table control panel
331
331
  table_control = QWidget()
@@ -1814,6 +1814,7 @@ class ImageViewerWindow(QMainWindow):
1814
1814
  new_xlim = [xdata - x_range, xdata + x_range]
1815
1815
  new_ylim = [ydata - y_range, ydata + y_range]
1816
1816
 
1817
+
1817
1818
  if (new_xlim[0] <= self.original_xlim[0] or
1818
1819
  new_xlim[1] >= self.original_xlim[1] or
1819
1820
  new_ylim[0] <= self.original_ylim[0] or
@@ -3228,6 +3229,9 @@ class ImageViewerWindow(QMainWindow):
3228
3229
  self.highlight_overlay = None
3229
3230
  except:
3230
3231
  pass
3232
+ if not data:
3233
+ self.original_xlim = None
3234
+ self.original_ylim = None
3231
3235
  continue
3232
3236
  else:
3233
3237
  old_shape = self.channel_data[i].shape[:3] #Ask user to resize images that are shaped differently
@@ -3987,7 +3991,7 @@ class CustomTableView(QTableView):
3987
3991
  desc_action.triggered.connect(lambda checked, c=col: self.sort_table(c, ascending=False))
3988
3992
 
3989
3993
  # Different menus for top and bottom tables
3990
- if self == self.parent.data_table: # Top table
3994
+ if self in self.parent.data_table: # Top table
3991
3995
  save_menu = context_menu.addMenu("Save As")
3992
3996
  save_csv = save_menu.addAction("CSV")
3993
3997
  save_excel = save_menu.addAction("Excel")
@@ -4096,7 +4100,7 @@ class CustomTableView(QTableView):
4096
4100
  df = self.model()._data
4097
4101
 
4098
4102
  # Get table name for the file dialog title
4099
- if self == self.parent.data_table:
4103
+ if self in self.parent.data_table:
4100
4104
  table_name = "Statistics"
4101
4105
  elif self == self.parent.network_table:
4102
4106
  table_name = "Network"
@@ -4105,7 +4109,7 @@ class CustomTableView(QTableView):
4105
4109
 
4106
4110
  # Get save file name
4107
4111
  file_filter = ("CSV Files (*.csv)" if file_type == 'csv' else
4108
- "Excel Files (*.xlsx)" if file_type == 'excel' else
4112
+ "Excel Files (*.xlsx)" if file_type == 'xlsx' else
4109
4113
  "Gephi Graph (*.gexf)" if file_type == 'gexf' else
4110
4114
  "GraphML (*.graphml)" if file_type == 'graphml' else
4111
4115
  "Pajek Network (*.net)")
@@ -4490,7 +4494,13 @@ class TabbedDataWidget(QTabWidget):
4490
4494
  """Add a new table with the given name"""
4491
4495
  if name in self.tables:
4492
4496
  # If tab already exists, update its content
4493
- idx = self.indexOf(self.tables[name])
4497
+ old_table = self.tables[name]
4498
+ idx = self.indexOf(old_table)
4499
+
4500
+ # Remove the old table reference from parent's data_table
4501
+ if self.parent_window and old_table in self.parent_window.data_table:
4502
+ self.parent_window.data_table.remove(old_table)
4503
+
4494
4504
  self.removeTab(idx)
4495
4505
 
4496
4506
  # Create a new CustomTableView with is_top_table=True
@@ -4510,7 +4520,7 @@ class TabbedDataWidget(QTabWidget):
4510
4520
 
4511
4521
  # Update parent's data_table reference
4512
4522
  if self.parent_window:
4513
- self.parent_window.data_table = new_table
4523
+ self.parent_window.data_table.append(new_table)
4514
4524
 
4515
4525
  def close_tab(self, index):
4516
4526
  """Close the tab at the given index"""
@@ -4525,12 +4535,12 @@ class TabbedDataWidget(QTabWidget):
4525
4535
  if name_to_remove:
4526
4536
  del self.tables[name_to_remove]
4527
4537
 
4528
- self.removeTab(index)
4529
-
4530
- # Update parent's data_table reference to current table
4531
- if self.parent_window and self.count() > 0:
4532
- self.parent_window.data_table = self.currentWidget()
4538
+ # Update parent's data_table reference by removing the widget
4539
+ if self.parent_window and widget in self.parent_window.data_table:
4540
+ self.parent_window.data_table.remove(widget)
4533
4541
 
4542
+ self.removeTab(index)
4543
+
4534
4544
  def clear_all_tabs(self):
4535
4545
  """Remove all tabs"""
4536
4546
  while self.count() > 0:
@@ -5433,18 +5443,23 @@ class RadialDialog(QDialog):
5433
5443
 
5434
5444
  def radial(self):
5435
5445
 
5436
- distance = float(self.distance.text()) if self.distance.text().strip() else 50
5446
+ try:
5437
5447
 
5438
- directory = str(self.distance.text()) if self.directory.text().strip() else None
5448
+ distance = float(self.distance.text()) if self.distance.text().strip() else 50
5439
5449
 
5440
- if my_network.node_centroids is None:
5441
- self.parent().show_centroid_dialog()
5450
+ directory = str(self.distance.text()) if self.directory.text().strip() else None
5442
5451
 
5443
- radial = my_network.radial_distribution(distance, directory = directory)
5452
+ if my_network.node_centroids is None:
5453
+ self.parent().show_centroid_dialog()
5444
5454
 
5445
- self.parent().format_for_upperright_table(radial, 'Radial Distance From Any Node', 'Average Number of Neighboring Nodes', title = 'Radial Distribution Analysis')
5455
+ radial = my_network.radial_distribution(distance, directory = directory)
5446
5456
 
5447
- self.accept()
5457
+ self.parent().format_for_upperright_table(radial, 'Radial Distance From Any Node', 'Average Number of Neighboring Nodes', title = 'Radial Distribution Analysis')
5458
+
5459
+ self.accept()
5460
+
5461
+ except Exception as e:
5462
+ print(f"An error occurred: {e}")
5448
5463
 
5449
5464
  class DegreeDistDialog(QDialog):
5450
5465
 
@@ -5477,7 +5492,7 @@ class DegreeDistDialog(QDialog):
5477
5492
 
5478
5493
  self.accept()
5479
5494
 
5480
- except Excpetion as e:
5495
+ except Exception as e:
5481
5496
  print(f"An error occurred: {e}")
5482
5497
 
5483
5498
  class NeighborIdentityDialog(QDialog):
@@ -7783,6 +7798,12 @@ class SkeletonizeDialog(QDialog):
7783
7798
  self.remove = QLineEdit("0")
7784
7799
  layout.addRow("Remove Branches Pixel Length (int):", self.remove)
7785
7800
 
7801
+ # auto checkbox (default True)
7802
+ self.auto = QPushButton("Auto")
7803
+ self.auto.setCheckable(True)
7804
+ self.auto.setChecked(False)
7805
+ layout.addRow("Attempt to Auto Correct Skeleton Looping:", self.auto)
7806
+
7786
7807
  # Add Run button
7787
7808
  run_button = QPushButton("Run Skeletonize")
7788
7809
  run_button.clicked.connect(self.run_skeletonize)
@@ -7796,11 +7817,17 @@ class SkeletonizeDialog(QDialog):
7796
7817
  remove = int(self.remove.text()) if self.remove.text() else 0
7797
7818
  except ValueError:
7798
7819
  remove = 0
7820
+
7821
+ auto = self.auto.isChecked()
7799
7822
 
7800
7823
  # Get the active channel data from parent
7801
7824
  active_data = self.parent().channel_data[self.parent().active_channel]
7802
7825
  if active_data is None:
7803
7826
  raise ValueError("No active image selected")
7827
+
7828
+ if auto:
7829
+ active_data = n3d.skeletonize(active_data)
7830
+ active_data = n3d.fill_holes_3d(active_data)
7804
7831
 
7805
7832
  # Call dilate method with parameters
7806
7833
  result = n3d.skeletonize(
@@ -8112,11 +8139,17 @@ class GenNodesDialog(QDialog):
8112
8139
  self.branch_removal = QLineEdit("0")
8113
8140
  layout.addRow("Skeleton Voxel Branch Length to Remove (int) (Compensates for spines off medial axis):", self.branch_removal)
8114
8141
 
8142
+ self.comp_dil = QLineEdit("0")
8143
+ layout.addRow("Voxel distance to merge nearby nodes (Int - compensates for multi-branch identification along thick branch regions):", self.comp_dil)
8144
+
8115
8145
  self.max_vol = QLineEdit("0")
8116
8146
  layout.addRow("Maximum Voxel Volume of Vertices to Retain (int - Compensates for skeleton looping - occurs before any node merging - the smallest objects are always 27 voxels):", self.max_vol)
8117
8147
 
8118
- self.comp_dil = QLineEdit("0")
8119
- layout.addRow("Voxel distance to merge nearby nodes (Int - compensates for multi-branch identification along thick branch regions):", self.comp_dil)
8148
+ # auto checkbox (default True)
8149
+ self.auto = QPushButton("Auto")
8150
+ self.auto.setCheckable(True)
8151
+ self.auto.setChecked(False)
8152
+ layout.addRow("Attempt to Auto Correct Skeleton Looping:", self.auto)
8120
8153
 
8121
8154
  if not down_factor:
8122
8155
  down_factor = None
@@ -8194,6 +8227,13 @@ class GenNodesDialog(QDialog):
8194
8227
  else:
8195
8228
  order = 0
8196
8229
 
8230
+ auto = self.auto.isChecked()
8231
+
8232
+
8233
+ if auto:
8234
+ my_network.edges = n3d.skeletonize(my_network.edges)
8235
+ my_network.edges = n3d.fill_holes_3d(my_network.edges)
8236
+
8197
8237
 
8198
8238
  result, skele = n3d.label_vertices(
8199
8239
  my_network.edges,
@@ -8248,6 +8288,7 @@ class GenNodesDialog(QDialog):
8248
8288
  )
8249
8289
 
8250
8290
 
8291
+
8251
8292
  class BranchDialog(QDialog):
8252
8293
 
8253
8294
  def __init__(self, parent=None):
@@ -8275,8 +8316,7 @@ class BranchDialog(QDialog):
8275
8316
  self.fix.setChecked(False)
8276
8317
  layout.addRow("Attempt to auto-correct branch labels:", self.fix)
8277
8318
 
8278
- self.fix_val = QLineEdit()
8279
- self.fix_val.setPlaceholderText("Empty = default value...")
8319
+ self.fix_val = QLineEdit('4')
8280
8320
  layout.addRow("If checked above - Avg Degree of Nearby Branch Communities to Merge (Attempt to fix branch labeling - try 4 to 6 to start or leave empty):", self.fix_val)
8281
8321
 
8282
8322
  self.down_factor = QLineEdit("0")
@@ -1024,11 +1024,8 @@ def histogram(counts, y_vals, directory = None):
1024
1024
  plt.ylabel('Avg Number of Neigbhoring Vertices')
1025
1025
 
1026
1026
  try:
1027
-
1028
1027
  if directory is not None:
1029
1028
  plt.savefig(f'{directory}/radial_plot.png')
1030
- else:
1031
- plt.savefig('radial_plot.png')
1032
1029
  except:
1033
1030
  pass
1034
1031
 
@@ -1439,6 +1436,7 @@ def degree_distribution(G, directory = None):
1439
1436
 
1440
1437
  def power_trendline(x, y, directory = None):
1441
1438
  # Handle zeros in y for logarithmic transformations
1439
+ """
1442
1440
  y = np.array(y)
1443
1441
  x = np.array(x)
1444
1442
  y[y == 0] += 0.001
@@ -1460,6 +1458,8 @@ def power_trendline(x, y, directory = None):
1460
1458
  ss_res = np.sum((y - y_pred) ** 2)
1461
1459
  ss_tot = np.sum((y - np.mean(y)) ** 2)
1462
1460
  r2 = 1 - (ss_res / ss_tot)
1461
+ """
1462
+ # ^ I commented out this power trendline stuff because I decided I no longer want it to do that so.
1463
1463
 
1464
1464
  # Create a scatterplot
1465
1465
  plt.scatter(x, y, label='Data')
@@ -1468,9 +1468,10 @@ def power_trendline(x, y, directory = None):
1468
1468
  plt.title('Degree Distribution of Network')
1469
1469
 
1470
1470
  # Plot the power trendline
1471
- plt.plot(x_fit, y_fit, color='red', label=f'Power Trendline: $y = {a:.2f}x^{{{b:.2f}}}$')
1471
+ #plt.plot(x_fit, y_fit, color='red', label=f'Power Trendline: $y = {a:.2f}x^{{{b:.2f}}}$')
1472
1472
 
1473
1473
  # Annotate the plot with the trendline equation and R-squared value
1474
+ """
1474
1475
  plt.text(
1475
1476
  0.05, 0.95,
1476
1477
  f'$y = {a:.2f}x^{{{b:.2f}}}$\n$R^2 = {r2:.2f}$',
@@ -1478,13 +1479,12 @@ def power_trendline(x, y, directory = None):
1478
1479
  fontsize=12,
1479
1480
  verticalalignment='top'
1480
1481
  )
1482
+ """
1481
1483
 
1482
1484
  try:
1483
1485
 
1484
1486
  if directory is not None:
1485
1487
  plt.savefig(f'{directory}/degree_plot.png')
1486
- else:
1487
- plt.savefig('degree_plot.png')
1488
1488
  except:
1489
1489
  pass
1490
1490
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nettracer3d
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: Scripts for intializing and analyzing networks from segmentations of three dimensional images.
5
5
  Author-email: Liam McLaughlin <mclaughlinliam99@gmail.com>
6
6
  Project-URL: User_Tutorial, https://www.youtube.com/watch?v=cRatn5VTWDY
@@ -45,10 +45,15 @@ NetTracer3D is free to use/fork for academic/nonprofit use so long as citation i
45
45
 
46
46
  NetTracer3D was developed by Liam McLaughlin while working under Dr. Sanjay Jain at Washington University School of Medicine.
47
47
 
48
- -- Version 0.6.3 updates --
48
+ -- Version 0.6.4 updates --
49
49
 
50
- 1. Fixed bug with the active channel indicator on the GUI not always updating when the active channel was changed by internal loading.
50
+ 1. Fixed bug with tabulated data in top right having a right click window corresponding to the bottom left.
51
51
 
52
- 2. Updated ram_lock mode in the segmenter to garbage collect better... again.
52
+ 2. Fixed bug when converting communities to nodes that let the same community connect to itself in the new network.
53
53
 
54
- 3. Added new function (Label neighborhoods). Find it in process -> image -> label neighborhoods. Allows a 3d labeled array to act as a kernel and extend its labels along a secondary binary array. This can be useful in evaluating nearest neighbors en-masse. The method behind this (smart label) was previously used internally for some methods (ie watershedding) but I thought I'd include it as an actual function since it has some more general uses.
54
+ 3. Removed attempted trendline fitting from degree distribution
55
+
56
+ 4. Added new feature to skeletonization (and corresponding branch labeler/gennodes)
57
+ Now you can have the program attempt to auto-correct 3D skeletonization loop artifacts through a method that just runs the 3d fill holes algo and then attempts to reskeletonize the output. This worked well in my own testing.
58
+
59
+ 5. Other minor fixes/improvements
File without changes
File without changes