nettracer3d 1.2.5__py3-none-any.whl → 1.2.7__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.
@@ -3,6 +3,7 @@ import networkx as nx
3
3
  from . import nettracer as n3d
4
4
  from scipy.ndimage import distance_transform_edt, gaussian_filter, binary_fill_holes
5
5
  from scipy.spatial import cKDTree
6
+ from . import smart_dilate as sdl
6
7
  from skimage.morphology import remove_small_objects, skeletonize
7
8
  import warnings
8
9
  warnings.filterwarnings('ignore')
@@ -14,9 +15,13 @@ class VesselDenoiser:
14
15
  """
15
16
 
16
17
  def __init__(self,
17
- score_thresh = 2):
18
+ score_thresh = 2,
19
+ xy_scale = 1,
20
+ z_scale = 1):
18
21
 
19
22
  self.score_thresh = score_thresh
23
+ self.xy_scale = xy_scale
24
+ self.z_scale = z_scale
20
25
 
21
26
 
22
27
  def select_kernel_points_topology(self, data, skeleton):
@@ -336,7 +341,7 @@ class VesselDenoiser:
336
341
  # Compute distance transform
337
342
  if verbose:
338
343
  print("Computing distance transform...")
339
- distance_map = distance_transform_edt(data)
344
+ distance_map = sdl.compute_distance_transform_distance(data, fast_dil = True)
340
345
 
341
346
  # Extract endpoints
342
347
  if verbose:
@@ -390,14 +395,14 @@ class VesselDenoiser:
390
395
  return label_dict
391
396
 
392
397
 
393
- def trace(data, labeled_skeleton, verts, score_thresh=10, verbose=False):
398
+ def trace(data, labeled_skeleton, verts, score_thresh=10, xy_scale = 1, z_scale = 1, verbose=False):
394
399
  """
395
400
  Trace and unify skeleton labels using vertex-based endpoint grouping
396
401
  """
397
402
  skeleton = n3d.binarize(labeled_skeleton)
398
403
 
399
404
  # Create denoiser
400
- denoiser = VesselDenoiser(score_thresh=score_thresh)
405
+ denoiser = VesselDenoiser(score_thresh=score_thresh, xy_scale = xy_scale, z_scale = z_scale)
401
406
 
402
407
  # Run label unification
403
408
  label_dict = denoiser.denoise(data, skeleton, labeled_skeleton, verts, verbose=verbose)
nettracer3d/filaments.py CHANGED
@@ -1,10 +1,11 @@
1
1
  import numpy as np
2
2
  import networkx as nx
3
3
  from . import nettracer as n3d
4
- from scipy.ndimage import distance_transform_edt, gaussian_filter, binary_fill_holes
4
+ from scipy.ndimage import gaussian_filter, binary_fill_holes
5
5
  from scipy.spatial import cKDTree
6
6
  from skimage.morphology import remove_small_objects, skeletonize
7
7
  import warnings
8
+ from . import smart_dilate as sdl
8
9
  warnings.filterwarnings('ignore')
9
10
 
10
11
 
@@ -22,6 +23,8 @@ class VesselDenoiser:
22
23
  blob_volume = 200,
23
24
  spine_removal=0,
24
25
  score_thresh = 2,
26
+ xy_scale = 1,
27
+ z_scale = 1,
25
28
  radius_aware_distance=True):
26
29
  """
27
30
  Parameters:
@@ -46,6 +49,8 @@ class VesselDenoiser:
46
49
  self.spine_removal = spine_removal
47
50
  self.radius_aware_distance = radius_aware_distance
48
51
  self.score_thresh = score_thresh
52
+ self.xy_scale = xy_scale
53
+ self.z_scale = z_scale
49
54
 
50
55
  self._sphere_cache = {} # Cache sphere masks for different radii
51
56
 
@@ -939,7 +944,7 @@ class VesselDenoiser:
939
944
 
940
945
  if verbose:
941
946
  print("Step 3: Computing distance transform...")
942
- distance_map = distance_transform_edt(cleaned)
947
+ distance_map = sdl.compute_distance_transform_distance(cleaned, fast_dil = True)
943
948
 
944
949
  # Step 3: Sample kernels along skeleton
945
950
  if verbose:
@@ -1036,7 +1041,7 @@ class VesselDenoiser:
1036
1041
  return result
1037
1042
 
1038
1043
 
