Utility package for Fieldline OPM-MEG data processing.
# Core install (HPI coregistration + channel utilities)
pip install .
# Include tools that need pandas (helmetscan_2d, check_events)
pip install ".[tools]"
# Include the 3-D VTK visualiser (also needs pandas, PyQt5, vtk)
pip install ".[vtk]"After installation the opmutil executable is available on PATH.
For local development without installation, run_hpi.py continues to work
as before (see Legacy usage).
opm_utility_scripts/
├── __init__.py Public API
├── cli.py opmutil console-script entry point
├── channels.py Channel utility functions
├── viz.py Visualisation utilities
├── io.py File I/O and dialog helpers
├── analog/
│ ├── __init__.py
│ ├── mapping.py Generate analog channel mapping
│ ├── rename.py Rename analog channels in FIF files
│ └── analog_channel_mapping.json Default mapping (ai1–ai20 → labelled channels)
├── hpi/
│ ├── __init__.py
│ ├── _core.py Shared HPI pipeline (fit_hpi, fit_hpi_amplitudes,
│ │ apply_transform, save_raw, compute_fit_diagnostics)
│ ├── coregister.py Unified single/multi-file entry point
│ └── check.py Dipole-fit HPI quality check
└── tools/
├── __init__.py
├── check_events.py Inspect trigger events in FIF files
├── helmetscan_2d.py 2-D helmet scan layout plot
├── helmetscan_3d_vtk.py 3-D VTK helmet scan visualisation (requires vtk, PyQt5)
├── hedscan_layout.mat HEDscan layout file
├── fieldlinebeta2bz_helmet.mat Fieldline helmet layout file
└── data_sensors.csv Sensor slot template for 3-D visualisation
opmutil is the single executable for all HPI and utility scripts.
usage: opmutil [-h] [--version] {check,coregister} ...
Select one or more data files; the transform is fitted once and applied to all
selected files. Output suffix: _proc-hpi_raw.fif, or
_proc-hpi+ds_raw.fif when resampling changes the sampling frequency.
Omitted required inputs are requested through command-line prompts, so the command works non-interactively, interactively, or anywhere in between.
# Fully interactive (command-line prompts)
opmutil coregister
# Fully non-interactive
opmutil coregister \
--data AudOdd_raw.fif RSEO_raw.fif \
--hpi HPIBefore_raw.fif \
--pol digitisation.json \
--freq 33 --sfreq 1000 \
--save --overwrite --plot
# With noise-reference bad-channel detection and a custom GOF threshold
opmutil coregister \
--data AudOdd_raw.fif \
--hpi HPIBefore_raw.fif \
--pol digitisation.json \
--reffile RestingState_raw.fif \
--freq 33 --sfreq 1000 --gof 0.9 \
--save
# Uncentred matching only (not the complete historical legacy engine)
opmutil coregister \
--data AudOdd_raw.fif \
--hpi HPIBefore_raw.fif \
--pol digitisation.json \
--no-center-matching \
--save
# Without rigid-refinement optimization
opmutil coregister \
--data AudOdd_raw.fif \
--hpi HPIBefore_raw.fif \
--pol digitisation.json \
--optimization none \
--save| Flag | Description | Default |
|---|---|---|
--data |
One or more data files to apply the transform to | (ask) |
--hpi |
Raw HPI recording | (ask) |
--pol |
Polhemus digitisation file (.json or .fif) |
(ask) |
--reffile |
Optional reference recording (e.g. resting state) used for background-power-based noisy channel detection | auto tries a clean HPI tail if omitted |
--freq |
HPI drive frequency in Hz | (ask) |
--gof |
Minimum dipole GOF for a coil to be included in the device-to-head transform fit | 0.95 |
--no-center-matching |
Compatibility alias for --matching-strategy coordinate_nearest; changes matching only, independently of refinement |
centred |
--optimization |
Post-registration refinement: none or rigid_gof (bounded L-BFGS-B maximizing field-fit GOF); rigid is an alias for rigid_gof |
rigid_gof |
--sfreq |
Target sampling frequency in Hz | (ask) |
--save / --overwrite / --plot |
Save outputs, overwrite existing FIF files, show alignment plot (not automatically saved) | off |
The following behavior controls are shared by coregister and check:
| Flag | Description | Default |
|---|---|---|
--bad-channel-policy {auto,reference,none} |
auto: use the reference or a clean final five-second HPI tail, warning and skipping detection if unavailable; reference: require --reffile; none: skip noise detection. Explicit bads and invalid geometry are still excluded |
auto |
--activation-window-s |
Actual amplitude-fit duration in seconds, centred on the detected activation midpoint; insufficient activation or out-of-bounds windows raise an error | 2.0 |
--localization-grid {coarse, medium,fine} |
Dipole search OPM grid. | medium |
--gof-comparison {inclusive,strict} |
Include coils with GOF >= --gof or > --gof, respectively |
inclusive |
--matching-strategy {centroid_nearest,coordinate_nearest} |
Nearest-target matching after centring each point cloud, or on raw coordinates; mutually exclusive with --no-center-matching |
centroid_nearest |
--allow-repeated-matches |
Permit repeated nearest targets; degenerate rigid fits still raise an error | off (unique targets required) |
--settings-json [DIR] |
coregister writes one sidecar per saved data output beside that output by default; this option is only needed to choose an optional existing sidecar directory. check writes its fit-stage record only when requested |
enabled for coregister; disabled for check |
Centring removes bulk translation offsets, not arbitrary inter-frame rotations. Matching and refinement are independent controls. There is no branch-selector option: the historical approach names below are comparisons, not CLI presets.
With coregister --save, a settings sidecar is written per
individual transformed data output by default, next to that output, not once per HPI
recording or batch. Its name is
hpi_<output-basename-with-.fif-replaced-by-.json>. For example:
| Transformed output | Settings sidecar in the same directory |
|---|---|
subject_proc-hpi_raw.fif |
hpi_subject_proc-hpi_raw.json |
subject_proc-hpi+ds_raw.fif |
hpi_subject_proc-hpi+ds_raw.json |
opmutil coregister \
--data subject_raw.fif resting_raw.fif \
--hpi HPIBefore_raw.fif --pol digitisation.json \
--freq 33 --sfreq 1000 --saveEach output has its own sidecar even though both share the fitted transform.
An optional --settings-json /existing/sidecar-directory changes the sidecar
directory, not the per-output naming convention. Sidecars record input/output
basenames, library versions, effective fit settings, a compact results summary,
and fit_results: the device-to-head transform (translation in metres),
optimizer method/status, and one entry per HPI coil with its dipole GOF and
inclusion decision. Included coils also record their matched Polhemus point
(zero-based index), post-fit residual in metres, and Polhemus-position GOF.
Non-finite diagnostic values are represented as JSON null. Sidecars are
provenance records, not JSON configuration files read by the CLI.
For the API, fit once and write a sidecar after saving each output:
from pathlib import Path
import mne
from opm_utility_scripts.hpi import fit_hpi, apply_transform, save_raw
from opm_utility_scripts.hpi._core import write_settings_json
hpifile, polfile = "HPIBefore_raw.fif", "digitisation.json"
fit = fit_hpi(hpifile, polfile, 33, optim="rigid_gof")
target_sfreq = 1000
for datafile in ("subject_raw.fif", "resting_raw.fif"):
source_sfreq = mne.io.read_info(datafile, verbose="error")["sfreq"]
suffix = "_proc-hpi"
if int(target_sfreq) != int(source_sfreq):
suffix += "+ds"
raw = apply_transform(datafile, fit, new_sfreq=target_sfreq)
output = Path(save_raw(raw, datafile, suffix + "_raw.fif", overwrite=True))
sidecar = output.with_name("hpi_" + output.with_suffix(".json").name)
write_settings_json(
sidecar, fit, hpifile=hpifile, polfile=polfile,
datafile=datafile, output_file=output,
)The lower-level fit_hpi(..., settings_json="fit.json") and
fit_hpi_amplitudes(..., settings_json="amplitudes.json") instead write an
explicit fit-stage record: they do not apply or save data outputs. Use the
per-output loop above for transformed-data provenance. check also has no
transformed data outputs; its settings record describes the check/fit stage.
The following commands show current CLI settings corresponding as closely as possible to each historical release. They are not version selectors and do not reproduce every historical implementation detail; all use the current sequential fitter. Replace paths and frequency with values for your recording.
v0.3.0:
opmutil coregister \
--data subject_raw.fif \
--hpi HPIBefore_raw.fif --pol digitisation.json \
--freq 33 --bad-channel-policy auto \
--activation-window-s 2 --gof 0.95 --gof-comparison inclusive \
--matching-strategy centroid_nearest \
--optimization rigid_gof --saveThis is the closest match to current defaults: consistent OPM magnetometer ordering,
unique matches, and bounded field-GOF refinement. When saving transformed FIFs,
coregister writes a provenance sidecar for each output by default; use
--settings-json /existing/sidecar-directory to change the sidecar directory.
v0.2.0:
opmutil coregister \
--data subject_raw.fif \
--hpi HPIBefore_raw.fif --pol digitisation.json \
--freq 33 --bad-channel-policy auto \
--activation-window-s 2 --gof 0.95 --gof-comparison inclusive \
--matching-strategy centroid_nearest --allow-repeated-matches \
--optimization rigid_gof --saveThe historical v0.2.0 implementation used noise exclusions after amplitude fitting, a six-second crop whose first two seconds were fitted, and asymmetric optimizer bounds. Those details cannot be selected by the current CLI; this command is only the closest available mapping.
v0.1.0:
opmutil coregister \
--data subject_raw.fif \
--hpi HPIBefore_raw.fif --pol digitisation.json \
--freq 33 --bad-channel-policy none \
--activation-window-s 2 --gof 0.9 --gof-comparison strict \
--matching-strategy coordinate_nearest --allow-repeated-matches \
--optimization none --saveThis does not select the historical duplicate-frequency amplitude model,
integer-frequency peak spacing, or legacy input parsing. The current sequential
fitter and its non-degeneracy checks remain in effect. To test whether the
localization search grid explains a result difference, add
--localization-grid coarse; this selects the stock-MNE initial-guess grid
without changing the rest of the current pipeline.
The --localization-grid option defaults to medium (5 mm grid and 2 mm distance).
Checks an HPI recording by fitting magnetic dipoles to the detected coil fields. Four modes depending on which arguments are supplied:
| Arguments | Mode |
|---|---|
--hpi only |
HPI-only — GOF + device-frame coil positions |
--pol only |
Polhemus-only — dig point summary + head-frame distances |
--hpi + --pol |
Full coregistration — GOF, residuals, recommendations |
| neither | GUI dialogs for both files |
# HPI-only (no polhemus required)
opmutil check --hpi HPIbefore_raw.fif
# Full coregistration
opmutil check --hpi HPIbefore_raw.fif --pol digitisation.json
# Override drive frequency and GOF threshold; verbose output
opmutil check --hpi HPIbefore_raw.fif --pol digitisation.json \
--freq 33 --gof 0.95 --detailed
# With noise-reference bad-channel detection and rigid-refinement optimization
opmutil check --hpi HPIbefore_raw.fif --pol digitisation.json \
--reffile RestingState_raw.fif --optimization rigid
# Uncentred matching only (not the complete historical legacy engine)
opmutil check --hpi HPIbefore_raw.fif --pol digitisation.json \
--no-center-matching| Flag | Description | Default |
|---|---|---|
--hpi |
Path to the raw HPI .fif file |
(ask) |
--pol |
Path to Polhemus file (.fif or .json) |
(ask) |
--freq |
HPI drive frequency in Hz | 33 |
--reffile |
Optional reference recording used for background-power-based noisy channel detection | auto tries a clean HPI tail if omitted |
--gof |
Minimum dipole GOF for a coil to be included in the device-to-head transform fit | 0.95 |
--detailed |
Show full diagnostics in --hpi + --pol mode |
off |
--optimization |
none or rigid_gof; rigid is a compatibility alias. Applies in full coregistration mode |
rigid_gof |
--no-center-matching |
Alias for --matching-strategy coordinate_nearest, independent of refinement; applies in full coregistration mode |
centred |
See the shared behavior controls above for noise policy, sensor population, activation window, GOF comparison, matching and settings output.
All submodules remain runnable with python -m directly:
# HPI coregistration
python -m opm_utility_scripts.hpi.coregister
# HPI quality check
python -m opm_utility_scripts.hpi.check --hpi HPIbefore_raw.fif
# Inspect trigger events
python -m opm_utility_scripts.tools.check_events --file my_raw.fif --stim di38
# 2-D helmet layout
python -m opm_utility_scripts.tools.helmetscan_2d
# Regenerate analog channel mapping JSON
python -m opm_utility_scripts.analog.mapping
# Rename analog channels in a FIF file
python -m opm_utility_scripts.analog.rename --file my_raw.fif --newfile my_renamed_raw.fifrun_hpi.py is kept as a compatibility wrapper for running from the source
tree without installing:
python run_hpi.py coregister
python run_hpi.py check --hpi HPIbefore_raw.fif
python run_hpi.py check --hpi HPIbefore_raw.fif --pol digitisation.jsonIt delegates directly to opmutil's cli.main() and accepts the same
arguments.
The channels, viz, io, and analog.* symbols below are also re-exported
(lazily) directly from the top-level opm_utility_scripts package, e.g.
from opm_utility_scripts import plot_hpi_alignment works. The hpi.*
symbols are not re-exported at the top level and must be imported from the
hpi subpackage as shown.
from opm_utility_scripts.channels import (
find_zero_location_channels, # was TC_findzerochans
get_hpi_output_channels, # was TC_get_hpiout_names
pick_low_noise_meg_chs,
)
from opm_utility_scripts.viz import (
plot_3d, plot_hpi_alignment, plot_psd, rotate_points, create_aligned_grid,
)
from opm_utility_scripts.io import (
get_file, get_files, get_input, get_boolean, write_bw_marker_file,
load_datafile, load_hpifile, load_polhemus, select_best_hpi_file,
)
from opm_utility_scripts.analog.mapping import generate_analog_channel_mapping # was generate_mapping
from opm_utility_scripts.analog.rename import rename_channels
from opm_utility_scripts.hpi import (
fit_hpi, fit_hpi_amplitudes, apply_transform, save_raw, compute_fit_diagnostics,
)| Group | Packages | Install extra |
|---|---|---|
| Core | mne>=1.12, numpy>=1.26, scipy>=1.12, matplotlib>=3.8 |
(default) |
| Tools | pandas>=2.2 |
[tools] |
| VTK visualiser | pandas>=2.2, PyQt5>=5.15, vtk>=9.3 |
[vtk] |