Nodes/Castcut nodes/Castcut Face Distance
ComfyUI Node

Castcut Face Distance

Is this still the same face?

By doodersrage·Created 21 days ago·Updated 14 days ago· 2
Castcut Face Distance
  • analysis_models
  • reference
  • image
  • distances_json
  • first_distance

You have a reference shot of a person and a folder of takes. Somewhere in there, one of them has drifted - the jaw is wrong, the eyes belong to a cousin. Castcut Face Distance answers that with a number: how far each image's largest face sits from the reference face, in ArcFace embedding space.

It is one node in the castcut-nodes pack, and it's the odd one out: it doesn't need the Castcut app at all. If you're hand-rolling an identity QA gate for a batch, this does the measuring part for you.

What it actually measures

It calls InsightFace - via ComfyUI_FaceAnalysis's model object, not directly. Each frame is converted to a uint8 RGB array, handed to analysis_models.get_embeds(), and comes back as an ArcFace embedding: a vector encoding facial geometry rather than appearance. That's the same backbone under IP-Adapter FaceID, InstantID, PuLID and every ReActor-adjacent tool in the ecosystem; nothing here is generative.

Then it does something opinionated: your whole reference batch gets averaged into one mean embedding vector before anything is compared. Feed it five reference photos and it becomes a single averaged face, not five comparisons. That's usually what you want for a consistent character, and occasionally not what you want when the reference set is inconsistent.

Distance per image is plain cosine distance, 1 - (ref · emb) / (|ref| · |emb|) - lower is closer, and it prints to two decimals in the JSON. Two special cases are worth knowing because they're visible in your data:

  • 100.0 means no face was detected in that image. It's a sentinel, not a score. A 100 doesn't tell you the face is wrong; it tells you DWPose's cousin didn't find one.
  • 0.0 is returned for an embedding exactly equal to the mean reference (the code short-circuits that case to dodge float noise).

Inputs and outputs

Three required inputs, no widgets to fiddle with:

  • analysis_models - the ANALYSIS_MODELS object, from ComfyUI_FaceAnalysis's Face Analysis Models node.
  • reference - the IMAGE batch of reference face(s).
  • image - the IMAGE batch you want scored.

Outputs are distances_json (one distance per image, in order, shaped {"version", "metric": "cosine", "distances": [...]}) and first_distance, the first entry as a FLOAT - handy for feeding a float compare, a threshold switch, or just a note. Wire distances_json into Castcut Report's extra_json, or any Show Text node, if you want the per-image numbers kept.

Pick your cutoff empirically. Every number here is relative to your reference, so run it against a few known-same and one known-different face first - and remember a distance of 100 has to be handled separately from "too far".

Installing it

All five nodes come from the same pack, so this is the same for every page in this set. In ComfyUI Manager, search Castcut nodes (it's on the Comfy Registry) or run comfy node install castcut-nodes. Manager's Git-URL install also works with https://github.com/doodersrage/castcut-nodes, though Manager 3.41+ gates that behind allow_git_url_install = true in ComfyUI/user/__manager/config.ini and a server listening on 127.0.0.1. The pack's own preferred manual route is the smallest one - it's a single file:

# from the cloned repo; use your own ComfyUI path
cp castcut_nodes.py /path/to/ComfyUI/custom_nodes/

Restart ComfyUI either way. The pack itself needs numpy and pillow, which ComfyUI already ships. This node is the exception: it needs ComfyUI_FaceAnalysis installed with insightface, and it will tell you so.

Where people get burned

  • CastcutFaceDistance needs ComfyUI_FaceAnalysis (FaceAnalysisModels) with insightface. Means the object you wired in doesn't have get_embeds - usually a missing pack, or an insightface import that failed silently at startup.
  • insightface is still the worst install in local generation, even after 1.0 removed the C++ build requirement. Most failures are a wheel built for the wrong Python: insightface-0.7.3-cp310-...whl will not install into a cp311 portable. Check your embedded Python version before downloading anything.
  • No face detected in the reference image. The node refuses to guess. If every image in your reference batch comes back empty, the reference faces are too small, too stylised, or profile-on.
  • The licensing thing. InsightFace's code is MIT; its weights are non-commercial by default. That reaches this node as much as it reaches any face-swap workflow - fine for personal work, a problem if you're shipping a product around the output.
CategoryCastcut

Inputs (3)

NameTypeDefaultDescription
analysis_modelsANALYSIS_MODELS—
referenceIMAGE—
imageIMAGE—

Outputs (2)

NameTypeDescription
distances_jsonSTRING—
first_distanceFLOAT—