1039
- def trace(data, kernel_spacing = 1, max_distance = 20, min_component = 20, gap_tolerance = 5, blob_sphericity = 1.0, blob_volume = 200, spine_removal = 0, score_thresh = 2):
1044
+ def trace(data, kernel_spacing = 1, max_distance = 20, min_component = 20, gap_tolerance = 5, blob_sphericity = 1.0, blob_volume = 200, spine_removal = 0, score_thresh = 2, xy_scale = 1, z_scale = 1):
1040
1045
 
1041
1046
  """Main function with user prompts"""
1042
1047
 
@@ -1054,7 +1059,9 @@ def trace(data, kernel_spacing = 1, max_distance = 20, min_component = 20, gap_t
1054
1059
  blob_sphericity = blob_sphericity,
1055
1060
  blob_volume = blob_volume,
1056
1061
  spine_removal = spine_removal,
1057
- score_thresh = score_thresh
1062
+ score_thresh = score_thresh,
1063
+ xy_scale = xy_scale,
1064
+ z_scale = z_scale
1058
1065
  )
1059
1066
 
1060
1067
  # Run denoising
nettracer3d/modularity.py CHANGED
@@ -103,7 +103,7 @@ def read_excel_to_lists(file_path, sheet_name=0):
103
103
 
104
104
 
105
105
 
106
- def show_communities_flex(G, master_list, normalized_weights, geo_info=None, geometric=False, directory=None, weighted=True, partition=None, style=0):
106
+ def show_communities_flex(G, master_list, normalized_weights, geo_info=None, geometric=False, directory=None, weighted=True, partition=None, style=0, show_labels = True):
107
107
 
108
108
  if normalized_weights is None:
109
109
  G, edge_weights = network_analysis.weighted_network(master_list)
