Nodes/ComfyUI-latent-ops/LatentOperationNormalizePercentile
ComfyUI Node

LatentOperationNormalizePercentile

Min-max normalization that ignores the weird values

By hnmr293·Created about a year ago·Updated about a year ago· 2
LatentOperationNormalizePercentile
    • op
    percentile0.9900

    LatentOperationNormalizeMinMax has one fatal flaw: a single outlier sets your 0 and 1 and crushes everything else into a thin gray band. LatentOperationNormalizePercentile is the fix - same stretch-to-a-range idea, but it anchors the range on percentiles instead of the raw min and max, so a few stray extreme values don't get to ruin the whole mapping. If you're normalizing noisy latents, this is the version you actually want.

    How it works

    v1 = torch.quantile(latent, percentile)
    v2 = torch.quantile(latent, 1 - percentile)
    lo, hi = min(v1, v2), max(v1, v2)
    return (latent - lo) / (hi - lo + eps)
    

    The single input is percentile (FLOAT, default 0.99, range 0–1). With the default 0.99, the node takes the 99th percentile as the top bound and the 1st percentile as the bottom bound - effectively trimming the outer 1% on each side before the stretch. Values outside those bounds still get mapped (they just clamp past 0 or 1, since the formula doesn't clamp them), but they no longer dictate the range. The min/max around the two quantiles is a belt-and-suspenders guard so the math works even if you set percentile below 0.5.

    That makes it the robust cousin in the pack's normalization ladder: Normalize (global z-score), NormalizeAlongAxis, LayerNorm (per sample), NormalizeMinMax (raw 0–1), and this - min-max that survives outliers. Where NormalizeMinMax gets burned by one hot channel value, this one just shrugs and stretches the middle 98%.

    When you'd use it

    Feeding a latent into something that expects a bounded, comparable range - a preview path, a custom op, a control signal - is where this earns its keep, especially if the latent's provenance is messy (came out of a CFG manipulation, an interpolation, or another model's sampler). It's also the gentler way to build that intuition: sweep percentile from 0.99 down toward 0.5 and you watch the mapping get more and more aggressive. The trade-off: it's not a true 0–1 mapping anymore - extremes do poke past the bounds - so don't use it where the consumer requires everything in [0,1]. For that, NormalizeMinMax (accepting the outlier risk) is the literal answer.

    The apply gotcha

    Zero optional inputs here, one required float, and the output is op of type LATENT_OPERATION - a deferred closure, exactly like every node in this pack. ComfyUI-latent-ops builds operations and ships no apply node, so you need a LATENT_OPERATION consumer (Sonar's SonarApplyLatentOperationCFG is the one that exists) or your own apply node. Plugging op into a VAE Decode gets you a type mismatch. It's the pack's universal first trap and it's not your fault - that's just how the toolkit is shaped.

    Install

    The usual. ComfyUI Manager → search ComfyUI-latent-ops, or:

    cd ComfyUI/custom_nodes
    git clone https://github.com/hnmr293/ComfyUI-latent-ops
    

    Restart. No requirements.txt, no model downloads - pure PyTorch, which ComfyUI already ships. This is hnmr293's personal workbench (sd-webui-cutoff, llul), essentially invisible to the community, so the source in latent_ops/normalize.py is your documentation. Everything lands under hnmr/latent_ops.

    Categoryhnmr/latent_ops

    Inputs (1)

    NameTypeDefaultDescription
    percentileFLOAT0.99000–1

    Outputs (1)

    NameTypeDescription
    opLATENT_OPERATION