nettracer3d 0.6.6__py3-none-any.whl → 0.6.8__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
@@ -547,7 +547,7 @@ def remove_branches(skeleton, length):
547
547
  return image_copy
548
548
 
549
549
 
550
- def estimate_object_radii(labeled_array, gpu=False, n_jobs=None):
550
+ def estimate_object_radii(labeled_array, gpu=False, n_jobs=None, xy_scale = 1, z_scale = 1):
551
551
  """
552
552
  Estimate the radii of labeled objects in a 3D numpy array.
553
553
  Dispatches to appropriate implementation based on parameters.
@@ -579,9 +579,9 @@ def estimate_object_radii(labeled_array, gpu=False, n_jobs=None):
579
579
  gpu = False
580
580
 
581
581
  if gpu:
582
- return morphology.estimate_object_radii_gpu(labeled_array)
582
+ return morphology.estimate_object_radii_gpu(labeled_array, xy_scale = xy_scale, z_scale = z_scale)
583
583
  else:
584
- return morphology.estimate_object_radii_cpu(labeled_array, n_jobs)
584
+ return morphology.estimate_object_radii_cpu(labeled_array, n_jobs, xy_scale = xy_scale, z_scale = z_scale)
585
585
 
586
586
 
587
587
  def break_and_label_skeleton(skeleton, peaks = 1, branch_removal = 0, comp_dil = 0, max_vol = 0, directory = None, return_skele = False, nodes = None):
@@ -862,23 +862,48 @@ def _rescale(array, original_shape, xy_scale, z_scale):
862
862
  return array
863
863
 
864
864
 
865
- def remove_trunk(edges):
865
+ def remove_trunk(edges, num_iterations=1):
866
866
  """
