Skip to content

Commit

Permalink
Enable injecting Python generated Stimuli [vcs: #minor] (#142)
Browse files Browse the repository at this point in the history
* make inject_current_waveform more readable

* rewrite add_step in Python

* remove injectCurrentWaveform[deprecated]

* rewrite add_ramp in Python

* move circuit_stimulus_definitions into stimulus

* docstring unit fix A->nA

* inject dt param to add_step and add_ramp

* add StimulusFactory

* add stimuli notebook

* update singlecell.ipynb

* update api docs

* run example notebook tests in isolation

* import libsonata before NEURON for libc conflict

* try only running 5-stimuli notebook

* disable multiple processes in example tests

* debug stimuli.ipynb

* use stimulus factory in add_step and add_ramp

* [Docs] add List of Stimuli page

* update CHANGELOG

* add features to combine Stimulus objects

* test combining multiple stimuli

* add other step kind stimuli

* cover uncovered test code in test_factory

* exclude ... lines from coverage

* pin pytest<8.1.0  gh:pytest/issues/12065

* Revert "pin pytest<8.1.0  gh:pytest/issues/12065"

This reverts commit c4e42e4.

* remove @staticmethod in test

* keep only examples tox env <8.1.0

* update pytest & nbmake dependencies

* [DOCS] add multiple stimuli on the same plot
  • Loading branch information
anilbey authored Mar 4, 2024
1 parent b8330e2 commit afded40
Show file tree
Hide file tree
Showing 29 changed files with 1,310 additions and 106 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Changelog
==========

2.4.0
------

* Add StimulusFactory enabling Python generated Stimulus creation
* Stimulus creation through StimulusFactory is decoupled from the Cell object
* Cell.add_step and Cell.add_ramp use StimulusFactory
* Move stimuli module to stimulus/circuit_stimulus_definitions
* [DOCS] Add list of stimuli page that describe, generate and plot the stimuli
* Add jupyter notebook displaying how to inject the StimulusFactory Stimulus into Cell

2.3.0
-------

Expand Down
2 changes: 1 addition & 1 deletion bluecellulab/cell/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from bluecellulab.importer import load_hoc_and_mod_files
from bluecellulab.neuron_interpreter import eval_neuron
from bluecellulab.rngsettings import RNGSettings
from bluecellulab.stimuli import SynapseReplay
from bluecellulab.stimulus.circuit_stimulus_definitions import SynapseReplay
from bluecellulab.synapse import SynapseFactory, Synapse
from bluecellulab.synapse.synapse_types import SynapseID
from bluecellulab.type_aliases import HocObjectType, NeuronSection
Expand Down
127 changes: 72 additions & 55 deletions bluecellulab/cell/injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Contains injection functionality for the cell."""

from __future__ import annotations
import math
import warnings
import logging
Expand All @@ -27,7 +27,7 @@
get_relative_shotnoise_params,
)
from bluecellulab.exceptions import BluecellulabError
from bluecellulab.stimuli import (
from bluecellulab.stimulus.circuit_stimulus_definitions import (
ClampMode,
Hyperpolarizing,
Noise,
Expand All @@ -36,7 +36,8 @@
RelativeOrnsteinUhlenbeck,
RelativeShotNoise,
)
from bluecellulab.type_aliases import HocObjectType
from bluecellulab.stimulus.factory import StimulusFactory
from bluecellulab.type_aliases import NeuronSection


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -69,36 +70,62 @@ def add_pulse(self, stimulus):
self.persistent.append(tstim)
return tstim

def add_step(self, start_time, stop_time, level, section=None, segx=0.5):
"""Add a step current injection."""
if section is None:
section = self.soma

tstim = neuron.h.TStim(segx, sec=section)
duration = stop_time - start_time
tstim.pulse(start_time, duration, level)
self.persistent.append(tstim)
return tstim
def add_step(
self,
start_time: float,
stop_time: float,
level: float,
section: NeuronSection | None = None,
segx: float = 0.5,
dt: float = 0.025
) -> tuple[np.ndarray, np.ndarray]:
"""Add a step current injection.
def add_ramp(self, start_time, stop_time, start_level, stop_level,
section=None, segx=0.5):
"""Add a ramp current injection."""
if section is None:
section = self.soma
Args:
start_time: Start time of the step injection in seconds.
stop_time: Stop time of the step injection in seconds.
level: Current level to inject in nanoamperes (nA).
section: The section to inject current into.
Defaults to the soma section.
segx: The fractional location within the section to inject.
Defaults to 0.5 (center of the section).
Returns:
Tuple of time and current data.
"""
stim = StimulusFactory(dt=dt).step(start_time, stop_time, level)
t_content, i_content = stim.time, stim.current
self.inject_current_waveform(t_content, i_content, section, segx)
return (t_content, i_content)

tstim = neuron.h.TStim(segx, sec=section)
def add_ramp(
self,
start_time: float,
stop_time: float,
start_level: float,
stop_level: float,
section: NeuronSection | None = None,
segx: float = 0.5,
dt: float = 0.025
) -> tuple[np.ndarray, np.ndarray]:
"""Add a ramp current injection.
tstim.ramp(
0.0,
start_time,
start_level,
stop_level,
stop_time - start_time,
0.0,
0.0)
Args:
start_time: Start time of the ramp injection in seconds.
stop_time: Stop time of the ramp injection in seconds.
start_level: Current level at the start of the ramp in nanoamperes (nA).
stop_level: Current level at the end of the ramp in nanoamperes (nA).
section: The section to inject current into (optional). Defaults to soma.
segx: The fractional location within the section to inject (optional).
Returns:
A tuple of numpy arrays containing time and current data.
"""
stim = StimulusFactory(dt=dt).ramp(start_time, stop_time, start_level, stop_level)
t_content, i_content = stim.time, stim.current
self.inject_current_waveform(t_content, i_content, section, segx)

self.persistent.append(tstim)
return tstim
return t_content, i_content

def add_voltage_clamp(
self, stop_time, level, rs=None, section=None, segx=0.5,
Expand Down Expand Up @@ -395,32 +422,22 @@ def add_relative_ornstein_uhlenbeck(
else:
return self.inject_current_clamp_signal(section, segx, tvec, svec)

def inject_current_waveform(self, t_content, i_content, section=None,
segx=0.5):
"""Inject a custom current to the cell."""
start_time = t_content[0]
stop_time = t_content[-1]
time = neuron.h.Vector()
currents = neuron.h.Vector()
time = time.from_python(t_content)
currents = currents.from_python(i_content)

def inject_current_waveform(self, t_content, i_content, section=None, segx=0.5):
"""Inject a custom current waveform into the cell."""
if section is None:
section = self.soma

time_vector = neuron.h.Vector().from_python(t_content)
current_vector = neuron.h.Vector().from_python(i_content)

pulse = neuron.h.IClamp(segx, sec=section)
self.persistent.append(pulse)
self.persistent.append(time)
self.persistent.append(currents)
setattr(pulse, 'del', start_time)
pulse.dur = stop_time - start_time
currents.play(pulse._ref_amp, time)
return currents

@deprecated("Use inject_current_waveform instead.")
def injectCurrentWaveform(self, t_content, i_content, section=None,
segx=0.5):
"""Inject a current in the cell."""
return self.inject_current_waveform(t_content, i_content, section, segx)
self.persistent.extend([pulse, time_vector, current_vector])

pulse.delay = t_content[0]
pulse.dur = t_content[-1] - t_content[0]
current_vector.play(pulse._ref_amp, time_vector)

return current_vector

@deprecated("Use add_sin_current instead.")
def addSineCurrentInject(self, start_time, stop_time, freq,
Expand All @@ -435,7 +452,7 @@ def addSineCurrentInject(self, start_time, stop_time, freq,
t_content = np.arange(start_time, stop_time, dt)
i_content = [amplitude * math.sin(freq * (x - start_time) * (
2 * math.pi)) + mid_level for x in t_content]
self.injectCurrentWaveform(t_content, i_content)
self.inject_current_waveform(t_content, i_content)
return (t_content, i_content)

def add_sin_current(self, amp, start_time, duration, frequency,
Expand All @@ -454,9 +471,9 @@ def add_alpha_synapse(
tau: float,
gmax: float,
e: float,
section: HocObjectType,
section: NeuronSection,
segx=0.5,
) -> HocObjectType:
) -> NeuronSection:
"""Add an AlphaSynapse NEURON point process stimulus to the cell."""
syn = neuron.h.AlphaSynapse(segx, sec=section)
syn.onset = onset
Expand Down
2 changes: 1 addition & 1 deletion bluecellulab/circuit/config/bluepy_simulation_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from bluepy.utils import open_utf8

from bluecellulab.circuit.config.sections import Conditions, ConnectionOverrides
from bluecellulab.stimuli import Stimulus
from bluecellulab.stimulus.circuit_stimulus_definitions import Stimulus


class BluepySimulationConfig:
Expand Down
2 changes: 1 addition & 1 deletion bluecellulab/circuit/config/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


from bluecellulab.circuit.config.sections import Conditions, ConnectionOverrides
from bluecellulab.stimuli import Stimulus
from bluecellulab.stimulus.circuit_stimulus_definitions import Stimulus


class SimulationConfig(Protocol):
Expand Down
2 changes: 1 addition & 1 deletion bluecellulab/circuit/config/sonata_simulation_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from typing import Optional

from bluecellulab.circuit.config.sections import Conditions, ConnectionOverrides
from bluecellulab.stimuli import Stimulus
from bluecellulab.stimulus.circuit_stimulus_definitions import Stimulus

from bluepysnap import Simulation as SnapSimulation

Expand Down
22 changes: 11 additions & 11 deletions bluecellulab/ssim.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
from bluecellulab.circuit.node_id import create_cell_id, create_cell_ids
from bluecellulab.circuit.simulation_access import BluepySimulationAccess, SimulationAccess, SonataSimulationAccess, _sample_array
from bluecellulab.importer import load_hoc_and_mod_files
from bluecellulab.stimuli import Noise, OrnsteinUhlenbeck, RelativeOrnsteinUhlenbeck, RelativeShotNoise, ShotNoise
import bluecellulab.stimuli as stimuli
from bluecellulab.stimulus.circuit_stimulus_definitions import Noise, OrnsteinUhlenbeck, RelativeOrnsteinUhlenbeck, RelativeShotNoise, ShotNoise
import bluecellulab.stimulus.circuit_stimulus_definitions as circuit_stimulus_definitions
from bluecellulab.exceptions import BluecellulabError
from bluecellulab.simulation import (
set_global_condition_parameters,
Expand Down Expand Up @@ -298,40 +298,40 @@ def _add_stimuli(self, add_noise_stimuli=False,
for cell_id in self.cells:
if cell_id not in gids_of_target:
continue
if isinstance(stimulus, stimuli.Noise):
if isinstance(stimulus, circuit_stimulus_definitions.Noise):
if add_noise_stimuli:
self.cells[cell_id].add_replay_noise(
stimulus, noisestim_count=noisestim_count)
elif isinstance(stimulus, stimuli.Hyperpolarizing):
elif isinstance(stimulus, circuit_stimulus_definitions.Hyperpolarizing):
if add_hyperpolarizing_stimuli:
self.cells[cell_id].add_replay_hypamp(stimulus)
elif isinstance(stimulus, stimuli.Pulse):
elif isinstance(stimulus, circuit_stimulus_definitions.Pulse):
if add_pulse_stimuli:
self.cells[cell_id].add_pulse(stimulus)
elif isinstance(stimulus, stimuli.RelativeLinear):
elif isinstance(stimulus, circuit_stimulus_definitions.RelativeLinear):
if add_relativelinear_stimuli:
self.cells[cell_id].add_replay_relativelinear(stimulus)
elif isinstance(stimulus, stimuli.ShotNoise):
elif isinstance(stimulus, circuit_stimulus_definitions.ShotNoise):
if add_shotnoise_stimuli:
self.cells[cell_id].add_replay_shotnoise(
self.cells[cell_id].soma, 0.5, stimulus,
shotnoise_stim_count=shotnoise_stim_count)
elif isinstance(stimulus, stimuli.RelativeShotNoise):
elif isinstance(stimulus, circuit_stimulus_definitions.RelativeShotNoise):
if add_shotnoise_stimuli:
self.cells[cell_id].add_replay_relative_shotnoise(
self.cells[cell_id].soma, 0.5, stimulus,
shotnoise_stim_count=shotnoise_stim_count)
elif isinstance(stimulus, stimuli.OrnsteinUhlenbeck):
elif isinstance(stimulus, circuit_stimulus_definitions.OrnsteinUhlenbeck):
if add_ornstein_uhlenbeck_stimuli:
self.cells[cell_id].add_ornstein_uhlenbeck(
self.cells[cell_id].soma, 0.5, stimulus,
stim_count=ornstein_uhlenbeck_stim_count)
elif isinstance(stimulus, stimuli.RelativeOrnsteinUhlenbeck):
elif isinstance(stimulus, circuit_stimulus_definitions.RelativeOrnsteinUhlenbeck):
if add_ornstein_uhlenbeck_stimuli:
self.cells[cell_id].add_relative_ornstein_uhlenbeck(
self.cells[cell_id].soma, 0.5, stimulus,
stim_count=ornstein_uhlenbeck_stim_count)
elif isinstance(stimulus, stimuli.SynapseReplay): # sonata only
elif isinstance(stimulus, circuit_stimulus_definitions.SynapseReplay): # sonata only
if self.circuit_access.target_contains_cell(
stimulus.target, cell_id
):
Expand Down
1 change: 1 addition & 0 deletions bluecellulab/stimulus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .factory import StimulusFactory
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
# 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.
"""Defines the expected data structures associated with the stimulus defined in
simulation configs.
Run-time validates the data via Pydantic.
"""
from __future__ import annotations

from enum import Enum
Expand Down
Loading

0 comments on commit afded40

Please sign in to comment.