A facial recognition login service for Linux.
2

Configure Feed

Select the types of activity you want to include in your feed.

cli: Add liveness-aware burst sizing helper

Micro-motion (and future multi-frame liveness checks) need several
same-illumination-phase frames, but the calibrated matching burst is
minimal — one strobe period, i.e. a single lit frame on a period-2
sensor. `liveness_burst(matching_burst, depth)` grows it to
`matching_burst * depth` so the burst spans `depth` periods and yields
`depth` same-phase frames (clamped to depth >= 1; saturating, no
overflow). `DEFAULT_LIVENESS_DEPTH` is 3.

Pure helper with unit tests; the `test` command wires it in when it runs
liveness (next commit). Matching-only paths keep using the minimal burst.

Isaac Corbrey (May 29, 2026, 7:34 AM EDT) f7d3f40b 49f85f3e

+43
+43
crates/cli/src/pipeline_runtime.rs
··· 466 466 } 467 467 } 468 468 469 + /// How many same-illumination-phase frames liveness checks want from a 470 + /// burst. Micro-motion needs at least 2 to measure change; 3 gives it 471 + /// two consecutive diffs for a steadier estimate without much added 472 + /// capture time. First-cut; profiling may revise it. 473 + pub const DEFAULT_LIVENESS_DEPTH: u32 = 3; 474 + 475 + /// Grow a matching burst length to also satisfy liveness, which needs 476 + /// several frames of the *same* illumination phase. 477 + /// 478 + /// The matching burst is effectively one strobe period (or 1 on a 479 + /// constant sensor): capturing it once yields a single same-phase (lit) 480 + /// frame. Capturing `depth` periods' worth yields `depth` same-phase 481 + /// frames, which is exactly what a check like micro-motion needs. So the 482 + /// liveness burst is simply `matching_burst * depth`: 483 + /// 484 + /// - strobed period-2, depth 3 → 6 frames → 3 lit frames; 485 + /// - constant (burst 1), depth 3 → 3 frames, all same-phase. 486 + /// 487 + /// `depth` is clamped to >= 1 (depth 1 == matching-only, no liveness 488 + /// frames). The extra frames cost ~tens of ms and are only captured when 489 + /// the caller actually intends to run liveness. 490 + pub fn liveness_burst(matching_burst: u32, depth: u32) -> u32 { 491 + matching_burst.saturating_mul(depth.max(1)) 492 + } 493 + 469 494 /// Resolve the mandatory RGB camera from an optional explicit path and a 470 495 /// camera snapshot, rendering the [`RgbCameraResolution`] outcome into a 471 496 /// concrete path or an actionable [`CliError`]. ··· 586 611 // misclassified as a dark IR frame. 587 612 let f = rgb_frame(vec![114; 300]); 588 613 assert!(frame_mean_brightness(&f) > DARK_FRAME_MEAN_THRESHOLD); 614 + } 615 + 616 + #[test] 617 + fn liveness_burst_multiplies_matching_burst_by_depth() { 618 + // strobed period-2 → 6 frames (3 lit); constant burst-1 → 3. 619 + assert_eq!(liveness_burst(2, 3), 6); 620 + assert_eq!(liveness_burst(1, 3), 3); 621 + } 622 + 623 + #[test] 624 + fn liveness_burst_depth_zero_is_treated_as_one() { 625 + // depth 0 would mean "no frames"; clamp to matching-only. 626 + assert_eq!(liveness_burst(2, 0), 2); 627 + } 628 + 629 + #[test] 630 + fn liveness_burst_saturates_rather_than_overflowing() { 631 + assert_eq!(liveness_burst(u32::MAX, 4), u32::MAX); 589 632 } 590 633 }