867
- Internal method used to remove the edge trunk. Essentially removes the largest object from
868
- a binary array.
867
+ Removes the largest connected objects from a 3D binary array in-place.
868
+
869
+ Parameters:
870
+ -----------
871
+ edges : ndarray
872
+ 3D binary array containing objects to process.
873
+ Will be modified in-place.
874
+ num_iterations : int, optional
875
+ Number of largest objects to remove, default is 1.
876
+
877
+ Returns:
878
+ --------
879
+ ndarray
880
+ Reference to the modified input array.
869
881
  """
870
- # Label connected components in the binary array
871
- labeled_array = measure.label(edges)
872
-
873
- # Get unique labels and their counts
874
- unique_labels, label_counts = np.unique(labeled_array, return_counts=True)
875
-
876
- # Find the label corresponding to the largest object
877
- largest_label = unique_labels[np.argmax(label_counts[1:]) + 1]
878
-
879
- # Set indices of the largest object to 0
880
- edges[labeled_array == largest_label] = 0
881
-
882
+ # Label connected components
883
+ labeled_array, num_features = measure.label(edges, background=0, return_num=True)
884
+
885
+ # If there are fewer objects than requested iterations, adjust
886
+ iterations = min(num_iterations, num_features)
887
+
888
+ if iterations == 0 or num_features == 0:
889
+ return edges
890
+
891
+ # Count occurrences of each label
892
+ label_counts = np.bincount(labeled_array.ravel())
893
+
894
+ # Skip background (label 0)
895
+ label_counts = label_counts[1:]
896
+
897
+ # Find indices of largest objects (argsort returns ascending order, so we reverse it)
898
+ largest_indices = np.argsort(label_counts)[::-1][:iterations]
899
+
900
+ # Convert back to actual labels (add 1 because we skipped background)
901
+ largest_labels = largest_indices + 1
902
+
903
+ # Modify the input array in-place
904
+ for label in largest_labels:
905
+ edges[labeled_array == label] = 0
906
+
882
907
  return edges
883
908
 
884
909
  def hash_inners(search_region, inner_edges, GPU = True):
@@ -912,10 +937,155 @@ def dilate_2D(array, search, scaling = 1):
912
937
 
913
938
  return inv
914
939
 
940
+ def erode_2D(array, search, scaling=1):
941
+ """
942
+ Erode a 2D array using distance transform method.
943
+
944
+ Parameters:
945
+ array -- Input 2D binary array
946
+ search -- Distance within which to erode
947
+ scaling -- Scaling factor (default: 1)
948
+
949
+ Returns:
950
+ Eroded 2D array
951
+ """
952
+ # For erosion, we work directly with the foreground
953
+ # No need to invert the array
954
+
955
+ # Compute distance transform on the foreground
956
+ dt = smart_dilate.compute_distance_transform_distance(array)
957
+
958
+ # Apply scaling
959
+ dt = dt * scaling
960
+
961
+ # Threshold to keep only points that are at least 'search' distance from the boundary
962
+ eroded = dt >= search
963
+
964
+ return eroded
965
+
966
+ def dilate_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0):
967
+ """
968
+ Dilate a 3D array using distance transform method. Dt dilation produces perfect results but only works in euclidean geometry and lags in big arrays.
969
+
970
+ Parameters:
971
+ array -- Input 3D binary array
972
+ search_distance -- Distance within which to dilate
973
+ xy_scaling -- Scaling factor for x and y dimensions (default: 1.0)
974
+ z_scaling -- Scaling factor for z dimension (default: 1.0)
975
+
976
+ Returns:
977
+ Dilated 3D array
978
+ """
979
+
980
+ if array.shape[0] == 1:
981
+
982
+ return dilate_2D(array, search_distance, scaling = xy_scaling) #Use the 2d method in psueod-3d cases
983
+
984
+
985
+ # Invert the array (find background)
986
+ inv = array < 1
987
+
988
+ del array
989
+
990
+ # Determine which dimension needs resampling
991
+ if (z_scaling > xy_scaling):
992
+ # Z dimension needs to be stretched
993
+ zoom_factor = [z_scaling/xy_scaling, 1, 1] # Scale factor for [z, y, x]
994
+ rev_factor = [xy_scaling/z_scaling, 1, 1]
995
+ cardinal = xy_scaling
996
+ elif (xy_scaling > z_scaling):
997
+ # XY dimensions need to be stretched
998
+ zoom_factor = [1, xy_scaling/z_scaling, xy_scaling/z_scaling] # Scale factor for [z, y, x]
999
+ rev_factor = [1, z_scaling/xy_scaling, z_scaling/xy_scaling] # Scale factor for [z, y, x]
1000
+ cardinal = z_scaling
1001
+ else:
1002
+ # Already uniform scaling, no need to resample
1003
+ zoom_factor = None
1004
+ rev_factor = None
1005
+ cardinal = xy_scaling
1006
+
1007
+ # Resample the mask if needed
1008
+ if zoom_factor:
1009
+ inv = ndimage.zoom(inv, zoom_factor, order=0) # Use order=0 for binary masks
1010
+
1011
+ # Compute distance transform (Euclidean)
1012
+ inv = smart_dilate.compute_distance_transform_distance(inv)
1013
+
1014
+ inv = inv * cardinal
1015
+
1016
+ # Threshold the distance transform to get dilated result
1017
+ inv = inv <= search_distance
1018
+
1019
+ if rev_factor:
1020
+ inv = ndimage.zoom(inv, rev_factor, order=0) # Use order=0 for binary masks
1021
+
1022
+ return inv.astype(np.uint8)
1023
+
1024
+ def erode_3D_dt(array, search_distance, xy_scaling=1.0, z_scaling=1.0):
1025
+ """
1026
+ Erode a 3D array using distance transform method. DT erosion produces perfect results
1027
+ with Euclidean geometry, but may be slower for large arrays.
1028
+
1029
+ Parameters:
1030
+ array -- Input 3D binary array
1031
+ search_distance -- Distance within which to erode
1032
+ xy_scaling -- Scaling factor for x and y dimensions (default: 1.0)
1033
+ z_scaling -- Scaling factor for z dimension (default: 1.0)
1034
+ GPU -- Whether to use GPU acceleration if available (default: False)
1035
+
1036
+ Returns:
1037
+ Eroded 3D array
1038
+ """
1039
+
1040
+ if array.shape[0] == 1:
1041
+ # Handle 2D case
1042
+ return erode_2D(array, search_distance, scaling=xy_scaling)
1043
+
1044
+ # For erosion, we work directly with the foreground (no inversion needed)
1045
+
1046
+ # Determine which dimension needs resampling
1047
+ if (z_scaling > xy_scaling):
1048
+ # Z dimension needs to be stretched
1049
+ zoom_factor = [z_scaling/xy_scaling, 1, 1] # Scale factor for [z, y, x]
1050
+ rev_factor = [xy_scaling/z_scaling, 1, 1]
1051
+ cardinal = xy_scaling
1052
+ elif (xy_scaling > z_scaling):
1053
+ # XY dimensions need to be stretched
1054
+ zoom_factor = [1, xy_scaling/z_scaling, xy_scaling/z_scaling] # Scale factor for [z, y, x]
1055
+ rev_factor = [1, z_scaling/xy_scaling, z_scaling/xy_scaling] # Scale factor for [z, y, x]
1056
+ cardinal = z_scaling
1057
+ else:
1058
+ # Already uniform scaling, no need to resample
1059
+ zoom_factor = None
1060
+ rev_factor = None
1061
+ cardinal = xy_scaling
1062
+
1063
+ # Resample the mask if needed
1064
+ if zoom_factor:
1065
+ array = ndimage.zoom(array, zoom_factor, order=0) # Use order=0 for binary masks
1066
+
1067
+ print("Computing a distance transform for a perfect erosion...")
1068
+
1069
+ array = smart_dilate.compute_distance_transform_distance(array)
1070
+
1071
+ # Apply scaling factor
1072
+ array = array * cardinal
1073
+
1074
+ # Threshold the distance transform to get eroded result
1075
+ # For erosion, we keep only the points that are at least search_distance from the boundary
1076
+ array = array >= search_distance
1077
+
1078
+ # Resample back to original dimensions if needed
1079
+ if rev_factor:
1080
+ array = ndimage.zoom(array, rev_factor, order=0) # Use order=0 for binary masks
1081
+
1082
+ return array.astype(np.uint8)
1083
+
915
1084
 
916
1085
  def dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z):
917
1086
  """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.
