pulse2percept.stimuli.encoders
Encoder,
AmplitudeEncoder,
FrequencyEncoder
Classes
|
Encode gray levels as pulse amplitudes |
|
Abstract base class for all stimulus encoders |
|
Encode gray levels as pulse train frequencies |
- class pulse2percept.stimuli.encoders.Encoder(implant=None, phase_dur=0.46, interphase_dur=0, cathodic_first=True, pulse=None, clock=None, n_levels=None, raster=None, frame_dur=None, stretch=False)[source]
Abstract base class for all stimulus encoders
An encoder translates the gray levels of an image or a video into the electrical stimulus that a retinal implant would actually deliver: each electrode emits a train of biphasic pulses, and the gray level of the pixel that the electrode sees determines some property of that train.
Three clocks are involved, and they are deliberately independent of one another:
The frame clock belongs to the video. It says when the modulation parameters update; that is, a new frame is a new gray level, and hence a new amplitude or a new frequency. It is also the rate at which a percept is worth reporting, which is why it is recorded in the encoded stimulus’ metadata for
predict_percept()to pick up. It takes no part in the timing of the pulses themselves.The pulse clock belongs to
freq. It runs continuously for the whole stimulus rather than restarting at every frame, so the frame rate has no say in the rate delivered. A pulse takes the modulation parameters of the frame its onset falls into, so it is never cut in half by a frame boundary.The rate can still come out below the one requested, but only where the hardware you described cannot express it:
clock, and a raster with electrodes on differing rates, both round a pulse period up. Neither ever rounds down, so an electrode is never driven faster, and so never given more charge, than was asked for.The raster sweep belongs to the
Raster, and says which electrodes may pulse when, so that no two raster groups are ever active at the same instant.
All encoders share the same two-step structure:
Reduce the source to one gray level per electrode per frame. If the encoder was given an
implant, the source is first sampled at the electrode locations, so that everything downstream works at electrode resolution.Map those gray levels onto pulse train parameters (
_modulate), then assemble the pulse trains (_assemble).
Subclasses only implement
_modulate; everything else is provided here.Added in version 0.10.0.
- Parameters:
implant (
ProsthesisSystem, optional) – The implant to encode for. Its electrode locations are used to sample the source, and its electrode names label the resulting stimulus. If None, every pixel of the source is treated as its own electrode.phase_dur (float, optional) – Duration (ms) of the cathodic/anodic phase of each pulse.
interphase_dur (float, optional) – Duration (ms) of the gap between the cathodic and anodic phases.
cathodic_first (bool, optional) – If True, the cathodic phase of each pulse is delivered first. Most temporal models in
pulse2percept.modelstreat cathodic current as brightness-increasing, so bright pixels map onto cathodic-first pulses.pulse (
Stimulus, optional) – A single pulse to repeat, in place of the symmetric biphasic pulse built fromphase_dur,interphase_durandcathodic_first(which are then ignored). Only its shape is used: its amplitude is normalized away, since that is what the encoder sets, and its time axis is shifted to start at zero. It must start and end at zero amplitude, since it is tiled into a train.clock (float, optional) –
Period (ms) of the stimulator’s time base. Pulse periods and raster offsets are rounded to a whole number of clock cycles, as they would be on real hardware. If None, they are placed at the full resolution of the simulation (
DT= 1e-3 ms).Important
Every timing constraint here (the clock, and the raster sweep) may lower the rate an electrode ends up on, and none of them may raise it. Rounding a period down would deliver more charge than was asked for, so a time base that cannot represent a rate exactly gives back the nearest slower one it can.
That makes a coarse clock expensive in frequency: realizable periods are
clock,2*clock,3*clock, … , so withclock=1a requested 300 Hz (3.33 ms) is delivered as 250 Hz (4 ms), and withclock=3as 166.7 Hz. Choose it against the top of your frequency range rather than in the abstract.n_levels (int, optional) – Number of gray levels the encoder can distinguish, mimicking the resolution of the device’s input stage. Gray levels are rounded onto
n_levelsvalues evenly spaced over [0, 1] before being modulated. If None, they are taken at full precision.raster (
Raster, optional) – How the stimulator takes turns between electrodes it cannot drive at the same time. Each group starts its pulse a fixedgroup_durbehind the group before it, so no two groups are ever active at once. Where electrodes run at differing rates they would drift into one another, so there their periods are pinned to whole sweeps; electrodes sharing one rate cannot drift and keep their period exactly. If None, theimplant’s own raster is used, and failing that every electrode fires on the same schedule.frame_dur (float, optional) – Duration (ms) of a single frame. If None, it is inferred from the source’s frame rate (or, failing that, from its time axis). A source without a time axis, such as an
ImageStimulus, is treated as a single frame lasting 500 ms.stretch (bool, optional) –
If True, the gray levels of the source are stretched to fill [0, 1] before they are modulated, so that the darkest pixel maps onto the bottom of the modulation range and the brightest onto the top. If False (the default), gray levels are taken at face value: a gray level of 0.5 always maps onto the middle of the range no matter how bright the rest of the source is.
Note
Stretching makes the encoding depend on the content of the source. A uniform image has no range to stretch, and encodes to a stimulus of zero amplitude everywhere.
Notes
Arguments may be given as plain numbers in the units documented above, or as unitful quantities (e.g.
0.05 * mA,460 * us,0.02 * kHz), which are converted to those units. Seepulse2percept.units.pulseis the exception: only its shape is borrowed and its amplitude is normalized away, so what that amplitude was measured in does not matter. A dimensionless waveform is a perfectly good template.
- encode(source)[source]
Encode an image or a video as a train of electrical pulses
- Parameters:
source (
Stimulus) – The image or video to encode. Gray levels are expected in [0, 1], which is whatImageStimulusandVideoStimulusproduce. It must be dimensionless: this method is the boundary at which a picture becomes stimulation, so an electrical stimulus is not a valid source for it.- Returns:
stim – The encoded stimulus, ready to assign to
implant.stim. Its amplitudes are in microamps and its time axis in milliseconds, whatever units the encoder’s own parameters were given in.- Return type:
- Raises:
DimensionMismatchError – If
sourceis not dimensionless.
- class pulse2percept.stimuli.encoders.AmplitudeEncoder(implant=None, amp_range=(0, 50), freq=20, **kwargs)[source]
Encode gray levels as pulse amplitudes
Every electrode emits a pulse train of the same fixed frequency, and the gray level of the pixel it sees sets the amplitude of those pulses. This is how most retinal prostheses encode a video.
Because every electrode shares one pulse period, a raster costs no frequency here: the groups hold fixed offsets from one another and so can never drift together, which means nothing has to be quantized and
freqis delivered exactly. With no explicitgroup_durthe groups also divide that period evenly, one turn each per pulse; an explicitgroup_durpacks them into a shorter sweep at the start of every period instead. Either way no two groups are ever active at the same instant.Added in version 0.10.0.
- Parameters:
implant (
ProsthesisSystem, optional) – The implant to encode for; seeEncoder.amp_range ((min_amp, max_amp), optional) – Range of pulse amplitudes (uA). A gray level of 0 maps onto
min_ampand a gray level of 1 ontomax_amp.freq (float, optional) –
Pulse train frequency (Hz), the same for every electrode. The pulse clock runs independently of the video, so the frame rate has no say in the rate delivered. Because every electrode shares this one period, a raster does not quantize it either: the groups keep a fixed offset from one another and cannot drift together. Only
clockcan lower it, by rounding the period up to a whole number of cycles.Note
A frequency below the frame rate is realizable, but wasteful: some frames then receive no pulse at all and their gray levels are never delivered. Encoding warns when this happens.
phase_dur – See
Encoder.interphase_dur – See
Encoder.cathodic_first – See
Encoder.frame_dur – See
Encoder.stretch – See
Encoder.
Notes
Arguments may be given as plain numbers in the units documented above, or as unitful quantities (e.g.
0.05 * mA,460 * us,0.02 * kHz), which are converted to those units. Seepulse2percept.units.
Examples
Encode a movie for Argus II, mapping gray levels onto 0-50 uA at 20 Hz:
>>> import pulse2percept as p2p >>> implant = p2p.implants.ArgusII() >>> encoder = p2p.stimuli.AmplitudeEncoder(implant, amp_range=(0, 50)) >>> implant.stim = encoder.encode(p2p.stimuli.BostonTrain())
- encode(source)[source]
Encode an image or a video as a train of electrical pulses
- Parameters:
source (
Stimulus) – The image or video to encode. Gray levels are expected in [0, 1], which is whatImageStimulusandVideoStimulusproduce. It must be dimensionless: this method is the boundary at which a picture becomes stimulation, so an electrical stimulus is not a valid source for it.- Returns:
stim – The encoded stimulus, ready to assign to
implant.stim. Its amplitudes are in microamps and its time axis in milliseconds, whatever units the encoder’s own parameters were given in.- Return type:
- Raises:
DimensionMismatchError – If
sourceis not dimensionless.
- class pulse2percept.stimuli.encoders.FrequencyEncoder(implant=None, freq_range=(0, 300), amp=50, **kwargs)[source]
Encode gray levels as pulse train frequencies
Every electrode emits pulses of the same fixed amplitude, and the gray level of the pixel it sees sets how often they come.
Important
Frequency modulation is far more expensive to simulate than amplitude modulation, because electrodes pulsing at different rates do not pulse at the same times: the stimulus needs a time point wherever any electrode’s pulse has an edge, rather than the handful of time points that amplitude modulation shares between all of them.
clockis the lever that cuts that down, and it is physically motivated: real stimulators have a time base. Encoding the 94-frameBostonTrainfor Argus II at frequencies in (0, 300] Hz:setting
time points
(amplitude modulation)
442
no quantization
143,771
clock=121,505
clock=210,893
n_levels=8127,327
clock=1, n_levels=820,917
clockis not free, though: it buys those time points with frequency resolution, and it spends it at the top of the range where the periods are shortest. Againstfreq_range=(0, 300),clock=1delivers the brightest pixels at 250 Hz rather than 300, andclock=2at 200 Hz. Pick it against the fastest train you actually need.n_levelsis a much weaker lever here than the numbers above might suggest, and only worth reaching for onceclockis set. Because the pulse clock keeps its phase across frames, two electrodes quantized onto the same gray level still pulse at different times unless their whole history matches; quantizing gray levels no longer collapses them onto a shared schedule the way it would if every frame restarted the train.A raster cuts the cost too, and for the same reason a clock does: it confines every onset to the raster grid.
Added in version 0.10.0.
- Parameters:
implant (
ProsthesisSystem, optional) – The implant to encode for; seeEncoder.freq_range ((min_freq, max_freq), optional) –
Range of pulse train frequencies (Hz). A gray level of 0 maps onto
min_freqand a gray level of 1 ontomax_freq. A frequency of 0 means no pulse at all.Note
Realizable frequencies are quantized by
clock, and, when a raster is in play, onto the raster sweep – which under frequency modulation is the usual case, since the electrodes are by construction on differing rates. Every period becomes a whole number of sweeps, so the realizable rates are1000 / (m * sweep)Hz.How coarse that grid is depends on how the sweep was set. With
group_dur=Nonethe sweep is the shortest period asked for, so the fastest electrode keeps its rate and pulses once per sweep while slower ones pulse every m-th. With an explicitgroup_durthe sweep isn_groups * group_durand unrelated to any requested rate, so even the fastest electrode is generally rounded: against a six-group 1 ms sweep, a requested 100 Hz (10 ms) is delivered as 83.3 Hz (12 ms, two sweeps).Quantizing onto the sweep always rounds the period up, so an electrode is never driven faster than it was asked for: against a 10 ms sweep, 67 Hz comes back as 50 Hz rather than 100 Hz. Rounding to the nearest sweep instead would deliver up to twice the charge the caller asked for. Shorten
group_durfor a finer grid.amp (float, optional) – Pulse amplitude (uA), the same for every electrode.
phase_dur – See
Encoder.interphase_dur – See
Encoder.cathodic_first – See
Encoder.pulse – See
Encoder.clock – See
Encoder.n_levels – See
Encoder.frame_dur – See
Encoder.stretch – See
Encoder.
Notes
Arguments may be given as plain numbers in the units documented above, or as unitful quantities (e.g.
0.05 * mA,460 * us,0.02 * kHz), which are converted to those units. Seepulse2percept.units.
Examples
Encode a movie for Argus II at 50 uA, mapping gray levels onto 0-300 Hz on a 1 ms stimulator clock:
>>> import pulse2percept as p2p >>> implant = p2p.implants.ArgusII() >>> encoder = p2p.stimuli.FrequencyEncoder(implant, freq_range=(0, 300), ... amp=50, clock=1) >>> implant.stim = encoder.encode(p2p.stimuli.BostonTrain())
- encode(source)[source]
Encode an image or a video as a train of electrical pulses
- Parameters:
source (
Stimulus) – The image or video to encode. Gray levels are expected in [0, 1], which is whatImageStimulusandVideoStimulusproduce. It must be dimensionless: this method is the boundary at which a picture becomes stimulation, so an electrical stimulus is not a valid source for it.- Returns:
stim – The encoded stimulus, ready to assign to
implant.stim. Its amplitudes are in microamps and its time axis in milliseconds, whatever units the encoder’s own parameters were given in.- Return type:
- Raises:
DimensionMismatchError – If
sourceis not dimensionless.