aliro_gait.py
Aliro Gait: carry-motion features from Aliro Lab walk-up captures.
Overview
Aliro Gait: carry-motion features from Aliro Lab walk-up captures.
Usage: python3 tools/aliro_gait.py [-o report.html] [label=]capture.log ...
E1 probe of the passive carry verification experiment: for every walk-up transaction in the given "[ALAB]" captures, detrend the per-block trusted-range series, FFT the residual, and report the carry-motion features (cadence, stride regularity, approach speed, deceleration, closest approach, residual RMS) plus a per-window carried/stationary verdict. With two or more labels (one per carrier, e.g. alice=alice.log bob=bob.log) it also runs leave-one-out nearest-centroid classification to measure whether the features separate the carriers — the pre-registered Tier-2 GO bar is >= 80%.
The block duration (and the phone's implied RAN multiplier) is derived from the range timestamps themselves, so no extra firmware logging is needed. Exit status: 0 = report produced, 2 = usage/input error.
depends on aliro_lab.py
API
Cclass WalkUp
One analyzed walk-up: identity, series shape, features (or skip reason).
analyze_walkupFWalkUp.__init__(self, label, txn_index)
Initialize one walk-up record: label and transaction index are fixed; n, dur_s, block_ms, ran, features, and skip are populated by analysis.
F_median(xs)
Return the median of xs (middle value if odd length, average of two middle values if even).
_approach_windows, _polyfit2, analyze_walkupF_solve3(a, b)
Gaussian elimination for the 3x3 normal equations of the quadratic fit.
_polyfit2F_polyfit2(ts, xs)
Least-squares x(t) = a*t^2 + b*t + c; returns (a, b, c).
analyze_walkup · calls _median, _solve3F_resample(ts, xs, dt)
Linear interpolation onto a uniform dt grid (missed blocks leave gaps in the capture; the spectrum needs even spacing).
analyze_walkupF_hann(n)
Return a Hann window of length n as a list; for n < 2 return all 1.0 (no window).
_norm_spectrum, incremental_cadence_hzF_dft_power(xs, nfft)
Power spectrum, bins 0..nfft/2 (input zero-padded to nfft). O(N*K) is fine at these sizes; keeps the tool stdlib-only.
_norm_spectrumF_norm_spectrum(xs, nfft)
Hann-windowed magnitude spectrum normalized by coherent gain, so a unit-amplitude sine at a bin center reads ~1.0 regardless of length.
_diff_spectrum · calls _dft_power, _hannF_diff_spectrum(xs, fs, nfft)
Spectrum of the first-differenced series, gain-compensated back to amplitude units. Differencing crushes what the polynomial detrend leaves below the gait band (slow multipath/body-shadow wander) — the dominant source of false cadence peaks — and dividing by the difference filter's gain |H| = 2 sin(pi f / fs) undoes its tilt so the peak location is unbiased. DC (gain 0) is pinned to zero, which the band never reaches.
_gait_spectra · calls _norm_spectrumF_gait_spectra(resid, fs, nfft)
(coincidence, full) magnitude spectra of the residual. The coincidence spectrum is the min() of the two half-window spectra: at ~26-sample windows and SNR ~1 a single periodogram's noise lobes can out-peak the true cadence line, but a noise lobe rarely lands on the same frequency in both halves, while the gait line is in both by construction. The caller picks the coarse peak on the min() and refines its location on the full-window spectrum, which has the sharper mainlobe.
analyze_walkup · calls _diff_spectrumF_goertzel_power(xs, f_hz, fs_hz)
Compute the power at frequency f_hz in a signal xs sampled at fs_hz using the Goertzel algorithm: real-time single-pass O(n) equivalent to FFT bin power.
incremental_cadence_hzFincremental_cadence_hz(xs, fs_hz, band_lo, band_hi)
Firmware-shaped estimator: first-difference high-pass (kills the approach trend), Hann weight over the trailing window, Goertzel bank across the gait band. This is what a ring buffer plus a few dozen multiplies per block can afford on target; test_aliro_gait.py holds it to the FFT answer so the future firmware learner mirrors this exactly.
analyze_walkup · calls _goertzel_power, _hannFanalyze_walkup(label, idx, ranges)
Features from one approach's trusted-range series, already windowed to the descending phase by _approach_windows.
walkups_from_text · calls WalkUp, _gait_spectra, _median, _polyfit2, _resample, incremental_cadence_hzF_approach_windows(txn)
Split one session into its individual approaches.
When the phone keeps the BLE session up while the carrier walks up several times, one session.start..session.end holds multiple approaches, each ended by a relock.sent. Split on those relocks and keep each approach's descending phase (segment start .. closest approach), dropping the walk-away and the door-side loitering that follow it. Legacy single-approach captures (no relock, e.g. synthetic traces) fall back to the whole series, cut at the bolt stamp as before.
walkups_from_text · calls _medianFwalkups_from_text(label, text)
Parse Aliro Lab event text into transactions, extract individual approaches (windowed ranges) from each, and return a list of WalkUp objects with features and skip reasons analyzed per approach.
load_walkups · calls _approach_windows, analyze_walkupFload_walkups(labeled_paths)
Load walk-up event sets from a list of labeled file paths. For each path, open the file and parse all transactions and approaches from its text, returning a list of WalkUp objects with features and skip reasons analyzed per approach.
main · calls walkups_from_textFclassify(walkups)
Leave-one-out nearest-centroid over z-scored features. Returns None unless there are >= 2 labels with >= 2 analyzed walk-ups each.
main · calls zFz(vec_)
Z-score a feature vector: compute mean and std of each feature across all samples, then return (vec[k] - mean[k]) / std[k] for each k (or 0.0 if std is negligible).
classifyFrender_terminal(walkups, cls, use_color)
Render walk-up data as a terminal table with optional ANSI color. Columns: label, transaction index, event count, duration (s), block (ms), ranged-sample count, cadence (Hz), incident cadence (Hz), prominence, regularity, speed (cm/s), RMS (cm), verdict (CARRY+, carry, or still). Skipped walk-ups show skip reason. If classifier results provided, display leave-one-out accuracy and confusion matrix. Prom weights the cadence estimate (Tier 2); motion verdict combines cadence and approach detection.
main · calls paintFpaint(code, text)
Return the string text wrapped in ANSI color code if use_color is true, otherwise return text unchanged.
render_terminalF_scatter_svg(walkups)
Cadence vs stride regularity, one dot per walk-up, colored by label: the E1 eyeball plot (do the carriers cluster?).
render_html · calls sx, syFsx(v)
Map a value v on the x-axis (cadence_hz range) to SVG pixel coordinate m + (v - x_lo) / (x_hi - x_lo) * (wpx - 2*m), where m is margin and wpx is plot width.
_scatter_svgFsy(v)
Map a value v on the y-axis (regularity range) to SVG pixel coordinate hpx - m - (v - y_lo) / (y_hi - y_lo) * (hpx - 2*m), where m is margin and hpx is plot height (inverted so higher values are higher on the plot).
_scatter_svgFrender_html(walkups, cls, title)
Render walk-ups as a standalone HTML document with a table of features (cadence, regularity, speed, RMS), a cadence-vs-regularity scatter plot, and (if classifier results provided) a leave-one-out confusion matrix. Title appears in <title> and <h1>.
main · calls _scatter_svgFmain(argv)
Parse command-line arguments (label=path pairs or bare paths), load walk-ups from files, run leave-one-out nearest-centroid classifier (if >= 2 labels), and render terminal output (with ANSI color if stdout is a TTY). If -o is given, also render HTML report to that file. Return 2 on argument error, 0 on success, or OSError on file read failure.
classify, load_walkups, render_html, render_terminal