Part VI: Motion, Location, and Inertial Intelligence
Chapter 27: Gesture, Pose, and Neuromotor Interfaces

IMU gestures and low-latency inference

"The wearer flicked a wrist and expected the world to have already answered. My whole job was to decide what they meant before they finished meaning it."

A Low-Latency AI Agent

Prerequisites

This section builds on the accelerometer and gyroscope measurement model of Chapter 23 (what an IMU actually senses, and its noise and bias) and the sampling, windowing, and synchronization ideas of Chapter 3. The segmentation and NULL-class problems from Chapter 26 reappear here at a shorter time scale. The causal filtering constraints come from Chapter 6, and the on-device optimization we lean on is developed in Chapter 59.

The Big Picture

A gesture is a promise about time. When someone flicks a smartwatch to dismiss a call, double-taps a ring to answer, or draws a circle in the air with a VR controller, they expect the response to feel simultaneous with the motion. That expectation is brutal: the model must recognize a short, deliberate movement from a raw inertial stream, and it must commit to an answer within tens of milliseconds of the movement ending, often before it ends. This is a fundamentally different problem from the activity recognition of Chapter 26, where a label describes minutes of behavior and a second of delay is invisible. Here the whole gesture may last 300 ms, the interaction budget may be 100 ms, and every architectural choice is a negotiation with the clock. This section is about designing IMU gesture recognizers that are accurate and fast enough to feel instant.

What an IMU gesture is, and the always-on spotting problem

An IMU gesture is a short, intentional, discrete movement drawn from a small vocabulary: wrist flick, double-tap, wake-on-raise, shake-to-undo, an air-drawn digit, a controller swipe. Unlike an activity, it has a defined onset and offset and it is rare. That rarity is the central design pressure. A wrist-worn recognizer runs continuously, sampling the accelerometer and gyroscope at 50 to 200 Hz, and for the overwhelming majority of that time the correct answer is "no gesture." The task is therefore not classification but gesture spotting: continuously detecting whether a gesture is occurring, where it starts and ends, and only then which one it is. This is open-set detection against an unbounded NULL class of everyday motion, the same asymmetry seen in Chapter 26 but far more extreme because the positive events are seconds apart.

The metric that governs a spotter is not accuracy but the false-activation rate, usually quoted as false positives per hour of ordinary wear. A raise-to-wake feature that fires spuriously twice an hour is unshippable, because each false wake drains battery and erodes trust, and the user cannot tell the system "no." So the operating point sits deep on the high-precision side of the curve, and the true enemy is the confuser: reaching for a coffee cup looks a lot like a deliberate raise. Handling this well means training with an enormous, diverse negative set of real daily motion, not just clean gesture recordings, and it means calibrating a confidence threshold rather than trusting a raw softmax, a theme we return to through the conformal methods of Chapter 18.

The latency budget: where the milliseconds go

"Low latency" is meaningless until you decompose it. The user-perceived, motion-to-response latency is a sum of stages, and a model that is fast in isolation can still feel sluggish because it is starved of context by the stages around it:

\[ T_{\text{response}} = T_{\text{sample}} + T_{\text{window}} + T_{\text{infer}} + T_{\text{debounce}} + T_{\text{actuate}}. \]

Here \(T_{\text{sample}}\) is the wait for the next IMU sample and its transport off the sensor (often the smallest term, but non-zero at 50 Hz where samples are 20 ms apart). \(T_{\text{window}}\) is the analysis window the model needs to see the gesture; a 250 ms window means the decision cannot, in principle, be made until at least 250 ms of motion has accrued. \(T_{\text{infer}}\) is the forward pass. \(T_{\text{debounce}}\) is the deliberate delay added to suppress repeats and confirm the event. \(T_{\text{actuate}}\) is the downstream response, up to the display or haptic. The uncomfortable truth is that \(T_{\text{window}}\) usually dominates, so the fight for latency is mostly a fight to shrink or partially overlap the window, not to shave microseconds off the network.

How fast is fast enough? Perceptual research gives rough anchors: below about 100 ms an interface feels instantaneous, and around 1 ms of controller-to-render latency is the target for AR and VR to avoid motion sickness. For discrete wearable gestures a common engineering budget is a sub-150 ms end-to-end response, which after subtracting actuation and debounce leaves the model and its window fighting over perhaps 80 ms. That constraint, not the dataset, is what forces the architecture. It also forces causal filtering: the zero-phase filters of Chapter 6 that look into the future are forbidden, so denoising must be strictly causal and its group delay counts against the budget.

