Release Notes

v0.10.0 Encoders (unreleased)

Highlights:

  • New Encoder classes translate images and videos into electrical stimulation. Amplitude and frequency modulation are supported, including stimulator timing, input resolution, and electrode multiplexing.

  • New Raster classes describe how stimulators multiplex electrodes that cannot be driven simultaneously. Each group starts its pulse a fixed group_dur behind the one before it. CheckerboardRaster implements the checkerboard pattern of [Kasowski2025].

  • play() and play() are roughly 100x faster and produce much smaller notebooks and documentation pages.

  • New units module. Arguments at pulse2percept’s public API boundaries may now be given as physical quantities (50 * uA, 450 * us, 15 * mm, 2 * dva), which are dimension-checked and converted to the unit the code expects. Bare numbers keep working and keep their documented meaning everywhere, and are never warned about. See Physical Units.

  • Python 3.14 is now supported. Python 3.11 and NumPy 2 are now required.

API changes:

  • The axlambda parameter of the axon map models was renamed to lam, which sits better next to rho. The old name still works everywhere the new one does, but raises a DeprecationWarning and will be removed in v0.11.0.

  • FadingTemporal is now driven by \(\max(-A, 0)\) rather than \(-A\): anodic current no longer reduces brightness, it is ignored. A stimulus that is purely cathodic is unaffected.

  • FadingTemporal requires tau >= dt. The integrator steps explicitly, so a shorter time constant overshoots its drive by dt / tau and oscillates instead of decaying.

  • Temporal models gained a reduce parameter. When predict_percept picks the output times itself (t_percept=None), reduce='peak' makes each point report the highest brightness reached over the interval leading up to it rather than the brightness at the instant it ends. Naming t_percept still asks for those instants.

  • encode() and encode() now use AmplitudeEncoder. Most importantly, gray levels map to amp_range absolutely rather than being stretched to fill it, and each frame now produces a pulse train rather than a single pulse. Pass stretch=True for the old gray-level mapping.

  • BiphasicPulseTrain now records amp in its metadata as a magnitude.

  • ProsthesisSystem now exposes raster and max_current.

  • play gained a fmt argument. play() defaults to JPEG, which is substantially smaller and faster to build for color video; play() defaults to PNG, which is pixel-exact and nearly as compact for scalar data. Pass fmt to override.

  • Objects now record what their numbers mean: unit, time_unit, values(), times(), coordinates(), time_unit, times(), and get_param_units(). Models declare stimulus_unit, space_unit and time_unit; visual field maps declare visual_unit and tissue_unit. Storage is unchanged: these report the unit, they do not convert what is stored.

  • predict_percept now raises DimensionMismatchError when the stimulus is not the physical quantity the model reads. Assigning an ImageStimulus or VideoStimulus straight to implant.stim previously had its gray levels silently treated as microamps. Encode it with AmplitudeEncoder or FrequencyEncoder first, or give the implant a preprocess function that does. The same check guards the safe_mode and max_current safety checks.

  • is_charge_balanced returns None for a stimulus that is not a current, rather than answering a question that does not apply to it.

  • from_coords() requires xrange, yrange and xystep together when locs is not given. They previously defaulted to a visual-field range, which placed implants at coordinates that were never meant to be microns.

  • Minimum dependency versions were raised for NumPy 2 compatibility. NumPy 1.x users should remain on v0.9.1.

