import { FrameSource, type FrameBlendshape } from "./frameSource.js"; import { type FaceBox } from "./faceFraming.js"; import { type FusionRoiName } from "./multiRoiFusion.js"; import { RppgProcessor } from "./rppgProcessor.js"; export type LastBlendshapes = { blendshapes: FrameBlendshape[]; atMs: number; }; export type LastFaceBox = { /** Normalized (0..1) head box — the mesh bounds padded out to the head. */ box: FaceBox; atMs: number; }; export type DemoRunnerOptions = { roi?: { x: number; y: number; w: number; h: number; } | null; sampleRate?: number; roiSmoothingAlpha?: number; useSkinMask?: boolean; onStats?: (stats: { intensity: number; skinRatio: number; fps: number | null; r: number; g: number; b: number; clipRatio: number; motion: number; }) => void; onDiagnostics?: (diagnostics: DemoRunnerDiagnostics) => void; onError?: (error: DemoRunnerError) => void; skinRatioSmoothingAlpha?: number; /** * Multi-ROI rPPG fusion: run CHROM + bandpass per face region (forehead + * both cheeks) and blend by in-band spectral SNR, so glare/hair/occlusion on * one region no longer poisons the estimate. Requires sub-ROIs on the frame * (face-mesh mode) + the skin mask. Defaults to on; falls back to the single * aggregated-ROI path when sub-ROIs are unavailable. */ multiRoiFusion?: boolean; }; export type DemoRunnerDropReason = "frame_invalid" | "roi_missing" | "non_finite_intensity" | "processor_error"; export type DemoRunnerDiagnostics = { framesSeen: number; framesWithFaceRoi: number; framesWithFallbackRoi: number; framesWithMultiRoi: number; samplesPushed: number; droppedFrames: number; lastDropReason: DemoRunnerDropReason | null; lastTimestampMs: number | null; lastIntensity: number | null; lastSkinRatio: number | null; lastClipRatio: number | null; lastMotion: number | null; lastProcessorMethod: "rgb_meta" | "rgb" | "intensity" | "fused" | null; lastRoiSource: "multi_roi" | "face_roi" | "fallback_roi" | null; /** Frames fed through the multi-ROI fuser (subset of framesWithMultiRoi). */ framesWithFusion: number; /** Per-region fusion weights (sum to 1), SNR-driven; null until fusion runs. */ lastFusionWeights: Record | null; /** In-band spectral SNR (linear) of the fused signal, or null. */ lastFusedSnr: number | null; }; export type DemoRunnerError = { code: "processor_error"; stage: "processor"; message: string; timestampMs: number; diagnostics: DemoRunnerDiagnostics; cause?: unknown; }; export declare class DemoRunner { private source; private processor; private opts; private running; private frameCount; private lastSampleTs; private smoothedRoi; private frameTimes; private lastFps; private lastCenter; private smoothedSkinRatio; private diagnostics; private lastError; private lastBlendshapes; private lastFaceBox; private fuser; constructor(source: FrameSource, processor: RppgProcessor, opts?: DemoRunnerOptions); /** Latest face blendshapes (for affect estimation), with capture timestamp. */ getLastBlendshapes(): LastBlendshapes | null; /** * Latest normalized head box (for framing guidance), with capture timestamp. * Null until a face is tracked; consumers should treat a stale entry as * "no face" against their own clock. */ getLastFaceBox(): LastFaceBox | null; start(): Promise; stop(): Promise; getDiagnostics(): DemoRunnerDiagnostics; getLastError(): DemoRunnerError | null; private onFrame; /** * Sample per-region skin-masked RGB and push one frame through the fuser. The * sub-ROIs arrive ordered as {@link FUSION_ROIS} (forehead, leftCheek, * rightCheek) from `computeFusionSubRois`; a region with too little skin is * skipped by the fuser. */ private runFusion; private recordDrop; private recordError; private emitDiagnostics; } //# sourceMappingURL=demoRunner.d.ts.map