Nodes/ComfyUI Latent Transform Sampler/Latent Transform Sampler πŸ”„
ComfyUI Node

Latent Transform Sampler πŸ”„

It shifts the latent mid-denoise and lets the model redraw around it

By pizurnyΒ·Created 11 months agoΒ·Updated 10 months agoΒ· 3
Latent Transform Sampler πŸ”„
  • model
  • positive
  • negative
  • latent_image
  • samples
β—„seed0β–Ί
β—„steps20β–Ί
β—„cfg8.0β–Ί
β—„sampler_nameβ–Ύβ–Ί
β—„schedulerβ–Ύβ–Ί
β—„denoise1.00β–Ί
β—„transform_count3β–Ί
β—„transform_typeshiftβ–Ί
β—„distributionmanualβ–Ί
β—„shift_pixels_x128β–Ί
β—„shift_pixels_y0β–Ί
β—„min_spacing2β–Ί
β—„transform_strength1.0β–Ί
β—„accumulate_shiftsfalseβ–Ί
β—„reverse_on_second_halffalseβ–Ί
β—„transform_sequenceshift,mirror_h,rotate_90_cwβ–Ί
β—„manual_steps3,6,9β–Ί
β—„debug_modetrueβ–Ί

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 thing
  • transform_type: shift, mirrors, rotations, random_transform, or sequence / alternate_mirrors / spiral (those three build a repeating list from transform_sequence)
  • distribution: how transform steps are spread across the schedule - manual plus the auto strategies
  • shift_pixels_x / shift_pixels_y: pan amounts, -512 to 512
  • manual_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_transform and distribution: random use Python's global random module, 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 list 3,6,9 you need transform_count of at least 3 or the tail is dropped.
  • Shifts wrap around. torch.roll sends 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. cfg defaults 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.
Categorysampling/transform

Inputs (22)

NameTypeDefaultDescription
modelMODELβ€”
positiveCONDITIONINGβ€”
negativeCONDITIONINGβ€”
latent_imageLATENTβ€”
seedINT00–18446744073709550000β€”
stepsINT201–10000β€”
cfgFLOAT8.00–100β€”
sampler_nameCOMBO44 options: euler, euler_cfg_pp, euler_ancestral, euler_ancestral_cfg_pp, heun, heunpp2, +38
schedulerCOMBO9 options: simple, sgm_uniform, karras, exponential, ddim_uniform, beta, +3
denoiseFLOAT1.000–1β€”
transform_countINT30–20Number of transformations to apply (0 = disabled)
transform_typeCOMBOshiftType of transformation to apply
distributionCOMBOmanualHow to distribute transforms across steps
shift_pixels_xINT128-512–512Horizontal shift in pixels (for shift transform)
shift_pixels_yINT0-512–512Vertical shift in pixels (for shift transform)
min_spacingINT21–10Minimum steps between transforms
transform_strengthFLOAT1.00–1Strength of transformation (for gradual effects)
accumulate_shiftsBOOLEANfalseWhether shifts should accumulate or reset each time
reverse_on_second_halfBOOLEANfalseReverse transformations in the second half
transform_sequenceoptSTRINGshift,mirror_h,rotate_90_cwComma-separated sequence (if type='sequence')
manual_stepsoptSTRING3,6,9Comma-separated step numbers (if distribution='manual')
debug_modeoptBOOLEANtruePrint debug information to console

Outputs (1)

NameTypeDescription
samplesLATENTβ€”