Skip to content

Commit c5e3ebb

Browse files
authored
Add FI curve (#5)
* add FI curve * support injecting and recording location in run_stimulus * add injecting and recording location support for iv and fi curves * add a helper function to validate a section/segment position * enhance spikedetector with support for custom locations * add unit tests for validate_section_and_segment * add unit tests for run_stimulus * improve robustness of 'create_netcon_spikedetector' * add unit tests * add support for calculating rheobase at specific sections/segments * improve error handling in method * add unit tests
1 parent 4a09b37 commit c5e3ebb

File tree

9 files changed

+777
-71
lines changed

9 files changed

+777
-71
lines changed

bluecellulab/analysis/analysis.py

Lines changed: 130 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,37 +8,72 @@
88
from bluecellulab.stimulus import StimulusFactory
99
from bluecellulab.tools import calculate_rheobase
1010
from bluecellulab.analysis.inject_sequence import run_stimulus
11-
from bluecellulab.analysis.plotting import plot_iv_curve
11+
from bluecellulab.analysis.plotting import plot_iv_curve, plot_fi_curve
1212

1313

14-
def compute_plot_iv_curve(cell, stim_start=100.0, duration=500.0, post_delay=100.0, threshold_voltage=-30, nb_bins=11):
15-
"""Compute and plot the IV curve from a given cell by injecting a
16-
predefined range of currents.
14+
def compute_plot_iv_curve(cell,
15+
injecting_section="soma[0]",
16+
injecting_segment=0.5,
17+
recording_section="soma[0]",
18+
recording_segment=0.5,
19+
stim_start=100.0,
20+
duration=500.0,
21+
post_delay=100.0,
22+
threshold_voltage=-30,
23+
nb_bins=11):
24+
"""Compute and plot the Current-Voltage (I-V) curve for a given cell by
25+
injecting a range of currents.
26+
27+
This function evaluates the relationship between the injected current amplitude and the resulting
28+
steady-state membrane potential of a neuronal cell model. Currents are injected at a specified section
29+
and segment, and the steady-state voltage at the recording location is used to construct the I-V curve.
1730
1831
Args:
19-
cell (bluecellulab.cell.Cell): The initialized cell model.
20-
stim_start (float): Start time for current injection (in ms). Default is 100.0 ms.
21-
duration (float): Duration of current injection (in ms). Default is 500.0 ms.
22-
post_delay (float): Delay after the stimulation ends (in ms). Default is 100.0 ms.
23-
nb_bins (int): Number of current injection levels. Default is 11.
32+
cell (bluecellulab.cell.Cell): The initialized BlueCelluLab cell model.
33+
injecting_section (str, optional): The name of the section where the stimulus is injected.
34+
Default is "soma[0]".
35+
injecting_segment (float, optional): The position along the injecting section (0.0 to 1.0)
36+
where the stimulus is applied. Default is 0.5.
37+
recording_section (str, optional): The name of the section where the voltage is recorded.
38+
Default is "soma[0]".
39+
recording_segment (float, optional): The position along the recording section (0.0 to 1.0)
40+
where the voltage is recorded. Default is 0.5.
41+
stim_start (float, optional): The start time of the current injection (in ms). Default is 100.0 ms.
42+
duration (float, optional): The duration of the current injection (in ms). Default is 500.0 ms.
43+
post_delay (float, optional): The delay after the stimulation ends before the simulation stops
44+
(in ms). Default is 100.0 ms.
45+
threshold_voltage (float, optional): The voltage threshold (in mV) for detecting a steady-state
46+
response. Default is -30 mV.
47+
nb_bins (int, optional): The number of discrete current levels between 0 and the maximum current.
48+
Default is 11.
2449
2550
Returns:
2651
tuple: A tuple containing:
27-
- list_amp (np.ndarray): The predefined injected step current levels (nA).
28-
- steady_states (np.ndarray): The corresponding steady-state voltages (mV).
52+
- list_amp (np.ndarray): The injected current amplitudes (nA).
53+
- steady_states (np.ndarray): The corresponding steady-state voltages (mV) recorded at the
54+
specified location.
55+
56+
Raises:
57+
ValueError: If the cell object is invalid, the specified sections/segments are not found, or if
58+
the simulation results are inconsistent.
2959
"""
30-
rheobase = calculate_rheobase(cell)
60+
rheobase = calculate_rheobase(cell=cell, section=injecting_section, segx=injecting_segment)
3161

3262
list_amp = np.linspace(rheobase - 2, rheobase - 0.1, nb_bins) # [nA]
3363

3464
steps = []
3565
times = []
3666
voltages = []
3767
# inject step current and record voltage response
68+
stim_factory = StimulusFactory(dt=0.1)
3869
for amp in list_amp:
39-
stim_factory = StimulusFactory(dt=0.1)
4070
step_stimulus = stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
41-
recording = run_stimulus(cell.template_params, step_stimulus, section="soma[0]", segment=0.5)
71+
recording = run_stimulus(cell.template_params,
72+
step_stimulus,
73+
section=injecting_section,
74+
segment=injecting_segment,
75+
recording_section=recording_section,
76+
recording_segment=recording_segment)
4277
steps.append(step_stimulus)
4378
times.append(recording.time)
4479
voltages.append(recording.voltage)
@@ -57,7 +92,86 @@ def compute_plot_iv_curve(cell, stim_start=100.0, duration=500.0, post_delay=100
5792
steady_state = features_results[0]['steady_state_voltage_stimend']
5893
steady_states.append(steady_state)
5994

60-
# plot I-V curve
61-
plot_iv_curve(list_amp, steady_states)
95+
plot_iv_curve(list_amp,
96+
steady_states,
97+
injecting_section=injecting_section,
98+
injecting_segment=injecting_segment,
99+
recording_section=recording_section,
100+
recording_segment=recording_segment)
62101

63102
return np.array(list_amp), np.array(steady_states)
103+
104+
105+
def compute_plot_fi_curve(cell,
106+
injecting_section="soma[0]",
107+
injecting_segment=0.5,
108+
recording_section="soma[0]",
109+
recording_segment=0.5,
110+
stim_start=100.0,
111+
duration=500.0,
112+
post_delay=100.0,
113+
max_current=0.8,
114+
nb_bins=11):
115+
"""Compute and plot the Frequency-Current (F-I) curve for a given cell by
116+
injecting a range of currents.
117+
118+
This function evaluates the relationship between injected current amplitude and the firing rate
119+
of a neuronal cell model. Currents are injected at a specified section and segment, and the number
120+
of spikes recorded in the specified recording location is used to construct the F-I curve.
121+
122+
Args:
123+
cell (bluecellulab.cell.Cell): The initialized BlueCelluLab cell model.
124+
injecting_section (str, optional): The name of the section where the stimulus is injected.
125+
Default is "soma[0]".
126+
injecting_segment (float, optional): The position along the injecting section (0.0 to 1.0)
127+
where the stimulus is applied. Default is 0.5.
128+
recording_section (str, optional): The name of the section where spikes are recorded.
129+
Default is "soma[0]".
130+
recording_segment (float, optional): The position along the recording section (0.0 to 1.0)
131+
where spikes are recorded. Default is 0.5.
132+
stim_start (float, optional): The start time of the current injection (in ms). Default is 100.0 ms.
133+
duration (float, optional): The duration of the current injection (in ms). Default is 500.0 ms.
134+
post_delay (float, optional): The delay after the stimulation ends before the simulation stops
135+
(in ms). Default is 100.0 ms.
136+
max_current (float, optional): The maximum amplitude of the injected current (in nA).
137+
Default is 0.8 nA.
138+
nb_bins (int, optional): The number of discrete current levels between 0 and `max_current`.
139+
Default is 11.
140+
141+
Returns:
142+
tuple: A tuple containing:
143+
- list_amp (np.ndarray): The injected current amplitudes (nA).
144+
- spike_count (np.ndarray): The corresponding spike counts for each current amplitude.
145+
146+
Raises:
147+
ValueError: If the cell object is invalid or the specified sections/segments are not found.
148+
"""
149+
rheobase = calculate_rheobase(cell=cell, section=injecting_section, segx=injecting_segment)
150+
151+
list_amp = np.linspace(rheobase, max_current, nb_bins) # [nA]
152+
steps = []
153+
spikes = []
154+
# inject step current and record spike response
155+
stim_factory = StimulusFactory(dt=0.1)
156+
for amp in list_amp:
157+
step_stimulus = stim_factory.step(pre_delay=stim_start, duration=duration, post_delay=post_delay, amplitude=amp)
158+
recording = run_stimulus(cell.template_params,
159+
step_stimulus,
160+
section=injecting_section,
161+
segment=injecting_segment,
162+
recording_section=recording_section,
163+
recording_segment=recording_segment,
164+
enable_spike_detection=True)
165+
steps.append(step_stimulus)
166+
spikes.append(recording.spike)
167+
168+
spike_count = [len(spike) for spike in spikes]
169+
170+
plot_fi_curve(list_amp,
171+
spike_count,
172+
injecting_section=injecting_section,
173+
injecting_segment=injecting_segment,
174+
recording_section=recording_section,
175+
recording_segment=recording_segment)
176+
177+
return np.array(list_amp), np.array(spike_count)

bluecellulab/analysis/inject_sequence.py

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Module for injecting a sequence of protocols to the cell."""
22
from __future__ import annotations
33
from enum import Enum, auto
4-
from typing import NamedTuple, Sequence, Dict
4+
from typing import NamedTuple, Sequence, Dict, Optional
55

66
import neuron
77
import numpy as np
@@ -11,6 +11,7 @@
1111
from bluecellulab.simulation.simulation import Simulation
1212
from bluecellulab.stimulus.circuit_stimulus_definitions import Hyperpolarizing
1313
from bluecellulab.stimulus.factory import Stimulus, StimulusFactory
14+
from bluecellulab.tools import validate_section_and_segment
1415

1516

1617
class StimulusName(Enum):
@@ -24,10 +25,12 @@ class StimulusName(Enum):
2425

2526

2627
class Recording(NamedTuple):
27-
"""A tuple of the current, voltage and time recordings."""
28+
"""A tuple of the current, voltage and time recordings with optional spike
29+
recordings."""
2830
current: np.ndarray
2931
voltage: np.ndarray
3032
time: np.ndarray
33+
spike: np.ndarray | None
3134

3235

3336
StimulusRecordings = Dict[str, Recording]
@@ -40,41 +43,91 @@ def run_stimulus(
4043
segment: float,
4144
cvode: bool = True,
4245
add_hypamp: bool = True,
46+
recording_section: str = "soma[0]",
47+
recording_segment: float = 0.5,
48+
enable_spike_detection: bool = False,
49+
threshold_spike_detection: float = -30,
4350
) -> Recording:
44-
"""Creates a cell and stimulates it with a given stimulus.
51+
"""Creates a cell from template parameters, applies a stimulus, and records
52+
the response.
53+
54+
This function simulates the electrical activity of a neuronal cell model by injecting
55+
a stimulus into a specified section and segment, recording the voltage response, and
56+
optionally detecting spikes.
4557
4658
Args:
47-
template_params: The parameters to create the cell from a template.
48-
stimulus: The input stimulus to inject into the cell.
49-
section: Name of the section of cell where the stimulus is to be injected.
50-
segment: The segment of the section where the stimulus is to be injected.
51-
cvode: True to use variable time-steps. False for fixed time-steps.
59+
template_params (TemplateParams): Parameters required to create the cell from a
60+
specified template, including morphology and mechanisms.
61+
stimulus (Stimulus): The stimulus waveform to inject, defined by time and current arrays.
62+
section (str): Name of the section of the cell where the stimulus is applied.
63+
(e.g. soma[0])
64+
segment (float): The normalized position (0.0 to 1.0) along the injecting
65+
section where the stimulus is applied.
66+
cvode (bool, optional): Whether to use variable time-step integration. Defaults to True.
67+
add_hypamp (bool, optional): If True, adds a hyperpolarizing stimulus before applying
68+
the main stimulus. Defaults to True.
69+
recording_section (str): Name of the section of the cell where voltage is recorded.
70+
recording_segment (float): The normalized position (0.0 to 1.0) along the recording
71+
section where voltage is recorded.
72+
enable_spike_detection (bool, optional): If True, enables spike detection at the
73+
recording location. Defaults to False.
74+
threshold_spike_detection (float, optional): The voltage threshold (mV) for spike detection.
75+
Defaults to -30 mV.
5276
5377
Returns:
54-
The voltage-time recording at the specified location.
78+
Recording: A `Recording` object containing the following:
79+
- `current` (np.ndarray): The injected current waveform (nA).
80+
- `voltage` (np.ndarray): The recorded membrane potential (mV) over time.
81+
- `time` (np.ndarray): The simulation time points (ms).
82+
- `spike` (np.ndarray or None): The detected spikes, if spike detection is enabled.
5583
5684
Raises:
57-
ValueError: If the time and voltage arrays are not the same length.
85+
ValueError: If the time, current, and voltage arrays do not have the same length,
86+
or if the specified sections or segments are not found in the cell model.
5887
"""
5988
cell = Cell.from_template_parameters(template_params)
60-
neuron_section = cell.sections[section]
89+
90+
validate_section_and_segment(cell, section, segment)
91+
validate_section_and_segment(cell, recording_section, recording_segment)
92+
6193
if add_hypamp:
6294
hyp_stim = Hyperpolarizing(target="", delay=0.0, duration=stimulus.stimulus_time)
6395
cell.add_replay_hypamp(hyp_stim)
64-
cell.add_voltage_recording(neuron_section, segment)
96+
97+
cell.add_voltage_recording(cell.sections[recording_section], recording_segment)
98+
99+
# Set up spike detection if enabled
100+
spikes: Optional[np.ndarray] = None
101+
if enable_spike_detection:
102+
recording_location = f"{recording_section}({str(recording_segment)})"
103+
cell.start_recording_spikes(None, location=recording_location, threshold=threshold_spike_detection)
104+
105+
# Inject the stimulus and run the simulation
65106
iclamp, _ = cell.inject_current_waveform(
66-
stimulus.time, stimulus.current, section=neuron_section, segx=segment
107+
stimulus.time, stimulus.current, section=cell.sections[section], segx=segment
67108
)
68109
current_vector = neuron.h.Vector()
69110
current_vector.record(iclamp._ref_i)
111+
70112
simulation = Simulation(cell)
71113
simulation.run(stimulus.stimulus_time, cvode=cvode)
114+
115+
# Retrieve simulation results
72116
current = np.array(current_vector.to_python())
73-
voltage = cell.get_voltage_recording(neuron_section, segment)
117+
voltage = cell.get_voltage_recording(cell.sections[recording_section], recording_segment)
74118
time = cell.get_time()
119+
75120
if len(time) != len(voltage) or len(time) != len(current):
76-
raise ValueError("Time, current and voltage arrays are not the same length")
77-
return Recording(current, voltage, time)
121+
raise ValueError("Time, current, and voltage arrays are not the same length")
122+
123+
if enable_spike_detection:
124+
results = cell.get_recorded_spikes(location=recording_location, threshold=threshold_spike_detection)
125+
if results is not None:
126+
spikes = np.array(results)
127+
else:
128+
spikes = None
129+
130+
return Recording(current=current, voltage=voltage, time=time, spike=spikes)
78131

79132

80133
def apply_multiple_stimuli(

bluecellulab/analysis/plotting.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,55 @@
33
import matplotlib.pyplot as plt
44

55

6-
def plot_iv_curve(currents, voltages):
6+
def plot_iv_curve(currents, voltages, injecting_section, injecting_segment, recording_section, recording_segment):
77
"""Plots the IV curve.
88
99
Args:
10-
currents (iterable): The injected current levels (nA).
11-
voltages (iterable): The corresponding steady-state voltages (mV).
10+
currents (list): The injected current levels (nA).
11+
voltages (list): The corresponding steady-state voltages (mV).
12+
injecting_section (str): The section in the cell where the current was injected.
13+
injecting_segment (float): The segment position (0.0 to 1.0) where the current was injected.
14+
recording_section (str): The section in the cell where spikes were recorded.
15+
recording_segment (float): The segment position (0.0 to 1.0) where spikes were recorded.
16+
1217
Raises:
1318
ValueError: If the lengths of currents and voltages do not match.
1419
"""
1520
if len(currents) != len(voltages):
1621
raise ValueError("currents and voltages must have the same length")
1722

18-
# Plotting
1923
plt.figure(figsize=(10, 6))
20-
plt.plot(voltages, currents, marker='o', linestyle='-', color='b')
24+
plt.plot(currents, voltages, marker='o', linestyle='-', color='b')
2125
plt.title("I-V Curve")
22-
plt.ylabel("Injected current [nA]")
23-
plt.xlabel("Steady state voltage [mV]")
26+
plt.xlabel(f"Injected Current [nA] at {injecting_section}({injecting_segment:.2f})")
27+
plt.ylabel(f"Steady state voltage [mV] at {recording_section}({recording_segment:.2f})")
28+
plt.grid(True)
29+
plt.tight_layout()
30+
plt.show()
31+
32+
33+
def plot_fi_curve(currents, spike_count, injecting_section, injecting_segment, recording_section, recording_segment):
34+
"""Plots the F-I (Frequency-Current) curve.
35+
36+
Args:
37+
currents (list): The injected current levels (nA).
38+
spike_count (list): The number of spikes recorded for each current level.
39+
injecting_section (str): The section in the cell where the current was injected.
40+
injecting_segment (float): The segment position (0.0 to 1.0) where the current was injected.
41+
recording_section (str): The section in the cell where spikes were recorded.
42+
recording_segment (float): The segment position (0.0 to 1.0) where spikes were recorded.
43+
44+
Raises:
45+
ValueError: If the lengths of currents and spike counts do not match.
46+
"""
47+
if len(currents) != len(spike_count):
48+
raise ValueError("currents and spike count must have the same length")
49+
50+
plt.figure(figsize=(10, 6))
51+
plt.plot(currents, spike_count, marker='o')
52+
plt.title("F-I Curve")
53+
plt.xlabel(f"Injected Current [nA] at {injecting_section}({injecting_segment:.2f})")
54+
plt.ylabel(f"Spike Count recorded at {recording_section}({recording_segment:.2f})")
55+
plt.grid(True)
2456
plt.tight_layout()
2557
plt.show()

0 commit comments

Comments
 (0)