Source code for neuron_morphology.feature_extractor.mark
from typing import TypeVar, Type, Dict
import inspect
import warnings
from neuron_morphology.feature_extractor.data import Data
from neuron_morphology.constants import (
SOMA, AXON, BASAL_DENDRITE, APICAL_DENDRITE)
Mr = TypeVar("Mr", bound="Mark")
[docs]class Mark:
""" A tag, intended for use in feature selection.
"""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
""" Determine if this feature is calculable from the provided data.
Parameters
----------
data : Data from a single morphological reconstruction
Returns
-------
whether marked features can be calculated from these data
"""
return True
[docs] @classmethod
def factory(cls: Type[Mr], name: str) -> Type[Mr]:
return type(name, (cls,), {})
[docs]class RequiresLayerAnnotations(Mark):
[docs] @classmethod
def validate(cls, data: Data) -> bool:
""" Checks whether each node in the data's morphology is annotated with
a cortical layer. Returns False if any are missing.
"""
return check_nodes_have_key(data, "layer")
[docs]class Intrinsic(Mark):
"""Indicates intrinsic features that don't rely on a ccf or scale."""
pass
[docs]class Geometric(Mark):
"""Indicates features that change depending on coordinate frame."""
pass
[docs]class AllNeuriteTypes(Mark):
"""Indicates features that are calculated for all neurite types."""
pass
[docs]class RequiresDendrite(Mark):
"""This feature can only be calculated for neurons with at least one
dendrite node"""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return data.morphology.has_type(APICAL_DENDRITE) \
or data.morphology.has_type(BASAL_DENDRITE)
[docs]class RequiresRelativeSomaDepth(Mark):
"""This feature can only be calculated for relative soma depth"""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return data.morphology.has_type(RELATIVE_SOMA_DEPTH)
[docs]class RequiresSoma(Mark):
"""Indicates that these features require a soma."""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return data.morphology.has_type(SOMA)
[docs]class RequiresApical(Mark):
"""Indicates that these features require an apical dendrite."""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return data.morphology.has_type(APICAL_DENDRITE)
[docs]class RequiresBasal(Mark):
"""Indicates that these features require a basal dendrite."""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return data.morphology.has_type(BASAL_DENDRITE)
[docs]class RequiresAxon(Mark):
"""Indicates that these features require an axon."""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return data.morphology.has_type(AXON)
# TODO: this describes the present requirements of root-dependent features. I
# think that in nearly every case we actually want a RequiresUniqueSomaNode
# mark
[docs]class RequiresRoot(Mark):
"""Indicates that this features require a root. Warns if the root
is not unique"""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
num_roots = len(data.morphology.get_roots())
if num_roots > 1:
warnings.warn(
f"This morphology is not uniquely rooted! Found {num_roots} "
"root nodes. Features using the root node of this morphology "
"may not select that node consistently. Some or all of these "
"root nodes may not be soma nodes."
)
elif num_roots < 1:
return False
return True
[docs]class BifurcationFeatures(Mark):
"""Indicates a feature calculated on bifurcations."""
pass
[docs]class CompartmentFeatures(Mark):
"""Indicates a feature calculated on compartments."""
pass
[docs]class TipFeatures(Mark):
"""Indicates a feature calculated on tips (leaf nodes)."""
pass
[docs]class NeuriteTypeComparison(Mark):
"""Indicates a feature that is a comparison between neurite types.
Function should be decorated with the appropriate RequiresType marks
"""
pass
[docs]class RequiresRadii(Mark):
""" This feature can only be calculated if the radii of nodes are annotated.
"""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return check_nodes_have_key(data, "radius")
[docs]class RequiresReferenceLayerDepths(Mark):
""" This feature can only be calculated if a referenceset of average depths
for cortical layers is provided. See features.layer.reference_layer_depths
for more information.
"""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return hasattr(data, "reference_layer_depths")
[docs]class RequiresLayeredPointDepths(Mark):
""" This feature can only be calculated if (cortical) points are annotated
with a collection of within-layer depths. See
features.layer.layered_point_depths for more information.
"""
[docs] @classmethod
def validate(cls, data: Data) -> bool:
return hasattr(data, "layered_point_depths")
[docs]class RequiresRegularPointSpacing(Mark):
""" This features can only be (meaningfully) calculated if the points (e.g.
node positions) on which it is based are resampled to have regular spacing.
"""
[docs]def check_nodes_have_key(data: Data, key: str) -> bool:
""" Checks whether each node in a morphology is annotated with some key.
"""
has_key = True
for node in data.morphology.nodes():
has_key = has_key and key in node
if not has_key:
break
return has_key