Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MRG] add method for updating cell biophysics from Network instance #321

Merged
merged 34 commits into from May 18, 2021
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e7b565a
create Network.set_cell_biophysics method
rythorpe Apr 26, 2021
9923c35
instantiate Cell objects in Network
rythorpe May 12, 2021
9360344
append by cell type
rythorpe May 12, 2021
cb07c1b
create gid-to-cell lookup method
rythorpe May 12, 2021
e970c14
add cell_properties attribute to Network
rythorpe May 12, 2021
30d1a74
call Cell.build() from network_builder.py
rythorpe May 12, 2021
e8a7457
remove pos arg from default cell funcs
rythorpe May 12, 2021
1b9f491
adjust tests
rythorpe May 12, 2021
e71cee5
instantiate cells in network_builder.py
rythorpe May 13, 2021
c8ee14f
demo in plot_simulate_gamma.py
rythorpe May 13, 2021
5a15db9
flake8
rythorpe May 13, 2021
40857a6
remove set_biophysics method
rythorpe May 13, 2021
bc89478
revert to when cells were instantiated in Network
rythorpe May 14, 2021
0ba4c25
fix to pass tests
rythorpe May 14, 2021
3320b7c
Revert "remove pos arg from default cell funcs"
rythorpe May 14, 2021
a03f6ad
reinstill pos arg in default cell funcs and fix Network.copy()
rythorpe May 14, 2021
cf9ef0f
streamline Cell.build() and pass tests
rythorpe May 14, 2021
4b173b5
gamma example
rythorpe May 14, 2021
f2efb8a
store template Cell objects in Network.cell_types
rythorpe May 14, 2021
fbe9d67
rename create_cells->update_cells
rythorpe May 14, 2021
89471de
make update_cells() private and call from NetworkBuilder
rythorpe May 14, 2021
78016c8
clean up cell arguments
rythorpe May 14, 2021
f20cb14
make insert_dipole private
rythorpe May 14, 2021
ecbe3c3
nitpicks
rythorpe May 14, 2021
c3c2923
add sec_name_apical test
rythorpe May 14, 2021
f3c89d2
make NetworkBuilder.cells private
rythorpe May 17, 2021
2c76882
docstring
rythorpe May 17, 2021
b613cc3
create test for _update_cells()
rythorpe May 17, 2021
3250b19
add _cell_type_names attribute to CellResponse
rythorpe May 17, 2021
ae52db7
adjust sphinxopts for circleci
rythorpe May 17, 2021
da58797
update whats_new.rst
rythorpe May 17, 2021
e1bfa15
simplify network test
rythorpe May 17, 2021
66dda85
fix whats_new.rst
rythorpe May 17, 2021
e9c79bf
remove Network._get_src_type_and_pos() method
rythorpe May 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/Makefile
Expand Up @@ -2,7 +2,7 @@
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXOPTS = -v
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
Expand Down
11 changes: 8 additions & 3 deletions doc/whats_new.rst
Expand Up @@ -20,6 +20,8 @@ Changelog

- Compute dipole component in z-direction automatically from cell morphology instead of hard coding, by `Mainak Jas`_ in `#327 <https://github.com/jonescompneurolab/hnn-core/pull/320>`_

- Store :class:`~hnn_core.Cell` instances in :class:`~hnn_core.Network`'s :attr:`~/hnn_core.Network.cells` attribute by `Ryan Thorpe`_ in `#321 <https://github.com/jonescompneurolab/hnn-core/pull/321>`_

Bug
~~~

Expand All @@ -30,7 +32,10 @@ API
- Remove :class:`~hnn_core.L2Pyr`, :class:`~hnn_core.L5Pyr`, :class:`~hnn_core.L2Basket`, and :class:`~hnn_core.L5Basket` classes
in favor of instantation through functions and a more consistent :class:`~hnn_core.Cell` class by `Mainak Jas`_ in `#322 <https://github.com/jonescompneurolab/hnn-core/pull/320>`_

- Remove parameter `distribution` in :func:`~hnn_core.Network.add_bursty_drive`. The distribution is now Gaussian by default, by `Mainak Jas` in `#330 <https://github.com/jonescompneurolab/hnn-core/pull/330>`_
- Remove parameter `distribution` in :func:`~hnn_core.Network.add_bursty_drive`. The distribution is now Gaussian by default, by `Mainak Jas`_ in `#330 <https://github.com/jonescompneurolab/hnn-core/pull/330>`_

- New API for accessing and modifying :class:`~hnn_core.Cell` attributes (e.g., synapse and biophysics parameters) as cells are now instantiated from template cells specified
in a :class:`~hnn_core.Network` instance's :attr:`~/hnn_core.Network.cell_types` attribute by `Ryan Thorpe`_ in `#321 <https://github.com/jonescompneurolab/hnn-core/pull/321>`_

