Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

Commit f41fed4

Browse files
Feat: Use MorphIO by default and fallbacks to internal loader in case of failure
1 parent 3fad927 commit f41fed4

4 files changed

Lines changed: 80 additions & 31 deletions

File tree

tests/test_neuron_conversion.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from numpy import testing as npt
99

1010
from tmd.io import conversion as tested
11+
from tmd.io.io import _load_neuron_morphio
1112
from tmd.io.io import load_neuron
12-
from tmd.io.io import load_neuron_from_morphio
1313

1414
_path = os.path.dirname(os.path.abspath(__file__))
1515
DATA_PATH = os.path.join(_path, "data")
@@ -166,11 +166,11 @@ def test_neuron_building_consistency__h5():
166166
path = f"{DATA_PATH}/valid/C010398B-P2.h5"
167167

168168
neuron1 = load_neuron(path)
169-
neuron2 = load_neuron_from_morphio(path)
169+
neuron2 = _load_neuron_morphio(path)
170170

171171
_assert_neurons_equal(neuron1, neuron2)
172172

173-
neuron2 = load_neuron_from_morphio(morphio.Morphology(path))
173+
neuron2 = _load_neuron_morphio(morphio.Morphology(path))
174174

175175
_assert_neurons_equal(neuron1, neuron2)
176176

@@ -180,10 +180,10 @@ def test_neuron_building_consistency__swc():
180180
path = f"{DATA_PATH}/valid/C010398B-P2.CNG.swc"
181181

182182
neuron1 = load_neuron(path)
183-
neuron2 = load_neuron_from_morphio(path)
183+
neuron2 = _load_neuron_morphio(path)
184184

185185
_assert_neurons_equal(neuron1, neuron2)
186186

187-
neuron2 = load_neuron_from_morphio(morphio.Morphology(path))
187+
neuron2 = _load_neuron_morphio(morphio.Morphology(path))
188188

189189
_assert_neurons_equal(neuron1, neuron2)

