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

Commit aeeb1f2

Browse files
authored
Support morphology containers (#274)
* MorphIO supports "containers"; this uses a file system layout or an h5 file containing many morphologies
1 parent 7ec05a6 commit aeeb1f2

File tree

6 files changed

+54
-9
lines changed

6 files changed

+54
-9
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Improvements
2020
- Update simulation validation to conform to the SONATA spec
2121

2222
- ``synapse_replay.source`` and ``.dat`` spike input files are no longer supported
23+
- Support morphology containers
2324

2425

2526
Version v3.0.1

bluepysnap/morph.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from pathlib import Path
2121

2222
import morph_tool.transform as transformations
23+
import morphio
2324
import numpy as np
24-
from morphio.mut import Morphology
2525

2626
from bluepysnap.exceptions import BluepySnapError
2727
from bluepysnap.sonata_constants import Node
@@ -51,8 +51,8 @@ def __init__(self, morph_dir, population, alternate_morphologies=None):
5151
self._alternate_morphologies = alternate_morphologies or {}
5252
self._population = population
5353

54-
def get_morphology_dir(self, extension):
55-
"""Return morphology directory based on a given extension."""
54+
def _get_morphology_base(self, extension):
55+
"""Get morphology base path; this will be a directory unless it's a morphology container."""
5656
if extension == "swc":
5757
if not self._morph_dir:
5858
raise BluepySnapError("'morphologies_dir' is not defined in config")
@@ -68,16 +68,33 @@ def get_morphology_dir(self, extension):
6868

6969
return morph_dir
7070

71+
def get_morphology_dir(self, extension="swc"):
72+
"""Return morphology directory based on a given extension."""
73+
morph_dir = self._get_morphology_base(extension)
74+
75+
if extension == "h5" and Path(morph_dir).is_file():
76+
raise BluepySnapError(
77+
f"'{morph_dir}' is a morphology container, so a directory does not exist"
78+
)
79+
80+
return morph_dir
81+
82+
def get_name(self, node_id):
83+
"""Get the morphology name for a `node_id`."""
84+
if not is_node_id(node_id):
85+
raise BluepySnapError("node_id must be a int or a CircuitNodeId")
86+
87+
name = self._population.get(node_id, Node.MORPHOLOGY)
88+
return name
89+
7190
def get_filepath(self, node_id, extension="swc"):
7291
"""Return path to SWC morphology file corresponding to `node_id`.
7392
7493
Args:
7594
node_id (int/CircuitNodeId): could be a int or CircuitNodeId.
7695
extension (str): expected filetype extension of the morph file.
7796
"""
78-
if not is_node_id(node_id):
79-
raise BluepySnapError("node_id must be a int or a CircuitNodeId")
80-
name = self._population.get(node_id, Node.MORPHOLOGY)
97+
name = self.get_name(node_id)
8198

8299
return Path(self.get_morphology_dir(extension), f"{name}.{extension}")
83100

@@ -90,11 +107,19 @@ def get(self, node_id, transform=False, extension="swc"):
90107
according to `node_id` position in the circuit.
91108
extension (str): expected filetype extension of the morph file.
92109
"""
93-
filepath = self.get_filepath(node_id, extension=extension)
94-
result = Morphology(filepath)
110+
collection = morphio.Collection(
111+
self._get_morphology_base(extension),
112+
[
113+
f".{extension}",
114+
],
115+
)
116+
name = self.get_name(node_id)
117+
result = collection.load(name, mutable=True)
118+
95119
if transform:
96120
T = np.eye(4)
97121
T[:3, :3] = self._population.orientations(node_id) # rotations
98122
T[:3, 3] = self._population.positions(node_id).values # translations
99123
transformations.transform(result, T)
124+
100125
return result.as_immutable()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def __init__(self, *args, **kwargs):
4949
"importlib_resources>=5.0.0",
5050
"jsonschema>=4.0.0,<5.0.0",
5151
"libsonata>=0.1.24,<1.0.0",
52-
"morphio>=3.0.0,<4.0.0",
52+
"morphio>=3.3.5,<4.0.0",
5353
"morph-tool>=2.4.3,<3.0.0",
5454
"numpy>=1.8",
5555
"pandas>=1.0.0",
5 KB
Binary file not shown.

tests/data/morphologies/morph-B.h5

10.1 KB
Binary file not shown.

tests/test_morph.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ def test_get_morphology(self):
126126
with pytest.raises(BluepySnapError, match="node_id must be a int or a CircuitNodeId"):
127127
self.test_obj.get([0, 1])
128128

129+
def test_get_alternate_morphology_collection(self):
130+
morph_path = TEST_DATA_DIR / "morphologies/container-morphs.h5"
131+
alternate_morphs = {"h5v1": str(morph_path)}
132+
133+
test_obj = test_module.MorphHelper(
134+
None, self.nodes, alternate_morphologies=alternate_morphs
135+
)
136+
137+
node_id = 0
138+
139+
with pytest.raises(BluepySnapError):
140+
test_obj.get_morphology_dir(extension="h5")
141+
142+
with pytest.raises(BluepySnapError):
143+
test_obj.get_filepath(node_id, extension="h5")
144+
145+
morph_A = test_obj.get(node_id, extension="h5")
146+
assert len(morph_A.points) == 13
147+
129148
def test_get_alternate_morphology(self):
130149
alternate_morphs = {"h5v1": str(self.morph_path)}
131150
test_obj = test_module.MorphHelper(

0 commit comments

Comments
 (0)