Files
r0zaic-comfyui-reference-im…/reference_image_comparer.py
r0zaicandClaude Fable 5 be257891ba Filter reference picker to image files; bump to 1.0.1
The picker listed every file in the input folder, including videos,
which cannot render in the comparer. Live reference preview confirmed
working on desktop/frontend 1.45 after the lazy widget lookup fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 19:38:58 -04:00

108 lines
3.8 KiB
Python

import hashlib
import os
import numpy as np
import torch
from PIL import Image, ImageOps
import folder_paths
from nodes import PreviewImage
def _resolve_reference_path(reference_image, reference_image_path):
override = (reference_image_path or "").strip().strip('"')
if override:
if not os.path.isfile(override):
raise FileNotFoundError(f"Reference image not found: {override}")
return override
if not reference_image:
raise ValueError(
"Pick a reference image in the node, or set reference_image_path to an absolute path."
)
path = folder_paths.get_annotated_filepath(reference_image)
if not path or not os.path.isfile(path):
raise FileNotFoundError(f"Reference image not found in input folder: {reference_image}")
return path
def _load_image_tensor(path):
img = Image.open(path)
img = ImageOps.exif_transpose(img)
img = img.convert("RGB")
arr = np.array(img).astype(np.float32) / 255.0
return torch.from_numpy(arr)[None,]
class ReferenceImageComparer(PreviewImage):
"""Compares a workflow result against a fixed reference image with a swipe slider."""
IMAGE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp")
@classmethod
def INPUT_TYPES(cls):
input_dir = folder_paths.get_input_directory()
files = sorted(
f for f in os.listdir(input_dir)
if f.lower().endswith(cls.IMAGE_EXTENSIONS)
and os.path.isfile(os.path.join(input_dir, f))
)
return {
"required": {
"reference_image": (files, {"image_upload": True}),
},
"optional": {
"image": ("IMAGE",),
"reference_image_path": (
"STRING",
{
"default": "",
"tooltip": "Optional absolute path to the reference image. Overrides the picker above.",
},
),
},
"hidden": {
"prompt": "PROMPT",
"extra_pnginfo": "EXTRA_PNGINFO",
},
}
RETURN_TYPES = ()
FUNCTION = "compare"
OUTPUT_NODE = True
CATEGORY = "image"
DESCRIPTION = (
"Compares the generated image against a fixed reference image. "
"Hover over the preview to swipe between reference (left) and result (right)."
)
def compare(self, reference_image=None, image=None, reference_image_path="",
prompt=None, extra_pnginfo=None):
path = _resolve_reference_path(reference_image, reference_image_path)
reference = _load_image_tensor(path)
ui = {"a_images": [], "b_images": []}
ui["a_images"] = self.save_images(
reference, "refcompare.a.", prompt, extra_pnginfo)["ui"]["images"]
if image is not None and len(image) > 0:
ui["b_images"] = self.save_images(
image, "refcompare.b.", prompt, extra_pnginfo)["ui"]["images"]
return {"ui": ui}
@classmethod
def IS_CHANGED(cls, reference_image=None, reference_image_path="", **kwargs):
# Re-run when the reference image file itself changes on disk.
try:
path = _resolve_reference_path(reference_image, reference_image_path)
m = hashlib.sha256()
m.update(path.encode("utf-8"))
m.update(str(os.path.getmtime(path)).encode("utf-8"))
return m.hexdigest()
except Exception:
return float("NaN")
@classmethod
def VALIDATE_INPUTS(cls, reference_image=None, reference_image_path=""):
# The path override may point anywhere and the combo list can be stale;
# real validation happens in compare() with a clearer error message.
return True