MultimodalPhysioKit Python guide
MultimodalPhysioKit's Python processing layer is input-agnostic. Standardized
Signal objects flow into reusable modality processors and produce
ProcessingResult objects:
arrays / existing Signals / mapped HDF5 / supported acquisition export / window
↓
Signal
↓
modality processor
↓
ProcessingResult
Recording is an optional container and offline orchestrator around this core
flow.
Installation
MultimodalPhysioKit v0.1.0 is not yet published to PyPI. Once it is available, the planned public installation command is:
python -m pip install multimodalphysiokit
For development from a repository checkout, run from the repository root:
python -m pip install -e "python[test]"
Install the notebook dependencies as well with:
python -m pip install -e "python[test,examples]"
Python 3.10 or newer is required. See
Dependencies
for the separately obtained official cvxEDA v1.1.0 software and other
dependency details. EDA processing accepts a configurable
minimum_scr_amplitude in µS (default 0.01).
Core architecture
Signal
Signal standardizes:
samplestimesampling_frequencylabelunitsmetadata
It may represent a complete acquisition channel, an extracted experimental phase, an offline window, or one completed real-time buffer.
import numpy as np
from multimodalphysiokit import Signal
time = np.arange(ecg_samples.size, dtype=float) / 1000.0
ecg_signal = Signal(
samples=ecg_samples,
time=time,
sampling_frequency=1000.0,
label="ecg",
units="mV",
)
Processor
A processor is the central plug-and-play component. It accepts one or more
Signal objects, performs modality-specific preprocessing and feature
extraction, optionally exposes intermediate outputs, and returns a
ProcessingResult.
from multimodalphysiokit.processors import ECGProcessor
processor = ECGProcessor(
label_frequency_analysis=1,
return_intermediates=True,
)
result = processor.process(ecg_signal)
Processors analyze exactly the supplied signal or synchronized signal pair. They are independent of its storage system and do not receive phase definitions.
Recording
Recording is an optional offline container for synchronized named signals.
It preserves modality-specific sampling frequencies, stores shared temporal
phases, maps intervals independently through each signal time vector, and
orchestrates whole-signal or phase processing.
It does not automatically synchronize independent streams, resample signals, interpolate missing values, infer protocol phases, or apply physiological processing.
ProcessingResult
ProcessingResult contains:
modalityprocessor_namefeaturessignal_labelsoptionsmetadataintermediates
features contains final scalar outputs. metadata holds concise descriptive
and scalar processing information. intermediates may hold optional arrays or
structured diagnostics.
Supported input workflows
All input methods converge on Signal objects and the same processor API.
1. Existing arrays
Use recording_from_arrays when arrays share one sampling frequency or one
explicit time vector:
from multimodalphysiokit.io import recording_from_arrays
recording = recording_from_arrays(
{
"ecg": ecg_samples,
"eda": eda_samples,
},
sampling_frequency=1000.0,
units={
"ecg": "mV",
"eda": "uS",
},
)
Its signature is:
recording_from_arrays(
signals,
*,
sampling_frequency=None,
time=None,
units=None,
labels=None,
name="recording",
metadata=None,
)
Provide exactly one of sampling_frequency or time. The helper does not
assign a different sampling frequency to every array.
For synchronized modalities with different sampling frequencies, construct individual signals and add them:
from multimodalphysiokit import Recording, Signal
ecg_signal = Signal(
ecg_samples,
ecg_time,
sampling_frequency=1000.0,
label="ecg",
units="mV",
)
temperature_signal = Signal(
temperature_samples,
temperature_time,
sampling_frequency=10.0,
label="skin_temperature",
units="degC",
)
recording = Recording(name="experiment")
recording.add_signal("ecg", ecg_signal)
recording.add_signal("skin_temperature", temperature_signal)
The user is responsible for ensuring that these signals are already synchronized on a meaningful time axis.
2. Existing Signal objects
Direct processing is the most general interface:
result = ECGProcessor(label_frequency_analysis=1).process(ecg_signal)
The signal may come from an application, acquisition adapter, phase extractor, offline window generator, or completed external buffer.
3. Generic HDF5
read_generic_hdf5 reads explicitly selected one-dimensional numeric
datasets. It does not infer arbitrary schemas.
from multimodalphysiokit.io import read_generic_hdf5
recording = read_generic_hdf5(
"recording.h5",
signal_paths={
"ecg": "/signals/ecg",
"eda": "/signals/eda",
},
sampling_frequency_path="/metadata/sampling_frequency",
)
Its signature is:
read_generic_hdf5(
file_path,
signal_paths,
*,
sampling_frequency=None,
sampling_frequency_path=None,
time_path=None,
units=None,
labels=None,
name=None,
metadata=None,
)
Provide exactly one timing source: a scalar sampling_frequency, a path to a
scalar sampling-frequency dataset, or a path to a one-dimensional time
dataset. Signal names and HDF5 dataset paths are explicit mappings.
4. BioSignalsPlux/OpenSignals
read_biosignalsplux_hdf5 is a specialized convenience adapter for the
supported OpenSignals/BioSignalsPlux layout:
from multimodalphysiokit.io import read_biosignalsplux_hdf5
recording = read_biosignalsplux_hdf5("recording.h5")
It recognizes supported sensor channels, converts raw ADC values to package
units, creates time vectors and Signal objects, records channel metadata, and
returns a Recording.
Its signature is:
read_biosignalsplux_hdf5(
file_path,
*,
fnirs_column_order=(0, 1),
name="biosignalsplux",
)
The bundled notebooks use this adapter, but processors are not coupled to it.
Direct processor usage
Recording is not required:
from multimodalphysiokit.processors import TemperatureProcessor
processor = TemperatureProcessor()
result = processor.process(temperature_signal)
For paired inputs:
from multimodalphysiokit.processors import FNIRSProcessor
processor = FNIRSProcessor(age_years=35)
result = processor.process(red_signal, infrared_signal)
The same processor object can be reused with another compatible signal:
window_result = processor.process(red_window, infrared_window)
Recording orchestration
Inspect and manage signals
recording.signal_names()
recording.has_signal("ecg")
ecg_signal = recording.get_signal("ecg")
recording.add_signal("temperature", temperature_signal)
Names are user-defined unless an input adapter assigns canonical names.
Whole-signal processing
process_signal retrieves named signals and forwards them in order:
result = recording.process_signal(
ECGProcessor(label_frequency_analysis=1),
signal_names="ecg",
)
For a paired processor:
result = recording.process_signal(
FNIRSProcessor(age_years=35),
signal_names=("fnirs_red", "fnirs_infrared"),
process_kwargs={
"baseline_red_value": baseline_red_value,
"baseline_infrared_value": baseline_infrared_value,
},
)
process_kwargs is forwarded without modality-specific interpretation.
Temporal phases
Phases are stored as seconds in a float matrix of shape (2, n_phases).
Columns are phases, row 0 contains starts, and row 1 contains ends:
import numpy as np
phase_intervals_seconds = np.array(
[
[0.0, 60.0],
[60.0, 120.0],
],
dtype=float,
)
recording.set_phases(
phase_intervals_seconds,
labels=["segment_1", "segment_2"],
)
This defines segment_1 = [0, 60) and segment_2 = [60, 120). Labels are
unique, supplied order is preserved, and overlaps are allowed. Users provide
temporal intervals rather than sample-index matrices.
phase_indices(signal_or_name, phases=None) maps intervals through a chosen
signal's time vector. extract_phase(signal_name, phase, reset_time=True)
returns a new Signal. clear_phases() removes the phase definition.
Plotting
from multimodalphysiokit.utils import plot_signal_with_phases
plot_signal_with_phases(
recording,
signal_name="ecg",
title="Raw ECG signal",
show=True,
)
Phases must already be defined. The utility retrieves the complete stored
signal and obtains overlays through Recording.phase_indices(). It supports
optional saving without duplicating temporal mapping logic.
Phase processing
phase_results = recording.process_phases(
ECGProcessor(label_frequency_analysis=1),
signal_names="ecg",
)
The result is:
{
"segment_1": ProcessingResult(...),
"segment_2": ProcessingResult(...),
}
Select and order phases with phases=["segment_2", "segment_1"]. Each phase is
mapped, extracted, and processed independently. Filtering, decomposition,
event detection, STFT, and feature extraction restart for each extracted
signal. This can produce different edge behavior from processing a complete
parent signal and slicing the processed output.
Multimodal postprocessing
Separate plug-and-play processors can reuse one shared temporal definition:
from multimodalphysiokit.processors import (
ECGProcessor,
RespirationProcessor,
TemperatureProcessor,
)
results = {
"ecg": recording.process_phases(
ECGProcessor(label_frequency_analysis=1),
signal_names="ecg",
),
"respiration": recording.process_phases(
RespirationProcessor(),
signal_names="rip",
),
"temperature": recording.process_phases(
TemperatureProcessor(),
signal_names="temp",
),
}
The rip and temp names above are assigned by the bundled input adapter;
other recordings may use different names. Every signal is mapped through its
own time vector. Processing is sequential and does not imply threading or
multiprocessing.
Long-format phase results
import pandas as pd
rows = [
{
"modality": modality,
"phase": phase_label,
"feature_name": feature_name,
"value": feature_value,
}
for modality, phase_results in results.items()
for phase_label, processing_result in phase_results.items()
for feature_name, feature_value in processing_result.features.items()
]
results_table = pd.DataFrame(rows)
Phase association belongs to the outer result dictionaries. Feature names are
phase-independent. Do not flatten intermediates or metadata into the
scalar feature table. Pandas is an example dependency, not a core runtime
dependency.
Externally managed sliding windows
Generate temporal windows outside the processing layer:
from multimodalphysiokit.utils import create_sliding_phase_intervals
intervals, labels = create_sliding_phase_intervals(
start_seconds=0.0,
end_seconds=300.0,
window_seconds=60.0,
overlap_seconds=30.0,
)
recording.set_phases(intervals, labels)
The utility operates only in seconds, returns complete-window intervals and labels, and does not process signals. Recording maps the intervals through each selected signal.
Window-based real-time integration
Acquisition and buffer management can produce a completed window:
window_signal = Signal(
samples=window_samples,
time=window_time,
sampling_frequency=1000.0,
label="ecg",
units="mV",
)
processor = ECGProcessor(label_frequency_analysis=2)
window_result = processor.process(window_signal)
The processor object can be reused for successive completed windows. Acquisition, buffering, scheduling, and hard real-time guarantees remain outside the core library. Current algorithms may use noncausal filtering or complete-window operations; this is not sample-by-sample stateful streaming.
Optional intermediate outputs
ECG exposes optional arrays when configured:
result = ECGProcessor(
label_frequency_analysis=1,
return_intermediates=True,
).process(ecg_signal)
Keys are filtered_signal, r_peak_indices, r_peak_times,
r_peak_amplitudes, accepted_r_peak_indices,
accepted_r_peak_times, ibi, ibi_times, bpm, and bpm_times.
STFT mode also returns stft_matrix, stft_power, stft_frequencies, and
stft_times.
fNIRS scalar baseline workflow
Compute filtered-current reference scalars once:
processor = FNIRSProcessor(age_years=35)
baseline_red_value, baseline_infrared_value = (
processor.compute_baseline_values(
baseline_red_signal,
baseline_infrared_signal,
)
)
Then reuse them:
results = recording.process_phases(
processor,
signal_names=("fnirs_red", "fnirs_infrared"),
phases=["segment_2", "segment_3"],
process_kwargs={
"baseline_red_value": baseline_red_value,
"baseline_infrared_value": baseline_infrared_value,
},
)
Both values must be supplied together, finite, and strictly positive. Baseline
signals are not arguments to process(). Without supplied values, the
processor derives both scalars from the target pair after excluding ten
seconds from each temporal end. Result metadata includes baseline_mode,
baseline_red_value, and baseline_infrared_value.
Modality guides
Example notebooks
The notebooks use one bundled synthetic BioSignalsPlux/OpenSignals fixture as a practical example source, not as an architectural requirement:
They demonstrate the specialized reader, signal retrieval, raw plotting, direct and phase processing, intermediate outputs, and multimodal result tables.
Testing and validation
Run:
cd python
python -m pytest -v
Unit tests, bundled demonstrations, private/local validation, and MATLAB/Python scientific comparisons are distinct activities. The bundled fixture contains mathematically generated demonstration data, not scientifically validated physiology. See Validation and Local validation.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file multimodalphysiokit-0.1.0.tar.gz.
File metadata
- Download URL: multimodalphysiokit-0.1.0.tar.gz
- Upload date:
- Size: 88.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
690b451356e41b112bc3f472a294046e8f3aae795ddd795b29f695a411b9e8e9
|
|
| MD5 |
d1935f8f9faf3e89d236105fb62f0067
|
|
| BLAKE2b-256 |
df5aece6c554758ac2476bafbdcbfae613bbbe56831cf3040580cecf94a80fc7
|
Provenance
The following attestation bundles were made for multimodalphysiokit-0.1.0.tar.gz:
Publisher:
release.yml on Gabbert97/MultimodalPhysioKit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
multimodalphysiokit-0.1.0.tar.gz -
Subject digest:
690b451356e41b112bc3f472a294046e8f3aae795ddd795b29f695a411b9e8e9 - Sigstore transparency entry: 2411437054
- Sigstore integration time:
-
Permalink:
Gabbert97/MultimodalPhysioKit@540b12475c0d5035911175815aa6da3b3451e98a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Gabbert97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@540b12475c0d5035911175815aa6da3b3451e98a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file multimodalphysiokit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: multimodalphysiokit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 68.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
814a523fe37a25bf6155f21bf6fa7797e50f2d107fc5b52cda3c16867079483b
|
|
| MD5 |
ae315bca1b1ad3b85b6acf8519dd5b39
|
|
| BLAKE2b-256 |
b4aad216522c97e602e9d7d4b6ba3dc7fb809da78d29d08b2287f50f09abe10e
|
Provenance
The following attestation bundles were made for multimodalphysiokit-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Gabbert97/MultimodalPhysioKit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
multimodalphysiokit-0.1.0-py3-none-any.whl -
Subject digest:
814a523fe37a25bf6155f21bf6fa7797e50f2d107fc5b52cda3c16867079483b - Sigstore transparency entry: 2411437316
- Sigstore integration time:
-
Permalink:
Gabbert97/MultimodalPhysioKit@540b12475c0d5035911175815aa6da3b3451e98a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Gabbert97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@540b12475c0d5035911175815aa6da3b3451e98a -
Trigger Event:
workflow_dispatch
-
Statement type: