Source code for emodelrunner.factsheets.morphology_features

"""Computation of morphology features."""

# Copyright 2020-2022 Blue Brain Project / EPFL

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging

import numpy as np
import neurom as nm
from neurom.core.morphology import iter_neurites
from neurom.core.types import tree_type_checker as is_type
from neurom.features.neurite import segment_radii, segment_lengths

logger = logging.getLogger(__name__)


[docs] class MorphologyFeature: """Morphology feature representation. Attributes: name (str): name of the feature value (float or list): value of the feature unit (str): unit of the feature """ def __init__(self): """Sets initial attribute values.""" self.name = None self.value = None self.unit = None
[docs] @staticmethod def replace_empty_value(value): """Replaces the empty value with 0 or [0]. Attrs: value (float or list): the value to be replaced if empty Returns: float/int or non-empty list """ if value is None: value = 0 elif hasattr(value, "len") and len(value) == 0: value = [0] return value
[docs] def to_dict(self): """Returns a dictionary from the fields.""" return self.__dict__
def _seg_lengths(neurite): """Syntactic sugar to retrieve segment_lengths as np.array.""" return np.array(segment_lengths(neurite)) def _seg_radii(neurite): """Syntactic sugar to retrieve segment_radii as np.array.""" return np.array(segment_radii(neurite))
[docs] class AverageDiameter(MorphologyFeature): """Average diameter (weighted by section length) feature for a neurite type.""" def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"average diameter, {neurite_name}" self.unit = "\u00b5m" neurites = list(iter_neurites(morphology, filt=is_type(neurite_type))) neurite_weighted_radii = [_seg_radii(n) * _seg_lengths(n) for n in neurites] neurite_seg_lengths = [_seg_lengths(n) for n in neurites] neurite_weighted_radii = np.concatenate(neurite_weighted_radii, axis=0) neurite_seg_lengths = np.concatenate(neurite_seg_lengths, axis=0) avg_radius = np.sum(neurite_weighted_radii) / np.sum(neurite_seg_lengths) avg_diameter = avg_radius * 2 self.value = avg_diameter
[docs] class TotalLength(MorphologyFeature): """Total length feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"total length, {neurite_name}" self.unit = "\u00b5m" feature_value = nm.get("total_length", morphology, neurite_type=neurite_type) self.value = self.replace_empty_value(feature_value)
[docs] class TotalHeight(MorphologyFeature): """Total height feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"total height, {neurite_name}" self.unit = "\u00b5m" feature_value = nm.get("total_height", morphology, neurite_type=neurite_type) self.value = self.replace_empty_value(feature_value)
[docs] class TotalWidth(MorphologyFeature): """Total width feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"total width, {neurite_name}" self.unit = "\u00b5m" feature_value = nm.get("total_width", morphology, neurite_type=neurite_type) self.value = self.replace_empty_value(feature_value)
[docs] class TotalDepth(MorphologyFeature): """Total depth feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"total depth, {neurite_name}" self.unit = "\u00b5m" feature_value = nm.get("total_depth", morphology, neurite_type=neurite_type) self.value = self.replace_empty_value(feature_value)
[docs] class TotalArea(MorphologyFeature): """Total area feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"total area, {neurite_name}" self.unit = "\u00b5m\u00b2" feature_value = nm.get("total_area", morphology, neurite_type=neurite_type) self.value = self.replace_empty_value(feature_value)
[docs] class TotalVolume(MorphologyFeature): """Total volume feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"total volume, {neurite_name}" self.unit = "\u00b5m\u00b3" feature_value = nm.get("total_volume", morphology, neurite_type=neurite_type) self.value = self.replace_empty_value(feature_value)
[docs] class NumberOfSections(MorphologyFeature): """Number of sections feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"number of sections, {neurite_name}" self.unit = "" feature_value = nm.get( "number_of_sections", morphology, neurite_type=neurite_type ) self.value = self.replace_empty_value(feature_value)
[docs] class NumberOfSegments(MorphologyFeature): """Number of segments feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"number of segments, {neurite_name}" self.unit = "" feature_values = nm.get( "number_of_segments", morphology, neurite_type=neurite_type ) self.value = self.replace_empty_value(feature_values)
[docs] class MeanNeuriteVolumes(MorphologyFeature): """Mean neurite volume feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"mean volume, {neurite_name}" self.unit = "\u00b5m\u00b3" feature_values = nm.get( "total_volume_per_neurite", morphology, neurite_type=neurite_type ) feature_values = self.replace_empty_value(feature_values) self.value = sum(feature_values) / len(feature_values)
[docs] class AverageSectionLength(MorphologyFeature): """Average section length feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"average section length, {neurite_name}" self.unit = "\u00b5m" feature_values = nm.get( "section_lengths", morphology, neurite_type=neurite_type ) feature_values = self.replace_empty_value(feature_values) self.value = sum(feature_values) / len(feature_values)
[docs] class AverageSegmentLength(MorphologyFeature): """Average segment length feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"average segment length, {neurite_name}" self.unit = "\u00b5m" feature_values = nm.get( "segment_lengths", morphology, neurite_type=neurite_type ) feature_values = self.replace_empty_value(feature_values) self.value = sum(feature_values) / len(feature_values)
[docs] class MaxBranchOrder(MorphologyFeature): """Maximum branch order feature. Attributes: name (str): name of the feature value (int): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"maximum branch order, {neurite_name}" self.unit = "" feature_values = nm.get( "section_branch_orders", morphology, neurite_type=neurite_type ) feature_values = self.replace_empty_value(feature_values) self.value = max(feature_values)
[docs] class MaxSectionLength(MorphologyFeature): """Maximum section length feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology, neurite_name, neurite_type): """Constructor. Args: morphology (neurom neuron object): morphology object neurite_name (str): neurite name, e.g. axon neurite_type (NeuriteType): enum for neurite type encoding """ super().__init__() self.name = f"maximum section length, {neurite_name}" self.unit = "\u00b5m" feature_values = nm.get( "section_lengths", morphology, neurite_type=neurite_type ) feature_values = self.replace_empty_value(feature_values) self.value = max(feature_values)
[docs] class SomaDiamater(MorphologyFeature): """Soma diameter feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology): """Constructor. Args: morphology (neurom neuron object): morphology object """ super().__init__() self.name = "soma diameter" self.unit = "\u00b5m" feature_value = nm.get("soma_radius", morphology) self.value = 2 * self.replace_empty_value(feature_value)
[docs] class SomaSurfaceArea(MorphologyFeature): """Soma surface area feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology): """Constructor. Args: morphology (neurom neuron object): morphology object """ super().__init__() self.name = "soma surface area" self.unit = "\u00b5m\u00b2" feature_value = nm.get("soma_surface_area", morphology) self.value = self.replace_empty_value(feature_value)
[docs] class SomaVolume(MorphologyFeature): """Soma volume feature. Attributes: name (str): name of the feature value (float): value of the feature unit (str): unit of the feature """ def __init__(self, morphology): """Constructor. Args: morphology (neurom neuron object): morphology object """ super().__init__() self.name = "soma volume" self.unit = "\u00b5m\u00b3" feature_value = nm.get("soma_volume", morphology) self.value = self.replace_empty_value(feature_value)
[docs] class MorphologyFactsheetBuilder: """Computes the factsheet values for a morphology. Attributes: morphology (neurom.core.morphology.Morphology): morphology of the neuron neurites (list): list of neurites to be considered neurite_features (list): list of neurite feature to be used soma_features (list): list of soma features to be used """ def __init__(self, morph_path): """Load the morphology. Args: morph_path (str or Path): Path to the morphology file. """ self.morphology = nm.load_morphology(morph_path) self.neurites = [] self.neurite_features = [] self.soma_features = []
[docs] def get_neurites(self): """Return neurite names (str) and types (neurom type). If basal or apical are not present, name them 'dendrite'. Returns: list of tuple: (neurite_names, neurite_types) """ api = nm.get("total_length", self.morphology, neurite_type=nm.APICAL_DENDRITE) bas = nm.get("total_length", self.morphology, neurite_type=nm.BASAL_DENDRITE) if api and bas: return [ ("axon", nm.AXON), ("apical", nm.APICAL_DENDRITE), ("basal", nm.BASAL_DENDRITE), ] elif api: return [("axon", nm.AXON), ("dendrite", nm.APICAL_DENDRITE)] elif bas: return [("axon", nm.AXON), ("dendrite", nm.BASAL_DENDRITE)] logger.warning("No dendrite found!") return [("axon", nm.AXON)]
[docs] def get_feature_values(self): """Returns the values of all features in a list.""" neurites = self.neurites all_values = [] for neurite_name, neurite_type in neurites: for feature in self.neurite_features: feature_dict = feature( self.morphology, neurite_name, neurite_type ).to_dict() all_values.append(feature_dict) for feature in self.soma_features: feature_dict = feature(self.morphology).to_dict() all_values.append(feature_dict) return all_values
[docs] def factsheet_dict(self): """Returns the factsheet as a dict.""" anatomy = self.get_feature_values() return {"name": "Anatomy", "values": anatomy}
[docs] class SSCXMorphologyFactsheetBuilder(MorphologyFactsheetBuilder): """Morphology factsheet builder for SSCX packages.""" def __init__(self, morph_path): """Load the morphology. Args: morph_path (str or Path): Path to the morphology file. """ super().__init__(morph_path) self.neurites = self.get_neurites() self.neurite_features = [ TotalLength, MeanNeuriteVolumes, MaxBranchOrder, MaxSectionLength, ] self.soma_features = [SomaDiamater]
[docs] class HippocampusMorphologyFactsheetBuilder(MorphologyFactsheetBuilder): """Morphology factsheet builder for Hippocampus packages.""" def __init__(self, morph_path): """Load the morphology. Args: morph_path (str or Path): Path to the morphology file. """ super().__init__(morph_path) self.neurites = [("all", nm.ANY_NEURITE)] + self.get_neurites() self.neurite_features = [ TotalWidth, TotalHeight, TotalDepth, TotalLength, TotalArea, TotalVolume, AverageDiameter, NumberOfSections, NumberOfSegments, AverageSectionLength, AverageSegmentLength, MaxBranchOrder, ] self.soma_features = [SomaDiamater, SomaSurfaceArea, SomaVolume]
[docs] class ThalamusMorphologyFactsheetBuilder(MorphologyFactsheetBuilder): """Morphology factsheet builder for Thalamus packages.""" def __init__(self, morph_path): """Load the morphology. Args: morph_path (str or Path): Path to the morphology file. """ super().__init__(morph_path) self.neurites = self.get_neurites() self.neurite_features = [ TotalLength, TotalVolume, MaxBranchOrder, ] self.soma_features = [SomaDiamater, SomaSurfaceArea, SomaVolume]