|
1 | 1 | # stamina engine |
2 | 2 |
|
3 | | -**Status:** `planned` |
| 3 | +**Status:** `imported` · v0.1.0 · Apache-2.0 |
4 | 4 |
|
5 | | -Turns 8-channel EMG into a stamina score (0–100) and a 4-state state machine. |
6 | | -Not a linear regression — three independent dimensions with explicit theoretical grounding. |
| 5 | +Three-dimensional EMG-based stamina estimation with **temporal attention** |
| 6 | +(exponential-decay weighting) and **variance-derived confidence**. Pure |
| 7 | +numpy, no training, fully deterministic. |
7 | 8 |
|
8 | | -## Three dimensions |
| 9 | +## What it computes |
9 | 10 |
|
10 | | -| Dimension | Weight | What it captures | Reference | |
11 | | -|---|---|---|---| |
12 | | -| Pattern Consistency | 0.40 | Coefficient of variation of RMS over a 30 s window — proxy for sustained focus | — | |
13 | | -| Baseline Tension | 0.25 | Residual muscle tone above rest baseline | — | |
14 | | -| Sustained Capacity | 0.35 | Median frequency (MDF) drift over time — physiological fatigue | De Luca, 1997 | |
| 11 | +For each analysis window (default 250 ms of EMG samples) the engine |
| 12 | +extracts: |
15 | 13 |
|
16 | | -## State machine |
| 14 | +- **RMS** — root mean square amplitude |
| 15 | +- **MDF** — median frequency of the power spectrum (De Luca, 1997) |
17 | 16 |
|
| 17 | +…then reduces them into three independent fatigue dimensions: |
| 18 | + |
| 19 | +| Dimension | Source | What it captures | |
| 20 | +|---|---|---| |
| 21 | +| Pattern Consistency | CoV of RMS over recent windows | Motor pattern stability | |
| 22 | +| Baseline Tension | RMS / resting-baseline RMS | Residual carried tension | |
| 23 | +| Sustained Capacity | MDF leftward drift vs. baseline | Spectral fatigue (De Luca 1997) | |
| 24 | + |
| 25 | +Each dimension is normalised to [0, 1] and aggregated across recent |
| 26 | +windows using **exponential decay** (newest window weight 1.0, with a |
| 27 | +configurable half-life). The aggregate is then linearly combined into a |
| 28 | +fatigue score that drives a stamina accumulator (drains in work, recovers |
| 29 | +in rest), and finally classified into a state. |
| 30 | + |
| 31 | +## What's new vs. a plain three-dimensional sum |
| 32 | + |
| 33 | +Compared to a flat per-window combination, this engine adds: |
| 34 | + |
| 35 | +1. **Temporal attention** via exponential decay. Recent windows weigh |
| 36 | + more than older ones — addresses the same intuition as the multilevel |
| 37 | + attention mechanism in *Yan et al., PLOS One 2024* on sEMG fatigue |
| 38 | + recognition, in closed form and without training. |
| 39 | +2. **Variance-based confidence**. Every reading carries a confidence |
| 40 | + score derived from per-window RMS variance. Sparse or extremely |
| 41 | + noisy data automatically reduces downstream trust. |
| 42 | +3. **Per-dimension audit**. Two readings can both be `stamina=55` but |
| 43 | + come from very different dimension breakdowns. Consumers (e.g. the |
| 44 | + intervention engine) can read each dimension to differentiate |
| 45 | + "motor pattern unstable, needs rest" from "baseline tension high, |
| 46 | + needs relaxation". |
| 47 | +4. **Persistence**. `save_state()` / `load_state()` / `compute_offline_recovery()` |
| 48 | + survive process restarts and resume with a sensible estimate of |
| 49 | + recovery during downtime. |
| 50 | + |
| 51 | +## Install |
| 52 | + |
| 53 | +```bash |
| 54 | +cd engines/stamina |
| 55 | +pip install -e . |
18 | 56 | ``` |
19 | | -focused (>60) → fading (30–60) → depleted (<30) → recovering |
| 57 | + |
| 58 | +## Use |
| 59 | + |
| 60 | +```python |
| 61 | +from focux_stamina import StaminaConfig, StaminaEngine |
| 62 | +import numpy as np |
| 63 | +import time |
| 64 | + |
| 65 | +cfg = StaminaConfig(sample_rate_hz=1000.0, window_size_sec=0.25) |
| 66 | +engine = StaminaEngine(cfg) |
| 67 | + |
| 68 | +# every 250 ms of EMG samples, push them through: |
| 69 | +samples = np.array([...]) # one window |
| 70 | +reading = engine.update(samples, now=time.time()) |
| 71 | + |
| 72 | +print(reading.stamina, reading.state.value, reading.confidence) |
| 73 | +print(reading.dimensions.to_dict()) |
| 74 | +# {'pattern_consistency': 0.21, |
| 75 | +# 'baseline_tension': 0.45, |
| 76 | +# 'sustained_capacity': 0.12} |
20 | 77 | ``` |
21 | 78 |
|
22 | | -## Accumulator behaviour |
| 79 | +See [`examples/demo.py`](examples/demo.py) for a full 60-second synthetic |
| 80 | +session that ramps up fatigue and exercises every dimension. |
| 81 | + |
| 82 | +## Public API |
| 83 | + |
| 84 | +| Name | Purpose | |
| 85 | +|---|---| |
| 86 | +| `StaminaState` | Enum: focused / fading / depleted / recovering / unknown | |
| 87 | +| `FatigueDimensions` | Per-axis breakdown (`pattern_consistency`, `baseline_tension`, `sustained_capacity`) | |
| 88 | +| `StaminaReading` | Engine output dataclass with `stamina`, `state`, `confidence`, `dimensions`, `notes` | |
| 89 | +| `StaminaConfig` | All sample/window/weight/threshold parameters | |
| 90 | +| `StaminaEngine` | `update(samples, *, now)`, `save_state()`, `load_state(snap)`, `compute_offline_recovery(elapsed_sec)`, `current_stamina` | |
| 91 | +| `rms`, `median_frequency`, `pattern_consistency`, `baseline_tension`, `sustained_capacity` | Standalone feature helpers | |
| 92 | +| `exponential_decay_weights`, `weighted_aggregate`, `confidence_from_variance` | Attention helpers | |
| 93 | + |
| 94 | +## Tests |
| 95 | + |
| 96 | +```bash |
| 97 | +pip install -e .[test] |
| 98 | +pytest |
| 99 | +``` |
23 | 100 |
|
24 | | -- Working state: stamina decreases at `drain_rate × (0.5 + fatigue_multiplier)` |
25 | | -- Resting state: recovers at `recovery_rate` |
26 | | -- Supports persistence: save / load state, compute natural recovery while offline |
| 101 | +48 tests covering: RMS / MDF on zero / empty / sine / dominant-frequency |
| 102 | +inputs; pattern consistency, baseline tension, sustained capacity edge |
| 103 | +cases (empty / constant / drop magnitudes); exponential decay weights |
| 104 | +(half-life correctness, monotonicity, degenerate zero half-life); |
| 105 | +weighted aggregate; confidence-from-variance; engine initial state, |
| 106 | +drain in work, recover in rest, state classification at thresholds, |
| 107 | +dimensions within unit interval, variable-amplitude increases pattern |
| 108 | +dimension, persistence roundtrip, offline recovery cap at 100, |
| 109 | +confidence within unit interval. |
27 | 110 |
|
28 | | -## Why three dimensions instead of one number |
| 111 | +## Design decisions |
29 | 112 |
|
30 | | -The same stamina = 60 can come from *unstable pattern* (rest needed) or *accumulated tension* |
31 | | -(relaxation needed) or *spectral fatigue* (stop working). Downstream policies need to differentiate. |
| 113 | +- **No neural net.** Exponential decay is the closed-form, training-free |
| 114 | + version of "attention over recent windows". For sEMG fatigue the |
| 115 | + marginal accuracy of a learned attention layer doesn't justify the |
| 116 | + audit-trail loss. |
| 117 | +- **Three dimensions, kept separate.** Same `stamina` value can mean |
| 118 | + different things; downstream policies need to differentiate them. |
| 119 | +- **Calibration is automatic.** The first few seconds of low-activity |
| 120 | + data populate per-user baselines for RMS and MDF. The engine reports |
| 121 | + `baseline_pending` in `notes` until calibration completes. |
| 122 | +- **Persistence over process boundary.** The engine is the |
| 123 | + user's continuous state record — it must survive restarts. |
32 | 124 |
|
33 | | -## To be imported |
| 125 | +## References |
34 | 126 |
|
35 | | -- `StaminaEngine` core class |
36 | | -- `StaminaReading` dataclass + state enum |
37 | | -- Persistence helpers (`save_state` / `load_state` / `compute_offline_recovery`) |
38 | | -- Unit tests covering each dimension and the state transitions |
| 127 | +- De Luca, C. J. *The Use of Surface Electromyography in Biomechanics.* |
| 128 | + J Applied Biomechanics, 1997. (MDF as fatigue marker) |
| 129 | +- Yan, X. et al. *Multilevel attention mechanism for motion fatigue |
| 130 | + recognition based on sEMG and ACC signal fusion.* PLOS One, 2024. |
| 131 | +- Park, J. et al. *A Multimodal Fatigue Detection System Using sEMG |
| 132 | + and IMU Signals with a Hybrid CNN-LSTM-Attention Model.* Sensors, 2025. |
0 commit comments