Skill Tracer
Skills › Scientific Agent Skills › neurokit2 › bio_module.md

bio_module.md

250 lines · skills/neurokit2/references/bio_module.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Multimodal processing with `bio_process`
2
3Checked **2026-10-01** against NeuroKit2 0.2.13 stable source/runtime
4and the official Bio API/examples.
5

Multimodal processing with bio_process

Checked 2026-10-01 against NeuroKit2 0.2.13 stable source/runtime and the official Bio API/examples.

Raw lines 1 to 5
6## What `bio_process()` does—and does not do
7
8Stable signature:
9
10```text
11bio_process(
12 ecg=None, rsp=None, eda=None, emg=None,
13 ppg=None, eog=None, keep=None, sampling_rate=1000
15```
17It dispatches each non-`None` vector to the modality's `*_process()` function,
18concatenates outputs by pandas index, adds `keep`, and computes continuous RSA when ECG
19and RSP are both present.
21It does **not**:
23- infer or accept one native sampling rate per modality;
24- synchronize clocks, estimate lag/drift, or align timestamps;
25- automatically resample streams to `sampling_rate`;
26- reject unequal lengths before concatenation;
27- standardize units; or
28- return a nested modality-info structure.
30Passing ECG at 1000 Hz and EDA at 100 Hz with `sampling_rate=1000` falsely tells the
31EDA processor that its samples are 1000 Hz. Unequal lengths are outer-concatenated by
32index and can introduce NaN. Align first.

What bio_process() does—and does not do

Stable signature:

bio_process(
  ecg=None, rsp=None, eda=None, emg=None,
  ppg=None, eog=None, keep=None, sampling_rate=1000
)

It dispatches each non-None vector to the modality's *_process() function, concatenates outputs by pandas index, adds keep, and computes continuous RSA when ECG and RSP are both present.

It does not:

  • infer or accept one native sampling rate per modality;
  • synchronize clocks, estimate lag/drift, or align timestamps;
  • automatically resample streams to sampling_rate;
  • reject unequal lengths before concatenation;
  • standardize units; or
  • return a nested modality-info structure.

Passing ECG at 1000 Hz and EDA at 100 Hz with sampling_rate=1000 falsely tells the EDA processor that its samples are 1000 Hz. Unequal lengths are outer-concatenated by index and can introduce NaN. Align first.

Raw lines 6 to 33
Starts an agent dispatches :17
34## Flat output schema
36```python
37bio_signals, bio_info = nk.bio_process(
38 ecg=ecg_aligned,
39 rsp=rsp_aligned,
40 eda=eda_aligned,
41 sampling_rate=100,
43```
45`bio_signals` is one wide DataFrame. With pinned synthetic ECG+RSP+EDA, it contained
4643 columns:
48- 19 ECG raw/clean/rate/quality/peaks/delineation/phase columns;
49- 11 RSP raw/clean/amplitude/rate/RVT/phase/symmetry/extrema columns;
50- 11 EDA raw/clean/tonic/phasic/SCR columns; and
51- `RSA_P2T`, `RSA_Gates`.
53`bio_info` is one flat dict built with repeated `dict.update()`. The pinned run
54contained prefixed ECG/RSP/SCR keys, method metadata, and one `sampling_rate`; it did
55not support:
57```python
58bio_info["ECG"]["ECG_R_Peaks"] # wrong for stable 0.2.13
59```
61Use:
63```python
64rpeaks = bio_info["ECG_R_Peaks"]
65rsp_troughs = bio_info["RSP_Troughs"]
66```
68Output columns depend on provided modalities, methods, optional dependencies, and
69release. Save `list(bio_signals.columns)` and `sorted(bio_info)`.

Flat output schema

bio_signals, bio_info = nk.bio_process(
    ecg=ecg_aligned,
    rsp=rsp_aligned,
    eda=eda_aligned,
    sampling_rate=100,
)

bio_signals is one wide DataFrame. With pinned synthetic ECG+RSP+EDA, it contained 43 columns:

  • 19 ECG raw/clean/rate/quality/peaks/delineation/phase columns;
  • 11 RSP raw/clean/amplitude/rate/RVT/phase/symmetry/extrema columns;
  • 11 EDA raw/clean/tonic/phasic/SCR columns; and
  • RSA_P2T, RSA_Gates.

