Castcut Pick Best
Keep the take that actually hit the pose
- images
- best
- other
- best_index
- report_json
Two takes, one hard pose, and you'd rather the graph decide than open twenty images and squint. Castcut Pick Best takes a two-image batch plus the pose scores for those images and hands you back the winner - plus the loser, which is the part that makes it more than a switch.
It's the middle of the pack's "Best of two for hard poses" loop, and the reason the pack exists: the sampler renders both takes as one batch, so it's one model load and one text encode instead of two queued jobs, and the pick happens inside the job.
How it decides
It walks the score list positionally and keeps the highest. Three rules in there are worth knowing because they're decisions, not accidents:
- Ties go to the later take. The second image wins unless the first is strictly closer to the guide. Codified from Castcut's own picker, and reasonable for a two-take batch where the second take was the retry.
- A take with no score loses to a take with one. A null score (nobody detected) can't win against a real number. If every take is unscored it falls through to the last frame - worth checking the report when that happens, because it means DWPose found no pose at all rather than that both takes were equally good.
- It's positional. Frame 0 of your batch is compared to entry 0 of the scores list. Nothing matches them by content.
Outputs: best and other are single-image batches (slices of the input, not copies), best_index is the INT you'd read if you want to log or branch on which take won, and report_json carries the whole decision - kind: "pick-best", bestIndex, otherIndex, the score list and the underlying per-image results.
One limitation to design around: other is only smart for a two-image batch. With more frames it returns index 0 unless the winner was index 0, in which case it returns index 1. It's a two-take node; don't hand it a batch of eight and expect ranked runners-up.
Wiring it
images wants the batch straight off the sampler (VAE Decode, whatever), and scores_json wants the STRING from Castcut Pose Score's scores_json output - it's a forced input, so it has to be a wire, not something you type or paste into the node.
From there the canonical graph is three wires: best → your SaveImage, other → Castcut Report's alternate, report_json → Castcut Report's report_json. The job's still is the good take; the runner-up is saved next to it with the scores written into the job's history, which is what the app reads back.
If you don't care about the loser and just want a chooser, skip the report and send best wherever you like. And if you're doing this without a pose guide at all, the general-purpose version of the same idea lives in the plumbing layer - an A/B switch with a selector, or rgthree's Any Switch if you want the graph to pick the first non-empty input.
Installing it
All of this is one small pack. ComfyUI Manager → search Castcut nodes (it's on the Comfy Registry; comfy node install castcut-nodes works too), or install it manually - the whole thing is a single Python file:
git clone https://github.com/doodersrage/castcut-nodes
cp castcut-nodes/castcut_nodes.py /path/to/ComfyUI/custom_nodes/
# restart ComfyUI afterwards
Only numpy and pillow beyond ComfyUI itself. Installing by Git URL through Manager needs allow_git_url_install = true under [default] in ComfyUI/user/__manager/config.ini and ComfyUI bound to 127.0.0.1. Pick one method: a castcut_nodes.py file and a castcut folder side by side both load. You can confirm it worked at http://127.0.0.1:8188/object_info/CastcutPickBest - the description ends with [castcut-nodes 1.1.0].
Where people get burned
- Index misalignment. Anything between the sampler and this node that reorders, filters or de-duplicates frames silently misaligns scores against images, and you get a confidently wrong pick with no error. Keep the batch and the score list coming from the same source.
- The scoring half needs DWPose, from
comfyui_controlnet_aux- and that's the dependency that actually breaks, usually as an ONNX/onnxruntimeCPU warning that people "fix" into a hard failure. Pick Best itself is trivial numpy and never the problem. - Both takes cost time. It's cheaper than two jobs, not free - the sampler still renders two images, and DWPose runs over both.
- Scores mean nothing on their own. A high score says "the pose matches the guide", not "this is the better image". If both takes nailed the pose, this node will still pick one, and it may not be the one with the better face.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| scores_json | STRING | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| best | IMAGE | — |
| other | IMAGE | — |
| best_index | INT | — |
| report_json | STRING | — |