Source code for dense.elements

# -*- coding: utf-8 -*-
#
# elements.py
#
# This file is part of DeNSE.
#
# Copyright (C) 2019 SeNEC Initiative
#
# DeNSE is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# DeNSE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with DeNSE. If not, see <http://www.gnu.org/licenses/>.


""" Containers for Neuronal shapes """

from logging import warnings as _warn
from collections import deque as _deque

import numpy as _np

from . import _pygrowth as _pg
from ._helpers import nonstring_container as _nsc
from .units import *


__all__ = ["Neuron", "Neurite", "Node", "Population", "Tree"]


[docs]class Neuron(object): ''' Container allowing direct access to a neuron. ''' def __init__(self, gid, **kwargs): ''' Create a neuron object. Parameters ---------- gid : int GID of the neuron. **kwargs : dict Optional arguments used when loading neurons that do not exist in the simulator. ''' kwargs = kwargs.copy() self._axon = None self._dendrites = {} self.__gid = int(gid) self._in_simulator = kwargs.get("in_simulator", True) if "in_simulator" in kwargs: del kwargs["in_simulator"] # check if object exists try: _pg.get_object_type(gid) self._in_simulator *= True except RuntimeError: self._in_simulator = False if not self._in_simulator: # object does not exist, use kwargs for some information for k, v in kwargs.items(): setattr(self, k, v) if "axon" not in kwargs: self._axon = None # necessary for initial setting of attributes due to __setattr__ self.__initialized = True # GID (integer) functions def __int__(self): return self.__gid def __eq__(self, other): return int(self) == int(other) def __lt__(self, other): return int(self) < int(other) def __le__(self, other): return int(self) <= int(other) def __gt__(self, other): return int(self) > int(other) def __ge__(self, other): return int(self) >= int(other) # representation functions def __str__(self): return str(self.__gid) def __repr__(self): return "Neuron<{}>".format(self.__gid) def _repr_pretty_(self, p, cycle): p.text("Neuron({})".format(self.__gid)) # attributes def __getattr__(self, attribute): ''' Access neuronal properties directly ''' if attribute.startswith("_"): return super(Neuron, self).__getattribute__(attribute) if attribute in self.dendrites: return self.dendrites[attribute] if self._in_simulator: ndict = _pg.get_object_properties(self, level="neuron") if attribute in ndict: return ndict[attribute] elif attribute in ndict.get("observables", {}): return self.get_state(attribute) raise AttributeError( "{!r} has not attribute '{}'".format(self, attribute)) def __setattr__(self, attribute, value): ''' Set neuronal properties directly ''' uninit = "_Neuron__initialized" not in self.__dict__ if uninit or attribute in self.__dict__: # set attributes declared in __init__ super().__setattr__(attribute, value) elif self._in_simulator: ndict = _pg.get_object_properties( self, level="neuron", settables_only=True) _pg.set_object_properties(self, {attribute: value}) else: raise AttributeError( "{!r} has not attribute '{}'".format(self, attribute)) @property def axon(self): ''' Return the :class:`~dense.elements.Neurite` container for the axon. ''' if self._in_simulator: neurites = _pg._get_neurites(self) has_axon = "axon" in neurites if has_axon and self._axon is None: self._axon = Neurite(None, "axon", name="axon", parent=self) elif not has_axon: self._axon = None return self._axon @property def dendrites(self): ''' Return a dict containing one :class:`~dense.elements.Neurite` container for each dendrite, with its name as key. ''' if self._in_simulator: set_dend = set(k for k in _pg._get_neurites(self) if k != "axon") if set_dend != set(self._dendrites.keys()): dendrites = {} for name in set_dend: dendrites[name] = Neurite( None, "dendrite", name=name, parent=self) self._dendrites = dendrites return self._dendrites.copy() @property def neurites(self): ''' Return a dict containing one :class:`~dense.elements.Neurite` container for each neurite, with its name as key. ''' neurites = self.dendrites if self.axon is not None: neurites["axon"] = self.axon return neurites @property def total_length(self): ''' Total arbor length of the neuron ''' if self._in_simulator: return _pg.get_object_state(self, observable="length") return _np.sum(n.total_length for n in self.neurites.values())
[docs] def create_neurites(self, num_neurites=1, params=None, angles=None, names=None): ''' Create new neurites. Neurite types (axon or dendrite) are based on the neurite names: axon must always be named "axon", all other names will be associated to a dendrite. Parameters ---------- num_neurites : int, optional (default: 1) Number of neurites that will be added to the neuron. params : dict, optional (default: None) Parameters of the neurites. angle : list, optional (default: automatically positioned) Angles of the newly created neurites. names : str or list, optional (default: "axon" and "dendrite_X") Names of the created neurites, if not provided, will an "axon" or a dendrite with default name "dendrite_X" (X being a number) will be created, depending on whether the neuron is supposed to have an axon or not, and depending on the number of pre-existing neurites. See also -------- :func:`~dense.create_neurites`. ''' _pg.create_neurites(self, num_neurites=num_neurites, params=params, angles=angles, names=names) # update _axon and _dendrites names = list(params) if names is None else names for name in names: if name == "axon": self._axon = Neurite(None, "axon", name="axon", parent=self) else: self._dendrites[name] = \ Neurite(None, "dendrite", name=name, parent=self)
[docs] def delete_neurites(self, neurite_names=None): ''' Delete neurites. Parameters ---------- neurite_names : str or list, optional (default: all neurites) Neurites which will be deleted. See also -------- :func:`~dense.delete_neurites` ''' _pg.delete_neurites(neurite_names=neurite_names, neurons=self) if isinstance(neurite_names, str): neurite_names = [neurite_names] for name in neurite_names: if name == "axon": self._axon = None elif name in self._dendrites: del self._dendrites[name]
[docs] def get_neurite(self, neurite): ''' Returns the required neurite. ''' if neurite == "axon": return self.axon return self.dendrites[neurite]
[docs] def get_state(self, observable=None): ''' Return the values of all or one state observable of the neuron. Parameters ---------- observable : str, optional (default: all observables) Observable to query. Returns ------- state : dict or scalar value See also -------- :func:`~dense.get_state` :func:`~dense.elements.Neuron.get_properties` ''' return _pg.get_object_state(self, observable)
[docs] def get_properties(self, property_name=None, level=None, neurite=None): ''' Get the neuron's properties. Parameters ---------- property_name : str, optional (default: None) Name of the property that should be queried. By default, the full dictionary is returned. level : str, optional (default: highest) Level at which the status should be obtained. Should be among "neuron", "neurite", or "growth_cone". neurite : str optional (default: None) Neurite that should be queried (either `axon` or `dendrites`). By default, both dictionaries are returned inside the neuronal status dictionary. If `neurite` is specified, only the parameters of this neurite will be returned. Returns ------- status : variable Properties of the objects' status: a single value if `property_name` was specified, the full status ``dict`` otherwise. See also -------- :func:`~dense.get_object_properties`, :func:`~dense.elements.Neurite.get_properties`, :func:`~dense.elements.Neuron.set_properties`. ''' return _pg.get_object_properties(self, property_name=property_name, level=level, neurite=neurite)
[docs] def set_properties(self, params=None, neurite_params=None): ''' Update the neuronal (and optionaly neurite) parameters. Parameters ---------- params : dict New neuron parameters. neurite_params : dict, optional (default: None) New neurite parameters. See also -------- :func:`~dense.set_object_properties`, :func:`~dense.elements.Neurite.set_properties`, :func:`~dense.elements.Neuron.get_properties`. ''' _pg.set_object_properties( self, params=params, neurite_params=neurite_params)
[docs] def to_swc(self, filename, resolution=10): ''' Save the neuron as a SWC file. Parameters ---------- filename : str Name of the SWC to write. resolution : int, optional (default: 10) Coarse-graining factor of the structure: only one point every `resolution` will be kept. ''' from .io import save_to_swc save_to_swc(filename, gid=self, resolution=resolution)
[docs] def to_neuroml(self, filename, resolution=10, write=True): ''' Save the neuron as a NeuroML (.nml) object. Parameters ---------- filename : str Name of the MNL file to write. resolution : int, optional (default: 10) Coarse-graining factor of the structure: only one point every `resolution` will be kept. write : bool, optional (default: True) Write the file. Returns ------- neuroml.Cell object. ''' import neuroml import neuroml.writers as writers x = self.position[0].to('micrometer').m y = self.position[1].to('micrometer').m z = 0. p = neuroml.Point3DWithDiam(x=x, y=y, z=z, diameter=2.*self.soma_radius.m) soma = neuroml.Segment(proximal=p, distal=p) soma.name = 'Soma' soma.id = 0 seg_id = 0 morpho = neuroml.Morphology() morpho.id = "Morphology neuron {}".format(int(self)) morpho.segments.append(soma) neurites_segments = [] neurites = list(self.dendrites.values()) if self.axon is not None: neurites.append(self.axon) # set dendrites for neurite in neurites: p_segment = soma parent = neuroml.SegmentParent(segments=soma.id) branch_seen = {} todo = _deque([branch for branch in neurite.branches]) indices = _deque([i for i in range(len(todo))]) while todo: branch = todo.popleft() idx = indices.popleft() if branch.parent in (-1, 0, None): p_segment = soma parent = neuroml.SegmentParent(segments=soma.id) elif branch.parent in branch_seen: p_segment = branch_seen[branch.parent] parent = neuroml.SegmentParent(segments=p_segment.id) else: parent = None if parent is not None: diameter = branch.diameter if neurite.taper_rate is not None: dist_to_tip = _np.cumsum(branch.r[::-1])[::-1] diameter = diameter + neurite.taper_rate*dist_to_tip else: diameter = (diameter for _ in range(len(branch.xy))) # subsample positions and diameters subnodes = branch.xy[::resolution].m subdiam = diameter[::resolution].m for pos, diam in zip(subnodes, subdiam): p = neuroml.Point3DWithDiam( x=p_segment.distal.x, y=p_segment.distal.y, z=p_segment.distal.z, diameter=p_segment.distal.diameter) d = neuroml.Point3DWithDiam(x=pos[0], y=pos[1], z=p_segment.distal.z, diameter=diam) n_segment = neuroml.Segment(proximal=p, distal=d, parent=parent) n_segment.id = seg_id n_segment.name = '{}_segment_{}'.format(neurite, seg_id) # set as next parent p_segment = n_segment parent = neuroml.SegmentParent(segments=p_segment.id) seg_id += 1 neurites_segments.append(n_segment) # store the last point as future parent for child branches branch_seen[branch.node_id] = p_segment else: todo.append(branch) indices.append(idx) morpho.segments += neurites_segments # make the neuroml cell cell = neuroml.Cell() cell.name = "Neuron {}".format(int(self)) cell.id = int(self) cell.morphology = morpho # write if write: doc = neuroml.NeuroMLDocument(id=filename) doc.cells.append(cell) writers.NeuroMLWriter.write(doc, filename) return cell
[docs]class Neurite(object): ''' Container to allow direct access to neurites. Also facilitates post processing of SWC files. branch path is a tuple with (xy, r, theta, diameter) ''' def __init__(self, branches, neurite_type, name="neurite", parent=None): self._parent = None if parent is None else int(parent) self._branches = branches self.__name = name self._in_simulator = \ False if (parent is None or not parent._in_simulator) else True if branches: self._has_branches = True else: self._has_branches = False # store last update time if self._has_branches: self._update_time = _pg.get_kernel_status("time") else: self._update_time = None def __str__(self): return self.__name def __repr__(self): name = str(self) if name != "axon" and "dend" not in name: name += " dendrite" if self._parent is None: return "Neurite<{} at {}>".format(name, id(self)) return "Neurite<{} of neuron {}>".format(name, int(self._parent)) def __getattr__(self, attribute): ''' Access neuronal properties directly ''' ndict = _pg.get_object_properties(self._parent, neurite=self) if attribute in ndict: return ndict[attribute] elif attribute in ndict.get("observables", {}): return self.get_state(attribute) super(Neurite, self).__getattribute__(attribute) def __setattr__(self, attribute, value): ''' Set neuronal properties directly ''' if attribute.startswith("_"): super(Neurite, self).__setattr__(attribute, value) else: ndict = _pg.get_object_properties( self._parent, neurite=self, settables_only=True) if attribute in ndict: _pg.set_neurite_properties(self._parent, self, {attribute: value}) else: super(Neurite, self).__setattr__(attribute, value)
[docs] def get_state(self, observable=None): ''' Return the values of all or one state observable of the neurite. Parameters ---------- observable : str, optional (default: all observables) Observable to query. Returns ------- state : dict or scalar value See also -------- :func:`~dense.get_object_state` :func:`~dense.elements.Neuron.get_state` :func:`~dense.elements.Neurite.get_properties` ''' return _pg.get_object_state(self, observable)
[docs] def get_tree(self): return _pg._get_tree(self._parent, str(self))
@property def name(self): ''' Name of the neurite ''' return self.__name @property def neuron(self): ''' Name of the parent neuron ''' return self._parent @property def branches(self): ''' Return the branches composing the neurite ''' if self._in_simulator: update = (self._update_time != _pg.get_kernel_status("time")) if not self._has_branches or update: self._update_branches() return self._branches return self._branches @property def empty(self): ''' Whether the neurite is empty or not ''' return self._has_branches @property def single_branch(self): ''' Whether the neurite is composed of a single branch ''' if not self._has_branches: return False else: return (len(self.branches) == 1) @property def xy(self): ''' Points constituting the different segments along the neurite ''' try: arr = _np.concatenate( [branch.xy.m for branch in self.branches])*um return arr except ValueError as e: print("{}\n{}.xy: {} missing".format( e, self.neurite_type, self.name)) return _np.array([[]])*um @property def theta(self): ''' Angles of the different segments along the neurite ''' try: return _np.concatenate([branch.theta for branch in self.branches]) except ValueError as e: print("{}\n{}.xy: {} missing".format( e, self.neurite_type, self.name)) return _np.array([[]]) @property def diameter(self): ''' Diameter of the different segments along the neurite ''' try: return _np.array([b.diameter.m for b in self.branches])*um except ValueError as e: print("{}\n{}.xy: {} missing".format( e, self.neurite_type, self.name)) return _np.array([]) @property def branching_points(self): ''' Return the B locations of the branching points, shape (B, 2) ''' return _np.array([branch.xy[0] for branch in self.branches]) @property def r(self): ''' Length of the different segments along the neurite ''' try: return _np.concatenate([branch.r for branch in self.branches]) except ValueError as e: print("{}\n{}.xy: {} missing".format( e, self.neurite_type, self.name)) return _np.array([[]]) @property def total_length(self): ''' Total length of the neurite ''' return _pg.get_object_state(self._parent, level=str(self), observable="length") @property def taper_rate(self): if self._parent is not None: return _pg.get_object_properties(self._parent, "taper_rate", neurite=self.name) return None
[docs] def get_properties(self, property_name=None, level=None): ''' Get the object's properties. Parameters ---------- property_name : str, optional (default: None) Name of the property that should be queried. By default, the full dictionary is returned. level : str, optional (default: highest) Level at which the status should be obtained. Should be among "neurite", or "growth_cone". Returns ------- status : variable Properties of the objects' status: a single value if `property_name` was specified, the full status ``dict`` otherwise. See also -------- :func:`~dense.get_object_properties`, :func:`~dense.elements.Neuron.get_properties`, :func:`~dense.elements.Neurite.set_properties`. ''' return _pg.get_object_properties( self._parent, property_name=property_name, level=level, neurite=str(self))
[docs] def set_properties(self, params): ''' Update the neuronal parameters using the entries contained in `params`. Parameters ---------- params : dict New neurite parameters. See also -------- :func:`~dense.set_neurite_properties`, :func:`~dense.elements.Neuron.set_properties`, :func:`~dense.elements.Neurite.get_properties`. ''' return _pg.set_neurite_properties( self._parent, self, params=params)
[docs] def plot_dendrogram(self, axis=None, show_node_id=False, aspect_ratio=None, vertical_diam_frac=0.2, ignore_diameter=False, show=True, **kwargs): ''' Plot the dendrogram of a neurite. Parameters ---------- neurite : :class:`~dense.elements.Neurite` object Neurite for which the dendrogram should be plotted. axis : matplotlib.Axes.axis object, optional (default: new one) Axis on which the dendrogram should be plotted. show_node_id : bool, optional (default: False) Display each node number on the branching points. aspect_ratio : float, optional (default: variable) Whether to use a fixed aspect ratio. Automatically set to 1 if `show_node_id` is True. vertical_diam_frac : float, optional (default: 0.2) Fraction of the vertical spacing taken by the branch diameter. ignore_diameter : bool, optional (default: False) Plot all the branches with the same width. show : bool, optional (default: True) Whether the figure should be shown right away. **kwargs : arguments for :class:`matplotlib.patches.Rectangle` For instance `facecolor` or `edgecolor`. Returns ------- The axis on which the plot was done. See also -------- :func:`~dense.plot.plot_dendrogram` ''' from .plot import plot_dendrogram return plot_dendrogram(self, axis=axis, show_node_id=show_node_id, aspect_ratio=aspect_ratio, vertical_diam_frac=vertical_diam_frac, ignore_diameter=ignore_diameter, show=show, **kwargs)
def _update_branches(self): cneurite = _pg._to_bytes(str(self)) self._branches = [] points, diameters, parents, nodes = _pg._get_branches_data( self._parent, cneurite) for p, d, parent, n in zip(points, diameters, parents, nodes): data = (_np.array(p).T, None, None, d) self._branches.append( Branch(data, parent=parent, node_id=n)) self._has_branches = True self._update_time = _pg.get_kernel_status("time")
class Branch(object): ''' Container to facilitate post processing of SWC files branch path is a tuple with (xy, r, theta, diameter) ''' def __init__(self, neurite_path, parent=None, node_id=None): xy, r, t, d = neurite_path self.xy = xy * um self._r = r if r is None else r*um self._theta = t if t is None else t*radian self.diameter = d if d is None else d*um self.parent = parent self.node_id = node_id @property def r(self): ''' Norm of each segment in the Branch. ''' if self._r is None: assert (isinstance(self.xy.m, _np.ndarray)) theta, r = _norm_angle_from_vectors(self.xy.m) self._theta, self._r = theta*rad, r*um return self._r else: assert (isinstance(self._r.m, _np.ndarray)), \ "branch's radius is not an array, there is a mistake" return self._r @property def theta(self): ''' Angle direction of each segment. ''' if self._r is None: assert (isinstance(self.xy.m, _np.ndarray)) theta, r = _norm_angle_from_vectors(self.xy.m) self._theta, self._r = theta*rad, r*um return self._theta else: assert (isinstance(self._theta.m, _np.ndarray)), \ "branch's angles is not an array, there is a mistake" return self._theta
[docs]class Node(int): ''' Container to facilitate drawing of dendrograms ''' def __new__(cls, node_id, tree=None, parent=None, diameter=None, dist_to_parent=None, pos=None): return super(Node, cls).__new__(cls, node_id) def __init__(self, node_id, tree=None, parent=None, diameter=None, dist_to_parent=None, pos=None): self._tree = tree self.diameter = diameter self.position = pos self.dist_to_parent = dist_to_parent self.children = [] self.parent = (tree.get(parent, parent) if parent is not None else None)
[docs] def add_child(self, child): self.children.append(child) self._tree[int(child)] = child child.parent = self
[docs] def distance_to_soma(self): dts = self.dist_to_parent node = self._tree.get(self.parent, None) while node is not None: dts += node.dist_to_parent node = self._tree.get(node.parent, None) return dts
[docs]class Tree(dict): def __init__(self, neuron, neurite): super(Tree, self).__init__() self._neuron = neuron self._neurite = neurite self._root = None self._tips = set() self._tips_set = False @property def neuron(self): return self._neuron @property def neurite(self): return self._neurite @property def root(self): return self._root @property def tips(self): assert self._tips_set, "Use `update_tips` first." return self._tips def __setitem__(self, key, value): super(Tree, self).__setitem__(int(key), value) if value.parent is None or value.parent == value: self._root = value value._tree = self self._tips_set = False
[docs] def update_tips(self): self._tips = set() for val in self.values(): if not val.children: self._tips.add(val) self._tips_set = True
[docs] def neurom_tree(self): from neurom.core import Neuron as nmNeuron from neurom.core import Neurite as nmNeurite from neurom.core import Section, Soma root = Section(_np.array([[0, 0, 0, 0]])) queue = _deque(self._root.children) edict = {int(self._root): root} sections = [] while queue: node = queue.popleft() parent = edict[node.parent] enode = Section(_np.array([[0, 0, 0, 0]])) parent.add_child(enode) edict[node] = enode queue.extend(node.children) sections.append(enode) neurite = nmNeurite(root) soma = Soma(self._root.position) neuron = nmNeuron(soma, [neurite], [sections]) return neuron
def _cleanup(self): ''' This step is necessary to use the Tree properly since it converts parents from int to Node. ''' for val in self.values(): val.children = [] for key, val in self.items(): if val.parent is None or val.parent == val: self._root = val val.parent = None self[val] = val else: self[val.parent].children.append(val) val.parent = self[val.parent] self.update_tips()
def _norm_angle_from_vectors(vectors): #~ angles = _np.arctan2(vectors[:, 1], vectors[:, 0]) vectors = _np.diff(vectors, axis = 0) angles = _np.arctan2(vectors[:,1], vectors[:,0]) norms = _np.linalg.norm(vectors, axis=1) return angles, norms
[docs]class Population(list): ''' Stores all the neurons in an unique object. Keeps data and info on each neuron. Each neuron is identified with its `gid`. Ensemble keeps the `info.json` file. In case the `info.json` is absent it's possile to pass a description with a dictionary with `name` and `description` '''
[docs] @classmethod def from_swc(cls, population, info=None): ensemble = cls(info=info) ensemble._add_swc_population(population) ensemble.sort() ensemble._idx = {} # converter from gid to idx for i, n in enumerate(ensemble): ensemble._idx[int(n)] = i return ensemble
[docs] @classmethod def from_structure(cls, structure, info=None): if info is not None: population = cls(info=info) else: population = cls(name="population from structure") population._add_structure_population(structure) population.sort() population._idx = {} # converter from gid to idx for i, n in enumerate(population): population._idx[int(n)] = i return population
[docs] @classmethod def from_gids(cls, gids, name="no_name"): ''' Create a population from a set of neurons. Parameters ---------- gids : list Gids of the neurons to include in the population. name : str, optional (default: "no_name") Name of the population. ''' gids = [int(n) for n in gids] pop = cls(name=name) for n in gids: super(Population, pop).append(Neuron(n)) pop.sort() pop._idx = {} # converter from gid to idx for i, n in enumerate(pop): pop._idx[int(n)] = i return pop
def __init__(self, population=None, info=None, name="no_name"): self.info = info self.name = name if population is not None: super(Population, self).__init__(population) else: super(Population, self).__init__() self.sort() self._idx = {} # converter from gid to idx for i, n in enumerate(self): self._idx[int(n)] = i # necessary for initial setting of attributes due to __setattr__ self.__initialized = True def __getitem__(self, key): if isinstance(key, slice): pop = Population(name="subpop_" + self.name) for i in range(self._idx[key.start], self._idx[key.stop]): super(Population, pop).append( super(Population, self).__getitem__(i)) return pop elif _nsc(key): pop = Population(name="subpop_" + self.name) for i in key: super(Population, pop).append( super(Population, self).__getitem__(self._idx[i])) return pop else: return super(Population, self).__getitem__(self._idx[key]) def __getattr__(self, attribute): ''' Access neuronal properties directly ''' try: return super().__getattribute__(attribute) except AttributeError as e: return {int(n): getattr(n, attribute) for n in self} def __setattr__(self, attribute, value): ''' Set neuronal properties directly ''' uninit = "_Population__initialized" not in self.__dict__ if uninit or attribute in self.__dict__: # set attributes declared in __init__ super().__setattr__(attribute, value) else: # set attributes of the neurons [setattr(n, attribute, value) for n in self] @property def size(self): ''' Number of neurons in the Population ''' return len(self)
[docs] def append(self, val): super().append(val) self.sort() self._idx = {} # converter from gid to idx for i, n in enumerate(self): self._idx[int(n)] = i
[docs] def extend(self, values): super().extend(values) self.sort() self._idx = {} # converter from gid to idx for i, n in enumerate(self): self._idx[int(n)] = i
[docs] def axon_all_points(self, center_zero=False): if center_zero: return _np.vstack( [neuron.axon.xy - neuron.position for neuron in self if neuron.axon.xy.shape[1] > 1]) else: return _np.vstack( [neuron.axon.xy for neuron in self if neuron.axon.xy.shape[1] > 1])
[docs] def dendrites_all_points(self, center_zero=False): if center_zero: return _np.vstack( [neuron.dendrites[0].xy - neuron.position for neuron in self if neuron.dendrites[0].xy.shape[1]>1]) else: return _np.vstack( [neuron.dendrites[0].xy for neuron in self if neuron.dendrites[0].xy.shape[1] > 1])
@property def ids(self): return [int(n) for n in self] @property def positions(self): return [neuron.position for neuron in self] def _add_structure_population(self, structure): dd = structure["dendrites"] ax = structure["axon"] for enum, gid in enumerate(structure['gid']): # @todo include radii in structure neuron = Neuron(gid, position=structure['position'][enum], soma_radius=8.) if _np.any(ax[enum]): neuron._axon = _neurite_from_skeleton( ax[enum], "axon", parent=gid) if _np.any(dd[enum]): neuron.dendrites[enum] = _neurite_from_skeleton( dd[enum], "dendrite", parent=gid) super(Population, self).append(neuron) self.sort() def _add_swc_population(self, neurons): ''' Build population from SWC files ''' # add neurons first self.extend(( Neuron(gid, position=neuron["position"], soma_radius=neuron["soma_radius"], in_simulator=False) for gid, neuron in neurons.items() )) # then add neurites for gid, neuron in neurons.items(): if neuron["axon"] is not None: axon = neuron["axon"][0] self[gid]._axon = Neurite( [Branch((ax["xy"], None, None, ax["diameter"]), parent=ax["parent_id"], node_id=ax["first_id"]) for ax in axon], neurite_type="axon", name="axon") if neuron["basal"] is not None: for i, basal in enumerate(neuron["basal"]): dendrite = Neurite( [Branch((dend["xy"], None, None, dend["diameter"]), parent=dend["parent_id"], node_id=dend["first_id"]) for dend in basal], neurite_type="dendrite", name="dendrite_{}".format(i)) self[gid]._dendrites[str(dendrite)] = dendrite if neuron["apical"] is not None: for j, apical in enumerate(neuron["apical"]): dendrite = Neurite( [Branch((dend["xy"], None, None, dend["diameter"]), parent=dend["parent_id"], node_id=dend["first_id"]) for dend in apical], neurite_type="dendrite", name="dendrite_{}".format(i + j)) self[gid]._dendrites[str(dendrite)] = dendrite
[docs] def get_properties(self, property_name=None, level=None, neurite=None): ''' Get the neurons's properties. Parameters ---------- property_name : str, optional (default: None) Name of the property that should be queried. By default, the full dictionary is returned. level : str, optional (default: highest) Level at which the status should be obtained. Should be among "neuron", "neurite", or "growth_cone". neurite : str optional (default: None) Neurite that should be queried (either `axon` or `dendrites`). By default, both dictionaries are returned inside the neuronal status dictionary. If `neurite` is specified, only the parameters of this neurite will be returned. Returns ------- status : variable Properties of the objects' status: a single value if `property_name` was specified, the full status ``dict`` otherwise. ''' return _pg.get_object_properties( list(self), property_name=property_name, level=level, neurite=neurite)
[docs] def set_properties(self, params=None, axon_params=None, dendrites_params=None): ''' Update the neuronal parameters using the entries contained in `params`. Parameters ---------- params : dict New neuron parameters. axon_params : dict, optional (default: None) New axon parameters. dendrites_params : dict, optional (default: None) New dendrites parameters. See also -------- :func:`~dense.set_object_properties`, :func:`~dense.elements.Neurite.set_properties`, :func:`~dense.elements.Neuron.get_properties`. ''' return _pg.set_object_properties( self, params=params, axon_params=axon_params, dendrites_params=dendrites_params)
# ------------- # # Tool function # # ------------- # def _neurite_from_skeleton(skeleton, neurite_type, parent=None): """ From a neurite whose branches are nan separated, returns a Neurite with separated arrays for branches. @todo: improve this function to compute diameter, theta, distance from soma; these are currently None. """ cuts = _np.where(_np.isnan(skeleton[1]))[0] cuts_2 = _np.where(_np.isnan(skeleton[0]))[0] assert _np.array_equal(cuts, cuts_2) neurite = Neurite([], neurite_type, name=neurite_type, parent=parent) prev_cut = 0 if len(cuts): cuts = cuts.tolist() + [len(skeleton[1])] for cut in cuts: branch = Branch( (skeleton[:, prev_cut:cut].transpose(), None, None, None)) neurite.branches.append(branch) prev_cut = cut + 1 else: branch = Branch((skeleton[:, :].transpose(), None, None, None)) neurite.branches.append(branch) return neurite