Skip to content

Commit 55deaa6

Browse files
author
anna-grim
committed
refactor: simplified swc reader
1 parent 11bb022 commit 55deaa6

File tree

2 files changed

+98
-67
lines changed

2 files changed

+98
-67
lines changed

src/segmentation_skeleton_metrics/utils/graph_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def run(self, swc_pointer):
4949
# --- Build Graphs ---
5050
def _build_graphs_from_swcs(self, swc_pointer):
5151
# Initializations
52-
swc_dicts = self.swc_reader.load(swc_pointer)
52+
swc_dicts = self.swc_reader.read(swc_pointer)
5353
pbar = tqdm(total=len(swc_dicts), desc="Build Graphs")
5454

5555
# Main

src/segmentation_skeleton_metrics/utils/swc_util.py

Lines changed: 97 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
"""
32
Created on Wed June 5 16:00:00 2023
43
@@ -41,21 +40,22 @@
4140
class Reader:
4241
"""
4342
Class that reads SWC files stored in a (1) local directory, (2) local ZIP
44-
archive, (3) local directory of ZIP archives or (4) GCS directory of ZIP
45-
archives.
43+
archive, and (3) local directory of ZIP archives.
4644
4745
"""
4846

4947
def __init__(self, anisotropy=(1.0, 1.0, 1.0), selected_ids=None):
5048
"""
51-
Initializes a Reader object that loads swc files.
49+
Initializes a Reader object that reads SWC files.
5250
5351
Parameters
5452
----------
5553
anisotropy : Tuple[float], optional
5654
Image to world scaling factors applied to xyz coordinates to
5755
account for anisotropy of the microscope. The default is
5856
(1.0, 1.0, 1.0).
57+
selected_ids : Set[int], optional
58+
Only SWC files with an swc_id contained in this set are read.
5959
6060
Returns
6161
-------
@@ -65,8 +65,8 @@ def __init__(self, anisotropy=(1.0, 1.0, 1.0), selected_ids=None):
6565
self.anisotropy = anisotropy
6666
self.selected_ids = selected_ids or set()
6767

68-
# --- Load Data ---
69-
def load(self, swc_pointer):
68+
# --- Read Data ---
69+
def read(self, swc_pointer):
7070
"""
7171
Load SWCs files based on the type pointer provided.
7272
@@ -82,80 +82,87 @@ def load(self, swc_pointer):
8282
8383
Returns
8484
-------
85-
dict
86-
Dictionary whose keys are filnames of SWC files and values are the
87-
corresponding graphs.
85+
Deque[dict]
86+
List of dictionaries whose keys and values are the attribute names
87+
and values from the SWC files. Each dictionary contains the
88+
following items:
89+
- "id": unique identifier of each node in an SWC file.
90+
- "pid": parent ID of each node.
91+
- "radius": radius value corresponding to each node.
92+
- "xyz": coordinate corresponding to each node.
93+
- "filename": filename of SWC file
94+
- "swc_id": name of SWC file, minus the ".swc".
8895
8996
"""
9097
# List of paths to SWC files
9198
if isinstance(swc_pointer, list):
92-
return self.load_from_local_paths(swc_pointer)
99+
return self.read_from_paths(swc_pointer)
93100

94101
# Directory containing...
95102
if os.path.isdir(swc_pointer):
96103
# ZIP archives with SWC files
97104
paths = util.list_paths(swc_pointer, extension=".zip")
98105
if len(paths) > 0:
99-
return self.load_from_local_zips(swc_pointer)
106+
return self.read_from_zips(swc_pointer)
100107

101108
# SWC files
102-
paths = util.list_paths(swc_pointer, extension=".swc")
109+
paths = util.read_paths(swc_pointer, extension=".swc")
103110
if len(paths) > 0:
104-
return self.load_from_local_paths(paths)
111+
return self.read_from_paths(paths)
105112

106113
raise Exception("Directory is invalid!")
107114

108115
# Path to...
109116
if isinstance(swc_pointer, str):
110117
# ZIP archive with SWC files
111118
if ".zip" in swc_pointer:
112-
return self.load_from_local_zip(swc_pointer)
119+
return self.read_from_zip(swc_pointer)
113120

114121
# Path to single SWC file
115122
if ".swc" in swc_pointer:
116-
return self.load_from_local_path(swc_pointer)
123+
return self.read_from_path(swc_pointer)
117124