918
- Arguments are an array, and the desired pixel dilation amounts in X, Y, Z."""
1087
+ 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.
1088
+ """
919
1089
 
920
1090
  if tiff_array.shape[0] == 1:
921
1091
  return dilate_2D(tiff_array, ((dilated_x - 1) / 2))
@@ -1041,118 +1211,41 @@ def dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z):
1041
1211
 
1042
1212
  return final_result
1043
1213
 
1044
-
1045
-
1046
- def dilate_3D_recursive(tiff_array, dilated_x, dilated_y, dilated_z, step_size=None):
1047
- """Recursive 3D dilation method that handles odd-numbered dilations properly.
1214
+ def dilate_3D_old(tiff_array, dilated_x=3, dilated_y=3, dilated_z=3):
1215
+ """
1216
+ Dilate a 3D array using scipy.ndimage.binary_dilation with a 3x3x3 cubic kernel.
1048
1217
 
1049
- Args:
1050
- tiff_array: Input 3D array
1051
- dilated_x, dilated_y, dilated_z: Odd numbers representing total dilation size
1052
- step_size: Size of dilation step for this iteration
1218
+ Arguments:
1219
+ tiff_array -- Input 3D binary array
1220
+ dilated_x -- Fixed at 3 for X dimension
1221
+ dilated_y -- Fixed at 3 for Y dimension
1222
+ dilated_z -- Fixed at 3 for Z dimension
1053
1223
 
