Draw ViT Pose (V2)
Turn detected poses into the skeleton image your Wan-Animate sampler actually sees
- pose_data
- face_images
- pose_images
- face_video
- face_cfg_scale
- critic_report_json
- worst_aus_csv
- overall_mae
Wan Animate is driven by a stick figure. The model's pose conditioning channel is a rendered skeleton image, and this node is what renders it. DrawViTPoseV2 takes the pose_data from PoseAndFaceDetectionV2 and draws the whole-body skeleton - bones, hands, face, even the iris and gaze arrows if you want them - onto a clean black canvas at your target latent resolution. Whatever you see in its pose_images output is, pixel for pixel, the pose the sampler is going to animate against. So get that skeleton right.
What you set
The required inputs are almost all self-explanatory, and two of them are the ones that matter:
width/height- render canvas in pixels, and they should match your sampler latent size (832×480 default). The pose is retargeted onto this canvas, so if it doesn't match, you get a skeleton that's the wrong size for the generation.retarget_padding(16) - padding around the body bbox when fitting. Larger = more headroom so big motions don't clip the frame edges. Bump it to 32–64 if limbs get cut off.pose_draw_threshold(0.3) - per-keypoint confidence for drawing. This is separate from the detection node'spose_threshold: detection stores everything, this decides what gets rendered. Lower to 0.1 if limbs keep disappearing from the output.body_stick_width/hand_stick_width- both default to-1(auto from canvas size).0on hands means don't draw them at all, which is handy if hand tracking is noisy.draw_head- whether the face/head skeleton renders.
The genuinely interesting stuff lives in the optional inputs. face_images (wire in face_images_512 from the detection node) makes this node a one-stop shop: it validates frame-count parity, force-resizes to 512×512, and forwards the crop on its face_video output, so a single DrawViTPoseV2 can feed both the pose and face inputs of your Wan-Animate sampler. That's the wiring you actually want for a clean graph.
The outputs beyond the skeleton
pose_images is the main event. But there are two things here that no other pose-render node does:
face_video + face_cfg_scale - passthroughs. face_cfg_scale is connection-only, deliberately: it used to be a second editable widget that could silently diverge from the detection node's value, so the author made this one read-only. Unconnected it's 1.0, a no-op. And as the detection node's tooltip hammers home, Kijai's wrapper has no face-CFG input today - use WanVideoAnimateEmbeds.face_strength for real expression control.
The closed-loop critic. Wire reference_expression_coeffs_json from a source-video run of the detection node (with export_expression_coeffs=True) and this node will measure ARKit-52 blendshapes from its own pose_data - i.e. the generated side - and report per-AU error against the source. You get critic_report_json, worst_aus_csv and overall_mae (0.0 = perfect). It's a numeric fidelity signal instead of eyeballing frames, and it's zero cost when left unwired. It's overkill for most people, but if you're chasing a specific expression that keeps getting lost, it tells you which action unit to blame.
One knob that quietly matters
apply_pose_edits_to_face (default "warp") is the delivery mechanism for expression edits made upstream (Wan Face Controller 3D's expression dials, dragged landmarks, or the detection node's force_eyes_open). The reason it exists: Wan Animate's face conditioning is 100% pixel-driven - landmarks only place the crop, the LIA motion encoder reads raw crop pixels. So editing landmarks alone changes nothing visible; this node warps the actual face-crop pixels to match the edited landmarks. If you set force_eyes_open upstream and see no effect, check this is still on warp - with "off", your edits silently do nothing.
Install-wise it's just part of the pack: ComfyUI Manager (search "WanAnimatePreprocessV2") or git clone https://github.com/Code2Collapse/ComfyUI-WanAnimatePreprocessV2 into custom_nodes, then pip install -r requirements.txt and restart. No extra model downloads for this node itself - the heavy lifting happens in the detection node upstream.
Inputs (21)
| Name | Type | Default | Description |
|---|---|---|---|
| pose_data | POSEDATA | From Pose and Face Detection (V2). | |
| width | INT | 83264–2048 | Render canvas width (px). Match the sampler latent size. |
| height | INT | 48064–2048 | Render canvas height (px). Match the sampler latent size. |
| retarget_padding | INT | 160–512 | Padding (px) added around the body bbox when retargeting. Larger = more headroom for big motions. |
| body_stick_width | INT | -1-1–20 | Body skeleton stick width in px. -1 = auto from canvas size. |
| hand_stick_width | INT | -1-1–20 | Hand skeleton stick width in px. -1 = auto. |
| draw_head | BOOLEAN | true | Draw the head/face skeleton (eyes, nose, ears). |
| pose_draw_threshold | FLOAT | 0.300–1 | Per-keypoint score threshold for drawing. |
| draw_irisopt | BOOLEAN | true | Draw iris/pupil markers from MediaPipe iris_data. |
| draw_gazeopt | BOOLEAN | true | Draw gaze direction arrows from iris_data. |
| iris_radiusopt | INT | 41–20 | Pupil circle radius in pixels. |
| gaze_arrow_lenopt | INT | 304–200 | Length of gaze direction arrow in pixels. |
| iris_min_confidenceopt | FLOAT | 0.050–1 | Skip iris frames whose detection confidence is below this. |
| iris_coloropt | COMBO | white | Color of the drawn pupil; magenta gives strongest sampler signal. |
| face_imagesopt | IMAGE | OPTIONAL face crop IMAGE batch (typically the face_images_512 output of PoseAndFaceDetectionV2). When wired, the node validates frame-count parity with the pose batch, optionally force-resizes to 512x512, and forwards it on the 'face_video' output so a single DrawViTPoseV2 can feed the Wan-Animate sampler's pose+face inputs in one place. | |
| face_cfg_scaleopt | FLOAT | 1.01–10 | Passthrough face CFG scale, CONNECTION-ONLY (forceInput) so there is exactly one source of truth: PoseAndFaceDetectionV2.face_cfg_scale. It used to be a second independently-editable widget with the same default, so you could set 2.0 upstream, leave 1.0 here, and get no warning that they had diverged. Unconnected = 1.0 (no-op), which matches the old default. NOTE: Kijai's ComfyUI-WanVideoWrapper has no face-CFG input to wire this into today — for real control over expression adherence use WanVideoAnimateEmbeds.face_strength (spec 2.2's stronger, more direct block-scale lever) instead. |
| enforce_512_faceopt | BOOLEAN | true | If True and 'face_images' is provided at a non-512 size, force-resize each frame to 512x512 (bilinear) before forwarding. Default True so the encoder always sees the trained input shape. |
| reference_expression_coeffs_jsonopt | STRING | Wan-Animate spec 3.1 (closed-loop critic): wire in the 'expression_coeffs_json' output of a PoseAndFaceDetectionV2 run (export_expression_coeffs=True) on the SOURCE driving video. When non-empty, this node measures ARKit-52 blendshapes from ITS OWN pose_data.iris_data (i.e. the GENERATED Wan-Animate output side, since this node is downstream of the generation pass) and reports per-AU + per-segment error against the reference — a numeric fidelity signal instead of eyeballing frames. Leave empty to skip entirely (zero extra cost). | |
| segment_lengthopt | INT | 771–100000 | Frames per segment for the critic's worst-segment breakdown — match WanVideoAnimateEmbeds.frame_window_size (default 77) so segments line up with Wan-Animate's own splice boundaries (spec 2.5/3.5). Only used when reference_expression_coeffs_json is wired. |
| top_k_ausopt | INT | 101–52 | How many worst-tracked AUs the critic reports, worst-first. Only used when reference_expression_coeffs_json is wired. |
| apply_pose_edits_to_faceopt | COMBO | warp | Expression-edit DELIVERY (2026-07-24). When pose_data carries edited face landmarks (WanFaceController3DV2 expression dials / dragged landmarks) AND face_images is wired, 'warp' moves the ACTUAL face-crop pixels from the original landmark positions to the edited ones (same Delaunay piecewise-affine engine as FC3D's preview), so the Wan-Animate face encoder sees the edit. Without this, landmark edits only change the drawn skeleton — the photographic face crop stays neutral and the sampler follows the crop, i.e. your expression edits silently do nothing. No-op when landmarks are unedited (zero cost), so the default stays 'warp'. |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| pose_images | IMAGE | Rendered skeleton IMAGE batch. Feed into your Wan 2.2 Animate sampler. |
| face_video | IMAGE | Passthrough face IMAGE batch (512x512 if enforce_512_face). Empty single-frame zero tensor if 'face_images' was not wired. |
| face_cfg_scale | FLOAT | Passthrough face_cfg_scale (Wan-Animate paper Sec. 4.3). 1.0 = CFG off. |
| critic_report_json | STRING | Wan-Animate spec 3.1 closed-loop critic report (JSON): per-AU mean-absolute-error, per-frame error curve, per-segment breakdown worst-first. '{}' when reference_expression_coeffs_json was not wired. |
| worst_aus_csv | STRING | CSV 'name,mae' for the top_k_aus worst-tracked AUs, worst first. Empty string when the critic did not run. |
| overall_mae | FLOAT | Mean of all per-AU MAE values (0.0 = perfect match to the reference). 0.0 when the critic did not run. |