Skip to content

Commit 3fa68e7

Browse files
author
anna-grim
committed
doc: skeleton graph
1 parent e5c2323 commit 3fa68e7

File tree

3 files changed

+36
-35
lines changed

3 files changed

+36
-35
lines changed

src/segmentation_skeleton_metrics/skeleton_graph.py

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,30 @@
2020

2121
class SkeletonGraph(nx.Graph):
2222
"""
23-
A subclass of the NetworkX.Graph that represents a skeleton graph with
24-
additional functionality for handling labels and voxel coordinates
25-
corresponding to the nodes. Note that node IDs directly index into the
26-
"labels" and "voxels" attributes.
23+
A subclass of the NetworkX.Graph designed for graphs built from SWC files.
24+
This class extends the functionality of the standard Graph by adding
25+
support for handling node labels and voxel coordinates. In this subclass,
26+
node IDs serve as direct indices for accessing the labels and voxels
27+
attributes.
2728
2829
Attributes
2930
----------
30-
anisotropy : ArrayLike
31+
anisotropy : np.ndarray
3132
Image to physical coordinates scaling factors to account for the
3233
anisotropy of the microscope.
3334
run_length : float
3435
Physical path length of the graph.
3536
labels : numpy.ndarray
3637
A 1D array that contains a label value associated with each node.
3738
voxels : numpy.ndarray
38-
A 3D array that contains a voxel coordinate of each node.
39+
A 3D array that contains a voxel coordinate for each node.
3940
4041
"""
4142

4243
def __init__(self, anisotropy=(1.0, 1.0, 1.0)):
4344
"""
4445
Initializes a SkeletonGraph, including setting the anisotropy and
45-
initializing the run length attribute.
46+
initializing the run length attributes.
4647
4748
Parameters
4849
----------
@@ -78,6 +79,8 @@ def init_labels(self):
7879
None
7980
8081
"""
82+
error_msg = "Graph must have nodes to initialize labels!"
83+
assert self.number_of_nodes() > 0, error_msg
8184
self.labels = np.zeros((self.number_of_nodes()), dtype=int)
8285

8386
def init_voxels(self, voxels):
@@ -86,7 +89,8 @@ def init_voxels(self, voxels):
8689
8790
Parameters
8891
----------
89-
None
92+
voxels : ArrayLike
93+
Voxel coordinates for each node in the graph.
9094
9195
Returns
9296
-------
@@ -112,21 +116,20 @@ def set_filename(self, filename):
112116
"""
113117
self.filename = filename
114118

115-
def set_nodes(self):
119+
def set_nodes(self, num_nodes):
116120
"""
117-
Adds nodes to the graph. The nodes are assigned indices from 0 to the
118-
total number of voxels in the image.
121+
Adds nodes to the graph. The nodes are assigned indices from 0 to
122+
"num_nodes".
119123
120124
Parameters
121125
----------
122-
None
126+
num_nodes
123127
124128
Returns
125129
-------
126130
None
127131
128132
"""
129-
num_nodes = len(self.voxels)
130133
self.add_nodes_from(np.arange(num_nodes))
131134

132135
# --- Getters ---
@@ -140,9 +143,8 @@ def get_labels(self):
140143
141144
Returns
142145
-------
143-
numpy.ndarray
144-
A 1D array of unique non-zero labels assigned to nodes in the
145-
graph.
146+
Set[int]
147+
Unique non-zero label values assigned to nodes in the graph.
146148
147149
"""
148150
labels = set(np.unique(self.labels))
@@ -202,7 +204,8 @@ def physical_dist(self, i, j):
202204
Returns
203205
-------
204206
float
205-
Euclidea distance between physical coordinates of the given nodes.
207+
Euclidean distance between physical coordinates of the given
208+
nodes.
206209
207210
"""
208211
xyz_i = self.voxels[i] * self.anisotropy
@@ -211,13 +214,13 @@ def physical_dist(self, i, j):
211214

212215
def get_bbox(self, nodes):
213216
"""
214-
Calculates the bounding box that contains the voxel coordinates for a
215-
given collection of nodes.
217+
Calculates the minimal bounding box containing the voxel coordinates
218+
for a given collection of nodes.
216219
217220
Parameters
218221
----------
219222
nodes : Container
220-
A collection of node indices for which to compute the bounding box.
223+
Node indices for which to compute the bounding box.
221224
222225
Returns
223226
-------
@@ -281,8 +284,8 @@ def run_length_from(self, root):
281284
282285
Parameters
283286
----------
284-
graph : networkx.Graph
285-
Graph to be parsed.
287+
root : int
288+
Node contained in connected component to compute run length of.
286289
287290
Returns
288291
-------
@@ -297,14 +300,14 @@ def run_length_from(self, root):
297300