tmd/Neuron/Neuron.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ def append_tree(self, new_tree, tree_types):
9191
correct list of trees in neuron.
9292
"""
9393
if isinstance(new_tree, Tree.Tree):
94-
if int(np.median(new_tree.t)) in tree_types.keys():
95-
neurite_type = tree_types[int(np.median(new_tree.t))]
94+
tree_type_key = int(np.median(new_tree.t))
95+
if tree_type_key in tree_types.keys():
96+
neurite_type = tree_types[tree_type_key]
9697
else:
9798
neurite_type = "undefined"
9899
getattr(self, neurite_type).append(new_tree)

tmd/io/conversion.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import numpy as np
2121

22+
from tmd.Neuron import Neuron
2223
from tmd.Soma.Soma import Soma
2324
from tmd.Tree import Tree
2425

@@ -128,3 +129,14 @@ def convert_morphio_trees(morphio_neuron):
128129
t=t[tree_beg:tree_end],
129130
p=p[tree_beg:tree_end],
130131
)
132+
133+
134+
def convert_morphio_neuron(morph, tree_types, name=""):
135+
"""Convert a MorphIO morphology into a Neuron object."""
136+
neuron = Neuron.Neuron()
137+
neuron.name = name
138+
neuron.set_soma(convert_morphio_soma(morph.soma))
139+
for tree in convert_morphio_trees(morph):
140+
neuron.append_tree(tree, tree_types)
141+
142+
return neuron

tmd/io/io.py

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
from pathlib import Path
2121

2222
import numpy as _np
23+
from morphio import Morphology
24+
from morphio import Option
2325
from scipy import sparse as sp
2426
from scipy.sparse import csgraph as cs
2527

26-
from tmd.io.conversion import convert_morphio_soma
27-
from tmd.io.conversion import convert_morphio_trees
28+
from tmd.io.conversion import convert_morphio_neuron
2829
from tmd.io.h5 import read_h5
2930
from tmd.io.swc import SWC_DCT
3031
from tmd.io.swc import read_swc
@@ -76,7 +77,7 @@ def redefine_types(user_types=None):
7677
return final_tree_types
7778

7879

79-
def load_neuron(
80+
def _load_neuron_internal(
8081
input_file, line_delimiter="\n", soma_type=None, user_tree_types=None, remove_duplicates=True
8182
):
8283
"""I/O method to load an swc or h5 file into a Neuron object."""
@@ -146,7 +147,7 @@ def load_neuron(
146147
return neuron
147148

148149

149-
def load_neuron_from_morphio(path_or_obj, user_tree_types=None):
150+
def _load_neuron_morphio(path_or_obj, user_tree_types=None):
150151
"""Create Neuron object from morphio object or from path loaded via morphio.
151152
152153
Supported file formats: h5, swc, asc.
@@ -158,32 +159,76 @@ def load_neuron_from_morphio(path_or_obj, user_tree_types=None):
158159
Returns:
159160
neuron (Neuron): tmd Neuron object
160161
"""
161-
from morphio import Morphology # pylint: disable=import-outside-toplevel
162-
163162
tree_types = redefine_types(user_tree_types)
164163

165164
if isinstance(path_or_obj, (str, Path)):
166-
obj = Morphology(path_or_obj)
165+
obj = Morphology(
166+
path_or_obj,
167+
Option.allow_root_bifurcations
168+
| Option.allow_soma_bifurcations
169+
| Option.allow_custom_root_id
170+
| Option.allow_multiple_somata,
171+
)
167172
filename = path_or_obj
168173
else:
169174
obj = path_or_obj
170175
# MorphIO does not support naming of objects yet.
171176
filename = ""
172177

173-
neuron = Neuron.Neuron()
174-
neuron.name = filename
175-
neuron.set_soma(convert_morphio_soma(obj.soma))
176-
for tree in convert_morphio_trees(obj):
177-
neuron.append_tree(tree, tree_types)
178+
return convert_morphio_neuron(obj, tree_types, filename)
178179

179-
return neuron
180180

181+
def load_neuron(
182+
input_file, user_tree_types=None, *, line_delimiter="\n", soma_type=None, remove_duplicates=True
183+
):
184+
"""I/O method to load an 'asc', 'h5' or 'swc' file into a Neuron object.
185+
186+
Args:
187+
input_file (Union[str, morphio.Morphology]):
188+
Filepath or morphio object
181189
182-
def load_population(neurons, user_tree_types=None, name=None, use_morphio=False):
190+
Returns:
191+
neuron (Neuron): tmd Neuron object
192+
193+
"""
194+
ext = os.path.splitext(input_file)[-1][1:]
195+
if ext not in ("h5", "swc", "asc"):
196+
raise ValueError("The file extension must be in ['asc', 'h5', 'swc']")
197+
198+
try:
199+
return _load_neuron_morphio(input_file, user_tree_types=user_tree_types)
200+
except Exception as morphio_exc: # pylint: disable=broad-except
201+
try:
202+
if ext not in ("h5", "swc"):
203+
raise ValueError(
204+
"The internal loader can only read '*.h5' and '*.swc' files."
205+
) from morphio_exc
206+
neuron = _load_neuron_internal(
207+
input_file,
208+
line_delimiter=line_delimiter,
209+
soma_type=soma_type,
210+
user_tree_types=user_tree_types,
211+
remove_duplicates=remove_duplicates,
212+
)
213+
warnings.warn(
214+
f"The file {input_file} was loaded using the internal loader because of a MorphIO "
215+
"failure."
216+
)
217+
return neuron
218+
except Exception as exc:
219+
raise exc from morphio_exc
220+
221+
222+
def load_population(neurons, user_tree_types=None, name=None, use_morphio=None):
183223
"""Load all data of recognised format (swc, h5) into a Population object.
184224
185225
Takes as input a directory or a list of files to load.
186226
"""
227+
if use_morphio is not None:
228+
warnings.warn(
229+
"The 'use_morphio' parameter is deprecated as the internal loader is only used "
230+
"when MorphIO fails."
231+
)
187232
if isinstance(neurons, (list, tuple)):
188233
files = neurons
189234
name = name if name is not None else "Population"
@@ -204,16 +249,7 @@ def load_population(neurons, user_tree_types=None, name=None, use_morphio=False)
204249

205250
for filename in files:
206251
try:
207-
ext = os.path.splitext(filename)[-1][1:].lower()
208-
if not use_morphio:
209-
assert ext in ("h5", "swc")
210-
pop.append_neuron(load_neuron(filename, user_tree_types=user_tree_types))
211-
else:
212-
assert ext in ("h5", "swc", "asc")
213-
pop.append_neuron(
214-
load_neuron_from_morphio(filename, user_tree_types=user_tree_types)
215-
)
216-
252+
pop.append_neuron(load_neuron(filename, user_tree_types=user_tree_types))
217253
except AssertionError as exc:
218254
raise Warning(
219255
"{filename} is not a valid h5, swc or asc file. If asc set use_morphio to True."

0 commit comments

Comments
 (0)