bio_info is one flat dict built with repeated dict.update(). The pinned run contained prefixed ECG/RSP/SCR keys, method metadata, and one sampling_rate; it did not support:

bio_info["ECG"]["ECG_R_Peaks"]  # wrong for stable 0.2.13

Use:

rpeaks = bio_info["ECG_R_Peaks"]
rsp_troughs = bio_info["RSP_Troughs"]

Output columns depend on provided modalities, methods, optional dependencies, and release. Save list(bio_signals.columns) and sorted(bio_info).

Raw lines 34 to 70
71## Alignment workflow

Alignment workflow

Raw lines 71 to 72
73### 1. Preserve native clocks
75For each stream record:
77- timestamp origin/time zone or monotonic device time;
78- native rate and observed timestamp intervals;
79- dropped/duplicate/backward samples;
80- clock reset, drift, and synchronization events;
81- sensor latency and acquisition filters; and
82- unit, polarity, and artifact mask.
84Do not align only by truncating arrays to equal length.

1. Preserve native clocks

For each stream record:

  • timestamp origin/time zone or monotonic device time;
  • native rate and observed timestamp intervals;
  • dropped/duplicate/backward samples;
  • clock reset, drift, and synchronization events;
  • sensor latency and acquisition filters; and
  • unit, polarity, and artifact mask.

Do not align only by truncating arrays to equal length.

Raw lines 73 to 85
86### 2. Establish synchronization evidence
88Prefer:
901. one acquisition system/shared clock;
912. common hardware trigger captured on each device;
923. validated timestamps with drift correction; or
934. a documented manual alignment with uncertainty.
95Cross-correlation can support QC when signals share physiology, but a correlation peak
96can be ambiguous and physiologically lagged. It is not a replacement for a clock.

2. Establish synchronization evidence

Prefer:

  1. one acquisition system/shared clock;
  2. common hardware trigger captured on each device;
  3. validated timestamps with drift correction; or
  4. a documented manual alignment with uncertainty.

Cross-correlation can support QC when signals share physiology, but a correlation peak can be ambiguous and physiologically lagged. It is not a replacement for a clock.

Raw lines 86 to 97
98### 3. Process at native rates
100Apply modality-specific cleaning, peak detection, decomposition, and quality at the
101correct native rate. Preserve native event/peak timestamps.

3. Process at native rates

Apply modality-specific cleaning, peak detection, decomposition, and quality at the correct native rate. Preserve native event/peak timestamps.

Raw lines 98 to 102
103### 4. Build a common grid
105Choose the target rate from the fastest retained continuous feature and analysis—not
106convenience. Anti-alias downsampling; report interpolation/filter method and edge
107validity. Map discrete peaks/triggers by time, using an explicit rounding/tolerance
108policy; never spline binary markers.

4. Build a common grid

Choose the target rate from the fastest retained continuous feature and analysis—not convenience. Anti-alias downsampling; report interpolation/filter method and edge validity. Map discrete peaks/triggers by time, using an explicit rounding/tolerance policy; never spline binary markers.

Raw lines 103 to 109
110### 5. Validate before `bio_process()`
112The bundled validator accepts a strict JSON manifest:
114```json
116 "schema_version": "1.0",
117 "streams": [
118 {
119 "name": "ECG",
120 "path": "ecg.csv",
121 "value_column": "ECG",
122 "time_column": "time_s",
123 "sampling_rate_hz": 250,
124 "unit": "mV"
125 },
126 {
127 "name": "RSP",
128 "path": "rsp.csv",
129 "value_column": "RSP",
130 "time_column": "time_s",
131 "sampling_rate_hz": 50,
132 "unit": "a.u."
133 }
134 ],
135 "alignment": {
136 "reference_stream": "ECG",
137 "synchronization": "shared_clock",
138 "max_start_offset_ms": 2,
139 "minimum_overlap_s": 60
140 }
142```
144```bash
145python skills/neurokit2/scripts/validate_multimodal.py \
146 --manifest streams.json --root . --deidentified
147```
149The validator reports units, rates, timestamp order/jitter, missingness, starts, common
150overlap, and whether streams meet its numerical requirements for `bio_process()`.
151It checks all observed timestamps against each stream's declared regular grid and
152against corresponding reference-stream timestamps, allowing at most half a sample.
153Equal starts/rates/counts can conceal accumulating drift or interior timing jumps.
154Without a time column, the assumed uniform grid is marked as declared, not observed.
155`valid` describes the manifest checks; direct compatibility is a separate field.
156Neither field validates synchronization hardware, sensor latency, or physiology. It
157does not resample or modify data.