Bug fixes:

  • PulseTrain no longer ends on partial, unbalanced pulses when its frequency does not divide stim_dur.

  • Stimulus time axes now use float64 precision, fixing false is_charge_balanced failures for longer pulse trains and improving frequency-modulation accuracy.

  • EnsembleImplant now merges nearly identical time points using the same tolerance as Stimulus.

  • Various fixes for ImageStimulus: keyword arguments are passed on to scikit-image; inputs are no longer modified in place; center() honors its loc argument instead of always centering on the middle of the image.

  • play(), play(), and save() now handle single-frame inputs correctly and give a useful error for nonuniform time axes.

  • vid_shape now reports the number of frames the stimulus actually has, rather than the number the source video had before compress=True dropped the redundant time points. This fixes play() and every other operation that reshapes data back into frames. Playing a video that was compressed in space raises an explanatory error instead of a reshape error.

  • A four-channel VideoStimulus is played back as RGBA, as documented: the alpha channel is preserved for fmt='png' and composited onto the axes background for fmt='jpg', which cannot carry it. Previously the four channels were reinterpreted as RGB, which sheared the color channels across every row.

  • Stimulus metadata now survives predict_percept and other transformations.

  • When a temporal model picks its own output times, the last one no longer falls after the end of the stimulus. The end of the range was nudged by one millisecond to make it inclusive, which added a frame wherever the frame interval did not divide that millisecond – most visibly in DynaphosModel with dt finer than 1 ms.

  • Visual-field-map equality now handles array-valued attributes correctly, maps are hashable again, and maps of different classes no longer compare equal.

  • cortex_to_dva() (and the v1_to_dva, v2_to_dva, v3_to_dva methods that call it) now returns coordinates with the same shape as its input. Previously NaN inputs were dropped rather than mapped to NaN outputs, which silently shifted every point after them into the wrong slot, and multidimensional inputs came back flattened. Cortical points that land exactly on a mesh vertex now map to that vertex instead of dividing by zero and returning NaN. The docstrings said the cortical coordinates were in mm; they are in um, as the code always assumed.

v0.9.1 (2026-08-06)

Highlights:

  • Python 3.13 support; the minimum supported Python is now 3.10 (PR #649)

  • NumPy 2.x support: wheels import under both NumPy 1.x and 2.x, so installing pulse2percept no longer downgrades NumPy in environments that ship it, such as Google Colab (PR #635, PR #736)

  • Removed the jax engine and predict_percept_batched from BiphasicAxonMapModel; the engine argument of the effect models and the pad argument of calc_axon_sensitivity() are deprecated, and will be removed in v0.10.0 (PR #788)

  • Removed the model_selection module (PR #685) and support for the joblib and dask parallel backends (PR #686); the engine and scheduler model parameters are deprecated, and will be removed in v0.10.0 (PR #788)

  • n_jobs is now an alias for n_threads: either name sets the number of OpenMP threads, and None or -1 uses every core (PR #788)

  • More robust dataset downloads, with updated OSF endpoints (PR #754)

  • Smarter reshape_stim for implants (PR #680)

  • Installation is now tested inside the real Google Colab runtime (PR #777)

  • Various bug fixes (PR #682, PR #700, PR #732, PR #776)

v0.9.0 Cortex (2025-02-17)

Highlights:

v0.8.0 Retina (2022-05-05)

Highlights:

v0.7.1 (2021-06-21)

Highlights:

v0.7.0 Implants (2021-04-04)

Highlights:

v0.6.0 API (2020-05-05)

Highlights:

v0.5.2 (2020-02-25)

Bug fix:

  • pulse2percept.retina.Nanduri2012: improved Cython implementation

v0.5.1 (2020-02-05)

Bug fixes:

  • pulse2percept.retina.Nanduri2012: allow switch between FFT/Cython

  • pulse2percept.retina.Horsager2009: respect use_jit option

  • pulse2percept.utils.center_vector: “cannot determine Numba type”

v0.5.0 Community (2019-11-29)

v0.4.3 Cython (2018-05-21)

Highlights:

  • Cython integration:

    • The model described in Nanduri et al. (2012) now uses a finite difference method implemented in Cython as opposed to FFT-based convolutions (PR #83)

    • Single-core benchmarks show a 200x speedup over a pure-Python implementation.

v0.3.0 Baby Steps (2018-02-20)

  • New, faster axon map calculation

  • Better plotting

  • Support for left/right eye