Skip to content

Commit

Permalink
Add new feature, refactor, and more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
eleftherioszisis committed Apr 16, 2022
1 parent 2357027 commit 8bba54f
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 19 deletions.
8 changes: 6 additions & 2 deletions neurom/features/morphology.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ def shape_factor(morph, neurite_type=NeuriteType.all, projection_plane="xy", use


@feature(shape=())
def length_fraction_above_soma(morph, neurite_type=NeuriteType.all, up="Y"):
def length_fraction_above_soma(morph, neurite_type=NeuriteType.all, up="Y", use_subtrees=False):
"""Returns the length fraction of the segments that have their midpoints higher than the soma.
Args:
Expand All @@ -779,7 +779,11 @@ def length_fraction_above_soma(morph, neurite_type=NeuriteType.all, up="Y"):
raise NeuroMError(f"Unknown axis {axis}. Please choose 'X', 'Y', or 'Z'.")

col = getattr(COLS, axis)
segments = list(iter_segments(morph, neurite_filter=is_type(neurite_type)))

if use_subtrees:
segments = list(iter_segments(morph, neurite_filter=is_type(neurite_type)))
else:
segments = list(iter_segments(morph, section_filter=is_type(neurite_type)))

if not segments:
return np.nan
Expand Down
10 changes: 3 additions & 7 deletions neurom/features/neurite.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,10 @@ def section_term_branch_orders(neurite, section_type=NeuriteType.all):
@feature(shape=(...,))
def section_path_distances(neurite, iterator_type=Section.ipreorder, section_type=NeuriteType.all):
"""Path lengths."""

def path_length(node):
"""Calculate the path length using cached section lengths."""
sections = utils.takeuntil(lambda s: s.id == neurite.root_node.id, node.iupstream())
return sum(n.length for n in sections)

return _map_sections(
path_length, neurite, iterator_type=iterator_type, section_type=section_type
partial(sf.section_path_length, stop_node=neurite.root_node),
neurite,
iterator_type=iterator_type, section_type=section_type
)


Expand Down
17 changes: 14 additions & 3 deletions neurom/features/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,27 @@
from neurom.core.morphology import iter_segments
from neurom.core.morphology import Section
from neurom.morphmath import interval_lengths
from neurom import utils


def section_points(section):
"""Returns the points in the section."""
return section.points[:, COLS.XYZ]


def section_path_length(section):
"""Path length from section to root."""
return sum(s.length for s in section.iupstream())
def section_path_length(section, stop_node=None):
"""Path length from section to root.
Args:
section: Section object.
stop_node: Node to stop the upstream traversal. If None, it stops when no parent is found.
"""
it = section.iupstream()

if stop_node:
it = utils.takeuntil(lambda s: s.id == stop_node.id, it)

return sum(map(section_length, it))


def section_length(section):
Expand Down
138 changes: 131 additions & 7 deletions tests/test_mixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,59 @@
from neurom.features import _POPULATION_FEATURES, _MORPHOLOGY_FEATURES, _NEURITE_FEATURES
import collections.abc

from neurom.core import morphology as tested
from neurom.core.types import tree_type_checker as is_type

import neurom.core.morphology
import neurom.features.neurite


@pytest.fixture
def mixed_morph():
"""
(1, 4, 1)
|
S7:B |
|
(1, 4, -1)-----(1, 4, 0) (2, 4, 0) (3, 3, 1)
S8:B | | |
| S10:A | S12:A |
| | S11:A |
S6:B | (2, 3, 0)-----(3, 3, 0)
| / |
| S9:A / S13:A |
| / |
(1, 2, 0) (3, 3, -1)
/
S5:B /
/ Axon on basal dendrite
(-3, 0, 1) (-2, 1, 0) (0, 1, 0)
| |
S2 | S4 |
| S1 | S0
(-3, 0, 0)-----(-2, 0, 0)-----(-1, 0, 0) (0, 0, 0) Soma
|
S3 | Basal Dendrite
|
(-3, 0, -1) (0, -1, 0)
|
S14 |
| S17
Apical Dendrite (0, -2, 0)-----(1, -2, 0)
|
S15 |
S17 | S16
(0, -3, -1)-----(0, -3, 0)-----(0, -3, 1)
basal_dendrite: homogeneous
section ids: [0, 1, 2, 3, 4]
axon_on_basal_dendrite: heterogeneous
apical_dendrite: homogeneous
section_ids:
- basal: [5, 6, 7, 8]
- axon : [9, 10, 11, 12, 13]
apical_dendrite: homogeneous:
section_ids: [14, 15, 16, 17, 18]
"""
return neurom.load_morphology(
"""
Expand Down Expand Up @@ -73,11 +117,11 @@ def test_homogeneous_subtrees(mixed_morph):

basal, axon_on_basal, apical = mixed_morph.neurites

assert tested._homogeneous_subtrees(basal) == [basal]
assert neurom.core.morphology._homogeneous_subtrees(basal) == [basal]

sections = list(axon_on_basal.iter_sections())

subtrees = tested._homogeneous_subtrees(axon_on_basal)
subtrees = neurom.core.morphology._homogeneous_subtrees(axon_on_basal)

assert subtrees[0].root_node.id == axon_on_basal.root_node.id
assert subtrees[0].root_node.type == NeuriteType.basal_dendrite
Expand All @@ -88,14 +132,14 @@ def test_homogeneous_subtrees(mixed_morph):

def test_iter_neurites__heterogeneous(mixed_morph):

subtrees = list(tested.iter_neurites(mixed_morph, use_subtrees=False))
subtrees = list(neurom.core.morphology.iter_neurites(mixed_morph, use_subtrees=False))

assert len(subtrees) == 3
assert subtrees[0].type == NeuriteType.basal_dendrite
assert subtrees[1].type == NeuriteType.basal_dendrite
assert subtrees[2].type == NeuriteType.apical_dendrite

subtrees = list(tested.iter_neurites(mixed_morph, use_subtrees=True))
subtrees = list(neurom.core.morphology.iter_neurites(mixed_morph, use_subtrees=True))

assert len(subtrees) == 4
assert subtrees[0].type == NeuriteType.basal_dendrite
Expand All @@ -104,6 +148,74 @@ def test_iter_neurites__heterogeneous(mixed_morph):
assert subtrees[3].type == NeuriteType.apical_dendrite


def test_core_iter_sections__heterogeneous(mixed_morph):

def assert_sections(neurite, section_type, expected_section_ids):

it = neurom.core.morphology.iter_sections(neurite, section_filter=is_type(section_type))
assert [s.id for s in it] == expected_section_ids

basal, axon_on_basal, apical = mixed_morph.neurites

assert_sections(basal, NeuriteType.all, [0, 1, 2, 3, 4])
assert_sections(basal, NeuriteType.basal_dendrite, [0, 1, 2, 3, 4])
assert_sections(basal, NeuriteType.axon, [])

assert_sections(axon_on_basal, NeuriteType.all, [5, 6, 7, 8, 9, 10, 11, 12, 13])
assert_sections(axon_on_basal, NeuriteType.basal_dendrite, [5, 6, 7, 8])
assert_sections(axon_on_basal, NeuriteType.axon, [9, 10, 11, 12, 13])

assert_sections(apical, NeuriteType.all, [14, 15, 16, 17, 18])
assert_sections(apical, NeuriteType.apical_dendrite, [14, 15, 16, 17, 18])


def test_features_neurite_map_sections__heterogeneous(mixed_morph):

def assert_sections(neurite, section_type, iterator_type, expected_section_ids):
function = lambda section: section.id
section_ids = neurom.features.neurite._map_sections(
function, neurite, iterator_type=iterator_type, section_type=section_type
)
assert section_ids == expected_section_ids

basal, axon_on_basal, apical = mixed_morph.neurites

# homogeneous tree, no difference between all and basal_dendrite types.
assert_sections(
basal, NeuriteType.all, neurom.core.morphology.Section.ibifurcation_point,
[0, 1],
)
assert_sections(
basal, NeuriteType.basal_dendrite, neurom.core.morphology.Section.ibifurcation_point,
[0, 1],
)
# heterogeneous tree, forks cannot be heterogeneous if a type other than all is specified
# Section with id 5 is the transition section, which has a basal and axon children sections
assert_sections(
axon_on_basal, NeuriteType.all, neurom.core.morphology.Section.ibifurcation_point,
[5, 6, 9, 11],
)
assert_sections(
axon_on_basal, NeuriteType.basal_dendrite,
neurom.core.morphology.Section.ibifurcation_point,
[6],
)
assert_sections(
axon_on_basal, NeuriteType.axon,
neurom.core.morphology.Section.ibifurcation_point,
[9, 11],
)
# homogeneous tree, no difference between all and basal_dendrite types.
assert_sections(
apical, NeuriteType.all, neurom.core.morphology.Section.ibifurcation_point,
[14, 15],
)
assert_sections(
apical, NeuriteType.apical_dendrite, neurom.core.morphology.Section.ibifurcation_point,
[14, 15],
)


@pytest.fixture
def population(mixed_morph):
return Population([mixed_morph, mixed_morph])
Expand Down Expand Up @@ -778,7 +890,19 @@ def _morphology_features(mode):
"expected_wout_subtrees": 0.25,
"expected_with_subtrees": 0.25,
},
]
],
"length_fraction_above_soma": [
{
"kwargs": {"neurite_type": NeuriteType.all},
"expected_wout_subtrees": 0.567898,
"expected_with_subtrees": 0.567898,
},
{
"kwargs": {"neurite_type": NeuriteType.basal_dendrite},
"expected_wout_subtrees": 0.61591,
"expected_with_subtrees": 0.74729,
},
],
}

features_not_tested = set(_MORPHOLOGY_FEATURES) - set(features.keys())
Expand Down

0 comments on commit 8bba54f

Please sign in to comment.