Nodes/ComfyUI-Tensor-Operations/Latent Match Normalize
ComfyUI Node

Latent Match Normalize

In latent space

By ttulttul·Created 3 years ago·Updated 2 years ago· 8
Latent Match Normalize
  • source_latent
  • target_latent
  • LATENT

This is the latent-space sibling of Image Match Normalize: same mean/std color transfer, but it operates on the compressed representation the sampler actually works in instead of on pixels. Two latents in, one latent out - no knobs, no settings, just "make the target latent's per-channel color statistics match the source latent's."

Why do it in latent space at all? Because pixels and latents encode color differently. A VAE compresses an image into a few channels (4 for SD1.5/SDXL, 16 for Flux) where brightness and hue are distributed across the channels in ways that don't map cleanly to what you see. The README makes the pitch directly: normalizing the latent instead of the image often gets you closer to the source's actual coloring, because that's the space the diffusion model reasons in. If you're already staying in latent space anyway - an img2img chain, a latent upscale pass - this saves you a decode-rescale-reencode round trip just to fix colors.

How it works

The node pulls the samples out of both latent dicts, computes each one's per-channel mean and standard deviation over the spatial dimensions, and rescales the target to borrow the source's:

(target - target_mean) / target_std * source_std + source_mean

All under torch.no_grad(), per frame in a batch - so it behaves on video the same way its image sibling does.

The inputs and outputs that matter

There are exactly two inputs, and only one output:

  • source_latent - provides the color statistics you want to copy.
  • target_latent - provides the content that keeps its structure while its colors get overwritten.

Output is a single LATENT, ready for a sampler or a VAE decode.

Where people get burned

  • Both latents must be the same spatial size - and same channel count. There's no resizing in here, and cross-model latents have different channel depths (SD1.5 and SDXL use 4 channels, Flux uses 16), so a mismatch errors out. If your source is a 512² latent and your target is 1024², upscale one latent first - or just drop to Image Match Normalize and do it on pixels instead.
  • One honest quirk: the returned latent is the source dict with the normalized target's samples swapped in. Any other metadata on the source latent - like a noise mask - rides along. Fine in most workflows, surprising if you were expecting the target's baggage.
  • It's as blunt as its image sibling. Mean and std matching gets you the source's average brightness and contrast, not its actual palette. If the result looks "off but you can't say why," this is why.

Installing it

Same pack as the other two, zero extra dependencies, no models:

cd ComfyUI/custom_nodes
git clone https://github.com/ttulttul/ComfyUI-Tensor-Operations

Or ComfyUI Manager → search ComfyUI-Tensor-Operations → install, then restart. It's a small, easy-to-read utility pack by the same author as the Iterative Mixing KSampler - worth a skim of the source before you wire it into anything critical.

Categorytensor_ops

Inputs (2)

NameTypeDefaultDescription
source_latentLATENT
target_latentLATENT

Outputs (1)

NameTypeDescription
LATENTLATENT