Source code for hnn_core.cell

"""Establish class def for general cell features."""

# Authors: Mainak Jas <mjas@mgh.harvard.edu>
#          Sam Neymotin <samnemo@gmail.com>

from copy import deepcopy

import numpy as np
from numpy.linalg import norm

from neuron import h, nrn

from .viz import plot_cell_morphology
from .externals.mne import _validate_type, _check_option

# Units for e: mV
# Units for gbar: S/cm^2


def _get_cos_theta(sections, sec_name_apical):
    """Get cos(theta) to compute dipole along the apical dendrite."""
    a = (np.array(sections[sec_name_apical].end_pts[1]) -
         np.array(sections[sec_name_apical].end_pts[0]))
    cos_thetas = dict()
    for sec_name, section in sections.items():
        b = np.array(section.end_pts[1]) - np.array(section.end_pts[0])
        cos_thetas[sec_name] = np.dot(a, b) / (norm(a) * norm(b))
    return cos_thetas


def _calculate_gaussian(x_val, height, lamtha):
    """Return height of gaussian at x_val.

    Parameters
    ----------
    x_val : float
        Value on x-axis to query height of gaussian curve.
    height : float
        Height of the gaussian curve at zero.
    lamtha : float
        Space constant.

    Returns
    -------
    x_height : float
        Height of gaussian at x_val.

    Notes
    -----
    Gaussian curve is centered at zero and has a fixed peak height
    such the _calculate_gaussian(0, lamtha) returns 1 for all lamtha.
    """
    x_height = height * np.exp(-(x_val**2) / (lamtha**2))

    return x_height


def _get_gaussian_connection(src_pos, target_pos, nc_dict,
                             inplane_distance=1.):
    """Calculate distance dependent connection properties.

    Parameters
    ----------
    src_pos : float
        Position of source cell.
    target_pos : float
        Position of target cell.
    nc_dict : dict
        Dictionary with keys: pos_src, A_weight, A_delay, lamtha
        Defines the connection parameters
    inplane_distance : float
        The in plane-distance (in um) between pyramidal cell somas in the
        square grid. Default: 1.0 um.

    Returns
    -------
    weight : float
        Weight of the synaptic connection.
    delay : float
        Delay of synaptic connection.

    Notes
    -----
    Distance in xy plane is used for gaussian decay.
    """
    x_dist = target_pos[0] - src_pos[0]
    y_dist = target_pos[1] - src_pos[1]
    cell_dist = np.sqrt(x_dist**2 + y_dist**2)
    scaled_lamtha = nc_dict['lamtha'] * inplane_distance

    weight = _calculate_gaussian(
        cell_dist, nc_dict['A_weight'], scaled_lamtha)
    delay = nc_dict['A_delay'] / _calculate_gaussian(
        cell_dist, 1, scaled_lamtha)
    return weight, delay


def node_to_str(node):
    return node[0] + "," + str(node[1])


class _ArtificialCell:
    """The ArtificialCell class for initializing a NEURON feed source.

    Parameters
    ----------
    event_times : list
        Spike times associated with a single feed source (i.e.,
        associated with a unique gid).
    threshold : float
        Membrane potential threshold that demarks a spike.
    gid : int or None (optional)
        Each cell in a network is uniquely identified by it's "global ID": GID.
        The GID is an integer from 0 to n_cells, or None if the cell is not
        yet attached to a network. Once the GID is set, it cannot be changed.

    Attributes
    ----------
    nrn_eventvec : instance of h.Vector()
        NEURON h.Vector() object of event times.
    nrn_vecstim : instance of h.VecStim()
        NEURON h.VecStim() object of spike sources created
        from nrn_eventvec.
    nrn_netcon : instance of h.NetCon()
        NEURON h.NetCon() object that creates the spike
        source-to-target references for nrn_vecstim.
    gid : int
        GID of the cell in a network (or None if not yet assigned)
    """
    def __init__(self, event_times, threshold, gid=None):
        # Convert event times into nrn vector
        self.nrn_eventvec = h.Vector()
        self.nrn_eventvec.from_python(event_times)

        # load eventvec into VecStim object
        self.nrn_vecstim = h.VecStim()
        self.nrn_vecstim.play(self.nrn_eventvec)

        # create the cell and artificial NetCon
        self.nrn_netcon = h.NetCon(self.nrn_vecstim, None)
        self.nrn_netcon.threshold = threshold

        self._gid = None
        if gid is not None:
            self.gid = gid  # use setter method to check input argument gid

    @property
    def gid(self):
        return self._gid

    @gid.setter
    def gid(self, gid):
        if not isinstance(gid, int):
            raise ValueError('gid must be an integer')
        if self._gid is None:
            self._gid = gid
        else:
            raise RuntimeError('Global ID for this cell already assigned!')


