Python modules¶
src/drowsyguard/ — installed by pip install -e .. Each module owns one stage,
and the ownership matters: it is what keeps the desktop and the firmware from
drifting apart.
| Module | Owns |
|---|---|
cli |
argument parsing and dispatch — no logic of its own |
data |
prepare_dataset(): subject-independent splits, video decoding, hardlinking |
ingest |
dataset importers; import_ddd() rebuilds DDD's flat folders into the subject layout |
model |
TinyDrowsyNet, the whole-face classifier |
train |
train_model(), evaluate_checkpoint() |
export |
export_onnx(), quantize_espdl() |
eyestate |
eye-state classification and PERCLOS — the drowsiness mechanism |
facedetect |
YuNet detection plus short-term tracking |
behavior |
multi-cue fusion: PERCLOS, long blinks, yawn, nod, sneeze suppression |
risk |
RiskFilter — the Python mirror of the firmware filter |
live |
LiveEngine: capture, inference and state for the dashboard |
server |
the FastAPI app |
The three that carry the design¶
risk — the mirror¶
drowsyguard.risk.RiskFilter is kept behaviourally identical to
firmware/esp32s3/main/risk_filter.cpp so that thresholds tuned in the dashboard
transfer to the device unchanged.
tests/test_firmware_parity.py parses the C++ constructor signature and compares
the defaults. If you change one, change both — ./plxy.sh test will tell you
if you did not.
from drowsyguard.risk import RiskFilter, DEFAULT_TRIGGER, DEFAULT_REQUIRED, DEFAULT_COOLDOWN
f = RiskFilter(DEFAULT_TRIGGER, DEFAULT_REQUIRED, DEFAULT_COOLDOWN)
alert = f.update(0.81) # True only after `required` consecutive frames
eyestate — the mechanism¶
Instead of asking a CNN "does this face look drowsy" — which learns who the driver is — this measures eyelid closure directly and integrates it over time. That is the drowsiness mechanism itself, and it is what makes a 32×32 input sufficient.
behavior — the fusion¶
Eye closure alone is not drowsiness. This module adds the behaviours that accompany it — yawning, long/slow blinks, head nodding — and fuses them into one score, with every cue measured against a rolling per-driver baseline so face shape and camera angle cancel out instead of becoming signal.
Sneezes are detected in order to be suppressed, not scored: a sneeze slams the eyes shut for about a second while the head jerks, which an eye-closure detector would otherwise record as a microsleep.
facedetect — why YuNet¶
OpenCV 5 removed CascadeClassifier and ships no bundled cascades, so Haar is
not an option. The YuNet ONNX file is fetched once by drowsyguard fetch-models.
Tracking is not bare per-frame detection: the box is EMA-smoothed and held for 15 frames when detection drops, because detectors lose the face at exactly the moment of interest — eyes closing, head nodding.
Scripts¶
scripts/ holds one-off tooling that is not part of the package:
| Script | Does |
|---|---|
export_eye_model.py |
export open-closed-eye-0001 for the device |
quantize_espdl.py |
ESP-DL quantization |
generate_tutorial_diagrams.py |
the eleven tutorial figures |
generate_step_diagrams.py |
the step-by-step figures |
generate_wiring_poster.py |
the one-page wiring poster |
diagram_kit.py, diagram_fonts.py, pinmap.py |
shared drawing helpers and the pin table the diagrams read |
board_reset.py |
drive the board into download mode |
make_voice_clips.py |
render the alert audio |
Regenerate every figure with ./plxy.sh diagrams;
tests/test_tutorial_diagrams.py checks they stay in step with pinmap.py.