pulse2percept.implants.rasters

Raster, SequentialRaster, CheckerboardRaster, CustomRaster

Classes

CheckerboardRaster(implant, n_groups[, ...])

Split electrodes into groups that are spread as far apart as possible

CustomRaster(groups[, group_dur])

Assign electrodes to raster groups by name

Raster([group_dur])

Abstract base class for all raster patterns

SequentialRaster(n_groups[, interleave, ...])

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_dur after group 0 does, so a sweep spans n_groups * group_dur. Two things then keep groups apart for good (see Encoder):

  1. A pulse has to be short enough to finish before the next group’s turn begins.

  2. 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_dur apart 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=None the 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_dur the sweep is n_groups * group_dur whatever 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_dur regardless 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 clock rounds 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 the period argument of slot_dur() and offsets(). See pulse2percept.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 electrodes belonging to group, 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 CheckerboardRaster a 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 its ElectrodeArray. 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

slot_dur(period)[source]

Duration (ms) of one group’s slot

Parameters:

period (float) – The pulse period (ms) a sweep has to fit into, so that every group gets its turn before the first one comes round again.

Returns:

slot_durgroup_dur if one was given, else the period split evenly between the groups.

Return type:

float

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 ElectrodeGrid runs row by row. So on a 6x10 array such as ArgusII, 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.

  • group_dur (float, optional) – See Raster.

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

groups(electrodes)[source]

Assign each electrode to a raster group

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 electrodes belonging to group, 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 CheckerboardRaster a 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 its ElectrodeArray. 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

slot_dur(period)[source]

Duration (ms) of one group’s slot

Parameters:

period (float) – The pulse period (ms) a sweep has to fit into, so that every group gets its turn before the first one comes round again.

Returns:

slot_durgroup_dur if one was given, else the period split evenly between the groups.

Return type:

float

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_groups fits a given grid, and one that does not raises a ValueError and 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_spacing on 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 its ElectrodeArray. 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.

  • group_dur (float, optional) – See Raster.

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 are members():

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

groups(electrodes)[source]

Assign each electrode to a raster group

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 electrodes belonging to group, 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 CheckerboardRaster a 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 its ElectrodeArray. 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

slot_dur(period)[source]

Duration (ms) of one group’s slot

Parameters:

period (float) – The pulse period (ms) a sweep has to fit into, so that every group gets its turn before the first one comes round again.

Returns:

slot_durgroup_dur if one was given, else the period split evenly between the groups.

Return type:

float

class pulse2percept.implants.rasters.CustomRaster(groups, group_dur=None)[source]

Assign electrodes to raster groups by name

Added in version 0.10.0.

Parameters:
  • groups (list of lists, or dict) – Either a list whose i-th element holds the names of the electrodes in group i, or a dict mapping each electrode name onto its group index. Every electrode in the stimulus must be accounted for, and no electrode may appear in two groups.

  • group_dur (float, optional) – See Raster.

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

groups(electrodes)[source]

Assign each electrode to a raster group

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 electrodes belonging to group, 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 CheckerboardRaster a 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 its ElectrodeArray. 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

slot_dur(period)[source]

Duration (ms) of one group’s slot

Parameters:

period (float) – The pulse period (ms) a sweep has to fit into, so that every group gets its turn before the first one comes round again.

Returns:

slot_durgroup_dur if one was given, else the period split evenly between the groups.

Return type:

float