Saccade Classifier (300°/s)
It classifies saccades at 300°/s — the eye twitches your character animation keeps losing
- pose_data
- saccade_report_json
- velocity_series_json
- saccade_count
- saccade_rate_hz
A saccade is a fast, ballistic eye movement - the tiny jump your eyes make when they dart to a new target. They're the most natural-looking thing in human gaze and the first thing synthetic eye motion loses. SaccadeClassifierC2C watches the gaze time series from your driving video's pose_data and flags every saccade, so you can actually measure whether the source footage has the micro-movements you're hoping the character will reproduce.
It's part of the pack's KANIBUS eye-measurement series - no model, no sampler involvement, just analysis. You run it after PoseAndFaceDetectionV2 and before you commit to a long render.
How it works
The node reads per-frame gaze yaw/pitch from pose_data's iris data, averages the two eyes, and smooths each angle independently with a One Euro low-pass filter - the adaptive filter that damps jitter without smearing real motion. Then it computes angular velocity between frames:
ω = ||Δ(yaw, pitch)|| / Δt
and marks a frame as saccadic when that velocity crosses velocity_threshold_deg_s. The default of 300°/s is the classical physiological saccade boundary - real saccades exceed it, and smooth pursuit and drift stay below. Sustained velocity over min_consecutive_frames frames is required before a run counts, so single-frame noise spikes get killed rather than counted as eye twitches.
The inputs that matter
velocity_threshold_deg_s(300) - the boundary. Raise it if you're getting false positives from shaky footage; lower it if you suspect small saccades are being missed.fps(30) - critical, because velocity is per-second. Wrong fps = wrong velocities = wrong classification.min_consecutive_frames(1) - minimum run length above threshold. Bump to 2–3 for noisy hand-held footage.one_euro_min_cutoff(1) andone_euro_beta(0.05) - the One Euro filter's two knobs. Lower cutoff = stronger jitter rejection; higher beta = the filter relaxes faster on genuinely quick movement. Defaults are sensible; don't touch unless you're chasing specific behavior.
Outputs: saccade_count and saccade_rate_hz for wiring into anything numeric, plus saccade_report_json (per-frame mask, threshold, rate) and velocity_series_json (the smoothed yaw/pitch and per-frame angular velocity in degrees/second) for the full picture.
Why you'd actually use this
Saccades are exactly what Wan Animate's face encoder has to decide is worth preserving against its small motion-basis budget - and exactly what gets flattened when a clip is fps-reduced upstream or a micro-movement lands on a segment boundary. Running this tells you two things: whether your source footage actually contains saccades (talking-head video shot at 24fps might have surprisingly few), and whether your generated output still has them when you run it on the result side. That before/after comparison is the honest use - the "300°/s" in the display name is the boundary, and a healthy clip should show a credible count on both sides. It's a diagnostic, not a fix, but it's the only node in this pack that will tell you your character's eyes have gone flat.
Install is the shared pack path (Manager search "WanAnimatePreprocessV2", or git clone + pip install -r requirements.txt). No downloads of its own.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| pose_data | POSEDATA | — | |
| fps | FLOAT | 30.01–240 | — |
| velocity_threshold_deg_s | FLOAT | 30030–1000 | — |
| min_consecutive_frames | INT | 11–30 | — |
| one_euro_min_cutoff | FLOAT | 1.00.1–10 | — |
| one_euro_beta | FLOAT | 0.050–1 | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| saccade_report_json | STRING | — |
| velocity_series_json | STRING | — |
| saccade_count | INT | — |
| saccade_rate_hz | FLOAT | — |