pulse2percept.implants.rasters
Raster,
SequentialRaster,
CheckerboardRaster,
CustomRaster
Classes
|
Split electrodes into groups that are spread as far apart as possible |
|
Assign electrodes to raster groups by name |
|
Abstract base class for all raster patterns |
|
Split electrodes into groups that fire one after another |
- class pulse2percept.implants.rasters.Raster(group_dur=None)[source]
Abstract base class for all raster patterns
A stimulator usually cannot drive every electrode at once, because the total current it can source at any instant is limited. Electrodes are therefore split into raster groups that take turns.
A raster is a scheduling constraint, not a hardware state machine. What it has to deliver is one property: no two groups are ever active at the same instant, so that the stimulator sources at most one group’s worth of current however the video is modulated. It is not a switch that cyclically enables group 0, then group 1, then group 0 again forever, and a group’s pulses do not have to land at the same phase of a repeating cycle.
Taking turns is described by a raster sweep: group g starts its pulse
g * group_durafter group 0 does, so a sweep spansn_groups * group_dur. Two things then keep groups apart for good (seeEncoder):A pulse has to be short enough to finish before the next group’s turn begins.
Electrodes on different pulse periods drift relative to one another, and would eventually collide however they started out. Their periods are therefore pinned to whole numbers of the sweep, which fixes their relative phase. Pinning rounds the period up, so multiplexing never drives an electrode faster – and so never delivers more charge – than asked.
Electrodes that share one period cannot drift in the first place: their onsets stay
group_durapart forever, whatever that period is. Nothing is quantized in that case and the requested rate is delivered exactly, even when the period is not a whole number of sweeps. This is the usual case under amplitude modulation, and it is why rastering costs no frequency there.So with two groups 1.5 ms apart on a common 10 ms period, group 0 pulses at 0, 10, 20, … and group 1 at 1.5, 11.5, 21.5, … – collision-free, but not a repeating 3 ms schedule, and the 10 ms period is left alone.
The sweep belongs to the stimulation schedule, not to the video: it is tied to the pulse period, not to the frame rate. Two rules settle how long it is and what it costs:
With
group_dur=Nonethe groups divide the shortest pulse period between them, so the sweep is exactly that period. Under frequency modulation that means the fastest electrode pulses once per sweep and slower ones every m-th sweep.With an explicit
group_durthe sweep isn_groups * group_durwhatever rate the electrodes run at – six groups of 1 ms sweep in 6 ms. It is then generally much shorter than a pulse period, so even the fastest electrode may pulse only every m-th sweep.
Either way, only periods that differ from one another are rounded up onto the sweep; a period they all share is delivered exactly, since fixed group offsets cannot drift into one another.
Subclasses only implement
groups.Added in version 0.10.0.
- Parameters:
group_dur (float, optional) –
Duration (ms) of a single group’s slot, and hence the spacing between one group’s turn and the next. If None, the groups are spread evenly over the pulse period, so that a sweep takes exactly one period to complete – which is what an encoder wants whenever every electrode pulses at the same rate.
Setting it explicitly makes the sweep
n_groups * group_durregardless of the pulse period, which is how you buy back frequency resolution under frequency modulation: a shorter slot means a shorter sweep, and the periods that have to be pinned are pinned onto a finer grid. It cannot be shorter than a single pulse.An encoder with a
clockrounds the slot onto it and rebuilds the sweep from the result, so every group keeps a turn of the same length.May be given as a plain number of milliseconds or as a unitful quantity (e.g.
1000 * us); the same goes for theperiodargument ofslot_dur()andoffsets(). Seepulse2percept.units.
- abstract property n_groups
Number of raster groups
- abstract groups(electrodes)[source]
Assign each electrode to a raster group
- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
- Returns:
group – The group each electrode belongs to, in
0..n_groups-1.- Return type:
(n_electrodes,) int array
- members(electrodes, group)[source]
The electrodes that take their turn together in one group
The inverse of
groups(), which says what group each electrode is in. This says which electrodes are in a group.- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
group (int) – Which group to look up, in
0..n_groups-1. Groups take their turns in index order, so group 0 is the one that goes first.
- Returns:
members – The entries of
electrodesbelonging togroup, in the order they were given: names in, names out.- Return type:
array
Examples
>>> from pulse2percept.implants import ArgusII, SequentialRaster >>> names = ArgusII().electrode_names >>> SequentialRaster(6).members(names, 0)[:4] array(['A1', 'A2', 'A3', 'A4'], dtype='<U3')
- plot(implant, annotate=None, ax=None, cmap='viridis', autoscale=True)[source]
Plot the electrode array, colored by raster group
What a raster does is spatial, so the quickest way to tell whether it does what was wanted is to look at it. Colors run in the order the groups take their turns, so the picture shows the schedule as well as the pattern: with
CheckerboardRastera group’s electrodes should be scattered over the whole array rather than gathered into a line, and neighboring colors should not lie next to one another in a consistent direction.- Parameters:
implant (
ProsthesisSystem) – The implant to draw, or itsElectrodeArray. Its electrodes are the ones the raster is asked about, so this has to be an implant the raster covers.annotate (bool, optional) – Whether to write the group index into each electrode. If None, they are written whenever there are few enough electrodes (at most 120) for the numbers to be readable.
ax (matplotlib.axes.Axes, optional) – Axes to draw on. If None, uses the current axes.
cmap (str, optional) – Matplotlib colormap the group colors are taken from, evenly spaced. A sequential map is the useful default, since the order the colors run in is the order the groups fire in.
autoscale (bool, optional) – Whether to fit the x/y limits to the implant.
- Returns:
ax – The axes drawn on.
- Return type:
matplotlib.axes.Axes
- offsets(electrodes, period)[source]
How far behind group 0 each electrode’s slot begins
- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
period (float) – The pulse period (ms) a sweep has to fit into.
- Returns:
offset – Time (ms) between the start of a sweep and the start of this electrode’s slot.
- Return type:
(n_electrodes,) float array
- class pulse2percept.implants.rasters.SequentialRaster(n_groups, interleave=False, group_dur=None)[source]
Split electrodes into groups that fire one after another
Electrodes are assigned to groups by their position in the stimulus, which for an
ElectrodeGridruns row by row. So on a 6x10 array such asArgusII,SequentialRaster(6)puts each row in its own group – a line raster.Added in version 0.10.0.
- Parameters:
n_groups (int) – Number of groups to split the electrodes into.
interleave (bool, optional) – If False (the default), each group is a contiguous block of electrodes. If True, groups are interleaved, so that consecutive electrodes end up in different groups. Interleaving spreads each group’s current further across the array.
Examples
A line raster for Argus II, one row of ten electrodes at a time:
>>> from pulse2percept.implants import ArgusII, SequentialRaster >>> implant = ArgusII() >>> implant.raster = SequentialRaster(6)
- property n_groups
Number of raster groups
- members(electrodes, group)[source]
The electrodes that take their turn together in one group
The inverse of
groups(), which says what group each electrode is in. This says which electrodes are in a group.- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
group (int) – Which group to look up, in
0..n_groups-1. Groups take their turns in index order, so group 0 is the one that goes first.
- Returns:
members – The entries of
electrodesbelonging togroup, in the order they were given: names in, names out.- Return type:
array
Examples
>>> from pulse2percept.implants import ArgusII, SequentialRaster >>> names = ArgusII().electrode_names >>> SequentialRaster(6).members(names, 0)[:4] array(['A1', 'A2', 'A3', 'A4'], dtype='<U3')
- offsets(electrodes, period)[source]
How far behind group 0 each electrode’s slot begins
- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
period (float) – The pulse period (ms) a sweep has to fit into.
- Returns:
offset – Time (ms) between the start of a sweep and the start of this electrode’s slot.
- Return type:
(n_electrodes,) float array
- plot(implant, annotate=None, ax=None, cmap='viridis', autoscale=True)[source]
Plot the electrode array, colored by raster group
What a raster does is spatial, so the quickest way to tell whether it does what was wanted is to look at it. Colors run in the order the groups take their turns, so the picture shows the schedule as well as the pattern: with
CheckerboardRastera group’s electrodes should be scattered over the whole array rather than gathered into a line, and neighboring colors should not lie next to one another in a consistent direction.- Parameters:
implant (
ProsthesisSystem) – The implant to draw, or itsElectrodeArray. Its electrodes are the ones the raster is asked about, so this has to be an implant the raster covers.annotate (bool, optional) – Whether to write the group index into each electrode. If None, they are written whenever there are few enough electrodes (at most 120) for the numbers to be readable.
ax (matplotlib.axes.Axes, optional) – Axes to draw on. If None, uses the current axes.
cmap (str, optional) – Matplotlib colormap the group colors are taken from, evenly spaced. A sequential map is the useful default, since the order the colors run in is the order the groups fire in.
autoscale (bool, optional) – Whether to fit the x/y limits to the implant.
- Returns:
ax – The axes drawn on.
- Return type:
matplotlib.axes.Axes
- class pulse2percept.implants.rasters.CheckerboardRaster(implant, n_groups, balance=0.05, group_dur=None)[source]
Split electrodes into groups that are spread as far apart as possible
Implements a generalized form of the checkerboard raster pattern tested in [Kasowski2025], which found that scattering raster groups over the whole array beat horizontal, vertical, and random rasters at letter recognition and motion discrimination, and matched not rastering at all.
Mathematically speaking, a raster group is a coset of a sublattice of the electrode grid. Within a group, electrodes sit as far from one another as the electrode count allows. Each group is a coarser copy of the grid. Between groups, the order is chosen so the pattern doubles back rather than marching on (to reduce apparent motion). For example: Five groups on a square grid come out one over, two over, one back, two over, so that the percept steps right, down, left, down, and back rather than sliding across the array.
The grid does not have to be rectangular: hexagonal grids, rotated grids, grids with unequal row and column spacing, and grids with electrodes trimmed off are all handled, since the pattern is derived from where the electrodes actually are. Arrays whose electrodes do not lie on a grid at all raise
NotImplementedError.Note
Not every
n_groupsfits a given grid, and one that does not raises aValueErrorand specifies counts that do.Both halves of the pattern are searched for when the raster is built, and the order the groups fire in is settled exactly only up to eight groups; beyond that a heuristic stands in for it, and the search grows with the group count.
It is worth checking
min_spacingon the ones that do fit, because a count can be accepted and still leave neighbors in the same group. The standard example is two groups on a hex grid, which degenerates to a line raster. In other words, implants like PRIMA cannot be two-colored; they want 3, 4, or 7 raster groups instead.Added in version 0.10.0.
- Parameters:
implant (
ProsthesisSystem) – The implant to build the pattern for, or itsElectrodeArray. The electrodes have to lie on a grid, and their names are how the raster recognizes them later, so this has to be the implant the stimulus will be applied to.n_groups (int) – Number of groups to split the electrodes into.
balance (float, optional) – How much bigger the largest group may be than an even split would make it, as a fraction of it. The largest group is what sets the current the stimulator has to source, so this is the price being paid; what it buys is spacing, because the patterns that spread furthest do not always land evenly on a grid whose edges have been trimmed. Pass 0 to add no imbalance beyond the rounding an uneven electrode count forces anyway – 378 electrodes in 5 groups are 76, 76, 75, 75, 76 at
balance=0, never 76 apiece – and take whatever spacing comes with it.
Examples
Five groups of twelve on Argus II, as in [Kasowski2025]:
>>> from pulse2percept.implants import ArgusII, CheckerboardRaster >>> implant = ArgusII() >>> implant.raster = CheckerboardRaster(implant, 5) >>> implant.raster.n_groups 5
No two electrodes of a group are closer than sqrt(5) pitches, where a line raster would have them adjacent:
>>> round(implant.raster.min_spacing / 575, 3) # 575 um pitch 2.236
The pattern is easiest to check by eye (
plot()), and the electrodes that fire together aremembers():>>> implant.raster.members(implant.electrode_names, 0)[:4].tolist() ['A1', 'A6', 'B3', 'B8']
- property n_groups
Number of raster groups
- property min_spacing
Distance (um) between the closest two electrodes of a group
How much the checkerboard bought over a line raster, which leaves neighboring electrodes in the same group and so would report the electrode pitch. Measured between electrodes the implant actually has, so a small or trimmed array can come out better spaced than the pattern it was cut from. Infinite when no group holds more than one electrode, since then no two electrodes ever fire together.
- members(electrodes, group)[source]
The electrodes that take their turn together in one group
The inverse of
groups(), which says what group each electrode is in. This says which electrodes are in a group.- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
group (int) – Which group to look up, in
0..n_groups-1. Groups take their turns in index order, so group 0 is the one that goes first.
- Returns:
members – The entries of
electrodesbelonging togroup, in the order they were given: names in, names out.- Return type:
array
Examples
>>> from pulse2percept.implants import ArgusII, SequentialRaster >>> names = ArgusII().electrode_names >>> SequentialRaster(6).members(names, 0)[:4] array(['A1', 'A2', 'A3', 'A4'], dtype='<U3')
- offsets(electrodes, period)[source]
How far behind group 0 each electrode’s slot begins
- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
period (float) – The pulse period (ms) a sweep has to fit into.
- Returns:
offset – Time (ms) between the start of a sweep and the start of this electrode’s slot.
- Return type:
(n_electrodes,) float array
- plot(implant, annotate=None, ax=None, cmap='viridis', autoscale=True)[source]
Plot the electrode array, colored by raster group
What a raster does is spatial, so the quickest way to tell whether it does what was wanted is to look at it. Colors run in the order the groups take their turns, so the picture shows the schedule as well as the pattern: with
CheckerboardRastera group’s electrodes should be scattered over the whole array rather than gathered into a line, and neighboring colors should not lie next to one another in a consistent direction.- Parameters:
implant (
ProsthesisSystem) – The implant to draw, or itsElectrodeArray. Its electrodes are the ones the raster is asked about, so this has to be an implant the raster covers.annotate (bool, optional) – Whether to write the group index into each electrode. If None, they are written whenever there are few enough electrodes (at most 120) for the numbers to be readable.
ax (matplotlib.axes.Axes, optional) – Axes to draw on. If None, uses the current axes.
cmap (str, optional) – Matplotlib colormap the group colors are taken from, evenly spaced. A sequential map is the useful default, since the order the colors run in is the order the groups fire in.
autoscale (bool, optional) – Whether to fit the x/y limits to the implant.
- Returns:
ax – The axes drawn on.
- Return type:
matplotlib.axes.Axes
- class pulse2percept.implants.rasters.CustomRaster(groups, group_dur=None)[source]
Assign electrodes to raster groups by name
Added in version 0.10.0.
- Parameters:
Examples
Fire the four corners of Argus II before everything else. Every other electrode has to be given a group too, or the current limit that the raster exists to respect could be violated without anyone noticing:
>>> from pulse2percept.implants import ArgusII, CustomRaster >>> corners = ['A1', 'A10', 'F1', 'F10'] >>> rest = [e for e in ArgusII().electrode_names if e not in corners] >>> raster = CustomRaster([corners, rest]) >>> raster.n_groups 2
- property n_groups
Number of raster groups
- members(electrodes, group)[source]
The electrodes that take their turn together in one group
The inverse of
groups(), which says what group each electrode is in. This says which electrodes are in a group.- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
group (int) – Which group to look up, in
0..n_groups-1. Groups take their turns in index order, so group 0 is the one that goes first.
- Returns:
members – The entries of
electrodesbelonging togroup, in the order they were given: names in, names out.- Return type:
array
Examples
>>> from pulse2percept.implants import ArgusII, SequentialRaster >>> names = ArgusII().electrode_names >>> SequentialRaster(6).members(names, 0)[:4] array(['A1', 'A2', 'A3', 'A4'], dtype='<U3')
- offsets(electrodes, period)[source]
How far behind group 0 each electrode’s slot begins
- Parameters:
electrodes (array_like) – Electrode names, in the order they appear in the stimulus.
period (float) – The pulse period (ms) a sweep has to fit into.
- Returns:
offset – Time (ms) between the start of a sweep and the start of this electrode’s slot.
- Return type:
(n_electrodes,) float array
- plot(implant, annotate=None, ax=None, cmap='viridis', autoscale=True)[source]
Plot the electrode array, colored by raster group
What a raster does is spatial, so the quickest way to tell whether it does what was wanted is to look at it. Colors run in the order the groups take their turns, so the picture shows the schedule as well as the pattern: with
CheckerboardRastera group’s electrodes should be scattered over the whole array rather than gathered into a line, and neighboring colors should not lie next to one another in a consistent direction.- Parameters:
implant (
ProsthesisSystem) – The implant to draw, or itsElectrodeArray. Its electrodes are the ones the raster is asked about, so this has to be an implant the raster covers.annotate (bool, optional) – Whether to write the group index into each electrode. If None, they are written whenever there are few enough electrodes (at most 120) for the numbers to be readable.
ax (matplotlib.axes.Axes, optional) – Axes to draw on. If None, uses the current axes.
cmap (str, optional) – Matplotlib colormap the group colors are taken from, evenly spaced. A sequential map is the useful default, since the order the colors run in is the order the groups fire in.
autoscale (bool, optional) – Whether to fit the x/y limits to the implant.
- Returns:
ax – The axes drawn on.
- Return type:
matplotlib.axes.Axes