298301
def upd_labels(self, nodes, label):
299302
"""
300-
Updates the label of each node in "nodes" with "label".
303+
Updates the label of the given nodes with a specified label.
301304
302305
Parameters
303306
----------
304307
nodes : List[int]
305308
Nodes to be updated.
306309
label : int
307-
Updated label.
310+
New label of nodes.
308311
309312
Returns
310313
-------
@@ -316,14 +319,16 @@ def upd_labels(self, nodes, label):
316319

317320
def to_zipped_swc(self, zip_writer, color=None):
318321
"""
319-
Writes a graph to an SWC file that is to be stored in a zip.
322+
Writes the graph to an SWC file format, which is then stored in a ZIP
323+
archive.
320324
321325
Parameters
322326
----------
323327
zip_writer : zipfile.ZipFile
324-
...
328+
A ZipFile object that will store the generated SWC file.
325329
color : str, optional
326-
...
330+
A string representing the color (e.g., "[1.0 0.0 0.0]") of the SWC
331+
file. The default is None.
327332
328333
Returns
329334
-------
@@ -336,24 +341,21 @@ def to_zipped_swc(self, zip_writer, color=None):
336341
text_buffer.write("# id, type, z, y, x, r, pid")
337342

338343
# Write entries
339-
n_entries = 0
340344
node_to_idx = dict()
341-
r = 5 if color else 3
345+
r = 6 if color else 3
342346
for i, j in nx.dfs_edges(self):
343347
# Special Case: Root
344348
x, y, z = tuple(self.voxels[i] * self.anisotropy)
345349
if len(node_to_idx) == 0:
346350
parent = -1
347351
node_to_idx[i] = 1
348352
text_buffer.write("\n" + f"1 2 {x} {y} {z} {r} {parent}")
349-
n_entries += 1
350353

351354
# General Case
352-
node = n_entries + 1
355+
node = len(node_to_idx) + 1
353356
parent = node_to_idx[i]
354-
node_to_idx[j] = n_entries + 1
357+
node_to_idx[j] = node
355358
text_buffer.write("\n" + f"{node} 2 {x} {y} {z} {r} {parent}")
356-
n_entries += 1
357359

358360
# Finish
359361
zip_writer.writestr(self.filename, text_buffer.getvalue())

src/segmentation_skeleton_metrics/skeleton_metric.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,6 @@ def adjust_metrics(self, key):
537537
# Update graph
538538
self.graphs[key].remove_nodes_from(nodes)
539539
visited.add(label)
540-
print("# nodes deleted:", len(nodes))
541540

542541
def find_label_intersections(self):
543542
"""

src/segmentation_skeleton_metrics/utils/graph_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def to_graph(self, swc_dict):
8989
graph = SkeletonGraph(anisotropy=self.anisotropy)
9090
graph.init_voxels(swc_dict["voxel"])
9191
graph.set_filename(swc_dict["swc_id"] + ".swc")
92-
graph.set_nodes()
92+
graph.set_nodes(len(swc_dict["id"]))
9393

9494
# Build graph
9595
id_lookup = dict()

0 commit comments

Comments
 (0)