@@ -179,7 +179,8 @@ def show_communities_flex(G, master_list, normalized_weights, geo_info=None, geo
179
179
  normalized_weight = G[edge[0]][edge[1]]['weight']
180
180
  nx.draw_networkx_edges(G, pos, edgelist=[edge], width=5 * normalized_weight, edge_color='black')
181
181
 
182
- nx.draw_networkx_labels(G, pos)
182
+ if show_labels:
183
+ nx.draw_networkx_labels(G, pos)
183
184
 
184
185
  else:
185
186
  pos = nx.spring_layout(G)
@@ -195,20 +196,28 @@ def show_communities_flex(G, master_list, normalized_weights, geo_info=None, geo
195
196
  normalized_weight = G[edge[0]][edge[1]]['weight']
196
197
  nx.draw_networkx_edges(G, pos, edgelist=[edge], width=5 * normalized_weight, edge_color='black')
197
198
 
198
- nx.draw_networkx_labels(G, pos)
199
+ if show_labels:
200
+ nx.draw_networkx_labels(G, pos)
199
201
 
200
202
  else:
201
203
  # Create node color list based on partition and the same color mapping
202
- node_colors = [colors_matplotlib[partition[node]] for node in G.nodes()]
204
+ node_colors = []
205
+ for node in G.nodes():
206
+ try:
207
+ node_colors.append(colors_matplotlib[partition[node]])
208
+ except:
209
+ node_colors.append((1, 1, 1))
210
+
211
+ #node_colors = [colors_matplotlib[partition[node]] for node in G.nodes()]
203
212
 
204
213
  if geometric:
205
214
  pos, z_pos = simple_network.geometric_positions(geo_info[0], geo_info[1])
206
215
  node_sizes_list = [z_pos[node] for node in G.nodes()]
207
- nx.draw(G, pos, with_labels=True, font_color='black', font_weight='bold',
216
+ nx.draw(G, pos, with_labels=show_labels, font_color='black', font_weight='bold',
208
217
  node_size=node_sizes_list, node_color=node_colors, alpha=0.8, font_size=12)
209
218
  else:
210
219
  pos = nx.spring_layout(G)
211
- nx.draw(G, pos, with_labels=True, font_color='black', font_weight='bold',
220
+ nx.draw(G, pos, with_labels=show_labels, font_color='black', font_weight='bold',
212
221
  node_size=100, node_color=node_colors, alpha=0.8)
213
222
 
214
223
  plt.axis('off')
nettracer3d/morphology.py CHANGED
@@ -337,7 +337,7 @@ def search_neighbor_ids(nodes, targets, id_dict, neighborhood_dict, totals, sear
337
337
  targets = np.isin(nodes, targets)
338
338
  targets = nettracer.binarize(targets)
339
339
 
340
- dilated = nettracer.dilate(targets, search, xy_scale = xy_scale, z_scale = z_scale, fast_dil = fastdil)
340
+ dilated = nettracer.dilate_3D_dt(targets, search, xy_scaling = xy_scale, z_scaling = z_scale, fast_dil = fastdil)
341
341
  dilated = dilated - targets #technically we dont need the cores
342
342
  search_vol = np.count_nonzero(dilated) * xy_scale * xy_scale * z_scale #need this for density
343
343
  targets = dilated != 0
nettracer3d/nettracer.py CHANGED
@@ -90,6 +90,7 @@ def reslice_3d_array(args):
90
90
  def _get_node_edge_dict(label_array, edge_array, label, dilate_xy, dilate_z):
91
91
  """Internal method used for the secondary algorithm to find which nodes interact with which edges."""
92
92
 
93
+ import tifffile
93
94
  # Create a boolean mask where elements with the specified label are True
94
95
  label_array = label_array == label
95
96
  label_array = dilate_3D(label_array, dilate_xy, dilate_xy, dilate_z) #Dilate the label to see where the dilated label overlaps
@@ -104,7 +105,7 @@ def _get_node_edge_dict(label_array, edge_array, label, dilate_xy, dilate_z):
104
105
  def process_label(args):
105
106
  """Modified to use pre-computed bounding boxes instead of argwhere"""
106
107
  nodes, edges, label, dilate_xy, dilate_z, array_shape, bounding_boxes = args
107
- print(f"Processing node {label}")
108
+ #print(f"Processing node {label}")
108
109
 
109
110
  # Get the pre-computed bounding box for this label
110
111
  slice_obj = bounding_boxes[int(label)-1] # -1 because label numbers start at 1
@@ -122,6 +123,7 @@ def process_label(args):
122
123
 
123
124
  def create_node_dictionary(nodes, edges, num_nodes, dilate_xy, dilate_z):
124
125
  """Modified to pre-compute all bounding boxes using find_objects"""
126
+ print("Calculating network...")
125
127
  node_dict = {}
126
128
  array_shape = nodes.shape
127
129
 
@@ -1520,7 +1522,7 @@ def dilate_2D(array, search, scaling = 1):
1520
1522
  return inv
1521
1523
 
1522
1524
 
1523
- def dilate_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0):
1525
+ def dilate_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0, fast_dil = False):
1524
1526
  """
1525
1527
  Dilate a 3D array using distance transform method. Dt dilation produces perfect results but only works in euclidean geometry and lags in big arrays.
1526
1528
 
@@ -1568,7 +1570,7 @@ def dilate_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0):
1568
1570
  """
1569
1571
 
1570
1572
  # Compute distance transform (Euclidean)
1571
- inv = smart_dilate.compute_distance_transform_distance(inv, sampling = [z_scaling, xy_scaling, xy_scaling])
1573
+ inv = smart_dilate.compute_distance_transform_distance(inv, sampling = [z_scaling, xy_scaling, xy_scaling], fast_dil = fast_dil)
1572
1574
 
1573
1575
  #inv = inv * cardinal
1574
1576
 
@@ -1615,7 +1617,7 @@ def erode_2D(array, search, scaling=1, preserve_labels = False):
1615
1617
 
1616
1618
  return array
1617
1619
 
1618
- def erode_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0, preserve_labels = False):
1620
+ def erode_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0, fast_dil = False, preserve_labels = False):
1619
1621
  """
1620
1622
  Erode a 3D array using distance transform method. DT erosion produces perfect results
1621
1623
  with Euclidean geometry, but may be slower for large arrays.
@@ -1643,34 +1645,30 @@ def erode_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0, preserve_
1643
1645
 
1644
1646
  borders = find_boundaries(array, mode='thick')
1645
1647
  mask = array * invert_array(borders)
1646
- mask = smart_dilate.compute_distance_transform_distance(mask, sampling = [z_scaling, xy_scaling, xy_scaling])
1648
+ mask = smart_dilate.compute_distance_transform_distance(mask, sampling = [z_scaling, xy_scaling, xy_scaling], fast_dil = fast_dil)
1647
1649
  mask = mask >= search_distance
1648
1650
  array = mask * array
1649
1651
  else:
1650
- array = smart_dilate.compute_distance_transform_distance(array, sampling = [z_scaling, xy_scaling, xy_scaling])
1652
+ array = smart_dilate.compute_distance_transform_distance(array, sampling = [z_scaling, xy_scaling, xy_scaling], fast_dil = fast_dil)
1651
1653
  # Threshold the distance transform to get eroded result
1652
1654
  # For erosion, we keep only the points that are at least search_distance from the boundary
1653
1655
  array = array > search_distance
1654
1656
 
1655
- # Resample back to original dimensions if needed
1656
- #if rev_factor:
1657
- #array = ndimage.zoom(array, rev_factor, order=0) # Use order=0 for binary masks
1658
-
1659
1657
  return array.astype(np.uint8)
1660
1658
 
1661
1659
 
1662
1660
  def dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z):
1663
- """Internal method to dilate an array in 3D. Dilation this way is much faster than using a distance transform although the latter is theoretically more accurate.
1661
+ """Internal method to dilate an array in 3D. Dilation this way is much faster than using a distance transform although the latter is more accurate.
1664
1662
  Arguments are an array, and the desired pixel dilation amounts in X, Y, Z. Uses psuedo-3D kernels (imagine a 3D + sign rather than a cube) to approximate 3D neighborhoods but will miss diagonally located things with larger kernels, if those are needed use the distance transform version.
1665
1663
  """
1666
1664
 
1667
- if tiff_array.shape[0] == 1:
1668
- return dilate_2D(tiff_array, ((dilated_x - 1) / 2))
1669
-
1670
1665
  if dilated_x == 3 and dilated_y == 3 and dilated_z == 3:
1671
1666
 
1672
1667
  return dilate_3D_old(tiff_array, dilated_x, dilated_y, dilated_z)
1673
1668
 
1669
+ if tiff_array.shape[0] == 1:
1670
+ return dilate_2D(tiff_array, ((dilated_x - 1) / 2))
1671
+
1674
1672
  def create_circular_kernel(diameter):
1675
1673
  """Create a 2D circular kernel with a given radius.
1676
1674
 
@@ -1802,11 +1800,6 @@ def dilate_3D_old(tiff_array, dilated_x=3, dilated_y=3, dilated_z=3):
1802
1800
  Dilated 3D array
1803
1801
  """
1804
1802
 
1805
- # Handle special case for 2D arrays
1806
- if tiff_array.shape[0] == 1:
1807
- # Call 2D dilation function if needed
1808
- return dilate_2D(tiff_array, 1) # For a 3x3 kernel, radius is 1
1809
-
1810
1803
  # Create a simple 3x3x3 cubic kernel (all ones)
1811
1804
  kernel = np.ones((3, 3, 3), dtype=bool)
1812
1805
 
@@ -1816,107 +1809,6 @@ def dilate_3D_old(tiff_array, dilated_x=3, dilated_y=3, dilated_z=3):
1816
1809
  return dilated_array.astype(np.uint8)
1817
1810
 
1818
1811
 
1819
- def erode_3D(tiff_array, eroded_x, eroded_y, eroded_z):
1820
- """Internal method to erode an array in 3D. Erosion this way is faster than using a distance transform although the latter is theoretically more accurate.
1821
- Arguments are an array, and the desired pixel erosion amounts in X, Y, Z."""
1822
-
1823
- if tiff_array.shape[0] == 1:
1824
- return erode_2D(tiff_array, ((eroded_x - 1) / 2))
1825
-
1826
- def create_circular_kernel(diameter):
1827
- """Create a 2D circular kernel with a given radius.
1828
- Parameters:
1829
- radius (int or float): The radius of the circle.
1830
- Returns:
1831
- numpy.ndarray: A 2D numpy array representing the circular kernel.
1832
- """
1833
- # Determine the size of the kernel
1834
- radius = diameter/2
1835
- size = radius # Diameter of the circle
1836
- size = int(np.ceil(size)) # Ensure size is an integer
1837
-
1838
- # Create a grid of (x, y) coordinates
1839
- y, x = np.ogrid[-radius:radius+1, -radius:radius+1]
1840
-
1841
- # Calculate the distance from the center (0,0)
1842
- distance = np.sqrt(x**2 + y**2)
1843
-
1844
- # Create the circular kernel: points within the radius are 1, others are 0
1845
- kernel = distance <= radius
1846
-
1847
- # Convert the boolean array to integer (0 and 1)
1848
- return kernel.astype(np.uint8)
1849
-
1850
- def create_ellipsoidal_kernel(long_axis, short_axis):
1851
- """Create a 2D ellipsoidal kernel with specified axis lengths and orientation.
1852
- Parameters:
1853
- long_axis (int or float): The length of the long axis.
1854
- short_axis (int or float): The length of the short axis.
1855
- Returns:
1856
- numpy.ndarray: A 2D numpy array representing the ellipsoidal kernel.
1857
- """
1858
- semi_major, semi_minor = long_axis / 2, short_axis / 2
1859
- # Determine the size of the kernel
1860
- size_y = int(np.ceil(semi_minor))
1861
- size_x = int(np.ceil(semi_major))
1862
-
1863
- # Create a grid of (x, y) coordinates centered at (0,0)
1864
- y, x = np.ogrid[-semi_minor:semi_minor+1, -semi_major:semi_major+1]
1865
-
1866
- # Ellipsoid equation: (x/a)^2 + (y/b)^2 <= 1
1867
- ellipse = (x**2 / semi_major**2) + (y**2 / semi_minor**2) <= 1
1868
-
1869
- return ellipse.astype(np.uint8)
1870
-
1871
- z_depth = tiff_array.shape[0]
1872
-
1873
- # Function to process each slice
1874
- def process_slice(z):
1875
- tiff_slice = tiff_array[z].astype(np.uint8)
1876
- eroded_slice = cv2.erode(tiff_slice, kernel, iterations=1)
1877
- return z, eroded_slice
1878
-
1879
- def process_slice_other(y):
1880
- tiff_slice = tiff_array[:, y, :].astype(np.uint8)
1881
- eroded_slice = cv2.erode(tiff_slice, kernel, iterations=1)
1882
- return y, eroded_slice
1883
-
1884
- # Create empty arrays to store the eroded results for the XY and XZ planes
1885
- eroded_xy = np.zeros_like(tiff_array, dtype=np.uint8)
1886
- eroded_xz = np.zeros_like(tiff_array, dtype=np.uint8)
1887
-
1888
- kernel_x = int(eroded_x)
1889
- kernel = create_circular_kernel(kernel_x)
1890
-
1891
- num_cores = mp.cpu_count()
1892
- with ThreadPoolExecutor(max_workers=num_cores) as executor:
1893
- futures = {executor.submit(process_slice, z): z for z in range(tiff_array.shape[0])}
1894
- for future in as_completed(futures):
1895
- z, eroded_slice = future.result()
1896
- eroded_xy[z] = eroded_slice
1897
-
1898
- kernel_x = int(eroded_x)
1899
- kernel_z = int(eroded_z)
1900
- kernel = create_ellipsoidal_kernel(kernel_x, kernel_z)
1901
-
1902
- if z_depth != 2:
1903
-
1904
- with ThreadPoolExecutor(max_workers=num_cores) as executor:
1905
- futures = {executor.submit(process_slice_other, y): y for y in range(tiff_array.shape[1])}
1906
-
1907
- for future in as_completed(futures):
1908
- y, eroded_slice = future.result()
1909
- eroded_xz[:, y, :] = eroded_slice
1910
-
1911
- # Overlay the results using AND operation instead of OR for erosion
1912
- if z_depth != 2:
1913
- final_result = eroded_xy & eroded_xz
1914
- else:
1915
- return eroded_xy
1916
-
1917
- return final_result
1918
-
1919
-
1920
1812
  def dilation_length_to_pixels(xy_scaling, z_scaling, micronx, micronz):
1921
1813
  """Internal method to find XY and Z dilation parameters based on voxel micron scaling"""
1922
1814
  dilate_xy = 2 * int(round(micronx/xy_scaling))
@@ -2321,12 +2213,13 @@ def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast
2321
2213
  def erode(arrayimage, amount, xy_scale = 1, z_scale = 1, mode = 0, preserve_labels = False):
2322
2214
  if not preserve_labels and len(np.unique(arrayimage)) > 2: #binarize
2323
2215
  arrayimage = binarize(arrayimage)
2324
- erode_xy, erode_z = dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
2325
2216
 
2326
- if mode == 2:
2327
- arrayimage = (erode_3D(arrayimage, erode_xy, erode_xy, erode_z)) * 255
2217
+ if mode == 0 or mode == 2:
2218
+ fast_dil = True
2328
2219
  else:
2329
- arrayimage = erode_3D_dt(arrayimage, amount, xy_scaling=xy_scale, z_scaling=z_scale, preserve_labels = preserve_labels)
2220
+ fast_dil = False
2221
+
2222
+ arrayimage = erode_3D_dt(arrayimage, amount, xy_scaling=xy_scale, z_scaling=z_scale, fast_dil = fast_dil, preserve_labels = preserve_labels)
2330
2223
 
2331
2224
  if np.max(arrayimage) == 1:
2332
2225
  arrayimage = arrayimage * 255
@@ -2378,7 +2271,7 @@ def skeletonize(arrayimage, directory = None):
2378
2271
 
2379
2272
  return arrayimage
2380
2273
 
2381
- def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = None, directory = None, nodes = None, bonus_array = None, GPU = True, arrayshape = None, compute = False, unify = False, union_val = 10, xy_scale = 1, z_scale = 1):
2274
+ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = None, directory = None, nodes = None, bonus_array = None, GPU = True, arrayshape = None, compute = False, unify = False, union_val = 10, mode = 0, xy_scale = 1, z_scale = 1):
2382
2275
  """
2383
2276
  Can be used to label branches a binary image. Labelled output will be saved to the active directory if none is specified. Note this works better on already thin filaments and may over-divide larger trunkish objects.
2384
2277
  :param array: (Mandatory, string or ndarray) - If string, a path to a tif file to label. Note that the ndarray alternative is for internal use mainly and will not save its output.
@@ -2419,24 +2312,25 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
2419
2312
  from . import branch_stitcher
2420
2313
  verts = dilate_3D_old(verts, 3, 3, 3,)
2421
2314
  verts, _ = label_objects(verts)
2422
- array = branch_stitcher.trace(bonus_array, array, verts, score_thresh = union_val)
2315
+ print("Merging branches...")
2316
+ array = branch_stitcher.trace(bonus_array, array, verts, score_thresh = union_val, xy_scale = xy_scale, z_scale = z_scale)
2423
2317
  verts = None
2424
2318
 
2425
2319
 
2426
2320
  if nodes is None:
2427
2321
 
2428
- array = smart_dilate.smart_label(array, other_array, GPU = GPU, remove_template = True)
2322
+ array = smart_dilate.smart_label(array, other_array, GPU = GPU, remove_template = True, mode = mode)
2429
2323
  #distance = smart_dilate.compute_distance_transform_distance(array)
2430
2324
  #array = water(-distance, other_array, mask=array) #Tried out skimage watershed as shown and found it did not label branches as well as smart_label (esp combined combined with post-processing label splitting if needed)
2431
2325
 
2432
2326
  else:
2433
2327
  if down_factor is not None:
2434
- array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, predownsample = down_factor, remove_template = True)
2328
+ array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, predownsample = down_factor, remove_template = True, mode = mode)
2435
2329
  #distance = smart_dilate.compute_distance_transform_distance(bonus_array)
2436
2330
  #array = water(-distance, array, mask=bonus_array)
2437
2331
  else:
2438
2332
 
2439
- array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, remove_template = True)
2333
+ array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, remove_template = True, mode = mode)
2440
2334
  #distance = smart_dilate.compute_distance_transform_distance(bonus_array)
2441
2335
  #array = water(-distance, array, mask=bonus_array)
2442
2336
 
@@ -2628,19 +2522,18 @@ def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
2628
2522
  if peaks > 0:
2629
2523
  image_copy = filter_size_by_peaks(image_copy, peaks)
2630
2524
  if comp_dil > 0:
2631
- image_copy = dilate(image_copy, comp_dil, fast_dil = fastdil)
2525
+ image_copy = dilate_3D_dt(image_copy, comp_dil, fast_dil = fastdil)
2632
2526
 
2633
2527
  labeled_image, num_labels = label_objects(image_copy)
2634
2528
  elif max_vol > 0:
2635
2529
  image_copy = filter_size_by_vol(image_copy, max_vol)
2636
2530
  if comp_dil > 0:
2637
- image_copy = dilate(image_copy, comp_dil, fast_dil = fastdil)
2531
+ image_copy = dilate_3D_dt(image_copy, comp_dil, fast_dil = fastdil)
2638
2532
 
2639
2533
  labeled_image, num_labels = label_objects(image_copy)
2640
2534
  else:
2641
-
2642
2535
  if comp_dil > 0:
2643
- image_copy = dilate(image_copy, comp_dil, fast_dil = fastdil)
2536
+ image_copy = dilate_3D_dt(image_copy, comp_dil, fast_dil = fastdil)
2644
2537
  labeled_image, num_labels = label_objects(image_copy)
2645
2538
 
2646
2539
  #if down_factor > 0:
@@ -2775,7 +2668,7 @@ def gray_watershed(image, min_distance = 1, threshold_abs = None):
2775
2668
  return image
2776
2669
 
2777
2670
 
2778
- def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_rad = None, predownsample = None, predownsample2 = None):
2671
+ def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_rad = None, fast_dil = False, predownsample = None, predownsample2 = None):
2779
2672
  """
