Documentation

Using RespMech

A usage-focused guide to the desktop app and the command line, every analysis parameter, and the physiology behind respiratory mechanics, work of breathing, diaphragm EMG and sample entropy. All screenshots use the built-in synthetic sample recording.

Overview

RespMech analyses respiratory-physiology recordings — airflow, lung volume, oesophageal (Poes), gastric (Pgas) and transdiaphragmatic (Pdi) pressures, and one or more diaphragm-EMG channels — exported from your acquisition system. It splits each recording into individual breaths and, for every breath, computes a full set of respiratory-mechanics, work-of-breathing, EMG and signal-complexity descriptors, each carried with its own physical unit.

You drive it two ways. The desktop app (respmech-gui) walks you through Setup → Preview & QC, with Run & results as a drawer inside the second tab; the command line runs a batch headless from a TOML settings file. The out-of-the-box defaults reproduce the validated reference pipeline. Three documented differences from RespMech 1.x remain — the PTP baseline window, the sample-entropy indexing fix and SciPy's change to Simpson integration — see Changes from RespMech 1.x.

respmech run settings.toml
The RespMech Run & results drawer, showing a batch of recordings processed, with the live log and the averaged-metrics table.
The Run & results drawer processes the whole batch and writes results per file and as a per-file average, alongside diagnostic plots.

What RespMech computes

RespMech produces four families of analysis. Each owns its own section below — this page is the map.

  • Respiratory mechanics — breath timing (inspiratory time Ti, expiratory time Te, total cycle time Ttot and the duty cycle Ti/Ttot), tidal volume (VT), breathing frequency (bf) and minute ventilation (VE), plus oesophageal, gastric and transdiaphragmatic pressure descriptors, their pressure–time products (PTP), an isovolume inspiratory lung-resistance estimate and the gastric/oesophageal pressure ratio.
  • Work of breathing — the mechanical work the respiratory muscles perform, read from the Campbell (pressure–volume) diagram and split into inspiratory elastic, inspiratory resistive and expiratory components, reported as a per-minute power in J·min⁻¹.
  • Diaphragm EMG — a per-breath RMS amplitude and integrated EMG for each EMG channel (whole breath, inspiration and expiration), with optional cardiac-artefact (ECG) removal, spectral noise reduction, a cardiac-gated peak, and per-file amplitude normalisation.
  • Sample entropy — a dimensionless measure of how irregular or complex a signal is, computed per breath on any column you tick as an entropy channel (typically the diaphragm EMG).

The breath-by-breath & averaged model

Everything RespMech reports rests on one model: the recording is cut into whole breaths, every descriptor is measured on each breath, and the breaths are then summarised. In detail:

  • Trim, then segment. RespMech first trims the recording to whole breaths, then splits it into individual breaths. By default a breath boundary is a flow zero-crossing; you can instead detect breaths from volume peaks (see Splitting the record into breaths). A breath is one inspiration plus the expiration that follows it.
  • Per breath, then averaged. Every descriptor is computed for each breath and again as a per-file average across the breaths you keep. You can mark artefactual breaths (coughs, swallows, sighs) in Preview & QC so they are dropped from the average.
  • Rates are scaled from the file's own duration. Breathing frequency and minute ventilation come from the trimmed recording length and the detected breath count.
bf = bcnt · 60 / T_filemin⁻¹
VE = VT · bcnt · 60 / T_fileL·min⁻¹

where T_file is the trimmed recording length in seconds and bcnt the detected breath count. This same per-minute factor turns each breath's pressure–time product and work into a rate, which is why those columns carry a ·min⁻¹ in their unit.

A flow and volume recording split into numbered individual breaths, with one breath marked as excluded, illustrating breath-by-breath segmentation.
RespMech segments each recording into numbered breaths (here one is excluded as artefactual); every metric is computed per breath and then averaged over the breaths you keep.

Sign convention. RespMech expects inspiration = negative flow and inspired volume = positive. If your acquisition records the opposite, flip it with processing.volume.inverse_flow / inverse_volume (see Flow and volume conventions). A wrong sign does not error — it silently mislabels every inspiratory and expiratory quantity.

Recording window. Export each epoch so it begins part-way through an expiration and ends part-way through an inspiration. RespMech drops only that leading partial expiration and trailing partial inspiration. An epoch that begins mid-inspiration or ends mid-expiration is not rejected: the truncated breath is kept and analysed as if complete, and because drift correction (on by default) is anchored on the first and last samples, the volume baseline of every breath in that file is tilted. A trim error is raised only when flow never crosses zero (typically a wrong flow column or sign) or when the computed window is empty.

Units at a glance

Every output column carries an explicit physical unit. Use this table to orient yourself before diving into a section:

QuantityUnitAnalysis
Breath timing (Ti, Te, Ttot)sMechanics
Duty cycle (Ti/Ttot)dimensionlessMechanics
Tidal volume (VT), end-tidal volumesLMechanics
Flows (peak, mid-volume)L·s⁻¹Mechanics
Breathing frequency (bf)min⁻¹Mechanics
Minute ventilation (VE)L·min⁻¹Mechanics
Pressures (Poes, Pgas, Pdi), tidal swingscmH₂OMechanics
Pressure–time product, per breath (int_*)cmH₂O·sMechanics
Pressure–time product, rate (ptp_*)cmH₂O·s·min⁻¹Mechanics
Work of breathing (wob*)J·min⁻¹Work of breathing
EMG RMS amplitudea.u. (uncalibrated)Diaphragm EMG
Integrated EMGa.u.·sDiaphragm EMG
Normalised EMG% of per-file referenceDiaphragm EMG
Sample entropydimensionless (—)Sample entropy

On the arbitrary and the dimensionless. EMG amplitude is uncalibrated (a.u.), so only relative comparisons are meaningful — that is why RespMech can renormalise it to each file's own reference value (its per-file maximum or mean RMS). Sample entropy is a pure number. Note also that two derived mechanics ratios — vmr (gastric/oesophageal pressure ratio) and tlr_insp (isovolume lung resistance) — are deliberately left unlabelled in the output to avoid mislabelling, though physiologically vmr is dimensionless and tlr_insp is cmH₂O·L⁻¹·s.

Watch the ·min⁻¹. A pressure–time product or work value ending in ·min⁻¹ is a rate (that breath sustained for one minute), not the raw per-breath amount. The int_* columns give the un-scaled per-breath integral; the paired ptp_* and every wob* column are rates. Divide by bf to recover the per-breath value.

Scope and limitations

RespMech is analysis software: it turns signals you have already recorded into breath-by-breath descriptors. It does not, and cannot, verify that the recording itself is a valid measurement of the physiology it is labelled as. This section states the assumptions the analysis rests on, none of which the program checks for you.

Oesophageal pressure measurement

Every Poes-derived descriptor — both inspiratory pressure–time products, the isovolume lung-resistance estimate, the gastric/oesophageal ratio, and the whole Campbell-diagram work calculation — assumes that the oesophageal balloon signal is a valid surrogate for pleural pressure. That assumption is a property of the measurement, not of the software, and RespMech has no way to test it from the recorded trace alone. Two things make it more or less true:

Validate the balloon before you trust any Poes-, tlr_insp- or work-of-breathing-derived number; RespMech will compute all of them from an invalid trace exactly as readily as from a valid one, with no warning either way.

A stable, representative epoch

The per-file average, the averaged Campbell loop and the per-file EMG reference all assume the exported window is a stable, representative stretch of a single condition. Breathing frequency and minute ventilation are computed from that window's own duration and its full detected breath count (see The breath-by-breath & averaged model), so an epoch that spans a transition — onset of exercise, a change in load, recovery — returns the average of two states and reports it as one. Export a settled period per condition rather than one long file. Excluding breaths in Preview & QC removes them from the averages but not from the per-minute scaling, so it cannot repair an epoch that mixes conditions.

Units are assumed, never checked

RespMech reads flow as L·s−1, volume as L, and Poes, Pgas and Pdi as cmH2O. Nothing in the program verifies this: the loader checks only that each column is numeric, free of NaN, and the same length as the others (see Supported file formats). A recording exported in mL·s−1, mbar, kPa or raw transducer volts will load, run and produce a full set of results with no error at all — and the numbers are not simply your numbers in another unit: work of breathing and the pressure–time products carry the conversion factor of two signals at once, and any setting expressed in signal units (the breath-detection minimum peak height, the legacy absolute trend anchor in litres) stops meaning what it says. It is the same failure mode as a wrong sampling frequency, and just as invisible. Confirm the export units in your acquisition system before the first run, and sanity-check one file's tidal volume and peak Poes against what you expect physiologically.

Getting started

RespMech turns raw respiratory recordings — flow, volume, oesophageal / gastric / transdiaphragmatic pressures, and optional diaphragm EMG — into breath-by-breath mechanics: tidal volume and ventilation, work of breathing from the Campbell (pressure–volume) diagram, pressure–time products, and EMG amplitudes. You can drive it two ways from the same analysis engine:

  • The desktop app (respmech-gui) — two tabs and a drawer: Setup (choose recordings, map channels, pick outputs) → Preview & QC (see the signals and tune breath detection and EMG conditioning while everything recomputes live), with Run & results a drawer under the file list (batch-process every file and write the workbooks). The workflow deliberately loops — tune, re-run — rather than running once.
  • The command-line tool (respmech) and Python library — the identical engine, driven headlessly from a plain-text settings file so a whole batch is scriptable and reproducible (ideal for pipelines and CI).

A single settings file holds every knob for one batch of recordings. Throughout the app this file is always called an analysis (the underlying format is TOML, but the UI never says so).

Install

Desktop app (recommended for most users). Download the installer for your platform from the project's latest release. It is fully self-contained — it bundles its own Python and every dependency, so there is nothing else to install.

  • macOS — RespMech-<version>.dmg (signed and notarised); open it and drag RespMech to Applications. The bundle is built for Apple silicon and requires macOS 12 or later. On an Intel Mac, install the Python package instead (pip install "respmech[gui]").
  • Windows — RespMech-<version>.msi (signed); run the installer.

See what's new in the latest release →

Python package (for the CLI or library). Requires Python 3.11 or newer (the settings loader uses the standard-library tomllib). Runs on macOS, Windows and Linux; the desktop installers above are built only for macOS and Windows, but the Python package runs anywhere Python does. The base install gives you the respmech command-line tool and the analysis core with no Qt or heavyweight audio stack; add extras for more:

pip install respmech                     # CLI + core analysis engine
pip install "respmech[gui,emg,plots]"    # + the desktop app, the built-in sample and diagnostic PDF figures
  • gui — the desktop app (PySide6, pyqtgraph, matplotlib); needed to launch respmech-gui.
  • emg — EMG spectral noise reduction (librosa).
  • plots — diagnostic PDF figures (matplotlib).

Confirm the install and see the available commands:

respmech --version
respmech --help

The base pip install respmech is CLI-only. Enabling processing.emg.noise.enabled = true needs the emg extra, and the diagnostic PDF figures need the plots extra; turning those features on without the matching extra installed fails at run time rather than on install. The built-in sample recording switches spectral EMG noise reduction on, so Explore with sample data needs the emg extra too: on a bare respmech[gui] install the first run stops with No module named 'librosa'. The desktop installer already includes everything.

Your first analysis (sample data)

The fastest way to see the whole pipeline is the built-in sample recording — synthetic data with a deliberate ECG artefact and EMG noise, so every stage (including EMG conditioning) has something to do. It contains no patient data, so you can experiment freely.

  1. Launch the app. Open RespMech from Applications / the Start menu, or from a terminal:

    respmech-gui

    A brand splash appears, then the window opens maximised and shows the start chooser.

  2. Choose “Explore with sample data.” The chooser offers New analysis (guided, step by step), New from last rig (reuses your last channel mapping and sampling rate, shown only if one exists), Open analysis… (a saved analysis or a legacy 1.x .py file, which is migrated for you), Explore with sample data, and up to five recent analyses. Pick Explore with sample data.

    RespMech start chooser dialog listing New analysis, New from last rig, Open analysis, and Explore with sample data, with a Skip button beneath.
    The start chooser on launch. “Explore with sample data” loads a ready-to-run synthetic recording so you can walk the full workflow without any of your own files.
  3. Setup — review what will be analysed. For the sample, the Input, Output and Channels cards are already filled in. The Input card names the recordings folder and file mask and shows a live read-out of the detected column count and delimiter of the first matching file; the Channels card shows the flow, volume, pressure and EMG columns assigned through the visual picker; the Output card lists what will be saved, with a live “You will get” summary. A pinned QC strip reports live cautions (channel collisions, EMG overlaps, a low sampling frequency) or an all-clear. Both tabs are reachable throughout — nothing to unlock; only the run itself waits for a valid setup, and says what is missing. The sample also arrives with its EMG conditioning pre-tuned, so both EMG tabs have something to show: ECG removal is on (capture channel EMG2, threshold 0.4) and spectral noise reduction is on, referenced to a quiet lead-in — both off by documented default. Your own analyses start from the documented defaults instead.

    Setup tab in two columns: Input and Channels on the left with the detected-format read-out and the assigned columns drawn as stacked traces, Output and cohort summary on the right, and an all-clear QC strip beneath.
    The Setup screen for the sample analysis: channels assigned, outputs chosen, QC strip clear.
  4. Preview & QC — look, then tune. Open the Preview tab and select the file (the ◀ / ▶ buttons, PageUp / PageDown, or Refresh). Selecting a file automatically fans every computation out onto worker threads, so the plots and the QC verdict update as you watch. The sub-tabs run in pipeline order: Mechanics (flow, volume and pressure on a shared time axis with breath boundaries and the trim window, a Campbell diagram and per-breath table below — click a numbered breath to include or exclude it), then, when EMG channels are present, › EMG – ECG reduction and › EMG – noise reduction. The Mechanics QC strip reads, for example, QC: N breaths used, M excluded · no flags. Tuning a parameter here recomputes only the affected panels — this live scoping affects the preview alone; the batch always uses the full settings.

  5. Run & results — process and write. Open the Run & results ▸ drawer beneath the file list and click Run batch (or Dry run to compute without writing anything); starting a run opens the drawer for you. A pre-flight plan logs exactly what will be read and written, and a real run warns before overwriting an output folder that already contains results. A live log tracks the batch and each file's outcome is marked on the file list itself; on completion you get the averaged-metrics table, Re-run failed, and Open output folder.

    In the shipped v2.4.0 this toggle button's own label reads “Run  results ▸” below (the ampersand is swallowed as a keyboard mnemonic marker and the gap is underlined) — it is the same control, and the caption is fixed in the next release.

    The Run & results drawer opened beneath the file list, with the run controls, the live log and the averaged per-file metrics table.
    The Run & results drawer after a batch. Each file's outcome is marked on the file list beside it; the averaged metrics fill in below.

A run writes the workbooks into the output folder's data/ subfolder: data/Average breathdata.xlsx (the across-breath average, paired with a cohort summary), a per-file data/<file>.breathdata.xlsx, and an optional processed-signal CSV; the diagnostic figures you enabled go into a sibling diagnostics/ subfolder. It always also writes an analysis-used.toml snapshot of the exact settings and a run-report.txt in the output folder's root, so any result is reproducible.

The work is a loop, not a one-way sequence — which is why the runner sits in the same place you inspect. The natural rhythm is: glance at Mechanics, exclude a bad breath or nudge breath detection, watch the numbers settle, then run from the drawer below. If a run flags a file, select it on the list to inspect and adjust it. Save your tuned analysis from the header Analysis menu (Save / Save as…) so you can reopen or re-run it later.

The sample lives in a temporary folder. RespMech writes the built-in sample recording, and its results, into your operating system's temp directory, which the system may clear at any time. A banner on Setup says so while the sample is loaded, and the first real run warns before writing there. Saving the sample analysis with Analysis ▸ Save as… copies the recording alongside the saved file and repoints the recordings, output and EMG noise-reference folders there, so the saved copy keeps working once the temporary folder is cleared.

The same run from the command line

Everything the app does in Run & results is available headlessly. The CLI reads the same analysis file and drives the same engine, so a batch can be scripted or dropped into CI. If you never open the desktop app, start from the annotated settings.toml further down — the same file also ships in the app repository as examples/settings.toml, already wired to a bundled sample recording, so respmech validate examples/settings.toml runs with nothing to edit. Copy it into your own study folder, set input.folder, input.files, input.format.sampling_frequency and the seven input.channels entries to match your export, then run respmech validate before anything else. Once you have an analysis file — the annotated example, one saved from the app, or hand-written — the typical end-to-end sequence is:

respmech validate settings.toml      # check the settings and count matching input files
respmech run settings.toml --dry-run # compute without writing anything (quick check)
respmech run settings.toml           # process the batch, write to <output.folder>/data/

There is also respmech migrate old_settings.py -o settings.toml, which converts a legacy 1.x settings file to the new format (without executing it) and prints a migration report — the same conversion the app runs when you open a .py file.

Exit codes make it script-friendly. respmech run returns 0 when every matched file processed, 1 when at least one file failed (the others still computed; failures are listed on stderr), and 2 on an unhandled error such as a missing or invalid settings file. validate returns 0 only when the settings are valid and at least one input file matches — a valid file that matches zero inputs prints a warning and exits 1.

Relative input.folder and output.folder paths in a settings file are resolved against the settings file's own directory, not the shell's working directory. This keeps a shared analysis portable when it is moved between machines, but it surprises anyone expecting shell-relative behaviour — if a run reads or writes the wrong place, check where your .toml lives. (The analysis-used.toml snapshot deliberately keeps absolute paths and is a record, not a template to copy.)

Preparing your recording

RespMech analyses breath-by-breath respiratory recordings exported from LabChart or a similar acquisition system. Before it can compute mechanics, three things must be true: the file must be in a format RespMech can read, each physiological signal must be mapped to the right data column, and the trace must follow RespMech's sign conventions and start and end in the right part of the breath. This section covers everything you set up before the analysis runs. Signal conditioning after loading — drift correction, breath segmentation, resampling, and per-breath exclusions — is covered in the sections that follow.

In the desktop app these settings live on the Setup screen (Input and Channels cards); CLI users edit the same keys directly in the analysis TOML.

The RespMech Setup screen showing the Input card with recordings folder, file mask, sampling frequency and MATLAB variant fields, plus a live read-out of matched files and detected columns.
The Setup screen. The Input card sets the recordings folder, file mask and sampling frequency, and reports how many files matched and the detected column count and delimiter of the first file.

Supported file formats

RespMech chooses a loader by file extension:

  • .mat — MATLAB export from LabChart. The input.format.matlab_variant setting selects the byte layout: windows reads a data_block1 structure, mac reads each variable in column order. Only simple LabChart .mat exports are supported; for anything more complex, export to CSV instead.
  • .xlsx — Excel workbooks. The older binary .xls format is not supported; re-save it as .xlsx or export to CSV.
  • .csv — comma- (or semicolon-) separated text.
  • .txt — tab-separated text.

Text files are read tolerantly across encodings (UTF-16 "Unicode Text", UTF-8 with or without BOM, cp1252, latin-1), so exports from mixed instrument and operating-system chains usually load without manual conversion.

The first row is read as a header row. For .csv, .txt and Excel inputs RespMech treats row 1 as channel names and does not analyse it. Most instrument exports carry one, but if yours starts straight at the first sample, that sample is silently dropped — add a header row before analysing. .mat inputs have no header row and are unaffected.

For delimited text (.csv and .txt), the input.format.decimal setting tells RespMech which character marks the decimal point. Setting it to , for a European export also switches CSV parsing to semicolon-separated columns, matching how those files are written. The decimal setting is ignored for Excel and MATLAB, which carry their numbers natively.

Recordings folder
input.folder
Default input
Unit / values folder path
Folder holding the recording files. Absolute or relative to the working directory. In the guided new-analysis flow it must be a real folder containing files that match the mask before later steps unlock.
Files to analyse
input.files
Default *.*
Unit / values filename or single glob, e.g. *.txt
Wildcard mask picking which files to load. Narrow it (e.g. *.csv) to load one recording type. The headless runner globs a single pattern; the app narrows a multi-pattern default such as *.csv; *.txt to one extension before running. Hidden dotfiles are skipped unless the mask itself starts with a dot.
Sampling frequency
input.format.sampling_frequency
Default None (required)
Unit / values Hz
Samples recorded per second — the time base for every derived quantity. Must exactly match your acquisition system (e.g. 2000). The app can auto-detect it from the recording's time column when you assign channels. Below 1000 Hz the app warns it is low for EMG.
MATLAB file variant
input.format.matlab_variant
Default mac
Unit / values windows | mac
Byte layout for .mat files: windows reads a data_block1 structure, mac reads each variable in column order. Set it to match how LabChart exported the file. Ignored for CSV/Excel/text; a wrong choice usually fails the load with a "verify the MATLAB file format" error.
Decimal separator
input.format.decimal
Default .
Unit / values . or ,
Decimal character in delimited text files. Set to , for European instrument or Excel exports (which also switches CSV to semicolon-separated). Honoured only for CSV and TXT. The app auto-detects it for .txt during channel assignment; there is no dedicated Setup field, so set it via the picker or edit the TOML.
Sampling frequency is load-bearing and silent. It is required, never inferred at run time, and a wrong value rescales every time-derived result — inspiratory and expiratory times, minute ventilation, pressure–time products and work of breathing — with no error message. If your timing looks off by a constant factor, check this first. Let the app auto-detect it from the time column whenever you can.

Mapping columns to signals

RespMech identifies each physiological signal by its column number in the recording. Columns are numbered from 1, matching the LabChart export exactly. Column 1 is normally the time axis, so pointing a required signal at column 1 is flagged as an error, as are leaving a required channel unassigned, colliding two channels on the same column, or referencing a column beyond the file's width.

In the desktop app you do not count columns by hand. Click Assign channels from data… to open the visual picker: it plots every column so you can see which is flow, which is a pressure, and so on, then assign each role from a dropdown and tick any EMG and Entropy columns. On OK it also auto-detects the sampling frequency and (for .txt) the decimal separator from the data. A live QC strip flags unassigned, colliding or time-axis channels and EMG overlaps.

The Assign channels dialog plotting each data column as a trace, with a role dropdown per column for flow, volume, Poes, Pgas and Pdi and per-column Entropy tickboxes.
The visual channel picker. Each 1-based data column is plotted; you pick its physiological role from a dropdown and tick EMG or Entropy columns as needed.

Flow, Poes (oesophageal pressure), Pgas (gastric pressure) and Pdi (transdiaphragmatic pressure) are always required. Volume is optional — but only if you let RespMech integrate it from flow (see the conventions below). EMG and entropy are optional lists of columns. Entropy is non-exclusive: a column already carrying flow, a pressure or EMG can also be selected for sample-entropy analysis, and when the same column is also an EMG channel, entropy is computed on the processed EMG rather than the raw trace.

Flow channel
input.channels.flow
Default None (required)
Unit / values 1-based column
Airflow signal, in L·s⁻¹. Reads negative during inspiration by convention. Drives trimming and flow-based breath segmentation.
Volume channel
input.channels.volume
Default None (optional)
Unit / values 1-based column, or unset
Lung-volume signal, in L. May be omitted only if processing.volume.integrate_from_flow is true; otherwise validation fails. In the TOML an absent volume is simply an omitted key (TOML has no null).
Oesophageal pressure (Poes)
input.channels.poes
Default None (required)
Unit / values 1-based column
Oesophageal pressure, in cmH₂O. Feeds pleural-pressure descriptors and the Campbell / work-of-breathing calculation.
Gastric pressure (Pgas)
input.channels.pgas
Default None (required)
Unit / values 1-based column
Gastric pressure, in cmH₂O. Feeds abdominal-pressure descriptors and the gastric-to-oesophageal pressure ratio (VMR).
Transdiaphragmatic pressure (Pdi)
input.channels.pdi
Default None (required)
Unit / values 1-based column
Transdiaphragmatic pressure, in cmH₂O (often recorded directly as Pgas − Poes). Feeds Pdi descriptors and PTPdi.
EMG channels
input.channels.emg
Default []
Unit / values list of 1-based columns
Diaphragm-EMG columns to analyse (RMS, integrated EMG, optional ECG removal and noise reduction). Amplitude is uncalibrated (a.u.). An EMG column overlapping a flow or pressure column is flagged as a caution.
Entropy channels
input.channels.entropy
Default []
Unit / values list of 1-based columns
Columns on which sample entropy is computed. Non-exclusive — a column may also carry another role. When it is also an EMG column, entropy is computed on the processed EMG.

Flow and volume conventions

RespMech expects two sign conventions:

  • Inspiration reads negative flow. Trimming and flow-based segmentation both depend on this.
  • Inspired volume is positive.

If your rig records the opposite polarity, do not edit the data — fix it at load time with the invert toggles. RespMech applies these steps in a fixed order when it loads each file: invert flow (if requested), integrate volume from flow (if no volume channel), invert volume (if requested), then validate.

When you have no recorded volume channel, RespMech derives volume by integrating flow. Because integration uses the file's true sampling rate (even when pre-analysis resampling is active), it stays accurate regardless of the analysis rate you later choose:

volume(t) = −∫ flow dt L (from L·s⁻¹ · s, cumulative trapezoid)

The leading minus sign is what turns an inspiration recorded as negative flow into a positive inspired volume — the second convention above.

Invert the flow signal
processing.volume.inverse_flow
Default false
Unit / values true / false
Negates flow at load time. Turn on if your acquisition records inspiration as positive flow. Applied before volume integration, so it also flips the sign of any flow-derived volume.
Calculate volume from flow
processing.volume.integrate_from_flow
Default false
Unit / values true / false
Derives volume by integrating flow instead of reading a volume channel. When on, input.channels.volume may be left unset. Integration always uses the file's true sampling rate.
Invert the volume signal
processing.volume.inverse_volume
Default false
Unit / values true / false
Negates volume at load time. Turn on if inspired volume reads negative on your rig. Applied after any flow-integration step.
Getting the flow sign right. If trimming fails with "flow never crosses zero as required", or your breaths come out upside down, the flow polarity is almost always the cause. Toggle processing.volume.inverse_flow rather than altering the recording. You can check the polarity at a glance in the channel picker: during a breath the inspiratory limb should dip below zero.

What a valid recording looks like

RespMech works on whole breaths, so it needs a clean epoch to slice. The recording must start in late expiration and end in early inspiration. RespMech then trims it automatically to whole breaths: it finds the first inspiration sample (the first point where flow goes negative) and the last sample where flow is still non-negative, and slices every channel to that range. The leading partial expiration and the trailing partial inspiration are discarded, leaving a run of complete breaths.

If flow never crosses zero — usually because the polarity is inverted or the wrong column is mapped to flow — or the trimmed range comes out empty, RespMech raises a trim error naming the flow channel and the invert-flow setting, and the file is not analysed.

Plan your export window. When you export an epoch from LabChart, begin it partway through an expiration and end it partway through the next inspiration. An epoch that starts mid-inspiration keeps a truncated first breath, and one that ends mid-expiration keeps a truncated last breath, both analysed as if complete and both tilting the drift-corrected volume baseline of the whole file. Re-export the epoch, or exclude those breaths in Preview & QC.

Once a file loads, maps cleanly to channels, follows the sign conventions and trims to whole breaths, it is ready for the conditioning and segmentation steps described next.

The app — Setup

Setup is the first of the desktop app's two tabs (Setup → Preview & QC, with Run & results a drawer inside the second). It answers three questions and nothing else: which recordings to analyse, which data column carries which signal, and what to save. Everything you set here is stored in one analysis — a single settings file that holds every knob for one batch of recordings. The interface always says “analysis”; the word “TOML” never appears on screen, but that is what the file is, and CLI users edit the same dotted keys directly.

The tab is laid out in two columns of cards: Input and Channels on the left, Output and — only when you assign an entropy column — Sample entropy on the right. Every card is visible from the start, and both tabs stay reachable whether or not the analysis validates yet: only the run itself is gated, and the reason is written beside it. A menu bar (File, View, Help) sits above the tabs.

The Setup screen showing the Input card and the Channels card with the assigned columns drawn as stacked traces, the detected-format read-out, and the pinned QC strip at the bottom
The Setup screen. Each card owns one part of the analysis; a live read-out under Input reports what was detected in the first matching file, and the pinned QC strip at the bottom flags any problem before you run.

Where the detail settings went. Breath-detection, volume/drift conditioning, resampling and the whole EMG/ECG conditioning pipeline are not on Setup. They live on the Preview & QC tabs behind each tab's Advanced… button, where you can see them act on a real file. Setup only decides the recordings, the channel map and the outputs.

Every control on Setup has a mouseover tooltip that shows its exact TOML key in bold over a one-line plain-language description — so the friendly label stays readable while the dotted key it maps to stays discoverable.

The Input card

The Input card points RespMech at your recordings and tells it how they are timed. RespMech reads breath-by-breath recordings exported from LabChart or a similar system in MATLAB (.mat), Excel (.xlsx), comma-separated (.csv) or tab-separated (.txt) form; the loader dispatches on the file extension.

A Browse… button picks the folder, and a live read-out underneath reports what was detected in the first matching file — how many files matched the mask, the column count, and the delimiter — so a mis-picked folder or a wrong mask is obvious before you go further.

