← Work

A photographer preset as a ComfyUI workflow, with the measurement in plain sight

Our own portfolio test, not a client job. 7 real photos under CC0 (Wikimedia Commons). The preset enhances existing photos: it generates no faces, hands or objects. Estimates are labelled as such.

Bar chart of structure kept (local luma correlation) by region, preset versus classic with no AI: subject 0.98 vs 0.96, edge 0.97 vs 0.98, sky 0.91 vs 0.99, rest 0.98 vs 0.99
Structure kept relative to the original, by region, across 7 real photos. Higher is better; the preset loses structure in the sky.
See the repository on GitHub →github.com/grawwww-studio/comfyui-photo-preset · MIT code · workflows, nodes, client and results

The problem

A photo-editing app already in production has its pipeline in Python: preprocess and scale, AI enhancement (say, fixing an overexposed shot), LUT colour grading and a final upscale. It wants to move that to ComfyUI run over the API, with each photographer preset as one workflow and the backend orchestrating. With one strict rule: this is enhancing real photos, not generating them; identity, skin texture and scene detail must survive, with no invented faces, hands or objects.

What we did

  • One preset = one workflow JSON, submitted by a Python client over the ComfyUI API (/prompt, websocket, /history); it logs seconds per node and stage, peak VRAM and cost per photo. Pinned versions (ComfyUI 0.37.0, model hashes).
  • Custom nodes in Python: matte refinement with a guided filter, per-region exposure recovery (splits base and detail and leaves the detail untouched), a .cube LUT node with region masks (the "port your Python grading into nodes" part) and a 2x upscale with a low-frequency anchor.
  • Licence-checked models: BiRefNet (MIT) for the matte and 2xNomosUni SPAN (CC BY 4.0) for upscaling. We dropped what we could not verify (for instance RealESRGAN's weights state no licence of their own).
  • A classic no-AI version (auto-levels + the same LUT + Lanczos) to compare photo by photo, and an evaluation on a fixed set with side-by-side sheets, faithfulness metrics and maps of "where the structure changed".

Examples: original, classic no AI, preset

Three of the 7 photos, the ones without people. Full sheets, 2x crops and change maps are in the repository. What you see is our reading: the preset is more conservative and flatter than classic, which tends to look punchier.

Sydney Opera House at midday: original
original
Sydney Opera House at midday: classic, no AI
classic, no AI
Sydney Opera House at midday: preset
preset

Midday exterior, strong whites and blue sky. Photo: Bernard Spragg, CC0.

Viewing scope against blue sky and mountains: original
original
Viewing scope against blue sky and mountains: classic, no AI
classic, no AI
Viewing scope against blue sky and mountains: preset
preset

Blue sky with blown areas and a foreground object. The preset pulls the sky down and flattens its texture: the worst structure number of the set (see below). Photo: Markus Spiske, CC0.

Cafe interior: original
original
Cafe interior: classic, no AI
classic, no AI
Cafe interior: preset
preset

Indoor mixed light, no people. Photo: Andy Li, CC0.

Numbers

ItemValue
GPUNVIDIA RTX 4090 (24 GB), shared with idle containers
Input → output1280 px long edge → 2560 px
Time per photo, warm (median)5.94 s (4.9–7.5 s)
Same photo, classic no-AI version0.85 s
VRAM above baseline (whole-card peak)2.9 GB
Cost per photo, ESTIMATE$0.0015 = seconds × $0.89/h, RunPod's listed RTX 4090 price at https://www.runpod.io/pricing on 2026-10-16. Assumes a 100 % busy GPU, no cold starts or storage; no rented hardware was used.

About half of the preset's time is two stages we did not optimise (exposure recovery 2.5 s and segmentation 1.9 s). The "first run after freeing models" (5.0 s) is not a true cold start: the files were in the OS cache.

Faithfulness: does the structure survive?

RegionPresetClassicPreset, worst photo
Subject0.9840.9610.953
Edge band (hair)0.9700.9810.943
Sky (only 3 photos)0.9090.9940.800
Rest0.9800.9860.945

Local normalised cross-correlation of luma against the original, on textured pixels only (1.0 = same structure; an exposure or colour edit does not change it, invented or destroyed structure does). On the subject the preset keeps structure better than classic; in the sky it is worse: the highlight shoulder flattens the texture of bright skies.

  • Colour: the preset moves subject colour less than classic (mean ΔE 8.0 vs 14.1) and barely shifts hue (2.9° vs 9.5°). These measure how far colour moved, not error: there is no "correct" image.
  • 2x upscale: downscaling the 2x result back matches the source image at 39.4 dB PSNR (Lanczos alone: 41.1 dB; the SPAN model without the anchor: 32.5 dB and it darkened the image). It still adds ×1.34 more detail energy: that is the sharpness, and also what we cannot certify as "not invented".
  • LUT node: against an independent numpy implementation reading the same .cube, the maximum difference is 1/255 and 98.5 % of pixels are identical.
  • Determinism: repeated runs give identical images (98 PNG pairs compared, maximum difference 0).

7 photos, 3 repeats, one GPU, one preset tuned by eye on this kind of photo: a proof of method, not a benchmark.

What still fails and what we did not do

  • The sky is found with a heuristic, not a model. Of the 4 photos with sky it finds it correctly on 2, refuses 2 (white snow, beige haze) and gives one false positive on a street (shaded buildings).
  • Blown highlights are not recovered: on the most overexposed photo of the set (11.5 % clipped pixels) they end up flat grey, because the detail was never in the file. A generative model would "invent" detail there, which is exactly what this job forbids; we tested none.
  • Strong backlight: the shadow lift is capped to avoid amplifying noise, so a very dark subject stays dark.
  • Matte refinement is mixed: it aligns edges better by our proxy (0.60 vs 0.51; no reference matte, so not an accuracy measure) but adds streaks on a photo with a bright background. BiRefNet also returns "the salient object", not "the person".
  • Not done: skin segmentation, generative relight, a photographer's style LoRA, cloud deployment (the Dockerfile was not built; nothing ran on RunPod or Modal). We also did not measure cold start, concurrency or other GPUs.

What a client gets

The same thing you see here, applied to your pipeline: your presets turned into ComfyUI workflows with a stable API for your backend, custom nodes for the logic that lives in your Python today (grading, masks, exposure recovery), pinned versions and models with their licences checked, an evaluation on your own photo set and an honest list of what fails.

Custom tools (extensions, nodes, plugins and connectors): from €600, excl. VAT. It is a starting price: after the first conversation, the proposal fixes scope and price. Details on the Technical teams page, or write to hello@grawwww.xyz. We answer in under 48 hours.

Credits and licences

  • BiRefNet (matte), MIT licence.
  • Upscaling: 2xNomosUni_span_multijpg by Philip Hofmann, CC BY 4.0 (https://huggingface.co/Phips/2xNomosUni_span_multijpg). The licence of its training dataset was not verified.
  • ComfyUI (GPL-3.0): used as is, not modified or distributed by us; our node pack is a separate plug-in under MIT.
  • Test photos: CC0, from Wikimedia Commons (authors and files in the repository). One photo was removed before publication because it showed an identifiable person without a model release.
  • Our code, README and results: MIT licence. No model weights are redistributed.
Code, results and licences on GitHub →github.com/grawwww-studio/comfyui-photo-preset