2780
2673
  Can be used to 3D watershed a binary image. Watershedding attempts to use an algorithm to split touching objects into seperate labelled components. Labelled output will be saved to the active directory if none is specified.
2781
2674
  This watershed algo essentially uses the distance transform to decide where peaks are and then after thresholding out the non-peaks, uses the peaks as labelling kernels for a smart label. It runs semi slow without GPU accel since it requires two dts to be computed.
@@ -2838,7 +2731,7 @@ def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_ra
2838
2731
  if GPU:
2839
2732
  print("GPU dt failed or did not detect GPU (cupy must be installed with a CUDA toolkit setup...). Computing CPU distance transform instead.")
2840
2733
  print(f"Error message: {str(e)}")
2841
- distance = smart_dilate.compute_distance_transform_distance(image)
2734
+ distance = smart_dilate.compute_distance_transform_distance(image, fast_dil = fast_dil)
2842
2735
 
2843
2736
 
2844
2737
  distance = threshold(distance, proportion, custom_rad = smallest_rad)
@@ -4189,10 +4082,7 @@ class Network_3D:
4189
4082
  if search is not None and hasattr(self, '_nodes') and self._nodes is not None and self._search_region is None:
4190
4083
  search_region = binarize(self._nodes)
4191
4084
  dilate_xy, dilate_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, search, search)
