Skip to content

Reading files

rec = mm.read(path)          # dispatches on content
mm.sniff(path)               # just tell me what this is

read inspects the file's first lines rather than its extension. In the corpus this was built for, the extension lies: the balance-board dumps are named .tsv and are space-delimited and headerless.

What you get back

Every reader returns a MotionRecord with the same fields whatever the source:

Field Meaning
data (n_samples, n_channels) float, gaps as NaN
fs sampling rate, measured where the file has a timebase
channels column names
kind "position" or "acceleration" — decides differentiate or integrate
unit "mm", "m", "g", "m/s^2", "counts"
vertical which axis is up; not always Z
t, t0 timestamps and absolute start, where they exist
meta whatever else the header carried

Supported layouts

Reader Format
read_qualisys Qualisys and Qualisys-style TSV, all three header shapes
read_sverm plain-header optical export, Time plus per-subject columns
read_ax3 Axivity AX3, ts,x,y,z
read_phone Physics Toolbox phone log
read_equivital Equivital physiology CSV
read_balance_board Wii balance board, headerless and irregular
read_fnirs Artinis Brite wide export, IMU channels only

The Qualisys reader detects its own variant by trying to parse the eleventh line as numbers, so exports differing in whether they carry a column-name row, or Frame/Time columns, all read correctly.

Sentinels become NaN

Each of these is a plausible-looking value that is not a measurement, and each has cost an analysis at least once:

Source Sentinel
optical zero coordinate triplet — a "marker at the origin"
phone exact-zero rows at the start of a recording
respiration belt samples pinned at 0 or 1023, the converter's rails
balance board centre of pressure at exactly (0.5, 0.5) under no load

Reading markers

rec.markers                  # names
rec.marker("HF")             # (n_samples, 3) for one marker

Always by name. Six files in one collection break the documented 22-marker order, and a positional read mis-assigns every marker in all of them.

Gaps

mm.gap_report(rec.data, rec.fs)
clean = mm.interpolate_gaps(rec.data, max_gap=200)

interpolate_gaps bridges short runs and leaves long ones as NaN. Bridging a dropped frame is reconstruction; bridging a 469-second hole is invention. A single missing fraction hides the distinction that matters — one per cent scattered evenly is a usable recording, one per cent in a single block is two recordings.