Latent Transform Sampler π
It shifts the latent mid-denoise and lets the model redraw around it
- model
- positive
- negative
- latent_image
- samples
The whole trick in one paragraph
Latent Transform Sampler is a drop-in replacement for the KSampler that doesn't just denoise - at N scheduled moments during the run it reaches into the latent being generated and shifts, mirrors, or rotates it, then lets the model keep going and redraw around the change. No VAE round-trip, no ControlNet, no API key. You pick which steps to poke, and the poking happens while the image is still being formed.
Why bother? Post-processing a finished image flattens it; nudging the latent mid-run is closer to telling the model "the frame moved" and letting it re-render coherently. The author's demo is exactly that: 128 pixels of horizontal shift applied at steps 3, 6 and 9, so the composition slides and the model invents what belongs in the newly exposed space. The same mechanism gets you loopable video, seam-filling, or just alternate compositions for a seed you already like. Honest framing: this is an experimenter's toy, not a daily driver. It's a one-author pack with essentially no community folklore behind it yet, and half the fun is figuring out what the trick is actually good for.
How it works
Mechanically it's a standard sampler with a callback. All the familiar KSampler inputs are there - model, positive and negative conditioning, latent_image, seed, steps, cfg, sampler_name, scheduler, denoise - and inside, the node calls comfy.sample.sample() and installs a per-step callback. At each scheduled step it mutates the in-progress latent x in place (x.copy_()) before the next denoise pass. The transformations are plain torch ops:
- shift =
torch.roll- and note roll wraps: content slides off one edge and reappears on the other rather than being cut - mirror_horizontal / mirror_vertical =
torch.flip - rotate_90_cw / rotate_90_ccw / rotate_180 =
torch.rot90
Shift amounts are in pixels, but latents live at an 8:1 ratio, so shift_pixels_x of 128 becomes a 16-unit latent roll. transform_strength at 1.0 applies the operation outright; below that the node blends the transformed latent with the original for a gradual effect.
The "when" is handled by distribution. Manual (the default) reads manual_steps, a comma-separated list like 3,6,9; the other ten strategies - even, front_loaded, back_loaded, edges, center, golden_ratio, fibonacci, exponential, logarithmic, random - compute step indices from your total steps. transform_count caps how many actually fire.
The inputs you actually set
transform_count(default 3): how many transforms run; 0 disables the whole thingtransform_type: shift, mirrors, rotations,random_transform, orsequence/alternate_mirrors/spiral(those three build a repeating list fromtransform_sequence)distribution: how transform steps are spread across the schedule - manual plus the auto strategiesshift_pixels_x/shift_pixels_y: pan amounts, -512 to 512manual_steps/transform_sequence: the comma-separated strings that power manual distribution and sequence type
Less common but worth knowing: accumulate_shifts stacks each shift on the previous one instead of resetting; reverse_on_second_half undoes transforms after the midpoint; min_spacing keeps transforms from clustering on top of each other. The single output is samples (LATENT), wired straight into VAE Decode like any sampler.
Installing it
ComfyUI Manager (search "Latent Transform Sampler"), or the old-school way:
cd ComfyUI/custom_nodes
git clone https://github.com/pizurny/Comfyui-Latent-Transform-Sampler
Then restart ComfyUI. There are no model downloads and no heavy dependencies - the pack's pyproject asks only for torch>=2.0 and numpy, both of which ComfyUI already ships. It's registered in the Comfy Registry under publisher "pizurny", which is why Manager finds it.
Troubleshooting, grounded in the code
- Random isn't reproducible.
transform_type: random_transformanddistribution: randomuse Python's globalrandommodule, not your seed - the same seed can give different results. Use a fixed transform or a sequence if you need repeatability. - Manual steps get truncated. Manual distribution slices to
transform_count, so if you list3,6,9you needtransform_countof at least 3 or the tail is dropped. - Shifts wrap around.
torch.rollsends content across the edges. Great for seamless loops; for a "pan" look you'll see ghosting on the opposite side unless you keep shift values small. - Denoise changes what a "step" means. Transform steps are indices in the sampling loop, not absolute noise levels. At denoise < 1 (img2img) ComfyUI runs a truncated schedule, so step 3 lands at a different point in the trajectory than it does at full denoise. Learn with denoise 1.0.
- Defaults assume SD-style CFG.
cfgdefaults to 8 - right for SD 1.5/SDXL finetunes, wrong for guidance-distilled models like Flux. The included example workflow is Flux at cfg 1 with a FluxGuidance node; steal that if you're on a flow-matching checkpoint. debug_mode(default on) prints the full schedule to the console - genuinely handy for seeing which steps got transformed.
Inputs (22)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | β | |
| positive | CONDITIONING | β | |
| negative | CONDITIONING | β | |
| latent_image | LATENT | β | |
| seed | INT | 00β18446744073709550000 | β |
| steps | INT | 201β10000 | β |
| cfg | FLOAT | 8.00β100 | β |
| sampler_name | COMBO | 44 options: euler, euler_cfg_pp, euler_ancestral, euler_ancestral_cfg_pp, heun, heunpp2, +38 | |
| scheduler | COMBO | 9 options: simple, sgm_uniform, karras, exponential, ddim_uniform, beta, +3 | |
| denoise | FLOAT | 1.000β1 | β |
| transform_count | INT | 30β20 | Number of transformations to apply (0 = disabled) |
| transform_type | COMBO | shift | Type of transformation to apply |
| distribution | COMBO | manual | How to distribute transforms across steps |
| shift_pixels_x | INT | 128-512β512 | Horizontal shift in pixels (for shift transform) |
| shift_pixels_y | INT | 0-512β512 | Vertical shift in pixels (for shift transform) |
| min_spacing | INT | 21β10 | Minimum steps between transforms |
| transform_strength | FLOAT | 1.00β1 | Strength of transformation (for gradual effects) |
| accumulate_shifts | BOOLEAN | false | Whether shifts should accumulate or reset each time |
| reverse_on_second_half | BOOLEAN | false | Reverse transformations in the second half |
| transform_sequenceopt | STRING | shift,mirror_h,rotate_90_cw | Comma-separated sequence (if type='sequence') |
| manual_stepsopt | STRING | 3,6,9 | Comma-separated step numbers (if distribution='manual') |
| debug_modeopt | BOOLEAN | true | Print debug information to console |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| samples | LATENT | β |