Skip to content

Recipes

Alexander Refsum Jensenius edited this page Jul 29, 2026 · 2 revisions

Quantity of motion for every marker in a file

import micromotion as mm
rec = mm.read("mocap_data/A0001.tsv")
for name in rec.markers:
    xyz = mm.interpolate_gaps(rec.marker(name), max_gap=200)
    xyz = xyz[~np.isnan(xyz).any(axis=1)]
    if len(xyz) < rec.fs * 60:
        continue
    print(name, round(mm.qom(xyz, rec.fs, kind="position").mean_mm_s, 3))

Drop series that are more than about 20 per cent missing rather than filling them.

A table comparable across datasets

y = mm.to_rate(xyz, rec.fs, mm.COMMON_RATE)
row = {
    "qom_mm_s":  mm.qom(y, mm.COMMON_RATE, kind="position").mean_mm_s,
    "native":    mm.qom(xyz, rec.fs, kind="position").mean_mm_s,
    "legacy":    mm.qom(xyz, rec.fs, kind="position", band="optical_legacy").mean_mm_s,
    "rate_hz":   rec.fs,
    "missing":   float(np.isnan(rec.data).mean()),
}

Carry the native and legacy columns even if you do not use them. Someone will ask, and recomputing a corpus takes ten minutes.

Record the task instruction and the body placement in the same table. They matter more than any filter choice.

Separating breathing and heartbeat from posture

raw  = mm.qom(acc, fs, unit="g")                      # everything
comp = mm.qom(acc, fs, unit="g", variant="compensated")  # postural only
mm.band_power_fraction(np.linalg.norm(acc, axis=1), fs,
                       {"resp": (0.1, 0.5), "card": (0.7, 2.2)})

The compensated variant raises the lower edge to 0.5 Hz and notches the recording's own cardiac peak. In one chest-worn dataset roughly 38 per cent of signal power sat at the heart rate.

Aligning two instruments with no shared clock

t1, hr1 = mm.instantaneous_rate(haemoglobin, 75.0)
t2, hr2 = mm.instantaneous_rate(np.linalg.norm(chest_acc, axis=1), 100.0)
r = mm.search_lag(t1, hr1, t2, hr2, max_lag_s=300)
if r["confident"]:
    t2_aligned = mm.apply_lag(t2, -r["lag_s"])

Works because both instruments see the same heart, and a heart rate wanders in a way specific enough to match. A flat rate cannot be aligned.

If both recordings contain a sync clap, mm.find_transient(x, fs, search_s=20) is exact and needs no assumptions.

Balance board to millimetres

rec = mm.read_balance_board(path)
cop = rec.data * np.array(mm.io.BOARD_MM)      # normalised 0-1 -> mm
grid, cop = mm.regularize(rec.t, cop, fs_out=20.0, max_gap_s=2.0)
mm.sway_geometry(cop), mm.ellipse_area_95(cop)

The board is irregularly sampled with duplicate and backward timestamps. regularize sorts and cleans before interpolating, and refuses to bridge long gaps.

Is this movement structured or random?

from micromotion import dynamics as dy
dy.dfa(speed)["alpha"]        # 0.5 white, 1.0 pink, 1.5 Brownian
dy.surrogate_test(speed, dy.trev, n=99)

The surrogate test asks whether the series differs from a linear Gaussian process with the same spectrum and distribution. Use at least 99 surrogates for anything reportable — 25 gives a p value that cannot go below 0.038.

Clone this wiki locally