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

Enable injecting Python generated Stimuli [vcs: #minor] #142

Merged
merged 30 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ab379cd
make inject_current_waveform more readable
anilbey Feb 26, 2024
487dc23
rewrite add_step in Python
anilbey Feb 26, 2024
848803e
remove injectCurrentWaveform[deprecated]
anilbey Feb 27, 2024
cb202cd
rewrite add_ramp in Python
anilbey Feb 27, 2024
c0bdafa
move circuit_stimulus_definitions into stimulus
anilbey Feb 27, 2024
226b389
docstring unit fix A->nA
anilbey Feb 27, 2024
a24b159
inject dt param to add_step and add_ramp
anilbey Feb 28, 2024
7621be1
add StimulusFactory
anilbey Feb 28, 2024
76583bc
add stimuli notebook
anilbey Feb 28, 2024
364c936
update singlecell.ipynb
anilbey Feb 28, 2024
9ccdc70
update api docs
anilbey Feb 28, 2024
50da338
run example notebook tests in isolation
anilbey Feb 28, 2024
1a5857e
import libsonata before NEURON for libc conflict
anilbey Feb 29, 2024
073c17a
try only running 5-stimuli notebook
anilbey Feb 29, 2024
5f5c8e4
disable multiple processes in example tests
anilbey Feb 29, 2024
dcd6bf1
debug stimuli.ipynb
anilbey Feb 29, 2024
bfcc6bf
use stimulus factory in add_step and add_ramp
anilbey Feb 29, 2024
8d47eeb
[Docs] add List of Stimuli page
anilbey Feb 29, 2024
2d5114e
update CHANGELOG
anilbey Feb 29, 2024
bb92e1b
add features to combine Stimulus objects
anilbey Mar 1, 2024
a6a6cac
test combining multiple stimuli
anilbey Mar 1, 2024
e39fd4d
add other step kind stimuli
anilbey Mar 1, 2024
08a5ac3
cover uncovered test code in test_factory
anilbey Mar 4, 2024
bfc8d3a
exclude ... lines from coverage
anilbey Mar 4, 2024
c4e42e4
pin pytest<8.1.0 gh:pytest/issues/12065
anilbey Mar 4, 2024
75f4c39
Revert "pin pytest<8.1.0 gh:pytest/issues/12065"
anilbey Mar 4, 2024
48abf5a
remove @staticmethod in test
anilbey Mar 4, 2024
6ba5746
keep only examples tox env <8.1.0
anilbey Mar 4, 2024
755b8f7
update pytest & nbmake dependencies
anilbey Mar 4, 2024
7ad1274
[DOCS] add multiple stimuli on the same plot
anilbey Mar 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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