Skip to main content

stimgrid

Your stimulator cannot produce every pulse you write down. It sets current in fixed steps and timing on a fixed clock, so every value gets rounded. For an asymmetric pulse (for example a short strong first phase and a long weak recharge phase) that rounding quietly breaks the charge balance you designed. A rate of 130 Hz may also become 129.87 Hz.

stimgrid shows what your device will actually deliver, and finds the nearest pulse on the device's grid whose two phases carry exactly equal charge. Pure Python, no dependencies.

For electrical neurostimulation and microstimulation on multi-electrode arrays (MEAs), microelectrodes and neural interfaces, with presets for the Intan RHS2116, Multi Channel Systems STG4000 and STG5, Tucker-Davis Technologies IZ2 and Cortical Labs CL, and support for any other current-controlled stimulator from its current and timing steps.

Research use only. stimgrid works from the resolution you give it (or a preset's published figures). Check the result on your own hardware and settings.

Install

Requires Python 3.9 or newer, and nothing else.

pip install git+https://github.com/sanjeevmuthusamy/stimgrid

Or from a downloaded copy, inside the stimgrid folder:

pip install .

This gives you the stimgrid command. You can also run it without installing, from the folder, as PYTHONPATH=src python -m stimgrid ....

Quick start

  1. Find your device. stimgrid devices lists the built-in presets and where their figures come from. If yours is not there, give its own steps instead (see Devices).

  2. Check your pulse. Give the first phase, the second phase and the rate:

    stimgrid quick --device mcs-stg4000 --amplitude-ua 7.3 --width-us 117 \
      --second-amplitude-ua 1.9 --second-width-us 450 --frequency-hz 130
    
  3. Read the result (see Reading the output). Use the balanced pulse, or one of the options, in your stimulation software.

  4. Save it for later or for a lab mate as a request file, and rerun it with stimgrid fit my_pulse.json.

Leave out --second-amplitude-ua and --second-width-us for a symmetric pulse; the second phase then copies the first.

Reading the output

For the quick start command above:

device: mcs-stg4000 (1.6 mA version): 20 µs time steps, 0.2 µA steps, max 1600 µA
  plan      7.3 µA x 117 µs | gap 0 µs | 1.9 µA x 450 µs
  rounded   7.4 µA x 120 µs | gap 0 µs | 2 µA x 460 µs
            charge 0.888 nC (+3.97% vs plan), net -0.032 nC, imbalance 3.4783%
  balanced  7.2 µA x 120 µs | gap 0 µs | 1.8 µA x 480 µs
            charge 0.864 nC (+1.16% vs plan), net 0 nC, imbalance 0.0000%
  option 1  7 µA x 120 µs | gap 0 µs | 2 µA x 420 µs
            charge 0.84 nC (-1.65% vs plan), net 0 nC, imbalance 0.0000%
  ...
  rate      frequency_hz 130 Hz -> 129.87 Hz (385 steps per period, -0.100%)

WARNING SG201 simple rounding leaves the pulse unbalanced (imbalance 3.4783%, net -0.032 nC per pulse)

best pulse: {"amplitude_ua": 7.2, "width_us": 120, "second_amplitude_ua": 1.8, "second_width_us": 480}
result: OK
Line Meaning
device The grid being used: time step, current step and maximum current
plan The pulse you asked for: first phase, gap, second phase
rounded What the device delivers if you type your plan in as it is
balanced The closest pulse whose two phases carry exactly equal charge. Use this one
option 1-3 Other exactly balanced pulses, next closest first
charge Charge in the first phase, and how far it moved from your plan
net Charge left on the electrode by every pulse; 0 means balanced
imbalance The difference between the two phases' charges, as a share of the larger one
rate The rate you asked for, and the rate the device's clock really gives
WARNING / ERROR Findings, explained in Findings
best pulse The pulse to use, in a form you can paste into a file or program
result OK, or ERROR if the pulse breaks a device limit or cannot be balanced

Here the planned pulse would leave 0.032 nC on the electrode with every pulse; the balanced one leaves none and keeps charge per phase within 1.2% of the plan.

Another example: Intan RHS2116

An asymmetric pulse on an Intan RHS2116 with 0.1 µA steps, driven at 30,000 samples/s:

stimgrid quick --device intan-rhs2116 --step-ua 0.1 --sample-rate-hz 30000 \
  --amplitude-ua 7.3 --width-us 117 --second-amplitude-ua 1.9 --second-width-us 450 --frequency-hz 40
device: intan-rhs2116 (step 0.1 µA, 30000 samples/s): 33.3333 µs time steps, 0.1 µA steps, max 25.5 µA
  plan      7.3 µA x 117 µs | gap 0 µs | 1.9 µA x 450 µs
  rounded   7.3 µA x 133.333 µs | gap 0 µs | 1.9 µA x 466.667 µs
            charge 0.97333 nC (+13.96% vs plan), net 0.08667 nC, imbalance 8.9041%
  balanced  7.2 µA x 100 µs | gap 0 µs | 1.8 µA x 400 µs
            charge 0.72 nC (-15.70% vs plan), net 0 nC, imbalance 0.0000%
  option 1  7.2 µA x 133.333 µs | gap 0 µs | 1.8 µA x 533.333 µs
            charge 0.96 nC (+12.40% vs plan), net 0 nC, imbalance 0.0000%
  ...
WARNING SG201 simple rounding leaves the pulse unbalanced (imbalance 8.9041%, net 0.08667 nC per pulse)
WARNING SG203 the balanced pulse changes charge per phase by -15.7%

Simple rounding would leave 0.087 nC behind on every pulse. stimgrid lists exactly balanced pulses instead, closest to your plan first, and warns when the grid forces charge per phase to move.

How it works

  1. Rounded. Each amplitude, phase width and gap is rounded (halves up) to the device's steps and clamped to its ranges. This is what you get if you type the plan in and let the device round it.
  2. Balanced. stimgrid searches small changes to both phases, mostly the second phase's width and amplitude, the usual balancing knobs. On a gridded device, balance is checked exactly, as whole current steps times whole time steps, so "balanced" means equal to the last bit. On a device with continuous amplitude it also tries the first-phase amplitude that keeps your planned charge exactly.
  3. Ranking. Exactly balanced candidates are ranked by closeness to your plan, with first-phase charge counted double because it sets the effect. The best and three alternatives are shown.
  4. Rates. Each rate is rounded to a whole number of time steps per period, and the delivered rate is reported.

Command options

stimgrid quick takes the pulse as flags. Amplitudes are in µA, times in µs, rates in Hz.

Flag Meaning
--device NAME A preset from stimgrid devices
--step-ua, --sample-rate-hz, --range-ua Preset settings (see Devices)
--time-step-us, --amplitude-step-ua Your own device's steps, without a preset (or to override one)
--max-amplitude-ua, --max-charge-per-phase-nc Your own device's limits
--amplitude-ua, --width-us First phase (required)
--second-amplitude-ua, --second-width-us Second phase (defaults to the same as the first)
--gap-us Gap between the phases
--monophasic One phase only
--frequency-hz, --intra-burst-hz, --burst-rate-hz Rates to check against the time grid
--keep-first Keep the rounded first phase as it is; balance with the second phase only
--max-imbalance 0.001 Accept up to 0.1% imbalance as balanced (the default is exact)

Both quick and fit also take:

Flag Meaning
--format text Readable report (the default)
--format json The full report as JSON, for scripts
--format pulse Only the best pulse as JSON
--strict Treat warnings as failures (exit code 1), for automated checks

stimgrid --help and stimgrid quick --help list them too.

Devices

Preset Time step Amplitude Source
intan-rhs2116 1 / sample rate (--sample-rate-hz, default 30,000) --step-ua of 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5 or 10 µA, up to 255 steps Intan RHS2116 datasheet
cortical-labs-cl 20 µs continuous (no step published), up to 3 µA, 3 nC per phase Cortical Labs CL API docs (StimDesign)
mcs-stg4000 20 µs, up to 25 kHz --range-ua 1600: 0.2 µA steps; --range-ua 16000: 2 µA steps MCS STG 4002/4004/4008 datasheets
mcs-stg5 5 µs, up to 100 kHz --range-ua 160: 0.1 µA; 1600: 1 µA; 16000: 10 µA steps MCS STG5 brochure
tdt-iz2 1 / RZ sample rate (--sample-rate-hz, default 24,414.0625) continuous (no step published); --range-ua 300 (IZ2, IZ2M) or 3000 (IZ2H, IZ2MH) TDT IZ2 manual, IZ2M/IZ2MH spec sheet

All presets are current mode. The MCS range is the model version you bought (STG4000) or the range you select (STG5). The STG4000 160 µA version is left out because its official datasheet could not be confirmed.

Not included yet, because the vendor does not publish a current step or a timing step:

Device Published Missing
Axion Maestro Pro / Edge ±250 µA, ±1.2 V, phase at least 100 µs current step, time step
MaxWell MaxOne / MaxTwo 32 stimulation units, ±1.0 V current range and step, time step
Blackrock CereStim phase 44–65,535 µs, 4 Hz–5 kHz, 100 µA steps (macro model) micro model step, time step
A-M Systems 4100 100 mA with 1 µA resolution, pulses from 1 µs time step

If you have one of these, describe it with your own numbers (below), or send the values from your manual and they can become a preset.

stimgrid devices prints each preset's source. The RHS2116 has no pulse timers; the host sets every edge, so its timing grid depends on your host software. Any other stimulator can be described by its own numbers:

stimgrid quick --time-step-us 10 --amplitude-step-ua 0.5 --max-amplitude-ua 100 \
  --amplitude-ua 20 --width-us 105 --second-amplitude-ua 4 --second-width-us 520

Fields: time_step_us (required), amplitude_step_ua (leave out for continuous), max_amplitude_ua, min_phase_us, max_phase_us, max_charge_per_phase_nc, max_frequency_hz.

Request files

{
  "stimgrid": 1,
  "device": {"preset": "intan-rhs2116", "step_ua": 0.1, "sample_rate_hz": 30000},
  "pulse": {"amplitude_ua": 7.3, "width_us": 117, "second_amplitude_ua": 1.9, "second_width_us": 450},
  "rates": {"frequency_hz": 40},
  "max_imbalance": 0,
  "keep_first": false
}
stimgrid fit examples/intan_asymmetric.json
stimgrid fit examples/intan_asymmetric.json --format json
cat request.json | stimgrid fit - --format pulse

A preset takes its options (step_ua, sample_rate_hz or range_ua, as in the table) and any device field to override, for example {"preset": "intan-rhs2116", "step_ua": 0.1, "max_amplitude_ua": 10} or {"preset": "mcs-stg5", "range_ua": 160}. Invalid input names the exact place, for example pulse: width_us must be a positive number.

--format pulse prints only the best pulse, in the common shape (amplitude_ua, width_us, second_amplitude_ua, second_width_us, gap_us), ready to paste into a protocol file or another tool.

Findings

Code Level Meaning
SG101 error An amplitude is above the device's maximum
SG102 error A phase width is outside the device's range
SG103 error An amplitude is below one current step
SG104 error Charge per phase is above the device's maximum
SG105 warning The interphase gap rounds to zero
SG106 warning Rounding moves a value by more than 10%
SG201 warning Simple rounding leaves the pulse unbalanced
SG202 error No balanced pulse was found within the search and the device's limits
SG203 warning The balanced pulse changes charge per phase by more than 5%
SG301 warning A rate changes by more than 0.1% on whole periods
SG303 error The pulse is longer than one period at that rate
SG304 error A rate is above the device's maximum

Exit codes

Code Meaning
0 Fitted with no errors (and no warnings, with --strict)
1 Errors (or warnings, with --strict)
2 Bad input or usage
3 Internal error

From Python

from stimgrid import Pulse, Rates, fit, intan_rhs2116

report = fit(Pulse(7.3, 117, second_amplitude_ua=1.9, second_width_us=450),
             intan_rhs2116(step_ua=0.1, sample_rate_hz=30000), Rates(frequency_hz=40))
print(report.balanced.as_pulse(), report.balanced.imbalance)

Limits of this tool

  • It models one pulse shape: rectangular phases with an optional gap. It does not model DAC settling, electrode impedance, channel-to-channel mismatch or the RHS2116's trim settings.
  • Rates are assumed to fall on the same time grid as phase widths.
  • Continuous amplitude means the device's real amplitude resolution is unknown; the reported amplitude may need rounding again on the hardware.

Citing

If you use stimgrid in published work, please cite it. GitHub's "Cite this repository" button gives the reference from CITATION.cff:

Muthusamy S. stimgrid (version 0.2.0) [Computer software]. https://github.com/sanjeevmuthusamy/stimgrid

Tests

python -m unittest discover -s tests

Licence

MIT. See LICENSE.

Metadata

Release files for stimgrid 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stimgrid 0.2.0
File Size Uploaded Provenance Actions
stimgrid-0.2.0.tar.gz 28.2 kB With provenance Details for stimgrid-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for stimgrid 0.2.0
File Interpreter ABI Platform Actions
stimgrid-0.2.0-py3-none-any.whl Python 3 none any Details for stimgrid-0.2.0-py3-none-any.whl

Total release size: 50.4 kB

Release files / stimgrid-0.2.0.tar.gz

Download URL stimgrid-0.2.0.tar.gz
Size 28.2 kB
Tags Source
SHA-256 checksum
How to use checksums
800c8fd5ccdf75172e3c48c4630d89bea18dd123593d8f7441176aec6f1c26d6
BLAKE2b-256 checksum
How to use checksums
432b66ed03327af9318927dfe98adff7d00e488b2d5b497acfb4318797efb8c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Release files / stimgrid-0.2.0-py3-none-any.whl

Download URL stimgrid-0.2.0-py3-none-any.whl
Size 22.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e5c5e23371d87d8b65f1138ad3304c0f0677604d1e5c8a47256b4bab76a6b25a
BLAKE2b-256 checksum
How to use checksums
6a0029be586facd67500a248919ea6f9778b48eff690f0b96d82df60c9e5b2f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Security for stimgrid 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for stimgrid-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page