Skip to content

Latest commit

 

History

History
297 lines (207 loc) · 9.06 KB

stimuli.rst

File metadata and controls

297 lines (207 loc) · 9.06 KB

Electrical Stimuli

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).

Understanding the Stimulus class

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()

Single-electrode stimuli

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]))

Creating multi-electrode stimuli

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)})

Interacting with stimuli

Accessing individual data points

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]

Accessing the raw data

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.

Assigning new coordinates to an existing stimulus

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])

Compressing a stimulus

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

Accessing stimulus metadata

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