5. Validate before bio_process()

The bundled validator accepts a strict JSON manifest:

{
  "schema_version": "1.0",
  "streams": [
    {
      "name": "ECG",
      "path": "ecg.csv",
      "value_column": "ECG",
      "time_column": "time_s",
      "sampling_rate_hz": 250,
      "unit": "mV"
    },
    {
      "name": "RSP",
      "path": "rsp.csv",
      "value_column": "RSP",
      "time_column": "time_s",
      "sampling_rate_hz": 50,
      "unit": "a.u."
    }
  ],
  "alignment": {
    "reference_stream": "ECG",
    "synchronization": "shared_clock",
    "max_start_offset_ms": 2,
    "minimum_overlap_s": 60
  }
}
python skills/neurokit2/scripts/validate_multimodal.py \
  --manifest streams.json --root . --deidentified

The validator reports units, rates, timestamp order/jitter, missingness, starts, common overlap, and whether streams meet its numerical requirements for bio_process(). It checks all observed timestamps against each stream's declared regular grid and against corresponding reference-stream timestamps, allowing at most half a sample. Equal starts/rates/counts can conceal accumulating drift or interior timing jumps. Without a time column, the assumed uniform grid is marked as declared, not observed. valid describes the manifest checks; direct compatibility is a separate field. Neither field validates synchronization hardware, sensor latency, or physiology. It does not resample or modify data.

Raw lines 110 to 158
Runs code python skills/neurokit2/scripts/validate_multimodal.py :145
159## `keep`
161`keep` must be a pandas Series or DataFrame and is concatenated after processed
162modalities. It is useful for a pre-aligned trigger or covariate:
164```python
165bio_signals, bio_info = nk.bio_process(
166 ecg=ecg,
167 rsp=rsp,
168 keep=aligned[["Trigger"]],
169 sampling_rate=100,
171```
173Verify equal index/length first. Do not use `keep` for participant identifiers or PHI.

keep

keep must be a pandas Series or DataFrame and is concatenated after processed modalities. It is useful for a pre-aligned trigger or covariate:

bio_signals, bio_info = nk.bio_process(
    ecg=ecg,
    rsp=rsp,
    keep=aligned[["Trigger"]],
    sampling_rate=100,
)

Verify equal index/length first. Do not use keep for participant identifiers or PHI.

Raw lines 159 to 174
175## EOG and optional dependencies
177The high-level Bio wrapper calls `eog_process()` without exposing an EOG method.
178Stable EOG peak detection defaults to MNE, which is optional. A core-only environment
179can therefore fail when `eog` is supplied. Process EOG explicitly with a chosen method
180and merge after alignment, or add MNE at a reviewed exact version to the project lock.

EOG and optional dependencies

The high-level Bio wrapper calls eog_process() without exposing an EOG method. Stable EOG peak detection defaults to MNE, which is optional. A core-only environment can therefore fail when eog is supplied. Process EOG explicitly with a chosen method and merge after alignment, or add MNE at a reviewed exact version to the project lock.

Raw lines 175 to 181
182## RSA
184When both ECG and RSP are present, stable `bio_process()` adds continuous:
186```text
187RSA_P2T, RSA_Gates
188```
190This assumes the arrays already represent synchronized samples at the supplied rate.
191It does not check respiration polarity, sensor lag, clock drift, or R-peak validity.
192For summary RSA:
194```python
195rsa = nk.hrv_rsa(
196 bio_signals,
197 bio_signals,
198 rpeaks=bio_info,
199 sampling_rate=100,
200 continuous=False,
202```
204Report the RSA method/output family, respiration behavior, usable cycles/windows, and
205alignment uncertainty. See `hrv.md`.