118125
raise Exception("Path is invalid!")
119126

120127
raise Exception("SWC Pointer is inValid!")
121128

122-
def load_from_local_path(self, path):
129+
def read_from_path(self, path):
123130
"""
124-
Reads a single SWC file from local machine.
131+
Reads a single SWC file.
125132
126133
Paramters
127134
---------
128135
path : str
129-
Path to SWC file stored on the local machine.
136+
Path to SWC file.
130137
131138
Returns
132139
-------
133140
dict
134-
Dictionary whose keys are filnames of SWC files and values are the
135-
corresponding graphs.
141+
Dictionary whose keys and values are the attribute names and
142+
values from an SWC file.
136143
137144
"""
138145
content = util.read_txt(path)
139146
filename = os.path.basename(path)
140-
if self.confirm_load(filename):
147+
if self.confirm_read(filename):
141148
return self.parse(content, filename)
142149
else:
143150
return None
144151

145-
def load_from_local_paths(self, paths):
152+
def read_from_paths(self, paths):
146153
"""
147-
Reads list of SWC files stored on the local machine.
154+
Reads SWC files given a list of paths.
148155
149156
Paramters
150157
---------
151-
swc_paths : list
152-
List of paths to SWC files stored on the local machine.
158+
swc_paths : List[str]
159+
Paths to SWC files.
153160
154161
Returns
155162
-------
156-
dict
157-
Dictionary whose keys are filnames of SWC files and values are the
158-
corresponding graphs.
163+
Deque[dict]
164+
Dictionaries whose keys and values are the attribute names and
165+
values from an SWC file.
159166
160167
"""
161168
with ThreadPoolExecutor() as executor:
@@ -164,9 +171,9 @@ def load_from_local_paths(self, paths):
164171
pbar = tqdm(total=len(paths), desc="Read SWCs")
165172
for path in paths:
166173
filename = os.path.basename(path)
167-
if self.confirm_load(filename):
174+
if self.confirm_read(filename):
168175
threads.append(
169-
executor.submit(self.load_from_local_path, path)
176+
executor.submit(self.read_from_path, path)
170177
)
171178

172179
# Store results
@@ -176,7 +183,22 @@ def load_from_local_paths(self, paths):
176183
pbar.update(1)
177184
return swc_dicts
178185

179-
def load_from_local_zips(self, zip_dir):
186+
def read_from_zips(self, zip_dir):
187+
"""
188+
Processes a directory containing ZIP archives with SWC files.
189+
190+
Parameters
191+
----------
192+
zip_dir : str
193+
Path to directory containing ZIP archives with SWC files.
194+
195+
Returns
196+
-------
197+
Deque[dict]
198+
Dictionaries whose keys and values are the attribute names and
199+
values from an SWC file.
200+
201+
"""
180202
# Initializations
181203
zip_names = [f for f in os.listdir(zip_dir) if f.endswith(".zip")]
182204
pbar = tqdm(total=len(zip_names), desc="Read SWCs")
@@ -188,7 +210,7 @@ def load_from_local_zips(self, zip_dir):
188210
for f in zip_names:
189211
zip_path = os.path.join(zip_dir, f)
190212
processes.append(
191-
executor.submit(self.load_from_local_zip, zip_path)
213+
executor.submit(self.read_from_zip, zip_path)
192214
)
193215

194216
# Store results
@@ -198,34 +220,32 @@ def load_from_local_zips(self, zip_dir):
198220
pbar.update(1)
199221
return swc_dicts
200222

201-
def load_from_local_zip(self, zip_path):
223+
def read_from_zip(self, zip_path):
202224
"""
203-
Reads SWC files from zip on the local machine.
225+
Reads SWC files from a ZIP archive.
204226
205227
Paramters
206228
---------
207-
swc_paths : list or dict
208-
If swc files are on local machine, list of paths to swc files where
209-
each file corresponds to a neuron in the prediction. If swc files
210-
are on cloud, then dict with keys "bucket_name" and "path".
229+
zip_path : str
230+
Path to ZIP archive.
211231
212232
Returns
213233
-------
214-
dict
215-
Dictionary whose keys are filnames of SWC files and values are the
216-
corresponding graphs.
234+
Deque[dict]
235+
Dictionaries whose keys and values are the attribute names and
236+
values from an SWC file.
217237
218238
"""
219239
with ThreadPoolExecutor() as executor:
220240
# Assign threads
221241
threads = list()
222242
zipfile = ZipFile(zip_path, "r")
223-
filesnames = [f for f in zipfile.namelist() if f.endswith(".swc")]
224-
for filename in filesnames:
225-
if self.confirm_load(filename):
243+
filenames = [f for f in zipfile.namelist() if f.endswith(".swc")]
244+
for filename in filenames:
245+
if self.confirm_read(filename):
226246
threads.append(
227247
executor.submit(
228-
self.load_from_zipped_file, zipfile, filename
248+
self.read_from_zipped_file, zipfile, filename
229249
)
230250
)
231251

