Features¶
A tool for analysing of morphologies. It allows to extract various information about morphologies.
For example if you need to know the segment lengths of a morphology then you need to call
segment_lengths
feature. The complete list of available features is spread among pages
neurom.features.neurite
, neurom.features.morphology
,
neurom.features.population
.
Features are spread among neurite
, morphology
, population
modules to emphasize their
expected input. Features from neurite
expect a neurite as their input. So calling it with
a morphology input will fail. morphology
expects a morphology only. population
expects a
population only.
This restriction can be bypassed if you call a feature from neurite
via the features
mechanism features.get
. However the mechanism does not allow to appply population
features to anything other than a morphology population, and morphology
features can be applied
only to a morphology or a morphology population.
An example for neurite
:
from neurom import load_morphology, features
from neurom.features.neurite import max_radial_distance
m = load_morphology('path/to/morphology')
# valid input
max_radial_distance(m.neurites[0])
# invalid input
max_radial_distance(m)
# valid input
features.get('max_radial_distance', m)
The features mechanism assumes that a neurite feature must be summed if it returns a number, and
concatenated if it returns a list. Other types of returns are invalid. For example lets take
a feature number_of_segments
of neurite
. It returns a number of segments in a neurite.
Calling it on a morphology will return a sum of number_of_segments
of all the morphology’s neurites.
Calling it on a morphology population will return a list of number_of_segments
of each morphology
within the population.
from neurom import load_morphology, features
m = load_morphology('path/to/morphology')
# a single number
features.get('number_of_segments', m.neurites[0])
# a single number that is a sum for all `m.neurites`.
features.get('number_of_segments', m)
pop = load_morphology('path/to/morphology population')
# a list of numbers
features.get('number_of_segments', pop)
if a list is returned then the feature results are concatenated.
from neurom import load_morphology, features
m = load_morphology('path/to/morphology')
# a list of lengths in a neurite
features.get('section_lengths', m.neurites[0])
# a flat list of lengths in a morphology, no separation among neurites
features.get('section_lengths', m)
pop = load_morphology('path/to/morphology population')
# a flat list of lengths in a population, no separation among morphologies
features.get('section_lengths', pop)
In case such implicit behaviour does not work a feature can be rewritten for each input separately.
For example, a feature max_radial_distance
that requires a max operation instead of implicit
sum. Its definition in neurite
:
@feature(shape=())
def max_radial_distance(neurite):
"""Get the maximum radial distances of the termination sections."""
term_radial_distances = section_term_radial_distances(neurite)
return max(term_radial_distances) if term_radial_distances else 0.
In order to make it work for a morphology, it is redefined in morphology
:
@feature(shape=())
def max_radial_distance(morph, neurite_type=NeuriteType.all):
"""Get the maximum radial distances of the termination sections."""
term_radial_distances = _map_neurites(nf.max_radial_distance, morph, neurite_type)
return max(term_radial_distances) if term_radial_distances else 0.0
Another feature that requires redefining is sholl_frequency
. This feature applies different
logic for a morphology and a morphology population. That is why it is defined in morphology
:
@feature(shape=(...,))
def sholl_frequency(morph, neurite_type=NeuriteType.all, step_size=10, bins=None):
"""Perform Sholl frequency calculations on a morph.
Args:
morph(Morphology): a morphology
neurite_type(NeuriteType): which neurites to operate on
step_size(float): step size between Sholl radii
bins(iterable of floats): custom binning to use for the Sholl radii. If None, it uses
intervals of step_size between min and max radii of ``morphologies``.
Note:
Given a morphology, the soma center is used for the concentric circles,
which range from the soma radii, and the maximum radial distance
in steps of `step_size`. Each segment of the morphology is tested, so a neurite that
bends back on itself, and crosses the same Sholl radius will get counted as
having crossed multiple times.
If a `neurite_type` is specified and there are no trees corresponding to it, an empty
list will be returned.
"""
_assert_soma_center(morph)
neurite_filter = is_type(neurite_type)
if bins is None:
min_soma_edge = morph.soma.radius
max_radius_per_neurite = [
np.max(np.linalg.norm(n.points[:, COLS.XYZ] - morph.soma.center, axis=1))
for n in morph.neurites if neurite_filter(n)
]
if not max_radius_per_neurite:
return []
bins = np.arange(min_soma_edge, min_soma_edge + max(max_radius_per_neurite), step_size)
return sholl_crossings(morph, neurite_type, morph.soma.center, bins)
and redefined in population
@feature(shape=(...,))
def sholl_frequency(morphs, neurite_type=NeuriteType.all, step_size=10, bins=None):
"""Perform Sholl frequency calculations on a population of morphs.
Args:
morphs(list|Population): list of morphologies or morphology population
neurite_type(NeuriteType): which neurites to operate on
step_size(float): step size between Sholl radii
bins(iterable of floats): custom binning to use for the Sholl radii. If None, it uses
intervals of step_size between min and max radii of ``morphs``.
Note:
Given a population, the concentric circles range from the smallest soma radius to the
largest radial neurite distance in steps of `step_size`. Each segment of the morphology is
tested, so a neurite that bends back on itself, and crosses the same Sholl radius will
get counted as having crossed multiple times.
"""
neurite_filter = is_type(neurite_type)
if bins is None:
min_soma_edge = min(n.soma.radius for n in morphs)
max_radii = max(np.max(np.linalg.norm(n.points[:, COLS.XYZ], axis=1))
for m in morphs
for n in m.neurites if neurite_filter(n))
bins = np.arange(min_soma_edge, min_soma_edge + max_radii, step_size)
def _sholl_crossings(morph):
_assert_soma_center(morph)
return sholl_crossings(morph, neurite_type, morph.soma.center, bins)
return np.array([_sholl_crossings(m) for m in morphs]).sum(axis=0)