Getting Started#
GastroPy provides a flat, NeuroKit2-style API: import the package and call functions directly.
import gastropy as gp
Sample Datasets#
GastroPy ships with bundled sample recordings so you can start experimenting immediately — no external downloads needed.
import gastropy as gp
# List available datasets
gp.list_datasets()
# Standalone 7-channel EGG (Wolpert et al., 2020)
egg = gp.load_egg()
print(egg["signal"].shape, egg["sfreq"])
# fMRI-concurrent 8-channel EGG (3 sessions available)
fmri = gp.load_fmri_egg(session="0001")
print(fmri["signal"].shape, fmri["trigger_times"].shape)
Importing Your Own Data#
GastroPy uses the BIDS peripheral physiology format
as its canonical data format: a gzip-compressed TSV (_physio.tsv.gz)
paired with a JSON sidecar (_physio.json).
From Python – use gastropy.io to read and write BIDS physio files:
from gastropy.io import read_bids_physio, write_bids_physio
# Write your own data
write_bids_physio(
"sub-01_task-rest_physio.tsv.gz",
signal=my_signal, # (n_channels, n_samples) array
sfreq=10.0,
columns=["EGG1", "EGG2", "EGG3"],
)
# Read it back
data = read_bids_physio("sub-01_task-rest_physio.tsv.gz")
print(data["signal"].shape, data["sfreq"])
From BrainVision – convert .vhdr files directly (requires MNE):
from gastropy.io import brainvision_to_bids
brainvision_to_bids("recording.vhdr", output_dir="bids/", subject="01")
From MATLAB – see examples/matlab/egg_to_bids.m for a
self-contained script that writes BIDS-compliant files from any MATLAB
array. No toolbox dependencies beyond base MATLAB (R2016b+).
Processing an EGG Signal#
The fastest way to go from a raw EGG signal to a full set of metrics is
egg_process. It filters the signal, extracts instantaneous phase and
amplitude, detects gastric cycles, and computes standard metrics.
import numpy as np
import gastropy as gp
# --- Simulate a 5-minute EGG recording at 10 Hz ---
sfreq = 10.0
t = np.arange(0, 300, 1 / sfreq)
freq_hz = 0.05 # 3 cycles per minute
signal = np.sin(2 * np.pi * freq_hz * t) + 0.1 * np.random.randn(len(t))
# --- Run the full pipeline ---
signals, info = gp.egg_process(signal, sfreq)
# signals is a DataFrame with columns: raw, filtered, phase, amplitude
print(signals.head())
# info is a dict of metrics
print(f"Peak frequency : {info['peak_freq_hz']:.3f} Hz")
print(f"Cycles detected: {info['cycle_stats']['n_cycles']}")
print(f"Instability IC : {info['instability_coefficient']:.4f}")
print(f"% Normogastric : {info['proportion_normogastric']:.0%}")
Tip
egg_clean supports multiple named cleaning pipelines via its
method parameter. method="fir" (default) uses a zero-phase
FIR bandpass. method="dalmaijer2025" applies Hampel spike removal
→ LMMSE movement filtering → IIR Butterworth bandpass, suitable for
ambulatory recordings with motion artifacts.
Step-by-Step Processing#
You can also call each processing step individually for more control.
1. Spectral analysis
# overlap controls PSD smoothing (default 0.25; use 0.75 for Wolpert-style)
freqs, psd = gp.psd_welch(signal, sfreq, fmin=0.01, fmax=0.1, overlap=0.75)
2. Bandpass filtering
# normogastric band: 2–4 cpm = NORMOGASTRIA (0.0333–0.0667 Hz)
filtered, filt_info = gp.apply_bandpass(signal, sfreq, low_hz=0.0333, high_hz=0.0667)
3. Phase and amplitude extraction
phase, analytic = gp.instantaneous_phase(filtered)
amplitude = np.abs(analytic)
4. Cycle detection
durations = gp.cycle_durations(phase, t)
5. Metric computation
from gastropy.metrics import band_power, NORMOGASTRIA
bp = band_power(freqs, psd, NORMOGASTRIA)
ic = gp.instability_coefficient(durations)
Artifact Removal & Detection#
GastroPy provides both time-domain preprocessing (before bandpass filtering) and phase-based artifact detection (after filtering).
Time-domain preprocessing
Three composable functions remove transient artifacts and movement noise
from the raw signal before filtering. They accept 1D single-channel or
2D (n_channels, n_samples) arrays.
# Spike removal: Hampel sliding-window median identifier (Davies & Gather 1993)
signal = gp.hampel_filter(signal)
# Faster global alternative: median absolute deviation filter
signal = gp.mad_filter(signal)
# Movement artifact attenuation: LMMSE Wiener filter (Gharibans et al. 2018)
signal = gp.remove_movement_artifacts(signal, sfreq)
# Bundled pipeline (Hampel → LMMSE → IIR Butterworth):
cleaned, info = gp.egg_clean(signal, sfreq, method="dalmaijer2025")
Phase-based detection
After filtering, detect_phase_artifacts flags cycles with
non-monotonic phase or outlier durations, following Wolpert et al. (2020).
artifacts = gp.detect_phase_artifacts(signals["phase"].values, t)
print(f"Artifacts found: {artifacts['n_artifacts']}")
print(f"Artifact mask : {artifacts['artifact_mask'].sum()} samples flagged")
Channel Selection & Multi-Channel Processing#
select_best_channel ranks channels by peak power in the normogastric
band and returns the best one. egg_process_multichannel provides a
higher-level interface with three named strategies.
# data shape: (n_channels, n_samples)
# Low-level: select best channel, then process
best_idx, peak_freq, freqs, psd = gp.select_best_channel(data, sfreq)
print(f"Best channel: {best_idx} (peak at {peak_freq:.3f} Hz)")
# High-level: process all channels independently
result = gp.egg_process_multichannel(data, sfreq, method="per_channel")
print(result["summary"]) # DataFrame: peak_freq, IC, prop_normo per channel
print(f"Best channel: {result['best_idx']}")
# Use ICA spatial denoising first (requires scikit-learn)
result = gp.egg_process_multichannel(data, sfreq, method="ica",
ica_snr_threshold=2.0)
print(result["ica_info"]["n_kept"], "ICA components retained")
Visualization#
GastroPy provides publication-ready plots that accept raw arrays and
return (fig, ax) tuples for easy customization.
# PSD with normogastric band shading
fig, ax = gp.plot_psd(freqs, psd)
# 4-panel EGG overview (raw, filtered, phase, amplitude)
fig, axes = gp.plot_egg_overview(signals, sfreq)
# Cycle duration histogram with normogastric boundaries
fig, ax = gp.plot_cycle_histogram(info["cycle_durations_s"])
# Phase time series with artifact regions highlighted
fig, ax = gp.plot_artifacts(signals["phase"].values, t, artifacts)
# Comprehensive multi-panel figure (overview + optional fMRI phase)
fig, axes = gp.plot_egg_comprehensive(signals, sfreq, artifact_info=artifacts)
fMRI-Concurrent EGG#
For EGG recorded inside an MRI scanner, gastropy.neuro.fmri provides
utilities for parsing scanner triggers, windowing data by volume, and
removing transient volumes.
from gastropy.neuro.fmri import (
find_scanner_triggers,
create_volume_windows,
phase_per_volume,
apply_volume_cuts,
)
# Parse R128 triggers from MNE annotations
onsets = find_scanner_triggers(raw.annotations, label="R128")
# Create per-volume windows and extract phase
windows = create_volume_windows(onsets, tr=1.856, n_volumes=420)
phases = phase_per_volume(analytic, windows)
# Remove transient volumes
phases_trimmed = apply_volume_cuts(phases, begin_cut=21, end_cut=21)
# Visualize per-volume phase with cut regions
fig, ax = gp.plot_volume_phase(phases, tr=1.856, cut_start=21, cut_end=21)
Time-Frequency Analysis#
Decompose the EGG signal across gastric frequency bands (bradygastria, normogastria, tachygastria) for per-band metrics.
# Single-band decomposition
result = gp.band_decompose(signal, sfreq)
# Multi-band analysis across all gastric bands
results = gp.multiband_analysis(signal, sfreq)
for band_name, res in results.items():
print(f"{band_name}: peak={res['peak_freq_hz']:.3f} Hz, "
f"IC={res['instability_coefficient']:.3f}")
Gastric-Brain Coupling#
GastroPy provides a complete pipeline for phase-locking analysis between
EGG and BOLD signals. The core coupling metrics work on plain phase arrays,
while convenience functions in gastropy.neuro.fmri handle the full
fMRIPrep-to-PLV-map workflow.
Phase-Locking Value (PLV)
import numpy as np
import gastropy as gp
# PLV between two phase time series
plv = gp.phase_locking_value(bold_phases, egg_phase)
# Complex PLV (magnitude + preferred phase lag)
cplv = gp.phase_locking_value_complex(bold_phases, egg_phase)
lag_deg = np.rad2deg(np.angle(cplv))
Surrogate Testing
# Null distribution via circular time-shifting
surr = gp.surrogate_plv(bold_phases, egg_phase, n_surrogates=200, seed=42)
# Z-score: empirical vs. surrogate
z = gp.coupling_zscore(plv, surr)
Full fMRI Pipeline
from gastropy.neuro.fmri import (
regress_confounds,
bold_voxelwise_phases,
compute_plv_map,
compute_surrogate_plv_map,
)
# Confound regression (motion + aCompCor)
residuals = regress_confounds(bold_2d, confounds_df)
# Extract BOLD phase at individual gastric frequency
bold_phases = bold_voxelwise_phases(residuals, peak_freq_hz=0.05, sfreq=1/1.856)
# Compute PLV and surrogate maps
plv_map = compute_plv_map(egg_phase, bold_phases)
surr_map = compute_surrogate_plv_map(egg_phase, bold_phases, n_surrogates=200)
Loading and Processing Real fMRI Data
from gastropy.neuro.fmri import load_bold, align_bold_to_egg
# Download preprocessed BOLD from GitHub Release (requires pooch)
data = gp.fetch_fmri_bold(session="0001")
# Load NIfTI and brain mask
bold_data = load_bold(data["bold"], data["mask"])
print(bold_data["bold_2d"].shape) # (n_voxels, n_volumes)
# Align BOLD to EGG trigger count
egg = gp.load_fmri_egg(session="0001")
bold_2d, confounds = align_bold_to_egg(
bold_data["bold_2d"], len(egg["trigger_times"]), confounds_df
)
Visualizing Coupling Maps
from gastropy.neuro.fmri import to_nifti
# Reconstruct 3D PLV volume
plv_3d = compute_plv_map(egg_phase, bold_phases,
vol_shape=bold_data["vol_shape"],
mask_indices=bold_data["mask"])
# Visualize with nilearn
plv_img = to_nifti(plv_3d, bold_data["affine"])
gp.plot_coupling_map(plv_img, threshold=0.04)
gp.plot_glass_brain(plv_img, threshold=0.04)
What’s Next#
For hands-on walkthroughs, see the tutorials:
EGG Signal Processing — standalone EGG analysis
Gastric-Brain Coupling (Concepts) — pipeline overview with synthetic BOLD
Real fMRI Coupling Pipeline — end-to-end with real fMRIPrep data
See the API Reference for the full list of available functions.