4192
- if fast_dil:
4193
- search_region = dilate_3D(search_region, dilate_xy, dilate_xy, dilate_z)
4194
- else:
4195
- search_region = dilate_3D_dt(search_region, diledge, self._xy_scale, self._z_scale)
4085
+ search_region = dilate_3D_dt(search_region, diledge, self._xy_scale, self._z_scale, fast_dil = fast_dil)
4196
4086
  else:
4197
4087
  search_region = binarize(self._search_region)
4198
4088
 
@@ -4210,10 +4100,8 @@ class Network_3D:
4210
4100
 
4211
4101
  if dilate_xy <= 3 and dilate_z <= 3:
4212
4102
  outer_edges = dilate_3D_old(outer_edges, dilate_xy, dilate_xy, dilate_z)
4213
- elif fast_dil:
4214
- outer_edges = dilate_3D(outer_edges, dilate_xy, dilate_xy, dilate_z)
4215
4103
  else:
4216
- outer_edges = dilate_3D_dt(outer_edges, diledge, self._xy_scale, self._z_scale)
4104
+ outer_edges = dilate_3D_dt(outer_edges, diledge, self._xy_scale, self._z_scale, fast_dil = fast_dil)
4217
4105
  else:
4218
4106
  outer_edges = dilate_3D_old(outer_edges)
4219
4107
 
@@ -4539,9 +4427,6 @@ class Network_3D:
4539
4427
  self._nodes = nodes
4540
4428
  del nodes
4541
4429
 
4542
- if self._nodes.shape[0] == 1:
4543
- fast_dil = True #Set this to true because the 2D algo always uses the distance transform and doesnt need this special ver
4544
-
4545
4430
  if label_nodes:
4546
4431
  self._nodes, num_nodes = label_objects(self._nodes)
4547
4432
  if other_nodes is not None:
@@ -4937,7 +4822,17 @@ class Network_3D:
4937
4822
  nodeb = []
4938
4823
  edgec = []
4939
4824
 
4940
- trunk = stats.mode(edgesc)
4825
+ from collections import Counter
4826
+ counts = Counter(edgesc)
4827
+ if 0 not in edgesc:
4828
+ trunk = stats.mode(edgesc)
4829
+ else:
4830
+ try:
4831
+ sorted_edges = counts.most_common()
4832
+ trunk = sorted_edges[1][0]
4833
+ except:
4834
+ return
4835
+
4941
4836
  addtrunk = max(set(nodesa + nodesb)) + 1
4942
4837
 
4943
4838
  for i in range(len(nodesa)):
@@ -4955,7 +4850,10 @@ class Network_3D:
4955
4850
 