1054
- Each dilation parameter represents (n-1)/2 steps outward from the object.
1224
+ Returns:
1225
+ Dilated 3D array
1055
1226
  """
1227
+
1228
+ # Handle special case for 2D arrays
1056
1229
  if tiff_array.shape[0] == 1:
1057
- return dilate_2D(tiff_array, ((dilated_x - 1) / 2))
1058
- # Calculate the smallest dimension of the array
1059
- min_dim = min(tiff_array.shape)
1230
+ # Call 2D dilation function if needed
1231
+ return dilate_2D(tiff_array, 1) # For a 3x3 kernel, radius is 1
1060
1232
 
1061
- # For small dilations relative to array size, don't use recursion
1062
- max_dilation = max(dilated_x, dilated_y, dilated_z)
1063
- if max_dilation < (0.2 * min_dim):
1064
- return dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z)
1065
- elif dilated_x == 1 and dilated_y == 1 and dilated_z == 1: #Also if there is only a single dilation don't do it
1066
- return dilate_3D(tiff_array, dilated_x, dilated_y, dilated_z)
1233
+ # Create a simple 3x3x3 cubic kernel (all ones)
1234
+ kernel = np.ones((3, 3, 3), dtype=bool)
1067
1235
 
1068
- # Initialize step_size for first call
1069
- if step_size is None:
1070
- # Start with a reasonable step size based on the largest dilation
1071
- step_size = min(5, max((max_dilation - 1) // 2 // 3, 1))
1236
+ # Perform binary dilation
1237
+ dilated_array = ndimage.binary_dilation(tiff_array.astype(bool), structure=kernel)
1072
1238
 
1073
- # Base case: if step_size is 1 or we've achieved full dilation
1074
- if step_size == 1 or (dilated_x <= 1 and dilated_y <= 1 and dilated_z <= 1):
1075
- def create_circular_kernel(diameter):
1076
- radius = diameter/2
1077
- size = radius
1078
- size = int(np.ceil(size))
1079
- y, x = np.ogrid[-radius:radius+1, -radius:radius+1]
1080
- distance = np.sqrt(x**2 + y**2)
1081
- kernel = distance <= radius
1082
- return kernel.astype(np.uint8)
1083
-
1084
- def create_ellipsoidal_kernel(long_axis, short_axis):
1085
- semi_major, semi_minor = long_axis / 2, short_axis / 2
1086
- size_y = int(np.ceil(semi_minor))
1087
- size_x = int(np.ceil(semi_major))
1088
- y, x = np.ogrid[-semi_minor:semi_minor+1, -semi_major:semi_major+1]
1089
- ellipse = (x**2 / semi_major**2) + (y**2 / semi_minor**2) <= 1
1090
- return ellipse.astype(np.uint8)
1091
-
1092
- def process_slice(z):
1093
- tiff_slice = tiff_array[z].astype(np.uint8)
1094
- dilated_slice = cv2.dilate(tiff_slice, kernel, iterations=1)
1095
- return z, dilated_slice
1096
-
1097
- def process_slice_other(y):
1098
- tiff_slice = tiff_array[:, y, :].astype(np.uint8)
1099
- dilated_slice = cv2.dilate(tiff_slice, kernel, iterations=1)
1100
- return y, dilated_slice
1101
-
1102
- # Create empty arrays for the dilated results
1103
- dilated_xy = np.zeros_like(tiff_array, dtype=np.uint8)
1104
- dilated_xz = np.zeros_like(tiff_array, dtype=np.uint8)
1105
-
1106
- # Create kernels for final dilation
1107
- kernel = create_circular_kernel(dilated_x)
1108
-
1109
- # Process XY plane
1110
- num_cores = mp.cpu_count()
1111
- with ThreadPoolExecutor(max_workers=num_cores) as executor:
1112
- futures = {executor.submit(process_slice, z): z for z in range(tiff_array.shape[0])}
1113
- for future in as_completed(futures):
1114
- z, dilated_slice = future.result()
1115
- dilated_xy[z] = dilated_slice
1116
-
1117
- # Process XZ plane
1118
- kernel = create_ellipsoidal_kernel(dilated_x, dilated_z)
1119
- with ThreadPoolExecutor(max_workers=num_cores) as executor:
1120
- futures = {executor.submit(process_slice_other, y): y for y in range(tiff_array.shape[1])}
1121
- for future in as_completed(futures):
1122
- y, dilated_slice = future.result()
1123
- dilated_xz[:, y, :] = dilated_slice
1239
+ return dilated_array.astype(np.uint8)
1124
1240
 
1125
- return dilated_xy | dilated_xz
1126
-
1127
- # Calculate current iteration's dilation sizes (must be odd numbers)
1128
- current_x_steps = min((dilated_x - 1) // 2, step_size)
1129
- current_y_steps = min((dilated_y - 1) // 2, step_size)
1130
- current_z_steps = min((dilated_z - 1) // 2, step_size)
1131
-
1132
- current_x_dilation = current_x_steps * 2 + 1
1133
- current_y_dilation = current_y_steps * 2 + 1
1134
- current_z_dilation = current_z_steps * 2 + 1
1135
-
1136
- # Perform current iteration's dilation
1137
- current_result = dilate_3D_recursive(tiff_array, current_x_dilation, current_y_dilation, current_z_dilation, step_size=1)
1138
-
1139
- # Calculate remaining dilation needed
1140
- # For X and Y, use the circle radius (current_x_steps)
1141
- # For Z, use the ellipse short axis (current_z_steps)
1142
- remaining_x = max(1, dilated_x - (current_x_steps * 2))
1143
- remaining_y = max(1, dilated_y - (current_y_steps * 2))
1144
- remaining_z = max(1, dilated_z - (current_z_steps * 2))
1145
-
1146
- # If no more dilation needed, return current result
1147
- if remaining_x == 1 and remaining_y == 1 and remaining_z == 1:
1148
- return current_result
1149
-
1150
- # Recursive call with remaining dilation and decreased step size
1151
- return dilate_3D_recursive(current_result, remaining_x, remaining_y, remaining_z, step_size=max(1, step_size - 1))
1152
1241
 
1153
1242
  def erode_3D(tiff_array, eroded_x, eroded_y, eroded_z):
1154
1243
  """Internal method to erode an array in 3D. Erosion this way is much faster than using a distance transform although the latter is theoretically more accurate.
1155
1244
  Arguments are an array, and the desired pixel erosion amounts in X, Y, Z."""
1245
+
1246
+ if tiff_array.shape[0] == 1:
1247
+ return erode_2D(tiff_array, ((eroded_x - 1) / 2))
1248
+
1156
1249
  def create_circular_kernel(diameter):
1157
1250
  """Create a 2D circular kernel with a given radius.
1158
1251
  Parameters:
@@ -1247,43 +1340,6 @@ def erode_3D(tiff_array, eroded_x, eroded_y, eroded_z):
1247
1340
  return final_result
1248
1341
 
1249
1342
 
