Fix the palette-wash pixel-block artifact: bilinear upscale instead of nearest

The wash pools the reference to a 2-10 cell color grid (the structure-safety
step) and upscaled it nearest-neighbor, leaving hard-edged giant cells - at
study 256 just 5x5 blocks of ~51px. With the retuned live shape pull the
model reproduced them literally: palette-wash recipes rendered subjects as
pixel-art mosaics (checkered spheres, quilted lamps, blocky ledges).

Bilinear upscale melts the identical palette relationships into smooth
gradients. A/B on identical seeds: mosaic gone, color transfer kept, subject
and structure safety unchanged (the pool step still destroys structure).
Affects every palette-wash recipe on both node generations. 57/57 tests.
This commit is contained in:
Kevin Gilper
2026-07-03 15:46:36 -04:00
parent f8e1e89611
commit 5f61727801
2 changed files with 13 additions and 2 deletions
+2
View File
@@ -2,6 +2,8 @@
## Unreleased - Krea 2 V10 Nodes
- Fixed the pixel-block artifact in `palette wash` preparation (affects every palette-wash recipe on V9 and V10: the style, palette, lighting, material, environment, and mood-board recipes and the starter pack). The wash reduced the reference to a coarse color grid and upscaled it with hard-edged nearest-neighbor cells; with the retuned live `shape` the model reproduced those cells literally, rendering subjects and surfaces as large pixel-art blocks. The upscale is now bilinear, so the same coarse palette relationships arrive as smooth color gradients - render-verified on identical seeds: the mosaic disappears entirely, the palette transfer and structure safety are unchanged.
- Card: the `focus` recipe field - a custom recipe can now study **one named aspect** of its reference ("the clothing and garment style worn by the person, not the person's identity, face, or the background"). The text is written into the vision encoder's instruction line for that card (`study only ... ignore everything else about it`; away cards get scoping language), the guide packet carries it as an append-only key, and the stack report prints it on the card's line. Render-verified on Krea 2 with same-seed A/B pairs: aspect *selection* works (a clothing-focused recipe kept the reference's garment and gained garment detail) and aspect *de-selection* is remarkably strong (focusing the same photo on "the background, not the person or their clothing" removed the reference's signature red coat entirely) - while scene-wide moods such as weather ride the image itself, so the docs steer `focus` toward object-bound aspects. Strings are whitespace-normalized and capped at 300 characters; V9 workflows are unaffected.
- Starter recipes: `custom_recipes/starter-pack.yaml` ships **enabled** with three render-validated recipes that appear in `Use image for` out of the box - `borrow the weather` (strong blur + low detail bring a reference's storm/fog/golden-hour mood over without its buildings or poles), `borrow the clothing style` (a `focus` recipe; its description documents the inverse trick), and `cinematic color grade` (a bolder palette sibling, capped at 1.2). Delete or underscore the file to opt out.
- Recipe Builder: an optional "Focus on one aspect" box (step 2) writes the `focus` field with the node's exact normalization, with honest guidance on where focus is strong; all builder outputs remain machine-verified through the real recipe loader.
+11 -2
View File
@@ -23,13 +23,22 @@ def blur_samples(samples, kernel_size):
def palette_wash_samples(samples):
"""Reduce an image to a blurred coarse color grid (palette-only signal)."""
"""Reduce an image to a smooth coarse color field (palette-only signal).
The adaptive pool destroys structure (that is the safety guarantee); the
upscale back is bilinear ON PURPOSE. The original nearest upscale left
hard-edged giant cells, and with a live shape pull the model reproduced
them literally - palette-wash recipes rendered subjects as pixel-art
mosaics. Bilinear melts the same palette relationships into gradients:
render-verified (same seed) to keep the color transfer and subject
safety while eliminating the block artifact entirely.
"""
height = int(samples.shape[2])
width = int(samples.shape[3])
grid_h = min(10, max(2, height // 48))
grid_w = min(10, max(2, width // 48))
palette = torch.nn.functional.adaptive_avg_pool2d(samples, (grid_h, grid_w))
palette = torch.nn.functional.interpolate(palette, size=(height, width), mode="nearest")
palette = torch.nn.functional.interpolate(palette, size=(height, width), mode="bilinear", align_corners=False)
average_color = samples.mean(dim=(2, 3), keepdim=True)
palette = palette * 0.85 + average_color * 0.15
return blur_samples(palette, 9)