Key Insight

The scarce resource in gesture recognition is not compute, it is future samples. A batch classifier gets the whole gesture and picks the best window; a streaming spotter must decide at time \(t\) using only samples up to \(t\). Every millisecond you wait for more of the gesture buys accuracy but spends latency. Low-latency inference is therefore the discipline of early commitment: deciding as soon as the evidence crosses a confidence threshold, and accepting that you will sometimes commit on a partial gesture. The entire accuracy-versus-latency trade lives in where you set that threshold.

Streaming, causal models, and early commitment

Two implementation styles dominate. The sliding-window approach re-runs a fixed-length classifier on the most recent \(W\) samples every stride, which is simple and maps onto the temporal convolutional and recurrent models of Chapter 14, but it recomputes overlapping work and its latency is floored by \(W\). The streaming-state approach keeps a recurrent hidden state or a causal convolution buffer and advances it one sample at a time, so per-sample cost is constant and the model can emit a decision at any instant. Streaming state is why linear-time recurrent and state-space models are attractive on wearables; the broader streaming-inference machinery, including how to warm and checkpoint that state, is the subject of Chapter 60.

On top of either style sits the commitment logic. A robust spotter does not fire the moment one frame's probability spikes; it waits until the top class has held above a threshold \(\tau\) for a short confirmation, then fires once and enters a refractory period during which it will not fire again, exactly like a debounced button. This suppresses the double-taps and mid-gesture flickers that otherwise turn one flick into three events. The expected latency of such a policy is the time for accumulated evidence to cross \(\tau\), which you can lower by lowering \(\tau\) at the cost of more false activations. The code below implements this streaming spotter with a stub scorer so the control logic is visible; swapping in a real quantized network changes only the score call.

import numpy as np
from collections import deque

class StreamingGestureSpotter:
    """Causal, one-sample-at-a-time IMU gesture spotter with early
    commitment and a refractory period. Runs in O(1) work per sample."""
    def __init__(self, win=25, tau=0.85, confirm=3, refractory=20, n_cls=4):
        self.buf = deque(maxlen=win)     # causal ring buffer of recent samples
        self.tau, self.confirm = tau, confirm
        self.refractory = refractory
        self.above = 0                   # consecutive frames top class > tau
        self.cooldown = 0                # samples left in refractory
        self.last_top = -1
        self.n_cls = n_cls

    def score(self, window):
        """Stub for a real quantized net: returns class probabilities.
        Here: a cheap energy/axis heuristic just to exercise the logic."""
        w = np.asarray(window)
        feat = np.array([w[:, 0].std(), w[:, 1].std(),
                         w[:, 2].std(), 1.0])          # 4th = NULL bias
        logits = feat - feat.mean()
        e = np.exp(logits - logits.max())
        return e / e.sum()

    def step(self, sample):
        """Feed one IMU sample (ax, ay, az); return fired class id or None."""
        self.buf.append(sample)
        if self.cooldown > 0:
            self.cooldown -= 1
            return None
        if len(self.buf) < self.buf.maxlen:
            return None                  # not enough history yet
        p = self.score(self.buf)
        top = int(p.argmax())
        if top != self.n_cls - 1 and p[top] >= self.tau and top == self.last_top:
            self.above += 1
        else:
            self.above = 0
        self.last_top = top
        if self.above >= self.confirm:   # early commit once evidence holds
            self.above, self.cooldown = 0, self.refractory
            return top
        return None

spotter = StreamingGestureSpotter()
rng = np.random.default_rng(0)
stream = rng.normal(0, 0.1, size=(200, 3))
stream[80:100, 0] += 1.5                 # inject a class-0 gesture burst
fires = [(t, spotter.step(s)) for t, s in enumerate(stream)]
print([f for f in fires if f[1] is not None])
A causal streaming gesture spotter. It keeps a fixed ring buffer, scores only the most recent window, and fires exactly once when the top non-NULL class holds above tau for confirm frames, then enters a refractory cooldown so a single gesture cannot double-fire. The score method is a placeholder for a real quantized network; the surrounding early-commitment and debounce logic is what makes the response feel instant and single. Referenced in the streaming discussion above.

Right Tool: template gestures without training data