1250
- def dilate_3D_old(tiff_array, dilated_x, dilated_y, dilated_z):
1251
- """(For cubey dilation only). Internal method to dilate an array in 3D.
1252
- Arguments are an array, and the desired pixel dilation amounts in X, Y, Z."""
1253
-
1254
- # Create empty arrays to store the dilated results for the XY and XZ planes
1255
- dilated_xy = np.zeros_like(tiff_array, dtype=np.uint8)
1256
- dilated_xz = np.zeros_like(tiff_array, dtype=np.uint8)
1257
-
1258
- # Perform 2D dilation in the XY plane
1259
- for z in range(tiff_array.shape[0]):
1260
- kernel_x = int(dilated_x)
1261
- kernel_y = int(dilated_y)
1262
- kernel = np.ones((kernel_y, kernel_x), dtype=np.uint8)
1263
-
1264
- # Convert the slice to the appropriate data type
1265
- tiff_slice = tiff_array[z].astype(np.uint8)
1266
-
1267
- dilated_slice = cv2.dilate(tiff_slice, kernel, iterations=1)
1268
- dilated_xy[z] = dilated_slice
1269
-
1270
- # Perform 2D dilation in the XZ plane
1271
- for y in range(tiff_array.shape[1]):
1272
- kernel_x = int(dilated_x)
1273
- kernel_z = int(dilated_z)
1274
- kernel = np.ones((kernel_z, kernel_x), dtype=np.uint8)
1275
-
1276
- # Convert the slice to the appropriate data type
1277
- tiff_slice = tiff_array[:, y, :].astype(np.uint8)
1278
-
1279
- dilated_slice = cv2.dilate(tiff_slice, kernel, iterations=1)
1280
- dilated_xz[:, y, :] = dilated_slice
1281
-
1282
- # Overlay the results
1283
- final_result = dilated_xy | dilated_xz
1284
-
1285
- return final_result
1286
-
1287
1343
  def dilation_length_to_pixels(xy_scaling, z_scaling, micronx, micronz):
1288
1344
  """Internal method to find XY and Z dilation parameters based on voxel micron scaling"""
1289
1345
  dilate_xy = 2 * int(round(micronx/xy_scaling))
@@ -1483,7 +1539,7 @@ def binarize(arrayimage, directory = None):
1483
1539
 
1484
1540
  return arrayimage
1485
1541
 
1486
- def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast_dil = False, recursive = False):
1542
+ def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast_dil = False, recursive = False, dilate_xy = None, dilate_z = None):
1487
1543
  """
1488
1544
  Can be used to dilate a binary image in 3D. Dilated output will be saved to the active directory if none is specified. Note that dilation is done with single-instance kernels and not iterations, and therefore
1489
1545
  objects will lose their shape somewhat and become cube-ish if the 'amount' param is ever significantly larger than the objects in quesiton.
@@ -1503,20 +1559,16 @@ def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast
1503
1559
  else:
1504
1560
  image = None
1505
1561
 
1506
- dilate_xy, dilate_z = dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
1562
+ if not dilate_xy:
1563
+ dilate_xy, dilate_z = dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
1507
1564
 
1508
1565
  if len(np.unique(arrayimage)) > 2: #binarize
1509
1566
  arrayimage = binarize(arrayimage)
1510
1567
 
1511
- if not fast_dil and not recursive:
1512
- arrayimage = (dilate_3D(arrayimage, dilate_xy, dilate_xy, dilate_z)) * 255
1513
- if np.max(arrayimage) == 1:
1514
- arrayimage = arrayimage * 255
1515
- elif not recursive:
1516
- arrayimage = (dilate_3D_old(arrayimage, dilate_xy, dilate_xy, dilate_z)) * 255
1568
+ if fast_dil:
1569
+ arrayimage = (dilate_3D(arrayimage, dilate_xy, dilate_xy, dilate_z))
1517
1570
  else:
1518
- arrayimage = (dilate_3D_recursive(arrayimage, dilate_xy, dilate_xy, dilate_z)) * 255
1519
-
1571
+ arrayimage = (dilate_3D_dt(arrayimage, amount, xy_scale, z_scale))
1520
1572
 
1521
1573
 
1522
1574
  if type(image) == str:
@@ -1530,15 +1582,16 @@ def dilate(arrayimage, amount, xy_scale = 1, z_scale = 1, directory = None, fast
1530
1582
 
1531
1583
  return arrayimage
1532
1584
 
1533
- def erode(arrayimage, amount, xy_scale = 1, z_scale = 1):
1585
+ def erode(arrayimage, amount, xy_scale = 1, z_scale = 1, mode = 0):
1534
1586
  if len(np.unique(arrayimage)) > 2: #binarize
1535
1587
  arrayimage = binarize(arrayimage)
1536
1588
  erode_xy, erode_z = dilation_length_to_pixels(xy_scale, z_scale, amount, amount)
1537
1589
 
1538
- if len(np.unique(arrayimage)) > 2: #binarize
1539
- arrayimage = binarize(arrayimage)
1590
+ if mode == 0:
1591
+ arrayimage = (erode_3D(arrayimage, erode_xy, erode_xy, erode_z)) * 255
1592
+ else:
1593
+ arrayimage = erode_3D_dt(arrayimage, amount, xy_scaling=xy_scale, z_scaling=z_scale)
1540
1594
 
1541
- arrayimage = (erode_3D(arrayimage, erode_xy, erode_xy, erode_z)) * 255
1542
1595
  if np.max(arrayimage) == 1:
1543
1596
  arrayimage = arrayimage * 255
1544
1597
 
@@ -1693,7 +1746,7 @@ def fix_branches(array, G, communities, fix_val = None):
1693
1746
 
1694
1747
 
1695
1748
 
1696
- def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = 0, directory = None, return_skele = False, order = 0):
1749
+ def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol = 0, down_factor = 0, directory = None, return_skele = False, order = 0, fastdil = True):
1697
1750
  """
