|
| 1 | +""" |
| 2 | +Created on Wed Dec 21 19:00:00 2022 |
| 3 | +
|
| 4 | +@author: Anna Grim |
| 5 | + |
| 6 | +
|
| 7 | +
|
| 8 | +Implementation of a custom subclass of NetworkX.Graph called SkeletonGraph. |
| 9 | +
|
| 10 | +""" |
| 11 | + |
1 | 12 | from scipy.spatial import distance |
2 | 13 |
|
3 | 14 | import networkx as nx |
|
7 | 18 |
|
8 | 19 |
|
9 | 20 | class SkeletonGraph(nx.Graph): |
| 21 | + """ |
| 22 | + A subclass of the NetworkX.Graph that represents a skeleton graph with |
| 23 | + additional functionality for handling labels and voxel coordinates |
| 24 | + corresponding to the nodes. Note that node IDs directly index into the |
| 25 | + "labels" and "voxels" attributes. |
| 26 | +
|
| 27 | + Attributes |
| 28 | + ---------- |
| 29 | + anisotropy : ArrayLike |
| 30 | + Image to physical coordinates scaling factors to account for the |
| 31 | + anisotropy of the microscope. |
| 32 | + run_length : float |
| 33 | + Physical path length of the graph. |
| 34 | + labels : numpy.ndarray |
| 35 | + A 1D array that contains a label value associated with each node. |
| 36 | + voxels : numpy.ndarray |
| 37 | + A 3D array that contains a voxel coordinate of each node. |
| 38 | +
|
| 39 | + """ |
10 | 40 |
|
11 | 41 | def __init__(self, anisotropy=(1.0, 1.0, 1.0)): |
| 42 | + """ |
| 43 | + Initializes a SkeletonGraph, including setting the anisotropy and |
| 44 | + initializing the run length attribute. |
| 45 | +
|
| 46 | + Parameters |
| 47 | + ---------- |
| 48 | + anisotropy : ArrayLike, optional |
| 49 | + Image to physical coordinates scaling factors to account for the |
| 50 | + anisotropy of the microscope. The default is (1.0, 1.0, 1.0). |
| 51 | +
|
| 52 | + Returns |
| 53 | + ------- |
| 54 | + None |
| 55 | +
|
| 56 | + """ |
12 | 57 | # Call parent class |
13 | 58 | super(SkeletonGraph, self).__init__() |
14 | 59 |
|
15 | 60 | # Instance attributes |
16 | | - self.anisotropy = anisotropy |
| 61 | + self.anisotropy = np.array(anisotropy) |
17 | 62 | self.run_length = 0 |
18 | 63 |
|
19 | | - def set_labels(self): |
| 64 | + def init_labels(self): |
| 65 | + """ |
| 66 | + Initializes the "labels" attribute for the graph. |
| 67 | +
|
| 68 | + Parameters |
| 69 | + ---------- |
| 70 | + None |
| 71 | +
|
| 72 | + Returns |
| 73 | + ------- |
| 74 | + None |
| 75 | +
|
| 76 | + """ |
20 | 77 | self.labels = np.zeros((self.number_of_nodes()), dtype=int) |
21 | 78 |
|
| 79 | + def init_voxels(self, voxels): |
| 80 | + """ |
| 81 | + Initializes the "voxels" attribute for the graph. |
| 82 | +
|
| 83 | + Parameters |
| 84 | + ---------- |
| 85 | + None |
| 86 | +
|
| 87 | + Returns |
| 88 | + ------- |
| 89 | + None |
| 90 | +
|
| 91 | + """ |
| 92 | + self.voxels = np.array(voxels, dtype=np.int32) |
| 93 | + |
22 | 94 | def set_nodes(self): |
| 95 | + """ |
| 96 | + Adds nodes to the graph. The nodes are assigned indices from 0 to the |
| 97 | + total number of voxels in the image. |
| 98 | +
|
| 99 | + Parameters |
| 100 | + ---------- |
| 101 | + None |
| 102 | +
|
| 103 | + Returns |
| 104 | + ------- |
| 105 | + None |
| 106 | +
|
| 107 | + """ |
23 | 108 | num_nodes = len(self.voxels) |
24 | 109 | self.add_nodes_from(np.arange(num_nodes)) |
25 | 110 |
|
26 | | - def set_voxels(self, voxels): |
27 | | - self.voxels = np.array(voxels, dtype=np.int32) |
28 | | - |
29 | 111 | # --- Getters --- |
30 | 112 | def get_labels(self): |
| 113 | + """ |
| 114 | + Gets the unique label values in the "labels" attribute. |
| 115 | +
|
| 116 | + Parameters |
| 117 | + ---------- |
| 118 | + None |
| 119 | +
|
| 120 | + Returns |
| 121 | + ------- |
| 122 | + numpy.ndarray |
| 123 | + A 1D array of unique labels assigned to nodes in the graph. |
| 124 | +
|
| 125 | + """ |
31 | 126 | return np.unique(self.labels) |
32 | 127 |
|
33 | 128 | def nodes_with_label(self, label): |
| 129 | + """ |
| 130 | + Gets the IDs of nodes that have the specified label value. |
| 131 | +
|
| 132 | + Parameters |
| 133 | + ---------- |
| 134 | + label : int |
| 135 | + Label value to search for in the "labels" attribute. |
| 136 | +
|
| 137 | + Returns |
| 138 | + ------- |
| 139 | + numpy.ndarray |
| 140 | + A 1D array of node IDs that have the specified label. |
| 141 | +
|
| 142 | + """ |
34 | 143 | return np.where(self.labels == label)[0] |
35 | 144 |
|
36 | 145 | # --- Computation --- |
@@ -69,14 +178,31 @@ def physical_dist(self, i, j): |
69 | 178 | Returns |
70 | 179 | ------- |
71 | 180 | float |
72 | | - Distance between physical coordinates of the given nodes. |
| 181 | + Euclidea distance between physical coordinates of the given nodes. |
73 | 182 |
|
74 | 183 | """ |
75 | 184 | xyz_i = self.voxels[i] * self.anisotropy |
76 | 185 | xyz_j = self.voxels[j] * self.anisotropy |
77 | 186 | return distance.euclidean(xyz_i, xyz_j) |
78 | 187 |
|
79 | 188 | def get_bbox(self, nodes): |
| 189 | + """ |
| 190 | + Calculates the bounding box that contains the voxel coordinates for a |
| 191 | + given collection of nodes. |
| 192 | +
|
| 193 | + Parameters |
| 194 | + ---------- |
| 195 | + nodes : Container |
| 196 | + A collection of node indices for which to compute the bounding box. |
| 197 | +
|
| 198 | + Returns |
| 199 | + ------- |
| 200 | + dict |
| 201 | + Dictionary containing the bounding box coordinates: |
| 202 | + - "min": minimum voxel coordinates along each axis. |
| 203 | + - "max": maximum voxel coordinates along each axis. |
| 204 | +
|
| 205 | + """ |
80 | 206 | bbox_min = np.inf * np.ones(3) |
81 | 207 | bbox_max = np.zeros(3) |
82 | 208 | for i in nodes: |
|
0 commit comments