monitor selection, model prototype and 2 more recordings

This commit is contained in:
2026-08-12 16:48:44 +02:00
parent 8d124a26b1
commit 6a13b1ef04
17 changed files with 214117 additions and 25 deletions
+62
View File
@@ -18,6 +18,19 @@ Press `Esc` or `Q` to close it. For a preview in a normal resizable window:
python show_apriltag_surface.py --windowed
```
On a multi-monitor setup, list detected displays (Windows display number,
resolution, position, physical diagonal) and target one of them:
```powershell
python show_apriltag_surface.py --list-monitors
python show_apriltag_surface.py --monitor 2
```
`gaze_exercises.py` accepts the same `--monitor`/`--list-monitors` flags and
uses the selected monitor's resolution and EDID-reported diagonal
automatically, so `--screen-diagonal` only needs to be passed to override an
inaccurate or missing EDID value.
By default, every marker is flush with both adjacent screen edges. Useful
options include `--tag-size 240`, `--margin 32`, and
`--tag-ids 10 11 12 13`. The requested size is rounded down to a multiple of
@@ -126,6 +139,55 @@ CSV columns beyond the basic error metrics:
every row so CSVs from different subjects, screens, and distances can be
concatenated directly for training/evaluation.
## Training the adaptive filter
Once you have one or more recordings in `results/`:
```powershell
python train_filter_model.py
```
This concatenates every `gaze_exercises_*.csv`, builds causal windowed
features (`gaze_features.py`), splits by whole trial (never by row, to avoid
leaking adjacent-in-time samples between train/test), trains a
`RandomForestClassifier` predicting `event_label`, and compares it against a
fixed 30 deg/s velocity-threshold baseline. It prints per-class precision/
recall, a confusion matrix, a saccade-recovery detection-latency comparison,
and feature importances, then saves `models/filter_model.joblib` +
`models/filter_model_meta.json`.
With only one subject/session recorded, the split holds out unseen trials
within that session — it does not yet test cross-subject generalization.
Once more subjects are recorded, re-run training; group the split by
`subject_id` (leave-one-subject-out) instead of by trial for the real paper
evaluation.
`adaptive_filter.py` defines the real-time filters built on that model:
`BaselineFilter` (the existing fixed median+EMA filter), and
`MLAdaptiveFilter` in `mode="hard"` (discrete fixation/saccade/pursuit
switching that discards history on a detected saccade) or `mode="soft"`
(continuous smoothing strength scaled by predicted saccade probability).
## Held-out evaluation: static fixation
`gaze_exercises.py` is what the model trains on, so it can't validate
generalization by itself. `eval_static_fixation.py` runs a task shape the
model has never seen — one unmoving target held for a long duration — and
reports RMS error, mean error, and on-target accuracy % for raw gaze, the
fixed baseline filter, and both ML-adaptive modes side by side:
```powershell
python eval_static_fixation.py --subject-id you --duration 60 --viewing-distance 70 --screen-diagonal 31.5
```
Press Space to start the 60-second hold. Results are saved to
`results/eval_static_fixation_<timestamp>.csv` plus a `_meta.json` summary
report. If `models/filter_model.joblib` doesn't exist yet, it records raw +
baseline only and prints a warning — run `train_filter_model.py` first for
the ML columns. A simplified eyes-as-aim game is a planned second held-out
evaluation task, to test generalization to fast target-acquisition demands
closer to real use.
Visualize the newest result file with:
```powershell