1698
1751
  Can be used to label vertices (where multiple branches connect) 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.
1699
1752
  Note that this can be used in tandem with an edge segmentation to create an image containing 'pseudo-nodes', meaning we can make a network out of just a single edge file.
@@ -1759,19 +1812,19 @@ def label_vertices(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
1759
1812
  if peaks > 0:
1760
1813
  image_copy = filter_size_by_peaks(image_copy, peaks)
1761
1814
  if comp_dil > 0:
1762
- image_copy = dilate(image_copy, comp_dil)
1815
+ image_copy = dilate(image_copy, comp_dil, fast_dil = fastdil)
1763
1816
 
1764
1817
  labeled_image, num_labels = label_objects(image_copy)
1765
1818
  elif max_vol > 0:
1766
1819
  image_copy = filter_size_by_vol(image_copy, max_vol)
1767
1820
  if comp_dil > 0:
1768
- image_copy = dilate(image_copy, comp_dil)
1821
+ image_copy = dilate(image_copy, comp_dil, fast_dil = fastdil)
1769
1822
 
1770
1823
  labeled_image, num_labels = label_objects(image_copy)
1771
1824
  else:
1772
1825
 
1773
1826
  if comp_dil > 0:
1774
- image_copy = dilate(image_copy, comp_dil)
1827
+ image_copy = dilate(image_copy, comp_dil, fast_dil = fastdil)
1775
1828
  labeled_image, num_labels = label_objects(image_copy)
1776
1829
 
1777
1830
  #if down_factor > 0:
@@ -3010,7 +3063,7 @@ class Network_3D:
3010
3063
  print(f"Assembling Network_3D object from files stored in directory: {directory}")
3011
3064
  self.load_nodes(directory, node_path)
3012
3065
  self.load_edges(directory, edge_path)
3013
- self.load_search_region(directory, search_region_path)
3066
+ #self.load_search_region(directory, search_region_path)
3014
3067
  self.load_network(directory, network_path)
3015
3068
  self.load_node_centroids(directory, node_centroids_path)
3016
3069
  self.load_node_identities(directory, node_identities_path)
@@ -3068,7 +3121,7 @@ class Network_3D:
3068
3121
 
3069
3122
  self._edge_centroids = edge_centroids
3070
3123
 
3071
- def calculate_search_region(self, search_region_size, GPU = True, fast_dil = False, GPU_downsample = None):
3124
+ def calculate_search_region(self, search_region_size, GPU = True, fast_dil = True, GPU_downsample = None):
3072
3125
 
3073
3126
  """
3074
3127
  Method to obtain the search region that will be used to assign connectivity between nodes. May be skipped if nodes do not want to search and only want to look for their
@@ -3087,7 +3140,7 @@ class Network_3D:
3087
3140
 
3088
3141
  if search_region_size != 0:
3089
3142
 
3090
- self._search_region = smart_dilate.smart_dilate(self._nodes, dilate_xy, dilate_z, GPU = GPU, fast_dil = fast_dil, predownsample = GPU_downsample) #Call the smart dilate function which essentially is a fast way to enlarge nodes into a 'search region' while keeping their unique IDs.
3143
+ self._search_region = smart_dilate.smart_dilate(self._nodes, dilate_xy, dilate_z, GPU = GPU, fast_dil = fast_dil, predownsample = GPU_downsample, use_dt_dil_amount = search_region_size) #Call the smart dilate function which essentially is a fast way to enlarge nodes into a 'search region' while keeping their unique IDs.
3091
3144
 
3092
3145
  else:
3093
3146
 
@@ -3121,14 +3174,15 @@ class Network_3D:
3121
3174
  if skeletonized:
3122
3175
  binary_edges = skeletonize(binary_edges)
3123
3176
 
3124
- if search is not None and hasattr(self, '_nodes') and self._nodes is not None:
3177
+
3178
+
3179
+ if search is not None and hasattr(self, '_nodes') and self._nodes is not None and self._search_region is None:
3125
3180
  search_region = binarize(self._nodes)
3126
3181
  dilate_xy, dilate_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, search, search)
3127
- if not fast_dil:
3182
+ if fast_dil:
3128
3183
  search_region = dilate_3D(search_region, dilate_xy, dilate_xy, dilate_z)
3129
3184
  else:
3130
- search_region = dilate_3D_old(search_region, dilate_xy, dilate_xy, dilate_z)
3131
-
3185
+ search_region = dilate_3D_dt(search_region, diledge, self._xy_scale, self._z_scale)
3132
3186
  else:
3133
3187
  search_region = binarize(self._search_region)
3134
3188
 
@@ -3138,19 +3192,20 @@ class Network_3D:
3138
3192
  del binary_edges
3139
3193
 
3140
3194
  if remove_edgetrunk > 0:
3141
- for i in range(remove_edgetrunk):
3142
- print(f"Snipping trunk {i + 1}...")
3143
- outer_edges = remove_trunk(outer_edges)
3195
+ print(f"Snipping trunks...")
3196
+ outer_edges = remove_trunk(outer_edges, remove_edgetrunk)
3144
3197
 
3145
3198
  if diledge is not None:
3146
3199
  dilate_xy, dilate_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, diledge, diledge)
3147
- if not fast_dil and dilate_xy > 3 and dilate_z > 3:
3200
+
3201
+ if dilate_xy <= 3 and dilate_z <= 3:
3202
+ outer_edges = dilate_3D_old(outer_edges, dilate_xy, dilate_xy, dilate_z)
3203
+ elif fast_dil:
3148
3204
  outer_edges = dilate_3D(outer_edges, dilate_xy, dilate_xy, dilate_z)
3149
3205
  else:
3150
- outer_edges = dilate_3D_old(outer_edges, dilate_xy, dilate_xy, dilate_z)
3151
-
3206
+ outer_edges = dilate_3D_dt(outer_edges, diledge, self._xy_scale, self._z_scale)
3152
3207
  else:
3153
- outer_edges = dilate_3D_old(outer_edges, 3, 3, 3)
3208
+ outer_edges = dilate_3D_old(outer_edges)
3154
3209
 
3155
3210
  labelled_edges, num_edge = label_objects(outer_edges)
3156
3211
 
@@ -3282,7 +3337,7 @@ class Network_3D:
3282
3337
  self._network_lists = network_analysis.read_excel_to_lists(df)
3283
3338
  self._network, net_weights = network_analysis.weighted_network(df)
3284
3339
 
3285
- def calculate_all(self, nodes, edges, xy_scale = 1, z_scale = 1, down_factor = None, search = None, diledge = None, inners = True, hash_inners = True, remove_trunk = 0, ignore_search_region = False, other_nodes = None, label_nodes = True, directory = None, GPU = True, fast_dil = False, skeletonize = False, GPU_downsample = None):
3340
+ def calculate_all(self, nodes, edges, xy_scale = 1, z_scale = 1, down_factor = None, search = None, diledge = None, inners = True, hash_inners = True, remove_trunk = 0, ignore_search_region = False, other_nodes = None, label_nodes = True, directory = None, GPU = True, fast_dil = True, skeletonize = False, GPU_downsample = None):
3286
3341
  """
3287
3342
  Method to calculate and save to mem all properties of a Network_3D object. In general, after initializing a Network_3D object, this method should be called on the node and edge masks that will be used to calculate the network.
3288
3343
  :param nodes: (Mandatory; String or ndarray). Filepath to segmented nodes mask or a numpy array containing the same.
@@ -3304,7 +3359,7 @@ class Network_3D:
3304
3359
  :param label_nodes: (Optional - Val = True; boolean). If True, all discrete objects in the node param (and all those contained in the optional other_nodes param) will be assigned a label. If files a prelabelled, set this to False to avoid labelling.
3305
3360
  :param directory: (Optional - Val = None; string). Path to a directory to save to hard mem all Network_3D properties. If not set, these values will be saved to the active directory.
3306
3361
  :param GPU: (Optional - Val = True; boolean). Will use GPU if avaialble for calculating the search_region step (including necessary downsampling for GPU RAM). Set to False to use CPU with no downsample. Note this only affects the search_region step.
3307
- :param fast_dil: (Optional - Val = False, boolean) - A boolean that when True will utilize faster cube dilation but when false will use slower spheroid dilation.
3362
+ :param fast_dil: (Optional - Val = False, boolean) - A boolean that when True will utilize faster psuedo3d kernel dilation but when false will use slower dt-based dilation.
3308
3363
  :param skeletonize: (Optional - Val = False, boolean) - A boolean of whether to skeletonize the edges when using them.
3309
3364
  """
3310
3365
 
@@ -4026,48 +4081,10 @@ class Network_3D:
4026
4081
  return isolated nodes, isolated edges, and isolated network in that order). IF gen_images == False (Will return just the network).
4027
4082
  """
4028
4083
 
4029
- if gen_images:
4030
-
4031
- if not hasattr(self, '_edges') or self._edges is None:
4032
-
4033
- connected_component, isonodes, _ = community_extractor.isolate_connected_component(self._nodes, self._network, key=key, directory = directory)
4034
-
4035
- nodea = []
4036
- nodeb = []
4037
- edgec = []
4038
- nodesa = self._network_lists[0]
4039
- nodesb = self._network_lists[1]
4040
- edgesc = self._network_lists[2]
4041
- for i in range(len(nodesa)):
4042
- if (nodesa[i], nodesb[i]) in connected_component:
4043
- nodea.append(nodesa[i])
4044
- nodeb.append(nodesb[i])
4045
- edgec.append(edgesc[i])
4046
- network_lists = [nodea, nodeb, edgec]
4047
- network, weights = network_analysis.weighted_network(network_lists)
4084
+ #Removed depricated gen_images functions
4048
4085
 
4049
- return isonodes, network
4050
-
4051
- else:
4052
- if full_edges is not None:
4053
- connected_component, isonodes, isoedges, searchers = community_extractor.isolate_connected_component(self._nodes, self._network, key=key, edge_file = self._edges, search_region = self.search_region, netlists = self._network_lists, directory = directory)
4054
-
4055
- else:
4056
- connected_component, isonodes, isoedges, searchers = community_extractor.isolate_connected_component(self._nodes, self._network, key=key, edge_file = self._edges, netlists = self._network_lists, directory = directory)
4057
-
4058
- df = create_and_save_dataframe(connected_component)
4059
- network_lists = network_analysis.read_excel_to_lists(df)
4060
- network, net_weights = network_analysis.weighted_network(df)
4061
-
4062
- if full_edges is not None:
4063
- full_edges = tifffile.imread(full_edges)
4064
- community_extractor.isolate_full_edges(searchers, full_edges, directory = directory)
4065
-
4066
- return isonodes, isoedges, network
4067
-
4068
- else:
4069
- G = community_extractor._isolate_connected(self._network, key = key)
4070
- return G
4086
+ G = community_extractor._isolate_connected(self._network, key = key)
4087
+ return G
4071
4088
 
4072
4089
 
4073
4090
  def isolate_mothers(self, directory = None, down_factor = 1, louvain = True, ret_nodes = False, called = False):
@@ -4325,7 +4342,7 @@ class Network_3D:
4325
4342
  return stats
4326
4343
 
4327
4344
 
4328
- def neighborhood_identities(self, root, directory = None, mode = 0, search = 0):
4345
+ def neighborhood_identities(self, root, directory = None, mode = 0, search = 0, fastdil = False):
4329
4346
 
4330
4347
 
4331
4348
 
@@ -4363,7 +4380,7 @@ class Network_3D:
4363
4380
 
4364
4381
 
4365
4382
  elif mode == 1: #Search neighborhoods morphologically, obtain densities
4366
- neighborhood_dict, total_dict, densities = morphology.search_neighbor_ids(self._nodes, targets, node_identities, neighborhood_dict, total_dict, search, self._xy_scale, self._z_scale, root)
4383
+ neighborhood_dict, total_dict, densities = morphology.search_neighbor_ids(self._nodes, targets, node_identities, neighborhood_dict, total_dict, search, self._xy_scale, self._z_scale, root, fastdil = fastdil)
4367
4384
  title1 = f'Volumetric Neighborhood Distribution of Nodes in image that are {search} from nodes: {root}'
4368
4385
  title2 = f'Density Distribution of Nodes in image that are {search} from Nodes {root} as a proportion of total node volume of that ID'
4369
4386
 
@@ -4410,19 +4427,23 @@ class Network_3D:
4410
4427
 
4411
4428
 
4412
4429
 
4413
- def interactions(self, search = 0, cores = 0, resize = None, save = False, skele = False):
4430
+ def interactions(self, search = 0, cores = 0, resize = None, save = False, skele = False, fastdil = False):
4431
+
4432
+ return morphology.quantify_edge_node(self._nodes, self._edges, search = search, xy_scale = self._xy_scale, z_scale = self._z_scale, cores = cores, resize = resize, save = save, skele = skele, fastdil = fastdil)
4414
4433
 
4415
- return morphology.quantify_edge_node(self._nodes, self._edges, search = search, xy_scale = self._xy_scale, z_scale = self._z_scale, cores = cores, resize = resize, save = save, skele = skele)
4416
4434
 
4417
4435
 
4436
+ def morph_proximity(self, search = 0, targets = None, fastdil = False):
4418
4437
 
4419
- def morph_proximity(self, search = 0, targets = None):
4438
+ if type(search) == list:
4439
+ search_x, search_z = search #Suppose we just want to directly pass these params
4440
+ else:
4441
+ search_x, search_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, search, search)
4420
4442
 
4421
- search_x, search_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, search, search)
4422
4443
 
4423
4444
  num_nodes = np.max(self._nodes)
4424
4445
 
4425
- my_dict = proximity.create_node_dictionary(self._nodes, num_nodes, search_x, search_z, targets = targets)
4446
+ my_dict = proximity.create_node_dictionary(self._nodes, num_nodes, search_x, search_z, targets = targets, fastdil = fastdil, xy_scale = self._xy_scale, z_scale = self._z_scale, search = search)
4426
4447
 
4427
4448
  my_dict = proximity.find_shared_value_pairs(my_dict)
4428
4449
 
@@ -4495,6 +4516,8 @@ class Network_3D:
4495
4516
 
4496
4517
  self._network_lists = network_analysis.read_excel_to_lists(network)
4497
4518
 
4519
+ #self._network is a networkx graph that stores the connections
4520
+
4498
4521
  self.remove_edge_weights()
4499
4522
 
4500
4523
  return array