.. _0.1:

Expand Down Expand Up @@ -74,9 +79,9 @@ Changelog

- Add functions for plotting power spectral density (:func:`~hnn_core.viz.plot_psd`) and Morlet time-frequency representations (:func:`~hnn_core.viz.plot_tfr_morlet`), by `Christopher Bailey`_ in `#264 <https://github.com/jonescompneurolab/hnn-core/pull/264>`_

- Add y-label units (nAm) to all visualisation functions involving dipole moments, by `Christopher Bailey`_ in `#264 <https://github.com/jonescompneurolab/hnn-core/pull/264>`_
- Add y-label units (nAm) to all visualisation functions involving dipole moments, by `Christopher Bailey`_ in `#264 <https://github.com/jonescompneurolab/hnn-core/pull/264>`_

- Add Savitzky-Golay filtering method :meth:`~hnn_core.dipole.Dipole.savgol_filter` to ``Dipole``; copied from ``mne-python`` :meth:`~mne.Evoked.savgol_filter`, by `Christopher Bailey`_ in `#264 <https://github.com/jonescompneurolab/hnn-core/pull/264>`_
- Add Savitzky-Golay filtering method :meth:`~hnn_core.dipole.Dipole.savgol_filter` to ``Dipole``; copied from ``mne-python`` :meth:`~mne.Evoked.savgol_filter`, by `Christopher Bailey`_ in `#264 <https://github.com/jonescompneurolab/hnn-core/pull/264>`_

Bug
~~~
Expand Down
17 changes: 16 additions & 1 deletion examples/plot_simulate_gamma.py
Expand Up @@ -82,7 +82,7 @@
dpls[trial_idx].plot_tfr_morlet(freqs, n_cycles=7, tmin=tmin, ax=axes[1])

###############################################################################
# As a final exercise, let us try to re-run the simulation with a tonic bias
# Now, let us try to re-run the simulation with a tonic bias
# applied to the L5 Pyramidal cells. Notice that the oscillation waveform is
# more regular, with less noise due to the fact that the tonic depolarization
# dominates over the influence of the Poisson drive. By default, a tonic bias
Expand All @@ -108,6 +108,21 @@
from hnn_core.viz import plot_psd
plot_psd(dpls[trial_idx], fmin=20., fmax=100., tmin=tmin)

###############################################################################
# Finally, we demonstrate the mechanistic link between PING and the GABAA decay
# time constant (`tau2`). Using the same network/drive configuration as before,
# we decrease `tau2` from 5 to 2 ms. This will shorten the effective
# refactory period between L5 pyramidal cell spikes and increase the PING
# frequency from ~50 to ~65 Hz.
net.cell_types['L5_pyramidal'].p_syn['gabaa']['tau2'] = 2
Copy link
Contributor

@ntolley ntolley May 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rythorpe what do you think about adding a few print statements (perhaps in another code block) to show off how you can inspect the biophysical parameter values?

Edit:
On second thought, a followup PR could just add a clean __repr__ that summarizes the important parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're thinking like a more verbose Cell.__repr__() method?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah exactly! I'm getting of the mindset that any API we add to modify parameters will need to come with a really straightforward way to inspect those parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds great actually. I'll leave that for the next PR since there's talk of potentially combining the Cell and CellResponse classes.

dpls = simulate_dipole(net, n_trials=1)
rythorpe marked this conversation as resolved.
Show resolved Hide resolved

fig, axes = plt.subplots(3, 1, sharex=True, figsize=(6, 6),
constrained_layout=True)
dpls[trial_idx].plot(ax=axes[0], show=False)
net.cell_response.plot_spikes_raster(ax=axes[1], show=False)
dpls[trial_idx].plot_tfr_morlet(freqs, n_cycles=7, tmin=tmin, ax=axes[2])

###############################################################################
# References
# ----------
Expand Down
128 changes: 66 additions & 62 deletions hnn_core/cell.py
Expand Up @@ -91,6 +91,29 @@ class Cell:
The name of the cell.
pos : tuple
The (x, y, z) coordinates.
p_secs : nested dict
Dictionary with keys as section name.
p_secs[sec_name] is a dictionary with keys
L, diam, Ra, cm, syns and mech.
syns is a list specifying the synapses at that section.
The properties of syn are specified in p_syn.
mech is a dict with keys as the mechanism names. The
values are dictionaries with properties of the mechanism.
p_syn : dict of dict
Keys are name of synaptic mechanism. Each synaptic mechanism
has keys for parameters of the mechanism, e.g., 'e', 'tau1',
'tau2'.
topology : list of list
The topology of cell sections. Each element is a list of
4 items in the format
[parent_sec, parent_loc, child_sec, child_loc] where
parent_sec and parent_loc are float between 0 and 1
specifying the location in the section to connect and
parent_sec and child_sec are names of the connecting
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
Expand Down Expand Up @@ -122,15 +145,37 @@ class Cell:
sect_loc : dict of list
Can have keys 'proximal' or 'distal' each containing
names of section locations that are proximal or distal.

