nettracer3d 0.8.3__py3-none-any.whl → 0.8.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of nettracer3d might be problematic. Click here for more details.
- nettracer3d/community_extractor.py +0 -1
- nettracer3d/excelotron.py +21 -2
- nettracer3d/nettracer.py +506 -79
- nettracer3d/nettracer_gui.py +605 -139
- nettracer3d/network_analysis.py +90 -29
- nettracer3d/node_draw.py +6 -2
- nettracer3d/proximity.py +50 -101
- nettracer3d/smart_dilate.py +44 -10
- {nettracer3d-0.8.3.dist-info → nettracer3d-0.8.4.dist-info}/METADATA +3 -3
- nettracer3d-0.8.4.dist-info/RECORD +24 -0
- nettracer3d-0.8.3.dist-info/RECORD +0 -24
- {nettracer3d-0.8.3.dist-info → nettracer3d-0.8.4.dist-info}/WHEEL +0 -0
- {nettracer3d-0.8.3.dist-info → nettracer3d-0.8.4.dist-info}/entry_points.txt +0 -0
- {nettracer3d-0.8.3.dist-info → nettracer3d-0.8.4.dist-info}/licenses/LICENSE +0 -0
- {nettracer3d-0.8.3.dist-info → nettracer3d-0.8.4.dist-info}/top_level.txt +0 -0
nettracer3d/nettracer.py
CHANGED
|
@@ -31,6 +31,7 @@ from . import community_extractor
|
|
|
31
31
|
from . import network_analysis
|
|
32
32
|
from . import morphology
|
|
33
33
|
from . import proximity
|
|
34
|
+
from skimage.segmentation import watershed as water
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
#These next several methods relate to searching with 3D objects by dilating each one in a subarray around their neighborhood although I don't explicitly use this anywhere... can call them deprecated although I may want to use them later again so I have them still written out here.
|
|
@@ -214,7 +215,6 @@ def establish_connections_parallel(edge_labels, num_edge, node_labels):
|
|
|
214
215
|
|
|
215
216
|
edge_connections.append(node_labels[index])
|
|
216
217
|
|
|
217
|
-
#the set() wrapper removes duplicates from the same sublist
|
|
218
218
|
my_connections = list(set(edge_connections))
|
|
219
219
|
|
|
220
220
|
|
|
@@ -282,8 +282,36 @@ def extract_pairwise_connections(connections):
|
|
|
282
282
|
|
|
283
283
|
|
|
284
284
|
#Saving outputs
|
|
285
|
+
def create_and_save_dataframe(pairwise_connections, excel_filename=None):
|
|
286
|
+
"""Internal method used to convert lists of discrete connections into an excel output"""
|
|
287
|
+
|
|
288
|
+
# Create DataFrame directly from the connections with 3 columns
|
|
289
|
+
df = pd.DataFrame(pairwise_connections, columns=['Node A', 'Node B', 'Edge C'])
|
|
290
|
+
|
|
291
|
+
if excel_filename is not None:
|
|
292
|
+
# Remove file extension if present to use as base path
|
|
293
|
+
base_path = excel_filename.rsplit('.', 1)[0]
|
|
294
|
+
|
|
295
|
+
# First try to save as CSV
|
|
296
|
+
try:
|
|
297
|
+
csv_path = f"{base_path}.csv"
|
|
298
|
+
df.to_csv(csv_path, index=False)
|
|
299
|
+
print(f"Network file saved to {csv_path}")
|
|
300
|
+
return
|
|
301
|
+
except Exception as e:
|
|
302
|
+
print(f"Could not save as CSV: {str(e)}")
|
|
303
|
+
|
|
304
|
+
# If CSV fails, try to save as Excel
|
|
305
|
+
try:
|
|
306
|
+
xlsx_path = f"{base_path}.xlsx"
|
|
307
|
+
df.to_excel(xlsx_path, index=False)
|
|
308
|
+
print(f"Network file saved to {xlsx_path}")
|
|
309
|
+
except Exception as e:
|
|
310
|
+
print(f"Unable to write network file to disk... please make sure that {base_path}.xlsx is being saved to a valid directory and try again")
|
|
311
|
+
else:
|
|
312
|
+
return df
|
|
285
313
|
|
|
286
|
-
def
|
|
314
|
+
def create_and_save_dataframe_old(pairwise_connections, excel_filename = None):
|
|
287
315
|
"""Internal method used to convert lists of discrete connections into an excel output"""
|
|
288
316
|
# Determine the length of the input list
|
|
289
317
|
length = len(pairwise_connections)
|
|
@@ -636,28 +664,25 @@ def threshold(arr, proportion, custom_rad = None):
|
|
|
636
664
|
def find_closest_index(target: float, num_list: list[float]) -> int:
|
|
637
665
|
return min(range(len(num_list)), key=lambda i: abs(num_list[i] - target))
|
|
638
666
|
|
|
639
|
-
# Step 1: Flatten the array
|
|
640
|
-
flattened = arr.flatten()
|
|
641
667
|
|
|
642
|
-
|
|
643
|
-
non_zero_values = list(set(flattened[flattened > 0]))
|
|
668
|
+
if custom_rad is not None:
|
|
644
669
|
|
|
645
|
-
|
|
646
|
-
sorted_values = np.sort(non_zero_values)
|
|
670
|
+
threshold_value = custom_rad
|
|
647
671
|
|
|
648
|
-
|
|
672
|
+
else:
|
|
673
|
+
# Step 1: Flatten the array
|
|
674
|
+
flattened = arr.flatten()
|
|
675
|
+
|
|
676
|
+
# Step 2: Filter out the zero values
|
|
677
|
+
non_zero_values = list(set(flattened[flattened > 0]))
|
|
649
678
|
|
|
650
|
-
|
|
679
|
+
# Step 3: Sort the remaining values
|
|
680
|
+
sorted_values = np.sort(non_zero_values)
|
|
651
681
|
|
|
652
682
|
threshold_index = int(len(sorted_values) * proportion)
|
|
653
683
|
threshold_value = sorted_values[threshold_index]
|
|
684
|
+
print(f"Thresholding as if smallest_radius as assigned {threshold_value}")
|
|
654
685
|
|
|
655
|
-
else:
|
|
656
|
-
|
|
657
|
-
targ = int(find_closest_index(custom_rad, sorted_values) - (0.02 * len(sorted_values)))
|
|
658
|
-
|
|
659
|
-
threshold_value = sorted_values[targ]
|
|
660
|
-
print(f"Suggested proportion for rad {custom_rad} -> {targ/len(sorted_values)}")
|
|
661
686
|
|
|
662
687
|
mask = arr > threshold_value
|
|
663
688
|
|
|
@@ -1574,6 +1599,187 @@ def directory_info(directory = None):
|
|
|
1574
1599
|
return items
|
|
1575
1600
|
|
|
1576
1601
|
|
|
1602
|
+
# Ripley's K Helpers:
|
|
1603
|
+
|
|
1604
|
+
def mirror_points_for_edge_correction(points_array, bounds, max_r, dim=3):
|
|
1605
|
+
"""
|
|
1606
|
+
Mirror points near boundaries to handle edge effects in Ripley's K analysis.
|
|
1607
|
+
Works with actual coordinate positions, not spatial grid placement.
|
|
1608
|
+
|
|
1609
|
+
Parameters:
|
|
1610
|
+
points_array: numpy array of shape (n, 3) with [z, y, x] coordinates (already scaled)
|
|
1611
|
+
bounds: tuple of (min_coords, max_coords) where each is array - can be 2D or 3D
|
|
1612
|
+
max_r: maximum search radius (determines mirroring distance)
|
|
1613
|
+
dim: dimension (2 or 3) - affects which coordinates are used
|
|
1614
|
+
|
|
1615
|
+
Returns:
|
|
1616
|
+
numpy array with original points plus mirrored points
|
|
1617
|
+
"""
|
|
1618
|
+
min_coords, max_coords = bounds
|
|
1619
|
+
|
|
1620
|
+
# Ensure bounds are numpy arrays and handle dimension mismatch
|
|
1621
|
+
min_coords = np.array(min_coords)
|
|
1622
|
+
max_coords = np.array(max_coords)
|
|
1623
|
+
|
|
1624
|
+
# Handle case where bounds might be 2D but points are 3D
|
|
1625
|
+
if len(min_coords) == 2 and points_array.shape[1] == 3:
|
|
1626
|
+
# Extend 2D bounds to 3D by adding z=0 dimension at the front
|
|
1627
|
+
min_coords = np.array([0, min_coords[0], min_coords[1]]) # [0, min_x, min_y] -> [min_z, min_y, min_x]
|
|
1628
|
+
max_coords = np.array([0, max_coords[0], max_coords[1]]) # [0, max_x, max_y] -> [max_z, max_y, max_x]
|
|
1629
|
+
elif len(min_coords) == 3 and points_array.shape[1] == 3:
|
|
1630
|
+
# Already 3D, but ensure it's in [z,y,x] format (your bounds are [x,y,z] and get flipped)
|
|
1631
|
+
pass # Should already be handled by the flip in your bounds calculation
|
|
1632
|
+
|
|
1633
|
+
# Start with original points
|
|
1634
|
+
all_points = points_array.copy()
|
|
1635
|
+
|
|
1636
|
+
if dim == 2:
|
|
1637
|
+
# For 2D: work with y, x coordinates (indices 1, 2), z should be 0
|
|
1638
|
+
active_dims = [1, 2] # y, x
|
|
1639
|
+
# 8 potential mirror regions for 2D (excluding center)
|
|
1640
|
+
mirror_combinations = [
|
|
1641
|
+
[0, -1], [0, 1], # left, right (y direction)
|
|
1642
|
+
[-1, 0], [1, 0], # bottom, top (x direction)
|
|
1643
|
+
[-1, -1], [-1, 1], # corners
|
|
1644
|
+
[1, -1], [1, 1]
|
|
1645
|
+
]
|
|
1646
|
+
else:
|
|
1647
|
+
# For 3D: work with z, y, x coordinates (indices 0, 1, 2)
|
|
1648
|
+
active_dims = [0, 1, 2] # z, y, x
|
|
1649
|
+
# 26 potential mirror regions for 3D (3^3 - 1, excluding center)
|
|
1650
|
+
mirror_combinations = []
|
|
1651
|
+
for dz in [-1, 0, 1]:
|
|
1652
|
+
for dy in [-1, 0, 1]:
|
|
1653
|
+
for dx in [-1, 0, 1]:
|
|
1654
|
+
if not (dz == 0 and dy == 0 and dx == 0): # exclude center
|
|
1655
|
+
mirror_combinations.append([dz, dy, dx])
|
|
1656
|
+
|
|
1657
|
+
# Process each potential mirror region
|
|
1658
|
+
for mirror_dir in mirror_combinations:
|
|
1659
|
+
# Find points that need this specific mirroring
|
|
1660
|
+
needs_mirror = np.ones(len(points_array), dtype=bool)
|
|
1661
|
+
|
|
1662
|
+
# Check each active dimension
|
|
1663
|
+
for i, dim_idx in enumerate(active_dims):
|
|
1664
|
+
direction = mirror_dir[i] if dim == 3 else mirror_dir[i]
|
|
1665
|
+
|
|
1666
|
+
# Safety check: make sure we have bounds for this dimension
|
|
1667
|
+
if dim_idx >= len(min_coords) or dim_idx >= len(max_coords):
|
|
1668
|
+
needs_mirror = np.zeros(len(points_array), dtype=bool) # Skip this mirror if bounds insufficient
|
|
1669
|
+
break
|
|
1670
|
+
|
|
1671
|
+
if direction == -1: # Points near minimum boundary
|
|
1672
|
+
# Distance from point to min boundary < max_r
|
|
1673
|
+
needs_mirror &= (points_array[:, dim_idx] - min_coords[dim_idx]) < max_r
|
|
1674
|
+
elif direction == 1: # Points near maximum boundary
|
|
1675
|
+
# Distance from point to max boundary < max_r
|
|
1676
|
+
needs_mirror &= (max_coords[dim_idx] - points_array[:, dim_idx]) < max_r
|
|
1677
|
+
# direction == 0 means no constraint for this dimension
|
|
1678
|
+
|
|
1679
|
+
# Create mirrored points if any qualify
|
|
1680
|
+
if np.any(needs_mirror):
|
|
1681
|
+
mirrored_points = points_array[needs_mirror].copy()
|
|
1682
|
+
|
|
1683
|
+
# Apply mirroring transformation for each active dimension
|
|
1684
|
+
for i, dim_idx in enumerate(active_dims):
|
|
1685
|
+
direction = mirror_dir[i] if dim == 3 else mirror_dir[i]
|
|
1686
|
+
|
|
1687
|
+
# Safety check again
|
|
1688
|
+
if dim_idx >= len(min_coords) or dim_idx >= len(max_coords):
|
|
1689
|
+
continue
|
|
1690
|
+
|
|
1691
|
+
if direction == -1: # Mirror across minimum boundary
|
|
1692
|
+
# Reflection formula: new_coord = 2 * boundary - old_coord
|
|
1693
|
+
mirrored_points[:, dim_idx] = 2 * min_coords[dim_idx] - mirrored_points[:, dim_idx]
|
|
1694
|
+
elif direction == 1: # Mirror across maximum boundary
|
|
1695
|
+
# Reflection formula: new_coord = 2 * boundary - old_coord
|
|
1696
|
+
mirrored_points[:, dim_idx] = 2 * max_coords[dim_idx] - mirrored_points[:, dim_idx]
|
|
1697
|
+
|
|
1698
|
+
# Add mirrored points to collection
|
|
1699
|
+
all_points = np.vstack([all_points, mirrored_points])
|
|
1700
|
+
|
|
1701
|
+
return all_points
|
|
1702
|
+
def get_max_r_from_proportion(bounds, proportion):
|
|
1703
|
+
"""
|
|
1704
|
+
Calculate max_r based on bounds and proportion, matching your generate_r_values logic.
|
|
1705
|
+
|
|
1706
|
+
Parameters:
|
|
1707
|
+
bounds: tuple of (min_coords, max_coords)
|
|
1708
|
+
proportion: maximum proportion of study area extent
|
|
1709
|
+
|
|
1710
|
+
Returns:
|
|
1711
|
+
max_r value
|
|
1712
|
+
"""
|
|
1713
|
+
min_coords, max_coords = bounds
|
|
1714
|
+
min_coords = np.array(min_coords)
|
|
1715
|
+
max_coords = np.array(max_coords)
|
|
1716
|
+
|
|
1717
|
+
# Calculate dimensions
|
|
1718
|
+
dimensions = max_coords - min_coords
|
|
1719
|
+
|
|
1720
|
+
# Remove placeholder dimensions (where dimension = 1, typically for 2D z-dimension)
|
|
1721
|
+
# But ensure we don't end up with an empty array
|
|
1722
|
+
filtered_dimensions = dimensions[dimensions != 1]
|
|
1723
|
+
if len(filtered_dimensions) == 0:
|
|
1724
|
+
# If all dimensions were 1 (shouldn't happen), use original dimensions
|
|
1725
|
+
filtered_dimensions = dimensions
|
|
1726
|
+
|
|
1727
|
+
# Use minimum dimension for safety (matches your existing logic)
|
|
1728
|
+
min_dimension = np.min(filtered_dimensions)
|
|
1729
|
+
max_r = min_dimension * proportion
|
|
1730
|
+
|
|
1731
|
+
return max_r
|
|
1732
|
+
|
|
1733
|
+
def apply_edge_correction_to_ripley(roots, targs, proportion, bounds, dim, node_centroids=None):
|
|
1734
|
+
"""
|
|
1735
|
+
Apply edge correction through mirroring to target points.
|
|
1736
|
+
|
|
1737
|
+
This should be called AFTER convert_centroids_to_array but BEFORE
|
|
1738
|
+
convert_augmented_array_to_points (for 2D case).
|
|
1739
|
+
|
|
1740
|
+
Parameters:
|
|
1741
|
+
roots: array of root points (search centers) - already scaled
|
|
1742
|
+
targs: array of target points (points being searched for) - already scaled
|
|
1743
|
+
proportion: the proportion parameter from your workflow
|
|
1744
|
+
bounds: boundary tuple (min_coords, max_coords) or None
|
|
1745
|
+
dim: dimension (2 or 3)
|
|
1746
|
+
node_centroids: dict of node centroids (needed if bounds is None)
|
|
1747
|
+
|
|
1748
|
+
Returns:
|
|
1749
|
+
tuple: (roots, mirrored_targs) where mirrored_targs includes edge corrections
|
|
1750
|
+
"""
|
|
1751
|
+
# Handle bounds calculation if not provided (matching your existing logic)
|
|
1752
|
+
if bounds is None:
|
|
1753
|
+
if node_centroids is None:
|
|
1754
|
+
# Fallback: calculate from the points we have
|
|
1755
|
+
all_points = np.vstack([roots, targs])
|
|
1756
|
+
else:
|
|
1757
|
+
# Use your existing method
|
|
1758
|
+
import proximity # Assuming this is available
|
|
1759
|
+
big_array = proximity.convert_centroids_to_array(list(node_centroids.values()))
|
|
1760
|
+
all_points = big_array
|
|
1761
|
+
|
|
1762
|
+
min_coords = np.array([0, 0, 0])
|
|
1763
|
+
max_coords = [np.max(all_points[:, 0]), np.max(all_points[:, 1]), np.max(all_points[:, 2])]
|
|
1764
|
+
max_coords = np.flip(max_coords) # Convert [x,y,z] to [z,y,x] format
|
|
1765
|
+
bounds = (min_coords, max_coords)
|
|
1766
|
+
|
|
1767
|
+
if 'big_array' in locals():
|
|
1768
|
+
del big_array
|
|
1769
|
+
|
|
1770
|
+
# Calculate max_r using your existing logic
|
|
1771
|
+
max_r = get_max_r_from_proportion(bounds, proportion)
|
|
1772
|
+
|
|
1773
|
+
# Mirror target points for edge correction
|
|
1774
|
+
mirrored_targs = mirror_points_for_edge_correction(targs, bounds, max_r, dim)
|
|
1775
|
+
|
|
1776
|
+
print(f"Original target points: {len(targs)}, After mirroring: {len(mirrored_targs)}")
|
|
1777
|
+
print(f"Added {len(mirrored_targs) - len(targs)} mirrored points for edge correction")
|
|
1778
|
+
print(f"Using max_r = {max_r} for mirroring threshold")
|
|
1779
|
+
print(f"Bounds used: min={bounds[0]}, max={bounds[1]}")
|
|
1780
|
+
|
|
1781
|
+
return roots, mirrored_targs
|
|
1782
|
+
|
|
1577
1783
|
|
|
1578
1784
|
#CLASSLESS FUNCTIONS THAT MAY BE USEFUL TO USERS TO RUN DIRECTLY THAT SUPPORT ANALYSIS IN SOME WAY. NOTE THESE METHODS SOMETIMES ARE USED INTERNALLY AS WELL:
|
|
1579
1785
|
|
|
@@ -1803,13 +2009,21 @@ def label_branches(array, peaks = 0, branch_removal = 0, comp_dil = 0, max_vol =
|
|
|
1803
2009
|
if nodes is None:
|
|
1804
2010
|
|
|
1805
2011
|
array = smart_dilate.smart_label(array, other_array, GPU = GPU, remove_template = True)
|
|
2012
|
+
#distance = smart_dilate.compute_distance_transform_distance(array)
|
|
2013
|
+
print("Watershedding result...")
|
|
2014
|
+
#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)
|
|
1806
2015
|
|
|
1807
2016
|
else:
|
|
1808
2017
|
if down_factor is not None:
|
|
1809
2018
|
array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, predownsample = down_factor, remove_template = True)
|
|
2019
|
+
#distance = smart_dilate.compute_distance_transform_distance(bonus_array)
|
|
2020
|
+
#array = water(-distance, array, mask=bonus_array)
|
|
1810
2021
|
else:
|
|
1811
2022
|
|
|
1812
2023
|
array = smart_dilate.smart_label(bonus_array, array, GPU = GPU, remove_template = True)
|
|
2024
|
+
#distance = smart_dilate.compute_distance_transform_distance(bonus_array)
|
|
2025
|
+
#array = water(-distance, array, mask=bonus_array)
|
|
2026
|
+
|
|
1813
2027
|
|
|
1814
2028
|
if down_factor is not None and nodes is None:
|
|
1815
2029
|
array = upsample_with_padding(array, down_factor, arrayshape)
|
|
@@ -2089,6 +2303,56 @@ def filter_size_by_vol(binary_array, volume_threshold):
|
|
|
2089
2303
|
|
|
2090
2304
|
return result
|
|
2091
2305
|
|
|
2306
|
+
def gray_watershed(image, min_distance = 1, threshold_abs = None):
|
|
2307
|
+
|
|
2308
|
+
|
|
2309
|
+
from skimage.feature import peak_local_max
|
|
2310
|
+
|
|
2311
|
+
if len(np.unique(image)) == 2:
|
|
2312
|
+
image = smart_dilate.compute_distance_transform_distance(image)
|
|
2313
|
+
|
|
2314
|
+
|
|
2315
|
+
is_pseudo_3d = image.shape[0] == 1
|
|
2316
|
+
if is_pseudo_3d:
|
|
2317
|
+
image = np.squeeze(image) # Convert to 2D for processing
|
|
2318
|
+
|
|
2319
|
+
#smoothed = ndimage.gaussian_filter(image.astype(float), sigma=2)
|
|
2320
|
+
|
|
2321
|
+
peaks = peak_local_max(image, min_distance = min_distance, threshold_abs = threshold_abs)
|
|
2322
|
+
if len(peaks) < 256:
|
|
2323
|
+
dtype = np.uint8
|
|
2324
|
+
elif len(peaks) < 65535:
|
|
2325
|
+
dtype = np.uint16
|
|
2326
|
+
else:
|
|
2327
|
+
dytpe = np.uint32
|
|
2328
|
+
|
|
2329
|
+
clone = np.zeros_like(image).astype(dtype)
|
|
2330
|
+
|
|
2331
|
+
if not is_pseudo_3d:
|
|
2332
|
+
for i, peak in enumerate(peaks):
|
|
2333
|
+
z, y, x = peak
|
|
2334
|
+
clone[z,y,x] = i + 1
|
|
2335
|
+
else:
|
|
2336
|
+
for i, peak in enumerate(peaks):
|
|
2337
|
+
y, x = peak
|
|
2338
|
+
clone[y,x] = i + 1
|
|
2339
|
+
|
|
2340
|
+
|
|
2341
|
+
if is_pseudo_3d:
|
|
2342
|
+
image = np.expand_dims(image, axis = 0)
|
|
2343
|
+
clone = np.expand_dims(clone, axis = 0)
|
|
2344
|
+
|
|
2345
|
+
|
|
2346
|
+
binary_image = binarize(image)
|
|
2347
|
+
#image = smart_dilate.smart_label(image, clone, GPU = False)
|
|
2348
|
+
|
|
2349
|
+
image = water(-image, clone, mask=binary_image)
|
|
2350
|
+
|
|
2351
|
+
|
|
2352
|
+
|
|
2353
|
+
return image
|
|
2354
|
+
|
|
2355
|
+
|
|
2092
2356
|
def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_rad = None, predownsample = None, predownsample2 = None):
|
|
2093
2357
|
"""
|
|
2094
2358
|
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.
|
|
@@ -2162,15 +2426,17 @@ def watershed(image, directory = None, proportion = 0.1, GPU = True, smallest_ra
|
|
|
2162
2426
|
if len(labels.shape) ==2:
|
|
2163
2427
|
labels = np.expand_dims(labels, axis = 0)
|
|
2164
2428
|
|
|
2165
|
-
del distance
|
|
2429
|
+
#del distance
|
|
2166
2430
|
|
|
2167
2431
|
|
|
2168
2432
|
if labels.shape[1] < original_shape[1]: #If downsample was used, upsample output
|
|
2169
2433
|
labels = upsample_with_padding(labels, downsample_needed, original_shape)
|
|
2170
2434
|
labels = labels * old_mask
|
|
2171
|
-
labels =
|
|
2435
|
+
labels = water(-distance, labels, mask=old_mask) # Here i like skimage watershed over smart_label, mainly because skimage just kicks out too-small nodes from the image, while smart label just labels them sort of wrongly.
|
|
2436
|
+
#labels = smart_dilate.smart_label(old_mask, labels, GPU = GPU, predownsample = predownsample2)
|
|
2172
2437
|
else:
|
|
2173
|
-
labels =
|
|
2438
|
+
labels = water(-distance, labels, mask=image)
|
|
2439
|
+
#labels = smart_dilate.smart_label(image, labels, GPU = GPU, predownsample = predownsample2)
|
|
2174
2440
|
|
|
2175
2441
|
if directory is None:
|
|
2176
2442
|
pass
|
|
@@ -3389,8 +3655,6 @@ class Network_3D:
|
|
|
3389
3655
|
if skeletonized:
|
|
3390
3656
|
binary_edges = skeletonize(binary_edges)
|
|
3391
3657
|
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
3658
|
if search is not None and hasattr(self, '_nodes') and self._nodes is not None and self._search_region is None:
|
|
3395
3659
|
search_region = binarize(self._nodes)
|
|
3396
3660
|
dilate_xy, dilate_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, search, search)
|
|
@@ -3550,11 +3814,12 @@ class Network_3D:
|
|
|
3550
3814
|
self._network_lists = network_analysis.read_excel_to_lists(df)
|
|
3551
3815
|
self._network, net_weights = network_analysis.weighted_network(df)
|
|
3552
3816
|
|
|
3553
|
-
if ignore_search_region and hasattr(self, '_edges') and self._edges is not None and hasattr(self, '_nodes') and self._nodes is not None
|
|
3554
|
-
dilate_xy, dilate_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, search, search)
|
|
3555
|
-
print(f"{dilate_xy}, {dilate_z}")
|
|
3817
|
+
if ignore_search_region and hasattr(self, '_edges') and self._edges is not None and hasattr(self, '_nodes') and self._nodes is not None:
|
|
3818
|
+
#dilate_xy, dilate_z = dilation_length_to_pixels(self._xy_scale, self._z_scale, search, search)
|
|
3819
|
+
#print(f"{dilate_xy}, {dilate_z}")
|
|
3556
3820
|
num_nodes = np.max(self._nodes)
|
|
3557
|
-
connections_parallel = create_node_dictionary(self._nodes, self._edges, num_nodes, dilate_xy, dilate_z) #Find which edges connect which nodes and put them in a dictionary.
|
|
3821
|
+
#connections_parallel = create_node_dictionary(self._nodes, self._edges, num_nodes, dilate_xy, dilate_z) #Find which edges connect which nodes and put them in a dictionary.
|
|
3822
|
+
connections_parallel = create_node_dictionary(self._nodes, self._edges, num_nodes, 3, 3) #For now I only implement this for immediate neighbor search so we'll just use 3 and 3 here.
|
|
3558
3823
|
connections_parallel = find_shared_value_pairs(connections_parallel) #Sort through the dictionary to find connected node pairs.
|
|
3559
3824
|
df = create_and_save_dataframe(connections_parallel)
|
|
3560
3825
|
self._network_lists = network_analysis.read_excel_to_lists(df)
|
|
@@ -3635,7 +3900,10 @@ class Network_3D:
|
|
|
3635
3900
|
except:
|
|
3636
3901
|
pass
|
|
3637
3902
|
|
|
3638
|
-
|
|
3903
|
+
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) #Will have to be moved out if the second method becomes more directly implemented
|
|
3904
|
+
else:
|
|
3905
|
+
self._edges, _ = label_objects(edges)
|
|
3906
|
+
|
|
3639
3907
|
del edges
|
|
3640
3908
|
if directory is not None:
|
|
3641
3909
|
try:
|
|
@@ -4650,27 +4918,50 @@ class Network_3D:
|
|
|
4650
4918
|
return neighborhood_dict, proportion_dict, title1, title2, densities
|
|
4651
4919
|
|
|
4652
4920
|
|
|
4653
|
-
|
|
4921
|
+
|
|
4922
|
+
def get_ripley(self, root = None, targ = None, distance = 1, edgecorrect = True, bounds = None, ignore_dims = False, proportion = 0.5, mode = 0, safe = False, factor = 0.25):
|
|
4923
|
+
|
|
4924
|
+
is_subset = False
|
|
4925
|
+
|
|
4926
|
+
if bounds is None:
|
|
4927
|
+
big_array = proximity.convert_centroids_to_array(list(self.node_centroids.values()))
|
|
4928
|
+
min_coords = np.array([0,0,0])
|
|
4929
|
+
max_coords = [np.max(big_array[:, 0]), np.max(big_array[:, 1]), np.max(big_array[:, 2])]
|
|
4930
|
+
del big_array
|
|
4931
|
+
max_coords = np.flip(max_coords)
|
|
4932
|
+
bounds = (min_coords, max_coords)
|
|
4933
|
+
else:
|
|
4934
|
+
min_coords, max_coords = bounds
|
|
4935
|
+
|
|
4936
|
+
min_bounds, max_bounds = bounds
|
|
4937
|
+
sides = max_bounds - min_bounds
|
|
4938
|
+
# Set max_r to None since we've handled edge effects through mirroring
|
|
4654
4939
|
|
|
4655
4940
|
|
|
4656
4941
|
if root is None or targ is None: #Self clustering in this case
|
|
4657
4942
|
roots = self._node_centroids.values()
|
|
4943
|
+
root_ids = self.node_centroids.keys()
|
|
4658
4944
|
targs = self._node_centroids.values()
|
|
4945
|
+
is_subset = True
|
|
4659
4946
|
else:
|
|
4660
4947
|
roots = []
|
|
4661
4948
|
targs = []
|
|
4949
|
+
root_ids = []
|
|
4662
4950
|
|
|
4663
4951
|
for node, nodeid in self.node_identities.items(): #Otherwise we need to pull out this info
|
|
4664
4952
|
if nodeid == root:
|
|
4665
4953
|
roots.append(self._node_centroids[node])
|
|
4954
|
+
root_ids.append(node)
|
|
4666
4955
|
if nodeid == targ:
|
|
4667
4956
|
targs.append(self._node_centroids[node])
|
|
4668
4957
|
|
|
4958
|
+
if not is_subset:
|
|
4959
|
+
if np.array_equal(roots, targs):
|
|
4960
|
+
is_subset = True
|
|
4961
|
+
|
|
4669
4962
|
rooties = proximity.convert_centroids_to_array(roots, xy_scale = self.xy_scale, z_scale = self.z_scale)
|
|
4670
4963
|
targs = proximity.convert_centroids_to_array(targs, xy_scale = self.xy_scale, z_scale = self.z_scale)
|
|
4671
|
-
|
|
4672
|
-
del rooties
|
|
4673
|
-
|
|
4964
|
+
|
|
4674
4965
|
try:
|
|
4675
4966
|
if self.nodes.shape[0] == 1:
|
|
4676
4967
|
dim = 2
|
|
@@ -4683,66 +4974,135 @@ class Network_3D:
|
|
|
4683
4974
|
dim = 3
|
|
4684
4975
|
break
|
|
4685
4976
|
|
|
4977
|
+
if dim == 2:
|
|
4978
|
+
volume = sides[0] * sides[1] * self.xy_scale**2
|
|
4979
|
+
else:
|
|
4980
|
+
volume = np.prod(sides) * self.z_scale * self.xy_scale**2
|
|
4981
|
+
|
|
4982
|
+
points_array = np.vstack((rooties, targs))
|
|
4983
|
+
del rooties
|
|
4984
|
+
max_r = None
|
|
4985
|
+
if safe:
|
|
4986
|
+
proportion = factor
|
|
4987
|
+
|
|
4988
|
+
|
|
4686
4989
|
if ignore_dims:
|
|
4687
4990
|
|
|
4688
|
-
|
|
4991
|
+
new_list = []
|
|
4689
4992
|
|
|
4690
|
-
|
|
4993
|
+
if mode == 0:
|
|
4691
4994
|
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
else:
|
|
4699
|
-
min_coords, max_coords = bounds
|
|
4995
|
+
try:
|
|
4996
|
+
dim_list = max_coords - min_coords
|
|
4997
|
+
except:
|
|
4998
|
+
min_coords = np.array([0,0,0])
|
|
4999
|
+
bounds = (min_coords, max_coords)
|
|
5000
|
+
dim_list = max_coords - min_coords
|
|
4700
5001
|
|
|
4701
|
-
try:
|
|
4702
|
-
dim_list = max_coords - min_coords
|
|
4703
|
-
except:
|
|
4704
|
-
min_coords = np.array([0,0,0])
|
|
4705
|
-
bounds = (min_coords, max_coords)
|
|
4706
|
-
dim_list = max_coords - min_coords
|
|
4707
5002
|
|
|
4708
|
-
|
|
5003
|
+
if dim == 3:
|
|
4709
5004
|
|
|
5005
|
+
"""
|
|
5006
|
+
if self.xy_scale > self.z_scale: # xy will be 'expanded' more so its components will be arbitrarily further from the border than z ones
|
|
5007
|
+
factor_xy = (self.z_scale/self.xy_scale) * factor # So 'factor' in the xy dim has to get smaller
|
|
5008
|
+
factor_z = factor
|
|
5009
|
+
elif self.z_scale > self.xy_scale: # Same idea
|
|
5010
|
+
factor_z = (self.xy_scale/self.z_scale) * factor
|
|
5011
|
+
factor_xy = factor
|
|
5012
|
+
else:
|
|
5013
|
+
factor_z = factor
|
|
5014
|
+
factor_xy = factor
|
|
5015
|
+
"""
|
|
5016
|
+
|
|
5017
|
+
for centroid in roots:
|
|
4710
5018
|
|
|
4711
|
-
|
|
4712
|
-
|
|
5019
|
+
if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor) and ((centroid[0] - min_coords[2]) > dim_list[2] * factor) and ((max_coords[2] - centroid[0]) > dim_list[2] * factor):
|
|
5020
|
+
new_list.append(centroid)
|
|
4713
5021
|
|
|
4714
|
-
if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor) and ((centroid[0] - min_coords[2]) > dim_list[2] * factor) and ((max_coords[2] - centroid[0]) > dim_list[2] * factor):
|
|
4715
|
-
new_list.append(centroid)
|
|
4716
5022
|
|
|
5023
|
+
#if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor) and ((centroid[0] - min_coords[2]) > dim_list[2] * factor) and ((max_coords[2] - centroid[0]) > dim_list[2] * factor):
|
|
5024
|
+
#new_list.append(centroid)
|
|
5025
|
+
#print(f"dim_list: {dim_list}, centroid: {centroid}, min_coords: {min_coords}, max_coords: {max_coords}")
|
|
5026
|
+
else:
|
|
5027
|
+
for centroid in roots:
|
|
5028
|
+
|
|
5029
|
+
if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor):
|
|
5030
|
+
new_list.append(centroid)
|
|
4717
5031
|
|
|
4718
|
-
#if ((centroid[2] - min_coords[0]) > dim_list[0] * factor) and ((max_coords[0] - centroid[2]) > dim_list[0] * factor) and ((centroid[1] - min_coords[1]) > dim_list[1] * factor) and ((max_coords[1] - centroid[1]) > dim_list[1] * factor) and ((centroid[0] - min_coords[2]) > dim_list[2] * factor) and ((max_coords[2] - centroid[0]) > dim_list[2] * factor):
|
|
4719
|
-
#new_list.append(centroid)
|
|
4720
|
-
#print(f"dim_list: {dim_list}, centroid: {centroid}, min_coords: {min_coords}, max_coords: {max_coords}")
|
|
4721
5032
|
else:
|
|
4722
|
-
for centroid in roots:
|
|
4723
5033
|
|
|
4724
|
-
|
|
4725
|
-
|
|
5034
|
+
if mode == 1:
|
|
5035
|
+
|
|
5036
|
+
legal = self.edges != 0
|
|
5037
|
+
|
|
5038
|
+
elif mode == 2:
|
|
5039
|
+
|
|
5040
|
+
legal = self.network_overlay != 0
|
|
5041
|
+
|
|
5042
|
+
elif mode == 3:
|
|
5043
|
+
|
|
5044
|
+
legal = self.id_overlay != 0
|
|
5045
|
+
|
|
5046
|
+
if self.nodes is None:
|
|
5047
|
+
|
|
5048
|
+
temp_array = proximity.populate_array(self.node_centroids, shape = legal.shape)
|
|
5049
|
+
else:
|
|
5050
|
+
temp_array = self.nodes
|
|
5051
|
+
|
|
5052
|
+
if dim == 2:
|
|
5053
|
+
volume = np.count_nonzero(legal) * self.xy_scale**2
|
|
5054
|
+
else:
|
|
5055
|
+
volume = np.count_nonzero(legal) * self.z_scale * self.xy_scale**2
|
|
5056
|
+
print(f"Using {volume} for the volume measurement (Volume of provided mask as scaled by xy and z scaling)")
|
|
5057
|
+
|
|
5058
|
+
legal = smart_dilate.compute_distance_transform_distance(legal, sampling = [self.z_scale, self.xy_scale, self.xy_scale]) # Get true distances
|
|
5059
|
+
|
|
5060
|
+
max_avail = np.max(legal) # Most internal point
|
|
5061
|
+
min_legal = factor * max_avail # Values of stuff 25% within the tissue
|
|
5062
|
+
|
|
5063
|
+
legal = legal > min_legal
|
|
5064
|
+
|
|
5065
|
+
if safe:
|
|
5066
|
+
max_r = min_legal
|
|
5067
|
+
|
|
5068
|
+
|
|
5069
|
+
legal = temp_array * legal
|
|
5070
|
+
|
|
5071
|
+
legal = np.unique(legal)
|
|
5072
|
+
if 0 in legal:
|
|
5073
|
+
legal = np.delete(legal, 0)
|
|
5074
|
+
for node in legal:
|
|
5075
|
+
if node in root_ids:
|
|
5076
|
+
new_list.append(self.node_centroids[node])
|
|
4726
5077
|
|
|
4727
5078
|
roots = new_list
|
|
4728
5079
|
print(f"Utilizing {len(roots)} root points. Note that low n values are unstable.")
|
|
4729
5080
|
is_subset = True
|
|
4730
|
-
else:
|
|
4731
|
-
is_subset = False
|
|
4732
|
-
|
|
4733
5081
|
|
|
4734
5082
|
|
|
4735
5083
|
|
|
4736
5084
|
roots = proximity.convert_centroids_to_array(roots, xy_scale = self.xy_scale, z_scale = self.z_scale)
|
|
4737
5085
|
|
|
5086
|
+
n_subset = len(targs)
|
|
5087
|
+
|
|
5088
|
+
# Apply edge correction through mirroring
|
|
5089
|
+
if edgecorrect:
|
|
5090
|
+
|
|
5091
|
+
|
|
5092
|
+
roots, targs = apply_edge_correction_to_ripley(
|
|
5093
|
+
roots, targs, proportion, bounds, dim,
|
|
5094
|
+
node_centroids=self.node_centroids # Pass this for bounds calculation if needed
|
|
5095
|
+
)
|
|
5096
|
+
|
|
4738
5097
|
|
|
4739
5098
|
if dim == 2:
|
|
4740
5099
|
roots = proximity.convert_augmented_array_to_points(roots)
|
|
4741
5100
|
targs = proximity.convert_augmented_array_to_points(targs)
|
|
4742
5101
|
|
|
4743
|
-
|
|
5102
|
+
print(f"Using {len(roots)} root points")
|
|
5103
|
+
r_vals = proximity.generate_r_values(points_array, distance, bounds = bounds, dim = dim, max_proportion=proportion, max_r = max_r)
|
|
4744
5104
|
|
|
4745
|
-
k_vals = proximity.optimized_ripleys_k(roots, targs, r_vals, bounds=bounds,
|
|
5105
|
+
k_vals = proximity.optimized_ripleys_k(roots, targs, r_vals, bounds=bounds, dim = dim, is_subset = is_subset, volume = volume, n_subset = n_subset)
|
|
4746
5106
|
|
|
4747
5107
|
h_vals = proximity.compute_ripleys_h(k_vals, r_vals, dim)
|
|
4748
5108
|
|
|
@@ -4804,7 +5164,7 @@ class Network_3D:
|
|
|
4804
5164
|
|
|
4805
5165
|
self.remove_edge_weights()
|
|
4806
5166
|
|
|
4807
|
-
def centroid_array(self, clip = False):
|
|
5167
|
+
def centroid_array(self, clip = False, shape = None):
|
|
4808
5168
|
"""Use the centroids to populate a node array"""
|
|
4809
5169
|
|
|
4810
5170
|
if clip:
|
|
@@ -4814,12 +5174,13 @@ class Network_3D:
|
|
|
4814
5174
|
|
|
4815
5175
|
else:
|
|
4816
5176
|
|
|
4817
|
-
array = proximity.populate_array(self.node_centroids)
|
|
5177
|
+
array = proximity.populate_array(self.node_centroids, shape = shape)
|
|
4818
5178
|
|
|
4819
5179
|
return array
|
|
4820
5180
|
|
|
4821
5181
|
|
|
4822
5182
|
|
|
5183
|
+
|
|
4823
5184
|
def random_nodes(self, bounds = None, mask = None):
|
|
4824
5185
|
|
|
4825
5186
|
if self.nodes is not None:
|
|
@@ -5043,21 +5404,23 @@ class Network_3D:
|
|
|
5043
5404
|
|
|
5044
5405
|
zero_group = {}
|
|
5045
5406
|
|
|
5407
|
+
comus = invert_dict(self.communities)
|
|
5046
5408
|
|
|
5047
|
-
if limit is not None:
|
|
5048
|
-
|
|
5049
|
-
coms = invert_dict(self.communities)
|
|
5050
5409
|
|
|
5410
|
+
if limit is not None:
|
|
5051
5411
|
|
|
5052
|
-
for com, nodes in
|
|
5412
|
+
for com, nodes in comus.items():
|
|
5053
5413
|
|
|
5054
5414
|
if len(nodes) < limit:
|
|
5055
5415
|
|
|
5056
5416
|
del identities[com]
|
|
5057
5417
|
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5418
|
+
try:
|
|
5419
|
+
if count > len(identities):
|
|
5420
|
+
print(f"Requested neighborhoods too large for available communities. Using {len(identities)} neighborhoods (max for these coms)")
|
|
5421
|
+
count = len(identities)
|
|
5422
|
+
except:
|
|
5423
|
+
pass
|
|
5061
5424
|
|
|
5062
5425
|
|
|
5063
5426
|
if mode == 0:
|
|
@@ -5068,13 +5431,21 @@ class Network_3D:
|
|
|
5068
5431
|
coms = {}
|
|
5069
5432
|
|
|
5070
5433
|
neighbors = {}
|
|
5434
|
+
len_dict = {}
|
|
5435
|
+
inc_count = 0
|
|
5071
5436
|
|
|
5072
5437
|
for i, cluster in enumerate(clusters):
|
|
5073
5438
|
|
|
5439
|
+
size = len(cluster)
|
|
5440
|
+
inc_count += size
|
|
5441
|
+
|
|
5442
|
+
len_dict[i + 1] = [size]
|
|
5443
|
+
|
|
5074
5444
|
for com in cluster: # For community ID per list
|
|
5075
5445
|
|
|
5076
5446
|
coms[com] = i + 1
|
|
5077
5447
|
|
|
5448
|
+
|
|
5078
5449
|
copy_dict = copy.deepcopy(self.communities)
|
|
5079
5450
|
|
|
5080
5451
|
for node, com in copy_dict.items():
|
|
@@ -5092,18 +5463,17 @@ class Network_3D:
|
|
|
5092
5463
|
|
|
5093
5464
|
if len(zero_group) > 0:
|
|
5094
5465
|
self.communities.update(zero_group)
|
|
5466
|
+
len_dict[0] = [len(comus) - inc_count]
|
|
5095
5467
|
|
|
5096
5468
|
|
|
5097
5469
|
identities, id_set = self.community_id_info_per_com()
|
|
5098
5470
|
|
|
5099
|
-
len_dict = {}
|
|
5100
|
-
|
|
5101
5471
|
coms = invert_dict(self.communities)
|
|
5102
5472
|
node_count = len(list(self.communities.keys()))
|
|
5103
5473
|
|
|
5104
5474
|
for com, nodes in coms.items():
|
|
5105
5475
|
|
|
5106
|
-
len_dict[com]
|
|
5476
|
+
len_dict[com].append(len(nodes)/node_count)
|
|
5107
5477
|
|
|
5108
5478
|
matrixes = []
|
|
5109
5479
|
|
|
@@ -5119,7 +5489,7 @@ class Network_3D:
|
|
|
5119
5489
|
|
|
5120
5490
|
identities3 = {}
|
|
5121
5491
|
for iden in identities2:
|
|
5122
|
-
identities3[iden] = identities2[iden]/len_dict[iden]
|
|
5492
|
+
identities3[iden] = identities2[iden]/len_dict[iden][1]
|
|
5123
5493
|
|
|
5124
5494
|
output = neighborhoods.plot_dict_heatmap(identities3, id_set2, title = "Neighborhood Heatmap by Proportional Composition of Nodes in Neighborhood vs All Nodes Divided by Neighborhood Total Proportion of All Nodes (val < 1 = underrepresented, val > 1 = overrepresented)", center_at_one = True)
|
|
5125
5495
|
matrixes.append(output)
|
|
@@ -5291,7 +5661,33 @@ class Network_3D:
|
|
|
5291
5661
|
pass
|
|
5292
5662
|
|
|
5293
5663
|
|
|
5294
|
-
def nearest_neighbors_avg(self, root, targ, xy_scale = 1, z_scale = 1, num = 1, heatmap = False, threed = True, numpy = False):
|
|
5664
|
+
def nearest_neighbors_avg(self, root, targ, xy_scale = 1, z_scale = 1, num = 1, heatmap = False, threed = True, numpy = False, quant = False):
|
|
5665
|
+
|
|
5666
|
+
def get_theoretical_nearest_neighbor_distance(compare_set, num_neighbors, volume, is_2d=False):
|
|
5667
|
+
"""
|
|
5668
|
+
Calculate theoretical expected distance to k-th nearest neighbor
|
|
5669
|
+
assuming random uniform distribution in 2D or 3D space.
|
|
5670
|
+
"""
|
|
5671
|
+
import math
|
|
5672
|
+
|
|
5673
|
+
if len(compare_set) == 0 or volume <= 0:
|
|
5674
|
+
raise ValueError("Invalid input: empty set or non-positive volume")
|
|
5675
|
+
|
|
5676
|
+
density = len(compare_set) / volume
|
|
5677
|
+
k = num_neighbors
|
|
5678
|
+
|
|
5679
|
+
if is_2d:
|
|
5680
|
+
# Expected distance to k-th nearest neighbor in 2D
|
|
5681
|
+
# μ1' = Γ(k + 1/2) / (Γ(k) × √(m × π))
|
|
5682
|
+
expected_distance = math.gamma(k + 0.5) / (math.gamma(k) * math.sqrt(density * math.pi))
|
|
5683
|
+
else:
|
|
5684
|
+
# Expected distance to k-th nearest neighbor in 3D
|
|
5685
|
+
# μ1' = Γ(k + 1/3) / (Γ(k) × (m × Φ)^(1/3))
|
|
5686
|
+
# where Φ = π^(3/2) / Γ(3/2 + 1) = π^(3/2) / Γ(5/2) = 4π/3
|
|
5687
|
+
phi_3d = 4 * math.pi / 3 # Volume of unit sphere in 3D
|
|
5688
|
+
expected_distance = math.gamma(k + 1/3) / (math.gamma(k) * (density * phi_3d)**(1/3))
|
|
5689
|
+
|
|
5690
|
+
return expected_distance
|
|
5295
5691
|
|
|
5296
5692
|
root_set = []
|
|
5297
5693
|
|
|
@@ -5335,6 +5731,14 @@ class Network_3D:
|
|
|
5335
5731
|
|
|
5336
5732
|
avg, output = proximity.average_nearest_neighbor_distances(self.node_centroids, root_set, compare_set, xy_scale=self.xy_scale, z_scale=self.z_scale, num = num)
|
|
5337
5733
|
|
|
5734
|
+
if quant:
|
|
5735
|
+
try:
|
|
5736
|
+
quant_overlay = node_draw.degree_infect(output, self._nodes, make_floats = True)
|
|
5737
|
+
except:
|
|
5738
|
+
quant_overlay = None
|
|
5739
|
+
else:
|
|
5740
|
+
quant_overlay = None
|
|
5741
|
+
|
|
5338
5742
|
if heatmap:
|
|
5339
5743
|
|
|
5340
5744
|
|
|
@@ -5345,7 +5749,30 @@ class Network_3D:
|
|
|
5345
5749
|
big_array = proximity.convert_centroids_to_array(list(self.node_centroids.values()))
|
|
5346
5750
|
shape = [np.max(big_array[0, :]) + 1, np.max(big_array[1, :]) + 1, np.max(big_array[2, :]) + 1]
|
|
5347
5751
|
|
|
5348
|
-
|
|
5752
|
+
|
|
5753
|
+
try:
|
|
5754
|
+
bounds = self.nodes.shape
|
|
5755
|
+
except:
|
|
5756
|
+
try:
|
|
5757
|
+
bounds = self.edges.shape
|
|
5758
|
+
except:
|
|
5759
|
+
try:
|
|
5760
|
+
bounds = self.network_overlay.shape
|
|
5761
|
+
except:
|
|
5762
|
+
try:
|
|
5763
|
+
bounds = self.id_overlay.shape
|
|
5764
|
+
except:
|
|
5765
|
+
big_array = proximity.convert_centroids_to_array(list(self.node_centroids.values()))
|
|
5766
|
+
max_coords = [np.max(big_array[:, 0]), np.max(big_array[:, 1]), np.max(big_array[:, 2])]
|
|
5767
|
+
del big_array
|
|
5768
|
+
volume = bounds[0] * bounds[1] * bounds[2] * self.z_scale * self.xy_scale**2
|
|
5769
|
+
if 1 in bounds or 0 in bounds:
|
|
5770
|
+
is_2d = True
|
|
5771
|
+
else:
|
|
5772
|
+
is_2d = False
|
|
5773
|
+
|
|
5774
|
+
pred = get_theoretical_nearest_neighbor_distance(compare_set, num, volume, is_2d = is_2d)
|
|
5775
|
+
#pred = avg
|
|
5349
5776
|
|
|
5350
5777
|
node_intensity = {}
|
|
5351
5778
|
import math
|
|
@@ -5359,12 +5786,12 @@ class Network_3D:
|
|
|
5359
5786
|
|
|
5360
5787
|
overlay = neighborhoods.create_node_heatmap(node_intensity, node_centroids, shape = shape, is_3d=threed, labeled_array = self.nodes, colorbar_label="Clustering Intensity", title = title)
|
|
5361
5788
|
|
|
5362
|
-
return avg, output, overlay
|
|
5789
|
+
return avg, output, overlay, quant_overlay
|
|
5363
5790
|
|
|
5364
5791
|
else:
|
|
5365
5792
|
neighborhoods.create_node_heatmap(node_intensity, node_centroids, shape = shape, is_3d=threed, labeled_array = None, colorbar_label="Clustering Intensity", title = title)
|
|
5366
5793
|
|
|
5367
|
-
return avg, output
|
|
5794
|
+
return avg, output, quant_overlay
|
|
5368
5795
|
|
|
5369
5796
|
|
|
5370
5797
|
|