This workflow is specifically designed to assist with preparing portrait datasets for stylistic Lora training. Prepare an image dataset in one or more directories and then point this workflow at them. It will automatically upscale, colorise, restore etc. the images and extract a normally rotated square 1024x1024 face image from each source image into an output directory you specify. The workflow utilises some ComfyUI custom nodes available on my github page, or published through ComfyUI. Additional details...
# Portrait Dataset Gate — self-driving LoRA dataset prep
Point it at a folder of source photos. For each image it decides what that
image actually needs, does only that, and names the output so you can see what
happened. Built for face-centric LoRA training sets.
**Workflow:** PORTRAIT_DATASET_GATE_v1.json
---
## What it does, per image
```
source ─▶ score ──reject (too small, or not a photograph) ──▶ gate_rejected/
│
├─▶ too soft/small? ──▶ SeedVR2 super-res ──▶ rescore (up to 2 passes)
│
▼ accepted
head crop (derotated to eye level, square, +15% padding)
│
├─▶ black & white or sepia? ──▶ DDColor colourise → tag _col
├─▶ single-hue colour cast? ──▶ skin-anchored correction
├─▶ skin texture below 1.0? ──▶ SUPIR restoration → tag _sup
├─▶ face drifted? ──▶ revert to the crop → tag _noid
└─▶ still marginal? → tag _rev
▼
train/<original stem>[_col][_sup][_noid][_rev].png
```
**The filename is the report.** No tag means it was clean out of the box.
_col means every colour is invented — check those, especially hair.
_sup means restoration ran. _sup_rev means it ran and the result is still
marginal, so look at it. You only eyeball what the workflow couldn't settle.
Every scoring also appends a row to quality_gate.csv with the metrics and the
reason, so you can tune thresholds against what you actually see.
---
## Setup
### Required custom nodes
- **[ComfyUI-PortraitDatasetTools](https://github.com/BillyBGit/ComfyUI-PortraitDatasetTools)** — the measurement and routing nodes
- [ComfyUI-SeedVR2_VideoUpscaler](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler) — super-resolution
- [ComfyUI-DDColor](https://github.com/kijai/ComfyUI-DDColor) — B&W colourisation
- [ComfyUI-KJNodes](https://github.com/kijai/ComfyUI-KJNodes) — ImageResizeKJv2
- [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) — ShowText
SUPIR is **built into ComfyUI core** SUPIRApply + ModelPatchLoader) — no
wrapper needed.
### Models
| put in | file |
|---|---|
| models/model_patches/ | SUPIR-v0Q_fp16.safetensors — [Kijai/SUPIR_pruned](https://huggingface.co/Kijai/SUPIR_pruned) (2.7 GB) |
| models/checkpoints/ | any photographic SDXL checkpoint (SUPIR is an SDXL pipeline) |
| SeedVR2's folder | seedvr2_ema_7b_sharp_fp16 + ema_vae_fp16 |
| models/upscale_models/ | a face upscaler such as 4xFaceUpSharpDAT |
| DDColor's checkpoints/ | ddcolor_modelscope.pth |
### Running
1. Set node *[2]** to your source folder — **one directory per line**. Every
output path is relative to it, so you can paste in fifty model folders and
each one's outputs land in its own place.
2. Queue **once per image** using the **batch-count** field.
3. Start ComfyUI **without --cache-none** — it breaks the lazy branches.
That's the only path you need to set.
---
## Tuning
Defaults came from surveying a ~400-image scraped dataset. Survey your own set
before trusting them; a corpus of studio portraits sits very differently from
scraped material.
| widget | default | meaning |
|---|---|---|
| min_face_px | 500 | face bbox short edge, **native** pixels |
| hard_floor_face_px | 200 | below this, reject without attempting super-res |
| min_clarity | 60 | ~20 soft, ~60 acceptable, 150+ crisp |
| min_source_grain | 0.15 | below = no sensor noise at all: illustration, airbrushed, or AI |
| texture gate | 1.0 | below this, run restoration |
| target_b (skin tone) | 14 | **dataset-specific.** ~14 warm-toned, ~9 East Asian |
**Forcing individual images through:** the FORCE primitive accept /
refine_1x / refine_2x) ignores every threshold. Put the stragglers in their
own folder, point [2] at it, set it, queue, then set it back to off.
---
## Findings worth knowing
**Restoration is capped by the VAE, not the model.** A pure VAE encode→decode
with no sampling at all loses as much skin texture as any img2img pass at
1024. Chroma, Flux.1, Flux.2, SRPO and Qwen-Image-Edit were all tested; all
returned the input or worse. **SUPIR is the exception** — it carries a
purpose-trained denoise encoder that replaces the VAE encoder for its hint
latent, so its conditioning skips that low-pass. Measured 0.63 → 0.87 texture
at ~20 s/image with identity preserved at 0.94.
**SUPIR's denoise is a peak, not a slider.** 0.65 is best; 0.75 collapses back
below the input. Raising it does not give you more.
**SUPIR converges rather than simply improving.** It lifts low texture and
pulls down high texture, so running it on everything would damage your best
images. Hence the 1.0 gate.
**A second restoration pass makes things worse** (0.45 → 0.36), and retrying at
a different denoise doesn't rescue a failure — the peak doesn't move per image.
There is deliberately no loopback.
**Over-processed output usually means the source had no real texture.** Already
airbrushed exports, illustrations and heavily compressed scans have nothing for
super-res to recover, so it synthesises wax. That's what min_source_grain
catches before any GPU time is spent.
**For anything the pipeline can't lift: do nothing.** A soft but genuine
photograph is better training data than invented texture — and the _rev tag
already tells you which ones to look at.
---
## Licence
Workflow: MIT. The models it loads carry their own licences.
Description
While this workflow uses SDXL for processing (used in conjunction with SUPIR), it is intended to prepare image datasets for lora training - which could be used to train any model.
