Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple Morphology constructor from io #1120

Merged
merged 4 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions neurom/core/morphology.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from neurom.core.soma import make_soma
from neurom.core.types import NeuriteIter, NeuriteType
from neurom.utils import flatten
from neurom.exceptions import NeuroMError


class Section:
Expand Down Expand Up @@ -538,18 +539,21 @@ def __repr__(self):
class Morphology:
"""Class representing a simple morphology."""

def __init__(self, filename, name=None, process_subtrees=False):
def __init__(self, morphio_morph, name=None, process_subtrees=False):
"""Morphology constructor.

Args:
filename (str|Path): a filename or morphio.{mut}.Morphology object
morphio_morph (morphio.Morphology|morphio.mut.Morphology): a morphio object
name (str): an optional morphology name
process_subtrees (bool): enable mixed tree processing if set to True
"""
self._morphio_morph = morphio.mut.Morphology(filename)
if not isinstance(morphio_morph, (morphio.Morphology, morphio.mut.Morphology)):
raise NeuroMError(
f"Expected morphio Morphology object but got: {morphio_morph}.\n"
f"Use neurom.load_morphology() to load from file."
)

if isinstance(filename, (str, Path, morphio.Morphology)):
self._morphio_morph = self._morphio_morph.as_immutable()
self._morphio_morph = morphio_morph
eleftherioszisis marked this conversation as resolved.
Show resolved Hide resolved

self.name = name if name else 'Morphology'
self.soma = make_soma(self._morphio_morph.soma)
Expand Down
29 changes: 19 additions & 10 deletions neurom/io/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def _get_file(stream, extension):
return temp_file


def load_morphology(morph, reader=None, process_subtrees=False):
def load_morphology(morph, reader=None, mutable=None, process_subtrees=False):
"""Build section trees from a morphology or a h5, swc or asc file.

Args:
Expand Down Expand Up @@ -157,15 +157,24 @@ def load_morphology(morph, reader=None, process_subtrees=False):
)'''), reader='asc')
"""
if isinstance(morph, Morphology):
return Morphology(morph.to_morphio(), process_subtrees=process_subtrees)

if isinstance(morph, (morphio.Morphology, morphio.mut.Morphology)):
return Morphology(morph, process_subtrees=process_subtrees)

if reader:
return Morphology(_get_file(morph, reader), process_subtrees=process_subtrees)

return Morphology(morph, Path(morph).name, process_subtrees=process_subtrees)
name = morph.name
morphio_morph = morph.to_morphio()
elif isinstance(morph, (morphio.Morphology, morphio.mut.Morphology)):
name = "Morphology"
morphio_morph = morph
else:
filepath = _get_file(morph, reader) if reader else morph
name = os.path.basename(filepath)
morphio_morph = morphio.Morphology(filepath)

# None does not modify existing mutability
if mutable is not None:
if mutable and isinstance(morphio_morph, morphio.Morphology):
morphio_morph = morphio_morph.as_mutable()
elif not mutable and isinstance(morphio_morph, morphio.mut.Morphology):
morphio_morph = morphio_morph.as_immutable()

return Morphology(morphio_morph, name=name, process_subtrees=process_subtrees)


def load_morphologies(
Expand Down
22 changes: 5 additions & 17 deletions tests/core/test_neuron.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
from copy import copy, deepcopy
from pathlib import Path

import pytest
import neurom as nm
import numpy as np
import morphio
from neurom.core.morphology import Morphology, graft_morphology, iter_segments
from numpy.testing import assert_array_equal
from neurom.exceptions import NeuroMError

SWC_PATH = Path(__file__).parent.parent / 'data/swc/'

Expand Down Expand Up @@ -65,9 +67,6 @@ def test_load_morphology_from_other_morphologies():
]

assert_array_equal(nm.load_morphology(nm.load_morphology(filename)).points, expected_points)

assert_array_equal(nm.load_morphology(Morphology(filename)).points, expected_points)

assert_array_equal(nm.load_morphology(morphio.Morphology(filename)).points, expected_points)


Expand Down Expand Up @@ -142,17 +141,6 @@ def test_str():
assert 'Section' in str(n.neurites[0].root_node)


def test_mut_nonmut_constructor():
path = SWC_PATH / 'simple.swc'

m = Morphology(path)
assert isinstance(m.to_morphio(), morphio.Morphology)

m = Morphology(str(path))
assert isinstance(m.to_morphio(), morphio.Morphology)

m = Morphology(morphio.Morphology(path))
assert isinstance(m.to_morphio(), morphio.Morphology)

m = Morphology(morphio.mut.Morphology(path))
assert isinstance(m.to_morphio(), morphio.mut.Morphology)
def test_morphology_raises_wrong_argument():
with pytest.raises(NeuroMError, match="Expected morphio Morphology object but got: my-path"):
Morphology("my-path")
59 changes: 59 additions & 0 deletions tests/io/test_io_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from pathlib import Path

import numpy as np
import morphio
from morphio import (
MissingParentError,
RawDataError,
Expand Down Expand Up @@ -183,6 +184,64 @@ def test_load_morphology():
utils.load_morphology(StringIO(morphology_str), reader='swc')


def test_load_morphology__conversions():

morphology_str = u""" 1 1 0 0 0 1. -1
2 3 0 0 0 1. 1
3 3 0 5 0 1. 2
4 3 -5 5 0 0. 3
5 3 6 5 0 0. 3
6 2 0 0 0 1. 1
7 2 0 -4 0 1. 6
8 2 6 -4 0 0. 7
9 2 -5 -4 0 0. 7
"""
filepath = FILENAMES[0]
morphio_mut = morphio.mut.Morphology(filepath)
morphio_immut = morphio_mut.as_immutable()

# default readonly
morph = utils.load_morphology(filepath)
assert isinstance(morph.to_morphio(), morphio.Morphology)

# should be same with mutable=False
morph = utils.load_morphology(filepath, mutable=False)
assert isinstance(morph.to_morphio(), morphio.Morphology)

morph = utils.load_morphology(filepath, mutable=True)
assert isinstance(morph.to_morphio(), morphio.mut.Morphology)

# default mutable=None maintains mutability
morph = utils.load_morphology(morphio_mut)
assert isinstance(morph.to_morphio(), morphio.mut.Morphology)

morph = utils.load_morphology(morphio_mut, mutable=False)
assert isinstance(morph.to_morphio(), morphio.Morphology)

morph = utils.load_morphology(morphio_mut, mutable=True)
assert isinstance(morph.to_morphio(), morphio.mut.Morphology)

# default mutable=None maintains mutability
morph = utils.load_morphology(morphio_immut)
assert isinstance(morph.to_morphio(), morphio.Morphology)

morph = utils.load_morphology(morphio_immut, mutable=False)
assert isinstance(morph.to_morphio(), morphio.Morphology)

morph = utils.load_morphology(morphio_immut, mutable=True)
assert isinstance(morph.to_morphio(), morphio.mut.Morphology)

# default mutable=None is readaonly
morph = utils.load_morphology(morphology_str, reader="swc")
assert isinstance(morph.to_morphio(), morphio.Morphology)

morph = utils.load_morphology(morphology_str, mutable=False, reader="swc")
assert isinstance(morph.to_morphio(), morphio.Morphology)

morph = utils.load_morphology(morphology_str, mutable=True, reader="swc")
assert isinstance(morph.to_morphio(), morphio.mut.Morphology)


def test_morphology_name():
for fn, nn in zip(FILENAMES, NRN_NAMES):
m = utils.load_morphology(fn)
Expand Down
Loading