def _get_nseg(L):
    nseg = 1
    if L > 100.:  # 100 um
        nseg = int(L / 50.)
        # make dend.nseg odd for all sections
        if not nseg % 2:
            nseg += 1
    return nseg


class Section:
    """Section class.

    Parameters
    ----------
    L : float
        length of a section in microns.
    diam : float
        diameter of a section in microns.
    cm : float
        membrane capacitance in micro-Farads.
    Ra : float
        axial resistivity in ohm-cm
    end_pts : list of [x, y, z]
        The start and stop points of the section.

    Attributes
    ----------
    mechs : dict
        Mechanisms to insert in this section. The keys
        are the names of the mechanisms and values
        are the properties. For e.g., {'ca': {'gbar_ca': 60}}
    syns : list of str
        The synaptic mechanisms to add in this section
    end_pts : list of [x, y, z]
        The start and stop points of the section. Cannot be changed.
    L : float
        length of a section in microns.
    diam : float
        diameter of a section in microns.
    cm : float
        membrane capacitance in micro-Farads.
    Ra : float
        axial resistivity in ohm-cm.
    nseg : int
        Number of segments in the section
    """
    def __init__(self, L, diam, Ra, cm, end_pts=None):

        self._L = L
        self._diam = diam
        self._Ra = Ra
        self._cm = cm
        if end_pts is None:
            end_pts = list()
        self._end_pts = end_pts

        self.mechs = dict()
        self.syns = list()

        # For distance functionality
        self.nseg = _get_nseg(self.L)

    def __repr__(self):
        return f'L={self.L}, diam={self.diam}, cm={self.cm}, Ra={self.Ra}'

    def __eq__(self, other):
        if not isinstance(other, Section):
            return NotImplemented

        # Check equality for mechs
        for mech_name in self.mechs.keys():
            self_mech = self.mechs[mech_name]
            other_mech = other.mechs[mech_name]
            for attr in self_mech.keys():
                if self_mech[attr] != other_mech[attr]:
                    return False

        # Check end_pts
        for self_end_pt, other_end_pt in zip(self.end_pts, other.end_pts):
            if np.testing.assert_almost_equal(self_end_pt,
                                              other_end_pt, 5) is not None:
                return False

        all_attrs = dir(self)
        attrs_to_ignore = [x for x in all_attrs if x.startswith('_')]
        attrs_to_ignore.extend(['end_pts', 'mechs', 'to_dict'])
        attrs_to_check = [x for x in all_attrs if x not in attrs_to_ignore]

        # Check all other attributes
        for attr in attrs_to_check:
            if getattr(self, attr) != getattr(other, attr):
                return False

        return True

    def to_dict(self):
        """Converts an object of Section class to a dictionary.

        Returns
        -------
        dictionary form of an object of Section class.
        """
        section_data = dict()
        section_data['L'] = self.L
        section_data['diam'] = self.diam
        section_data['cm'] = self.cm
        section_data['Ra'] = self.Ra
        section_data['end_pts'] = self.end_pts
        section_data['nseg'] = self.nseg
        # Need to solve the partial function problem
        # in mechs
        section_data['mechs'] = self.mechs
        section_data['syns'] = self.syns
        return section_data

    @property
    def L(self):
        return self._L

    @property
    def diam(self):
        return self._diam

    @property
    def cm(self):
        return self._cm

    @property
    def Ra(self):
        return self._Ra

    @property
    def end_pts(self):
        return self._end_pts


