The :py~pulse2percept.stimuli
module provides a number of common electrical stimulus types, which can be assigned to electrodes of a :py~pulse2percept.implants.ProsthesisSystem
object:
Stimulus | Description |
---|---|
MonophasicPulse | single phase: cathodic or anodic |
BiphasicPulse | biphasic: cathodic + anodic |
AsymmetricBiphasicPulse | biphasic with unequal amplitude/duration |
PulseTrain | combine any Stimulus into a pulse train |
BiphasicPulseTrain | series of (symmetric) biphasic pulses |
AsymmetricBiphasicPulseTrain | series of asymmetric biphasic pulses |
BiphasicTripletTrain | series of biphasic pulse triplets |
In addition, pulse2percept provides convenience functions to convert images and videos into :py~pulse2percept.stimuli.Stimulus
objects (see the :py~pulse2percept.io
module).
The :py~pulse2percept.stimuli.Stimulus
object defines a common interface for all electrical stimuli, consisting of a 2D data array with labeled axes, where rows denote electrodes and columns denote points in time.
A stimulus can be created from a variety of source types. The number of electrodes and time points will be automatically extracted from the source type:
Source type | electrodes | time |
---|---|---|
Scalar value | 1 | None |
Nx1 NumPy array | N | None |
NxM NumPy array | N | M |
In addition, you can also pass a collection of source types (e.g., list, dictionary).
Note
Depending on the source type, a stimulus might have a time component or not.
python
# Use defaults so we don't get gridlines in generated docs import matplotlib as mpl mpl.rcdefaults()
The easiest way to create a stimulus is to specify the current amplitude (uA) to be delivered to an electrode:
python
from pulse2percept.stimuli import Stimulus
# Stimulate an unnamed electrode with -14uA: Stimulus(-14)
You can also specify the name of the electrode to be stimulated:
python
# Stimulate Electrode 'B7' with -14uA: Stimulus(-14, electrodes='B7')
By default, this stimulus will not have a time component (stim.time
is None). Some models, such as :py~pulse2percept.models.ScoreboardModel
, cannot handle stimuli in time.
To create stimuli in time, you can use one of the above mentioned stimulus types, such as :py~pulse2percept.stimuli.MonophasicPulse
or :py~pulse2percept.stimuli.BiphasicPulseTrain
:
python
# Stimulate Electrode 'A001' with a 20Hz pulse train lasting 0.5s # (pulses: cathodic-first, 10uA amplitude, 0.45ms phase duration): from pulse2percept.stimuli import BiphasicPulseTrain pt = BiphasicPulseTrain(20, 10, 0.45, stim_dur=500) stim = Stimulus(pt) stim
# This stimulus has a time component: stim.time
You can specify not only the name of the electrode but also the time steps to be used:
python
# Stimulate Electrode 'C7' with int time steps: Stimulus(pt, electrodes='C7', time=np.arange(pt.shape[-1]))
Stimuli can also be created from a list or dictionary of source types:
python
# Stimulate three unnamed electrodes with -2uA, 14uA, and -100uA, # respectively: Stimulus([-2, 14, -100])
Electrode names can be passed in a list:
python
Stimulus([-2, 14, -100], electrodes=['A1', 'B1', 'C1'])
Alternatively, stimuli can be created from a dictionary:
python
# Equivalent to the previous one: Stimulus({'A1': -2, 'B1': 14, 'C1': -100})
The same is true for a dictionary of pulse trains:
python
from pulse2percept.stimuli import BiphasicPulse Stimulus({'A1': BiphasicPulse(10, 0.45, stim_dur=100), 'C9': BiphasicPulse(-30, 1, delay_dur=10, stim_dur=100)})
You can directly index into the :py~pulse2percept.stimuli.Stimulus
object to retrieve individual data points: stim[item]
. item
can be an integer, string, slice, or tuple.
For example, to retrieve all data points of the first electrode in a multi-electrode stimulus, use the following:
python
stim = Stimulus(np.arange(10).reshape((2, 5))) stim[0]
Here 0
is a valid electrode index, because we did not specify an electrode name. Analogously:
python
stim = Stimulus(np.arange(10).reshape((2, 5)), electrodes=['B1', 'C2']) stim['B1']
Similarly, you can retrieve all data points at a particular time:
python
stim = Stimulus(np.arange(10).reshape((2, 5))) stim[:, 3]
This works even when the specified time is not explicitly provided in the stimulus! In that case, the value is automatically interpolated (using SciPy's interp1d
):
python
# A single-electrode ramp stimulus: stim = Stimulus(np.arange(10).reshape((1, -1))) stim
# Retrieve stimulus at t=3: stim[0, 3]
# Time point 3.45 is not in the data provided above, but can be # interpolated as follows: stim[0, 3.45]
# This also works for multiple time points: stim[0, [3.45, 6.78]]
# Time points above the valid range will return the largest stored value: stim[0, 123.45]
The raw data is accessible as a 2D NumPy array (electrodes x time) stored in the data
container of a Stimulus:
python
stim = Stimulus(np.arange(10).reshape((2, 5))) stim.data
You can index and slice the data
container like any NumPy array.
You can change the coordinates of an existing :py~pulse2percept.stimuli.Stimulus
object, but retain all its data, by wrapping it in a second Stimulus object:
python
# Say you have a Stimulus object with unlabeled axes: stim = Stimulus(np.ones((2, 5))) stim
# You can create a new object from it with named electrodes: Stimulus(stim, electrodes=['A1', 'F10'])
# Same goes for time points: Stimulus(stim, time=[0, 0.1, 0.2, 0.3, 0.4])
The :py~pulse2percept.stimuli.Stimulus.compress
method automatically compresses the data in two ways:
- Removes electrodes with all-zero activation.
- Retains only the time points at which the stimulus changes.
For example, only the signal edges of a pulse train are saved. That is, rather than saving the current amplitude at every 0.1ms time step, only the non-redundant values are retained. This drastically reduces the memory footprint of the stimulus. You can convince yourself of that by inspecting the size of a Stimulus object before and after compression:
python
# An uncompressed stimulus: stim = Stimulus([[0, 0, 0, 1, 2, 0, 0, 0]], time=[0, 1, 2, 3, 4, 5, 6, 7]) stim
# Now compress the data: stim.compress()
# Notice how the time axis have changed: stim
Stimuli can store additional relevant information in the metadata
dictionary. Users can also pass their own metadata, which will be stored in metadata["user"]
Stimuli built from a collection of sources store the metadata for each source in metadata["electrodes"][electrode]["metadata"]
:
python
# Accessing metadata stim = Stimulus([[0, 1, 2, 3]], metadata='user_metadata') stim.metadata
# Some objects store their own metadata stim = BiphasicPulseTrain(1,1,1, metadata='user_metadata') stim.metadata
# Multiple source metadata stim = Stimulus({'A1' : BiphasicPulseTrain(1,1,1, metadata='A1 metadata'), 'B2' : BiphasicPulseTrain(1,1,1, metadata='B2 metadata')}, metadata='stimulus metadata') stim.metadata
pulse2percept.stimuli.Stimulus