@@ -235,29 +255,44 @@ def load_from_local_zip(self, zip_path):
235255
swc_dicts.append(thread.result())
236256
return swc_dicts
237257

238-
def load_from_zipped_file(self, zipfile, path):
258+
def read_from_zipped_file(self, zipfile, path):
239259
"""
240-
Reads swc file stored at "path" which points to a file in a zip.
260+
Reads an SWC file stored in a ZIP archive.
241261
242262
Parameters
243263
----------
244-
zipfile : ZipFile
245-
Zip containing swc file to be read.
264+
zip_file : ZipFile
265+
ZIP archive containing SWC files.
246266
path : str
247-
Path to swc file to be read.
267+
Path to SWC file.
248268
249269
Returns
250270
-------
251271
dict
252-
Dictionary whose keys are filnames of SWC files and values are the
253-
corresponding graphs.
272+
Dictionary whose keys and values are the attribute names and
273+
values from an SWC file.
254274
255275
"""
256276
content = util.read_zip(zipfile, path).splitlines()
257277
filename = os.path.basename(path)
258278
return self.parse(content, filename)
259279

260-
def confirm_load(self, filename):
280+
def confirm_read(self, filename):
281+
"""
282+
Checks whether the swc_id corresponding to the given filename is
283+
contained in the attribute "selected_ids".
284+
285+
Parameters
286+
----------
287+
filename : str
288+
Name of SWC file to be checked.
289+
290+
Returns
291+
-------
292+
bool
293+
Indication of whether to read SWC file.
294+
295+
"""
261296
if len(self.selected_ids) > 0:
262297
segment_id = util.get_segment_id(filename)
263298
return True if segment_id in self.selected_ids else False
@@ -268,8 +303,6 @@ def confirm_load(self, filename):
268303
def parse(self, content, filename):
269304
"""
270305
Parses an SWC file to extract the content which is stored in a dict.
271-
Note that node_ids from SWC are reindex from 0 to n-1 where n is the
272-
number of nodes in the SWC file.
273306
274307
Parameters
275308
----------
@@ -279,8 +312,8 @@ def parse(self, content, filename):
279312
Returns
280313
-------
281314
dict
282-
Dictionaries whose keys and values are the attribute names
283-
and values from an SWC file.
315+
Dictionary whose keys and values are the attribute names and
316+
values from an SWC file.
284317
285318
"""
286319
# Initializations
@@ -314,11 +347,10 @@ def process_content(self, content):
314347
315348
Returns
316349
-------
317-
List[str]
318-
A list of strings representing the lines of text starting from the
319-
line immediately after the last commented line.
320-
List[int]
321-
Offset used to shift coordinates.
350+
tuple
351+
A tuple containing the following:
352+
- "content" (List[str]): lines from an SWC file after comments.
353+
- "offset" (Tuple[int]): offset used to shift coordinate.
322354
323355
"""
324356
offset = (0, 0, 0)
@@ -331,15 +363,14 @@ def process_content(self, content):
331363

332364
def read_voxel(self, xyz_str, offset):
333365
"""
334-
Reads the coordinates from a string, then transforms them to image
335-
coordinates (if applicable).
366+
Reads a coordinate from a string and converts it to voxel coordinates.
336367
337368
Parameters
338369
----------
339370
xyz_str : str
340-
Coordinate stored in a str.
371+
Coordinate stored as a string.
341372
offset : list[int]
342-
Offset of coordinates in swc file.
373+
Offset of coordinates in SWC file.
343374
344375
Returns
345376
-------

0 commit comments

Comments
 (0)