Castcut Face Distance
Is this still the same face?
- 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.0means 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.0is 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- theANALYSIS_MODELSobject, 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 haveget_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-...whlwill not install into acp311portable. 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.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| analysis_models | ANALYSIS_MODELS | — | |
| reference | IMAGE | — | |
| image | IMAGE | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| distances_json | STRING | — |
| first_distance | FLOAT | — |