ꘇ top.cleanup
Resize and tidy pixels without touching a model
- image
- image
First, the thing everyone gets wrong: top.cleanup is not an upscaler. It resizes and tidies; it does not invent detail. If you feed it a 256px image and ask for 2048, you get a smooth 2048px image, not a detailed one. Reaching for this when you wanted ESRGAN or SeedVR2 is the classic misread, and the pack's own docs are blunt about it - "spatial filtering, not learned restoration or reconstruction of missing detail."
So what is it for? The job that sits between a real upscaler and a blur: explicit dimensions, a pre-resize edge-preserving smoothing pass, cheap antialiasing on hard edges, and a mild sharpen that doesn't halo. It's a TOP in ꘇ Genereti (languel/genereti, by Liuboto, MIT), the realtime creative-coding layer for ComfyUI, and it's model-free by design - no downloads, no checkerboarding, milliseconds per frame.
The stages, in the order they run
The implementation reads in a straight line, which makes it easy to reason about:
- Bilateral smoothing (if
smoothing> 0) - a 3×3 blend where each neighbour's weight falls off both by colour distance (controlled bycolor_threshold) and spatial distance. Edge-preserving, so it smooths flat regions and leaves boundaries. Runs before the resize, which is why it's good at killing grime before you enlarge it. - Resize to
width×heightwithmethod:bilinearorbicubic. The bicubic coefficient is -0.75, and interpolation is premultiplied so transparent edges don't pick up fringes. Downsampling here is plain resampling - not a dedicated antialiased reduction filter, so don't expect area-averaging quality. - Antialiasing (if
antialias> 0) - a short edge-direction filter driven by the luminance gradient, taking two taps at half a pixel along the tangent. The strength per pixel is scaled by local contrast, so flat areas aren't blurred. The author is careful about the name: it's FXAA-style, "not full NVIDIA FXAA." - Sharpen (if
sharpen> 0) - an unsharp pass whose result is clamped to the local envelope (the min/max of the pixel and its four neighbours), which is what keeps it from producing halos.
Each optional stage is skipped at zero, so plain bicubic resize and the full chain cost very different amounts.
The controls that matter
width and height are independent INTs, 1–4096, defaulting to 1024×1024. They do not preserve aspect ratio for you - the docs say this twice, and defaulting to a square is exactly how you smear a 16:9 frame. Set them to match the source.
method picks bicubic (default) or bilinear. Then the three amount dials, and the tooltips are the author's own guidance: smoothing starts around 0.2 (raise it and thin lines start to disappear), color_threshold at 0.12 with lower values protecting boundaries, antialias around 0.3, sharpen around 0.1. The docs give a recipe worth starting from: bicubic, smoothing 0.25, color_threshold 0.12, antialias 0.3, sharpen 0.1.
Output is a single image (IMAGE).
Live versus Queue
Live, the whole chain runs in the browser on WebGPU; the pack measured a full bilateral + bicubic + AA + sharpen chain from 512→1024 at roughly 0.8 ms p50 in a small browser fixture (their numbers, their Mac, their caveat that this isn't full graph FPS). On Queue, the same spatial stages run in PyTorch on the input tensor's device, and a synthetic CPU/GPU comparison differed by at most one 8-bit colour level. That equivalence is the point: you can dial it in live and trust the queued frame.
Install
Manager → search "Genereti" → install ꘇ Genereti → restart ComfyUI → refresh the browser page. Manual:
git clone https://github.com/languel/genereti.git custom_nodes/genereti
./.venv/bin/python -m pip install -r custom_nodes/genereti/requirements.txt
The pinned requirement is only comfyui-frontend-package>=1.53.6 - no models, no Core ML, no extra servers. ComfyUI 0.38.2+, Python 3.11+, WebGPU-capable browser for the live path. The bundled ꘇ-Realtime-Cleanup workflow shows a 256px source against a 512px bilinear baseline and a 512px cleanup branch, which is the fastest way to see the difference.
Gotchas
Batches broadcast but must otherwise match. A single B image broadcasts across A's batch; mismatched batch sizes raise instead of guessing.
Premultiplication is doing work you don't see. If you're compositing cutouts over new backgrounds, the premultiplied sampling is why edges stay clean - but it also means an image with garbage colours in fully transparent pixels will sample as transparent, not as garbage. That's correct behaviour, occasionally surprising.
Rabbit hole alert. The pack's notes list FSR1 EASU/RCAS and SMAA as future comparison options and explicitly say neither is implemented. Don't go looking for upscaler-grade output in this node; it isn't claiming to be one.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| width | INT | 10241–4096 | — |
| height | INT | 10241–4096 | — |
| method | COMBO | bicubic | 2 options: bilinear, bicubic |
| smoothing | FLOAT | 0.000–1 | 3×3 edge-preserving bilateral blend, before enlargement. Start around 0.2. |
| color_threshold | FLOAT | 0.120.001–1 | Bilateral color/alpha threshold. Lower values protect boundaries. |
| antialias | FLOAT | 0.000–1 | Short luminance-edge directional filter after resize; FXAA-style, not full NVIDIA FXAA. |
| sharpen | FLOAT | 0.000–1 | Local-envelope-clamped sharpening after antialiasing. Start around 0.1. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |