Image Similarity (Sequential CLIP)
Match a Stack of Reference Images Against Your Crops — One-to-One, No Repeats
- image_ref
- image_batch
- batch_masks
- batch_blank_masks
- bounding_boxes
- matched_images
- matched_masks
- matched_blank_masks
- matched_bounding_boxes
- clip_similarities
- lpips_distances
"Image Similarity (Sequential CLIP)" is the brain of Pixel-Forge: a node that takes a batch of reference images, a batch of candidates, and pairs them up by visual similarity - every reference gets its best match, no candidate used twice. If you've ever wanted to sort a pile of crops against a set of "known good" examples, this is the node you've been hand-rolling a mess of Python for.
The inputs - it expects the full pipeline
image_ref(IMAGE) - your reference images, in a batchimage_batch(IMAGE) - the candidates. In the intended flow this is thecropped_imagesoutput of Crop Disjoint Mask Regions.batch_masks,batch_blank_masks(MASK) andbounding_boxes(BOX) - the travel companions that node also emits. They all must match the candidate batch size; the node will throw a ValueError otherwise. You can't skip them, so the node is really "match my crops against my references," not a general image comparator.threshold(FLOAT, default 0.8) - the CLIP cosine-similarity bar. Higher = stricter.
How it works
Every image is embedded with OpenAI's CLIP ViT-B-32 (via open_clip). Then comes the "Sequential" part, which is the interesting bit: it processes references one at a time, in order, and for each one picks the best remaining candidate by cosine similarity. Once a candidate is matched it's removed from the pool - so with 5 references and 20 crops, you get 5 distinct pairings, not 5 all pointing at the same best crop. This greedy-without-replacement behavior is what makes it actually useful for assignment problems (pairing product shots to crops, matching frames to references) rather than just ranking.
After each match it computes an LPIPS distance (AlexNet) as a second opinion - a perceptual "how different are these really" number. If the CLIP score clears the threshold, the candidate's image, mask, blank mask and box flow through to the outputs; if not, you get a black image and zeroed mask/box in that slot, keeping the batch aligned.
Outputs: matched_images, matched_masks, matched_blank_masks, matched_bounding_boxes (in reference order), plus clip_similarities and lpips_distances as comma-separated score strings. It's flagged as an output node, so the matched batch shows up in the UI for a quick eyeball.
Setting the threshold
0.8 is a genuinely strict bar for CLIP cosine similarity - a "same subject, same angle" match scores high, but a same-subject-different-lighting crop can dip below. If you're getting all black output, you're not broken, you're below threshold: drop it to 0.7 or 0.65 and watch what comes through. If everything matches when it shouldn't, raise it.
The load cost
The node downloads open_clip's ViT-B-32 (OpenAI weights) and the LPIPS AlexNet on first use. A few hundred MB and a wait the first time - after that they're cached. The pack's open_clip_torch and lpips requirements install with the pack.
Install
ComfyUI Manager → search "ComfyUI-Pixel-Forge" → install → restart, or:
cd ComfyUI/custom_nodes
git clone https://github.com/ThunderBolt4931/comfyui_pixel_forge
restart, under Pixel-Forge/Utils. New-pack caveat applies - December 2025, no community footprint, README documents nothing. On a workflow that matters, sanity-check a few of its pairings by eye; the scores in the strings are your audit trail.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| image_ref | IMAGE | — | |
| image_batch | IMAGE | — | |
| batch_masks | MASK | — | |
| batch_blank_masks | MASK | — | |
| bounding_boxes | BOX | — | |
| threshold | FLOAT | 0.800–1 | — |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| matched_images | IMAGE | — |
| matched_masks | MASK | — |
| matched_blank_masks | MASK | — |
| matched_bounding_boxes | BOX | — |
| clip_similarities | STRING | — |
| lpips_distances | STRING | — |