Examples
--------
>>> p_secs = {
'soma':
{
'L': 39,
'diam': 20,
'cm': 0.85,
'Ra': 200.,
'sec_pts': [[0, 0, 0], [0, 39., 0]],
'syns': ['ampa', 'gabaa', 'nmda'],
'mechs' : {
'ca': {
'gbar_ca': 60
}
}
}
}
"""

def __init__(self, name, pos, gid=None):
def __init__(self, name, pos, p_secs, p_syn, topology, sect_loc, gid=None):
self.name = name
self.pos = pos
self.p_secs = p_secs
self.p_syn = p_syn
self.topology = topology
self.sect_loc = sect_loc
self.sections = dict()
self.synapses = dict()
self.dipole_pp = list()
self.sect_loc = dict()
self.rec_v = h.Vector()
self.rec_i = dict()
# insert iclamp
Expand Down Expand Up @@ -232,60 +277,27 @@ def _create_sections(self, p_secs, topology):
child_loc = connection[3]
child_sec.connect(parent_sec, parent_loc, child_loc)

def build(self, p_secs, p_syn, topology, sect_loc):
"""Build cell in Neuron.
def build(self, sec_name_apical=None):
"""Build cell in Neuron and insert dipole if applicable.

Parameters
----------
p_secs : nested dict
Dictionary with keys as section name.
p_secs[sec_name] is a dictionary with keys
L, diam, Ra, cm, syns and mech.
syns is a list specifying the synapses at that section.
The properties of syn are specified in p_syn.
mech is a dict with keys as the mechanism names. The
values are dictionaries with properties of the mechanism.
p_syn : dict of dict
Keys are name of synaptic mechanism. Each synaptic mechanism
has keys for parameters of the mechanism, e.g., 'e', 'tau1',
'tau2'.
topology : list of list
The topology of cell sections. Each element is a list of
4 items in the format
[parent_sec, parent_loc, child_sec, child_loc] where
parent_sec and parent_loc are float between 0 and 1
specifying the location in the section to connect and
parent_sec and child_sec are names of the connecting
sections.
sect_loc : dict of list
Can have keys 'proximal' or 'distal' each containing
names of section locations that are proximal or distal.

Examples
--------
>>> p_secs = {
'soma':
{
'L': 39,
'diam': 20,
'cm': 0.85,
'Ra': 200.,
'sec_pts': [[0, 0, 0], [0, 39., 0]],
'syns': ['ampa', 'gabaa', 'nmda'],
'mechs' : {
'ca': {
'gbar_ca': 60
}
}
}
}
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.
"""
if 'soma' not in p_secs:
if 'soma' not in self.p_secs:
raise KeyError('soma must be defined for cell')
self._create_sections(p_secs, topology)
self._create_synapses(p_secs, p_syn)
self._set_biophysics(p_secs)
self.sect_loc = sect_loc
self._create_sections(self.p_secs, self.topology)
self._create_synapses(self.p_secs, self.p_syn)
self._set_biophysics(self.p_secs)
if sec_name_apical in self.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}.')

def move_to_pos(self):
"""Move cell to position."""
Expand All @@ -306,28 +318,20 @@ def move_to_pos(self):
# 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, p_secs, sec_name_apical):
def _insert_dipole(self, sec_name_apical):
"""Insert dipole into each section of this cell.

Parameters
----------
p_secs : nested dict
Dictionary with keys as section name.
p_secs[sec_name] is a dictionary with keys
L, diam, Ra, cm, syns and mech.
syns is a list specifying the synapses at that section.
The properties of syn are specified in p_syn.
mech is a dict with keys as the mechanism names. The
values are dictionaries with properties of the mechanism.
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(p_secs, 'apical_trunk')
cos_thetas = _get_cos_theta(self.p_secs, 'apical_trunk')

# setting pointers and ztan values
for sect_name in p_secs:
for sect_name in self.p_secs:
sect = self.sections[sect_name]
sect.insert('dipole')

Expand Down
16 changes: 12 additions & 4 deletions hnn_core/cell_response.py
Expand Up @@ -27,6 +27,12 @@ class CellResponse(object):
The inner list contains the type of spike (e.g., evprox1
or L2_pyramidal) that occured at the corresonding time stamp.
Each gid corresponds to a type via Network().gid_ranges.
times : numpy array | None
Array of time points for samples in continuous data.
This includes vsoma and isoma.
cell_type_names : list
List of unique cell type names that are explicitly modeled in the
network

Attributes
----------
Expand All @@ -48,7 +54,6 @@ class CellResponse(object):
isoma : list (n_trials,) of dict, shape
Each element of the outer list is a trial.
Dictionary indexed by gids containing somatic currents.

times : numpy array
Array of time points for samples in continuous data.
This includes vsoma and isoma.
Expand All @@ -70,7 +75,10 @@ class CellResponse(object):
"""