[docs]class Cell: """Create a cell object. Parameters ---------- name : str The name of the cell. pos : tuple The (x, y, z) coordinates. sections : dict of Section Dictionary with keys as section name. synapses : dict of dict Keys are name of synaptic mechanism. Each synaptic mechanism has keys for parameters of the mechanism, e.g., 'e', 'tau1', 'tau2'. sections. sect_loc : dict of list Can have keys 'proximal' or 'distal' each containing names of section locations that are proximal or distal. gid : int or None (optional) Each cell in a network is uniquely identified by it's "global ID": GID. The GID is an integer from 0 to n_cells, or None if the cell is not yet attached to a network. Once the GID is set, it cannot be changed. cell_tree : dict of list Stores the tree representation of a cell. Root is the 0 end of 'soma'. Nodes are a tuple (sec_name, node_pos) where sec_name is the name of the section and node_pos is the 0 end or 1 end. The data structure is the adjacency list representation of a tree. The keys of the dict are the parent nodes. The value is the list of nodes (children nodes) connected to the parent node. Attributes ---------- pos : list of length 3 The position of the cell. sections : nested dict The section parameters. The key is the name of the section and the value is a dictionary parametrizing the morphology of the section and the mechanisms inserted. synapses : dict The synapses that the cell can use for connections. dipole_pp : list of h.Dipole() The Dipole objects (see dipole.mod). vsec : dict Recording of section specific voltage. Must be enabled by running simulate_dipole(net, record_vsec=True) or simulate_dipole(net, record_vsoma=True) isec : dict Contains recording of section specific currents indexed by synapse type (keys can be soma_gabaa, soma_gabab etc.). Must be enabled by running simulate_dipole(net, record_isec=True) or simulate_dipole(net, record_isoma=True) ca : dict Contains recording of section speicifc calcium concentration. Must be enabled by running simulate_dipole(net, record_ca=True). tonic_biases : list of h.IClamp The current clamps inserted at each section of the cell for tonic biasing inputs. gid : int GID of the cell in a network (or None if not yet assigned) sect_loc : dict of list Can have keys 'proximal' or 'distal' each containing names of section locations that are proximal or distal. cell_tree : dict of list Stores the tree representation of a cell. Root is the 0 end of 'soma'. Nodes are a tuple (sec_name, node_pos) where sec_name is the name of the section and node_pos is the 0 end or 1 end. The data structure is the adjacency list representation of a tree. The keys of the dict are the parent nodes. The value is the list of nodes (children nodes) connected to the parent node. Examples -------- >>> section_soma = Section( L=39, diam=20, cm=0.85, Ra=200., end_pts=[[0, 0, 0], [0, 39., 0]] ) """ def __init__(self, name, pos, sections, synapses, sect_loc, cell_tree, gid=None): self.name = name self.pos = pos for section in sections.values(): if not isinstance(section, Section): raise ValueError(f'Items in section must be instances' f' of Section. Got {type(section)}') self.sections = sections self.synapses = synapses self.sect_loc = sect_loc self._nrn_sections = dict() self._nrn_synapses = dict() self.dipole_pp = list() self.vsec = dict() self.isec = dict() self.ca = dict() # insert iclamp self.list_IClamp = list() self._gid = None self.tonic_biases = list() if gid is not None: self.gid = gid # use setter method to check input argument gid # Store the tree representation of the cell self.cell_tree = cell_tree self._update_end_pts() # New implementation self._compute_section_mechs() # Set mech values of all sections
[docs] def __repr__(self): class_name = self.__class__.__name__ return f'<{class_name} | gid={self._gid}>'
def __eq__(self, other): if not isinstance(other, Cell): return NotImplemented all_attrs = dir(self) attrs_to_ignore = [x for x in all_attrs if x.startswith('_')] attrs_to_ignore.extend(['build', 'copy', 'create_tonic_bias', 'define_shape', 'distance_section', 'gid', 'list_IClamp', 'modify_section', 'parconnect_from_src', 'plot_morphology', 'record', 'sections', 'setup_source_netcon', 'syn_create', 'to_dict']) attrs_to_check = [x for x in all_attrs if x not in attrs_to_ignore] # Check all other attributes for attr in attrs_to_check: if getattr(self, attr) != getattr(other, attr): return False if not (self.sections.keys() == other.sections.keys()): return False for key in self.sections.keys(): if self.sections[key] != other.sections[key]: return False return True
[docs] def to_dict(self): """Converts an object of Cell class to a dictionary. Returns ------- dictionary form of an object of Cell class. """ cell_data = dict() cell_data['name'] = self.name cell_data['pos'] = self.pos cell_data['sections'] = dict() for key in self.sections: cell_data['sections'][key] = self.sections[key].to_dict() cell_data['synapses'] = self.synapses # cell_data['cell_tree'] = self.cell_tree if self.cell_tree is None: cell_data['cell_tree'] = None else: cell_tree_dict = dict() for parent, children in self.cell_tree.items(): key = node_to_str(parent) value = list() for child in children: value.append(node_to_str(child)) cell_tree_dict[key] = value cell_data['cell_tree'] = cell_tree_dict cell_data['sect_loc'] = self.sect_loc cell_data['gid'] = self.gid cell_data['dipole_pp'] = self.dipole_pp cell_data['vsec'] = self.vsec cell_data['isec'] = self.isec cell_data['ca'] = self.ca cell_data['tonic_biases'] = self.tonic_biases return cell_data
@property def gid(self): return self._gid @gid.setter def gid(self, gid): if not isinstance(gid, int): raise ValueError('gid must be an integer') if self._gid is None: self._gid = gid else: raise RuntimeError('Global ID for this cell already assigned!')
[docs] def distance_section(self, target_sec_name, curr_node): """Find distance between the current node and the target section. Parameters ---------- target_sec_name : string Name of the target section curr_node : tuple Source node from where search begins. It is of the the form (sec_name, end_pt). Returns ------- distance : float Path distance between source node and mid of the target section. """ # Python version of the Neuron distance function # https://nrn.readthedocs.io/en/latest/python/modelspec/programmatic/topology/geometry.html#distance # noqa if self.cell_tree is None: raise TypeError("distance_section() " "cannot work with cell_tree as None.") if curr_node not in self.cell_tree: return np.nan # Children of the current section curr_sec_children = self.cell_tree[curr_node] # All sections have 0 and 1 ends end_pts = (0, 1) # Base condition # If target section is connected to current section # Return (target section length / 2) # As distances are measured till the centre of the target section for end_pt in end_pts: if (target_sec_name, end_pt) in curr_sec_children: return self.sections[target_sec_name].L / 2 dist = np.nan # Return nan # Recursion to find distance for node in self.cell_tree[curr_node]: if (node[0] == curr_node[0]): dist_temp = (self.distance_section(target_sec_name, node) + self.sections[node[0]].L) else: dist_temp = self.distance_section(target_sec_name, node) if np.isnan(dist) and np.isnan(dist_temp): dist = np.nan else: dist = np.nanmin([dist, dist_temp]) return dist
def _set_biophysics(self, sections): "Set the biophysics for the cell." # neuron syntax is used to set values for mechanisms # sec.gbar_mech = x sets value of gbar for mech to x for all segs # in a section. This method is significantly faster than using # a for loop to iterate over all segments to set mech values # If value depends on distance from the soma. Soma is set as # origin by passing cell.soma as a sec argument to h.distance() # Then iterate over segment nodes of dendritic sections # and set attribute depending on h.distance(seg.x), which returns # distance from the soma to this point on the CURRENTLY ACCESSED # SECTION!!! h.distance(sec=self._nrn_sections['soma']) for sec_name, section in sections.items(): sec = self._nrn_sections[sec_name] for mech_name, p_mech in section.mechs.items(): sec.insert(mech_name) for attr, val in p_mech.items(): if isinstance(val, list): seg_xs, seg_vals = val[0], val[1] for seg, seg_x, seg_val in zip(sec, seg_xs, seg_vals): setattr(seg, attr, seg_val) else: setattr(sec, attr, val) def _compute_section_mechs(self): sections = self.sections for sec_name, section in sections.items(): for mech_name, p_mech in section.mechs.items(): for attr, val in p_mech.items(): if hasattr(val, '__call__'): seg_xs, seg_vals = list(), list() section_distance = self.distance_section(sec_name, ('soma', 0)) seg_centers = (np.linspace(0, 1, section.nseg * 2 + 1) [1::2]) for seg_x in seg_centers: # sec_end_dist is distance between 0 end of soma to # the 0 or 1 end of section (whichever is closer) sec_end_dist = section_distance - (section.L / 2) seg_xs.append(seg_x) seg_vals.append(val(sec_end_dist + (seg_x * section.L))) p_mech[attr] = [seg_xs, seg_vals] return self.sections def _create_synapses(self, sections, synapses): """Create synapses.""" for sec_name in sections: for receptor in sections[sec_name].syns: syn_key = f'{sec_name}_{receptor}' seg = self._nrn_sections[sec_name](0.5) self._nrn_synapses[syn_key] = self.syn_create( seg, **synapses[receptor]) def _create_sections(self, sections, cell_tree): """Create soma and set geometry. Notes ----- By default neuron uses xy plane for height and xz plane for depth. This is opposite for model as a whole, but convention is followed in this function ease use of gui. """ if 'soma' not in self.sections: raise KeyError('soma must be defined for cell') for sec_name in sections: sec = h.Section(name=f'{self.name}_{sec_name}') self._nrn_sections[sec_name] = sec h.pt3dclear(sec=sec) h.pt3dconst(0, sec=sec) # be explicit, see documentation for pt in sections[sec_name].end_pts: h.pt3dadd(pt[0], pt[1], pt[2], 1, sec=sec) # with pt3dconst==0, these will alter the 3d points defined above! sec.L = sections[sec_name].L sec.diam = sections[sec_name].diam sec.Ra = sections[sec_name].Ra sec.cm = sections[sec_name].cm sec.nseg = sections[sec_name].nseg if cell_tree is None: cell_tree = dict() # Connects sections of THIS cell together. for parent_node in cell_tree: for child_node in cell_tree[parent_node]: parent_sec = self._nrn_sections[parent_node[0]] child_sec = self._nrn_sections[child_node[0]] if parent_sec == child_sec: continue parent_loc = parent_node[1] child_loc = child_node[1] child_sec.connect(parent_sec, parent_loc, child_loc) # be explicit about letting sec.L dominate over the 3d points used by # h.pt3dadd(); see # https://nrn.readthedocs.io/en/latest/python/modelspec/programmatic/topology/geometry.html?highlight=pt3dadd#pt3dadd # noqa h.define_shape()
[docs] def build(self, sec_name_apical=None): """Build cell in Neuron and insert dipole if applicable. Parameters ---------- sec_name_apical : str | None If not None, a dipole will be inserted in this cell in alignment with this section. The section should belong to the apical dendrite of a pyramidal neuron. """ self._create_sections(self.sections, self.cell_tree) self._create_synapses(self.sections, self.synapses) self._set_biophysics(self.sections) if sec_name_apical in self._nrn_sections: self._insert_dipole(sec_name_apical) elif sec_name_apical is not None: raise ValueError(f'sec_name_apical must be an existing ' f'section of the current cell or None. ' f'Got {sec_name_apical}.')
[docs] def copy(self): """Return copy of instance.""" return deepcopy(self)
# two things need to happen here for h: # 1. dipole needs to be inserted into each section # 2. a list needs to be created with a Dipole (Point Process) in each # section at position 1 # In Cell() and not Pyr() for future possibilities def _insert_dipole(self, sec_name_apical): """Insert dipole into each section of this cell. Parameters ---------- sec_name_apical : str The name of the section along which dipole moment is calculated. """ self.dpl_vec = h.Vector(1) self.dpl_ref = self.dpl_vec._ref_x[0] cos_thetas = _get_cos_theta(self.sections, 'apical_trunk') # setting pointers and ztan values for sect_name in self.sections: sect = self._nrn_sections[sect_name] sect.insert('dipole') dpp = h.Dipole(1, sec=sect) # defined in dipole_pp.mod self.dipole_pp.append(dpp) dpp.ri = h.ri(1, sec=sect) # assign internal resistance # sets pointers in dipole mod file to the correct locations dpp._ref_pv = sect(0.99)._ref_v dpp._ref_Qtotal = self.dpl_ref # gives INTERNAL segments of the section, non-endpoints # creating this because need multiple values simultaneously pos_all = np.array([seg.x for seg in sect.allseg()]) seg_lens = np.diff(pos_all) * sect.L seg_lens_z = seg_lens * cos_thetas[sect_name] # alternative procedure below with y_long(itudinal) # y_long = (h.y3d(1, sec=sect) - h.y3d(0, sec=sect)) * pos # y_diff = np.diff(y_long) # doing range to index multiple values of the same # np.array simultaneously for idx, pos in enumerate(pos_all[1:-1]): # assign the ri value to the dipole # ri not defined at 0 and L sect(pos).dipole.ri = h.ri(pos, sec=sect) # range variable 'dipole' # set pointers to previous segment's voltage, with # boundary condition sect(pos).dipole._ref_pv = sect(pos_all[idx])._ref_v # set aggregate pointers sect(pos).dipole._ref_Qsum = dpp._ref_Qsum sect(pos).dipole._ref_Qtotal = self.dpl_ref # add ztan values sect(pos).dipole.ztan = seg_lens_z[idx] # set the pp dipole's ztan value to the last value from seg_lens_z dpp.ztan = seg_lens_z[-1] self.dipole = h.Vector().record(self.dpl_ref)
[docs] def create_tonic_bias(self, amplitude, t0, tstop, loc=0.5): """Create tonic bias at the soma. Parameters ---------- amplitude : float The amplitude of the input. t0 : float The start time of tonic input (in ms). tstop : float The end time of tonic input (in ms). loc : float (0 to 1) The location of the input in the soma section. """ stim = h.IClamp(self._nrn_sections['soma'](loc)) stim.delay = t0 stim.dur = tstop - t0 stim.amp = amplitude self.tonic_biases.append(stim)
[docs] def record(self, record_vsec=False, record_isec=False, record_ca=False): """ Record current and voltage from all sections Parameters ---------- record_vsec : 'all' | 'soma' | False Option to record voltages from all sections ('all'), or just the soma ('soma'). Default: False. record_isec : 'all' | 'soma' | False Option to record voltages from all sections ('all'), or just the soma ('soma'). Default: False. record_ca : 'all' | 'soma' | False Option to record calcium concentration from all sections ('all'), or just the soma ('soma'). Default: False. """ section_names = list(self.sections.keys()) # Logic checks if just recording soma, sections, or both if record_vsec == 'soma': self.vsec = dict.fromkeys(['soma']) elif record_vsec == 'all': self.vsec = dict.fromkeys(section_names) if record_vsec: for sec_name in self.vsec: self.vsec[sec_name] = h.Vector() self.vsec[sec_name].record( self._nrn_sections[sec_name](0.5)._ref_v) if record_isec == 'soma': self.isec = dict.fromkeys(['soma']) elif record_isec == 'all': self.isec = dict.fromkeys(section_names) if record_isec: for sec_name in self.isec: list_syn = [key for key in self._nrn_synapses.keys() if key.startswith(f'{sec_name}_')] self.isec[sec_name] = dict.fromkeys(list_syn) for syn_name in self.isec[sec_name]: self.isec[sec_name][syn_name] = h.Vector() self.isec[sec_name][syn_name].record( self._nrn_synapses[syn_name]._ref_i) # calcium concentration if record_ca == 'soma': self.ca = dict.fromkeys(['soma']) elif record_ca == 'all': self.ca = dict.fromkeys(section_names) if record_ca: for sec_name in self.ca: if hasattr(self._nrn_sections[sec_name](0.5), '_ref_cai'): self.ca[sec_name] = h.Vector() self.ca[sec_name].record( self._nrn_sections[sec_name](0.5)._ref_cai)
[docs] def syn_create(self, secloc, e, tau1, tau2): """Create an h.Exp2Syn synapse. Parameters ---------- secloc : instance of nrn.Segment The section location. E.g., soma(0.5). e: float Reverse potential (in mV) tau1: float Rise time (in ms) tau2: float Decay time (in ms) Returns ------- syn : instance of h.Exp2Syn A two state kinetic scheme synapse. """ if not isinstance(secloc, nrn.Segment): raise TypeError(f'secloc must be instance of' f'nrn.Segment. Got {type(secloc)}') syn = h.Exp2Syn(secloc) syn.e = e syn.tau1 = tau1 syn.tau2 = tau2 return syn
[docs] def setup_source_netcon(self, threshold): """Created for _PC.cell and specifies SOURCES of spikes. Parameters ---------- threshold : float The voltage threshold for action potential. """ nc = h.NetCon(self._nrn_sections['soma'](0.5)._ref_v, None, sec=self._nrn_sections['soma']) nc.threshold = threshold return nc
[docs] def parconnect_from_src(self, gid_presyn, nc_dict, postsyn, inplane_distance): """Parallel receptor-centric connect FROM presyn TO this cell, based on GID. Parameters ---------- gid_presyn : int The cell ID of the presynaptic neuron nc_dict : dict Dictionary with keys: pos_src, A_weight, A_delay, lamtha Defines the connection parameters postsyn : instance of h.Exp2Syn The postsynaptic cell object. inplane_distance : float The in plane-distance (in um) between pyramidal cell somas in the square grid. Returns ------- nc : instance of h.NetCon A network connection object. """ from .network_builder import _PC nc = _PC.gid_connect(gid_presyn, postsyn) # set props here. nc.threshold = nc_dict['threshold'] nc.weight[0], nc.delay = _get_gaussian_connection( nc_dict['pos_src'], self.pos, nc_dict, inplane_distance=inplane_distance) return nc
[docs] def plot_morphology(self, ax=None, color=None, pos=(0, 0, 0), xlim=(-250, 150), ylim=(-100, 100), zlim=(-100, 1200), show=True): """Plot the cell morphology. Parameters ---------- ax : instance of Axes3D Matplotlib 3D axis color : str | dict | None Color of cell. If str, entire cell plotted with color indicated by str. If dict, colors of individual sections can be specified. Must have a key for every section in cell as defined in the `Cell.sections` attribute. | Ex: ``{'apical_trunk': 'r', 'soma': 'b', ...}`` pos : tuple of int or float | None Position of cell soma. Must be a tuple of 3 elements for the (x, y, z) position of the soma in 3D space. Default: (0, 0, 0) xlim : tuple of int | tuple of float x limits of plot window. Default (-250, 150) ylim : tuple of int | tuple of float y limits of plot window. Default (-100, 100) zlim : tuple of int | tuple of float z limits of plot window. Default (-100, 1200) show : bool If True, show the plot Returns ------- axes : instance of Axes3D The matplotlib 3D axis handle. """ return plot_cell_morphology(self, ax=ax, color=color, pos=pos, xlim=xlim, ylim=ylim, zlim=zlim, show=show)
def _update_section_end_pts_L(self, node, dpt): if self.cell_tree is None: return x = self.sections[node[0]].end_pts[node[1]][0] y = self.sections[node[0]].end_pts[node[1]][1] z = self.sections[node[0]].end_pts[node[1]][2] self.sections[node[0]].end_pts[node[1]][0] = x + dpt[0] self.sections[node[0]].end_pts[node[1]][1] = y + dpt[1] self.sections[node[0]].end_pts[node[1]][2] = z + dpt[2] # If current node is a leaf node if node not in self.cell_tree: return # If current node is an internal node for child_node in self.cell_tree[node]: self._update_section_end_pts_L(child_node, dpt)
[docs] def define_shape(self, node): """Redefines end_pts according to section lengths. Detects change in section lengths of the sections in the subtree of the input node. Parameters ---------- node : tuple of size 2 The first element is the section name The second element is the node end used (0 or 1) Note ---- Using sec_name as 'soma' and node end as 0 checks for changes in any section length of the cell as (soma, 0) is the root node of the cell. """ # Python version of Neuron define_shape function # https://nrn.readthedocs.io/en/latest/python/modelspec/programmatic/topology/geometry.html?highlight=pt3dadd#pt3dadd # noqa # cell tree is None therefore cannot define shape if self.cell_tree is None: return # Find the end pts of the section node_opp_end = 1 if node[1] == 1: node_opp_end = 0 pts = self.sections[node[0]].end_pts x0, y0, z0 = pts[node[1]][0], pts[node[1]][1], pts[node[1]][2] x1, y1, z1 = (pts[node_opp_end][0], pts[node_opp_end][1], pts[node_opp_end][2]) # Find the factor by which length is changed end_1 = np.array((x0, y0, z0)) end_2 = np.array((x1, y1, z1)) old_len = np.linalg.norm(end_1 - end_2) new_len = self.sections[node[0]].L fac = new_len / old_len x_new = x0 + (x1 - x0) * fac y_new = y0 + (y1 - y0) * fac z_new = z0 + (z1 - z0) * fac # Find the change in coordinates dx = x_new - x1 dy = y_new - y1 dz = z_new - z1 dpt = [dx, dy, dz] # Update all coordinates in the subtree self._update_section_end_pts_L((node[0], node_opp_end), dpt) # Check for change in section lengths in the subtree if node in self.cell_tree: for child_node in self.cell_tree[node]: self.define_shape(child_node)
def _update_end_pts(self): """Update all end pts according to the length of the sections. Can be used whenever length of any section is updated Returns ------- Updated end pts for the cell """ if 'soma' not in self.sections: raise KeyError('soma must be defined for cell') # cell tree is None therefore no end_pts to update if self.cell_tree is None: return # shift cell to self.pos and reorient apical dendrite # along z direction of self.pos dx = self.pos[0] - self.sections['soma'].end_pts[0][0] dy = self.pos[1] - self.sections['soma'].end_pts[0][1] dz = self.pos[2] - self.sections['soma'].end_pts[0][2] for sec_name in self.sections: end_pts = self.sections[sec_name].end_pts updated_end_pts = list() for pt in end_pts: updated_end_pts.append( [ pt[0] + dx, pt[1] + dy, pt[2] + dz ] ) self.sections[sec_name]._end_pts = updated_end_pts # Check and update all end pts starting from root according to length # of sections. self.define_shape(('soma', 0))
[docs] def modify_section(self, sec_name, L=None, diam=None, cm=None, Ra=None): """Change attributes of section specified by `sec_name` Parameters ---------- sec_name : str Name of section to be modified. Must be a key of Cell.sections L : float | int | None length of a section in microns. Default None. diam : float | int | None diameter of a section in microns. cm : float | int | None membrane capacitance in micro-Farads. Ra : float | int | None axial resistivity in ohm-cm. Notes ----- Leaving default of None produces no change. """ valid_sec_names = list(self.sections.keys()) _check_option('sec_name', sec_name, valid_sec_names) if L is not None: _validate_type(L, (float, int), 'L') self.sections[sec_name]._L = L if diam is not None: _validate_type(diam, (float, int), 'diam') self.sections[sec_name]._diam = diam if cm is not None: _validate_type(cm, (float, int), 'cm') self.sections[sec_name]._cm = cm if Ra is not None: _validate_type(Ra, (float, int), 'Ra') self.sections[sec_name]._Ra = Ra self._update_end_pts()