Style transfer: honor trained crop_size (I2-A) + partition-of-unity tile blending (I2-B)#53
Open
sibocw wants to merge 1 commit into
Open
Style transfer: honor trained crop_size (I2-A) + partition-of-unity tile blending (I2-B)#53sibocw wants to merge 1 commit into
sibocw wants to merge 1 commit into
Conversation
…ile blending (I2-B) I2-A: run_inference.py hard-coded image_side_length=256 and a fixed resize_and_crop preprocess, ignoring the resolution the CUT model was trained at (so a model trained at higher resolution still ran at 256). It now sources crop_size/load_size/preprocess from the trained model's train_options.json via parse_hyperparameters_from_checkpoint_path (mirroring run_tiled_inference.py and test_trained_models.py), falls back to the requested size when the file is absent, logs the resolution actually used, and warns if the user-supplied size disagrees with the trained crop_size. I2-B (quality improvement; existing outputs were already judged acceptable): - Add a "feather" trapezoidal partition-of-unity blend window that is strictly nonzero at the seam and whose 50%-overlap copies sum to exactly 1, unlike cosine/pyramid (zero at the border) or uniform (no feathering). Make it the recommended default for the tiled CLI; all legacy windows remain selectable. - Explicitly zero-weight mirror-padded out-of-frame tile regions (_clamp_weights_to_frame) so hallucinated content cannot leak into real edge pixels. - Default randomize_seams to False to avoid per-frame seam flicker. Adds tests/test_tiled_blend.py (pure-numpy; stubs the unavailable CUT lib): partition-of-unity + nonzero-seam checks for feather, contrast against cosine/pyramid, out-of-frame zero-weight checks, weight-type normalization regression, and crop_size-from-train_options parsing. Refs #48 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This work was implemented by Claude Code (Opus 4.8) upon user instruction.
Addresses the style-transfer resolution/tiling findings from the audit in #48 (I2-A and I2-B).
I2-A — Honor the trained CUT resolution at inference (was: frozen at 256)
style_transfer/scripts/run_inference.pyhard-codedimage_side_length=256(its CLI default) and a fixedresize_and_croppreprocess, so every frame was hard-resized to 256 and the generator emitted 256 — even for a model trained at a higher resolution. Unlike the tiled script, it ignored thecrop_size/load_size/preprocessstored in the trained model'strain_options.json.Fix: a new
resolve_trained_resolution()helper sourcescrop_size(→image_side_length),load_size, andpreprocessfrom the model'strain_options.jsonvia the existingparse_hyperparameters_from_checkpoint_path(mirroringrun_tiled_inference.pyandtest_trained_models.py). It falls back to the requestedimage_side_lengthwhentrain_options.jsonis absent, logs the resolution actually used, and warns if a user-supplied size conflicts with the trainedcrop_size.cut_inference.pyalready accepts a parsedpreprocess_opt, so no change was needed there.I2-B — Partition-of-unity tile blending (quality improvement; existing outputs were already acceptable)
style_transfer/tiled_inference.pyblended overlapping tiles withweight_type="uniform"by default — an all-ones map (plain averaging, no feathering) that can leave hard seams where two independently InstanceNorm-normalized tiles meet. The non-defaultcosine(0.5-0.5cos) andpyramid(1-|2t-1|) windows are zero at the tile border (so on a 50%-overlap grid the seam pixel gets ~0 weight from one tile) and are not a partition of unity.randomize_seams=Trueby default moved seams every frame (temporal flicker).Fixes (all safe and backward-compatible — every existing option stays selectable):
"feather"window: a symmetric trapezoidal ramp sampled at pixel midpoints. Its 50%-overlap copies sum to exactly 1 (verified) and it is strictly nonzero at the seam, so two adjacent tiles always co-contribute there. It is now the recommended default for the tiled CLI (run_tiled_inference.py)._clamp_weights_to_frame) before accumulation, so hallucinated padding cannot leak into genuine edge pixels.randomize_seamschanged toFalseto avoid per-frame seam flicker.Note: per #48, outputs were manually judged OK with the old
uniformdefault, so I2-B is an improvement, not a critical bug fix.Findings from #48 addressed
crop_size/preprocessfromtrain_options.json.uniformdefault does no feathering;cosine/pyramidare zero at the seam and not a partition of unity; mirror-padded regions leak into edge pixels;randomize_seams=Truecauses flicker.Refs #48
Tests
New
tests/test_tiled_blend.py(pure-numpy; stubs the unavailable CUT GAN library so the realtiled_inferencehelpers are imported and exercised directly — the GAN path is not run):cosine/pyramidcollapse to ~0 at the border andcosine's overlap-add is visibly non-uniform._compute_half_overlap_tile_startsgrid)._normalize_weight_type(all five types, case/whitespace,guassian→gaussian, unknown raises) and all legacy windows still build valid maps.parse_hyperparameters_from_checkpoint_pathsourcesimage_side_lengthfromcrop_size(andfinetune_load_sizewhen decaying), and raises whentrain_options.jsonis missing.Additionally validated end-to-end with an identity "GAN" on a constant frame: only
featheryields an accumulation weight of exactly 1.0 everywhere (including edges) and reproduces the input exactly, whereascosineproduces a non-constant weight map.Caveats
cutpackage + a CUDA model, unavailable here); I2-A is verified by reading the code and unit-testing the parsing helper it relies on.run_tiled_inference.py:run_tiled_inference_for_checkpoint. The separate cluster wrapperscripts_on_cluster/.../run_tiled_inference_all_simulations.py(outside this PR's scope) sets its own explicitweight_type/randomize_seams, so its behavior is unchanged; it can now also selectfeather.load_size > crop_size, the inheritedresize_and_croppreprocess uses aRandomCropat inference (non-deterministic) — this is the pre-existing behavior of the parsed-options path (test_trained_models.py), not introduced here.🤖 Generated with Claude Code