Before you train a network, a strong baseline for a handful of gestures is Dynamic Time Warping against a few recorded templates, which tolerates the speed variation between a slow and a fast wrist flick that a fixed-window classifier cannot. Hand-rolling DTW with a proper Sakoe-Chiba band, per-class templates, and a rejection threshold is roughly 80 lines and easy to get subtly wrong at the boundaries. tslearn reduces it to about 5: from tslearn.metrics import dtw plus a nearest-template loop with a distance cutoff for the NULL class. Reach for this when you have fewer than a dozen gestures and only a few examples each; graduate to the streaming net above once the vocabulary or the false-activation demands grow.

In Practice: the earbud tap that had to beat the finger

A wireless-earbud team wanted a tap-to-play gesture sensed by the bud's onboard IMU, with no capacitive touch pad. The physics was favorable: a fingertip tap on the housing produces a sharp, high-frequency acceleration transient unlike almost any head motion. The problem was latency and confusers. Their first model used a 400 ms window centered on the peak, which meant it could not decide until 200 ms after the tap, and the audio felt laggy. They rebuilt it as a causal streaming detector on a 60 ms post-onset window, triggered by a cheap energy threshold that woke the classifier only on candidate transients, so the expensive model ran on perhaps 1% of samples. Chewing and footfalls were the stubborn confusers; the fix was a large negative set recorded during eating and stair descent, plus a two-tap confirmation for the destructive "skip" action. End-to-end response fell under 90 ms, and false activations dropped to well below one per hour, the number the product manager had refused to ship without.

Making it fit and stay honest

The model that satisfies this budget is almost always tiny: a small 1D temporal convolutional network or a light recurrent model, quantized to 8-bit integers and running on the wearable's microcontroller or low-power DSP, using the compression and kernel-fusion techniques of Chapter 59. A frequent and effective pattern is the two-stage cascade the earbud team used: a nearly free always-on trigger (an energy or jerk threshold) gates a heavier classifier that runs only on candidate segments, cutting average power by an order of magnitude while preserving worst-case latency on real gestures.

Evaluation must respect the same asymmetry the metric does. Report latency as a distribution, not a mean: the median tells you how it usually feels, but the 95th percentile tells you how often it feels broken, and a long tail from occasional recomputation is what users actually complain about. Report false activations over long, unscripted, real-world wear, not over a balanced test set, because a balanced set hides the base-rate problem that dominates always-on use. And evaluate on held-out users and sessions: gesture styles are idiosyncratic, and a spotter that memorizes one person's exact flick trajectory will fail on the next wrist, the leakage-safe discipline that runs through this whole book and is formalized in Chapter 5. The cross-user generalization problem is severe enough that Section 27.5 devotes itself to calibration-free decoding.

Research Frontier

The current push is toward always-on gesture spotting that is both cheaper and lower-latency than sliding-window nets. Linear-time state-space models such as Mamba and S4 (Chapter 16) offer constant per-sample streaming cost with long effective context, attractive for tiny always-on recognizers. In parallel, event-driven and neuromorphic inertial pipelines (Chapter 46) fire computation only when the signal changes, matching the rare, bursty nature of gestures and slashing idle power. And on the modeling side, wearable IMU foundation models (Chapter 20) pretrained self-supervised on unlabeled motion are beginning to give few-shot gesture vocabularies, where a user teaches a new gesture from two or three examples rather than a labeled corpus. The open problem binding these together is guaranteeing a hard worst-case latency bound on hardware while keeping false activations below one per hour across an unseen population.

Exercise

Take the streaming spotter above and instrument it. (1) Add a per-fire latency measurement: for the injected burst starting at sample 80, record how many samples after onset the fire occurs, and convert to milliseconds assuming 50 Hz. (2) Sweep tau from 0.6 to 0.95 and plot fire latency against the number of spurious fires you get when you feed pure noise; you have just drawn the accuracy-latency Pareto curve for this detector. (3) Explain, using your \(T_{\text{response}}\) decomposition, which single change would cut perceived latency most: a smaller window, a lower threshold, or a shorter refractory. Justify from the budget, not intuition.

Self-Check

1. Why is false-activations-per-hour, rather than balanced accuracy, the governing metric for an always-on IMU gesture spotter?
2. In the latency decomposition, which term usually dominates for a short discrete gesture, and what does that imply about where to spend engineering effort?
3. What is early commitment, and what does lowering the confidence threshold \(\tau\) buy and cost?

What's Next

In Section 27.3, we move from recognizing a handful of discrete gestures to continuously reconstructing hand and body pose from wearable sensors. The step up is large: instead of committing to one of a few labels, the system must regress a full articulated configuration in real time, which raises the same latency questions we met here but against a far richer, continuous output space.