FGResQ NR Score
A 0-to-1 quality grade for one image, no original needed
- model
- image
- score
If you want one number that means "how good is this picture, judged cold", FGResQ is a strong place to start and it's on the pack author's own shortlist after his blind A/B tests. It's a fine-grained quality model from AAAI 2026, built on a CLIP vision backbone, and its NR mode gives you a clean 0-to-1 score where higher is better - no inverted sign, no "is 0.08 good?" moment like you get with LPIPS.
What it does
Feed it a batch, get a per-image quality score. Zero to one, higher better, and it reads like a soft grade rather than a distance - no inverted sign, no "wait, is 0.08 good?" moment like LPIPS gives you. What counts as a pass is yours to calibrate: score a batch you already like and one you'd throw away, and let the gap tell you where the line is. The ordering is the useful part anyway.
image takes the batch, scored one image at a time. model is the FGResQ model, and this is the one thing to get right: the FGResQ architecture covers two pyiqa metrics - the NR quality head this node uses and the pair/FR metric that FGResQ FR Score uses - over the same trained net. Both variants under (Down)load pyiqa Model → architecture FGResQ load the same bundle, so either one drives both nodes. Wire either; you won't get a mismatch.
memory_management is the unload-vs-keep switch every score node in this pack carries. Default unloads the metric after scoring, which is right for a generate-then-score graph; keep loaded is for loops.
Output: a single score socket, emitted as a list, one float per input image.
Why not the generic NR node?
You can reach FGResQ through (Down)load pyiqa Model + pyiqa NR Score. It's the same metric and the same numbers. This dedicated node exists because FGResQ needed handling the generic path couldn't express - one net serving two metrics, plus a checkpoint wrinkle described below - and because people who use FGResQ usually also want the pair node. If you're already using the generic nodes, no need to switch.
The transformers trap, which the pack handles for you
Here's a genuinely nasty one that's worth understanding because it applies beyond this pack. The published FGResQ checkpoints were saved with transformers 4 and carry a vision_model. key prefix that transformers ≥ ~5.6 no longer uses. pyiqa only warns about the mismatch - it doesn't fail - so the fine-tuned backbone silently doesn't load, and you end up scoring with stock HuggingFace CLIP while believing you're running FGResQ. Numbers that look plausible, measuring the wrong thing.
The pack detects that layout and reloads both checkpoints with the prefix stripped. On transformers ≤ 5.5.4 it's a no-op because the checkpoint loads as published. So: keep the pack updated, and if you ever see transformer-version weirdness in your logs, that's the thing to check.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/AnsteinHuynh/ComfyUI-pyiqa-toolbox
python -m pip install -r ComfyUI-pyiqa-toolbox/requirements.txt
Or ComfyUI Manager → search pyiqa-toolbox, then restart. The node lives under pyiqa/FGResQ.
Weights aren't bundled - they auto-download on first score into ComfyUI/models/pyiqa (the loader checks for FGResQ.pth and FGResQ_Degradation.pth in the registered pyiqa folder, plain layout or HF-cache layout). If you already have them somewhere else, point a pyiqa entry in extra_model_paths.yaml at that folder and nothing re-downloads.
Dependency honesty: this pack needs a pyiqa install, which resolves transformers>=5, accelerate, bitsandbytes and friends into your ComfyUI environment. That's the standard way custom nodes break each other, and it's worth a snapshot of your environment before you start.
Common issues
Score feels stuck around the same value for everything. Generated images from one checkpoint do cluster. Try it on a deliberately blurred and a deliberately over-sharpened version of the same image to see the spread it can actually produce.
Weights folder confusion. Only the registered pyiqa folder (default ComfyUI/models/pyiqa) is searched, plus any yaml entries. Loose weights elsewhere do nothing.
Model wired from a different loader. A non-FGResQ model gets refused at run time with a message naming what would work - not a shape error halfway through pyiqa. If you see that message, the loader's architecture is the fix.
Long graph, VRAM tight at the end. Scoring happens after generation thanks to lazy evaluation, so the metric loads late - but it stays there until memory_management unloads it. Leave the default on, or add Unload Pyiqa Models as the last node.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| memory_management | COMBO | unload model (VRAM + RAM) | What happens to this metric after it scores. 'unload model (VRAM + RAM)': the metric is fully unloaded - gone from VRAM and RAM, only the file on disk remains; the next score reloads it (seconds for small models, ~30s for Q-ReAlign Pro). Re-queueing just works: a previously-unloaded model handed back by ComfyUI's output cache is rebuilt transparently. 'keep loaded': stays on the GPU, fastest to re-score, holds its VRAM between runs. With several score nodes sharing one loader, unload only on the LAST one - earlier nodes should 'keep loaded' or you pay a reload between them. |
| model | PYIQA_FGRESQ,PYIQA_ANY | From the matching Loader node. This input (and the loader feeding it) is evaluated LAZILY - only once this node's image inputs exist - so a generation-only run never loads the metric model. | |
| image | IMAGE | Image(s) to score; batch is scored per-image. Lazy: evaluated before the model loads. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| score | FLOAT | Per-image FGResQ quality score, 0-1, higher is better. (pyiqa's NR fgresq metric returns a single score per image; the quality0/quality1 pair only exists in the full-reference pair mode.) |