From 3fb37a7e09a5db1a8960313aa4ac7b23c2dacd2f Mon Sep 17 00:00:00 2001 From: Mike Gevaert Date: Wed, 30 Oct 2024 13:35:17 +0100 Subject: [PATCH 1/3] Support morphology containers * when the `alternative-morphs` has an `h5` version use the `morphio.Container`, to both load from a directory of from a file --- CHANGELOG.rst | 1 + bluepysnap/morph.py | 40 ++++++++++++++++---- tests/data/morphologies/container-morphs.h5 | Bin 0 -> 5120 bytes tests/data/morphologies/morph-B.h5 | Bin 0 -> 10336 bytes tests/test_morph.py | 19 ++++++++++ 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/data/morphologies/container-morphs.h5 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 96fdd2b1..bd6749c3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,6 +20,7 @@ Improvements - Update simulation validation to conform to the SONATA spec - ``synapse_replay.source`` and ``.dat`` spike input files are no longer supported +- Support morphology containers Version v3.0.1 diff --git a/bluepysnap/morph.py b/bluepysnap/morph.py index 0f200c90..738241a4 100644 --- a/bluepysnap/morph.py +++ b/bluepysnap/morph.py @@ -20,8 +20,8 @@ from pathlib import Path import morph_tool.transform as transformations +import morphio import numpy as np -from morphio.mut import Morphology from bluepysnap.exceptions import BluepySnapError from bluepysnap.sonata_constants import Node @@ -51,8 +51,8 @@ def __init__(self, morph_dir, population, alternate_morphologies=None): self._alternate_morphologies = alternate_morphologies or {} self._population = population - def get_morphology_dir(self, extension): - """Return morphology directory based on a given extension.""" + def _get_morphology_base(self, extension): + """Get morphology base path; this will be a directory unless it's a morphology container.""" if extension == "swc": if not self._morph_dir: raise BluepySnapError("'morphologies_dir' is not defined in config") @@ -68,6 +68,25 @@ def get_morphology_dir(self, extension): return morph_dir + def get_morphology_dir(self, extension="swc"): + """Return morphology directory based on a given extension.""" + morph_dir = self._get_morphology_base(extension) + + if extension == "h5" and Path(morph_dir).is_file(): + raise BluepySnapError( + f"'{morph_dir}' is a morphology container, so a directory does not exist" + ) + + return morph_dir + + def get_name(self, node_id): + """Get the morphology name for a `node_id`.""" + if not is_node_id(node_id): + raise BluepySnapError("node_id must be a int or a CircuitNodeId") + + name = self._population.get(node_id, Node.MORPHOLOGY) + return name + def get_filepath(self, node_id, extension="swc"): """Return path to SWC morphology file corresponding to `node_id`. @@ -75,9 +94,7 @@ def get_filepath(self, node_id, extension="swc"): node_id (int/CircuitNodeId): could be a int or CircuitNodeId. extension (str): expected filetype extension of the morph file. """ - if not is_node_id(node_id): - raise BluepySnapError("node_id must be a int or a CircuitNodeId") - name = self._population.get(node_id, Node.MORPHOLOGY) + name = self.get_name(node_id) return Path(self.get_morphology_dir(extension), f"{name}.{extension}") @@ -90,11 +107,18 @@ def get(self, node_id, transform=False, extension="swc"): according to `node_id` position in the circuit. extension (str): expected filetype extension of the morph file. """ - filepath = self.get_filepath(node_id, extension=extension) - result = Morphology(filepath) + if extension == "h5": + collection = morphio.Collection(self._get_morphology_base(extension)) + name = self.get_name(node_id) + result = collection.load(name, mutable=True) + else: + filepath = self.get_filepath(node_id, extension=extension) + result = morphio.mut.Morphology(filepath) + if transform: T = np.eye(4) T[:3, :3] = self._population.orientations(node_id) # rotations T[:3, 3] = self._population.positions(node_id).values # translations transformations.transform(result, T) + return result.as_immutable() diff --git a/tests/data/morphologies/container-morphs.h5 b/tests/data/morphologies/container-morphs.h5 new file mode 100644 index 0000000000000000000000000000000000000000..967c33fc0204a0e98be37df8ee8ec45e8f2e0a9e GIT binary patch literal 5120 zcmeHJ&ubG=5T4y_lg+Y18dE|8>NZ$VL{QR;peE5;0v0M>1Sv>J6AdIy$&XqOVlF-C zNqP_xJOq33pgk5mn8uSL>A_RI^dHch2d{M|`(E6)ZWFK=Qptp6=F5BY?ab`Uet8<7 znLVZjHAPi{+XWu@%^=J0-t_E``S{Fj*@#bt?pv?PzzOh663+t|;o&~Bz4gRf=iF7V z7vQsECQP$DO$KBe_Yo1N;9j2sP(THp7FSY*VkW-?UM%0dsR1&Q5TDBj$l}iB3(F7A z$MzXDjI!Y2vpeMjN|G`2&l{UXxyFdqwVcl^m5S@EXBUo20#C72C?`whLTZz0Kyw|Z z@6}wYlvqrZ5?HlqY_be>+^gsAZ|#yMt<$9#B|?KZmwuw%s!(wK3oGmU@> zfhU+ViX&A#st=?Qi7kS`%7l8SgntWM|O#_2mr!ZFi#qP>E2Fwd%VF;dDCP#%tfdwDC@B zPQc-CSirUFgn*Gsz&owhRpRG-wQ5zs$Zx}~E7tuKlkt4ydoSWS?BVx1@(URE3mCU< zi~A=8jQt51>k}}}N5DA0HvV~LAws+W^=R7xkjKj#vtSHwSuw6#am9wCh$~Gn4P(O1 zrU{9gFnkcP(FN1aZtKmcxK1xH^IbTOUdD?oh!_7=on$es8I~BsL*-Kb(EjZNoCWZW z^NXabDqo@lq2mX&Et$$@@1+yDO!g6=x-+>!WtPxbL);V8+sP#9nB0+cTOvPt+!F!C znWTHsxsBCsZ3sGk*Mmf8b{${Q*!MOar>4jb(D4`UU9Fc^g-ixEo4?d?-6S#iOzMnu zfL*Nic&pcJ4x{4>M_9)Ln`@u?`{;)HztO<|FM^&A_22scpugvFc(?vez*#ttoc{NB NXv4b=C-9$4;1`YPS3m#& literal 0 HcmV?d00001 diff --git a/tests/data/morphologies/morph-B.h5 b/tests/data/morphologies/morph-B.h5 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6b65c705e4c8b009edb93cdc413e377bf4455644 100644 GIT binary patch literal 10336 zcmeHLF>e!54E7~$iJ~A<1S;tOC#FtSq$~`Dq-qOADzpLy1|$;NiyEnEqU2i9i7ZIv zC!`Y+3u9LXAc%z_m0*JZz|IQxdv>{ZC1pTVQROGyvp?JC^ZU;4E|(XJ^Ec0?C(}S9 zm4Xq-8davBJjzqnB3S%oRy>xwEMF(A;z1I~cZuy;&j0OOojptArJalYZs4 z7V{D5!I`Btcg!kO4~;T!(0#V^BEU~t|Daj+ z_0!Qo5vR-kx4se@gtj5<3B0j;VNi7@%O|O zzbE#&koa>z`s*|Io75})xFUvsl;ix~iuZR0N+z?1`!a*oZwIY*t+8pMqxZ%0Y(=t} zMUs{D3@mb2c@uz&UPLU=UC#iRr4e~zDfMta0y@2(uHtO|Y zGlU%0N7tak=CaI6-I8Bq4&%yvv&6=iazy&;t9JmMA{qC(Z;FJg)vCeIzkf0Kpugnc zLZRT`y>8CIn99Ki{r(NgFZuSmT?b?S817$>#*!LQi~t~sM|L)XQC*2*^ha?ghD(S$J+K3H!o8k_ zl$$VoaIt?3rpV*`y^`y<{YJm{F5;O9IvvL(9`fwF%sTn}h>ofbWk4BF29$vzWx#&d zgMQZ!>HX2NlmTTx8Bhk4f&an4t-0kTj}p@#3pk99lKwxSG1m$zVX)>u+41i*TFv#_ v%l>v@x-gx$?!&ov?od@l8Bhk40cAiLPzIC%Wk4BF29yD1Kp9X5hKzw<{oW3C literal 0 HcmV?d00001 diff --git a/tests/test_morph.py b/tests/test_morph.py index 5ede964e..6182ef5b 100644 --- a/tests/test_morph.py +++ b/tests/test_morph.py @@ -126,6 +126,25 @@ def test_get_morphology(self): with pytest.raises(BluepySnapError, match="node_id must be a int or a CircuitNodeId"): self.test_obj.get([0, 1]) + def test_get_alternate_morphology_collection(self): + morph_path = TEST_DATA_DIR / "morphologies/container-morphs.h5" + alternate_morphs = {"h5v1": str(morph_path)} + + test_obj = test_module.MorphHelper( + None, self.nodes, alternate_morphologies=alternate_morphs + ) + + node_id = 0 + + with pytest.raises(BluepySnapError): + test_obj.get_morphology_dir(extension="h5") + + with pytest.raises(BluepySnapError): + test_obj.get_filepath(node_id, extension="h5") + + morph_A = test_obj.get(node_id, extension="h5") + assert len(morph_A.points) == 13 + def test_get_alternate_morphology(self): alternate_morphs = {"h5v1": str(self.morph_path)} test_obj = test_module.MorphHelper( From ba91a88d2d297005650c2927f0afd9d05a5998be Mon Sep 17 00:00:00 2001 From: Mike Gevaert Date: Thu, 31 Oct 2024 13:35:38 +0100 Subject: [PATCH 2/3] switch to using morphio.Collections for everything --- bluepysnap/morph.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bluepysnap/morph.py b/bluepysnap/morph.py index 738241a4..7d3864a9 100644 --- a/bluepysnap/morph.py +++ b/bluepysnap/morph.py @@ -107,13 +107,14 @@ def get(self, node_id, transform=False, extension="swc"): according to `node_id` position in the circuit. extension (str): expected filetype extension of the morph file. """ - if extension == "h5": - collection = morphio.Collection(self._get_morphology_base(extension)) - name = self.get_name(node_id) - result = collection.load(name, mutable=True) - else: - filepath = self.get_filepath(node_id, extension=extension) - result = morphio.mut.Morphology(filepath) + collection = morphio.Collection( + self._get_morphology_base(extension), + [ + f".{extension}", + ], + ) + name = self.get_name(node_id) + result = collection.load(name, mutable=True) if transform: T = np.eye(4) From 22c7a7d47cd70689639bc335392416fba98fa840 Mon Sep 17 00:00:00 2001 From: Mike Gevaert Date: Thu, 31 Oct 2024 13:36:33 +0100 Subject: [PATCH 3/3] make sure morphio version supports Collections --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b29f1c19..e658074d 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def __init__(self, *args, **kwargs): "importlib_resources>=5.0.0", "jsonschema>=4.0.0,<5.0.0", "libsonata>=0.1.24,<1.0.0", - "morphio>=3.0.0,<4.0.0", + "morphio>=3.3.5,<4.0.0", "morph-tool>=2.4.3,<3.0.0", "numpy>=1.8", "pandas>=1.0.0",