Recordings folder
input.folder
Default input
Unit / values folder path
Folder holding the recordings to analyse (absolute, or relative to the analysis file's own folder). In the guided flow this must be a real folder containing files that match the mask before the later cards unlock.
Files to analyse
input.files
Default *.*
Unit / values filename or single glob, e.g. *.txt
Wildcard mask picking which files in the folder to load. Narrow it (e.g. *.csv) when a folder mixes file types or subjects. Hidden dotfiles are skipped unless the mask itself starts with a dot.
Sampling frequency
input.format.sampling_frequency
Default required — none
Unit / values Hz (1–1 000 000)
Samples recorded per second — the time base for every derived quantity. Must match the acquisition system exactly. Auto-detected from the recording's time column when you assign channels; the widget shows 2000 only as a display fallback when unset.
MATLAB file variant
input.format.matlab_variant
Default mac
Unit / values windows | mac
Byte-order/layout for .mat files: windows reads a data_block1 structure, mac reads each variable in column order. Ignored for CSV/Excel/text. Legacy values 1/2 map to windows/mac.
Decimal separator
input.format.decimal
Default .
Unit / values . or ,
Decimal mark for delimited text; a comma decimal switches CSV parsing to ;-separated. Honoured for CSV/TXT only (not Excel/MATLAB). No dedicated widget — auto-detected for .txt when you assign channels, or set it in the file.

Time is derived, not read: for sample n, RespMech uses

t = n / f_ss

Sampling frequency is load-bearing and silent. It is required and is never re-inferred at run time. A wrong value rescales every time-derived result — inspiratory and expiratory times, breathing frequency, minute ventilation, the per-breath pressure–time integrals and work of breathing — with no error message. Confirm it matches the acquisition system, and note that below 1000 Hz the app warns that the rate is low when EMG channels are present.

The Channels card

RespMech needs to know which raw data column carries each physiological signal. Columns are numbered from 1, matching the LabChart export, and column 1 is normally the time axis. Rather than typing column numbers, you assign them visually: the Assign channels from data… button opens a picker that plots every column on a shared time axis so you can see which trace is flow, which is a pressure, and which is EMG, then pick each column's role from a dropdown.

The Assign-channels-from-data dialog: every data column stacked on a shared time axis, each with a role dropdown coloured by role and an independent Entropy tickbox
The visual channel picker. Every column is stacked on one time axis with a role dropdown (Flow, Volume, Poes, Pgas, Pdi, EMG, unused) and an independent Entropy tick. The mapping applies to every file in the batch; the required roles gate the OK button.

Single roles are mutually exclusive — a column is Flow or Poes, not both — but the Entropy tickbox is independent, so a column can carry a role and be an entropy column. The required roles (Flow, Poes, Pgas, Pdi) gate the picker's OK button, and the chosen mapping applies to every file in the batch. When you click OK the picker also auto-detects the sampling frequency and (for .txt) the decimal separator from the data, and narrows a multi-pattern file mask to a single extension.

Back on the card, a read-only summary shows the assignments (and reads “Volume: derived from flow” when volume is integrated rather than recorded), while the pinned QC strip flags live cautions — an unassigned required channel, two channels colliding on one column, a required channel pointed at the time axis, or an EMG column overlapping a pressure/flow column.

Flow channel
input.channels.flow
Default required — none
Unit / values 1-based column
Column holding airflow (reads negative during inspiration by convention). Drives trimming and flow-based breath segmentation. Required.
Volume channel
input.channels.volume
Default none (optional)
Unit / values 1-based column, or unset
Column holding lung volume. May be left unset only if volume is integrated from flow (a Preview→Mechanics Advanced… option); otherwise required.
Poes channel
input.channels.poes
Default required — none
Unit / values 1-based column
Oesophageal pressure (cmH₂O). Feeds pleural-pressure descriptors and the Campbell/WOB calculation. Required.
Pgas channel
input.channels.pgas
Default required — none
Unit / values 1-based column
Gastric pressure (cmH₂O). Feeds abdominal-pressure descriptors. Required.
Pdi channel
input.channels.pdi
Default required — none
Unit / values 1-based column
Transdiaphragmatic pressure (cmH₂O), often recorded directly as Pgas − Poes. Feeds Pdi descriptors and PTPdi. Required.
EMG channels
input.channels.emg
Default [] (empty)
Unit / values list of 1-based columns
Diaphragm-EMG columns to quantify (RMS, integrated EMG, optional ECG removal / noise reduction). Assigning any EMG column reveals the two EMG sub-tabs on Preview. Amplitude is uncalibrated (a.u.).
Entropy columns
input.channels.entropy
Default [] (empty)
Unit / values list of 1-based columns
Columns to also compute sample entropy on. Non-exclusive — a column may carry a role and entropy at once. Assigning any entropy column reveals the Sample-entropy card below.

Columns are 1-based and assigned only through the picker. You cannot type a column number. Mapping a required channel to column 1 (the time axis), leaving a required role unassigned, or duplicating a column on two roles are all hard errors blocked by the QC strip.

Flow-sign convention. Inspiration must read negative flow and inspired volume must read positive. If a transducer's polarity is reversed, fix it with Invert flow / Invert volume on the Preview→Mechanics Advanced… modal — not by editing the data. A reversed flow sign does not raise an error: trimming and breath segmentation simply lock onto the wrong phase, so every inspiratory and expiratory number is mislabelled while the run still looks healthy (see Troubleshooting). The message “the flow signal never crosses zero as required” appears only when the flow column contains no sample ≤ 0 or no sample ≥ 0 — a strictly one-signed column, typically a wrong channel.

The Output card

The Output card decides where results are written and which of them to write. Numeric tables are saved into a data subfolder inside the output folder, diagnostic figures into a diagnostics subfolder, and a settings snapshot (analysis-used.toml) and a run-report.txt are written at the top of the output folder itself; a full run always writes both. A live “You will get” line lists the exact deliverables before you run, and a real run warns before overwriting an output folder that already contains results.

Output folder
output.folder
Default output
Unit / values folder path
Where results are saved: numeric tables go into a data subfolder, figures into diagnostics, and the two provenance files to the root. Point it at a per-study results folder.
Average breath-data workbook
output.data.save_average
Default true
Unit / values on/off
The across-breath average workbook — the main per-condition deliverable. The cohort summary (mean ± SD, CV%, by group) is always written alongside it. Leave on for most work.
Breath-by-breath workbook (per file)
output.data.save_breath_by_breath
Default true
Unit / values on/off
A per-file breath-by-breath workbook. Needed for per-breath inspection; also produces the normalised-EMG sheet when EMG and amplitude normalisation are configured.
Processed-signal CSV (per file)
output.data.save_processed
Default false
Unit / values on/off
Writes the fully conditioned time series as a CSV per file. Turn on to export the processed signal for external analysis. Off by default because the files are large.
Include excluded breaths in the processed CSV
output.data.include_ignored_breaths
Default false
Unit / values on/off
Keeps the breaths you excluded in the per-file processed CSV only — useful to audit what was dropped. Excluded breaths are always left out of the averages and workbooks; this does not change them.
Campbell / PV diagram — averaged
output.diagnostics.save_pv_average
Default true
Unit / values on/off
The averaged Campbell (pressure–volume) diagram — the headline work-of-breathing figure.
Campbell / PV diagram — individual breaths
output.diagnostics.save_pv_individual
Default true
Unit / values on/off
A Campbell diagram per individual breath, to inspect breath-to-breath consistency. Grid layout is fixed at 3×4 (not exposed in the UI).
Raw-signal figures
output.diagnostics.save_raw
Default true
Unit / values on/off
Raw-signal diagnostic figures — QC that the right channels loaded.
Trimmed-signal figures
output.diagnostics.save_trimmed
Default true
Unit / values on/off
Trimmed-signal diagnostic figures — confirm the analysis window.
Drift-correction figures
output.diagnostics.save_drift
Default true
Unit / values on/off
Drift-correction diagnostic figures — verify that volume-drift removal did the right thing.
EMG channel overviews
output.diagnostics.save_emg
Default true
Unit / values on/off
Per-channel EMG overview figures (raw / ECG-removed / noise-reduced) with the flow reference and R-peak capture markers — QC the EMG conditioning pipeline per channel.
Group files by
output.group_regex
Default blank (leading token)
Unit / values regex with one capture group, or blank
How files are grouped (subject/condition) for the by-group cohort summary. Blank uses the leading filename token (e.g. P03_120W → P03); enter a regex whose first capture group is the key when the naming convention is more complex.

Excluded is not the same as uncounted. Excluding a breath (done later, by clicking it in the Preview→Mechanics plot) drops it from the averages and workbooks but keeps it drawn in the plots. The separate breath-count override (Preview→Mechanics Advanced…) changes only the per-minute scaling factor bf = count · 60 / duration, not which breaths are averaged. Both are keyed by file basename and stamped with the recordings folder they were made in. If you later point the analysis at a different folder (for example with Duplicate for another recordings folder…), Setup shows a banner naming the exclusions, breath-count overrides or rest reference that still belong to the previous folder, with Keep (leave them applying as they are) and Clear (drop them); until you clear them, Preview draws such carried-over exclusions hatched, the QC line says “carried over from a previous recordings folder”, and the file list marks them with a ↺ badge. The folder stamp drives that warning only — matching is still by basename, so anything you Keep keeps applying exactly as before.

The Sample-entropy card

This card appears only when you have assigned at least one entropy column in the picker; otherwise it stays hidden. It exposes the two parameters of the sample-entropy statistic

sample entropy, SampEn(m, r)dimensionless

computed on each ticked entropy column. When a column is also an EMG channel, entropy is computed on the processed (conditioned) EMG rather than the raw trace.

Template length (m + 1)
processing.entropy.epochs
Default 3
Unit / values 1–100 (dimensionless)
The length of the longest template compared, one more than the embedding dimension m used in the sample-entropy literature. The default 3 gives m = 2, which is conventional there; set 2 for the m = 1 RespMech reported before this default changed.
Tolerance (r), × SD
processing.entropy.tolerance
Default 0.1
Unit / values 0.0–10.0 (dimensionless)
Matching tolerance r, as a multiple of the standard deviation of the segment being analysed. Published values are typically 0.1–0.25 × SD, with 0.2 × SD the most widely used default (Richman & Moorman, Am J Physiol Heart Circ Physiol 2000;278:H2039–49; Yentes et al., Ann Biomed Eng 2013;41:349–65); RespMech defaults to the lower end, 0.1. Keep it fixed across every file in a study.

Entropy assignment is independent of a column's physiological role. Tick Entropy on a pressure or EMG column to add a variability measure without disturbing anything else that column feeds — the entropy result is reported on its own, and the raw role outputs are unchanged.

With Input, Channels and Output valid and the QC strip clear, the run is ready to start. Continue to Preview & QC to tune breath detection and EMG conditioning on a real file, then open the Run & results drawer there to process the batch.

The app — Preview & QC

Preview & QC is the second of the desktop app's two tabs (respmech-gui) and the one where you spend most of your time. You work on one recording at a time, watch every plot redraw as you tune a parameter, read the quality-control verdict, and exclude any breath that should not count — all before you commit the whole batch to a run. Setup decides what to analyse; Preview decides how; Run does it to every file with the exact settings you tuned here.

How the Preview screen works

Selecting a file kicks off every computation the current settings allow. Independent jobs run concurrently on worker threads (capped at two at once so the window stays responsive), and edits are debounced by 300 ms and collapsed into a single re-dispatch, so dragging a value redraws smoothly rather than in a stutter. A dependency map scopes which panels recompute for a given change — editing an EMG control does not re-run the mechanics preview and vice-versa.

This preview scoping affects the on-screen panels only. The batch run and the respmech run CLI always evaluate the full, real settings, so what you see in Preview is faithful to what a run will produce.

A file list on the left (the same list Run & results marks outcomes on) holds one row per file matched by your Setup mask, with a Filter files… box above it. Click a row to preview it, or step with the ◀ / ▶ buttons, Ctrl+[ / Ctrl+] (⌘[ / ⌘] on macOS), Alt+← / Alt+→, or PageUp / PageDown while the list has focus. Refresh recomputes every preview panel for the current file (its tooltip: “Recompute all preview panels for the current file.”). Nothing is watched in the background, but the per-file caches are keyed on the file's modification time and size, so pressing Refresh after editing a recording on disk picks up the new data. The mouse wheel scrolls the list and the page; over a spin box it is passed on to the page rather than changing the value, because every such change would dirty the analysis and schedule a recompute.

Below the file list, sub-tabs run in fixed pipeline order, the two EMG tabs carrying a leading chevron to signal the flow: Mechanics, › EMG – ECG reduction, › EMG – noise reduction. The two EMG tabs appear only when you have assigned EMG channels on Setup; with no EMG channels, Mechanics is the whole screen.

The Mechanics tab

The Mechanics tab stacks flow, volume and the pressure channels (Poes, Pgas, Pdi) on a shared time axis, already trimmed to whole breaths, with the detected breath boundaries drawn on top. A crosshair read-out reports the value under the cursor. Below the traces sit a Campbell (pressure–volume) diagram and a per-breath table of the computed metrics, so you can see the loop and the numbers change together.

Preview Mechanics tab: flow, volume and pressure channels stacked on a shared time axis with numbered breath boundaries, a Campbell diagram and a per-breath metrics table below
The Mechanics tab: stacked channels with numbered breaths, the Campbell diagram and the per-breath table beneath.

Excluding a breath. Every detected breath is numbered on the traces. Click a breath to toggle it in or out of the analysis; excluded breaths are shaded out. This is your main QC lever: drop a cough, a swallow, a movement artefact or a mis-segmented boundary and every downstream average updates immediately.

Numbered breaths on a volume trace with one breath shaded to show it has been excluded from the analysis
Click a numbered breath to include or exclude it. Excluded breaths are shaded and left out of every average.
Excluded breaths are always left out of the averages and the written workbooks. The only place they can reappear is the per-file processed-signal CSV, and only if you tick Include excluded breaths on Setup — that option changes the CSV alone, never the numbers.

A QC overview strip summarises the file at a glance — for example QC: 14 breaths used, 2 excluded — followed by any warning flags (such as vt≤0 or wobtotal NaN) or · no flags when the file is clean. Two buttons let you act on the single file without leaving Preview: Export Campbell… saves the current loop as an image, and Process & write this file runs and writes just this recording so you can inspect one full output before committing the batch.

The Campbell diagram shows the work of breathing: pressure (cmH₂O) on one axis against volume (L), with the elastic, resistive and expiratory areas that the analysis integrates (the loop's own enclosed area is not the work). Work of breathing is reported in J·min⁻¹ and the pressure–time product in cmH₂O·s·min⁻¹.

Mechanics — Advanced settings

Only the handful of controls anyone reaches for sit on the tab itself; everything that shapes segmentation, work-of-breathing and volume conditioning lives behind the Advanced… button. Edits in the modal are staged — nothing reaches the analysis (and nothing recomputes) until you click OK, and OK without a change neither dirties the file nor triggers a recompute.

Mechanics Advanced modal listing segmentation, work-of-breathing, PTP, volume and drift, resampling and breath-count override controls, each with a TOML-path tooltip
The Mechanics Advanced… modal holds every segmentation, work-of-breathing, volume/drift, resampling and breath-count setting.
Signal used to split breaths
processing.segmentation.method
Default flow
Unit / values flow | volume
Which signal marks where each breath begins and ends. Switch to volume when flow zero-crossings are noisy.
Breath-separation buffer
processing.segmentation.buffer
Default 800
Unit / values samples
Forward look-ahead that tolerates zero-crossing wobble (flow method only): a phase continues while flow still has the phase sign, or the mean flow over the next 'buffer' samples still does. Widen if single breaths are being fragmented by flow noise near zero; narrow if adjacent breaths merge. In samples, so its duration depends on the sampling frequency — re-tune it after any change to fs.
Breath peak — minimum height
processing.segmentation.peak.height
Default 0.1
Unit / values signal units
Minimum peak height for breath detection (in the segmentation signal's units). Raise to ignore small non-breath excursions.
Breath peak — minimum distance
processing.segmentation.peak.distance_s
Default 0.1
Unit / values s
Minimum time between detected breath peaks. Raise to stop one breath being counted twice.
Breath peak — minimum width
processing.segmentation.peak.width_s
Default 0.5
Unit / values s
Minimum width of a detected breath peak. Rejects narrow, artefactual peaks.
Work of breathing from
processing.wob.calc_from
Default average
Unit / values average | individual
Compute WOB from one averaged breath (default) or each breath then averaged. individual gives a per-breath distribution at higher cost.
Average-breath resampling points
processing.wob.avg_resampling_obs
Default 500
Unit / values points
Points each breath is resampled to for the average breath and WOB. More points give a finer average-breath resolution.
PTP baseline window
processing.ptp.baseline_window_s
Default 0.05
Unit / values s
End-expiratory (or end-inspiratory) window whose mean pressure is the pressure–time-product baseline. A window is more robust to boundary noise than a single sample.
Calculate volume from flow
processing.volume.integrate_from_flow
Default false
Unit / values on/off
Derive volume by integrating flow instead of using a separate channel. Turn on when there is no volume channel — the volume channel then stops being required.
Correct volume drift
processing.volume.correct_drift
Default true
Unit / values on/off
Remove slow baseline drift from the volume trace. On by default; turn off only to inspect the raw drift.
Correct end-expiratory trend
processing.volume.correct_trend
Default false
Unit / values on/off
Remove a between-breath trend in end-expiratory lung volume. Use when EELV drifts across the recording.
Trend interpolation
processing.volume.trend_method
Default linear
Unit / values linear | nearest | cubic | quadratic | previous | next
How the end-expiratory trend is interpolated between breaths. Only used when Correct end-expiratory trend is on.
Invert the flow signal
processing.volume.inverse_flow
Default false
Unit / values on/off
Flip the flow sign if inspiration reads positive — fixes a reversed transducer polarity.
Invert the volume signal
processing.volume.inverse_volume
Default false
Unit / values on/off
Flip the volume sign — fixes a reversed volume polarity.
Resample before analysis
processing.sampling.resample
Default false
Unit / values on/off
Resample every recording to a common rate first (polyphase). Use to harmonise a batch acquired at different rates. Off by default.
Resample to
processing.sampling.resample_to_frequency
Default 200
Unit / values Hz
Target rate for the pre-analysis resample. Keep ≥ ~1000 Hz when EMG channels are present, or EMG detail is lost. Only used when Resample before analysis is on.
Breath-count overrides
processing.breath_counts
Default []
Unit / values one filename = count per line
Per-file override of the breath count used for per-minute scaling (V̇E, PTP·min⁻¹, WOB·min⁻¹). Blank means each file's detected count. Correct it when detection over- or under-counts for a specific file.

EMG — ECG reduction

Raw diaphragm EMG is uncalibrated (a.u.) and, on chest recordings, contaminated by the heartbeat. The › EMG – ECG reduction tab removes that cardiac artefact by detecting each R-wave on one capture channel, averaging a heartbeat template, and subtracting it from every EMG channel. It is the first EMG conditioning stage and the prerequisite for the two features that follow.

Preview EMG ECG-reduction tab: capture-channel picker, Remove ECG toggle, Min height and Min gap fields, Auto-suggest button, and the capture channel showing detected R-peak markers with the ECG-removed channels below
The ECG-reduction tab: pick a capture channel, tune Min height and Min gap while watching the R-peak markers, then read the ECG-removed channels below.

Workflow. Pick the Capture channel — the one with the clearest heartbeat and weakest EMG, often the middle electrode. Optionally click Auto-suggest settings to analyse the raw EMG and fill the channel, height, gap, width and template automatically. Then tick Remove ECG and adjust Min height and Min gap while watching the detected-peak markers (▼) on the capture-channel plot; the ECG-processed channels render underneath so you can confirm the heartbeat is gone without eroding the EMG bursts.

HR_max = 60 / ecg_min_distance_sbpm

Min gap is the refractory gap between beats, and it doubles as a ceiling: it implies the maximum heart rate the detector can resolve. On fast (exercise) files, lower it so real beats are not merged or missed.

Capture channel is an index, not a column. processing.emg.detect_channel is a 0-based index into your list of EMG channels, not a raw data-column number. Re-assigning EMG channels on Setup silently re-points it; the preview re-seeds it to the middle channel and clamps out-of-range values with a status warning.
Tune once, for the whole test. The detection and template parameters are test-level — they are applied identically to every file. Never re-tune them per file: doing so breaks the shared-transformation requirement that makes relative EMG comparable across a study.
Capture channel
processing.emg.detect_channel
Default 0
Unit / values index into input.channels.emg
Which EMG channel the R-waves are detected on. Pick the clearest ECG / weakest EMG channel (often the middle one).
Remove ECG
processing.emg.remove_ecg
Default false
Unit / values on/off
Subtract an averaged ECG template from every EMG channel. Turn on whenever the heartbeat bleeds into the EMG. Prerequisite for noise reduction and the gated peak.
Min height
processing.emg.ecg_min_height
Default 0.0005
Unit / values a.u. (signal units)
Minimum height of an R-wave peak on the capture channel. Raise to reject small non-QRS peaks; lower to catch weak beats. Electrode-specific — let Auto-suggest set it.
Min gap
processing.emg.ecg_min_distance_s
Default 0.5
Unit / values s
Minimum time between heartbeats (refractory gap). Implies a max heart rate of 60/gap bpm — lower it on fast heart rates.
Minimum peak width
processing.emg.ecg_min_width_s
Default 0.001
Unit / values s
Minimum width of an R-wave peak — a shape guard against counting a narrow spike as a heartbeat. Rarely moved.
Template width
processing.emg.ecg_window_s
Default 0.4
Unit / values s
Width of the ECG template (QRS-T) averaged and subtracted around each beat. Physiologically fixed — 0.4 s is right for adults.

RespMech reports a suppression figure per file — the drop in peak-window RMS around each R-peak, before versus after:

suppression = 1 − RMS_after / RMS_beforefraction

EMG — noise reduction

The › EMG – noise reduction tab is the second conditioning stage: it subtracts a shared spectral noise profile — built once from a rest reference and applied identically to every file — from every EMG channel. It removes a stationary broadband/electrical noise floor while preserving the true EMG band (roughly 20–250 Hz).

Preview EMG noise-reduction tab: a single control strip with the Reduce EMG noise toggle, a Set noise profile button, the chosen-reference read-out, a Noise/Auto chip and an Advanced… button, above the Detail channel and Conditioned result graphs and, below them, the Raw EMG channels, Noise fidelity frontier and Detail PSD panels
The noise-reduction tab: set a rest reference, then let Auto pick the strongest suppression that stays above your fidelity target. The fidelity frontier panel plots the trade-off.

Workflow. Click Set noise profile to choose the rest reference. In that dialog you either tick Use the whole expiration of this recording — recommended, because every expiratory phase is diaphragm-quiet and yields a far more stable estimate (hundreds of STFT frames rather than a handful) — or untick it and drag a quiet rest span yourself. Then tick Reduce EMG noise. Leave Auto on — beside the on/off toggle and the reference picker it is the only noise control left on the strip. The fidelity target moved into Advanced… ▸ Noise suppression ▸ Fidelity target (Keep ≥); with Auto on, the app then picks, once per test, the strongest suppression that keeps every channel at or above that target. The Noise fidelity frontier (1 = untouched) panel plots one fidelity curve per EMG channel against suppression strength, with your target as a dotted horizontal line and the strength Auto chose as a dashed vertical one, so the chosen point is visible.

fidelity = P_band(processed inspiration) / P_band(raw inspiration)dimensionless — 1 = untouched
Prerequisites. Noise reduction (and the gated peak) stay greyed out until Remove ECG is on — the noise profile is measured on the ECG-cleaned signal. A ticked Reduce EMG noise with no reference set runs nothing on-screen and raises an error in a batch run.
The "great SNR trap." Raising the gate threshold n_std_thresh looks like it improves signal-to-noise, but it collapses fidelity — at ≥ 1.5 it destroys real EMG. Trust the fidelity gate, not the SNR number, and leave the default of 1.0 alone.
Reduce EMG noise
processing.emg.noise.enabled
Default false
Unit / values on/off
Subtract the shared spectral noise profile from every EMG channel. Requires Remove ECG on and a reference set.
Auto
processing.emg.noise.auto_prop
Default true
Unit / values on/off
Automatically pick the strongest suppression that keeps every channel at/above the fidelity target. When on, Suppression strength (when Auto is off) is greyed out in the Advanced… modal.
Suppression strength (when Auto is off)
processing.emg.noise.prop_decrease
Default 0.6
Unit / values 0.0–1.0
How aggressively to remove noise when Auto is off (0 none → 1 maximum). Raise for noisier recordings; lower to preserve signal. 0.5–0.7 is safe.⚠ Lives in Preview & QC → EMG – noise reduction → Advanced…, under Noise suppression.
Fidelity target (Keep ≥)
processing.emg.noise.fidelity_target
Default 0.8
Unit / values 0.50–0.99
Smallest fraction of inspiratory EMG power that must survive removal. Only governs the Auto choice. Lower to allow more suppression; raise to protect signal.⚠ Lives in Preview & QC → EMG – noise reduction → Advanced…, under Noise suppression.
Spectral gate threshold
processing.emg.noise.n_std_thresh
Default 1.0
Unit / values SD above profile mean
A bin below the mean noise level plus this many SDs is treated as noise. Essentially never changed.⚠ Lives in Preview & QC → EMG – noise reduction → Advanced…, under Spectral gate (STFT).

The reference itself is stored, not typed — the Set-noise-profile dialog writes processing.emg.noise.reference_file, processing.emg.noise.reference_intervals and processing.emg.noise.use_expiration for you. The gate builds its per-frequency threshold as:

T(f) = mean_t(dB|STFT(noise)|) + n_std_thresh · SD_t(dB|STFT(noise)|)dB

Cardiac-gated peak (in this tab’s Advanced… modal)

Even after template subtraction, the plain maximum RMS of a breath often lands on a residual heartbeat rather than the diaphragm. Open Advanced… on this tab and tick Add gated-peak columns, under Gated peak (saved output) — available only when Remove ECG is on. It measures each breath's peak from only the heartbeat-free stretches: it blanks a window around every detected R-peak and takes the maximum of what survives. Set Blanked around each heartbeat to cover the heartbeat's footprint in the RMS envelope. It is output-only — it adds columns to the saved data and changes nothing you can see on this tab, which is why it sits behind Advanced… rather than on the strip.

Rolling RMS envelope of a breath with gate windows blanked around each R-peak, contrasting the ungated maximum falling on a residual heartbeat with the gated maximum on diaphragm activity
The gated peak blanks ± the gate half-width around each heartbeat and reads the maximum from the surviving islands, avoiding a beat-locked peak.
blanked window = 2 · gate_half_width_ss
It only adds columns. Enabling the gated peak changes nothing on the live plots and never alters an existing number — it adds gated columns to the saved data (they read NaN if the guards below trip). The blank field is a half-width: the default 0.120 discards a 0.240 s window in total.

EMG — Advanced settings

The Advanced… button on the noise tab holds the spectral-gate internals, the RMS/normalisation settings that define the reported EMG number, and the gated-peak quality guards. A live read-out shows what each STFT sample count means in milliseconds at the current sampling rate — 256 samples is a different duration at 1000 versus 2000 Hz.

The EMG Advanced… modal, laid out as titled section cards in responsive columns: RMS and normalisation, Noise suppression (suppression strength and the Keep ≥ fidelity target), Spectral gate (STFT) with a live milliseconds read-out, Gated peak (saved output), Heartbeat and island guards, and Diagnostics with the WAV-export switch
The EMG Advanced… modal, one titled card per group: RMS and normalisation, Noise suppression (the suppression strength and the “Keep ≥” fidelity target), the spectral-gate internals with a live ms read-out, the gated peak, the heartbeat guards and the diagnostics switch.

The RMS window length defines every reported EMG amplitude. Each breath reports the largest value of a sliding-window RMS:

env[i] = √( mean( x[i−w+1 … i]² ) ), w = rms_window_s · fsa.u.
STFT length (n_fft)
processing.emg.noise.n_fft
Default 256
Unit / values samples, power of two
FFT length for the spectral gate. Trades frequency against time resolution; rarely changed.
STFT window
processing.emg.noise.win_length
Default 256
Unit / values samples
Analysis window length. Kept separate from n_fft on purpose — collapsing them would change output where they differ.
STFT hop
processing.emg.noise.hop_length
Default 64
Unit / values samples
Advance between successive windows. Smaller hop gives smoother gating at higher cost.
Mask smoothing — frequency
processing.emg.noise.n_grad_freq
Default 0
Unit / values frequency bins
How many frequency bins the suppression mask is smoothed over. Rarely changed.
Mask smoothing — time
processing.emg.noise.n_grad_time
Default 4
Unit / values time frames
How many time frames the suppression mask is smoothed over — avoids "musical noise". Rarely changed.
RMS window length
processing.emg.rms_window_s
Default 0.050
Unit / values s
Sliding-window length for the EMG RMS envelope; each breath takes its largest windowed value. Changing it changes every reported EMG amplitude. 0.05 s is RespMech's default and the value the reference pipeline was built with, not a validated external standard.
RMS outlier limit
processing.emg.outlier_rms_sd_limit
Default 0.0
Unit / values SD (0 = off)
Replace any breath whose EMG RMS lies more than this many SD from the across-breath mean. Use to suppress a single artefactual breath; 0 disables it.
Amplitude normalisation
processing.emg.normalization
Default per_file_max
Unit / values none | per_file_max | per_file_mean
Also report each RMS column as a % of that column's own peak or mean breath, so within-file amplitude patterns compare across subjects/electrodes (each file's own peak reaches 100% by construction — set normalization_reference_file for a shared, cross-file reference). Adds a separate normalised sheet; never changes the raw RMS.
Export each EMG stage as WAV
processing.emg.save_sound
Default false
Unit / values on/off
Write an audio file per channel per conditioning stage — listen to raw versus conditioned EMG as a diagnostic. Off by default (adds several files per recording).
Least of each phase that must survive
processing.emg.robust_peak.min_survival
Default 0.40
Unit / values fraction
Below this surviving fraction the gated value is left blank rather than read from a sliver of breath.
Shortest usable stretch between beats
processing.emg.robust_peak.min_island_s
Default 0.20
Unit / values s
Cardiac-free gaps shorter than this cannot hold a full RMS window, so they are ignored.
Missed-heartbeat factor
processing.emg.robust_peak.long_rr_factor
Default 1.6
Unit / values × median R-R
An R-R gap this many times the median is treated as a missed heartbeat (a missed beat roughly doubles the interval).
Missed heartbeats tolerated
processing.emg.robust_peak.max_long_rr_frac
Default 0.02
Unit / values fraction
Above this fraction of long gaps the detection is distrusted and all gated columns are left blank for the file.
Heart-rate ceiling margin
processing.emg.robust_peak.hr_ceiling_margin
Default 0.10
Unit / values fraction
How close the detected rate may come to the ceiling implied by Min gap before the result is distrusted.
If the gated peak keeps blanking a fast (exercise) file because the heart rate approaches the ceiling, the fix is to lower Min gap on the › EMG – ECG reduction tab, not to raise the ceiling margin. A NaN gated column is an honest refusal, not a failure — the guards report nothing rather than a wrong number.

The Analysis menu

Which analysis is open is a window-level concern, so New / Open / Save live in the header Analysis menu and are reachable from every tab. An analysis is one settings file holding every knob for one batch of recordings.

The app never says "TOML" — the settings file is always called an analysis in the UI, even though it is a .toml file on disk.

The menu offers New analysis, Open analysis…, Save, Save as…, Get started… (re-opens the start chooser), Explore with sample data (loads the built-in sample again at any time), Duplicate for another recordings folder…, and up to five recent analyses as one-click entries.

Duplicate for another recordings folder… keeps your settings, asks for a new recordings folder, suggests a sibling output folder you can edit, clears the ECG reference file (it could only ever name a file in the old folder) and opens Save as… so the copy is written to a new file rather than over the template. It is the intended route for the next subject in a multi-subject study, and the intended way to turn a tuned sample analysis into a real one.

Opening a legacy 1.x .py settings file runs the migrator and shows a copyable migration report. The window title names the open analysis and marks unsaved edits with * (modified); switching away with unsaved changes prompts Save / Discard / Cancel first. The same commands also sit in the menu bar's File menu, which adds Open Recent, Open output folder and Close Window (Ctrl+W); View switches tabs (Ctrl+1 Setup, Ctrl+2 Preview & QC) and Help opens the documentation, the website, the issue tracker and the offline About box. Shortcuts: Ctrl+N, Ctrl+O, Ctrl+S, Ctrl+Shift+S. On macOS the menu bar is merged into the system menu bar and About RespMech moves into the application menu. The header Analysis button locks during a batch run; the same commands remain reachable from the File menu and its shortcuts, so avoid opening or creating an analysis while a run is in flight.

Because Preview recomputes live, this is the natural loop: open or switch an analysis, tune on one representative file until the QC strip is clean and the conditioning looks right, save, then move to Run & results to process the whole batch with exactly these settings.

respmech run settings.toml

The same analysis file drives the headless CLI, so anything you tune here reproduces identically in a scripted batch.

The app — Run & results

Run & results is a collapsible drawer under Preview & QC's file list, not a screen you navigate to — so you can dry-run, run and read the run report without leaving the file you are looking at, and starting a run opens it for you. It processes every recording in the batch — applying exactly the settings you tuned — and writes the workbooks, diagnostic figures and provenance files to your output folder. Everything runs on a background worker thread, so the log updates live and the window never freezes. The drawer is always reachable; it is the Run batch button alone that waits for a valid setup, and it names what is missing.

In the shipped v2.4.0 the drawer's own toggle button, shown below, reads “Run  results ▸” (the ampersand is swallowed as a keyboard mnemonic marker and the gap is underlined) rather than “Run & results ▸” — it is the same control, and the caption is fixed in the next release.

The Run and results drawer: a Run batch button, a Dry run button, Cancel, Re-run failed, Open output folder and Show full output plan, a status line, a live text log, and the averaged per-file metrics table below.
The Run & results drawer. The action row runs the batch or a dry run; the log streams progress; each file's outcome is marked on the file list above, and the averaged-metrics table fills in below.

Because the real workflow loops rather than runs once, you will often return here after adjusting a parameter — the results row for any file drills back into Preview & QC on a double-click, so tune → re-run is a single continuous cycle.

Run batch vs. Dry run

Two buttons start a run:

  • Run batch — processes every matching recording and writes all enabled outputs to the output folder (workbooks under data/, diagnostic PDFs under diagnostics/, and the provenance files at the top).
  • Dry run (no files written) — runs the identical computation but writes nothing to disk. Use it to confirm the batch computes cleanly, check breath counts per file, and read the pre-flight plan before you commit any files.

Both buttons are disabled while the setup is incomplete: the status line reads “Setup incomplete: …”, the summary above the buttons lists the reason as a full sentence, and each button's tooltip repeats it. They are disabled the same way while a run is already in flight. When a run starts, Cancel becomes active. Setup's controls grey out and the header Analysis menu locks, so the settings cannot change underneath the running batch; on Preview & QC the two write actions lock too, so clicking a breath answers “Breath selection is locked while a run is in progress.” and “Process & write this file” greys out with the tooltip “Locked while a run is in progress.” Browsing, zooming and switching files stay available throughout.

Do a dry run first. A dry run is the fastest way to catch a mis-picked folder, an empty file mask or a file that fails to parse: it surfaces the full read/write plan and per-file breath counts without leaving anything on disk. In the app, both Run batch and Dry run additionally prove the output folder is usable by creating, writing and removing a temporary file there, and refuse to start with “Cannot run: cannot write to the output folder: …” if it is not. The command line has no such check: respmech validate and respmech run --dry-run never touch the output folder, so a read-only destination or a full disk only shows up after a real run has computed the whole batch. On the CLI, write to a folder you know you own.
The batch reads a frozen snapshot. The moment a run starts, RespMech deep-copies the current settings, so any edit you make mid-run cannot change what the worker processes. What you launched is what gets written.

Pre-flight plan & overwrite guard

Every run — real or dry — logs a pre-flight plan first, so a run is never a black box. The plan names the mode, lists every input file that matched, and lists every output file that will (real run) or would (dry run) be written:

── RUN — writing output. ──
Input:  3 files matching '*.csv' in /study/input
    • P03_120W.csv
    • P03_rest.csv
    • P07_120W.csv
Grouping: 3 files → 2 groups · P03 (2) · P07 (1)
Output: Will write up to 29 files into /study/output:
    • data/P03_120W.csv.breathdata.xlsx
    • …
    • data/Average breathdata.xlsx
    • data/Cohort summary.xlsx
    • diagnostics/P03_120W.csv – Campbell (average).pdf
    • …
    • diagnostics/All files – Campbell (average).pdf
    • run-report.txt
    • analysis-used.toml

The planned list is built from your Setup output tickboxes, in this order: a per-file <file>.breathdata.xlsx when the breath-by-breath workbook is on, a <file> – Processed data.csv when the processed CSV is on, Average breathdata.xlsx together with Cohort summary.xlsx when the average workbook is on, every diagnostic figure the ticked figure boxes imply (including the cohort-wide All files – Campbell (average).pdf, which follows the “Campbell / PV diagram — individual breaths” box and only for a batch of more than one file), any EMG WAV exports, and finally run-report.txt and analysis-used.toml, which are always emitted. The Grouping line is shown only when the average workbook is on. The count is a ceiling (“up to N”), not a promise: a file can fail, and some figures depend on the data (the volume-trend figure is only drawn when trend correction actually finds anchors; EMG stage figures need the stage to have been computed). Up to ten input files every path is listed; beyond that the log summarises the plan by category and Show full output plan… opens the complete list.

A real run warns before overwriting. If the output folder already holds results, Run batch asks first: the dialog “Output folder already has results” names what is already there (provenance files, data workbooks/CSVs, diagnostic figures and audio exports) and when it was written, then says how many of those files this run will replace and how many from an earlier run will be left alone. It offers a “Clear previous results first” tickbox, which empties data/ and diagnostics/ before the run starts, and takes No as the safe default. Without that tick, only same-named files are replaced and everything else in the folder is left exactly as it is, so point the output folder at a per-study or per-session location. A Dry run never writes, so it is never gated by this prompt. A single-file write, or Re-run failed when it covers only some of the matching files, gets its own dialog (“Write a subset into existing results”): the per-file outputs of those files are written or replaced, while the study-wide Average breathdata.xlsx, Cohort summary.xlsx and (when enabled) the cohort Campbell figure are left untouched — run the full batch to refresh them.
Provenance is always written. A full run writes analysis-used.toml (the exact, reloadable settings) and run-report.txt (what was read, kept, excluded and written, plus ECG/noise diagnostics) regardless of the output tickboxes, so a result folder always carries its own recipe. A partial run — “Process & write this file” from Preview, or Re-run failed when it covers only some of the matched files — never overwrites that record. Its report is always written as run-report (partial, <timestamp>).txt. Its settings snapshot is written as plain analysis-used.toml only when the folder has none yet; if one already stands and differs, the partial run's settings are written beside it as analysis-used (partial, <timestamp>).toml, and if they are identical nothing new is written. The study-wide outputs — Average breathdata.xlsx, Cohort summary.xlsx and the cohort Campbell figure — are deliberately left untouched by a partial run, so after tuning one recording, run the full batch before you read or publish them. Open the analysis snapshot later to reproduce a run exactly.

Progress & live log

While the batch computes, the progress bar fills once across the whole batch and the status line reads e.g. “File 2 of 12 — P03_120W.csv: breath 14/22”. Once the analysis is done and writing starts, Cancel greys out (writing cannot be interrupted) and the status line ticks “Writing output… 23s, cannot be interrupted” and then “Writing diagnostic figures — 3 of 12 files (P03_120W.csv) · 1m 04s elapsed · ~40s left”. The scrolling log records each file starting, each processing stage, and a per-file done line, then a summary (Finished: N ok, M failed, the chosen noise prop_decrease against its fidelity target, each failed file, and each file's breath count and ECG suppression), and finally what was written by category.

The status line reports the outcome distinctly. Besides the ordinary “Finished: N ok, M failed — wrote K files to <folder> in <time>.” and “Dry run complete — nothing written.”, you may see:

  • No file processed — “Finished — no file could be processed; nothing usable was written.” The batch ran to the end but every file failed; open the error log for the reason per file.
  • Cancelled — “Run cancelled — no output written.”
  • Cancelled too late — “Finished writing; the cancel arrived after the analysis, so the output is complete.” The write phase cannot be interrupted; a cancel that lands inside it is acknowledged, not obeyed.
  • Write failed — “Analysis complete, but writing failed — …” The numbers computed correctly; only the file write failed (e.g. a permissions or disk problem on the output folder).
  • Run failed — “Run failed — …” The batch stopped before completing.

Cancelling leaves earlier results untouched, with one exception: if you ticked “Clear previous results first” in the overwrite prompt, everything then in the output folder's data/ and diagnostics/ subfolders was already deleted before the run started.

If any individual file fails, or the run fails outright, a separate copyable error-log window opens with the full detail for each failed file, so you can paste it into a bug report or a note to a colleague.

Results: per-file & averaged tables

On completion, each recording's outcome is marked on the shared file list and the averaged metrics fill in below.

Per-file outcomes live on the file list, not in a separate results table. Each row carries a glyph — ✓ processed, ✗ failed, • not yet run — plus a [N excl] badge when breaths were manually excluded, marked ↺ when those exclusions were carried over from a different recordings folder. Hovering a row gives the breath count, or the full error for a failed file. Selecting a row opens that recording in Preview & QC, which is where you were already looking. One list, filterable and sortable: a single failed file in a 25-file batch never means scrolling past every success to find it.

The averaged-metrics table below it shows the across-breath average — one row per recording, each cell the mean over that file's non-excluded breaths. It is the on-screen view of Average breathdata.xlsx, carrying the same mechanics, work-of-breathing, EMG and entropy columns in their native units — pressures in cmH₂O, tidal volume in L, flows in L·s⁻¹, work of breathing in J·min⁻¹, pressure–time products in cmH₂O·s·min⁻¹, and EMG amplitudes in a.u. (uncalibrated). Excluded breaths never enter these means.

The tables are read-only views. Column names in the workbooks are deliberately terse and their units live on a companion Units sheet rather than in the names — see the outputs reference for the full column list and unit rules. Nothing on this screen edits the written values. They are copyable, though: select a range and press Ctrl+C (⌘C on macOS) to put it on the clipboard as tab-separated text at full precision, with a header line when whole columns are selected, ready to paste into a spreadsheet. The same works in the per-breath table on the Mechanics tab.

Open output folder & Re-run failed

A summary line above the buttons states the file count, the planned output count and any blocking reason. Eight buttons act on the run:

  • Run batch, Dry run (no files written) and Cancel — described above.
  • Open output folder — opens the output folder in your system file browser. It is enabled only after a real write run has completed cleanly (a dry run writes nothing, so it stays disabled).
  • Re-run failed — re-processes only the files that failed in the last run, in the same mode (write or dry) as that run. It is enabled only when the previous run had failures. Use it after fixing the cause — a corrupt file, a wrong channel map — without re-processing the whole batch.
  • Show full output plan… — opens the complete pre-flight plan when it was too long to log in full.
  • Write results to another folder… — available when the analysis finished but writing its results failed; writes the same, already-computed results to a folder you choose, with no re-analysis. The one recovery path after a failed write.
  • Show run report — available once a run has written its own run-report.txt; opens it in a plain-English, copyable window.
Single-file writes come from Preview. If you only need to (re)process one recording, the “Process & write this file” button on the Preview Mechanics tab reuses this same run machinery — overwrite guard, provenance and all, subject to the partial-run naming above — restricted to that one file. The shared EMG noise profile is still built from the whole test, so the result matches a full batch.

Keyboard shortcuts

The desktop app's most-used actions have keyboard shortcuts, gathered here in one place:

ShortcutAction
Ctrl+NNew analysis
Ctrl+OOpen analysis…
Ctrl+SSave
Ctrl+Shift+SSave as…
Ctrl+1 / Ctrl+2Switch to the Setup / Preview & QC tab
Ctrl+WClose the window
Ctrl+[ / Ctrl+] (⌘[ / ⌘] on macOS)Step to the previous / next file in Preview & QC's file list
Alt+← / Alt+→Same as above — step to the previous / next file
PageUp / PageDownSame, while the file list has focus
Ctrl+C (⌘C on macOS)Copy the selected range of a results table to the clipboard as tab-separated text

Running headless (CLI)

The Run & results drawer mirrors the command-line batch runner, which is handy for scripted or server-side processing. Given a saved analysis (a .toml settings file):

respmech run settings.toml

processes the batch and writes all enabled outputs, printing Wrote N file(s) to <output.folder>/data. Add --dry-run to compute without writing anything: it prints the same output plan the desktop app's Dry run button shows (one line per output category with its file count and destination folder, plus a total), then one <file>: <N> breaths line per file. Unlike the app, the CLI's plan does not list the individual matched input files or the full list of output paths:

respmech run settings.toml --dry-run

A non-zero exit code from run means one or more files failed; the failures are listed on standard error — the CLI equivalent of the Re-run failed list and the error-log window.

Command line & scripting

RespMech is driven by a plain-text settings file (TOML) and a small command-line tool, respmech, with three subcommands: run (process a batch), validate (check the settings and count the input files), and migrate (convert a legacy v1 .py settings file to TOML). Batch processing is first-class and scriptable — you never edit Python to launch a run — and respmech run returns a non-zero exit code if any file fails, so it drops cleanly into shell scripts and CI. The same settings file also underpins the desktop app, so anything you build on the command line opens unchanged in the GUI and vice versa.

The TOML file is declarative data, not code. Unlike the legacy .py settings, nothing in it is executed on load — it is read with the Python standard library, every field has a real default, and a missing subsection can never crash a run. That makes it safe to share, comment, and diff in version control.

Installing the CLI

RespMech requires Python 3.11 or newer (the loader uses the standard-library tomllib). Install from PyPI:

pip install respmech             # CLI + analysis engine
pip install "respmech[emg]"      # + spectral EMG noise reduction (librosa)
pip install "respmech[plots]"    # + diagnostic PDF figures (matplotlib)
respmech --version               # prints: respmech <version>

The base install computes every numeric result and writes the Excel/CSV workbooks. The two extras are only needed for optional stages: [emg] enables spectral EMG noise reduction (processing.emg.noise.enabled = true), and [plots] enables the diagnostic PDF figures (the Campbell diagrams, raw/trimmed/drift overviews, EMG overviews).

The two extras fail differently, and neither is caught by respmech validate. Without respmech[emg], a run with processing.emg.noise.enabled = true stops outright while building the shared noise profile, with the bare Python message error: No module named 'librosa' and exit code 2. Without respmech[plots], the run succeeds: every workbook is written and the exit code is 0, but a WARNING: N diagnostic figure(s) skipped … line is printed to stderr and the FIGURES SKIPPED section of run-report.txt lists each figure and the reason. Check that section whenever you expect figures.

The three commands

A subcommand is always required. Run respmech --help or respmech <cmd> --help for the argparse usage summary.

Process a batch
respmech run settings.toml
Default —
Unit / values add --dry-run
Load → validate → run the batch → write workbooks to <output.folder>/data/. Streams per-file progress and a live breath counter, then prints Wrote <N> file(s) to <output.folder> (the count covers every file the run wrote: data/, diagnostics/ and the two provenance files, not just data/).
Dry run
respmech run settings.toml --dry-run
Default —
Unit / values flag
Runs the full computation but writes nothing. Prints the full output plan (the same ceiling the desktop app's Dry run shows: data/, diagnostics/, provenance, each group's file count), then one <file>: <N> breaths line per file. Ideal for tuning segmentation or smoke-testing settings.
Validate settings
respmech validate settings.toml
Default —
Unit / values —
Load → validate → count the matching input files with the same case-insensitive matcher run uses, report any unrecognised settings key, and probe that the output folder is actually writable (a real, if harmless, write-and-remove — never os.access). Prints Settings valid. Input pattern '<folder/mask>' matches <N> file(s). No computation of the recordings themselves. Run this first.
Migrate legacy .py
respmech migrate old.py -o new.toml
Default —
Unit / values -o required
Parse a legacy v1 .py (without executing it), map/rename/normalise the keys onto the v2 model, validate, write the TOML, then print a migration report. See Migrating a legacy .py.
Version
respmech --version
Default —
Unit / values —
Prints respmech <version> and exits.

The recommended end-to-end sequence is: migrate (if you have a legacy file), validate, dry-run, then a real run.

respmech migrate old_settings.py -o settings.toml   # 1. convert legacy .py → TOML
respmech validate settings.toml                      # 2. check settings + file count
respmech run settings.toml --dry-run                 # 3. compute, write nothing
respmech run settings.toml                           # 4. process + write workbooks

A real run writes into output.folder: data/Average breathdata.xlsx, one data/<file>.breathdata.xlsx per input, an optional data/<file> – Processed data.csv, data/Cohort summary.xlsx, plus a run-report.txt and an analysis-used.toml in the folder root. The data/ subfolder is created automatically.

analysis-used.toml is a per-run manifest recording the exact settings a run used, with absolute paths, for reproducibility. It is deliberately not a portable template — do not copy it to another machine. Keep editing your hand-authored settings.toml (whose folders stay relative, see below).

Exit codes for scripting

Every command returns a POSIX exit code so RespMech composes with shell scripts and CI pipelines. There are three, and codes 1 and 2 differ in kind — a script that only tests != 0 conflates them.

Success
0
Default —
Unit / values run / validate
run processed every matched file; validate found the settings valid and matched at least one input file.
Handled failure
1
Default —
Unit / values run / validate
run had at least one file FAIL (the failures are listed on stderr; the other files were still computed); or validate matched zero input files, found an unrecognised settings key, or found the output folder unwritable (each printed as its own WARNING: on stderr).
Unhandled error
2
Default —
Unit / values any command
An exception escaped the command — a missing or malformed TOML, a parse error, a SettingsError from validation (e.g. missing sampling_frequency or a required channel), or a migrate on a non-literal .py. Printed as error: <message> on stderr.

validate can print Settings valid. and still exit 1: the settings can be internally valid yet match no files because of a mask typo or a wrong folder. Treat a zero-match validate as a red flag before you launch a run.

The settings file (TOML)

A settings file has a top-level schema_version tag and nested tables under [input], [processing], and [output]. The structural keys the run depends on — where to read, what the columns mean, and where to write — live in [input] and [output]; the processing depth (mechanics, EMG, PTP) is documented in its own sections and shown in the annotated example below.

Schema version
schema_version
Default 2
Unit / values 2
Version tag of the file layout. Emitted automatically (currently 2) — never edit by hand. Omitting it is safe: the file is then read as the current schema, not the oldest. A file that declares 1 is upgraded on load — the retired absolute trend_peak_min_height default is cleared in favour of the fraction-of-range criterion, and the upgrade is reported rather than applied silently.
Input folder
input.folder
Default "input"
Unit / values path
Folder holding the recordings. A relative path is resolved against the TOML file's own directory (not the shell's working directory), which keeps a shared analysis portable when moved between machines.
File mask
input.files
Default "*.*"
Unit / values glob, e.g. "RIU_H1*.txt"
Glob selecting which files to process. Matching is case-insensitive on every OS. The mask may carry a subdirectory (raw/*.csv); leading-dot files are skipped unless the mask itself starts with a dot.
Sampling frequency
input.format.sampling_frequency
Default (none — REQUIRED)
Unit / values Hz (samples/s)
Samples recorded per second. Every time-based window (RMS, ECG, PTP) is converted from seconds to samples using it. Must be an integer; a missing or non-integer value raises SettingsError (exit 2). All files in a batch share this rate.
MATLAB file variant
input.format.matlab_variant
Default "mac"
Unit / values "windows" | "mac"
Byte/format variant used when reading MATLAB .mat inputs; match the platform that exported them. Ignored for text/CSV/Excel. Legacy 1 → "windows", 2 → "mac".
Decimal character
input.format.decimal
Default "."
Unit / values "." | ","
Decimal separator for numeric text inputs. Set to "," for European-locale CSV/text exports. Only relevant to delimited text formats.
Flow channel
input.channels.flow
Default (none — REQUIRED)
Unit / values column no. (1-based)
Column carrying respiratory flow. Required.
Volume channel
input.channels.volume
Default (REQUIRED unless integrating)
Unit / values column no. (1-based)
Column carrying inspired volume (BTPS). Required unless processing.volume.integrate_from_flow = true, in which case volume is derived from flow and this may be omitted.
Oesophageal pressure
input.channels.poes
Default (none — REQUIRED)
Unit / values column no. (1-based)
Column carrying oesophageal pressure (Poes). Required.
Gastric pressure
input.channels.pgas
Default (none — REQUIRED)
Unit / values column no. (1-based)
Column carrying gastric pressure (Pgas). Required.
Transdiaphragmatic pressure
input.channels.pdi
Default (none — REQUIRED)
Unit / values column no. (1-based)
Column carrying transdiaphragmatic pressure (Pdi). Required.
EMG channels
input.channels.emg
Default []
Unit / values list, e.g. [6,7,8]
1-based columns holding EMG signals to compute RMS from. An empty list disables all EMG processing.
Entropy channels
input.channels.entropy
Default []
Unit / values list, e.g. [6,7,8]
1-based columns to run sample-entropy on (usually the same as EMG). Empty disables entropy. Independent of the EMG list.
Exclude breaths
processing.exclude_breaths
Default []
Unit / values tables {file, breaths}
Per-file breath numbers (1-based) to drop as artefacts. Excluded breaths are always left out of the averages and workbooks.
Breath counts override
processing.breath_counts
Default []
Unit / values tables {file, count}
Per-file manual override of the auto-detected breath count. Use when an irregular pattern yields a wrong count that would skew rate-scaled outputs (VE, WOB·min⁻¹, PTP·min⁻¹).
Output folder
output.folder
Default "output"
Unit / values path
Root for results; workbooks go to <folder>/data. A relative path is resolved against, and saved back relative to, the TOML file's directory. The data/ subfolder is created automatically.
Cohort group regex
output.group_regex
Default null (none)
Unit / values regex
Regex whose first capture group is the subject/condition key for the cohort summary. If null, the leading filename token is used (e.g. P03_120W → P03).
Save average data
output.data.save_average
Default true
Unit / values true|false
Write the averaged per-file breath-data workbook. Core output.
Save breath-by-breath
output.data.save_breath_by_breath
Default true
Unit / values true|false
Write the per-breath workbook. Turn off to reduce output volume.
Save processed data
output.data.save_processed
Default false
Unit / values true|false
Write the full processed input signal as a CSV per file. Enable for auditing; produces large files.

Two conventions worth remembering

Column numbers are 1-based under input.channels.* (matching LabChart exports) — but processing.emg.detect_channel is a 0-based index into the emg list, not a raw column number. These are easy to conflate.

TOML has no null, and unknown keys never fail a load. Omitting a key and setting it to its default are equivalent (on save, None-valued keys are dropped). A misspelled key is not rejected, but it is not silently ignored either: respmech validate and the UNKNOWN SETTINGS KEYS section of run-report.txt both name it, alongside the default that was used instead of the value you meant to set.

An annotated settings.toml

A realistic file covering the common keys. Comments explain each block; defaults are shown so you can see what you are (or are not) overriding. Note the use of a TOML literal string (single quotes) for the regex so backslashes are not escaped.

schema_version = 2

[input]
folder = "input"            # recordings to analyse; relative → resolved against THIS file's folder
files  = "RIU_*.txt"        # case-insensitive glob; may carry a subfolder, e.g. "raw/*.csv"

[input.format]
sampling_frequency = 2000   # Hz — REQUIRED, integer; every time window is derived from this
matlab_variant     = "mac"  # "windows" | "mac"; only affects .mat inputs
decimal            = "."    # use "," for European-locale text/CSV

[input.channels]            # 1-based column numbers (LabChart convention);
                            # column 1 is the time axis, so signals start at 2
flow    = 2                 # REQUIRED
volume  = 3                 # REQUIRED unless processing.volume.integrate_from_flow = true
poes    = 4                 # REQUIRED — oesophageal pressure
pgas    = 5                 # REQUIRED — gastric pressure
pdi     = 6                 # REQUIRED — transdiaphragmatic pressure
emg     = [7, 8, 9]         # EMG channels; [] disables all EMG processing
entropy = [7, 8, 9]         # sample-entropy channels; [] disables entropy

[processing.segmentation]
method = "flow"             # "flow" | "volume" — which signal detects breath boundaries
buffer = 800                # samples averaged for cycle detection (scale with fs)

[processing.segmentation.peak]
height     = 0.1            # min peak height, in the segmentation signal's units
distance_s = 0.1            # s — min time between peaks
width_s    = 0.5            # s — min peak width

[processing.volume]
integrate_from_flow = false # true → derive volume from flow (volume channel becomes optional)
correct_drift       = true  # remove slow baseline drift (ON by default)
correct_trend       = false # also remove a between-breath trend in end-expiratory volume
trend_method        = "linear"  # scipy interp1d kind for the trend envelope
trend_peak_min_prominence_frac = 0.05  # trough depth to anchor on, as a fraction of
                                       # THIS recording's volume range (scale-free)
trend_peak_min_distance_s      = 0.4   # s — min time between two anchors
# trend_peak_min_height        = 0.8   # legacy ABSOLUTE depth below the recording's
                                       # highest volume. Omit it (recommended): a fixed
                                       # value larger than the volume range finds no
                                       # troughs and the correction cannot run.

[processing.wob]
calc_from          = "average"  # "average" (classic Campbell) | "individual" (per breath)
avg_resampling_obs = 500        # samples each phase is resampled to for P–V averaging (~ fs ÷ 8–10)

[processing.emg]
rms_window_s  = 0.050            # s — sliding RMS window (effective length fs·rms_window_s − 1 samples, trailing, not centred)
remove_ecg    = false           # subtract cardiac contamination before RMS
detect_channel = 0              # 0-based INDEX into the emg list above (not a column number)
normalization = "per_file_max"  # "none" | "per_file_max" | "per_file_mean" (adds a normalised sheet)

[processing.emg.noise]
enabled = false                 # spectral STFT noise reduction; needs the [emg] extra (librosa)

[processing.ptp]
baseline_window_s = 0.05        # s — window whose mean is the pressure–time-product baseline

# --- Per-file overrides (zero or more of each) ---
[[processing.exclude_breaths]]
file    = "RIU_H1_120W.txt"
breaths = [1, 2]                # 1-based breath numbers; always dropped from averages/workbooks

[[processing.breath_counts]]
file  = "RIU_H1_120W.txt"
count = 12                      # override the auto-detected breath count for this file

[output]
folder      = "output"          # workbooks land in <folder>/data
group_regex = '^(P\d+)'         # first capture group = cohort key; omit → leading filename token

[output.data]
save_average          = true
save_breath_by_breath = true
save_processed        = false   # large per-file processed-signal CSV; off by default

[output.diagnostics]
save_pv_average = true          # averaged Campbell diagram; needs the [plots] extra
save_emg        = true          # per-channel EMG overviews (raw / ECG-removed / noise-reduced)

Check your column numbers against the first row of your own export. The desktop app blocks a required channel pointed at column 1 (the time axis), but respmech validate does not: an off-by-one channel map validates cleanly and produces a completed run with meaningless numbers.

Relative folders are relative to the TOML file, not the shell. This is what keeps a moved or shared study working (and stops a packaged app from writing into its install directory), but it surprises anyone expecting shell-relative behaviour. On save, an absolute folder that lives at or under the file's directory is re-written relative to it; folders elsewhere (or on a different Windows drive) stay absolute.

Migrating a legacy .py

RespMech v1 configured runs by editing a Python .py file that assigned a settings = {…} dictionary. respmech migrate converts one to a v2 TOML:

respmech migrate old_settings.py -o settings.toml

The legacy file is never executed. The migrator locates the settings = {…} assignment in the parsed syntax tree and evaluates it with ast.literal_eval, which accepts only plain literals (numbers, strings, lists, dicts). Anything else — an import, a function call, a computed value — raises rather than running (exit 2). This is safe to point at a file you did not write.

Migration does three things and reports each under its own heading: it prints Wrote <path>, then a report with Mapped (keys renamed / re-homed), Normalised (genuine legacy bugs fixed), and Dropped (keys that were defined but never actually read by the v1 code). Representative renames:

  • input.inputfolder → input.folder; output.outputfolder → output.folder
  • input.data.column_* → input.channels.*
  • format.matlabfileformat (1|2) → matlab_variant ("windows"|"mac"); format.samplingfrequency → sampling_frequency
  • mechanics.separateby → segmentation.method; breathseparationbuffer → segmentation.buffer
  • emg.rms_s → emg.rms_window_s; emg.column_detect → emg.detect_channel; emg.windowsize → emg.ecg_window_s
  • excludebreaths and breathcounts (lists of pairs) → the [[processing.exclude_breaths]] / [[processing.breath_counts]] tables

Two normalisations fix silent v1 bugs. First, calcwobfromaverage / avgresamplingobs were read by the code under wob.* but placed under [mechanics] in the example files, so they were quietly ignored — the migrator maps them to wob.calc_from / wob.avg_resampling_obs where they take effect. Second, the legacy per-file noise profiles are consolidated into one shared processing.emg.noise profile with a fixed n_fft = 256, dropping the old n_fft = len(noise)**2 bug.

Always run respmech validate on the migrated file and skim the report. Because the two normalisations change which settings are actually honoured, a migrated run can legitimately differ from the legacy run — that difference is the v1 bug being corrected, not a regression. Confirm the results look right before trusting the batch.

Breath segmentation & volume

Before any mechanics are computed, RespMech turns a raw recording into a clean sequence of whole breaths. It trims the record so it starts and ends on breath boundaries, conditions the volume trace (zeroing, drift and optional trend removal), and then walks the signal to mark where each breath begins and ends. This page covers those steps, the settings that control them, and how you drop individual breaths or override the breath count used for per-minute scaling. Every step here runs on the signed, conditioned signals and assumes RespMech's two sign conventions: inspiration reads negative flow, and inspired volume is positive.

Sign conventions drive everything below. Trimming, flow-based segmentation and volume integration all depend on inspiration being negative flow. If your rig records inspiration as positive flow, turn on processing.volume.inverse_flow rather than editing the data. A reversed sign does not raise the trim error: flow still crosses zero, so trimming and breath segmentation simply lock onto the wrong phase, and every inspiratory and expiratory number is silently mislabelled while the run still looks healthy. The message “the flow signal never crosses zero as required” appears only when the flow column has no sample ≤ 0 or no sample ≥ 0 — a strictly one-signed column, typically a wrong channel.

Trimming to whole breaths

A recording almost never starts and ends exactly on a breath boundary. RespMech discards the leading partial expiration and the trailing partial inspiration so the analysis window contains only complete breaths. It finds the first inspiration onset and the last end of expiration in the flow signal, then slices every channel (flow, volume, all pressures, EMG, entropy) to that window so they stay sample-aligned.

startix = first i where flow[i] < 0 endix = last i where flow[i] ≥ 0
all channels ← channel[startix : endix]

This imposes a precondition on the raw file: it must start in late expiration and end in early inspiration. If the flow signal never crosses zero — or the computed window is empty — RespMech raises a TrimError that names the flow channel and the inverse_flow setting, because a wrong flow sign is by far the most common cause.

If a file fails to load with a trim error, check three things in order: (1) the flow channel points at the right column, (2) inverse_flow matches your rig's polarity, and (3) the recording genuinely brackets whole breaths. Trimming discards, at most, one partial expiration at the start and one partial inspiration at the end.

Splitting the record into breaths

Once trimmed, the record is divided into individual breaths. A breath is always one inspiration plus the expiration that follows it. RespMech offers two detection methods — flow (the robust default) and volume (for noisy flow with clean volume peaks) — selected by processing.segmentation.method.

A flow trace divided into numbered breaths, each shaded as an inspiration-plus-expiration span, with one breath drawn in red to show it has been excluded from the averages.
Detected breaths, numbered in acquisition order. Each shaded span is one inspiration plus the following expiration. The red span is a breath the user marked as excluded (see Excluding breaths) — still drawn and numbered, but dropped from the averages.

By flow (default)

Walking the trimmed signal, an inspiration is the run where flow is negative, and the following expiration is the run where flow is positive. To stop brief zero-crossing wobble from chopping one breath into several, a phase is allowed to continue as long as the forward mean of flow over the next buffer samples keeps the phase's sign:

phase continues while flow[i] has the phase sign OR mean(flow[i : i+buffer]) has the phase sign

The buffer is measured in samples, not seconds. Its effective smoothing window therefore scales with sampling frequency: 800 samples is 0.4 s at 2000 Hz but 0.8 s at 1000 Hz. Re-tune buffer whenever you change the sampling frequency or enable resampling.

By volume

With method = "volume", RespMech instead runs scipy.signal.find_peaks on the inspired volume to locate end-inspiration, and on the inverted volume to locate end-expiration. Detection is gated by three thresholds — a minimum peak height (in litres), a minimum time between peaks and a minimum peak width (both in seconds, converted internally to seconds · fs samples). This method relies entirely on the peak.* settings and usually needs tuning per recording; the flow method needs only the buffer.

Signal used to split breaths
processing.segmentation.method
Default flow
Unit / values "flow" | "volume"
Which signal marks breath boundaries. Keep flow for most recordings; switch to volume only when the flow zero-crossing is noisy but the volume peaks are clean.
Breath-separation buffer
processing.segmentation.buffer
Default 800
Unit / values samples
Forward look-ahead that tolerates zero-crossing wobble (flow method only). Increase if single breaths are being fragmented by flow noise near zero; decrease if adjacent breaths are being merged. Units are samples, so re-tune it after any change to fs.
Breath peak — minimum height
processing.segmentation.peak.height
Default 0.1
Unit / values L
Minimum volume-peak height for the volume method. Raise to ignore small non-breath excursions; lower to catch shallow breaths. Ignored under flow segmentation.
Breath peak — minimum distance
processing.segmentation.peak.distance_s
Default 0.1
Unit / values s
Minimum time between detected peaks (volume method). Set near the shortest expected breath period to prevent double-detection within one breath. Ignored under flow segmentation.
Breath peak — minimum width
processing.segmentation.peak.width_s
Default 0.5
Unit / values s
Minimum width of a detected peak (volume method). Raise to reject narrow spikes; lower for very short breaths. Ignored under flow segmentation.

Volume: reading it or integrating from flow

Volume can come from a recorded channel or, when none exists, be derived by integrating flow. With integrate_from_flow on, RespMech computes the negative cumulative trapezoidal integral of flow using the file's true sampling rate (even when pre-analysis resampling is active), so the sign convention "inspired volume positive" holds:

volume(t) = −∫ flow dt (via cumulative_trapezoid)L = L·s⁻¹ · s

Sign flips are applied at load time in a fixed order: invert flow (if set) → integrate volume from flow (if no volume channel) → invert volume (if set). Because inversion of flow happens first, it also flips the sign of any flow-derived volume. At analysis time the volume trace is then zeroed to its first sample before drift and trend correction:

V ← V − V[0]
Calculate volume from flow
processing.volume.integrate_from_flow
Default false
Unit / values true/false
Derives volume by integrating flow instead of reading a volume channel. Turn on when no volume channel was recorded; the volume channel may then be left unset. Integrated volume tends to accumulate baseline drift, which is why correct_drift is on by default.
Invert the flow signal
processing.volume.inverse_flow
Default false
Unit / values true/false
Negates flow at load time so inspiration reads negative. Turn on if your rig records inspiration as positive flow. Applied before integration, so it also flips flow-derived volume. Getting this wrong makes trimming fail or produces upside-down breaths.
Invert the volume signal
processing.volume.inverse_volume
Default false
Unit / values true/false
Negates volume at load time so inspired volume is positive. Turn on if your volume trace decreases on inspiration. Applied after any flow-integration step.

Drift and end-expiratory trend correction

Integrated and thermally-sensitive volume traces wander over time. RespMech applies up to two corrections to the zeroed volume, in order: a linear drift removal (on by default) and an optional between-breath trend removal.

Linear drift correction

correct_drift removes a straight-line baseline slope. It measures the slope from the first and last samples and subtracts a matching ramp, which leaves the very last sample pinned at zero — a deliberate boundary behaviour preserved for byte-for-byte reproducibility of the validated reference results:

a = (V[N−1] − V[0]) / (N − 1)L·sample⁻¹
V′[i] = V[i] + a · (1 − i) (linear de-trend; ramp starts at +a, V′[N−1] = 0)L
Four stacked panels showing the volume-correction stages of one recording: flow, uncorrected volume sinking off its baseline across the run, the same trace zeroed, and finally linear drift-corrected so every breath starts from a common end-expiratory baseline with the tidal excursions preserved.
Volume drift correction. Before (top): a slow linear baseline slope, typical of flow-integrated volume, carries every breath progressively off baseline. After (bottom): the linear de-trend removes the slope while preserving each breath's tidal excursion. Tidal volume is max(V) − min(V) within a breath, so a mis-corrected baseline distorts VT and the P–V representation.

End-expiratory trend correction

When the operating volume itself wanders across a run (for example during exercise, as end-expiratory lung volume shifts), a single straight line is not enough. correct_trend detects the end-expiratory points breath by breath, interpolates an envelope through them, and subtracts it so every breath is referenced to a common end-expiratory level.

A sample anchors the envelope when it is a local minimum of the volume, is at least trend_peak_min_distance_s from the previous anchor, and is deep enough: its prominence — the smaller of the two inspiratory excursions on either side of it, which for a normal breath is about one tidal volume — must be at least trend_peak_min_prominence_frac of the recording's own volume range. Because the threshold is a fraction of that range rather than a fixed number of litres, the same setting works for quiet tidal breathing and for maximal exercise, and in any volume unit. The recording's own first and last samples are considered as anchors too — an edge is never a local maximum, so the peak search can never return one, and including them stops the envelope extrapolating past the outermost detected trough. They are accepted only when they really sit at an end-expiratory level: trimming ends the window at the last sample where flow is still outward, which is in expiration rather than at end-expiration, so a recording stopped mid-breath has an end part way up a breath. Anchoring there would invent a trend and distort that breath, so such an end is dropped.

anchor if prominence(trough) ≥ trend_peak_min_prominence_frac · (max V − min V)L

All of these live under Preview & QC → Mechanics → Advanced…, where the dialog also reports how many troughs the current settings find in the previewed file. If a recording yields too few anchors for the chosen interpolation, RespMech stops on that file and says so, rather than writing numbers derived from an undefined envelope.

Correct volume drift
processing.volume.correct_drift
Default true
Unit / values true/false
Removes a slow linear baseline slope from the trimmed volume. Leave on for typical recordings (especially flow-integrated volume); turn off only to inspect the raw trace or when your volume channel is already stable.
Correct end-expiratory trend
processing.volume.correct_trend
Default false
Unit / values true/false
Subtracts an interpolated envelope through end-expiratory volume troughs, removing a between-breath drift in end-expiratory lung volume. Turn on when operating volume wanders across a trial. Also emits a diagnostic plot.
Trend interpolation
processing.volume.trend_method
Default linear
Unit / values linear | nearest | nearest-up | zero | slinear | quadratic | cubic | previous | next
The scipy interp1d kind used to interpolate the trend envelope between detected troughs. Switch from linear to a smoother kind (e.g. cubic) if the trend is curved. Must be a valid interp1d kind or validation fails; only used when correct_trend is on.
Trend anchor — minimum breath depth
processing.volume.trend_peak_min_prominence_frac
Default 0.05
Unit / values fraction of the volume range
How deep a trough must be, relative to this recording's own volume range, to anchor the trend envelope. Measured per trough (the smaller of the two inspirations flanking it, i.e. about one tidal volume), so it needs no tuning at any tidal volume or in any volume unit. Lower it only if breaths are missed on a recording that also contains a large manoeuvre. Only used when correct_trend is on.
Trend anchor — absolute threshold (legacy)
processing.volume.trend_peak_min_height
Default unset (auto)
Unit / values volume units (L)
A fixed depth below the recording's highest volume that a trough must reach. Set it only to reproduce an analysis made before v2.3.3 — because it is measured against the whole recording rather than per breath, a value larger than the recording's volume range matches no trough at all and the correction cannot run. Leave unset to use the breath-depth rule above.
Trend anchor — minimum spacing
processing.volume.trend_peak_min_distance_s
Default 0.4
Unit / values s
Minimum time between end-expiratory troughs (converted to seconds · fs samples). Increase to pick one trough per breath. Only used when correct_trend is on.

Optional pre-analysis resampling

When you switch it on, RespMech brings every loaded channel — the 1-D signals and the 2-D EMG/entropy matrices — onto one common sample rate before trimming, segmentation and compute. It uses anti-aliased polyphase resampling, with the up/down ratio reduced to lowest terms:

up / down = f_out / f_in (reduced by their GCD)
x_out = resample_poly(x, up, down)

The file is always loaded at its true rate first — so flow-to-volume integration still uses the real sampling frequency — and only then resampled; all downstream computation, including the shared EMG-noise profile, runs at the new rate. Keeping a single common rate preserves one time base, so flow-based segmentation and the sample-indexed EMG stay consistent. The EMG-noise STFT window is auto-scaled to preserve its time extent at the new rate.

Resample before analysis
processing.sampling.resample
Default false
Unit / values true/false
Resamples every channel to one common rate up front. Turn on to analyse a batch recorded at mixed rates, or to standardise the time base across a cohort. Off by default, so results are byte-identical to no resampling.
Resample to
processing.sampling.resample_to_frequency
Default 200
Unit / values Hz
Target common rate applied when resampling is on; becomes the analysis fs for all timing, PTP and segmentation. Has no effect unless resample is true and the target differs from the file rate.

Keep the target ≥ ~1000 Hz when EMG channels are present. The 200 Hz default is fine for mechanics but too low for EMG. Remember that buffer is measured in samples — enabling resampling changes its effective duration, so re-tune it.

Excluding breaths & breath-count overrides

Two related controls let you clean up the averages and correct per-minute scaling. They serve different purposes and are easy to confuse.

Excluding breaths

exclude_breaths marks named breaths in a named file as ignored: they remain drawn and numbered in the diagnostic plots but are dropped from the per-file average and per-breath workbooks. In the desktop app you exclude a breath by clicking its shaded region in the Mechanics preview (it turns red, as in the figure above under Splitting the record into breaths). Use it to drop swallows, coughs, sighs and other artefactual breaths. Excluded breaths appear in the processed-signal CSV only when output.data.include_ignored_breaths is on.

Breath-count overrides & per-minute scaling

Every per-minute quantity is scaled by a factor derived from the file's trimmed duration and the breath count:

vefactor = 60 / Tmin⁻¹ (T = trimmed file duration, s)
bf = count · vefactormin⁻¹
VE = VT · count · vefactorL·min⁻¹

By default count is the number of detected breaths — and that count includes breaths you marked as ignored. If you exclude breaths and care about absolute per-minute values (bf, VE, and the breaths·min⁻¹ factor on every PTP and WOB), set breath_counts to the true number of breaths in the epoch. It changes only the scaling factor, not which breaths are averaged.

Excluded breaths
processing.exclude_breaths
Default []
Unit / values per-file list of 1-based breath numbers
Marks named breaths as ignored: dropped from the averaged and per-breath workbooks, still drawn in plots. Keyed by file basename and stamped with the recordings folder it was made in — pointing the analysis at a different folder shows a Keep/Clear banner instead of silently dropping or silently reapplying the choice. In the app, click a breath in the Mechanics preview (red = excluded).
Breath-count overrides
processing.breath_counts
Default []
Unit / values per-file integer count
Overrides the breath count used for per-minute scaling of that file. Set the true breath count when detection over- or under-counts, or when you have excluded breaths, so bf, VE, PTP and per-minute WOB scale correctly. Keyed by file basename and folder tag, same as exclusions above; does not change which breaths are averaged.

Exclude vs. override in one line: excluding a breath removes it from the averages; a breath-count override corrects the per-minute scaling factor. Excluding breaths alone leaves the detected count unchanged, so pair the two when you need both clean averages and accurate VE / bf.

respmech validate settings.toml
respmech run settings.toml

Respiratory mechanics

For every breath it detects, RespMech computes the classic respiratory-mechanics descriptors: breath timing (inspiratory time, expiratory time, total cycle time and the duty cycle), tidal volume, breathing frequency and minute ventilation, plus a full set of oesophageal (Poes), gastric (Pgas) and transdiaphragmatic (Pdi) pressure descriptors. From these it derives the pressure–time products (PTP), an isovolume inspiratory lung-resistance estimate (tlr_insp) and a gastric/oesophageal pressure ratio (vmr). Results are reported breath-by-breath and as a per-file average, each number carried with its physical unit. The defaults reproduce the validated reference pipeline; the PTP columns differ from 1.x by the documented baseline change (see Changes from RespMech 1.x).

How a breath is defined

Everything on this page is computed on the signed, trimmed and conditioned signals. A single breath is one inspiration followed by the next expiration. The recording is first trimmed to whole breaths — the first sample where flow goes negative (onset of the first inspiration) to the last sample where flow is still positive (end of the last expiration) — so, provided the export window brackets whole breaths, the analysis begins and ends on a phase boundary.

Sign and unit conventions. Flow is negative on inspiration and positive on expiration; inspired volume is positive. Pressures are in cmH₂O, flow in L·s⁻¹, volume in L, and fs is the sampling frequency in Hz. If your acquisition system records inspiration as positive flow (or volume decreasing on inspiration), fix it with the sign toggles in signal conditioning — getting them wrong silently mislabels every inspiratory and expiratory quantity.

Trimming precondition. The recording must start in late expiration and end in early inspiration. Loading fails with a TrimError only if flow never crosses zero or the computed window is empty; an epoch cut mid-inspiration or mid-expiration loads without error but keeps a truncated breath and tilts the volume baseline (see Trimming to whole breaths). If a file refuses to load, check the flow channel assignment and the invert-flow setting first.

Breath timing

Timing is read straight off the sample counts of each phase. With n_insp, n_exp and n_total the number of samples in the inspiratory phase, the expiratory phase and the whole breath:

Ti = n_insp / fss
Te = n_exp / fss
Ttot = n_total / fs ( = Ti + Te )s
duty cycle = Ti / Ttotdimensionless

The duty cycle Ti/Ttot is the fraction of the cycle spent inspiring: the timing component of the classical drive-and-timing decomposition of ventilation, V̇E = (VT/Ti) × (Ti/Ttot), i.e. mean inspiratory flow scaled by the duty cycle (× 60 for the L·min⁻¹ RespMech reports), whose other factor VT/Ti (available from the vt and ti columns) is the drive component (Milic-Emili & Grunstein, Chest 1976;70:131–133). It is also the timing factor of the diaphragm tension–time index, TTdi = (Pdi/Pdi,max) × (Ti/Ttot) (Bellemare & Grassino, J Appl Physiol Respir Environ Exerc Physiol 1982;53:1190–1195). Because every timing number divides a sample count by fs, an incorrect sampling frequency rescales all of them; confirm fs before you trust any timing output.

A flow trace above a volume trace over two breaths, with the flow zero-crossings marked and the inspiratory (Ti) and expiratory (Te) phases and total cycle time (Ttot) bracketed on the time axis.
Ti, Te and Ttot read off the flow and volume traces. A breath is one inspiration (flow < 0) plus the following expiration (flow > 0); Ttot spans both.

Tidal volume, frequency & ventilation

Tidal volume is the peak-to-trough swing of the conditioned volume trace over the breath:

VT = max(volume) − min(volume)L

Breathing frequency and minute ventilation are put on a per-minute footing with a single scaling factor derived from the trimmed file duration. With N_trimmed the trimmed flow length and bcnt the number of detected breaths in the file:

vefactor = 60 / (N_trimmed / fs) = 60 / file_duration_smin⁻¹
bf = bcnt · vefactormin⁻¹
VE = VT · bcnt · vefactorL·min⁻¹

bcnt includes breaths you excluded. The per-minute scaling uses the detected breath count, which still counts breaths you marked as ignored in Preview & QC. If you exclude breaths and care about absolute bf/VE, set the true count per file with processing.breath_counts. The same · bcnt factor scales every PTP as well.

Pressures & tidal swings

From the oesophageal, gastric and transdiaphragmatic pressure channels, RespMech reports end-inspiratory, end-expiratory, maximum and minimum values, the mid-tidal-volume Poes points, and the phase rises and whole-breath tidal swings. Pdi is read as a supplied channel (Pdi = Pgas − Poes on the acquisition system); RespMech does not recompute it. All are in cmH₂O.

p*_tidal_swing = max(p) − min(p) over the breathcmH₂O
insp_pdi_rise = max(Pdi_insp) − min(Pdi_insp)cmH₂O
exp_pgas_rise = max(Pgas_exp) − min(Pgas_exp)cmH₂O

The mid-tidal-volume points (poes_midvolinsp, poes_midvolexp, and the matching flows) are sampled where each phase reaches half its tidal volume; they feed the isovolume resistance estimate below. See the full list in the output columns.

Pressure–time products (PTP)

The pressure–time product measures the area under a phase's pressure trace relative to a baseline at the phase start — the standard surrogate for respiratory-muscle energy expenditure that work of breathing (which needs volume) cannot capture during, for example, ineffective efforts. RespMech computes three: inspiratory oesophageal, inspiratory transdiaphragmatic and expiratory gastric.

For a phase pressure signal p, the baseline is the mean of a short window at the phase start (end-expiratory for the inspiratory phases), the signal is shifted onto that baseline, and the area is integrated over time with Simpson's rule:

b = mean(p[:n]), n = max(1, round(baseline_window_s · fs))cmH₂O
int = simpson(p − b, t), t = linspace(0, len(p)/fs, len(p))cmH₂O·s
ptp = int · bcnt · vefactorcmH₂O·s·min⁻¹

The integral is signed: any part of the phase where the shifted pressure lies below the baseline is subtracted, not clipped. For the inspiratory Poes and Pdi products this rarely matters, but the expiratory gastric product is referenced to Pgas at the start of expiration, so a passive expiration in which Pgas falls back towards its end-expiratory level gives a negative int_pgasexp / ptp_pgasexp; it turns positive only when expiratory abdominal recruitment holds Pgas above its end-inspiratory level. A negative value is a finding about the breath, not an error.

What the baseline means. The baseline is that breath's own pressure at the start of the phase, not a relaxation pressure. RespMech therefore measures the pressure the muscles generate above the level they started from. Where a threshold load is already present at end-expiration — intrinsic PEEP, dynamic hyperinflation — the effort spent overcoming it falls before the flow zero-crossing that defines the phase, and below the baseline, so it is not counted. If end-expiratory pressure drifts across the recording, each breath is referenced to its own starting point, which keeps breaths comparable but hides the drift.

  • int_oesinsp / ptp_oesinsp — from −Poes over inspiration (Poes is negated so inspiratory effort is positive).
  • int_pdiinsp / ptp_pdiinsp — from Pdi over inspiration.
  • int_pgasexp / ptp_pgasexp — from Pgas over expiration.
An inspiratory pressure trace with a short baseline window shaded at the phase start, the baseline mean drawn as a horizontal line, and the signed area either side of it filled — the positive area above the baseline, and hatched below it where the trace briefly dips under its own baseline — the integral that becomes int and ptp.
The PTP integral. The mean of the shaded window at the phase start sets the baseline; the signed area between the baseline-shifted pressure and zero is int_*, with excursions below the baseline counting negative, and is then scaled by breaths·min⁻¹ to give the ptp_* rate.

The int_ and ptp_ columns are not the same quantity. int_* is the un-scaled per-breath integral in cmH₂O·s. ptp_* multiplies it by bcnt · vefactor, so it is a rate in cmH₂O·s·min⁻¹. Do not compare one against the other.

Baseline differs from RespMech 1.x. v2 uses the mean over a short end-expiratory window (baseline_window_s, default 0.05 s) rather than the single first sample — the robust form of the same definition, not a new one. This is a deliberate change, and old/new PTP values are not directly comparable. To reproduce the legacy single-sample baseline exactly, set baseline_window_s small enough that round(baseline_window_s · fs) collapses to 1 (e.g. 0.0005). See Changes from RespMech 1.x for the full comparison.

Resistance & the gastric/oesophageal ratio

Lung resistance is estimated by the isovolume method (Mead & Whittenberger, J Appl Physiol 1953;5:779–96): the change in oesophageal pressure divided by the change in flow between the inspiratory and expiratory points at the same lung volume, so that the elastic recoil term cancels:

tlr_insp = |(poes_midvolexp − poes_midvolinsp) / (flow_midvolexp − flow_midvolinsp)|cmH₂O·L⁻¹·s

What it assumes. The cancellation is exact only if the elastic recoil is identical at the two points, i.e. if end-expiratory lung volume is stable across the breath and there is no dynamic hyperinflation or intrinsic PEEP. RespMech samples the two points where each phase first reaches half its own volume swing, so a breath with an irregular or non-monotonic volume trace can place them at volumes that are not truly equal. Just as important, each pressure is a single sample, so cardiac pressure ripple or a movement artefact at either point propagates directly into the ratio: on the bundled sample recording, whose model resistance is 2.0 cmH₂O·L⁻¹·s, the per-breath estimate ranges from 1.2 to 2.4. Treat tlr_insp as an index for within-subject comparison, averaged over breaths, rather than as an absolute airway resistance.

The gastric/oesophageal pressure ratio compares the end-tidal gastric swing to the end-tidal oesophageal swing — an index of the abdominal versus pleural pressure contribution to the breath:

vmr = (pgas_endinsp − pgas_endexp) / (poes_endinsp − poes_endexp)dimensionless

Reading vmr. It is the end-tidal slope of the gastric-versus-oesophageal (Macklem) pressure diagram (Macklem, Gross, Grassino & Roussos, J Appl Physiol 1978;44:200–208). With RespMech's sign conventions a normal breath gives a negative value, because Pgas rises while Poes falls during inspiration. A value near zero means little abdominal (diaphragmatic) contribution to the pressure swing; a positive value means Pgas fell together with Poes, i.e. paradoxical abdominal motion. Compare magnitudes within a subject only, and treat a value near zero with caution: the ratio becomes unstable whenever the oesophageal swing is small, and the guarded value returned for a zero denominator is also 0, indistinguishable in the workbook from a genuine vmr of zero.

A division by zero in vmr is guarded and returns 0.

Blank units are intentional. In the workbook's Units sheet, vmr and tlr_insp are left blank to avoid mislabelling the derived ratios. Physiologically, vmr is dimensionless and tlr_insp is in cmH₂O·L⁻¹·s, as shown in the formulas above.

Settings this section uses

The pressure descriptors come directly from the channels you assign to Poes, Pgas and Pdi; the time base, the PTP baseline and the per-minute scaling are governed by the settings below. Breath detection and volume conditioning are covered under signal conditioning.

Sampling frequency
input.format.sampling_frequency
Default none (required)
Unit / values Hz
Sets the whole time base — Ti, Te, Ttot, VE and the PTP integration all derive from it. Must match the acquisition system exactly; a wrong value rescales every time, frequency and integral. The Setup screen can auto-detect it, but confirm it.
Oesophageal pressure (Poes) channel
input.channels.poes
Default none (required)
Unit / values 1-based column
Column carrying oesophageal (pleural) pressure — source of every poes_* descriptor, the inspiratory PTP and vmr. Assign to your balloon-catheter oesophageal trace. 1-based (LabChart convention).
Gastric pressure (Pgas) channel
input.channels.pgas
Default none (required)
Unit / values 1-based column
Column carrying gastric (abdominal) pressure — source of the pgas_* descriptors, the expiratory PTP and vmr. Assign to your gastric balloon trace.
Transdiaphragmatic pressure (Pdi) channel
input.channels.pdi
Default none (required)
Unit / values 1-based column
Column carrying Pdi (= Pgas − Poes) — source of the pdi_* descriptors and the inspiratory Pdi PTP. Read as a supplied channel; RespMech does not recompute it from Poes and Pgas.
PTP baseline window
processing.ptp.baseline_window_s
Default 0.05
Unit / values s (0.0–1.0)
Length of the short phase-start window whose mean pressure is the phase-start PTP baseline (end-expiratory for the inspiratory Poes/Pdi PTPs, end-inspiratory for the expiratory Pgas PTP). A window (vs a single sample) is robust to boundary noise; widen slightly if the phase start is noisy. Affects all int_*/ptp_* outputs.
Breath-count overrides
processing.breath_counts
Default [] (empty)
Unit / values per-file integer count
Overrides, per file, the breath count used for per-minute scaling (bf, VE and the · bcnt factor on every PTP). Use when the detected count differs from the true count — e.g. a partial breath at the ends, or because you excluded breaths.
Excluded breaths
processing.exclude_breaths
Default [] (empty)
Unit / values per-file list of 1-based breath numbers
Marks named breaths as ignored: they are dropped from the per-file averages and workbooks (but still drawn in the diagnostic plots). Set interactively by clicking breaths in Preview & QC. They still count towards bcnt unless you also set breath_counts.

Assign channels and set the sampling frequency on the Setup screen; tune the PTP baseline and breath-count overrides in the Preview & QC advanced panel. Then run the batch:

respmech run settings.toml

Output columns — respiratory mechanics

The per-breath table (<file>.breathdata, Data sheet) and the per-file Average breathdata row carry the columns below, keyed by breath_no (per breath) or file (average row). The same tables also hold work-of-breathing and, when configured, EMG/entropy columns, which are documented in their own sections.

ColumnUnitMeaning
tisInspiratory time
tesExpiratory time
ttotsTotal breath cycle time
ti_ttot—Inspiratory duty cycle, Ti/Ttot
vtLTidal volume (max − min volume)
bfmin⁻¹Breathing frequency (bcnt · vefactor)
veL·min⁻¹Minute ventilation (VT · bcnt · vefactor)
poes_mininspcmH₂OMin oesophageal pressure during inspiration (peak inspiratory effort)
poes_maxexpcmH₂OMax oesophageal pressure during expiration
poes_endinspcmH₂OPoes at end-inspiration
poes_endexpcmH₂OPoes at end-expiration
poes_midvolinspcmH₂OPoes at mid-tidal-volume, inspiration
poes_midvolexpcmH₂OPoes at mid-tidal-volume, expiration
poes_tidal_swingcmH₂Omax(Poes) − min(Poes) over the breath
int_oesinspcmH₂O·sPer-breath integral of −Poes over inspiration (baseline-referenced) ⚠ Differs from 1.x: baseline change + Simpson drift
ptp_oesinspcmH₂O·s·min⁻¹Inspiratory oesophageal PTP (int × breaths·min⁻¹) ⚠ Differs from 1.x: baseline change + Simpson drift
pgas_endinspcmH₂OPgas at end-inspiration
pgas_endexpcmH₂OPgas at end-expiration
pgas_maxexpcmH₂OMax gastric pressure during expiration
pgas_minexpcmH₂OMin gastric pressure during expiration
exp_pgas_risecmH₂Omax(Pgas_exp) − min(Pgas_exp)
pgas_tidal_swingcmH₂Omax(Pgas) − min(Pgas) over the breath
int_pgasexpcmH₂O·sPer-breath integral of Pgas over expiration (signed; negative during passive expiration) ⚠ Differs from 1.x: baseline change + Simpson drift
ptp_pgasexpcmH₂O·s·min⁻¹Expiratory gastric PTP (signed; negative during passive expiration) ⚠ Differs from 1.x: baseline change + Simpson drift
pdi_maxinspcmH₂OMax Pdi during inspiration
pdi_minexpcmH₂OMin Pdi during expiration
pdi_endinspcmH₂OPdi at end-inspiration
pdi_endexpcmH₂OPdi at end-expiration
insp_pdi_risecmH₂Omax(Pdi_insp) − min(Pdi_insp)
pdi_tidal_swingcmH₂Omax(Pdi) − min(Pdi) over the breath
int_pdiinspcmH₂O·sPer-breath integral of Pdi over inspiration ⚠ Differs from 1.x: baseline change + Simpson drift
ptp_pdiinspcmH₂O·s·min⁻¹Inspiratory transdiaphragmatic PTP ⚠ Differs from 1.x: baseline change + Simpson drift
max_in_flowL·s⁻¹Peak inspiratory flow (−min(flow_insp))
max_ex_flowL·s⁻¹Peak expiratory flow
in_flow_midvolL·s⁻¹Inspiratory flow at mid-tidal volume, sign-flipped so it reads positive. Identical to flow_midvolinsp.
ex_flow_midvolL·s⁻¹Expiratory flow at mid-tidal volume, positive (the raw expiratory sample).
flow_midvolinspL·s⁻¹Inspiratory flow at mid-tidal volume, sign-flipped so it reads positive. Same value as in_flow_midvol.
flow_midvolexpL·s⁻¹Minus the expiratory flow at mid-tidal volume, so it is written as a negative number, against the usual convention that expiration is positive flow. It exists as the counterpart of flow_midvolinsp in the tlr_insp difference; use ex_flow_midvol for the expiratory flow itself.
vol_endinspLVolume at end-inspiration
vol_endexpLVolume at end-expiration
vmr— (dimensionless)Gastric/oesophageal end-tidal pressure ratio (negative for a normal breath)
tlr_insp— (cmH₂O·L⁻¹·s)Isovolume inspiratory lung-resistance estimate

Per-breath mechanics always use the raw per-breath signals. The work-of-breathing settings (one averaged breath vs each breath then averaged) shape only the averaged breath / average P–V loop — they do not change any pressure, timing or volume descriptor in the table above.

Work of breathing & the Campbell diagram

Work of breathing (WOB) is the mechanical work the respiratory muscles perform to move air in and out of the lungs. RespMech quantifies it the classic way — from a Campbell diagram, a loop of oesophageal pressure (Poes, cmH₂O) plotted against lung volume (Volume, L) over a breath, the pressure–volume framework set out by Otis, Fenn & Rahn (1950) and Campbell (1958) — see References › Respiratory mechanics. The inspiratory work is the area between the inspiratory pressure trace and the end-expiratory pressure level, computed as ∫ P dV; the elastic-recoil line joining EELV and EILV splits it into an elastic triangle and a resistive bulge, and any expiratory pressure above the end-expiratory level adds expiratory work. RespMech reports these three components as a per-minute power in J·min⁻¹.

WOB is the most conditioning-sensitive number in the pipeline: it depends entirely on a correctly signed, drift-corrected Poes and Volume signal. Get the sign conventions or drift correction wrong and RespMech will still produce a WOB number — just a wrong one, with no error. See Conventions & gotchas before trusting any value.

The Campbell diagram

Each breath is reduced to two endpoints on the pressure–volume plane:

  • EELV — the end-expiratory lung volume point [V_endexp, Poes_endexp] (last sample of expiration), the relaxed baseline.
  • EILV — the end-inspiratory lung volume point [V_endinsp, Poes_endinsp] (last sample of inspiration), the top of the tidal excursion.

The straight line joining these two zero-flow points is the lung's elastic-recoil line: at zero flow the alveolar pressure is atmospheric, so Poes equals minus the elastic recoil pressure of the lung, and the chord is that breath's dynamic lung-compliance line. It is the pressure the lung alone would demand at each volume if there were no flow resistance. The bulge of the real inspiratory Poes trace beyond this line is resistive work; the triangle bounded by the line, the constant-Poes level through EELV and the end-inspiratory volume is the elastic work RespMech reports.

An averaged Campbell loop of oesophageal pressure versus lung volume, annotated with the EELV and EILV endpoints, the dashed elastic-recoil line joining them, the shaded inspiratory elastic triangle, the inspiratory resistive area between the pressure trace and the recoil line, and the expiratory work area.
The averaged Campbell loop. The dashed recoil line joins EELV and EILV; the elastic triangle (wob_in_ela), the inspiratory resistive area (wob_in_res) and the expiratory area (wob_ex_total) are the three work components RespMech measures. Drawn in the classic Campbell orientation, with volume on the vertical axis; RespMech's own Preview panel and the written Campbell (average).pdf show the same loop transposed, with lung volume on the x-axis (inverted) and Poes on the y-axis.

The live Campbell diagram in the Preview & QC screen plots Poes on the x-axis and lung volume above end-expiration on the y-axis. The diagnostic Campbell (average).pdf transposes this — Volume on the x-axis (inverted) and Poes on the y-axis. Same physics, transposed presentation; it is not a discrepancy. Use Export Campbell… in Preview to save the live diagram as PNG or PDF.

The three work components

Every area starts as a pressure×volume quantity in cmH₂O·L and is multiplied by the unit-change factor (see below) to become Joules per breath.

1. Inspiratory elastic WOB (wob_in_ela) — the triangle between EELV, EILV and the recoil line: the energy stored stretching the lung over the tidal volume (equivalently ½·VT²/C_L,dyn). The chest wall is not included — see the note below.

wob_in_ela = ½ · |V_endinsp − V_endexp| · |Poes_endinsp − Poes_endexp| · (98.0638/1000)J per breath

No chest-wall compliance line. The classic Campbell diagram also carries a chest-wall relaxation line, whose slope is set by the chest-wall compliance Ccw, through the end-expiratory point, and measures elastic work between the two lines, which adds a chest-wall term of about ½·VT²/Ccw (around 0.2 L·cmH₂O⁻¹ in healthy adults). RespMech draws no Ccw line: the reference for both the elastic triangle and the expiratory term is the line of constant Poes through the end-expiratory point (vertical in the schematic above, horizontal in RespMech's own figures, where volume is on the x-axis), which is equivalent to an infinitely compliant chest wall. wob_in_ela is therefore lung elastic work only, and wobtotal is lower than a Campbell diagram built with a measured or assumed chest-wall compliance. Note also that the end-expiratory Poes is a relaxed pressure only when expiration is passive; with active expiration, or an EELV below the relaxation volume, it is not.

2. Inspiratory resistive WOB (wob_in_res) — the area between the inspiratory Poes trace and a straight line fitted between the first and last inspiratory samples. This is not quite the recoil line drawn in the figure above: the figure's line starts at the breath's own end-expiratory point (EELV), while the line used here starts wherever that breath's inspiration begins, which is the end of the previous breath's expiration. The two coincide only when end-expiratory volume and pressure are stable from breath to breath; its intercept is also taken at V = 0 rather than at the inspiration's own starting volume (an inherited 1.x quirk), so on a breath whose inspiration does not start at V = 0 — for example because end-expiratory volume wanders between breaths — wob_in_res is shifted by an amount with no physiological meaning. Keep processing.volume.correct_drift on, and enable processing.volume.correct_trend when EELV wanders, so that every breath starts at V = 0. Only the positive (effort) part of the excess is kept, and it is integrated over volume with Simpson's rule.

wob_in_res = |simpson(excess, x = V_in)| · (98.0638/1000), excess = max((−Poes_in) − (−insp_line), 0)J per breath

3. Expiratory WOB (wob_ex_total) — the expiratory Poes rising above the end-expiratory level, positive part integrated over volume: active expiratory effort or expiratory resistance above the relaxed baseline.

wob_ex_total = |simpson(excess, x = V_ex)| · (98.0638/1000), excess = max(Poes_ex − Poes_ex_endexp, 0)J per breath

The totals then combine:

wob_in_total = wob_in_ela + wob_in_resJ per breath
wobtotal = wob_in_total + wob_ex_totalJ per breath

Only positive work is counted. The resistive and expiratory integrands are clipped at zero, so areas on the "wrong" side of the recoil / end-expiratory line never subtract. This is deliberate — you cannot do negative respiratory work in this model — but it means every WOB value is non-negative by construction.

From cmH₂O·L to J·min⁻¹

Two conversions turn a raw loop area into the reported number.

Pressure×volume to Joules. Since 1 cmH₂O = 98.0638 Pa, 1 L = 10⁻³ m³, and Pa·m³ = J:

1 cmH₂O·L = 98.0638 × 10⁻³ J ⇒ J = area[cmH₂O·L] · (98.0638/1000)J·(cmH₂O·L)⁻¹

Per breath to per minute. Each per-breath Joule quantity is then scaled to a per-minute rate by the breathing frequency, so the reported value is "the work of this breath sustained for one minute":

reported = per_breath_J · bcnt · vefactor, vefactor = 60 / file_duration_sJ·min⁻¹

Here bcnt · vefactor is the breathing frequency in min⁻¹ (breath count over the file duration, scaled to 60 s).

Every WOB output column is J·min⁻¹, not Joules. The raw per-breath work is internal only — there is no un-scaled Joule column. To recover per-breath Joules, divide a WOB column by the breathing-frequency column (min⁻¹).

Output columns — work of breathing

All five WOB columns appear in both the Average breath-data workbook and the per-file breath-by-breath workbook. Units are attached automatically (any column beginning wob is J·min⁻¹) and are not baked into the column names.

  • wobtotal — total work of breathing (inspiratory + expiratory). J·min⁻¹ ⚠ Differs from 1.x (Simpson rule change, negligible)
  • wob_in_total — total inspiratory WOB (wob_in_ela + wob_in_res). J·min⁻¹ ⚠ Differs from 1.x (Simpson rule change, negligible)
  • wob_ex_total — expiratory WOB. J·min⁻¹ ⚠ Differs from 1.x (Simpson rule change, negligible)
  • wob_in_ela — inspiratory elastic WOB (the recoil triangle). J·min⁻¹ — analytic, no Simpson integration: bit-identical to 1.x.
  • wob_in_res — inspiratory resistive WOB (area to the recoil line). J·min⁻¹ ⚠ Differs from 1.x (Simpson rule change, negligible)

Average vs individual breaths

You choose whether WOB — and the Campbell loop it is read from — is measured once from a single averaged breath or breath-by-breath and then averaged. In "average" mode, every non-ignored breath is resampled to avg_resampling_obs points and mean-averaged; the resulting loop is robust to irregular or noisy breaths and is also what the plotted average Campbell figure is drawn from. In "individual" mode, WOB is computed from each breath's own trace, which is more faithful to breath-to-breath variability but sensitive to atypical breaths.

Work of breathing from
processing.wob.calc_from
Default average
Unit / values "average" | "individual"
Selects whether WOB (and the Campbell loop it is read from) is computed once from one averaged breath, or per breath then averaged. Keep "average" for robustness when breaths are irregular or noisy; switch to "individual" to inspect breath-to-breath variability on clean breaths.
Average-breath resampling points
processing.wob.avg_resampling_obs
Default 500
Unit / values count (UI 10–100000, step 10)
Number of points each breath's inspiration and expiration are resampled to (linear interp1d) before mean-averaging into the average breath. Raise for a smoother averaged loop / more precise area on long, high-rate breaths; 500 is ample for typical breaths. Only affects the averaged breath — no effect in "individual" mode.

In "average" mode every per-breath row in the breath-by-breath workbook shows the identical WOB value — the averaged-breath result is shared across all rows. This looks like a bug but is expected. Only "individual" mode yields distinct per-breath WOB.

Setting avg_resampling_obs very low coarsens the averaged loop and can bias the measured area. If a breath cannot be resampled, that one file stops with "Could not resample breath #N: it is too short to average. Check Preview & QC ▸ Mechanics ▸ Advanced… ▸ Breath detection (peak thresholds / breath-separation buffer), or exclude this breath in Preview & QC." The rest of the batch still runs, and respmech run writes the other files' output before exiting non-zero with the failed file listed. Despite the setting this note sits under, avg_resampling_obs is only the output length and can neither cause nor prevent the failure: resampling fails only when one phase of a breath holds fewer than two samples, which is always a segmentation or peak-detection problem.

Campbell diagnostic figures

RespMech can render the Campbell loops as diagnostic PDFs into <output>/diagnostics/ — an averaged loop per file with the WOB polygon shaded, and a paginated grid of every breath. Use them to visually confirm the loops look physiological and to spot mis-segmented or atypical breaths worth excluding.

Campbell / PV diagram — averaged
output.diagnostics.save_pv_average
Default true
Unit / values true | false
Writes "<file> – Campbell (average).pdf": every breath overlaid faintly, the average breath bold, plus the recoil line and shaded elastic-WOB triangle. Turn on to verify the averaged loop and polygon; turn off to speed up batch runs when you only need numbers.
Campbell / PV diagram — individual breaths
output.diagnostics.save_pv_individual
Default true
Unit / values true | false
Writes "<file> – Campbell (breaths).pdf": one Campbell loop per breath in a paginated grid (ignored breaths crossed out). With more than one file in the batch it also writes a cohort overview, "All files – Campbell (average).pdf", with one mean loop per file. Turn on to QC individual breaths.
PV grid columns
output.diagnostics.pv_columns
Default 3
Unit / values columns per page (≥ 1)
Panels per row in the per-breath / cohort grid PDFs. Increase for a denser overview, decrease for larger, more legible loops. TOML-only — no GUI control.
PV grid rows
output.diagnostics.pv_rows
Default 4
Unit / values rows per page (≥ 1)
Panel rows per page (so pv_columns × pv_rows panels per page) in the grid PDFs. TOML-only — no GUI control.

The cohort overview "All files – Campbell (average).pdf" is only produced when the batch has more than one successful file.

respmech run settings.toml

The batch computes WOB per breath, writes the five WOB columns into the workbooks, and — where the output.diagnostics.* keys are enabled — renders the Campbell PDFs. Use respmech validate settings.toml first to catch invalid values (calc_from must be "average" or "individual"; avg_resampling_obs must be an integer).

Conventions & gotchas

WOB is only as good as the Poes and volume conditioning. It draws on settings from other groups: processing.volume.correct_drift (ON by default — uncorrected drift leaves successive inspirations starting away from V = 0, which shifts the resistive term as described above; the elastic triangle is a within-breath difference and is barely affected, about 2% on the bundled sample against a doubling of wob_in_res), the inverse_flow / inverse_volume sign conventions, and integrate_from_flow. Wrong signs or uncorrected drift silently produce wrong WOB with no error. See also Campbell diagnostic figures for how to visually confirm a loop looks physiological before trusting its numbers.

Poes sign matters for the resistive term. The model negates Poes internally and assumes inspiration drives it more negative. If your Poes sign is inverted relative to that convention, the inspiratory resistive area collapses to roughly zero. Verify the sign in Preview before running a batch.

Legacy setting names are obsolete. Old settings used wob.calcwobfrom / mechanics.calcwobfromaverage and avgresamplingobs; the current TOML paths are processing.wob.calc_from and processing.wob.avg_resampling_obs. The compute core still reads the legacy attribute names internally, so grepping the engine shows calcwobfrom/avgresamplingobs — those are not user-facing keys. Run respmech migrate old_settings.py to convert legacy files automatically.

Diaphragm EMG

RespMech turns each EMG channel — typically diaphragm EMG (EMGdi) — into two numbers per breath: the RMS amplitude (the loudest short window of muscle activity) and the integrated EMG (the area under the rectified signal). Both are computed for the whole breath and separately for inspiration and expiration, for every channel, plus the across-channel maximum and mean.

Raw EMG is uncalibrated, so its absolute value carries no physical meaning — only comparisons against the same signal do. Two things follow. First, RespMech can re-express every RMS column as a percentage of that same column's own peak (or mean) value across the file's breaths (see Amplitude normalisation), which removes the electrode- and gain-dependent scale and lets the within-file pattern — breath-to-breath modulation, inspiratory versus expiratory activity — be compared across subjects and electrodes. Because every file's own maximum becomes 100% by construction, this does not on its own make absolute activation levels comparable across files, conditions or subjects — that needs a shared reference (a maximal-manoeuvre file recorded once per subject, or an external calibration), which processing.emg.normalization_reference_file supports (see Amplitude normalisation). Second, any signal conditioning must be applied identically to every file in a test, or the comparison breaks.

Units convention. EMG amplitude is in arbitrary units (a.u.) — it depends on the electrode, gain and skin contact and is not calibrated to any physical quantity. Treat every raw amplitude as relative. This is the whole reason normalisation exists.

RMS and integrated EMG

For each channel the signal is scanned with a sliding window of length L = int(rms_window_s · fs) samples. Each window's root-mean-square is computed, and the breath's reported RMS is the maximum of that rolling value across the phase:

rms_window = √( mean( x[i : i+L−1]² ) )a.u.
rms = maxᵢ( rms_window )a.u.

The integrated EMG is the area under the rectified signal over the phase, evaluated by Simpson's rule with time in seconds:

integral_emg = ∫ |x(t)| dta.u.·s

The window is L−1 samples, on purpose. The rolling RMS uses the slice x[i : i+L−1] — e.g. 99 samples for a 0.05 s window at 2000 Hz, not 100. This is a legacy off-by-one that is reproduced exactly so results stay identical to the reference dataset. It has no practical effect on the reported amplitude.

RMS window length
processing.emg.rms_window_s
Default 0.050
Unit / values s (0.01–0.5)
Length of the sliding window whose RMS is measured; each breath takes its largest windowed value, and this window also defines the gated peak's envelope. Shorter windows track fast bursts and give a higher, spikier peak; longer windows smooth it. 0.05 s is RespMech's default and the value the reference pipeline was built with — a conventional length for EMGdi envelopes, but not a validated standard, so keep it fixed across a study rather than optimising it per recording.
Export each EMG stage as WAV
processing.emg.save_sound
Default false
Unit / values true | false
Writes one audio file per channel per conditioning stage (raw / ECG-removed / noise-reduced) so you can listen to whether the cleanup worked. Diagnostic only; adds several files per recording. Off by default.

Amplitude normalisation

Because raw amplitude is in a.u., comparing EMGdi across subjects or electrodes requires expressing each file against a reference. When enabled, RespMech re-expresses every RMS column as a percentage:

rms_pct = 100 · v / ref% of the chosen reference

ref is computed separately for every RMS column: by default, the largest (per_file_max) or the mean (per_file_mean) value of that same column across the file's own kept breaths — so each column, including the inspiratory, expiratory, gated and across-channel columns, reaches 100% in whichever breath that particular column peaks, not necessarily the file's overall peak breath. This is written to a separate EMG normalised sheet — the raw RMS columns are never touched.

Because every file's own maximum becomes 100% by construction, the per-file default does not by itself make amplitudes comparable across files, conditions or subjects — it only makes a column's within-file pattern comparable. Set processing.emg.normalization_reference_file to another file already in the batch (typically a maximal inspiratory/expiratory manoeuvre recorded once per subject) to normalise every file's RMS columns against that one file's own reference instead, so a percentage means the same thing across the whole study; the reference file itself still reads 100% at its own peak, and every other file's peak reads below 100% unless it happens to match the manoeuvre. Settings-file only, off by default — existing analyses are unaffected until it is set.

Amplitude normalisation
processing.emg.normalization
Default per_file_max
Unit / values none | per_file_max | per_file_mean
Also reports each file's RMS columns as a percentage of a per-file reference. per_file_max = % of the peak breath; per_file_mean = % of the mean breath; none writes no normalised sheet. Choose per_file_mean if a single high breath makes the peak an unstable denominator.

Under per_file_max the denominator is itself a per-breath maximum, so it can land on a residual heartbeat instead of true diaphragm activity. On strongly cardiac-coupled recordings this is a reason to enable the cardiac-gated peak, whose maximum is read only from heartbeat-free signal.

The conditioning pipeline

Two optional stages clean each EMG channel before RMS and integrated EMG are measured, always in this fixed order:

  1. ECG removal — subtract an averaged heartbeat template so the cardiac artefact does not inflate the EMG.
  2. Spectral noise reduction — subtract a shared spectral noise profile, built once from a rest reference and applied identically to every file.

Both are off by default; with both off, the RMS and integrated EMG are measured on the raw signal and the output is byte-identical to an unconditioned run.

Three stacked EMG traces of the same breath: the raw signal with large periodic cardiac spikes, the ECG-removed signal with those spikes flattened, and the noise-reduced signal with a cleaner baseline and the inspiratory EMG burst preserved.
The conditioning pipeline on one channel: raw EMG → ECG removed → spectral noise reduced. The periodic cardiac artefact is subtracted first, then the stationary noise floor is gated away, leaving the inspiratory diaphragm burst intact.

Detection and noise parameters are test-level. Every ECG-detection and noise-reduction setting is applied identically to all files in a test. Never re-tune them per file — doing so breaks the shared-transformation requirement that makes relative EMG comparable across the test.

ECG (cardiac artefact) removal

When enabled, ECG removal works in three steps:

  1. R-peak detection on the chosen capture channel, using find_peaks with the height, refractory-distance and width guards below. The same heartbeat train is reused for every EMG channel.
  2. Averaged template. Windows of half-width ecg_window_s/2 either side of each R-peak (total span ≈ ecg_window_s) are time-aligned within ±0.2 s and averaged into one ECG template per channel.
  3. Per-beat subtraction. For each beat the template is time-shifted (±0.2 s) and amplitude-scaled (within ±1.25×) to best fit that beat, then subtracted.

RespMech reports how well this worked as a suppression figure — the peak-window RMS within ±40 ms of each R-peak, before versus after:

suppression = 1 − RMS_after / RMS_beforefraction (per file)
Remove ECG
processing.emg.remove_ecg
Default false
Unit / values true | false
Master switch for cardiac-artefact removal. Turn on whenever the heartbeat contaminates the EMG (diaphragm/oesophageal recordings). Also the prerequisite for the cardiac-gated peak.
Capture channel
processing.emg.detect_channel
Default 0
Unit / values 0-based index
Which EMG channel the R-waves are detected on. Pick the channel with the clearest ECG and weakest EMG — often the middle electrode. Auto-suggest can set it.
Min height
processing.emg.ecg_min_height
Default 0.0005
Unit / values a.u.
Minimum R-wave peak height to count as a heartbeat. Raise to reject small non-cardiac spikes; lower if real beats are missed. In the same arbitrary units as the raw EMG, so it is electrode/recording specific — best set by Auto-suggest or by watching the peak markers.
Min gap
processing.emg.ecg_min_distance_s
Default 0.5
Unit / values s (0.05–2.0)
Refractory gap — minimum time between detected beats. This sets the maximum detectable heart rate = 60/gap bpm; lower it for fast (exercise) heart rates so real beats are not merged or rejected. It also doubles as the gated peak's heart-rate ceiling (see below).
Minimum peak width
processing.emg.ecg_min_width_s
Default 0.001
Unit / values s (0.0–0.1)
Shape guard against counting a narrow spike as a heartbeat. Rarely changed; raise slightly if sharp non-cardiac transients are being detected as beats. In the ECG-removal advanced dialog.
Template width
processing.emg.ecg_window_s
Default 0.4
Unit / values s (0.05–1.0)
Span of the QRS-T template averaged and subtracted around each beat. Physiologically fixed — 0.4 s captures the QRS-T complex in adults; it must not overlap neighbouring beats. In the ECG-removal advanced dialog.

detect_channel is an index, not a column number. It is a 0-based index into your configured EMG channel list (input.channels.emg), not a raw data column. The first EMG channel is 0, the second is 1, and so on — easy to mis-set.

Spectral noise reduction

This is stationary spectral gating driven by a fixed per-frequency threshold. The threshold is built once per test from an EMG-free rest reference and applied identically to every file — the same design constraint that governs ECG removal, so the transformation stays comparable across the test.

For each frequency bin the threshold is the mean noise level plus a margin in standard deviations:

T(f) = meanₜ( dB|STFT(noise)| ) + n_std_thresh · stdₜ( dB|STFT(noise)| )dB

To apply it, RespMech STFTs the signal, masks bins that fall below T(f), smooths the mask, and attenuates the masked energy by prop_decrease towards the spectral floor before inverse-STFT.

Two in-band metrics (20–250 Hz, Welch PSD) judge the result. Fidelity is the fraction of true inspiratory EMG power retained — the over-subtraction guard — and the in-band SNR contrasts inspiration against expiration:

fidelity = P_band(processed insp) / P_band(raw insp)fraction (1.0 = nothing lost)
SNR = 10 · log₁₀( P_band(insp) / P_band(exp) )dB

With Auto on, RespMech picks — once per test — the strongest prop_decrease on a 0.1–1.0 grid whose worst-channel fidelity still meets the target; if none qualify, it uses the gentlest. Auto measures the fidelity frontier on a capped sample of the test, not on every file: it walks the sorted file list and stops as soon as it has gathered 40,000 inspiratory samples (20 s of inspiration at 2 kHz, 40 s at 1 kHz), which is often the first file alone, and uses the matching expiratory samples as the quiet reference. The strength it picks is then applied identically to every file, so put a representative recording first in the sort order, and check the per-channel fidelity printed in run-report.txt whenever the batch mixes very different signal levels.

Reduce EMG noise
processing.emg.noise.enabled
Default false
Unit / values true | false
Master switch for spectral noise reduction. Turn on to remove a stationary broadband/electrical noise floor (e.g. a 20–50 Hz floor) while preserving the 20–250 Hz band that carries the diaphragm EMG power. Does nothing without a reference file set.
Noise reference file
processing.emg.noise.reference_file
Default null
Unit / values filename
The EMG-free rest recording the shared profile is built from. Use a diaphragm-quiet reference with several seconds of EMG-free signal. Required when noise reduction is enabled — a batch run raises an error if it is missing.
Build from expiration
processing.emg.noise.use_expiration
Default true
Unit / values true | false
Builds the profile from every expiration of the reference file (diaphragm-quiet, ~hundreds of stable STFT frames). Far more stable than a short hand-picked gap. When true, the intervals below are ignored.
Reference intervals
processing.emg.noise.reference_intervals
Default []
Unit / values list of [t0, t1] s
Explicit EMG-free windows to build the profile from, used only when use_expiration is false. Hand-pick quiet spans when you don't trust automatic expiration detection.
Auto (pick strength)
processing.emg.noise.auto_prop
Default true
Unit / values true | false
Automatically picks the strongest suppression that still keeps every channel at or above the fidelity target. Leave on. When on, prop_decrease is ignored and the chosen value is applied to all files.
Fidelity target (Keep ≥)
processing.emg.noise.fidelity_target
Default 0.8
Unit / values fraction (0.50–0.99)
Smallest fraction of inspiratory in-band EMG power that must survive; the auto search keeps the worst channel at or above this. 0.8 (retain ≥80% of EMG power) is the validated guard. Only meaningful with Auto on.⚠ Lives in Preview & QC → EMG – noise reduction → Advanced…, under Noise suppression.
Suppression strength
processing.emg.noise.prop_decrease
Default 0.6
Unit / values 0.0 (none) – 1.0 (max)
How aggressively noise is removed when Auto is off. 0.5–0.7 is a safe range (fidelity ≈0.91–0.97); 1.0 gives maximum ΔSNR at fidelity ≈0.84. Ignored when Auto is on.
Spectral gate threshold
processing.emg.noise.n_std_thresh
Default 1.0
Unit / values SD (0.0–10.0)
How many SD above the noise profile a bin must exceed to survive. 1.0 is gentle and safe; ≥1.5 destroys real EMG. Essentially never changed — see the trap below.
STFT length (n_fft)
processing.emg.noise.n_fft
Default 256
Unit / values samples, power of two
FFT length for the spectral gate (~128 ms at 2 kHz), tuned for the 20–250 Hz band. Decoupled from the noise-clip length on purpose; auto-scaled under pre-analysis resampling to hold a fixed time window. Rarely changed.
STFT window
processing.emg.noise.win_length
Default 256
Unit / values samples
Analysis window length. Normally equals n_fft, but kept as a separate control on purpose — changing it independently changes output for analyses where they differ.
STFT hop
processing.emg.noise.hop_length
Default 64
Unit / values samples
Advance between successive STFT windows (default n_fft/4). Smaller hop = more overlap and smoother gating at higher cost.
Mask smoothing — frequency
processing.emg.noise.n_grad_freq
Default 0
Unit / values frequency bins (0–64)
Bins the suppression mask is smoothed over in frequency. Increase to soften spectral edges of the mask; 0 is the tuned default.
Mask smoothing — time
processing.emg.noise.n_grad_time
Default 4
Unit / values time frames (0–64)
STFT frames the mask is smoothed over in time, to avoid musical-noise artefacts; 4 is the tuned default.

The "great SNR trap". Raising n_std_thresh (or pushing prop_decrease to 1.0) makes the SNR number look better while it quietly destroys real EMG. Trust the fidelity gate, not the SNR: keep n_std_thresh at 1.0 and let Auto hold fidelity at the target.

A ticked Reduce EMG noise toggle with no reference file set runs nothing. The reference must be a proper rest recording (several seconds of EMG-free expiration) — a single short gap gives a degenerate, unusable estimate.

Cardiac-gated peak EMG

Even after template subtraction, a residual heartbeat often remains, and the plain RMS maximum tends to land on it rather than on diaphragm activity. The cardiac-gated peak reads the maximum from only the heartbeat-free stretches of each breath: RespMech computes the rolling RMS envelope, blanks ± gate_half_width_s around every detected R-peak, and takes the maximum of what survives — per breath, inspiration and expiration. It reuses the R-peaks that ECG removal already found, so it requires remove_ecg = true.

One rolling-RMS envelope of a strongly cardiac-coupled breath: the ±120 ms windows blanked around each detected heartbeat are shaded; the naïve maximum marker sits on a tall heartbeat residual, while the cardiac-gated maximum marker sits on the diaphragm burst that survives the gating.
Cardiac-gated peak on one RMS envelope: the naïve maximum (red) lands on a residual heartbeat, while blanking ±gate_half_width_s around each detected R-peak (the shaded bands) leaves the gated maximum (green) on true diaphragm activity.

Losing samples costs nothing here: every breath — and the normalising maximum — undergoes the same blanking, so the loss cancels in the relative measure. When the guards below cannot trust a result, RespMech reports NaN rather than a wrong number.

Add gated-peak columns
processing.emg.robust_peak.enabled
Default false
Unit / values true | false
Also measures the per-breath peak RMS from heartbeat-free signal (adds gated columns). Enable on strongly cardiac-coupled recordings where the plain max lands on a residual heartbeat. Requires remove_ecg = true; adds columns and never changes existing ones.
Blanked around each heartbeat
processing.emg.robust_peak.gate_half_width_s
Default 0.120
Unit / values s half-width (±0.02–0.5)
How much of the envelope is blanked either side of each R-peak. Must cover the heartbeat's footprint (≈ rms_window_s + QRS duration); 120 ms covers it with margin. It is a half-width, so the default blanks a 0.240 s total window.⚠ Lives in Preview & QC → EMG – noise reduction → Advanced…, under Gated peak (saved output).
Least of each phase that must survive
processing.emg.robust_peak.min_survival
Default 0.40
Unit / values fraction (0.0–1.0)
Minimum fraction of a phase that must remain outside the gates, else that phase's gated value is NaN. Prevents reporting a peak read from a sliver of breath at fast heart rates. Trips independently per phase.
Shortest usable stretch between beats
processing.emg.robust_peak.min_island_s
Default 0.20
Unit / values s (0.0–2.0)
Minimum length of the longest cardiac-free run for a phase to be measurable. A gap shorter than this cannot hold a full RMS window, so it yields no valid peak.
Missed-heartbeat factor
processing.emg.robust_peak.long_rr_factor
Default 1.6
Unit / values × median RR (1.1–5.0)
An RR interval this many times the median RR is treated as a missed heartbeat (a missed beat produces a roughly doubled interval). Feeds the per-file detection guard.
Missed heartbeats tolerated
processing.emg.robust_peak.max_long_rr_frac
Default 0.02
Unit / values fraction (0.0–1.0)
Maximum fraction of long (missed-beat) RR intervals before the file's detection is distrusted and all its gated columns become NaN. Gating on incomplete detection would report a heartbeat with false confidence.
Heart-rate ceiling margin
processing.emg.robust_peak.hr_ceiling_margin
Default 0.10
Unit / values fraction (0.0–0.5)
How close the detected heart rate may come to the detector's refractory ceiling (60/ecg_min_distance_s bpm) before the gated result is distrusted. Near the ceiling the detector starts missing beats, which is exactly where gating breaks.

The detector's ceiling is set entirely by the ECG refractory gap:

HR ceiling = 60 / ecg_min_distance_sbpm

NaN is honest, not broken. The gated peak returns NaN — never a guessed number — when its guards trip: too little cardiac-free signal in a phase, too many missed beats, or a heart rate too near the detector's ceiling. If the ceiling guard fires on a fast (exercise) file, the fix is to lower ecg_min_distance_s on the ECG tab, not to raise hr_ceiling_margin.

RMS outlier handling

An optional filter suppresses a single artefactual breath's RMS from distorting the file average. For each breath RespMech forms the ratio rms_poes = rms_max / poes_mininsp. If a breath's ratio lies more than outlier_rms_sd_limit standard deviations from the mean of the other breaths, its rms_max and rms_mean are replaced with the other breaths' means. It only fires when the limit is above 0.

RMS outlier limit
processing.emg.outlier_rms_sd_limit
Default 0.0
Unit / values SD (0 = off; 0–10)
Replace any breath whose RMS/Poes ratio lies more than this many SD from the across-breath mean with the mean RMS of the other breaths. Use to stop one artefactual breath distorting the file average. 0 disables it (the default).

Output columns — diaphragm EMG

The RMS and integrated-EMG columns appear on the main breath tables for every file. The gated columns are added only when the gated peak is enabled, and the normalised _pct columns live on a separate EMG normalised sheet, added only when normalisation is not none. Neither ever alters the raw RMS.

Column(s)MeaningUnit
rms_col_<n>Per-breath max rolling RMS for EMG channel n ⚠ Differs from 1.x when noise reduction was useda.u.
rms_max, rms_meanMax / mean of the per-channel RMS across channels ⚠ Differs from 1.x when noise reduction was useda.u.
rms_insp_col_<n>, rms_insp_max, rms_insp_meanSame, inspiration only ⚠ Differs from 1.x when noise reduction was useda.u.
rms_exp_col_<n>, rms_exp_max, rms_exp_meanSame, expiration only ⚠ Differs from 1.x when noise reduction was useda.u.
integral_emg_col_<n>, integralemg_max, integralemg_meanIntegrated |EMG| over the whole breath, per channel + max/mean ⚠ Differs from 1.x (Simpson rule change) and, when noise reduction was used, EMG conditioninga.u.·s
integral_emg_insp_col_<n>, integralemg_insp_max, integralemg_insp_meanIntegrated EMG, inspiration ⚠ Differs from 1.x (Simpson rule change) and, when noise reduction was used, EMG conditioninga.u.·s
integral_emg_exp_col_<n>, integralemg_exp_max, integralemg_exp_meanIntegrated EMG, expiration ⚠ Differs from 1.x (Simpson rule change) and, when noise reduction was used, EMG conditioninga.u.·s
rms_gated_col_<n>, rms_gated_max, rms_gated_meanCardiac-gated peak RMS, whole breath — opt-in ⚠ Differs from 1.x when noise reduction was useda.u. (NaN if guards trip)
rms_gated_insp_*, rms_gated_exp_*Gated peak, inspiration / expiration — opt-in ⚠ Differs from 1.x when noise reduction was useda.u.
rms_col_<n>_pct, rms_max_pct, … (all RMS cols)Normalised RMS = % of per-file max/mean breath — separate EMG normalised sheet% of per-file reference

The ECG suppression report (peak count, peak RMS before/after, suppression %) and the noise report (chosen prop_decrease, per-channel fidelity and ΔSNR) are written to the run's provenance/diagnostics text, not to the breath tables.

Sample entropy

Sample entropy (SampEn) is a single dimensionless number that measures how irregular or unpredictable a signal is. A highly repetitive, self-similar trace gives a low value; a noisy, complex trace gives a high value. In RespMech it is most often applied to the diaphragm EMG to quantify the complexity of neural drive breath by breath, but you can compute it on any recorded column you tick as an Entropy channel. It has only two tunable knobs — the template length m (Embedding) and the matching tolerance r (Tolerance) — and both default to conventional values, so most users never touch them.

What it measures

Sample entropy is the negative natural logarithm of the conditional probability that two sub-sequences (templates) which match for m consecutive samples will still match when each is extended by one more sample. In plain terms it asks: when a pattern repeats, how reliably does it keep repeating?

  • Low SampEn — the signal is regular and self-repeating; neural drive is rhythmic and stereotyped.
  • High SampEn — the signal is irregular and complex; drive is variable or noise-like.

The statistic is dimensionless (reported unit —). EMG amplitude in this pipeline is uncalibrated, but SampEn is a shape/complexity measure, not an amplitude measure: the tolerance is defined relative to the signal's own standard deviation, which cancels the amplitude scale and makes SampEn scale-invariant. That is what lets you compare complexity across breaths, channels and recordings even though the raw EMG has arbitrary units. It does not make every comparison valid, though: sample entropy is also sensitive to sampling frequency (via the number of samples in the segment) and to the choice of m and r, so only compare values computed at the same sampling frequency (and the same resampling setting, if used) and with the same m and r.

How RespMech computes it

For a channel segment x, using the Chebyshev (maximum) norm, RespMech counts template-vector pairs at two lengths and takes:

SampEn = − ln( A / B )dimensionless

where B is the number of template pairs that match at the shorter length and A is the number that still match when each template is extended by one sample. Two points count as a match when they differ by less than the tolerance r — that is, |x_j − x_k| < r across every element of the template (the Chebyshev norm takes the largest per-element difference).

The tolerance you enter is a fraction. RespMech converts it to an absolute tolerance separately for each segment it analyses, scaling by that segment's own standard deviation:

r_absolute = r · SD(x_segment)same scale as x

Because the standard deviation is recomputed on the exact segment being analysed, the same fractional r produces a different absolute tolerance for the whole breath, for inspiration and for expiration. This is intentional — each phase is judged against its own variability.

SampEn is computed independently for every entropy channel and three times per breath — over the whole breath, over inspiration only, and over expiration only. After the per-channel numbers, RespMech appends the max, min and mean across the entropy channels for each of the three phases.

The statistic follows Richman and Moorman (Am J Physiol Heart Circ Physiol 2000;278:H2039–49, PMID 10843903, doi:10.1152/ajpheart.2000.278.6.H2039), the consistency-corrected successor to Pincus's approximate entropy (Proc Natl Acad Sci USA 1991;88:2297–301, PMID 11607165, doi:10.1073/pnas.88.6.2297). The routine itself is the pyEntropy implementation, vendored into the package and distributed under its own Apache-2.0 licence alongside RespMech's GPL-3.0 — see References › Third-party code.

Embedding value versus classic m. RespMech passes epochs to the routine as the longest template length and reports the last element of the returned vector. With the default epochs = 3 the reported number therefore compares length-3 against length-2 templates. If you are reconciling against strict Richman–Moorman notation (templates of length m and m+1), the reported statistic corresponds to m = epochs − 1 — so the default reports m = 2, the value conventional in the literature. Set epochs = 2 to reproduce the m = 1 RespMech reported before this default changed.

Short segments can leave SampEn undefined. Because SampEn is −ln(A/B), a segment with zero extended matches (A = 0) makes the ratio underflow and the logarithm diverge. Very short inspiration/expiration segments, a too-small tolerance, or a too-large embedding can all trigger this. The defaults (template length 3, i.e. m = 2, and r = 0.1) are safe for typical per-breath EMG.

Parameters

Sample entropy owns the channel-selection role plus two numeric knobs. All three keep conventional defaults, so a typical run only needs you to pick the entropy channel(s).

Entropy channels
input.channels.entropy
Default []
Unit / values 1-based column numbers
The list of columns to compute SampEn on. This is the one non-exclusive role: a column can carry another role (EMG, or even Flow) and be an entropy channel at the same time. Add your diaphragm-EMG column here to quantify the complexity of neural drive; leave empty to skip entropy entirely (no entropy columns are written).
Template length (m + 1)
processing.entropy.epochs
Default 3
Unit / values 1–100 (GUI range); dimensionless
The template / embedding length — how many consecutive samples must match before RespMech checks whether the match extends by one more sample. The default 3 gives m = 2, the near-universal literature convention (set 2 for the m = 1 RespMech reported before this default changed). Larger m demands longer matching sub-sequences, which needs more data to estimate reliably; on short per-breath EMG, raising it can make SampEn unstable or undefined.
Tolerance (r), × SD
processing.entropy.tolerance
Default 0.1
Unit / values 0.0–10.0 (GUI range); fraction of channel SD
The matching tolerance, expressed as a fraction of the segment's standard deviation. This is what makes SampEn scale-invariant. Published values are typically 0.1–0.25 × SD, with 0.2 × SD the most widely used default (Richman & Moorman 2000; Yentes et al. 2013 — full citations in References › Sample entropy); RespMech defaults to the lower end, 0.1. A larger r is more forgiving (more matches, lower SampEn, less noise sensitivity but coarser discrimination); a smaller r is stricter (higher SampEn, but risks too few matches to estimate reliably). Setting it to 0 makes matches essentially impossible.

Beyond the GUI spin box ranges, RespMech does not range-check the entropy parameters, so out-of-convention values entered via TOML are accepted as-is. Stay near the conventions unless you have a specific reason not to.

Assigning entropy channels

In the channel picker, tick the Entropy tickbox on any column you want SampEn for — including a column that already has another role such as EMG. The tooltip reads: "Also compute sample entropy on this column. Independent of the role above — a column can be both."

The Assign-channels dialog with a role dropdown per column and a separate Entropy tickbox beside it; ticking Entropy on a column makes it an entropy channel as well as whatever role it carries
The Assign-channels dialog. Ticking Entropy on a column adds it to input.channels.entropy; the role is non-exclusive, so the same column can also be your EMG channel.

Overlapping EMG + Entropy uses the processed signal. If a column is both an EMG channel and an entropy channel, SampEn is computed on the processed EMG — after any ECG removal and noise reduction — not the raw trace. This is usually what you want, but it means the entropy values depend on your EMG conditioning settings.

On the Setup tab a Sample entropy card appears only when at least one column is actually assigned to entropy. It holds the Template length (m + 1) and Tolerance (r), × SD spin boxes plus a hint: "Computed on the columns ticked as Entropy in the channel picker."

To configure it directly in the analysis TOML:

[input.channels]
entropy = [6]          # 1-based column number(s); non-exclusive

[processing.entropy]
epochs = 3             # template length = m + 1 (3 gives m = 2; use 2 for m = 1)
tolerance = 0.1        # Tolerance (r), as a fraction of the channel SD

Then run the analysis:

respmech run settings.toml

Omitting [processing.entropy] uses the defaults; an empty entropy = [] disables entropy entirely.

Only sample entropy is active. RespMech ships code for Shannon, multiscale, permutation and multiscale-permutation entropy, but none of those are wired into the pipeline — there are no settings for them and they produce no output. "Entropy" in RespMech always means sample entropy.

Output columns — sample entropy

Entropy columns appear only when at least one entropy channel is assigned; otherwise none are written. When present, they appear in both the per-file breath-by-breath sheet and, averaged, in the Average breathdata sheet. All resolve to the dimensionless unit —. Here <c> is the 1-based column number of the entropy channel.

Column Phase Unit
sample_entropy_col_<c>whole breath ⚠ shifted vs 1.x—
sample_entropy_insp_col_<c>inspiration ⚠ invalid in 1.x—
sample_entropy_exp_col_<c>expiration ⚠ invalid in 1.x—
sample_entropy_max / _min / _meanwhole breath, reduced across channels ⚠ shifted vs 1.x—
sample_entropy_insp_max / _insp_min / _insp_meaninspiration, across channels ⚠ invalid in 1.x—
sample_entropy_exp_max / _exp_min / _exp_meanexpiration, across channels ⚠ invalid in 1.x—

With a single entropy channel the max, min and mean columns all equal the per-channel value; they become useful when you assign several entropy channels and want a one-number summary per phase.

The _insp_ / _exp_ columns above are invalid in RespMech 1.x — a phase-indexing bug read every entropy window too early, mixing the end of the preceding phase into the "inspiratory"/"expiratory" segment. Do not reuse or compare old inspiratory/expiratory sample-entropy values; see Changes from RespMech 1.x for the full explanation.

Changes from RespMech 1.x

A controlled comparison between RespMech 1.0.0 and 2.3.2, run without EMG channels, established what changed between the legacy 1.x line and the current 2.x engine, and why. Most of the columns the two versions have in common are bit-identical; the rest differ for three isolated and confirmed causes, each explained below. The affected columns also carry an inline ⚠ note where they are documented (mechanics output columns, WOB output columns, entropy output columns). EMG amplitude columns were outside this comparison — see EMG conditioning below for how they differ when noise reduction is used.

2.x (2.3.2) is the reference going forward. Where the two versions disagree, 2.x is the correct one: the PTP baseline change is the more robust form of the same definition, the sample-entropy change is a fixed indexing bug, and the Simpson-integration drift is not a RespMech change at all. Do not treat a 1.x value as ground truth when it disagrees with 2.x.

The mechanical core is unchanged

Breath timing (ti, te, ttot, ti_ttot), volumes (vt, vol_endinsp, vol_endexp), ventilation (bf, ve), the pressure descriptors, resistance/ratio outputs (tlr_insp, vmr) and every work-of-breathing component except the Simpson-integrated ones (see below) are bit-identical between 1.x and 2.x. Breath detection, trimming and exclusion also segment the same breaths in both versions. If you are migrating a pipeline from 1.x, these columns need no adjustment at all.

PTP baseline: deliberate, robust form of the same definition

Affects: ptp_oesinsp, ptp_pdiinsp, ptp_pgasexp and the paired integrals int_oesinsp, int_pdiinsp, int_pgasexp (see Pressure–time products).

The PTP baseline changed from a single sample at the phase start to the mean over a short (50 ms) window (processing.ptp.baseline_window_s, default 0.05). This is the robust form of the same definition, not a new one — a single sample sits where the pressure is moving steeply and is noise-sensitive. Old and new PTP values are therefore not directly comparable. Legacy behaviour is exactly reproducible with baseline_window_s = 0.0005 (= 1 sample) if strictly needed, but this is not recommended.

Sample entropy: bug fix, old inspiratory/expiratory values invalid

Affects: sample_entropy_insp_* and sample_entropy_exp_* (including the per-channel sample_entropy_insp_col_<c> / sample_entropy_exp_col_<c> and the _max/_min/_mean reductions), and to a lesser degree sample_entropy_col_<c> (see Sample entropy output columns).

In 1.x the entropy windows were mis-indexed: the entropy matrix was left untrimmed but indexed with trimmed coordinates, so every window was read too early by the recording's trim offset. That offset can amount to a substantial fraction of an inspiration, so the "inspiration" window in practice captured the end of the preceding expiration plus only the start of the inspiration — a phase-mixed window, read using the wrong offset rather than the phase it claims to measure. Consequently the old inspiratory and expiratory values are invalid; how far off a given value is depends on the recording and the size of the trim offset, so there is no fixed direction to the error.

The old _insp_/_exp_ entropy values are INVALID (wrong phase), not merely scaled. Do not reuse them and do not compare them across versions. Whole-breath values are less affected, because the window spans both phases, but they are technically shifted too. This is fixed in 2.x.

Simpson integration: library drift, not a RespMech change

Affects: int_oesinsp, int_pdiinsp, int_pgasexp, ptp_oesinsp, ptp_pdiinsp, ptp_pgasexp, wob_in_res, wob_ex_total and their sums wob_in_total, wobtotal (see mechanics output columns and WOB output columns), and the integrated-EMG columns integral_emg_col_<n>, integral_emg_insp_col_<n>, integral_emg_exp_col_<n> with their _max/_mean reductions.

SciPy dropped the even='avg' convention (the pre-1.11 default) for an even number of samples. This is not a RespMech change; it is documented only so that anyone failing to reproduce an old spreadsheet bit-exactly knows why. The size of the shift depends on the recording and is not the same for every column — the integrated-EMG columns move more than the pressure–time integrals and the work-of-breathing totals, because their integrand is broadband and noise-like, and wob_in_res/wob_ex_total can move by as much or more on a recording where that component is itself close to zero — so no single bound is quoted here. Run RespMech on SciPy 1.11 or later, the version floor in pyproject.toml, so the numbers match the validated reference. wob_in_ela (analytic area, no Simpson) and the volume/flow columns (trapezoidal) are unaffected.

EMG conditioning: a redesign, not a drift

Affects: rms_* and integral_emg_* (and their per-channel and reduction variants) — but only when EMG noise reduction was used; ECG removal itself is unchanged from 1.x.

RespMech 1.x re-estimated the noise spectrum inside every call and derived the FFT length from the noise clip itself, so the same settings filtered different files differently. 2.x builds one fixed, shared noise profile per test (band-limited, with a fidelity gate) and applies it identically to every file. EMG amplitude columns from a 1.x run with noise reduction on are therefore not comparable with 2.x, and they were outside the controlled comparison above, which was run without EMG channels.

Guidance. Do not mix old and new PTP values without noting the baseline change, and do not reuse old inspiratory/expiratory sample-entropy values. Everything else — including all of breath timing, volumes, ventilation and the elastic work-of-breathing component — carries over from 1.x unchanged; EMG amplitude columns do not, if noise reduction was in use.

Parameter reference

Every setting RespMech accepts, grouped by area, with its exact TOML key, default and unit. In the desktop app these live on the Setup cards and behind the Advanced… buttons on the Preview tabs; in a settings file they are the dotted keys below. Defaults reproduce the validated reference pipeline.

Input

Where the recordings are and how their columns map to signals. See: Preparing your recording · Setup – Channels

EMG channels
input.channels.emg
Default []
Unit / values 1-based column numbers
Columns of diaphragm-EMG channels to analyse (RMS, integrated EMG, optional ECG removal/noise reduction).
When to change: Optional — assigning any EMG column reveals the two EMG sub-tabs on Preview.
⚠ An EMG column that overlaps a flow/pressure column is flagged as a caution. EMG amplitude is uncalibrated (a.u.).
Entropy channels
input.channels.entropy
Default []
Unit / values 1-based column numbers
The list of 1-based column numbers to compute sample entropy on. In the GUI you tick an "Entropy" tickbox on each column in the channel picker. It is the one NON-EXCLUSIVE role: a column can carry another role (e.g. EMG, or even Flow) AND be an entropy channel at the same time.
When to change: Add the diaphragm-EMG column(s) here to quantify the complexity/irregularity of neural drive. You can select several columns; SampEn is reported for each, plus max/min/mean across them. Leave empty to skip entropy entirely (no entropy columns are added to the output).
⚠ If a column is BOTH an EMG channel and an entropy channel, entropy is computed on the PROCESSED EMG (after any ECG removal / noise reduction), not the raw signal. Ticking entropy on a bare column that has no other role is allowed and valid.
Flow channel
input.channels.flow
Default (required — none)
Unit / values column number (1-based)
Column holding the airflow signal (reads negative during inspiration by convention).
When to change: Assign via 'Assign channels from data…'; drives trimming and flow-based segmentation.
⚠ 1-based column numbering (LabChart convention) — a common off-by-one error. Convention is inspiration = negative flow; use inverse_flow if yours reads positive on inspiration.
Transdiaphragmatic pressure channel (Pdi)
input.channels.pdi
Default (required — none)
Unit / values column number (1-based)
Column carrying transdiaphragmatic pressure (Pdi = Pgas − Poes); source of pdi_* descriptors and the inspiratory Pdi PTP.
When to change: Assign the Pdi channel (often Pgas − Poes recorded directly); feeds Pdi descriptors and PTPdi.
⚠ Required. 1-based. RespMech reads it as a supplied channel; it does not recompute Pdi from Poes and Pgas.
Gastric pressure channel (Pgas)
input.channels.pgas
Default (required — none)
Unit / values column number (1-based)
Column carrying gastric (abdominal) pressure; source of pgas_* descriptors, expiratory PTP and the vmr ratio.
When to change: Assign the Pgas channel; feeds abdominal-pressure descriptors and VMR.
⚠ Required. Collision/time-axis checks apply.
Oesophageal pressure channel (Poes)
input.channels.poes
Default (required — none)
Unit / values column number (1-based)
Column carrying oesophageal (pleural) pressure; source of all poes_* descriptors, the inspiratory PTP and work of breathing.
When to change: Assign the Poes balloon/catheter channel; feeds pleural-pressure descriptors and the Campbell/WOB calculation.
⚠ 1-based (kept for familiarity with LabChart exports), not 0-based. Missing → SettingsError.
Volume channel
input.channels.volume
Default (none — REQUIRED unless integrating from flow)
Unit / values 1-based column number, or unset
Column carrying lung volume; used for VT, mid-volume points and the Campbell/PV plots.
When to change: Set when volume is recorded directly. Omit it (and set processing.volume.integrate_from_flow=true) to derive volume from the flow signal.
⚠ May be omitted ONLY if processing.volume.integrate_from_flow is true; otherwise validation fails. In the TOML an absent volume is null (the engine treats it as NaN internally).
Files to analyse
input.files
Default "*.*"
Unit / values a filename or single glob pattern, e.g. *.txt
Filename or glob mask selecting which files in the input folder to process.
When to change: Restrict a run to one subject/condition, or widen it to a whole folder. Matching is case-INSENSITIVE on every OS and safe against '[' or '*' in the folder name.
⚠ The core batch runner globs ONE pattern; a multi-pattern mask ('*.csv; *.txt', the guided-mode default) is auto-narrowed to the dominant extension on channel-setup OK. Help text: 'Filename or wildcard mask picking which recordings to load, e.g. *.txt; defaults to *.* (all files).'
Recordings folder
input.folder
Default "input"
Unit / values a folder path
Folder holding the recording files to analyse; a Browse… button picks it.
When to change: Point it at your study's raw-data folder. A relative path is resolved against the TOML file's own directory (portable), so a shared analysis keeps working when moved.
⚠ A live read-out under it reports what was detected in the first matching file (column count + delimiter), so a mis-picked folder is obvious. Help text: 'Folder containing the recording files to analyse; defaults to "input".'
Decimal separator
input.format.decimal
Default "."
Unit / values '.' or ','
Decimal character used in delimited text files; a ',' decimal switches CSV parsing to ';'-separated.
When to change: Set to ',' for European instrument/Excel exports.
⚠ Honoured only for CSV and TXT (not Excel or MATLAB). The app auto-detects it for .txt when you assign channels; there is no dedicated Setup field, so it is set via the picker or edited in the TOML.
MATLAB file variant
input.format.matlab_variant
Default "mac"
Unit / values "windows" | "mac" (legacy 1 | 2)
Byte-order/layout for .mat input files: 'windows' reads a data_block1 structure, 'mac' reads each variable in column order.
When to change: Set to match the platform that exported the .mat files. Legacy value 1 → "windows", 2 → "mac".
⚠ Ignored for CSV/Excel/text. A wrong choice usually fails the .mat load with a 'verify the MATLAB file format' error. Only simple LabChart exports are supported; otherwise export to CSV.
Sampling frequency
input.format.sampling_frequency
Default (required — none)
Unit / values Hz (1–1000000)
Samples recorded per second; the analysis derives the whole time base (Ti, Te, Ttot, VE, PTP integration) from this, so every timing and per-minute number depends on it.
When to change: Must exactly match the acquisition system (e.g. 2000). The app can auto-detect it from the recording's time column when you assign channels.
⚠ Required — validation fails without it. Auto-detected from the time column on channel-setup OK; the widget shows 2000 as a display fallback when unset. Help text: 'Samples recorded per second, in hertz (Hz); required, and must match the acquisition system (e.g. 2000).'

Pre-analysis resampling

Optional resampling of every channel to one common rate before analysis. See: Resampling

Resample before analysis
processing.sampling.resample
Default false
Unit / values true|false
Resamples every recording to one common rate before analysis, fixing the analysis sampling frequency up front.
When to change: Turn on to normalise recordings taken at different rates, or to reduce cost. Default OFF keeps output byte-identical to a non-resampled run.
⚠ Default OFF ⇒ output is byte-identical to no resampling. When on and the target differs from the file rate, the whole pipeline (including the shared EMG-noise profile) runs at the new rate.
Resample-to frequency
processing.sampling.resample_to_frequency
Default 200
Unit / values Hz (1–1000000)
Target common rate (Hz) applied when resample is on; becomes the analysis fs for all downstream timing/PTP/segmentation.
When to change: Pick a rate high enough to preserve EMG bandwidth if EMG matters. Coupled to STFT params so the noise profile stays consistent.
⚠ Keep ≥ ~1000 Hz when EMG channels are present, or EMG detail is lost. Only used when 'Resample before analysis' is on. Help text: 'Target rate (Hz). Keep ≥ ~1000 Hz with EMG channels present.'

Breath segmentation

How the recording is cut into individual breaths. See: Breath segmentation

Breath-separation buffer
processing.segmentation.buffer
Default 800
Unit / values samples (0–100000)
Forward-looking guard window (in samples) used by the flow method: a phase continues while flow has the phase sign OR the mean over the next 'buffer' samples still has that sign, so brief zero-crossing wobble does not split a breath.
When to change: Increase if single breaths are being split into several by flow noise near zero; decrease if adjacent breaths are being merged.
⚠ Measured in SAMPLES, not seconds, so its time extent depends on the sampling frequency (800 samples = 0.4 s at 2000 Hz but 0.8 s at 1000 Hz). Re-tune it if you change fs or enable resampling.
Signal used to split breaths
processing.segmentation.method
Default "flow"
Unit / values "flow" | "volume"
Chooses which signal marks where each breath begins and ends: flow zero-crossings (default) or peak-detected volume.
When to change: Flow (default) is robust for most recordings; switch to volume when the flow zero-crossing is noisy but the volume peaks are clean.
⚠ UI tooltip: 'Which signal marks where each breath begins and ends.' The 'volume' method uses the peak.* settings; the 'flow' method uses buffer.
Breath peak — minimum distance
processing.segmentation.peak.distance_s
Default 0.1
Unit / values s (0–60)
Minimum time between detected breath peaks (converted internally to distance_s · fs samples).
When to change: Increase to prevent double-detection within one breath at high breathing frequency.
⚠ UI tooltip: 'Minimum time between detected breath peaks.' Volume method only.
Breath peak — minimum height
processing.segmentation.peak.height
Default 0.1
Unit / values signal units (0–1000000)
Minimum peak height for volume-based breath detection (find_peaks height on inspired and inverted volume).
When to change: Raise to ignore small non-breath fluctuations; lower to catch shallow breaths. Only used when method = 'volume'.
⚠ In the units of the segmentation signal (volume-based). Help text: 'Minimum peak height for breath detection (volume-based).'
Breath peak — minimum width
processing.segmentation.peak.width_s
Default 0.5
Unit / values s (0–60)
Minimum width of a detected breath peak (converted to width_s · fs samples).
When to change: Increase to reject narrow spikes; decrease for very short breaths.
⚠ UI tooltip: 'Minimum width of a detected breath peak.' Volume method only.

Volume & drift

Volume sign, integration from flow, and drift/trend correction. See: Volume, drift & trend · Integrating from flow

Correct volume drift
processing.volume.correct_drift
Default true
Unit / values true|false
Removes a slow linear baseline slope from the (zeroed) volume trace before segmentation.
When to change: Keep ON for flow-integrated volume, which drifts; disable only if your volume channel is already stable and you want the raw trace.
⚠ UI tooltip: 'Remove slow baseline drift from the volume trace; ON by default.' The linear de-trend leaves the last sample at 0 (a deliberate boundary quirk, not a bug).
Correct end-expiratory trend
processing.volume.correct_trend
Default false
Unit / values true|false
Subtracts an interpolated envelope through detected end-expiratory (volume trough) points to remove a between-breath trend in end-expiratory lung volume.
When to change: Enable when the operating volume wanders across a run (e.g. exercise) and you want each breath referenced to a common end-expiratory level.
⚠ UI tooltip: 'Remove a between-breath trend in end-expiratory lung volume.' Uses trend_method plus the trend_peak_* anchor thresholds, all of them under Preview & QC → Mechanics → Advanced…; also emits a diagnostic plot.
Calculate volume from flow
processing.volume.integrate_from_flow
Default false
Unit / values true|false
Derives volume by integrating flow (volume = −cumulative_trapezoid(flow, t)) instead of reading a separate volume channel.
When to change: Use when volume was not recorded directly. Makes input.channels.volume optional.
⚠ When on, the volume channel is no longer required, and the Setup channel summary shows 'Volume: derived from flow'. Help text: 'Derive volume by integrating flow instead of a separate channel.'
Invert the flow signal
processing.volume.inverse_flow
Default false
Unit / values true|false
Negates the flow channel at load time so the convention 'inspiration = negative flow' holds.
When to change: Turn on if your acquisition records inspiration as positive flow (RespMech's convention is inspiration = negative).
⚠ Applied before volume integration, so it also flips the sign of any flow-derived volume. Getting this wrong makes trimming fail ('flow never crosses zero') or produces upside-down breaths.
Invert the volume signal
processing.volume.inverse_volume
Default false
Unit / values true|false
Negates the volume channel at load time so inspired volume is positive.
When to change: Turn on if inspired volume reads negative on your rig (convention is inspired volume positive).
⚠ UI tooltip: 'Flip the volume sign.' Applied after integration.
Trend interpolation method
processing.volume.trend_method
Default "linear"
Unit / values linear | nearest | cubic | quadratic | previous | next (scipy interp1d kinds; also nearest-up, zero, slinear valid)
scipy interp1d 'kind' used to interpolate the end-expiratory trend envelope between detected troughs.
When to change: Use 'cubic'/'quadratic' for a smoother envelope, 'linear' (default) for a robust straight-line fit.
⚠ UI tooltip: 'How the end-expiratory trend is interpolated between breaths.' Validated against the scipy interp1d kinds; only used when correct_trend is on.
Trend anchor — minimum spacing
processing.volume.trend_peak_min_distance_s
Default 0.4
Unit / values s
Minimum time between detected end-expiratory trough points for the trend envelope (converted to samples via fs).
When to change: Set near the breath period so one trough per breath is picked.
⚠ Only used when correct_trend is on. Must be at least one sample at the analysis sampling rate.
Trend anchor — minimum breath depth
processing.volume.trend_peak_min_prominence_frac
Default 0.05
Unit / values fraction of the recording's volume range
How deep a trough must be to anchor the trend envelope, as a fraction of this recording's own volume range. The depth is the prominence of the trough — the smaller of the two inspiratory excursions flanking it, i.e. roughly one tidal volume — so the criterion is per breath and scale-free.
When to change: Rarely. Across the validation recordings the detected anchors are identical anywhere between 0.005 and 0.20; lower it only if breaths are missed on a recording that also contains a large manoeuvre (an inspiratory capacity, a sigh or a cough inflates the volume range).
⚠ Only used when correct_trend is on, and only while trend_peak_min_height is unset.
Trend anchor — absolute threshold (legacy)
processing.volume.trend_peak_min_height
Default unset (auto)
Unit / values volume units (L)
The pre-v2.3.3 rule: a trough qualifies only if it lies at least this far below the highest volume in the whole recording. It is not a per-breath measure, and it does not scale — which is why it is no longer the default.
When to change: Only to reproduce an older analysis exactly. Setting it overrides the breath-depth rule.
⚠ A value larger than the recording's volume range matches no trough at all; the run then stops on that file with an explanatory error rather than producing numbers. Analyses saved before v2.3.3 carried 0.8 as an inherited default and are read as unset on load, which is reported in the GUI and at the top of the run report.

Work of breathing

How work of breathing is read from the Campbell diagram. See: Work of breathing · Average vs individual

Average-breath resampling points
processing.wob.avg_resampling_obs
Default 500
Unit / values count of samples; UI range 10–100000, step 10
Number of points each phase (inspiration, expiration) is resampled to when building the averaged breath — the averaged breath therefore has twice this many points when building the averaged breath used for the average P–V loop / WOB. (A Work-of-Breathing / averaging setting; does not affect the per-breath mechanics table.)
When to change: Raise it for finer resolution of the averaged pressure–volume loop (smoother Campbell figure, more precise area) when breaths are long / sampled at high rate. The default 500 is ample for typical breaths; there is little benefit above a few hundred and it only costs a little compute.
⚠ If resampling of a breath fails, that one file stops with "Could not resample breath #N: it is too short to average. Check Preview & QC ▸ Mechanics ▸ Advanced… ▸ Breath detection (peak thresholds / breath-separation buffer), or exclude this breath in Preview & QC." — the rest of the batch still runs. This setting is only the output length and cannot cause or prevent the failure (a segmentation or peak-detection problem always can). Very low values coarsen the averaged loop and can bias the measured area. Only affects the AVERAGE breath (and thus "average"-mode WOB and the averaged Campbell figure); it does not change "individual"-mode WOB. UI label "Average-breath resampling points"; tooltip: "Points each breath is resampled to for the average breath / WOB." The TOML key avg_resampling_obs and the legacy name avgresamplingobs mentioned in error messages refer to the same setting.
Work of breathing from
processing.wob.calc_from
Default "average"
Unit / values "average" | "individual"
Whether WOB and the P–V (Campbell) representation use one averaged breath (default) or each breath computed then averaged. (Primarily a Work-of-Breathing setting; listed here because it lives in the same Mechanics panel and shapes the averaged-breath representation.)
When to change: Use "average" (default) for robustness when breaths are irregular or noisy — the averaging smooths breath-to-breath wobble before the area is measured. Switch to "individual" when you need each breath measured on its own trace (e.g. to inspect breath-to-breath variability of WOB) and the breaths are clean.
⚠ In "average" mode every per-breath row in the breath-by-breath workbook shows the SAME WOB value (the averaged-breath result), which can look like a bug but is expected. UI label is "Work of breathing from"; tooltip: "One averaged breath (default), or each breath then averaged." The TOML key calc_from and the legacy name calcwobfrom mentioned in error messages refer to the same setting.

Diaphragm EMG

The diaphragm-EMG amplitude measures and ECG-artefact removal. See: Diaphragm EMG · ECG removal

Auto (whole batch)
processing.emg.ecg_auto_detect
Default false
Unit / values true/false
Derives the ECG capture settings once from a reference recording and applies them unchanged to every file in the batch — the batch equivalent of the Auto-suggest button, mirroring how noise.auto_prop works for noise reduction. While it is on, the capture channel, Min height, Min gap, Auto-suggest and Advanced… are disabled: the run overwrites them. Each file reports a detection-quality warning in run-report.txt if the shared settings look like they are missing beats, so an unsupervised batch still leaves a visible trail.⚠ Requires processing.emg.remove_ecg and configured EMG channels — the settings file is refused otherwise. On the Preview & QC ▸ › EMG – ECG reduction strip it is the Auto (whole batch) tickbox.
ECG reference recording
processing.emg.ecg_reference_file
Default unset
Unit / values a filename in input.folder
Which recording Auto (whole batch) learns the ECG parameters from. Unset means the first file the input pattern matches. Pick the file with the clearest heartbeat.⚠ No GUI control — settings-file only. A name that does not resolve inside input.folder stops the run with an error naming the path it tried.
ECG detection channel
processing.emg.detect_channel
Default 0
Unit / values 0-based INDEX into input.channels.emg
Which EMG channel the R-waves (heartbeats) are detected on for ECG removal. Detection runs on the DC-removed positive signal.
When to change: Choose the channel with the clearest R-wave and weakest EMG (often the middle electrode).
⚠ It is an INDEX into the EMG list, not a column number — re-assigning EMG channels re-points it; the preview seeds it to the middle channel and clamps out-of-range values with a status warning. Help text: 'EMG channel used to detect the R-waves (heartbeats). Pick the channel with the clearest ECG and weakest EMG (often the middle channel).'
ECG R-wave min distance
processing.emg.ecg_min_distance_s
Default 0.5
Unit / values s (UI 0.05-2.0)
Minimum time between detected heartbeats (find_peaks distance) - the refractory gap.
When to change: Sets the maximum detectable heart rate = 60/gap bpm. Lower it for fast (exercise) heart rates so real beats are not merged/rejected.
⚠ Doubles as the cardiac-gated peak's heart-rate ceiling: 60/ecg_min_distance_s bpm. The shipped 0.3613 s -> 166 bpm ceiling refuses gating on a 162 bpm file. UI tooltip: 'Minimum time between heartbeats (the refractory gap).'
ECG R-wave min height
processing.emg.ecg_min_height
Default 0.0005
Unit / values signal units (a.u.); UI 0-1,000,000, 6 decimals
Minimum height of an R-wave peak on the capture channel for it to count as a heartbeat (find_peaks height).
When to change: Raise to reject small non-cardiac spikes; lower if real beats are being missed. Best set by Auto-suggest or by watching the detected-peak markers.
⚠ UI tooltip: 'Minimum height of an R-wave peak (in signal units) on the capture channel.' In the same arbitrary units as the raw EMG, so it is electrode/recording specific.
ECG R-wave min width
processing.emg.ecg_min_width_s
Default 0.001
Unit / values s (0–0.1, 4 dp)
Minimum width of an R-wave peak (find_peaks width) - a shape guard against counting a narrow spike as a heartbeat.
When to change: Rarely changed; raise slightly if sharp non-cardiac transients are being detected as beats.
⚠ Lives in the ECG-removal Advanced… modal, not the strip. Help text: 'Minimum width of an R-wave peak. A shape guard against counting a narrow spike as a heartbeat; the default rarely needs moving.'
ECG averaging window
processing.emg.ecg_window_s
Default 0.4
Unit / values s (UI 0.05-1.0)
Width of the ECG template (QRS-T span) averaged and subtracted around each beat; the template spans +/- ecg_window_s/2 around each R-peak.
When to change: Physiologically fixed - 0.4 s captures the QRS-T complex in adults. Must not overlap neighbouring beats.
⚠ Lives in the ECG-removal 'advanced' dialog. UI tooltip: 'Width of the ECG template averaged and subtracted around each beat (QRS-T). Physiologically fixed - 0.4 s is right for adults.'
EMG output normalisation
processing.emg.normalization
Default "per_file_max"
Unit / values "none" | "per_file_max" | "per_file_mean"
Adds an 'EMG normalised' sheet to each breath workbook that re-expresses every RMS column as a percentage of that column's own peak or mean value across the file's breaths — see Amplitude normalisation for how this differs from a true cross-file reference (normalization_reference_file).
When to change: EMG amplitude is uncalibrated, so cross-subject/electrode comparison needs normalising to each file's own reference. per_file_max = % of the peak breath; per_file_mean = % of the mean breath.
⚠ Never changes the raw RMS - it only adds a sheet. UI tooltip: 'Also report each file's EMG RMS as a percentage of a per-file reference (adds a normalised sheet to the output; never changes the raw RMS).' Under per_file_max the denominator is itself a per-breath maximum and can be beat-locked - a reason to enable the gated peak.
EMG normalisation reference file
processing.emg.normalization_reference_file
Default unset
Unit / values a filename already in this batch
Normalises every file's RMS columns against this ONE file's own per_file_max/per_file_mean instead of each file's own — typically a maximal inspiratory/expiratory manoeuvre recorded once per subject, so a percentage means the same thing across the whole study instead of every file trivially reaching 100% at its own peak.
When to change: Set it whenever files in the same study need amplitudes comparable to each other, not just internally consistent.
⚠ No GUI control — settings-file only. Unset (the default) reproduces the plain per-file behaviour above exactly. A name not among this batch's successfully processed files is silently ignored (falls back to per-file), rather than stopping the run.
Outlier RMS SD limit (edits output values)
processing.emg.outlier_rms_sd_limit
Default 0.0
Unit / values SD (0 = off; UI 0-10)
Before the tables are built, replaces any breath whose RMS/|poes_mininsp| lies more than this many SD from the other breaths' mean with the other breaths' mean RMS.
When to change: Use to suppress a single artefactual breath's RMS from distorting the file average. 0 disables it (default).
⚠ 0 disables it (default). When >0 it CHANGES the rms_max/rms_mean values written to the output tables (it edits values, not just flags them). UI tooltip: 'Replace any breath's EMG RMS lying more than this many SD from the across-breath mean; 0 = off (default).'
EMG figure y-scale
processing.emg.plot_yscale
Default [-0.1, 0.1]
Unit / values [ymin, ymax] in a.u.
Fixed y-axis range for the EMG channel-overview diagnostic figures.
When to change: Pin a common amplitude window so channels/stages/files are visually comparable, or widen it if the signal clips the default range.
⚠ Only affects the EMG overview figures; ignored unless it is a valid 2-element range with ymin < ymax (otherwise autoscale).
Remove ECG
processing.emg.remove_ecg
Default false
Unit / values true | false
Subtract an averaged, per-beat time-and-amplitude-fitted ECG template from every EMG channel to remove the cardiac artefact.
When to change: Turn on whenever the heartbeat contaminates the EMG (diaphragm/oesophageal recordings). Also the prerequisite for the cardiac-gated peak.
⚠ Off by default. UI tooltip: 'Subtract an averaged ECG template from every EMG channel; off by default.' Detection/template parameters are test-level and applied identically to every file - never re-tune per file.
RMS window length
processing.emg.rms_window_s
Default 0.05
Unit / values s (range 0.01-0.5 in UI)
Length of the sliding window whose RMS is measured; each breath reports the largest windowed value. Defines the reported EMG amplitude and the gated peak's envelope.
When to change: Shorter windows track fast bursts and give a higher, spikier peak; longer windows smooth the envelope. 0.05 s is RespMech's default and the value the reference pipeline was built with, not a validated standard for surface/oesophageal EMGdi.
⚠ Golden-pinned at the default. UI tooltip: 'Sliding-window length for the EMG RMS envelope; each breath takes its largest windowed value (default 0.05). Defines the reported EMG number.' The effective window is L-1 samples, an intentional legacy off-by-one.
Export each EMG stage as WAV
processing.emg.save_sound
Default false
Unit / values true | false
Exports each EMG channel, at each conditioning stage, as a normalised int16 WAV file (at the analysis sample rate) into the diagnostics folder.
When to change: Listening to the EMG is a fast way to judge ECG contamination / noise-reduction quality.
⚠ Adds several files per recording; off by default. EMG Advanced… modal. Help text: 'Writes an audio file per channel per conditioning stage. Diagnostic; off by default.'

EMG spectral noise reduction

Spectral noise reduction, trained on a shared rest reference. See: Spectral noise reduction

Auto (pick prop_decrease)
processing.emg.noise.auto_prop
Default true
Unit / values true | false
Automatically pick, once per test, the strongest prop_decrease that keeps every channel's fidelity at or above the target.
When to change: Leave on to let the fidelity gate choose safely; turn off only to force a fixed prop_decrease.
⚠ When on, prop_decrease (Advanced… → Noise suppression, or the TOML) is ignored for the run and the chosen value is applied identically to all files. UI tooltip: 'Automatically picks the strongest suppression that still keeps every channel at or above the fidelity target; on by default.'
Noise reduction enabled
processing.emg.noise.enabled
Default false
Unit / values true | false
Enable shared-profile spectral noise reduction: subtract a spectral noise profile (built from a rest reference) from every EMG channel.
When to change: Turn on to remove a stationary broadband/electrical noise floor (e.g. the 20-50 Hz floor) while preserving the 20-250 Hz band that carries the diaphragm EMG power.
⚠ Requires 'Remove ECG' on (the profile is measured on the ECG-cleaned signal) AND a reference set. In a settings file there is no such gate: noise reduction runs whenever it is enabled and EMG columns exist, and a missing reference_file stops the run with an error. Help text: 'Subtract a shared spectral noise profile (built from a rest reference) from every EMG channel. Pick the reference with "Set noise profile"; off by default.'
Fidelity target (Keep ≥)
processing.emg.noise.fidelity_target
Default 0.8
Unit / values fraction 0-1 (UI 0.50-0.99)
The smallest fraction of inspiratory in-band (20-250 Hz) EMG power that must survive noise removal; the auto search keeps the worst channel at or above this.
When to change: 0.8 (retain >=80% of EMG power) is the validated over-subtraction guard. Lower only if you accept more signal loss for more suppression.
⚠ Only meaningful with Auto on. UI tooltip: 'Smallest fraction of inspiratory EMG power that must survive noise removal, from 0 to 1; default 0.8.'
STFT hop
processing.emg.noise.hop_length
Default 64
Unit / values samples (UI 1-8192)
Advance between successive STFT windows, in samples.
When to change: Smaller hop = more overlap and smoother gating at higher cost. Default 64 (n_fft/4).
⚠ EMG Advanced… modal. Help text: 'Advance between successive windows, in samples.'
STFT length (n_fft)
processing.emg.noise.n_fft
Default 256
Unit / values samples, power of two (UI 16-8192)
FFT length in samples for the spectral gate.
When to change: 256 (~128 ms at 2 kHz) is tuned for the 20-250 Hz EMG band and rarely changed. Kept separate from win_length on purpose.
⚠ Decoupled from the noise-clip length - the key fix vs the legacy len(noise)^2 bug. Under pre-analysis resampling it is auto-scaled to hold a fixed time window. UI tooltip: 'FFT length in samples for the spectral gate; a power of two.'
Mask smoothing — frequency (n_grad_freq)
processing.emg.noise.n_grad_freq
Default 0
Unit / values frequency bins (UI 0-64)
Number of frequency bins the suppression mask is smoothed over.
When to change: Increase to soften spectral edges of the mask; 0 is the tuned default.
⚠ EMG Advanced… modal. Help text: 'Frequency bins the suppression mask is smoothed over.'
Mask smoothing — time (n_grad_time)
processing.emg.noise.n_grad_time
Default 4
Unit / values time frames (UI 0-64)
Number of STFT time frames the suppression mask is smoothed over.
When to change: Smooths gating across time to avoid musical-noise artefacts; 4 is the tuned default.
⚠ EMG Advanced… modal. Help text: 'Time frames the suppression mask is smoothed over.'
Spectral gate threshold
processing.emg.noise.n_std_thresh
Default 1.0
Unit / values standard deviations (UI 0.0-10.0)
How many SD above the mean noise level a frequency bin must exceed to survive the gate (threshold = mean + n_std_thresh*std).
When to change: This parameter dominates over-subtraction: 1.0 is gentle (fidelity 0.84-1.07); >=1.5 destroys real EMG. Essentially never changed.
⚠ Raising it looks like it improves SNR but collapses fidelity (the 'great SNR trap'). UI tooltip: 'How many standard deviations above the noise profile a frequency bin must be to survive. The default of 1.0 is essentially never changed.'
Suppression strength (prop_decrease)
processing.emg.noise.prop_decrease
Default 0.6
Unit / values 0.0 (none) - 1.0 (maximum)
How aggressively noise is removed when Auto is off: fraction of the below-threshold energy that is attenuated.
When to change: Trades delta-SNR against fidelity along a smooth frontier; 0.5-0.7 is a safe range (fidelity 0.91-0.97), 1.0 gives max delta-SNR at fidelity ~0.84.
⚠ Only used when auto_prop is false. With Auto on, this value is overwritten by the auto-picked one. UI tooltip: 'How aggressively to remove noise when auto is off, from 0 (none) to 1 (maximum); default 0.6.'
Noise reference file
processing.emg.noise.reference_file
Default null (none)
Unit / values filename in the input folder
The EMG-free rest recording the shared noise profile is built from (one profile per channel, applied to every file).
When to change: Should be a diaphragm-quiet rest reference with several seconds of EMG-free signal for a stable per-frequency estimate.
⚠ Required when noise reduction is enabled, or the run raises an error. Not a single 0.05 s gap - the legacy short-clip approach was degenerate.
Noise reference intervals
processing.emg.noise.reference_intervals
Default []
Unit / values list of [start_s, end_s] seconds in reference_file
Explicit EMG-free windows in the reference file to build the noise profile from, used only when use_expiration is false.
When to change: Use when you want to hand-pick quiet spans rather than trust automatic expiration detection.
⚠ Ignored while use_expiration is true. If empty and use_expiration is false, the code still falls back to expiration.
Use the whole expiration of this recording
processing.emg.noise.use_expiration
Default true
Unit / values true | false
Build the noise reference from every expiration of the reference file (diaphragm-quiet, giving ~hundreds of stable STFT frames) instead of explicit intervals.
When to change: Default true because expiration is diaphragm-quiet and yields a far more stable per-frequency estimate (~500 frames vs ~7 for a short gap).
⚠ Set in the Set-noise-profile dialog. Help text: 'Sample the noise profile from every expiratory phase, which is diaphragm-quiet and gives a more stable estimate than one hand-marked span. Untick to mark a rest span yourself.'
STFT window
processing.emg.noise.win_length
Default 256
Unit / values samples (UI 16-8192)
Analysis window length in samples for the STFT.
When to change: Normally equals n_fft. Kept as a separate control on purpose.
⚠ Deliberately NOT collapsed into n_fft: writing both from one control would change output for any analysis where they differ. UI tooltip warns of exactly this.

Cardiac-gated peak EMG

Optional cardiac-gated peak EMG for strongly heart-coupled recordings. See: Cardiac-gated peak EMG

Cardiac-gated peak EMG (adds columns)
processing.emg.robust_peak.enabled
Default false
Unit / values true | false
When on, ADDS gated RMS columns (rms_gated_col_<n>, rms_gated_max/mean, and _insp/_exp variants) to the breath-by-breath and average tables, computed from cardiac-free signal.
When to change: On strongly cardiac-coupled recordings the plain RMS maximum can sit on a residual heartbeat; the gated columns read the peak from signal blanked around each R-peak.
⚠ Requires 'Remove ECG' on. Adds 'gated' columns to the saved data; changes NOTHING on the live plots and never alters existing numbers. Help text: '...Tick this to ALSO measure the peak from just the heartbeat-free stretches of each breath. It has no effect on the live plots here: it adds extra "gated" columns to the saved data...The existing numbers never change.'
Blanked around each heartbeat (± half-width)
processing.emg.robust_peak.gate_half_width_s
Default 0.12
Unit / values s HALF-width (UI +/- 0.02-0.5)
How much of the RMS envelope is blanked either side of each detected R-peak before taking the surviving maximum.
When to change: Must cover the heartbeat's footprint in the RMS envelope ~ rms_window_s + QRS duration; 120 ms covers it with margin.
⚠ It is a HALF-width — the field shows '± 0.120 s' meaning a 0.240 s total blanked window. Help text: 'Blanked either side of each detected heartbeat, so the default 0.120 discards a 0.240 s window in total...'⚠ Lives in Preview & QC → EMG – noise reduction → Advanced…, under Gated peak (saved output).
Heart-rate ceiling margin (hr_ceiling_margin)
processing.emg.robust_peak.hr_ceiling_margin
Default 0.1
Unit / values fraction 0-1 (UI 0.0-0.5)
How close the detected heart rate may come to the detector's refractory ceiling (60/ecg_min_distance_s bpm) before the gated result is distrusted (NaN).
When to change: Near the ceiling the detector starts missing real beats, which is exactly where gating breaks.
⚠ If it fires, the fix is usually to LOWER Min gap on the ECG-reduction tab, not to raise this. Help text: '...If this is firing, the fix is usually to lower Min gap on the › EMG – ECG reduction tab, not to raise this.'
Missed-heartbeat factor (long_rr_factor)
processing.emg.robust_peak.long_rr_factor
Default 1.6
Unit / values x median RR (UI 1.1-5.0)
An RR interval this many times the median RR is treated as a missed heartbeat.
When to change: A missed beat produces a roughly doubled interval; 1.6x reliably flags it without false positives.
⚠ Feeds the per-file detection guard. UI tooltip: 'A gap this many times the median R-R is treated as a missed heartbeat.'
Missed heartbeats tolerated (max_long_rr_frac)
processing.emg.robust_peak.max_long_rr_frac
Default 0.02
Unit / values fraction 0-1 (UI 0.0-1.0)
Maximum tolerated fraction of long (missed-beat) RR intervals before the file's R-peak detection is distrusted and all gated columns become NaN.
When to change: Gating on incomplete detection reports a heartbeat with extra confidence, so the whole file is refused above this.
⚠ On production data 0.02 flags exactly the file with known-broken gating (H6_Peak220W at 0.267). UI tooltip: 'Above this fraction of long gaps the detection is not trusted and the gated columns are left blank.'
Shortest usable stretch between beats (min_island_s)
processing.emg.robust_peak.min_island_s
Default 0.2
Unit / values s (UI 0.0-2.0)
Minimum length of the longest contiguous cardiac-free run for a phase to be measurable; shorter and the phase is NaN.
When to change: A gap shorter than this cannot hold a full RMS window, so it cannot yield a valid peak.
⚠ EMG Advanced… modal. Help text: 'Gaps shorter than this cannot hold a full RMS window, so they are ignored.'
Least of each phase that must survive (min_survival)
processing.emg.robust_peak.min_survival
Default 0.4
Unit / values fraction 0-1 (UI 0.0-1.0)
Minimum fraction of a phase that must remain outside the gates, otherwise that phase's gated value is NaN.
When to change: Prevents reporting a peak read from a sliver of breath when too much has been blanked (fast heart rate).
⚠ A phase guard - trips independently for inspiration/expiration/whole breath. UI tooltip: 'Below this fraction the gated value is left blank rather than reported from a sliver of breath. Measured default 0.40.'

Sample entropy

Sample entropy of the ticked channels. See: Sample entropy

Entropy epochs
processing.entropy.epochs
Default 3
Unit / values 1–100 (GUI range); dimensionless count
The template length (m + 1) for sample entropy — how many consecutive samples must match before checking whether the match extends by one more sample. UI tooltip: "Template length for sample entropy, one more than the embedding dimension. The default 3 gives m = 2, which is conventional in the literature; set 2 for the m = 1 RespMech used before this change."
When to change: m = 2 (the current default) is the near-universal convention in physiological complexity analysis and rarely needs changing. Larger m demands longer matching sub-sequences, which needs more data to estimate reliably; for short per-breath EMG segments, raising m can make SampEn undefined or unstable.
⚠ The value is passed to the vendored routine as the LONGEST template length (`sample_length`), and RespMech reports the last element of the SampEn vector. So with the default epochs=3 the reported number is −ln(count of length-3 matches / count of length-2 matches), i.e. m = epochs−1 = 2 in Richman–Moorman notation. To reproduce the m=1 statistic RespMech reported before this default changed, set epochs=2. GUI spin box accepts 1–100.
Entropy tolerance
processing.entropy.tolerance
Default 0.1
Unit / values 0.0–10.0 (GUI range); fraction of channel SD
The matching tolerance r, expressed as a FRACTION of the signal's standard deviation. Two samples count as a match when they differ by less than r × SD. UI tooltip: "Matching tolerance r, as a multiple of the per-column standard deviation. Published values are typically 0.1-0.25 × SD, with 0.2 × SD the most common; RespMech defaults to 0.1 × SD."
When to change: r is what makes SampEn scale-invariant (uncalibrated EMG amplitude cancels out). Published values are typically 0.1–0.25 × SD, with 0.2 × SD the most widely used default (Richman & Moorman 2000; Yentes et al. 2013 — full citations in References › Sample entropy); RespMech defaults to the lower end. A larger r is more forgiving (more matches, lower SampEn, less noise sensitivity but coarser discrimination); a smaller r is stricter (fewer matches, higher SampEn, but risks too few matches to estimate reliably).
⚠ It is a FRACTION, not an absolute amplitude — RespMech multiplies it by the SD of the exact segment being analysed (whole breath / inspiration / expiration each get their own absolute tolerance). Setting it to 0 would make matches essentially impossible. GUI spin box accepts 0.0–10.0 with 4 decimals, step 0.05.

Pressure–time product (PTP)

The baseline window for the pressure–time products. See: Pressure–time product

PTP baseline window
processing.ptp.baseline_window_s
Default 0.05
Unit / values s (0.0–1.0)
Length of the window at each phase start whose mean is the pressure–time-product baseline (end-expiratory for inspiration, end-inspiratory for expiration).
When to change: A window (vs a single sample) is robust to boundary noise; widen slightly if the phase start is noisy.
⚠ UI tooltip: 'End-expiratory window whose mean is the PTP baseline.' Internally n = max(1, round(baseline_window_s · fs)); a value small enough that n = 1 reproduces the legacy single-sample baseline. Affects all int_*/ptp_* outputs.

Exclusions & breath-count overrides

Dropping specific breaths and overriding per-file breath counts. See: Excluding breaths

Breath-count overrides
processing.breath_counts
Default []
Unit / values e.g. {file="x.txt", count=12}
Overrides, per file, the breath count used for per-minute scaling (bf and VE = VT · count · vefactor).
When to change: Use when the detected breath count differs from the true count (e.g. a partial breath at the recording ends) so bf/VE are scaled correctly.
⚠ Blank ⇒ each file's detected count. Parsed by splitting on the LAST '=' so filenames may contain one. Help text: 'Per-file override of the breath count used for per-minute scaling — one "filename = count" per line. Blank = each file's detected count.'
Excluded breaths
processing.exclude_breaths
Default []
Unit / values per-file list of 1-based breath numbers
Marks named breaths as 'ignored': they are dropped from the per-file averages and workbooks (but still drawn in the diagnostic plots).
When to change: Drop artefactual or non-representative breaths (swallows, coughs, sighs) from the averages.
⚠ Keyed by file BASENAME plus the recordings folder it was made in. Ignored breaths stay drawn/numbered in plots but are dropped from the average workbook and per-breath workbook; they appear in the processed CSV only if include_ignored_breaths is on. In the app you exclude a breath by clicking its shaded region in the Mechanics preview (red = excluded). Pointing the analysis at a different recordings folder shows a Keep/Clear banner in Setup instead of silently carrying the entry over or dropping it.

Output

Which workbooks, sheets and diagnostic plots are written. See: Outputs

Include excluded breaths in the processed CSV
output.data.include_ignored_breaths
Default false
Unit / values true|false
Include excluded breaths in the per-file processed-signal CSV only.
When to change: Turn on when you want the full recording (including rejected breaths) in the exported waveform for review.
⚠ Only affects the processed CSV — excluded breaths are ALWAYS left out of the averages and workbooks. Help text: 'Include the breaths you've excluded in the per-file processed-signal CSV only. Excluded breaths are always left out of the averages and workbooks; this does not change them.'
Average breath-data workbook
output.data.save_average
Default true
Unit / values true|false
Writes 'Average breathdata.xlsx' — one row per recording, each value meaned across that file's non-ignored breaths.
When to change: The headline per-recording numbers most analyses need; turn off only for a figures-only or CSV-only run.
⚠ Turning this on also triggers 'Cohort summary.xlsx' automatically (they are paired); there is no separate switch for the cohort summary.
Breath-by-breath workbook (per file)
output.data.save_breath_by_breath
Default true
Unit / values true|false
Writes one '<file>.breathdata.xlsx' per recording with one row per breath (plus the EMG-normalised sheet when normalisation is on).
When to change: Needed for per-breath inspection; also produces the normalised-EMG sheet when EMG + normalisation are configured.
⚠ One workbook per input file — a large batch produces many files.
Processed-signal CSV (per file)
output.data.save_processed
Default false
Unit / values true|false
Writes '<file> – Processed data.csv' — the trimmed per-sample signals (Time, Breathno, Flow, Volume, Poes, Pgas, Pdi, EMG<n>).
When to change: For re-plotting or re-analysing the conditioned waveforms outside RespMech.
⚠ Off by default (legacy default was off). These files can be large (one row per sample). Units are not written in the CSV header.
Campbell grid columns
output.diagnostics.pv_columns
Default 3
Unit / values columns per page (>= 1)
Number of Campbell panels per row in the paginated per-breath / cohort grid PDFs.
When to change: Increase for more breaths per page (denser overview); decrease for larger, more legible individual loops.
⚠ No GUI control — TOML-only. Values are floored to at least 1. Only affects the gridded figures (save_pv_individual / cohort), not the averaged single-loop figure.
Campbell grid rows
output.diagnostics.pv_rows
Default 4
Unit / values rows per page (>= 1)
Number of Campbell panel rows per page in the paginated per-breath / cohort grid PDFs (so pv_columns × pv_rows panels per page).
When to change: Increase for more breaths per page; decrease for larger panels.
⚠ No GUI control — TOML-only. Floored to at least 1.
Drift-correction figures
output.diagnostics.save_drift
Default true
Unit / values true|false
Writes THREE figures: 'volume correction.pdf' (staged: uncorrected → zeroed → drift-corrected → trend-adjusted), 'volume trend.pdf' (trend diagnostic, only when trend correction is on), and 'volume endpoints.pdf' (end-inspiratory/expiratory volume per breath — a drift check).
When to change: Verify each stage of volume conditioning did what you intended.
⚠ The trend figure is skipped unless processing.volume.correct_trend is on, and when the recording yields too few end-expiratory anchors for the chosen trend_method (the run stops on that file in that case); the drift-corrected stage is only drawn when drift correction actually ran.
EMG channel overviews
output.diagnostics.save_emg
Default true
Unit / values true|false
Writes one PDF per EMG conditioning stage present — '<file> – Raw EMG.pdf', '<file> – EMG (ECG removed).pdf', '<file> – EMG (ECG removed + noise reduced).pdf' — one stacked panel per channel, with the flow reference on a twin axis, R-peak capture markers and breath boundaries.
When to change: Judge ECG removal and noise reduction quality per channel.
⚠ Help text: 'Per-channel EMG overview figures (raw / ECG-removed / noise-reduced) with the flow reference and R-peak capture markers.'
Campbell / PV diagram — averaged
output.diagnostics.save_pv_average
Default true
Unit / values true | false
Writes a per-file Campbell (Poes–Volume) PDF: every breath overlaid faintly, the average breath drawn bold, with the elastic-recoil line and the shaded elastic-WOB triangle. Output file: "<file> – Campbell (average).pdf" under <output>/diagnostics/.
When to change: Turn on to visually verify the averaged loop and the WOB polygon look physiological (proper open loop, sensible recoil line). Turn off to speed up batch runs when you only need the numbers.
⚠ The diagnostic PDF plots Volume on the x-axis (inverted) and Poes on the y-axis — a different axis convention from the Preview screen's live Campbell (Poes on x, volume on y). Same diagram, transposed axes. UI label "Campbell / PV diagram — averaged"; tooltip: "The averaged Campbell (pressure–volume) diagram."
Campbell / PV diagram — individual breaths
output.diagnostics.save_pv_individual
Default true
Unit / values true | false
Writes a paginated grid PDF of one Campbell loop per breath (recoil line + WOB polygon, shared axes, inverted x-axis, ignored breaths crossed out): "<file> – Campbell (breaths).pdf". When the batch has more than one file it also writes a cohort overview "All files – Campbell (average).pdf" with one mean loop per file.
When to change: Turn on to QC individual breaths — spot atypical loops, mis-segmented breaths, or breaths you should exclude. Turn off for faster/leaner output.
⚠ Grid layout is controlled by output.diagnostics.pv_columns/pv_rows. The cohort "All files" page is only produced when >1 file succeeds. UI label "Campbell / PV diagram — individual breaths"; tooltip: "A Campbell diagram per individual breath."
Raw-signal figures
output.diagnostics.save_raw
Default true
Unit / values true|false
Writes '<file> – signals (raw).pdf': the FULL untrimmed recording (flow, volume, Poes, Pgas, Pdi) in real time, with breath boundaries and ignored-breath shading.
When to change: Pre-trim QC — verify segmentation and spot artefacts across the whole recording.
⚠ Requires the raw signals to have been retained; a panel is drawn only for channels that are present.
Trimmed-signal figures
output.diagnostics.save_trimmed
Default true
Unit / values true|false
Writes '<file> – signals (trimmed).pdf': the analysed (trimmed) signals with breaths concatenated and boundaries marked.
When to change: Confirm exactly which samples entered the analysis after trimming.
⚠ X-axis is 'sample (trimmed, breaths concatenated)', not real time.
Output folder
output.folder
Default "output"
Unit / values folder path
Root folder for all results; numeric tables go to a 'data' subfolder, figures to a 'diagnostics' subfolder, and the two provenance files at the root.
When to change: Point it at a per-study or per-session folder so each run's results stay together with their settings snapshot.
⚠ A run warns before overwriting an output folder that already contains results. Help text: 'Where results are saved; files are written to a "data" subfolder inside it; defaults to "output".'
Cohort group regex
output.group_regex
Default none (leading filename token)
Unit / values regex with one capture group, or blank
How files are grouped (subject/condition) for the cohort 'By group' summary. Blank = the leading filename token (up to the first space/underscore/hyphen, e.g. 'P03_120W' → 'P03'); otherwise a regular expression whose FIRST capture group is the key.
When to change: Set to group files into subjects/conditions in the summary. If null, the leading filename token is used (e.g. "P03_120W" → "P03").
⚠ Blank ⇒ the leading filename token is used (e.g. P03_120W → P03). Help text: 'How files are grouped (subject / condition) for the by-group summary. Leave blank to use the leading filename token; or enter a regular expression whose first capture group is the key.'

Outputs

When a batch finishes, RespMech writes a self-contained, publication-ready result folder. The compute core itself writes nothing to disk — a separate writer turns the finished result into files, so what you see on disk is a faithful snapshot of what was computed. Everything lands under one output folder you choose: numeric tables in a data/ subfolder, diagnostic figures in a diagnostics/ subfolder, and two provenance files at the top so a result is never orphaned from the exact settings that produced it.

You control what is written with a set of tickboxes on the Setup screen's Output card, plus a few EMG-output controls on the Preview & QC screen. The run report then records exactly what was read, kept, excluded and written.

The results folder

For an output folder named output (the default), a completed run produces this layout:

  • output/
    • analysis-used.toml — the exact settings that produced this run, reloadable to reproduce it.
    • run-report.txt — a plain-text log of what was read, kept, excluded, written, plus ECG/noise diagnostics.
    • data/
      • Average breathdata.xlsx — one row per recording (mean across each file's kept breaths).
      • <file>.breathdata.xlsx — one workbook per recording, one row per breath.
      • Cohort summary.xlsx — mean ± SD, n and CV% across the batch, with a by-group breakdown.
      • <file> – Processed data.csv — optional per-sample trimmed signals (one CSV per recording).
    • diagnostics/
      • *.pdf — the diagnostic figures (Campbell, raw/trimmed signals, volume correction, EMG overviews).
      • *.wav — optional EMG audio, one file per channel per conditioning stage.

The output folder and its data/ and diagnostics/ subfolders are created if missing. Files with the same names are overwritten silently, so point each run at a per-study or per-session folder to keep results and their settings snapshot together.

Filenames contain a literal en-dash (–) and spaces, e.g. <file> – Processed data.csv and <file> – Campbell (average).pdf. If you glob the output folder from a downstream script, match the en-dash character, not a hyphen.

Choosing what gets written

The Setup screen's Output card holds the output folder, a Tables checklist, a Diagnostic figures checklist, and a Group files by box for cohort grouping. A live "You will get" line lists the exact deliverables the current ticks will write, so you can confirm before running.

The Setup Output card with an output-folder field, a Tables checklist, a Diagnostic figures checklist, a Group files by box, and a live You will get line summarising the deliverables.
The Output card, the right-hand column of Setup. The "You will get" line updates as you tick outputs on and off.
Output folder
output.folder
Default output
Unit / values path
Root folder for all results. Numeric tables go to a data/ subfolder, figures to diagnostics/, and the two provenance files to the root. Point it at a per-study or per-session folder.
Average breath-data workbook
output.data.save_average
Default true
Unit / values true/false
Writes Average breathdata.xlsx — the headline per-recording numbers. Turning this on also triggers Cohort summary.xlsx; there is no separate cohort switch. Turn off only for a figures-only or CSV-only run.
Breath-by-breath workbook (per file)
output.data.save_breath_by_breath
Default true
Unit / values true/false
Writes one <file>.breathdata.xlsx per recording, one row per breath. Needed to inspect breath-to-breath variability or do your own averaging downstream. A large batch produces many files.
Processed-signal CSV (per file)
output.data.save_processed
Default false
Unit / values true/false
Writes <file> – Processed data.csv — the trimmed per-sample signals, for re-plotting or re-analysing the conditioned waveforms outside RespMech. Off by default; these files can be large (one row per sample).
Include excluded breaths in the CSV
output.data.include_ignored_breaths
Default false
Unit / values true/false
Adds the breaths you excluded to the processed-signal CSV. Affects the CSV only — excluded breaths are always dropped from the averages and workbooks regardless of this flag.

From the command line, the same choices are read from the settings file:

respmech run settings.toml

This processes the batch and writes all enabled outputs to <output.folder>/data plus diagnostics and provenance, printing "Wrote N file(s)". Two variants are useful when setting up: respmech run settings.toml --dry-run computes but writes nothing (it prints the same output plan the desktop app's Dry run button shows, then per-file breath counts), and respmech validate settings.toml checks the settings and reports how many input files match. A non-zero exit from run means one or more files failed; the failures are listed on stderr.

Inside every workbook

Both breath-data workbooks (Average breathdata.xlsx and <file>.breathdata.xlsx) carry the same set of sheets, in this order:

  • Data — the result table itself (the numbers). This is the only load-bearing sheet.
  • Units — Column/Unit/Note columns giving the physical unit of each Data column. The Note column is blank except on the wob* columns, where it records the work-of-breathing source ("Work of breathing from: averaged breath" or "… individual breaths"), so a reader can see that in averaged-breath mode those values are one whole-file value repeated on every breath of the breath-by-breath workbook.
  • EMG normalised — breath workbook only, present when EMG normalisation is on and EMG channels exist (see the normalised-EMG sheet).
  • Provenance — Key/Value rows: RespMech version, the Python and library versions used (see Provenance: settings snapshot and run report), generation timestamp, input folder and pattern, sampling frequency (Hz), breath-separation method and buffer, the work-of-breathing source (averaged breath or individual breaths), drift correction, EMG normalisation, the sample-entropy parameters ("m = …, r = … × SD", only when entropy channels are assigned), and the settings-snapshot filename. Average breathdata.xlsx and Cohort summary.xlsx get one more, leading row — INCOMPLETE, naming how many files failed and were excluded — whenever the batch had at least one failed file.
  • Version — a "Created with RespMech v…" line and a clickable link to the GitHub repository.

Why units live on their own sheet. Column names are deliberately terse (e.g. ptp_oesinsp, wob_in_ela) and carry no units, so downstream scripts can key on stable names. The units sit alongside the data on the Units sheet instead. Column widths are auto-fit and the website cell is hyperlinked, but cell values are never altered by this presentation layer.

A column only appears on the Units sheet when its unit is unambiguous. A handful of ratios that are intentionally left unlabelled — vmr and tlr_insp — are simply absent from the Units sheet rather than mislabelled.

The Data sheet: result columns

The two breath-data workbooks share one column set. The breath-by-breath sheet leads with breath_no and gives one row per breath; the average sheet leads with file, drops breath_no, and reports every value meaned across that file's kept breaths. Both then carry the respiratory-mechanics and work-of-breathing columns, plus EMG and sample-entropy columns when those channels are configured.

Two unit conventions to watch. Pressure–time products (ptp_*) and work of breathing (wob*) are reported as per-minute rates — they are scaled by breaths·min⁻¹ — so their units are cmH₂O·s·min⁻¹ and J·min⁻¹ respectively. The paired inspiratory/expiratory integrals (int_*) are the un-scaled per-breath pressure integrals in cmH₂O·s. EMG RMS is uncalibrated (a.u.): never present it as an absolute value — use the normalised sheet for cross-subject comparison.

Respiratory mechanics

Column(s)UnitMeaning
poes_maxexp, poes_mininsp, poes_endinsp, poes_endexp, poes_midvolexp, poes_midvolinspcmH₂OOesophageal (pleural) pressure at named phase points.
poes_tidal_swingcmH₂OPeak-to-trough Poes over the breath.
int_oesinspcmH₂O·sInspiratory Poes integral (per breath). ⚠ Differs from 1.x: baseline change + Simpson drift
ptp_oesinspcmH₂O·s·min⁻¹Oesophageal pressure–time product (per-minute rate). ⚠ Differs from 1.x: baseline change + Simpson drift
pgas_endinsp, pgas_endexp, pgas_maxexp, pgas_minexpcmH₂OGastric pressure at named phase points.
exp_pgas_risecmH₂OExpiratory rise in Pgas.
pgas_tidal_swingcmH₂OPeak-to-trough Pgas.
int_pgasexpcmH₂O·sExpiratory Pgas integral (per breath, signed; negative during passive expiration). ⚠ Differs from 1.x: baseline change + Simpson drift
ptp_pgasexpcmH₂O·s·min⁻¹Gastric pressure–time product (rate, signed; negative during passive expiration). ⚠ Differs from 1.x: baseline change + Simpson drift
pdi_minexp, pdi_maxinsp, pdi_endinsp, pdi_endexpcmH₂OTransdiaphragmatic pressure at named phase points.
insp_pdi_risecmH₂OInspiratory rise in Pdi.
pdi_tidal_swingcmH₂OPeak-to-trough Pdi.
int_pdiinspcmH₂O·sInspiratory Pdi integral (per breath). ⚠ Differs from 1.x: baseline change + Simpson drift
ptp_pdiinspcmH₂O·s·min⁻¹Transdiaphragmatic pressure–time product (rate). ⚠ Differs from 1.x: baseline change + Simpson drift
flow_midvolexp, flow_midvolinsp, in_flow_midvol, ex_flow_midvolL·s⁻¹Flow at mid-tidal-volume. in_flow_midvol and flow_midvolinsp are the sign-corrected (positive) inspiratory flow and are identical; ex_flow_midvol is the positive expiratory flow; flow_midvolexp is its negative.
max_in_flow, max_ex_flowL·s⁻¹Peak inspiratory / expiratory flow.
vol_endinsp, vol_endexpLEnd-inspiratory / end-expiratory lung volume.
ti, te, ttotsInspiratory, expiratory and total breath time.
ti_ttot—Duty cycle (inspiratory fraction), dimensionless.
vtLTidal volume.
bfmin⁻¹Breathing frequency.
veL·min⁻¹Minute ventilation.
vmr, tlr_insp(blank)Left unlabelled by design; omitted from the Units sheet.

Work of breathing

ColumnUnitMeaning
wobtotalJ·min⁻¹Total work of breathing (a power, ×breaths·min⁻¹). ⚠ Differs from 1.x (Simpson rule change, negligible)
wob_in_totalJ·min⁻¹Inspiratory work total. ⚠ Differs from 1.x (Simpson rule change, negligible)
wob_ex_totalJ·min⁻¹Expiratory work total. ⚠ Differs from 1.x (Simpson rule change, negligible)
wob_in_elaJ·min⁻¹Inspiratory elastic work. Bit-identical to 1.x (analytic, no Simpson integration).
wob_in_resJ·min⁻¹Inspiratory resistive work. ⚠ Differs from 1.x (Simpson rule change, negligible)

EMG (one set per configured EMG channel)

Column patternUnitMeaning
rms_col_<n>, rms_max, rms_meana.u.Whole-breath rolling-RMS per channel, plus across-channel max / mean. ⚠ Differs from 1.x when noise reduction was used
rms_insp_col_<n>, rms_insp_max, rms_insp_meana.u.Inspiratory RMS. ⚠ Differs from 1.x when noise reduction was used
rms_exp_col_<n>, rms_exp_max, rms_exp_meana.u.Expiratory RMS. ⚠ Differs from 1.x when noise reduction was used
integral_emg_col_<n>, integralemg_max, integralemg_meana.u.·sIntegrated EMG (whole breath). ⚠ Differs from 1.x (Simpson rule change) and, when noise reduction was used, EMG conditioning
integral_emg_insp_col_<n> … , integral_emg_exp_col_<n> …a.u.·sIntegrated EMG, inspiration / expiration. ⚠ Differs from 1.x (Simpson rule change) and, when noise reduction was used, EMG conditioning
rms_gated_col_<n>, rms_gated_max/mean (+ _insp/_exp)a.u.Opt-in cardiac-gated peak RMS — present only when processing.emg.robust_peak.enabled is on. ⚠ Differs from 1.x when noise reduction was used

Sample entropy (one set per configured entropy channel)

Column patternUnitMeaning
sample_entropy_col_<n>, sample_entropy_max/min/mean—Whole-breath sample entropy per channel, plus summary (dimensionless). ⚠ Shifted vs 1.x
sample_entropy_insp_*, sample_entropy_exp_*—Inspiratory / expiratory sample entropy. ⚠ Invalid in 1.x — do not reuse

EMG columns that edit the written numbers

Two EMG controls change the values written to these tables rather than merely adding sheets — worth knowing when you audit a result.

Cardiac-gated peak EMG
processing.emg.robust_peak.enabled
Default false
Unit / values true/false
Adds gated RMS columns (rms_gated_*) read from signal blanked around each R-peak; never replaces the existing RMS columns. Use on strongly cardiac-coupled recordings where the plain RMS maximum sits on a residual heartbeat. Needs ECG removal on, or the gated columns come back blank (NaN).
Outlier RMS SD limit
processing.emg.outlier_rms_sd_limit
Default 0.0
Unit / values SD (0 = off)
Before the tables are built, replaces any breath whose RMS lies more than this many SD from the other breaths' mean with that mean. With it >0 it overwrites the rms_max/rms_mean values written to the tables — it edits values, not just flags them. Use to stop a single artefactual breath dominating the RMS max/mean.
One rolling-RMS envelope of a strongly cardiac-coupled breath: the ±120 ms windows blanked around each detected heartbeat are shaded; the naïve maximum marker sits on a tall heartbeat residual, while the cardiac-gated maximum marker sits on the diaphragm burst that survives the gating.
Cardiac-gated peak EMG (robust_peak.enabled): the gated maximum ignores residual heartbeats. The gated values are added as extra columns; the original RMS columns are untouched.

Cohort summary and grouping

Cohort summary.xlsx is written automatically whenever the average workbook is — the two are paired, and there is no separate switch for it. It aggregates the finished average table across files, and never touches the per-file or per-breath tables, so it is purely additive.

  • Summary sheet — one row per numeric variable (column variable), with n, mean, sd (sample SD, ddof = 1) and cv_pct (coefficient of variation, %). sd and cv_pct are NaN whenever fewer than two files contribute a value, so a one-file group on the By group sheet always shows NaN.
  • By group sheet — the same statistics per group, with leading group and n_files columns. Written only when files fall into more than one group.
cv_pct = 100 · sd / mean% (dimensionless)

CV% is NaN for signed quantities. The coefficient of variation is reported only when the mean is positive, so a signed variable like poes_mininsp (negative by nature) shows NaN CV by design — not a bug.

Grouping. By default the group key is the leading filename token — everything up to the first space, underscore or hyphen — so P03_120W.csv groups as P03. Override this with a regular expression whose first capture group becomes the key, for filenames that don't front-load the grouping token or when you want to group by condition rather than subject.

Group files by
output.group_regex
Default null
Unit / values regex, or blank
Blank = leading filename token (e.g. P03_120W → P03); otherwise a regex whose first capture group is the key. A regex that matches nothing lands the file in group (all); a malformed regex never crashes the run — every file lands in group (all), exactly as for a regex that matches nothing. The By-group sheet appears only when there is more than one group.

The normalised-EMG sheet

EMG amplitude is uncalibrated and electrode-dependent, so raw RMS values (in a.u.) cannot be compared across subjects or electrode placements. To help with that, the breath workbook can carry an extra EMG normalised sheet that re-expresses every RMS column as a percentage of a reference — by default that same column's own peak or mean breath within the file, which makes the within-file pattern comparable but still leaves each file's own peak at 100%; set normalization_reference_file to a shared manoeuvre file (see Amplitude normalisation) for amplitudes genuinely comparable across files.

The sheet's columns are breath_no plus one <rms column>_pct column for each RMS column, in % of the per-file reference. The raw RMS on the Data sheet is never changed — normalisation only adds a sheet.

Amplitude normalisation
processing.emg.normalization
Default per_file_max
Unit / values none | per_file_max | per_file_mean
Adds the EMG normalised sheet, re-expressing each RMS column as a percentage of the file's peak or mean breath. Adds a sheet only — it never changes the raw RMS Data sheet. Requires EMG channels. Set to none to suppress the sheet.

Normalisation is on by default (per_file_max): every breath workbook gets the normalised sheet when EMG channels exist. If a whole file's cardiac-gate quality guards refuse it, the corresponding _pct column comes back all-NaN rather than wrong — the sheet handles this silently.

Processed-signal CSV

When output.data.save_processed is on, RespMech writes <file> – Processed data.csv for each recording: the trimmed, per-sample conditioned signals, for re-plotting or re-analysing the waveforms outside RespMech. Columns are:

ColumnUnitMeaning
TimesTime within the breath (sample index inside the breath / sampling frequency). It restarts at 0 for every breath, so it is not a continuous recording clock.
Breathno—Breath number.
FlowL·s⁻¹Conditioned flow.
VolumeLConditioned volume.
PoescmH₂OOesophageal pressure.
PgascmH₂OGastric pressure.
PdicmH₂OTransdiaphragmatic pressure.
EMG<col>a.u.One column per configured EMG channel.
Time = sample_index / sampling_frequencys

The CSV carries no units and can be huge — one row per sample, with units living only in the workbooks, not the CSV header. It is off by default. The include_ignored_breaths flag affects this file only: turn it on to keep excluded breaths in the exported waveform for review. The averages and workbooks always exclude them.

Diagnostic PDFs

The diagnostic figures are paginated vector PDFs written to the diagnostics/ subfolder. Each is optional and independently toggled. They always render in the light theme even when the GUI is dark, so exported PDFs are print-ready and reproducible. A figure that fails to draw is skipped and recorded in the run report — it never aborts the run.

Campbell / PV — averaged
output.diagnostics.save_pv_average
Default true
Unit / values true/false
Writes <file> – Campbell (average).pdf: every breath's Poes–volume loop overlaid, the average breath bold, with the elastic-recoil line and shaded WOB triangle. Needs a computed average breath — a file with no usable breaths produces no figure.
Campbell / PV — individual breaths
output.diagnostics.save_pv_individual
Default true
Unit / values true/false
Writes <file> – Campbell (breaths).pdf: a paginated grid of one loop per breath (ignored breaths crossed out). With more than one file it also writes All files – Campbell (average).pdf, one panel per file's mean loop.
Campbell grid columns
output.diagnostics.pv_columns
Default 3
Unit / values ≥ 1
Columns in the per-breath Campbell grid. pv_columns × pv_rows = panels per page; a low product means many pages.
Campbell grid rows
output.diagnostics.pv_rows
Default 4
Unit / values ≥ 1
Rows in the per-breath Campbell grid. Raise for more loops per page; lower for larger panels.
Raw-signal figures
output.diagnostics.save_raw
Default true
Unit / values true/false
Writes <file> – signals (raw).pdf: the full untrimmed recording (flow, volume, Poes, Pgas, Pdi) in real time, with breath boundaries and ignored-breath shading. Pre-trim QC — verify segmentation and spot artefacts.
Trimmed-signal figures
output.diagnostics.save_trimmed
Default true
Unit / values true/false
Writes <file> – signals (trimmed).pdf: the analysed signals with breaths concatenated and boundaries marked. The x-axis is "sample (trimmed, breaths concatenated)", not real time.
Drift-correction figures
output.diagnostics.save_drift
Default true
Unit / values true/false
Writes <file> – volume correction.pdf (uncorrected → zeroed → drift-corrected → trend-adjusted), <file> – volume endpoints.pdf (end-inspiratory/expiratory volume per breath), and <file> – volume trend.pdf — the last only when processing.volume.correct_trend is on.
EMG channel overviews
output.diagnostics.save_emg
Default true
Unit / values true/false
Writes one PDF per conditioning stage present — <file> – Raw EMG.pdf, <file> – EMG (ECG removed).pdf, <file> – EMG (ECG removed + noise reduced).pdf — one stacked panel per channel, with the flow reference on a twin axis, R-peak markers and breath boundaries. Only stages actually computed are written.
EMG figure y-scale
processing.emg.plot_yscale
Default [-0.1, 0.1]
Unit / values [ymin, ymax] in a.u.
Fixed y-axis range for the EMG overview figures, so channels, stages and files stay visually comparable. Widen it if the signal clips the default range. Ignored (autoscale) unless it is a valid 2-element range with ymin < ymax.
The Campbell / PV loop output for one recording: a faint pressure–volume loop for every breath with the average breath drawn bold, the elastic-recoil line joining its endpoints, and the work area shaded.
<file> – Campbell (average).pdf — the primary visual QC of work-of-breathing geometry per recording. The bold loop is the average breath; the shaded triangle is the elastic work against the recoil line.
Volume-correction stages stacked top to bottom: flow, an uncorrected volume trace that wanders off baseline, the same trace zeroed, and the linear drift-corrected result with end-expiratory volume back on a common baseline.
<file> – volume correction.pdf — the staged volume figure produced by save_drift: uncorrected → zeroed → drift-corrected → trend-adjusted.
Illustration of one EMG channel across the conditioning stages: the raw signal with clear ECG spikes, the same signal after ECG removal, and finally after noise reduction.
Illustration of the EMG overview PDFs' three conditioning stages — raw → ECG-removed → ECG-removed + noise-reduced. The real, written PDFs are one file per stage, one stacked panel per EMG channel, with a flow reference on a twin axis and R-peak capture markers; only computed stages are written.

EMG audio export (WAV)

As a diagnostic aid, RespMech can export each EMG channel — at each conditioning stage — as a normalised 16-bit WAV file at the analysis sample rate, dropped into the diagnostics/ folder. Listening to the EMG is a fast way to judge ECG contamination and noise-reduction quality.

Export each EMG stage as WAV
processing.emg.save_sound
Default false
Unit / values true/false
Exports every EMG channel, at every stage, as a normalised int16 WAV. Diagnostic and off by default — it adds several files per recording (channels × stages).

Provenance: settings snapshot and run report

Two provenance files are written to the top of the output folder on every run, regardless of which output tickboxes are ticked, so a result folder always carries its own recipe.

  • analysis-used.toml — the exact settings that produced the run. Reload it to reproduce the analysis byte-for-byte.
  • run-report.txt — a plain-text log covering the RespMech version and the Python/numpy/scipy/pandas/librosa versions the run used; the input folder and pattern; the files processed and failed (with excluded → used breath counts); the processing flags in effect; EMG diagnostics (R-peaks detected, suppression, ΔSNR); and the list of outputs actually written. If figures are skipped (for example because matplotlib is unavailable), they are recorded under a "FIGURES SKIPPED" heading and the run still completes with all tables written.

Every written workbook's Provenance sheet carries the same RespMech version and library-version line, alongside the run's settings summary, so a numeric mismatch against an older result can be checked against a library upgrade before it is treated as a regression.

The Run & results drawer after a completed batch, showing the run log, a written-files count, and a summary of the results folder.
The Run & results drawer after a batch. The run report on disk mirrors this summary in plain text, itemising what was read, kept, excluded and written.

Keep the pair together — and keep the recordings. analysis-used.toml records the settings and run-report.txt records what happened, so an archived result folder is fully auditable on its own: anyone can read exactly how the numbers were produced. Re-running it is a second step. The snapshot holds absolute paths (see the note under the CLI section), so archive the recordings alongside the results, and on a different machine repoint input.folder, output.folder and processing.emg.noise.reference_folder — plus the folder key on each exclusion and breath-count entry — before you re-run. If you repoint only the input folder in the app, RespMech marks the exclusions, breath-count overrides and noise reference as carried over from a different recordings folder (the ↺ badge); use Clear only if you really want them dropped.

Troubleshooting & FAQ

Most problems in RespMech come from a handful of load-time settings that are silent when wrong: the recording is rescaled, mislabelled or trimmed away without an error message, and only the numbers look off. This section collects the failures reported most often, the symptom that gives each one away, and the exact setting that fixes it. Every fix here is a setting change, not a data edit — RespMech is designed so you never touch the raw recording.

"No breaths were detected", or the file refuses to load

Before it measures anything, RespMech trims the recording down to whole breaths. It scans the flow signal for the first sample where flow < 0 (the onset of the first inspiration) and the last sample where flow ≥ 0 (the end of the last expiration), and keeps only what lies between. If flow never crosses zero — or the computed window is empty — it stops with a TrimError that names the flow channel and the inverse_flow setting.

The trim precondition. The epoch must start in late expiration and end in early inspiration. RespMech discards the leading partial expiration and the trailing partial inspiration, so an epoch that begins in an expiration or ends in an inspiration works; one that begins mid-inspiration or ends mid-expiration is not caught: the partial breath is analysed and, with drift correction on, the volume of every breath is biased. Only a recording with no zero-crossing at all, or an empty computed window, is rejected outright.

Work through these causes in order:

  • Wrong flow channel. Columns are 1-based and column 1 is usually the time axis (see A channel shows the wrong signal). A flow channel pointed at a flat or monotonic column never crosses zero.
  • Flow sign reversed. If your rig records inspiration as positive flow, the trim logic looks for the wrong phase. Fix it with processing.volume.inverse_flow, not by editing data — see Breaths look upside-down.
  • Volume-based segmentation with thresholds set too high. When processing.segmentation.method = "volume", breaths are found by peak detection; a peak.height (default 0.1 L) or peak.width_s higher than your actual breaths yields zero peaks and therefore zero breaths. Lower the thresholds, or switch back to method = "flow", which is more robust for most recordings.
  • Every breath excluded. Breaths you have clicked out in Preview & QC (or listed under processing.exclude_breaths) are still detected but not analysed. Excluding all of them leaves nothing to measure, and RespMech says so separately from "none detected" — re-include at least one.

A file that reaches the run with nothing to analyse fails with a NoBreathsError naming the file and the setting to change, and the rest of the batch continues. Before v2.3.3 the same situation surfaced as an internal pandas message ('list' object has no attribute 'mean') that named neither.

A single breath, rather than the whole file, can also fail on its own: if a breath's inspiration and expiration phases cannot be joined into one pair, RespMech raises a DegenerateBreathError naming the breath number and the file, and stops that one file with exit code 1 — the rest of the batch is unaffected. The explanation depends on where the breath sits: an actual first or last detected breath is blamed on an incomplete breath at the start or end of the recording, exactly as before; a breath in the middle of a recording gets a different message instead, naming the breath's own timestamp and pointing at noise around zero flow, a mis-assigned flow channel, or more than one recording merged into the file (see the next section) — a truncated recording is not a plausible cause for a breath nowhere near either end of it, and the message no longer suggests one. Either way, exclude the breath in Preview & QC, or check the breath-separation settings under Preview & QC → Mechanics → Advanced… → Breath detection.

With method = "volume", a whole file can also fail with a VolumeSegmentationError when peak detection finds fewer expiratory than inspiratory peaks, so at least one breath cannot be paired with its expiration — slow, quiet breathing with pauses at zero flow between phases is the usual cause. The message names the file and the peak counts found. Loosen the peak.height / peak.width_s thresholds, or switch back to method = "flow".

With the default method = "flow", a file fails immediately with a ConstantFlowError if the flow channel is flat at exactly zero — either across the whole recording, or across a stretch reached partway through (a genuine pause with no flow at all is enough): segmentation walks the signal looking for a sign change between inspiration and expiration, and a flat-zero flow satisfies neither, so there is nothing to split on at that point. This is almost always a mis-assigned or unused/grounded flow column — check the channel assignment in Setup, or switch to method = "volume" if there genuinely is no flow channel for this recording.

A file fails to load with a data or format error

Before trimming ever runs, RespMech checks that every assigned column actually holds the data it claims to. Each of the following stops only the one file: the message names the exact channel and file, the run reports it as a failure, the rest of the batch is still processed, and the command exits 1 (see the exit codes).

  • Column number out of range. "<channel> is set to column <n>, but columns are numbered from 1." for zero or a negative number, and "<channel> is set to column <n>, but <file> has only <n> column(s)." for a number past the end of the file. Recheck the channel against the column count, especially after switching between recordings with a different layout.
  • Text in a numeric column. "<channel> column contains text values – all values must be numeric." A stray label, unit or comment cell sitting in an otherwise numeric column — often a header row exported as if it were data.
  • Missing (NaN) cells. "<channel> column contains NaN values." A dropped sample or a blank cell in the export. RespMech does not interpolate across a gap in the raw data; re-export the recording, or restrict the epoch to a stretch without gaps.
  • Columns of different lengths. "Data column lengths differ. All columns must have the same number of observations." Only reachable from a .mat file, where each channel is read independently; a CSV, tab-text or Excel export is always rectangular and cannot trigger this one.
  • Unsupported file type. "Unsupported input file type: <.ext>" RespMech reads .csv, .txt, .xlsx and .mat only; anything else — including an export simply renamed to one of these extensions, or the older binary .xls format — fails with this message before RespMech even tries to read it.
  • A .mat file RespMech cannot parse. "Cannot load MATLAB file – verify the MATLAB file format setting (windows/mac). Only simple LabChart exports are supported; otherwise export to CSV." Set MATLAB file format to match the machine that wrote the file, or re-export to CSV.

A channel left completely unassigned never reaches any of the above: respmech validate and a run both refuse first, with "input.channels.<name> is required" and exit code 2, before a file is ever opened (see the exit codes).

A recording looks like more than one export merged together, or an assigned channel never varies

Two further checks run before any of the above, over the recording as RespMech first sees it — in the desktop app's Setup QC strip, and in respmech validate, so a bad file is visible before you spend time running it:

  • Merged multi-block export. If a file's time column otherwise looks like a regular sample clock but has duplicated or decreasing timestamps, RespMech flags it as looking like more than one recording (for example several separate LabChart blocks) exported into a single file and merged row-by-row by timestamp. Analysing a file like this is not merely risky — it makes the flow signal jump between different recordings from sample to sample, which can surface much later as an unrelated-looking DegenerateBreathError on one breath in the middle of the file. Re-export each block to its own file instead.
  • A constant assigned channel. If flow, Poes, Pgas, Pdi, an EMG channel or an entropy channel never varies across the whole recording — most often a mis-assigned column, or a genuinely unused/grounded input on the acquisition system — it is flagged by name and column number. A constant flow channel is additionally a hard failure at run time (see the ConstantFlowError above); a constant channel of any other role is a caution, not a failure, since it does not stop segmentation, only makes that one channel's own numbers meaningless.

Both checks read only a bounded sample of the file (the first several thousand rows) for cost reasons, so a channel that is constant only for part of a very long recording, or a merge that only begins well into the file, may not be caught here — the checks are a fast first pass, not a substitute for looking at the data.

The breaths look upside-down, or inspiration reads positive

RespMech has one fixed sign convention, and every inspiratory/expiratory quantity depends on it:

Convention: inspiration = negative flow; inspired volume = positive. Pressures are in cmH₂O, flow in L·s⁻¹, volume in L.

If the Campbell loop is mirrored, inspiration and expiration are swapped, or the flow trace points the wrong way, your acquisition uses the opposite polarity. Correct it at load time with the two invert toggles — getting them wrong does not error, it silently mislabels every phase-dependent number.

Invert the flow signal
processing.volume.inverse_flow
Default false
Unit / values true / false
Negates flow so inspiration reads negative. Turn on if inspiration reads positive on your rig. Applied before volume integration, so it also flips the sign of any flow-derived volume.
Invert the volume signal
processing.volume.inverse_volume
Default false
Unit / values true / false
Negates volume so inspired volume is positive. Turn on if your volume trace decreases on inspiration. Applied after integration.
Calculate volume from flow
processing.volume.integrate_from_flow
Default false
Unit / values true / false
Derives volume by integrating flow when there is no volume channel. Lets you leave the volume channel unset.

"Could not correct the end-expiratory trend"

The trend correction fits an envelope through the end-expiratory trough of each breath. It refuses in two distinct ways, both on the file only, and the rest of the batch still runs. If the volume signal itself is unusable — it contains a NaN or infinite sample, typically an unset volume channel or a broken flow-to-volume integration — it stops immediately with "Could not correct the end-expiratory trend: the volume signal contains missing or infinite samples.", before it ever looks for troughs; check the volume channel under Setup, or Calculate volume from flow under Mechanics → Advanced…. Otherwise, the run stops on a file when too few troughs are found to fit an envelope — two for the default linear interpolation, three for quadratic, four for cubic. That second message reports how many troughs were found and against which threshold, and the file is skipped rather than given numbers derived from an undefined envelope.

  • The message names trend_peak_min_height. That is the legacy absolute rule: a trough must lie that many litres below the highest volume in the whole recording. On ordinary tidal breathing the entire volume range is often smaller than the threshold, so nothing qualifies. Set Trend anchor — absolute threshold (legacy) back to Auto under Preview & QC → Mechanics → Advanced…, or remove trend_peak_min_height from the TOML. Analyses saved before v2.3.3 that merely inherited the old 0.8 default are switched to Auto for you on load, and RespMech says so.
  • The message names the breath depth. The recording has genuinely shallow breaths relative to its own volume range — usually quiet breathing recorded alongside a large manoeuvre (an inspiratory capacity, a sigh, a cough), which inflates the range. Lower Trend anchor — minimum breath depth; the Advanced dialog shows how many troughs the current value finds in the previewed file, so you can watch the count as you change it.
  • Too few breaths for the interpolation. A short recording cannot support cubic (four anchors) or quadratic (three). Use linear.
  • You do not need the correction. If end-expiratory volume is stable across the recording, untick Correct end-expiratory trend — drift correction alone is enough, and it is on by default.

The Preview & QC channel stack keeps working while this is unresolved: it falls back to the drift-corrected volume and says so in the status line, so you can see the trace and tune the thresholds against it.

Timing, bf, VE and the per-breath integrals look wrong by a constant factor

The sampling frequency is the time base of the whole analysis, and nothing checks it against the data at run time. Declaring a rate that is k times too low (k = true fs / declared fs) multiplies Ti, Te, Ttot and the per-breath integrals (int_*) by k, divides bf, VE and the per-minute work of breathing by k, and leaves ti_ttot, VT, every pressure descriptor, tlr_insp and vmr unchanged.

T = N_samples / fss
VE = VT · count · 60/TL·min⁻¹

The ptp_* rates are essentially unchanged too, because the k in the integral and the 1/k in the breaths-per-minute factor cancel — so a plausible PTP does not prove the rate is right. When volume is integrated from flow the pattern shifts: VT itself scales by k, while VE and the per-minute work of breathing come out unchanged. Anything defined in seconds rather than samples moves as well: with segmentation.method = "volume" the peak-distance and width thresholds are converted with fs, so a wrong rate changes which breaths are detected at all, and the same holds for the EMG RMS window, the ECG refractory gap, the cardiac-gated blanking window and pre-analysis resampling. If a whole batch of results is off by a pattern like this, suspect the sampling frequency first.

Confirm the auto-detected value. When you assign channels from data the app reads input.format.sampling_frequency from the recording's time column, but it is still required and the run will not validate without it. Below 1000 Hz the app warns that the rate is low for EMG. All files in one batch are assumed to share this rate.

A channel shows the wrong signal (off-by-one column mapping)

Channel columns are numbered from 1, matching the LabChart export, and column 1 is normally the time axis. Assigning a required channel to column 1, leaving one unassigned, or pointing two roles at the same column are all flagged in the QC strip; a zero, negative or out-of-range column number is a hard load error. The safest way to map them is the visual Assign channels from data… picker, which plots every column so you match it to its physiological role rather than counting.

The Assign channels dialog: each data column is plotted on a shared time axis with a role dropdown (Flow, Volume, Poes, Pgas, Pdi) and a per-column Entropy tick.
The Assign-channels picker maps each 1-based column to a role visually, so an off-by-one is obvious before you run.

One channel does not use column numbers. The ECG capture channel processing.emg.detect_channel is a 0-based index into the input.channels.emg list, not a raw data column. If you have EMG columns [2,3,4,5,6], detect_channel = 0 means column 2 and detect_channel = 2 means column 4. Re-assigning EMG channels silently re-points it; the preview re-seeds it to the middle channel and warns if it falls out of range.

Volume drifts, or VT and the Campbell loop look wrong

Flow-integrated volume accumulates baseline drift, which is why drift correction is on by default. It removes a linear baseline slope from the zeroed volume trace (a de-trend with one deliberate quirk: the very last sample is pinned to 0 — that is expected, not a bug). If you turned drift correction off while integrating volume from flow, VT and the pressure–volume loop distort as the baseline wanders.

Volume correction stacked top to bottom: the sloping baseline in the uncorrected panel is removed, leaving a level end-expiratory baseline in the drift-corrected panel at the foot.
Before / after linear drift correction (processing.volume.correct_drift), on by default.
  • Volume still trends across the run (end-expiratory lung volume wanders, e.g. during exercise): enable processing.volume.correct_trend, which subtracts an interpolated envelope through the end-expiratory points. It anchors on each breath's end-expiratory trough, judged relative to the recording's own volume range, and emits its own diagnostic figure. See "Could not correct the end-expiratory trend" if it stops on a file.
  • Volume decreases on inspiration: enable processing.volume.inverse_volume (see the sign conventions above).
  • Inspecting raw drift: turn correct_drift off only to see the untouched trace; leave it on for analysis.

The ECG detector will not lock onto the heartbeats

ECG removal first detects R-peaks on one capture channel, then averages a template and subtracts it beat-by-beat. If detection is unstable, the whole stage misbehaves. The EMG – ECG reduction tab plots the capture channel with a marker on every detected beat, so you can tune the two critical thresholds while watching them land.

The EMG - ECG reduction preview tab: the capture-channel plot with detected R-peak markers, the Remove ECG toggle, and the Min height and Min gap controls.
Watch the R-peak markers on the capture channel while adjusting Min height and Min gap; use Auto-suggest as a starting point.

Three things determine whether it locks:

  • Capture channel. Pick the EMG channel with the clearest R-wave and the weakest EMG — often the middle electrode. Detection runs on that one channel and the same beat train is applied to every EMG channel.
  • Min height. The R-wave height threshold must sit between the EMG burst amplitude and the R-wave amplitude. Set it too low and the detector locks onto EMG bursts; too high and it misses real beats. It is in the same uncalibrated units (a.u.) as the raw EMG, so it is electrode- and recording-specific.
  • Min gap. The refractory gap sets the maximum detectable heart rate. For fast (exercise) recordings, a gap that is too long merges or drops real beats.
HR_max = 60 / ecg_min_distance_smin⁻¹
Capture channel
processing.emg.detect_channel
Default 0
Unit / values 0-based index into the EMG list
Which EMG channel the R-waves are detected on. Choose the clearest ECG / weakest EMG. Not a raw column number.
Min height
processing.emg.ecg_min_height
Default 0.0005
Unit / values a.u. (signal units)
Minimum R-wave peak height. Raise to reject EMG-sized peaks; lower if real beats are missed. Best set with Auto-suggest, then nudged while watching the markers.
Min gap
processing.emg.ecg_min_distance_s
Default 0.5
Unit / values s
Refractory gap between beats; caps detectable HR at 60/gap. Lower it for fast heart rates so beats are not merged.

ECG detection and template parameters are test-level: they are built once and applied identically to every file. Never re-tune them per file — doing so breaks the shared-transformation requirement that makes relative EMG comparable across the test.

The cardiac-gated peak columns come out blank (NaN)

The cardiac-gated peak reads the per-breath peak from only the heartbeat-free stretches of each breath, blanking a window around every detected R-peak and taking the maximum of what survives. It deliberately reports NaN rather than a wrong number when it cannot trust the result, so blank gated columns are a normal, honest outcome — not a failure.

One rolling-RMS envelope of a strongly cardiac-coupled breath: the ±120 ms windows blanked around each detected heartbeat are shaded; the naïve maximum marker sits on a tall heartbeat residual, while the cardiac-gated maximum marker sits on the diaphragm burst that survives the gating.
The gated peak blanks the intervals around each heartbeat so the reported peak is read from diaphragm activity, not a residual R-wave.

Check these, in order:

  • ECG removal must be on. The gated peak reuses the R-peaks that processing.emg.remove_ecg = true found. With no beats detected, there is nothing to gate around and every gated column is NaN.
  • Heart rate near the detector ceiling. If the detected rate approaches 60 / ecg_min_distance_s, the guard refuses to gate because the detector is about to start missing beats. The fix is to lower Min gap (ecg_min_distance_s) on the ECG tab, not to raise hr_ceiling_margin.
  • Too little cardiac-free signal. A phase goes NaN when less than min_survival (default 0.40) of it survives the gates, or its longest cardiac-free run is shorter than min_island_s (default 0.20 s) — a gap too short to hold a full RMS window.
  • Missed beats. If more than max_long_rr_frac (default 0.02) of R-R intervals exceed long_rr_factor × the median, detection is distrusted for the whole file and every gated column is blanked.

The blank width is a half-width. processing.emg.robust_peak.gate_half_width_s defaults to 0.120 s, which blanks a 0.240 s window in total around each beat. It must cover the heartbeat's footprint in the RMS envelope (roughly the RMS window plus the QRS duration).

Noise reduction removes too much EMG — or does nothing at all

Spectral noise reduction subtracts one shared spectral profile, built from a rest reference and applied identically to every file. Two failure modes dominate.

It does nothing. Noise reduction requires ECG removal to be on and a reference file to be set. A ticked processing.emg.noise.enabled with no reference_file runs nothing in the app and raises an error in a batch run. The reference must be a proper rest recording with several seconds of EMG-free signal; the default use_expiration = true builds the profile from every expiration of that file (hundreds of stable STFT frames), which is far better than a single short quiet gap.

It refuses to build a profile at all. The reference needs at least one full FFT window (n_fft, default 256 samples) of EMG-free signal. Shorter than that, the run stops before processing a single file with "Noise reference is too short (<N> samples) for a stable estimate at n_fft=<n_fft>. Provide a longer EMG-free reference (≥ several STFT windows)." — exit code 2. A reference just above that floor (fewer than about 8 STFT frames) does not stop the run, but the per-frequency noise estimate is unstable: RespMech prints a UserWarning on stderr that is easy to miss and that never reaches run-report.txt. If fidelity looks noisy from channel to channel, lengthen the reference before tuning anything else.

It destroys real EMG. The over-subtraction guard is fidelity — the fraction of true inspiratory in-band (20–250 Hz) EMG power that survives removal:

fidelity = P_band(processed inspiration) / P_band(raw inspiration)dimensionless

The "great SNR trap". Raising the spectral gate threshold n_std_thresh (Spectral gate threshold, in the EMG Advanced… modal) looks like it improves the in-band SNR, but it collapses fidelity — you are reading a cleaner signal that is mostly gone. The default of 1.0 is essentially never changed; values ≥ 1.5 destroy real EMG. Trust the fidelity gate, not the SNR number.

Leave Auto (auto_prop) on: it picks, once per test, the strongest suppression (prop_decrease) that keeps every channel's fidelity at or above fidelity_target (default 0.8, i.e. retain ≥ 80 % of EMG power). With Auto on, any manual Strength value is ignored. The EMG – noise reduction tab plots the fidelity frontier — fidelity retained against suppression strength, one curve per EMG channel — so you can see the trade-off before committing. (ΔSNR is measured too, but it is reported in run-report.txt, not drawn on that panel.)

One EMG channel shown at three stages: raw with cardiac spikes and a broadband noise floor, ECG-removed, then noise-reduced with the noise floor gone but the inspiratory burst preserved.
The three EMG conditioning stages: raw, ECG-removed, then noise-reduced. Good settings strip the noise floor while preserving the inspiratory burst.

From the CLI, spectral noise reduction needs the EMG extra: pip install "respmech[emg]" (it pulls in librosa) — without it, a run stops outright with exit code 2. Diagnostic PDF figures need pip install "respmech[plots]" — without it, the run still succeeds (exit 0) with a WARNING on stderr and a FIGURES SKIPPED section in run-report.txt instead of the figures themselves.

"Settings valid" but no files run, and what the exit codes mean

A common surprise is respmech validate printing Settings valid. and then WARNING: no input files match. before exiting with code 1. The settings really are valid — RespMech just found nothing to process. The usual causes:

  • The input folder is relative to the settings file, not your shell. A relative input.folder / output.folder is resolved against the directory of the .toml file, not the working directory you ran the command from. This keeps a shared analysis portable, but it surprises anyone expecting shell-relative paths. Use an absolute path if in doubt.
  • Mask typo or wrong extension. input.files is one glob (matching is case-insensitive on every OS). A multi-pattern mask like *.csv; *.txt is narrowed to a single extension by the app before a run; the core batch runner globs one pattern only.
  • Leading-dot files are skipped unless the mask itself starts with a dot.

The exit codes form a script-facing contract worth distinguishing:

OutcomeExit codeKindWhat it means
Success0—exit statusrun processed every matched file; validate found the settings valid and matched at least one file.
Handled failure1—exit statusA run where at least one file FAILED (the others still completed; failures listed on stderr), or a validate that matched zero input files, found an unrecognised settings key, or found the output folder unwritable.
Unhandled error2—exit statusThe command threw: a missing or unparseable TOML, or a SettingsError from validation (missing sampling_frequency, a required channel, an invalid trend_method, etc.). Printed as error: <message>.
respmech validate settings.toml   # check settings + count matching files
respmech run settings.toml --dry-run   # compute without writing anything

"I changed a setting and nothing happened." Unknown keys never fail a load, but they are not silently ignored: a misspelled TOML key is reported by respmech validate and in run-report.txt's UNKNOWN SETTINGS KEYS section, alongside the default it fell back to. Check the exact dotted key (every control's tooltip in the app shows it in bold), and remember TOML has no null: an omitted key and its default are equivalent.

The app won't open on first launch (macOS Gatekeeper / Windows SmartScreen)

On the first launch of a freshly downloaded desktop build, your operating system may hold it back until you confirm you trust it. This is a generic protection for downloaded applications, not a fault in RespMech, and it typically appears only once per install.

  • macOS (Gatekeeper). If a double-click is blocked, Control-click (right-click) the RespMech app icon, choose Open, then confirm Open in the dialog. Alternatively open System Settings › Privacy & Security and click Open Anyway next to the RespMech entry.
  • Windows (SmartScreen). If you see "Windows protected your PC", click More info, then Run anyway. SmartScreen may warn for a while on a brand-new release until it has built reputation, even for a signed installer.

Only bypass these prompts for a build you obtained from the official RespMech source. Do not disable Gatekeeper or SmartScreen globally to work around a single app — approve just the one application, as above.

Windows: a run fails on a deeply nested input or output folder

The packaged Windows build is subject to Windows' legacy 260-character path limit, and RespMech writes long, descriptive output names (for example diagnostics/<recording> – EMG (ECG removed + noise reduced).pdf). If a run fails on files whose full paths are very long, move the recordings and the output folder closer to the drive root (for example C:\RespMech\study1) and run again. Long-path awareness is not yet enabled in the installer.

Reference tables

Units of the result columns

Every number RespMech writes carries its unit on the workbook's Units sheet. The pipeline asserts a unit only where the physiology is unambiguous; EMG amplitude is reported in arbitrary units because this pipeline does not calibrate it, and sample entropy is dimensionless.

QuantityUnit
Timing — Ti, Te, Ttots
Duty cycle — Ti/Ttotdimensionless
Tidal volume (VT), lung volumesL
FlowL·s⁻¹
Breathing frequency (bf)min⁻¹
Minute ventilation (VE)L·min⁻¹
Pressures — Poes, Pgas, Pdi, swings, risescmH₂O
Pressure–time product integral (int_*)cmH₂O·s
Pressure–time product rate (ptp_*)cmH₂O·s·min⁻¹
Work of breathing (wob*)J·min⁻¹
Isovolume inspiratory resistance (tlr_insp)cmH₂O·L⁻¹·s
Gastric/oesophageal ratio (vmr)dimensionless (unlabelled in output)
EMG RMS amplitude (rms*)a.u. (uncalibrated)
Integrated EMG (integral_emg*)a.u.·s
Sample entropy (sample_entropy*)dimensionless

↑ Back to top

References

RespMech implements published methods. The list below gives the primary source for each method the manual describes, plus the third-party code the analysis engine builds on. Several in-text mentions elsewhere on this page link back to the matching entry here. Nothing here is a validation of RespMech itself; see Changes from RespMech 1.x for what has been verified about the engine, and the project's Zenodo record (linked in the footer) for how to cite the software.

Respiratory mechanics and work of breathing

  1. Otis AB, Fenn WO, Rahn H. Mechanics of breathing in man. J Appl Physiol. 1950;2(11):592–607. doi:10.1152/jappl.1950.2.11.592
  2. Mead J, Whittenberger JL. Physical properties of human lungs measured during spontaneous respiration. J Appl Physiol. 1953;5(12):779–796. doi:10.1152/jappl.1953.5.12.779
  3. Campbell EJM. The Respiratory Muscles and the Mechanics of Breathing. London: Lloyd-Luke; 1958.
  4. Milic-Emili J, Mead J, Turner JM, Glauser EM. Improved technique for estimating pleural pressure from esophageal balloons. J Appl Physiol. 1964;19:207–211. doi:10.1152/jappl.1964.19.2.207
  5. Milic-Emili J, Grunstein MM. Drive and timing components of ventilation. Chest. 1976;70(1 Suppl):131–133. doi:10.1378/chest.70.1_supplement.131
  6. Baydur A, Behrakis PK, Zin WA, Jaeger M, Milic-Emili J. A simple method for assessing the validity of the esophageal balloon technique. Am Rev Respir Dis. 1982;126(5):788–791. PMID 7149443, doi:10.1164/arrd.1982.126.5.788
  7. Bellemare F, Grassino A. Effect of pressure and timing of contraction on human diaphragm fatigue. J Appl Physiol Respir Environ Exerc Physiol. 1982;53(5):1190–1195. doi:10.1152/jappl.1982.53.5.1190
  8. Field S, Sanci S, Grassino A. Respiratory muscle oxygen consumption estimated by the diaphragm pressure–time index. J Appl Physiol Respir Environ Exerc Physiol. 1984;57(1):44–51. PMID 6469790, doi:10.1152/jappl.1984.57.1.44
  9. Cabello B, Mancebo J. Work of breathing. Intensive Care Med. 2006;32(9):1311–1314. doi:10.1007/s00134-006-0278-3

Diaphragm EMG

  1. Levine S, Gillen J, Weiser P, Gillen M, Kwatny E. Description and validation of an ECG removal procedure for EMGdi power spectrum analysis. J Appl Physiol. 1986;60(3):1073–1081. doi:10.1152/jappl.1986.60.3.1073
  2. Bartolo A, Roberts C, Dzwonczyk RR, Goldman E. Analysis of diaphragm EMG signals: comparison of gating vs. subtraction for removal of ECG contamination. J Appl Physiol. 1996;80(6):1898–1902. doi:10.1152/jappl.1996.80.6.1898
  3. Sinderby CA, Beck JC, Lindström LH, Grassino AE. Enhancement of signal quality in esophageal recordings of diaphragm EMG. J Appl Physiol. 1997;82(4):1370–1377. doi:10.1152/jappl.1997.82.4.1370
  4. Luo YM, Moxham J, Polkey MI. Diaphragm electromyography using an oesophageal catheter: current concepts. Clin Sci (Lond). 2008;115(8):233–244. PMID 18782085, doi:10.1042/CS20070348
  5. Jolley CJ, Luo YM, Steier J, et al. Neural respiratory drive in healthy subjects and in COPD. Eur Respir J. 2009;33(2):289–297. doi:10.1183/09031936.00093408

Sample entropy

  1. Pincus SM. Approximate entropy as a measure of system complexity. Proc Natl Acad Sci USA. 1991;88(6):2297–2301. PMID 11607165, doi:10.1073/pnas.88.6.2297
  2. Richman JS, Moorman JR. Physiological time-series analysis using approximate entropy and sample entropy. Am J Physiol Heart Circ Physiol. 2000;278(6):H2039–H2049. PMID 10843903, doi:10.1152/ajpheart.2000.278.6.H2039
  3. Lake DE, Richman JS, Griffin MP, Moorman JR. Sample entropy analysis of neonatal heart rate variability. Am J Physiol Regul Integr Comp Physiol. 2002;283(3):R789–R797. doi:10.1152/ajpregu.00069.2002
  4. Yentes JM, Hunt N, Schmid KK, Kaipust JP, McGrath D, Stergiou N. The appropriate use of approximate entropy and sample entropy with short data sets. Ann Biomed Eng. 2013;41(2):349–365. doi:10.1007/s10439-012-0668-3

Methodological standards

  1. American Thoracic Society / European Respiratory Society. ATS/ERS statement on respiratory muscle testing. Am J Respir Crit Care Med. 2002;166(4):518–624. PMID 12186831, doi:10.1164/rccm.166.4.518
  2. Laveneziana P, Albuquerque A, Aliverti A, et al. ERS statement on respiratory muscle testing at rest and during exercise. Eur Respir J. 2019;53(6):1801214. PMID 30956204, doi:10.1183/13993003.01214-2018

Third-party code RespMech builds on

RespMech is GPL-3.0-or-later. Two components come from other projects and keep their own licences and credit:

  • Sample entropy is vendored from pyEntropy (pyentrp) by Nikolay Donets, Apache-2.0 (github.com/nikdon/pyEntropy); the licence ships with the application as LICENSE pyentrp. This is why the Embedding (m) setting is passed as the longest template length (see Sample entropy › Parameters).
  • Spectral noise reduction uses the spectral-gating method of Tim Sainburg (the algorithm behind the noisereduce package), adapted with permission and driven by a fixed, precomputed threshold spectrum. Cite: Sainburg T, Thielk M, Gentner TQ. Finding, visualizing, and quantifying latent structure across diverse animal vocal repertoires. PLoS Comput Biol. 2020;16(10):e1008228. doi:10.1371/journal.pcbi.1008228; and Sainburg T. timsainb/noisereduce: v1.0. Zenodo; 2019. doi:10.5281/zenodo.3243139.

The analysis engine also depends on NumPy, SciPy, pandas, openpyxl and tomli-w, and optionally on librosa (spectral noise reduction), matplotlib (diagnostic figures) and PySide6/pyqtgraph (the desktop app).

↑ Back to top