stimgrid 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
stimgrid-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sanjeev Muthusamy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: stimgrid
3
+ Version: 0.2.0
4
+ Summary: Fit stimulation pulses to what a stimulator can actually produce, and find the nearest exactly charge-balanced pulse.
5
+ Author: Sanjeev Muthusamy
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sanjeevmuthusamy/stimgrid
8
+ Project-URL: Issues, https://github.com/sanjeevmuthusamy/stimgrid/issues
9
+ Keywords: neuroscience,electrophysiology,neurostimulation,electrical stimulation,microstimulation,charge balance,charge-balanced,biphasic pulse,asymmetric pulse,stimulation safety,multi-electrode array,MEA,microelectrode,neural interface,brain-computer interface,DAC quantization,Intan,RHS2116,Multi Channel Systems,STG4000,STG5,Tucker-Davis Technologies,IZ2,Cortical Labs
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Environment :: Console
22
+ Classifier: Intended Audience :: Science/Research
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
25
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Dynamic: license-file
30
+
31
+ # stimgrid
32
+
33
+ Your stimulator cannot produce every pulse you write down. It sets current in fixed steps and timing on
34
+ a fixed clock, so every value gets rounded. For an asymmetric pulse (for example a short strong first
35
+ phase and a long weak recharge phase) that rounding quietly breaks the charge balance you designed. A
36
+ rate of 130 Hz may also become 129.87 Hz.
37
+
38
+ stimgrid shows what your device will actually deliver, and finds the nearest pulse on the device's grid
39
+ whose two phases carry exactly equal charge. Pure Python, no dependencies.
40
+
41
+ For electrical neurostimulation and microstimulation on multi-electrode arrays (MEAs), microelectrodes
42
+ and neural interfaces, with presets for the Intan RHS2116, Multi Channel Systems STG4000 and STG5,
43
+ Tucker-Davis Technologies IZ2 and Cortical Labs CL, and support for any other current-controlled
44
+ stimulator from its current and timing steps.
45
+
46
+ **Research use only.** stimgrid works from the resolution you give it (or a preset's published
47
+ figures). Check the result on your own hardware and settings.
48
+
49
+ ## Install
50
+
51
+ Requires Python 3.9 or newer, and nothing else.
52
+
53
+ ```bash
54
+ pip install git+https://github.com/sanjeevmuthusamy/stimgrid
55
+ ```
56
+
57
+ Or from a downloaded copy, inside the `stimgrid` folder:
58
+
59
+ ```bash
60
+ pip install .
61
+ ```
62
+
63
+ This gives you the `stimgrid` command. You can also run it without installing, from the folder, as
64
+ `PYTHONPATH=src python -m stimgrid ...`.
65
+
66
+ ## Quick start
67
+
68
+ 1. **Find your device.** `stimgrid devices` lists the built-in presets and where their figures come
69
+ from. If yours is not there, give its own steps instead (see [Devices](#devices)).
70
+ 2. **Check your pulse.** Give the first phase, the second phase and the rate:
71
+
72
+ ```bash
73
+ stimgrid quick --device mcs-stg4000 --amplitude-ua 7.3 --width-us 117 \
74
+ --second-amplitude-ua 1.9 --second-width-us 450 --frequency-hz 130
75
+ ```
76
+
77
+ 3. **Read the result** (see [Reading the output](#reading-the-output)). Use the `balanced` pulse, or one
78
+ of the options, in your stimulation software.
79
+ 4. **Save it for later or for a lab mate** as a [request file](#request-files), and rerun it with
80
+ `stimgrid fit my_pulse.json`.
81
+
82
+ Leave out `--second-amplitude-ua` and `--second-width-us` for a symmetric pulse; the second phase then
83
+ copies the first.
84
+
85
+ ## Reading the output
86
+
87
+ For the quick start command above:
88
+
89
+ ```
90
+ device: mcs-stg4000 (1.6 mA version): 20 µs time steps, 0.2 µA steps, max 1600 µA
91
+ plan 7.3 µA x 117 µs | gap 0 µs | 1.9 µA x 450 µs
92
+ rounded 7.4 µA x 120 µs | gap 0 µs | 2 µA x 460 µs
93
+ charge 0.888 nC (+3.97% vs plan), net -0.032 nC, imbalance 3.4783%
94
+ balanced 7.2 µA x 120 µs | gap 0 µs | 1.8 µA x 480 µs
95
+ charge 0.864 nC (+1.16% vs plan), net 0 nC, imbalance 0.0000%
96
+ option 1 7 µA x 120 µs | gap 0 µs | 2 µA x 420 µs
97
+ charge 0.84 nC (-1.65% vs plan), net 0 nC, imbalance 0.0000%
98
+ ...
99
+ rate frequency_hz 130 Hz -> 129.87 Hz (385 steps per period, -0.100%)
100
+
101
+ WARNING SG201 simple rounding leaves the pulse unbalanced (imbalance 3.4783%, net -0.032 nC per pulse)
102
+
103
+ best pulse: {"amplitude_ua": 7.2, "width_us": 120, "second_amplitude_ua": 1.8, "second_width_us": 480}
104
+ result: OK
105
+ ```
106
+
107
+ | Line | Meaning |
108
+ |---|---|
109
+ | `device` | The grid being used: time step, current step and maximum current |
110
+ | `plan` | The pulse you asked for: first phase, gap, second phase |
111
+ | `rounded` | What the device delivers if you type your plan in as it is |
112
+ | `balanced` | The closest pulse whose two phases carry exactly equal charge. **Use this one** |
113
+ | `option 1-3` | Other exactly balanced pulses, next closest first |
114
+ | `charge` | Charge in the first phase, and how far it moved from your plan |
115
+ | `net` | Charge left on the electrode by every pulse; 0 means balanced |
116
+ | `imbalance` | The difference between the two phases' charges, as a share of the larger one |
117
+ | `rate` | The rate you asked for, and the rate the device's clock really gives |
118
+ | `WARNING` / `ERROR` | Findings, explained in [Findings](#findings) |
119
+ | `best pulse` | The pulse to use, in a form you can paste into a file or program |
120
+ | `result` | `OK`, or `ERROR` if the pulse breaks a device limit or cannot be balanced |
121
+
122
+ Here the planned pulse would leave 0.032 nC on the electrode with every pulse; the balanced one leaves
123
+ none and keeps charge per phase within 1.2% of the plan.
124
+
125
+ ## Another example: Intan RHS2116
126
+
127
+ An asymmetric pulse on an Intan RHS2116 with 0.1 µA steps, driven at 30,000 samples/s:
128
+
129
+ ```bash
130
+ stimgrid quick --device intan-rhs2116 --step-ua 0.1 --sample-rate-hz 30000 \
131
+ --amplitude-ua 7.3 --width-us 117 --second-amplitude-ua 1.9 --second-width-us 450 --frequency-hz 40
132
+ ```
133
+
134
+ ```
135
+ device: intan-rhs2116 (step 0.1 µA, 30000 samples/s): 33.3333 µs time steps, 0.1 µA steps, max 25.5 µA
136
+ plan 7.3 µA x 117 µs | gap 0 µs | 1.9 µA x 450 µs
137
+ rounded 7.3 µA x 133.333 µs | gap 0 µs | 1.9 µA x 466.667 µs
138
+ charge 0.97333 nC (+13.96% vs plan), net 0.08667 nC, imbalance 8.9041%
139
+ balanced 7.2 µA x 100 µs | gap 0 µs | 1.8 µA x 400 µs
140
+ charge 0.72 nC (-15.70% vs plan), net 0 nC, imbalance 0.0000%
141
+ option 1 7.2 µA x 133.333 µs | gap 0 µs | 1.8 µA x 533.333 µs
142
+ charge 0.96 nC (+12.40% vs plan), net 0 nC, imbalance 0.0000%
143
+ ...
144
+ WARNING SG201 simple rounding leaves the pulse unbalanced (imbalance 8.9041%, net 0.08667 nC per pulse)
145
+ WARNING SG203 the balanced pulse changes charge per phase by -15.7%
146
+ ```
147
+
148
+ Simple rounding would leave 0.087 nC behind on every pulse. stimgrid lists exactly balanced pulses
149
+ instead, closest to your plan first, and warns when the grid forces charge per phase to move.
150
+
151
+ ## How it works
152
+
153
+ 1. **Rounded.** Each amplitude, phase width and gap is rounded (halves up) to the device's steps and
154
+ clamped to its ranges. This is what you get if you type the plan in and let the device round it.
155
+ 2. **Balanced.** stimgrid searches small changes to both phases, mostly the second phase's width and
156
+ amplitude, the usual balancing knobs. On a gridded device, balance is checked exactly, as whole
157
+ current steps times whole time steps, so "balanced" means equal to the last bit. On a device with
158
+ continuous amplitude it also tries the first-phase amplitude that keeps your planned charge exactly.
159
+ 3. **Ranking.** Exactly balanced candidates are ranked by closeness to your plan, with first-phase charge
160
+ counted double because it sets the effect. The best and three alternatives are shown.
161
+ 4. **Rates.** Each rate is rounded to a whole number of time steps per period, and the delivered rate is
162
+ reported.
163
+
164
+ ## Command options
165
+
166
+ `stimgrid quick` takes the pulse as flags. Amplitudes are in µA, times in µs, rates in Hz.
167
+
168
+ | Flag | Meaning |
169
+ |---|---|
170
+ | `--device NAME` | A preset from `stimgrid devices` |
171
+ | `--step-ua`, `--sample-rate-hz`, `--range-ua` | Preset settings (see [Devices](#devices)) |
172
+ | `--time-step-us`, `--amplitude-step-ua` | Your own device's steps, without a preset (or to override one) |
173
+ | `--max-amplitude-ua`, `--max-charge-per-phase-nc` | Your own device's limits |
174
+ | `--amplitude-ua`, `--width-us` | First phase (required) |
175
+ | `--second-amplitude-ua`, `--second-width-us` | Second phase (defaults to the same as the first) |
176
+ | `--gap-us` | Gap between the phases |
177
+ | `--monophasic` | One phase only |
178
+ | `--frequency-hz`, `--intra-burst-hz`, `--burst-rate-hz` | Rates to check against the time grid |
179
+ | `--keep-first` | Keep the rounded first phase as it is; balance with the second phase only |
180
+ | `--max-imbalance 0.001` | Accept up to 0.1% imbalance as balanced (the default is exact) |
181
+
182
+ Both `quick` and `fit` also take:
183
+
184
+ | Flag | Meaning |
185
+ |---|---|
186
+ | `--format text` | Readable report (the default) |
187
+ | `--format json` | The full report as JSON, for scripts |
188
+ | `--format pulse` | Only the best pulse as JSON |
189
+ | `--strict` | Treat warnings as failures (exit code 1), for automated checks |
190
+
191
+ `stimgrid --help` and `stimgrid quick --help` list them too.
192
+
193
+ ## Devices
194
+
195
+ | Preset | Time step | Amplitude | Source |
196
+ |---|---|---|---|
197
+ | `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 |
198
+ | `cortical-labs-cl` | 20 µs | continuous (no step published), up to 3 µA, 3 nC per phase | Cortical Labs CL API docs (StimDesign) |
199
+ | `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 |
200
+ | `mcs-stg5` | 5 µs, up to 100 kHz | `--range-ua 160`: 0.1 µA; `1600`: 1 µA; `16000`: 10 µA steps | MCS STG5 brochure |
201
+ | `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 |
202
+
203
+ All presets are current mode. The MCS range is the model version you bought (STG4000) or the range you
204
+ select (STG5). The STG4000 160 µA version is left out because its official datasheet could not be
205
+ confirmed.
206
+
207
+ **Not included yet**, because the vendor does not publish a current step or a timing step:
208
+
209
+ | Device | Published | Missing |
210
+ |---|---|---|
211
+ | Axion Maestro Pro / Edge | ±250 µA, ±1.2 V, phase at least 100 µs | current step, time step |
212
+ | MaxWell MaxOne / MaxTwo | 32 stimulation units, ±1.0 V | current range and step, time step |
213
+ | Blackrock CereStim | phase 44–65,535 µs, 4 Hz–5 kHz, 100 µA steps (macro model) | micro model step, time step |
214
+ | A-M Systems 4100 | 100 mA with 1 µA resolution, pulses from 1 µs | time step |
215
+
216
+ If you have one of these, describe it with your own numbers (below), or send the values from your
217
+ manual and they can become a preset.
218
+
219
+ `stimgrid devices` prints each preset's source. The RHS2116 has no pulse timers; the host sets every
220
+ edge, so its timing grid depends on your host software. Any other stimulator can be described by its
221
+ own numbers:
222
+
223
+ ```bash
224
+ stimgrid quick --time-step-us 10 --amplitude-step-ua 0.5 --max-amplitude-ua 100 \
225
+ --amplitude-ua 20 --width-us 105 --second-amplitude-ua 4 --second-width-us 520
226
+ ```
227
+
228
+ Fields: `time_step_us` (required), `amplitude_step_ua` (leave out for continuous), `max_amplitude_ua`,
229
+ `min_phase_us`, `max_phase_us`, `max_charge_per_phase_nc`, `max_frequency_hz`.
230
+
231
+ ## Request files
232
+
233
+ ```json
234
+ {
235
+ "stimgrid": 1,
236
+ "device": {"preset": "intan-rhs2116", "step_ua": 0.1, "sample_rate_hz": 30000},
237
+ "pulse": {"amplitude_ua": 7.3, "width_us": 117, "second_amplitude_ua": 1.9, "second_width_us": 450},
238
+ "rates": {"frequency_hz": 40},
239
+ "max_imbalance": 0,
240
+ "keep_first": false
241
+ }
242
+ ```
243
+
244
+ ```bash
245
+ stimgrid fit examples/intan_asymmetric.json
246
+ stimgrid fit examples/intan_asymmetric.json --format json
247
+ cat request.json | stimgrid fit - --format pulse
248
+ ```
249
+
250
+ A preset takes its options (`step_ua`, `sample_rate_hz` or `range_ua`, as in the table) and any device
251
+ field to override, for example `{"preset": "intan-rhs2116", "step_ua": 0.1, "max_amplitude_ua": 10}` or
252
+ `{"preset": "mcs-stg5", "range_ua": 160}`. Invalid input names the exact
253
+ place, for example `pulse: width_us must be a positive number`.
254
+
255
+ `--format pulse` prints only the best pulse, in the common shape (`amplitude_ua`, `width_us`,
256
+ `second_amplitude_ua`, `second_width_us`, `gap_us`), ready to paste into a protocol file or another tool.
257
+
258
+ ## Findings
259
+
260
+ | Code | Level | Meaning |
261
+ |---|---|---|
262
+ | SG101 | error | An amplitude is above the device's maximum |
263
+ | SG102 | error | A phase width is outside the device's range |
264
+ | SG103 | error | An amplitude is below one current step |
265
+ | SG104 | error | Charge per phase is above the device's maximum |
266
+ | SG105 | warning | The interphase gap rounds to zero |
267
+ | SG106 | warning | Rounding moves a value by more than 10% |
268
+ | SG201 | warning | Simple rounding leaves the pulse unbalanced |
269
+ | SG202 | error | No balanced pulse was found within the search and the device's limits |
270
+ | SG203 | warning | The balanced pulse changes charge per phase by more than 5% |
271
+ | SG301 | warning | A rate changes by more than 0.1% on whole periods |
272
+ | SG303 | error | The pulse is longer than one period at that rate |
273
+ | SG304 | error | A rate is above the device's maximum |
274
+
275
+ ## Exit codes
276
+
277
+ | Code | Meaning |
278
+ |---|---|
279
+ | 0 | Fitted with no errors (and no warnings, with `--strict`) |
280
+ | 1 | Errors (or warnings, with `--strict`) |
281
+ | 2 | Bad input or usage |
282
+ | 3 | Internal error |
283
+
284
+ ## From Python
285
+
286
+ ```python
287
+ from stimgrid import Pulse, Rates, fit, intan_rhs2116
288
+
289
+ report = fit(Pulse(7.3, 117, second_amplitude_ua=1.9, second_width_us=450),
290
+ intan_rhs2116(step_ua=0.1, sample_rate_hz=30000), Rates(frequency_hz=40))
291
+ print(report.balanced.as_pulse(), report.balanced.imbalance)
292
+ ```
293
+
294
+ ## Limits of this tool
295
+
296
+ - It models one pulse shape: rectangular phases with an optional gap. It does not model DAC settling,
297
+ electrode impedance, channel-to-channel mismatch or the RHS2116's trim settings.
298
+ - Rates are assumed to fall on the same time grid as phase widths.
299
+ - Continuous amplitude means the device's real amplitude resolution is unknown; the reported amplitude
300
+ may need rounding again on the hardware.
301
+
302
+ ## Citing
303
+
304
+ If you use stimgrid in published work, please cite it. GitHub's "Cite this repository" button gives the
305
+ reference from `CITATION.cff`:
306
+
307
+ > Muthusamy S. stimgrid (version 0.2.0) [Computer software]. https://github.com/sanjeevmuthusamy/stimgrid
308
+
309
+ ## Tests
310
+
311
+ ```bash
312
+ python -m unittest discover -s tests
313
+ ```
314
+
315
+ ## Licence
316
+
317
+ MIT. See `LICENSE`.
@@ -0,0 +1,287 @@
1
+ # stimgrid
2
+
3
+ Your stimulator cannot produce every pulse you write down. It sets current in fixed steps and timing on
4
+ a fixed clock, so every value gets rounded. For an asymmetric pulse (for example a short strong first
5
+ phase and a long weak recharge phase) that rounding quietly breaks the charge balance you designed. A
6
+ rate of 130 Hz may also become 129.87 Hz.
7
+
8
+ stimgrid shows what your device will actually deliver, and finds the nearest pulse on the device's grid
9
+ whose two phases carry exactly equal charge. Pure Python, no dependencies.
10
+
11
+ For electrical neurostimulation and microstimulation on multi-electrode arrays (MEAs), microelectrodes
12
+ and neural interfaces, with presets for the Intan RHS2116, Multi Channel Systems STG4000 and STG5,
13
+ Tucker-Davis Technologies IZ2 and Cortical Labs CL, and support for any other current-controlled
14
+ stimulator from its current and timing steps.
15
+
16
+ **Research use only.** stimgrid works from the resolution you give it (or a preset's published
17
+ figures). Check the result on your own hardware and settings.
18
+
19
+ ## Install
20
+
21
+ Requires Python 3.9 or newer, and nothing else.
22
+
23
+ ```bash
24
+ pip install git+https://github.com/sanjeevmuthusamy/stimgrid
25
+ ```
26
+
27
+ Or from a downloaded copy, inside the `stimgrid` folder:
28
+
29
+ ```bash
30
+ pip install .
31
+ ```
32
+
33
+ This gives you the `stimgrid` command. You can also run it without installing, from the folder, as
34
+ `PYTHONPATH=src python -m stimgrid ...`.
35
+
36
+ ## Quick start
37
+
38
+ 1. **Find your device.** `stimgrid devices` lists the built-in presets and where their figures come
39
+ from. If yours is not there, give its own steps instead (see [Devices](#devices)).
40
+ 2. **Check your pulse.** Give the first phase, the second phase and the rate:
41
+
42
+ ```bash
43
+ stimgrid quick --device mcs-stg4000 --amplitude-ua 7.3 --width-us 117 \
44
+ --second-amplitude-ua 1.9 --second-width-us 450 --frequency-hz 130
45
+ ```
46
+
47
+ 3. **Read the result** (see [Reading the output](#reading-the-output)). Use the `balanced` pulse, or one
48
+ of the options, in your stimulation software.
49
+ 4. **Save it for later or for a lab mate** as a [request file](#request-files), and rerun it with
50
+ `stimgrid fit my_pulse.json`.
51
+
52
+ Leave out `--second-amplitude-ua` and `--second-width-us` for a symmetric pulse; the second phase then
53
+ copies the first.
54
+
55
+ ## Reading the output
56
+
57
+ For the quick start command above:
58
+
59
+ ```
60
+ device: mcs-stg4000 (1.6 mA version): 20 µs time steps, 0.2 µA steps, max 1600 µA
61
+ plan 7.3 µA x 117 µs | gap 0 µs | 1.9 µA x 450 µs
62
+ rounded 7.4 µA x 120 µs | gap 0 µs | 2 µA x 460 µs
63
+ charge 0.888 nC (+3.97% vs plan), net -0.032 nC, imbalance 3.4783%
64
+ balanced 7.2 µA x 120 µs | gap 0 µs | 1.8 µA x 480 µs
65
+ charge 0.864 nC (+1.16% vs plan), net 0 nC, imbalance 0.0000%
66
+ option 1 7 µA x 120 µs | gap 0 µs | 2 µA x 420 µs
67
+ charge 0.84 nC (-1.65% vs plan), net 0 nC, imbalance 0.0000%
68
+ ...
69
+ rate frequency_hz 130 Hz -> 129.87 Hz (385 steps per period, -0.100%)
70
+
71
+ WARNING SG201 simple rounding leaves the pulse unbalanced (imbalance 3.4783%, net -0.032 nC per pulse)
72
+
73
+ best pulse: {"amplitude_ua": 7.2, "width_us": 120, "second_amplitude_ua": 1.8, "second_width_us": 480}
74
+ result: OK
75
+ ```
76
+
77
+ | Line | Meaning |
78
+ |---|---|
79
+ | `device` | The grid being used: time step, current step and maximum current |
80
+ | `plan` | The pulse you asked for: first phase, gap, second phase |
81
+ | `rounded` | What the device delivers if you type your plan in as it is |
82
+ | `balanced` | The closest pulse whose two phases carry exactly equal charge. **Use this one** |
83
+ | `option 1-3` | Other exactly balanced pulses, next closest first |
84
+ | `charge` | Charge in the first phase, and how far it moved from your plan |
85
+ | `net` | Charge left on the electrode by every pulse; 0 means balanced |
86
+ | `imbalance` | The difference between the two phases' charges, as a share of the larger one |
87
+ | `rate` | The rate you asked for, and the rate the device's clock really gives |
88
+ | `WARNING` / `ERROR` | Findings, explained in [Findings](#findings) |
89
+ | `best pulse` | The pulse to use, in a form you can paste into a file or program |
90
+ | `result` | `OK`, or `ERROR` if the pulse breaks a device limit or cannot be balanced |
91
+
92
+ Here the planned pulse would leave 0.032 nC on the electrode with every pulse; the balanced one leaves
93
+ none and keeps charge per phase within 1.2% of the plan.
94
+
95
+ ## Another example: Intan RHS2116
96
+
97
+ An asymmetric pulse on an Intan RHS2116 with 0.1 µA steps, driven at 30,000 samples/s:
98
+
99
+ ```bash
100
+ stimgrid quick --device intan-rhs2116 --step-ua 0.1 --sample-rate-hz 30000 \
101
+ --amplitude-ua 7.3 --width-us 117 --second-amplitude-ua 1.9 --second-width-us 450 --frequency-hz 40
102
+ ```
103
+
104
+ ```
105
+ device: intan-rhs2116 (step 0.1 µA, 30000 samples/s): 33.3333 µs time steps, 0.1 µA steps, max 25.5 µA
106
+ plan 7.3 µA x 117 µs | gap 0 µs | 1.9 µA x 450 µs
107
+ rounded 7.3 µA x 133.333 µs | gap 0 µs | 1.9 µA x 466.667 µs
108
+ charge 0.97333 nC (+13.96% vs plan), net 0.08667 nC, imbalance 8.9041%
109
+ balanced 7.2 µA x 100 µs | gap 0 µs | 1.8 µA x 400 µs
110
+ charge 0.72 nC (-15.70% vs plan), net 0 nC, imbalance 0.0000%
111
+ option 1 7.2 µA x 133.333 µs | gap 0 µs | 1.8 µA x 533.333 µs
112
+ charge 0.96 nC (+12.40% vs plan), net 0 nC, imbalance 0.0000%
113
+ ...
114
+ WARNING SG201 simple rounding leaves the pulse unbalanced (imbalance 8.9041%, net 0.08667 nC per pulse)
115
+ WARNING SG203 the balanced pulse changes charge per phase by -15.7%
116
+ ```
117
+
118
+ Simple rounding would leave 0.087 nC behind on every pulse. stimgrid lists exactly balanced pulses
119
+ instead, closest to your plan first, and warns when the grid forces charge per phase to move.
120
+
121
+ ## How it works
122
+
123
+ 1. **Rounded.** Each amplitude, phase width and gap is rounded (halves up) to the device's steps and
124
+ clamped to its ranges. This is what you get if you type the plan in and let the device round it.
125
+ 2. **Balanced.** stimgrid searches small changes to both phases, mostly the second phase's width and
126
+ amplitude, the usual balancing knobs. On a gridded device, balance is checked exactly, as whole
127
+ current steps times whole time steps, so "balanced" means equal to the last bit. On a device with
128
+ continuous amplitude it also tries the first-phase amplitude that keeps your planned charge exactly.
129
+ 3. **Ranking.** Exactly balanced candidates are ranked by closeness to your plan, with first-phase charge
130
+ counted double because it sets the effect. The best and three alternatives are shown.
131
+ 4. **Rates.** Each rate is rounded to a whole number of time steps per period, and the delivered rate is
132
+ reported.
133
+
134
+ ## Command options
135
+
136
+ `stimgrid quick` takes the pulse as flags. Amplitudes are in µA, times in µs, rates in Hz.
137
+
138
+ | Flag | Meaning |
139
+ |---|---|
140
+ | `--device NAME` | A preset from `stimgrid devices` |
141
+ | `--step-ua`, `--sample-rate-hz`, `--range-ua` | Preset settings (see [Devices](#devices)) |
142
+ | `--time-step-us`, `--amplitude-step-ua` | Your own device's steps, without a preset (or to override one) |
143
+ | `--max-amplitude-ua`, `--max-charge-per-phase-nc` | Your own device's limits |
144
+ | `--amplitude-ua`, `--width-us` | First phase (required) |
145
+ | `--second-amplitude-ua`, `--second-width-us` | Second phase (defaults to the same as the first) |
146
+ | `--gap-us` | Gap between the phases |
147
+ | `--monophasic` | One phase only |
148
+ | `--frequency-hz`, `--intra-burst-hz`, `--burst-rate-hz` | Rates to check against the time grid |
149
+ | `--keep-first` | Keep the rounded first phase as it is; balance with the second phase only |
150
+ | `--max-imbalance 0.001` | Accept up to 0.1% imbalance as balanced (the default is exact) |
151
+
152
+ Both `quick` and `fit` also take:
153
+
154
+ | Flag | Meaning |
155
+ |---|---|
156
+ | `--format text` | Readable report (the default) |
157
+ | `--format json` | The full report as JSON, for scripts |
158
+ | `--format pulse` | Only the best pulse as JSON |
159
+ | `--strict` | Treat warnings as failures (exit code 1), for automated checks |
160
+
161
+ `stimgrid --help` and `stimgrid quick --help` list them too.
162
+
163
+ ## Devices
164
+
165
+ | Preset | Time step | Amplitude | Source |
166
+ |---|---|---|---|
167
+ | `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 |
168
+ | `cortical-labs-cl` | 20 µs | continuous (no step published), up to 3 µA, 3 nC per phase | Cortical Labs CL API docs (StimDesign) |
169
+ | `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 |
170
+ | `mcs-stg5` | 5 µs, up to 100 kHz | `--range-ua 160`: 0.1 µA; `1600`: 1 µA; `16000`: 10 µA steps | MCS STG5 brochure |
171
+ | `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 |
172
+
173
+ All presets are current mode. The MCS range is the model version you bought (STG4000) or the range you
174
+ select (STG5). The STG4000 160 µA version is left out because its official datasheet could not be
175
+ confirmed.
176
+
177
+ **Not included yet**, because the vendor does not publish a current step or a timing step:
178
+
179
+ | Device | Published | Missing |
180
+ |---|---|---|
181
+ | Axion Maestro Pro / Edge | ±250 µA, ±1.2 V, phase at least 100 µs | current step, time step |
182
+ | MaxWell MaxOne / MaxTwo | 32 stimulation units, ±1.0 V | current range and step, time step |
183
+ | Blackrock CereStim | phase 44–65,535 µs, 4 Hz–5 kHz, 100 µA steps (macro model) | micro model step, time step |
184
+ | A-M Systems 4100 | 100 mA with 1 µA resolution, pulses from 1 µs | time step |
185
+
186
+ If you have one of these, describe it with your own numbers (below), or send the values from your
187
+ manual and they can become a preset.
188
+
189
+ `stimgrid devices` prints each preset's source. The RHS2116 has no pulse timers; the host sets every
190
+ edge, so its timing grid depends on your host software. Any other stimulator can be described by its
191
+ own numbers:
192
+
193
+ ```bash
194
+ stimgrid quick --time-step-us 10 --amplitude-step-ua 0.5 --max-amplitude-ua 100 \
195
+ --amplitude-ua 20 --width-us 105 --second-amplitude-ua 4 --second-width-us 520
196
+ ```
197
+
198
+ Fields: `time_step_us` (required), `amplitude_step_ua` (leave out for continuous), `max_amplitude_ua`,
199
+ `min_phase_us`, `max_phase_us`, `max_charge_per_phase_nc`, `max_frequency_hz`.
200
+
201
+ ## Request files
202
+
203
+ ```json
204
+ {
205
+ "stimgrid": 1,
206
+ "device": {"preset": "intan-rhs2116", "step_ua": 0.1, "sample_rate_hz": 30000},
207
+ "pulse": {"amplitude_ua": 7.3, "width_us": 117, "second_amplitude_ua": 1.9, "second_width_us": 450},
208
+ "rates": {"frequency_hz": 40},
209
+ "max_imbalance": 0,
210
+ "keep_first": false
211
+ }
212
+ ```
213
+
214
+ ```bash
215
+ stimgrid fit examples/intan_asymmetric.json
216
+ stimgrid fit examples/intan_asymmetric.json --format json
217
+ cat request.json | stimgrid fit - --format pulse
218
+ ```
219
+
220
+ A preset takes its options (`step_ua`, `sample_rate_hz` or `range_ua`, as in the table) and any device
221
+ field to override, for example `{"preset": "intan-rhs2116", "step_ua": 0.1, "max_amplitude_ua": 10}` or
222
+ `{"preset": "mcs-stg5", "range_ua": 160}`. Invalid input names the exact
223
+ place, for example `pulse: width_us must be a positive number`.
224
+
225
+ `--format pulse` prints only the best pulse, in the common shape (`amplitude_ua`, `width_us`,
226
+ `second_amplitude_ua`, `second_width_us`, `gap_us`), ready to paste into a protocol file or another tool.
227
+
228
+ ## Findings
229
+
230
+ | Code | Level | Meaning |
231
+ |---|---|---|
232
+ | SG101 | error | An amplitude is above the device's maximum |
233
+ | SG102 | error | A phase width is outside the device's range |
234
+ | SG103 | error | An amplitude is below one current step |
235
+ | SG104 | error | Charge per phase is above the device's maximum |
236
+ | SG105 | warning | The interphase gap rounds to zero |
237
+ | SG106 | warning | Rounding moves a value by more than 10% |
238
+ | SG201 | warning | Simple rounding leaves the pulse unbalanced |
239
+ | SG202 | error | No balanced pulse was found within the search and the device's limits |
240
+ | SG203 | warning | The balanced pulse changes charge per phase by more than 5% |
241
+ | SG301 | warning | A rate changes by more than 0.1% on whole periods |
242
+ | SG303 | error | The pulse is longer than one period at that rate |
243
+ | SG304 | error | A rate is above the device's maximum |
244
+
245
+ ## Exit codes
246
+
247
+ | Code | Meaning |
248
+ |---|---|
249
+ | 0 | Fitted with no errors (and no warnings, with `--strict`) |
250
+ | 1 | Errors (or warnings, with `--strict`) |
251
+ | 2 | Bad input or usage |
252
+ | 3 | Internal error |
253
+
254
+ ## From Python
255
+
256
+ ```python
257
+ from stimgrid import Pulse, Rates, fit, intan_rhs2116
258
+
259
+ report = fit(Pulse(7.3, 117, second_amplitude_ua=1.9, second_width_us=450),
260
+ intan_rhs2116(step_ua=0.1, sample_rate_hz=30000), Rates(frequency_hz=40))
261
+ print(report.balanced.as_pulse(), report.balanced.imbalance)
262
+ ```
263
+
264
+ ## Limits of this tool
265
+
266
+ - It models one pulse shape: rectangular phases with an optional gap. It does not model DAC settling,
267
+ electrode impedance, channel-to-channel mismatch or the RHS2116's trim settings.
268
+ - Rates are assumed to fall on the same time grid as phase widths.
269
+ - Continuous amplitude means the device's real amplitude resolution is unknown; the reported amplitude
270
+ may need rounding again on the hardware.
271
+
272
+ ## Citing
273
+
274
+ If you use stimgrid in published work, please cite it. GitHub's "Cite this repository" button gives the
275
+ reference from `CITATION.cff`:
276
+
277
+ > Muthusamy S. stimgrid (version 0.2.0) [Computer software]. https://github.com/sanjeevmuthusamy/stimgrid
278
+
279
+ ## Tests
280
+
281
+ ```bash
282
+ python -m unittest discover -s tests
283
+ ```
284
+
285
+ ## Licence
286
+
287
+ MIT. See `LICENSE`.
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "stimgrid"
7
+ version = "0.2.0"
8
+ description = "Fit stimulation pulses to what a stimulator can actually produce, and find the nearest exactly charge-balanced pulse."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "Sanjeev Muthusamy" }]
13
+ dependencies = []
14
+ keywords = [
15
+ "neuroscience", "electrophysiology", "neurostimulation", "electrical stimulation", "microstimulation",
16
+ "charge balance", "charge-balanced", "biphasic pulse", "asymmetric pulse", "stimulation safety",
17
+ "multi-electrode array", "MEA", "microelectrode", "neural interface", "brain-computer interface",
18
+ "DAC quantization", "Intan", "RHS2116", "Multi Channel Systems", "STG4000", "STG5",
19
+ "Tucker-Davis Technologies", "IZ2", "Cortical Labs",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3 :: Only",
26
+ "Programming Language :: Python :: 3.9",
27
+ "Programming Language :: Python :: 3.10",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Programming Language :: Python :: 3.13",
31
+ "Programming Language :: Python :: 3.14",
32
+ "Operating System :: OS Independent",
33
+ "Environment :: Console",
34
+ "Intended Audience :: Science/Research",
35
+ "Topic :: Scientific/Engineering",
36
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
37
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
38
+ ]
39
+
40
+ [project.urls]
41
+ Homepage = "https://github.com/sanjeevmuthusamy/stimgrid"
42
+ Issues = "https://github.com/sanjeevmuthusamy/stimgrid/issues"
43
+
44
+ [project.scripts]
45
+ stimgrid = "stimgrid.cli:main"
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+