4956
4851
  self.network_lists = [nodea, nodeb, edgec]
4957
4852
 
4958
- self._node_centroids[addtrunk] = self._edge_centroids[trunk]
4853
+ try:
4854
+ self._node_centroids[addtrunk] = self._edge_centroids[trunk]
4855
+ except:
4856
+ pass
4959
4857
 
4960
4858
  if self._node_identities is None:
4961
4859
  self._node_identities = {}
@@ -5193,7 +5091,7 @@ class Network_3D:
5193
5091
 
5194
5092
  #Methods related to visualizing the network using networkX and matplotlib
5195
5093
 
5196
- def show_network(self, geometric = False, directory = None):
5094
+ def show_network(self, geometric = False, directory = None, show_labels = True):
5197
5095
  """
5198
5096
  Shows the network property as a simplistic graph, and some basic stats. Does not support viewing edge weights.
5199
5097
  :param geoemtric: (Optional - Val = False; boolean). If False, node placement in the graph will be random. If True, nodes
@@ -5204,19 +5102,19 @@ class Network_3D:
5204
5102
 
5205
5103
  if not geometric:
5206
5104
 
5207
- simple_network.show_simple_network(self._network_lists, directory = directory)
5105
+ simple_network.show_simple_network(self._network_lists, directory = directory, show_labels = show_labels)
5208
5106
 
5209
5107
  else:
5210
- simple_network.show_simple_network(self._network_lists, geometric = True, geo_info = [self._node_centroids, self._nodes.shape], directory = directory)
5108
+ simple_network.show_simple_network(self._network_lists, geometric = True, geo_info = [self._node_centroids, self._nodes.shape], directory = directory, show_labels = show_labels)
5211
5109
 
5212
- def show_communities_flex(self, geometric = False, directory = None, weighted = True, partition = False, style = 0):
5110
+ def show_communities_flex(self, geometric = False, directory = None, weighted = True, partition = False, style = 0, show_labels = True):
5213
5111
 
5214
5112
 
5215
- self._communities, self.normalized_weights = modularity.show_communities_flex(self._network, self._network_lists, self.normalized_weights, geo_info = [self._node_centroids, self._nodes.shape], geometric = geometric, directory = directory, weighted = weighted, partition = partition, style = style)
5113
+ self._communities, self.normalized_weights = modularity.show_communities_flex(self._network, self._network_lists, self.normalized_weights, geo_info = [self._node_centroids, self._nodes.shape], geometric = geometric, directory = directory, weighted = weighted, partition = partition, style = style, show_labels = show_labels)
5216
5114
 
5217
5115
 
5218
5116
 
5219
- def show_identity_network(self, geometric = False, directory = None):
5117
+ def show_identity_network(self, geometric = False, directory = None, show_labels = True):
5220
5118
  """
5221
5119
  Shows the network property, and some basic stats, as a graph where nodes are labelled by colors representing the identity of the node as detailed in the node_identities property. Does not support viewing edge weights.
5222
5120
  :param geoemtric: (Optional - Val = False; boolean). If False, node placement in the graph will be random. If True, nodes
@@ -5225,9 +5123,9 @@ class Network_3D:
5225
5123
  :param directory: (Optional – Val = None; string). An optional string path to a directory to save the network plot image to. If not set, nothing will be saved.
5226
5124
  """
5227
5125
  if not geometric:
5228
- simple_network.show_identity_network(self._network_lists, self._node_identities, geometric = False, directory = directory)
5126
+ simple_network.show_identity_network(self._network_lists, self._node_identities, geometric = False, directory = directory, show_labels = show_labels)
5229
5127
  else:
5230
- simple_network.show_identity_network(self._network_lists, self._node_identities, geometric = True, geo_info = [self._node_centroids, self._nodes.shape], directory = directory)
5128
+ simple_network.show_identity_network(self._network_lists, self._node_identities, geometric = True, geo_info = [self._node_centroids, self._nodes.shape], directory = directory, show_labels = show_labels)
5231
5129
 
5232
5130
 
5233
5131