def __init__(self, spike_times=None, spike_gids=None, spike_types=None,
times=None):
times=None, cell_type_names=['L2_basket',
'L2_pyramidal',
'L5_basket',
'L5_pyramidal']):
if spike_times is None:
spike_times = list()
if spike_gids is None:
Expand Down Expand Up @@ -106,6 +114,7 @@ def __init__(self, spike_times=None, spike_gids=None, spike_types=None,
if not isinstance(times, np.ndarray):
raise TypeError("'times' is an np.ndarray of simulation times")
self._times = times
self._cell_type_names = cell_type_names

def __repr__(self):
class_name = self.__class__.__name__
Expand Down Expand Up @@ -281,7 +290,6 @@ def mean_rates(self, tstart, tstop, gid_ranges, mean_type='all'):
spike_rate : dict
Dictionary with keys 'L5_pyramidal', 'L5_basket', etc.
"""
cell_types = ['L5_pyramidal', 'L5_basket', 'L2_pyramidal', 'L2_basket']
spike_rates = dict()

if mean_type not in ['all', 'trial', 'cell']:
Expand All @@ -295,7 +303,7 @@ def mean_rates(self, tstart, tstop, gid_ranges, mean_type='all'):
elif tstop <= tstart:
raise ValueError('tstop must be greater than tstart')

for cell_type in cell_types:
for cell_type in self._cell_type_names:
cell_type_gids = np.array(gid_ranges[cell_type])
n_trials, n_cells = len(self._spike_times), len(cell_type_gids)
gid_spike_rate = np.zeros((n_trials, n_cells))
Expand Down
42 changes: 24 additions & 18 deletions hnn_core/cells_default.py
Expand Up @@ -146,15 +146,20 @@ def _get_mechanisms(p_all, cell_type, section_names, mechanisms):
return mech_props


def basket(pos, cell_name='L2Basket', gid=None):
def _set_variable_mech(dist_from_soma):
"""Set a cell mechanism based on its distance from the soma"""
return 1e-6 * np.exp(3e-3 * dist_from_soma)


def basket(cell_name, pos=(0, 0, 0), gid=None):
"""Get layer 2 / layer 5 basket cells.

Parameters
----------
pos : tuple
Coordinates of cell soma in xyz-space
cell_name : str
The name of the cell.
pos : tuple
Coordinates of cell soma in xyz-space
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
Expand Down Expand Up @@ -182,21 +187,23 @@ def basket(pos, cell_name='L2Basket', gid=None):
for sec_name in p_secs:
p_secs[sec_name]['sec_pts'] = sec_pts[sec_name]

cell = Cell(cell_name, pos=pos, gid=gid)
cell.build(p_secs, p_syn, topology, sect_loc)

return cell
return Cell(cell_name, pos,
p_secs=p_secs,
p_syn=p_syn,
topology=topology,
sect_loc=sect_loc,
gid=gid)


def pyramidal(pos, cell_name, override_params=None, gid=None):
def pyramidal(cell_name, pos=(0, 0, 0), override_params=None, gid=None):
"""Pyramidal neuron.

Parameters
----------
pos : tuple
Coordinates of cell soma in xyz-space
cell_name : str
'L5Pyr' or 'L2Pyr'. The pyramidal cell type.
pos : tuple
Coordinates of cell soma in xyz-space
override_params : dict or None (optional)
Parameters specific to L2 pyramidal neurons to override the default set
gid : int or None (optional)
Expand Down Expand Up @@ -258,7 +265,7 @@ def pyramidal(pos, cell_name, override_params=None, gid=None):
syns = list(p_syn.keys())
if cell_name == 'L5Pyr':
p_secs[key]['mechs'][
'ar']['gbar_ar'] = lambda x: 1e-6 * np.exp(3e-3 * x)
'ar']['gbar_ar'] = _set_variable_mech
p_secs[key]['syns'] = syns

for sec_name in p_secs:
Expand All @@ -267,10 +274,9 @@ def pyramidal(pos, cell_name, override_params=None, gid=None):
sect_loc = {'proximal': ['apical_oblique', 'basal_2', 'basal_3'],
'distal': ['apical_tuft']}

cell = Cell(name=cell_name, pos=pos, gid=gid)
cell.build(p_secs, p_syn, topology, sect_loc=sect_loc)

# insert dipole
cell.insert_dipole(p_secs, 'apical_trunk')

return cell
return Cell(cell_name, pos,
p_secs=p_secs,
p_syn=p_syn,
topology=topology,
sect_loc=sect_loc,
gid=gid)