RSA

When both ECG and RSP are present, stable bio_process() adds continuous:

RSA_P2T, RSA_Gates

This assumes the arrays already represent synchronized samples at the supplied rate. It does not check respiration polarity, sensor lag, clock drift, or R-peak validity. For summary RSA:

rsa = nk.hrv_rsa(
    bio_signals,
    bio_signals,
    rpeaks=bio_info,
    sampling_rate=100,
    continuous=False,
)

Report the RSA method/output family, respiration behavior, usable cycles/windows, and alignment uncertainty. See hrv.md.

Raw lines 182 to 206
207## `bio_analyze()`
209```text
210bio_analyze(
211 data, sampling_rate=1000, method="auto",
212 window_lengths="constant"
214```
216It detects available column prefixes and joins modality-specific analysis. With
217interval-related data it can add summary RSA. `method="auto"` uses event-related mode
218when mean duration is under 10 seconds; use explicit `event-related` or
219`interval-related` for a prespecified design.
221`window_lengths` can assign different epoch subwindows by modality. Prespecify them;
222choosing each window after seeing effects multiplies researcher degrees of freedom.
224There is no generic “multimodal arousal,” coherence, or cardiorespiratory-coupling
225score automatically produced by this wrapper. Any custom cross-modal statistic needs
226its own synchronization, lag, stationarity, null model, and multiplicity analysis.

bio_analyze()

bio_analyze(
  data, sampling_rate=1000, method="auto",
  window_lengths="constant"
)

It detects available column prefixes and joins modality-specific analysis. With interval-related data it can add summary RSA. method="auto" uses event-related mode when mean duration is under 10 seconds; use explicit event-related or interval-related for a prespecified design.

window_lengths can assign different epoch subwindows by modality. Prespecify them; choosing each window after seeing effects multiplies researcher degrees of freedom.

There is no generic “multimodal arousal,” coherence, or cardiorespiratory-coupling score automatically produced by this wrapper. Any custom cross-modal statistic needs its own synchronization, lag, stationarity, null model, and multiplicity analysis.

Raw lines 207 to 227
228## Missingness and statistics
230- Keep one validity/artifact mask per modality; complete-case intersection can remove
231 large or condition-dependent periods.
232- Do not replace a poor modality with another and claim the same construct.
233- Summarize quality/exclusions by participant and condition.
234- Split train/test/validation by participant, not rows or epochs.
235- Avoid pseudo-replication from dense samples.
236- Predefine cross-modal features and correct multiplicity.

Missingness and statistics

  • Keep one validity/artifact mask per modality; complete-case intersection can remove large or condition-dependent periods.
  • Do not replace a poor modality with another and claim the same construct.
  • Summarize quality/exclusions by participant and condition.
  • Split train/test/validation by participant, not rows or epochs.
  • Avoid pseudo-replication from dense samples.
  • Predefine cross-modal features and correct multiplicity.
Raw lines 228 to 237
238## Interpretation boundary
240Multimodal convergence does not prove a latent state, diagnosis, or causal mechanism.
241Use `bio_*` for research/education only—not patient, worker, driver, athlete, or device
242monitoring and not medical-device validation.

Interpretation boundary

Multimodal convergence does not prove a latent state, diagnosis, or causal mechanism. Use bio_* for research/education only—not patient, worker, driver, athlete, or device monitoring and not medical-device validation.

Raw lines 238 to 243
244## Sources checked 2026-10-01
246- [Official Bio API](https://neuropsychology.github.io/NeuroKit/functions/bio.html)
247- [Official custom Bio example](https://neuropsychology.github.io/NeuroKit/examples/bio_custom/bio_custom.html)
248- [Stable v0.2.13 `bio_process` source](https://github.com/neuropsychology/NeuroKit/blob/v0.2.13/neurokit2/bio/bio_process.py)
249- [NeuroKit2 main paper](https://doi.org/10.3758/s13428-020-01516-y)
250- [Grossman & Taylor (2007), RSA interpretation](https://doi.org/10.1016/j.biopsycho.2005.11.014)

Sources checked 2026-10-01

Raw lines 244 to 250