Nodes/WAS Node Suite v3/Latent Hybrid Upscale
ComfyUI Node Runs on cloud

Latent Hybrid Upscale

Latent upscaling that protects edges instead of smearing them

By WASasquatch·Created 3 years ago·Updated 3 days ago· 1,832
Latent Hybrid Upscale
  • latent
  • vae
  • donor_latent
  • latent
  • edge_mask
scale2.00
pre_blur_sigma_px1.00
canny_threshold125
canny_threshold2155
canny_l2gradienttrue
dilate_radius_px8
feather_sigma_px6.00
mask_min0.00
mask_max1.00
use_nearest_exacttrue
output_mask_resolutionimage
video_decode_horizontal_tiles2
video_decode_vertical_tiles2
video_decode_overlap_latent4
video_decode_last_frame_fixfalse
video_decode_enable_cudnntrue

Plain latent upscaling - the nearest/bilinear resize you get from ComfyUI's stock latent upscale node - is fast, but it doesn't know where your edges are. It'll happily smear a hard line between a subject and its background right along with the smooth gradients. Latent Hybrid Upscale (class WASLatentUpscaleHybrid) is WAS_Extras' answer: it decodes the latent, figures out where the real edges are, and uses that map to protect them while it upscales.

This is one of several nodes the pack's author, WASasquatch, has shipped since retiring the old WAS Node Suite (the sprawling original pack, archived June 2025 and now community-forked). WAS_Extras is the newer, smaller, still-actively-updated home for stuff like this - don't confuse the two if you're hunting for it in ComfyUI Manager.

How it works

The node decodes your latent to pixel space with the vae you feed it, then runs a Canny edge detector (via OpenCV, "if available" per the pack's own README) to build an edge mask - dilated and feathered so it's a soft band around edges rather than a hard line. That mask governs how the upscale blends: near edges it leans on the decoded image-space detail to avoid ringing and haloing; elsewhere it upscales the latent more freely. You can also hand it a donor_latent to blend in instead of relying purely on the computed scale. It has explicit handling for video latents too, with its own tiled-decode controls so a long clip doesn't blow your VRAM at the decode step.

The inputs and outputs that matter

  • latent, vae - required, the obvious two.
  • scale (default 2, range 1–8) - the one knob you'll actually touch day to day.
  • canny_threshold1 / canny_threshold2 / canny_l2gradient / pre_blur_sigma_px - tune how aggressively the edge detector fires. Leave at defaults until you see ringing or a mask that's obviously wrong.
  • dilate_radius_px / feather_sigma_px / mask_min / mask_max - grow and soften the edge band, and clamp how strong its influence gets.
  • use_nearest_exact - the resize kernel used away from edges.
  • output_mask_resolution (image or latent) - whether the mask output matches your image size or your latent size.
  • video_decode_* group (horizontal_tiles, vertical_tiles, overlap_latent, last_frame_fix, enable_cudnn) - only relevant when your latent is a video batch; these tile the decode step to keep it VRAM-safe.
  • Optional donor_latent.

Outputs: latent (the upscaled result, wire straight into your next sampler or VAE decode) and edge_mask (a MASK you can preview to sanity-check what the node thought counted as an edge, or feed into a compositing step).

How to install it

Via ComfyUI Manager: search WAS_Extras, install, restart. Manually:

cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/WAS_Extras

Then restart ComfyUI. None of the requirements-file dependencies the README mentions apply to this node - those are for a different file in the pack (the sampler sequencer). This one just needs OpenCV.

Common issues & troubleshooting

Edge mask looks blank or solid. The README is explicit that Canny detection depends on OpenCV being importable - if opencv-python isn't installed in ComfyUI's Python environment, run pip install opencv-python there. Otherwise, your canny_threshold1/canny_threshold2 pair is probably mismatched for your image's contrast; nudge them before assuming the node is broken.

You grabbed the wrong node from a shared workflow. WAS_Extras ships a second, related node - WAS Adaptive Difference Latent Upscale (Damped) - that does a similar-sounding but different job (damping the upscale delta near edges rather than Canny-masking in image space). If a downloaded workflow's behavior doesn't match what's described here, check you actually have WASLatentUpscaleHybrid and not its sibling.

VRAM spikes on video latents. The decode step for a batch of frames is where this bites. Reduce video_decode_horizontal_tiles/vertical_tiles, or increase overlap_latent only if you're seeing seams - it costs more VRAM, not less.

Ringing near hard edges at high scale. Back off dilate_radius_px or raise feather_sigma_px so the protected band is softer and wider rather than a hard cutout.

CategoryWAS Suite/Latent/Transform

Inputs (19)

NameTypeDefaultDescription
latentLATENTThe latent to enlarge. A video latent with a time axis is handled frame by frame.
vaeVAEThe VAE used to decode the latent so its edges can be found. It must be the one that matches the latent, or the edges will be found in the wrong places. It only reads the latent; the result is still built in latent space.
scaleFLOAT2.001–8How much larger the result is. 2.0 doubles both sides, 1.5 adds half again. Sizes are rounded to whole latent blocks.
pre_blur_sigma_pxFLOAT1.000–20Blur applied to the decoded picture before edges are looked for, in pixels. It keeps film grain and fine texture from registering as edges; 0.0 finds every last one, 2.0 or more keeps only the major outlines.
canny_threshold1INT250–1000The lower of the two edge-detection levels, on a 0-255 scale. A faint edge is kept only when it joins a strong one, and this is how faint it may be. Lower values trace more of an outline; raise it if speckles appear in flat areas.
canny_threshold2INT1550–1000The upper of the two edge-detection levels, on a 0-255 scale. Anything this strong starts an edge on its own. Raise it to keep only bold outlines, lower it to catch soft ones.
canny_l2gradientBOOLEANtrueHow edge strength is measured. On, the true length of the gradient is used, which is slightly slower and more accurate on diagonals. Off, a cheaper approximation is used that reads diagonal edges as stronger than they are.
dilate_radius_pxINT80–64How far the found edges are grown, in pixels. Edges are hairline by themselves, so growing them is what gives the smooth enlargement a band to work in: 8 covers a typical outline, 0 leaves the raw one-pixel lines.
feather_sigma_pxFLOAT6.000–50How far the grown edge fades out, in pixels. Without it the band would have a visible border of its own; 6.0 gives a soft changeover, 0.0 leaves a hard-edged band.
mask_minFLOAT0.000–1Floor under the finished mask. Raise it above 0.0 to let a little of the smooth enlargement into areas with no edges at all, which takes the hard blockiness off the whole picture.
mask_maxFLOAT1.000–1Ceiling over the finished mask. Lower it below 1.0 to keep some of the crisp enlargement even on the strongest edges, which is the way back when outlines come out too soft.
use_nearest_exactBOOLEANtrueHow the crisp half of the blend is enlarged. On, each output block takes the value of the source block whose centre is nearest, which keeps the picture from drifting half a block sideways. Off uses the older nearest-neighbour rule.
output_mask_resolutionCOMBOimageWhich size the mask output comes out at. `image` gives it at the size the enlarged latent decodes to, ready to view or reuse against the finished picture. `latent` gives the small version that actually drove the blend.
video_decode_horizontal_tilesINT21–8How many columns a video latent is split into for the decode that finds edges. More tiles means less VRAM and more time. Ignored on an image latent.
video_decode_vertical_tilesINT21–8How many rows a video latent is split into for that decode. 2 rows and 2 columns is four tiles, each a quarter of the frame. Ignored on an image latent.
video_decode_overlap_latentINT40–32How far neighbouring tiles overlap, in latent units. The overlap is cross-faded, so raise it if seams show along the tile boundaries; 0 turns the fade off entirely.
video_decode_last_frame_fixBOOLEANfalseWhether the final frame is duplicated before decoding and the extra output dropped afterwards. Turn it on when the last frames of a clip decode to something corrupt, which some video VAEs do.
video_decode_enable_cudnnBOOLEANtrueWhether cuDNN is left on for the video decode. Turning it off is slower and avoids the large workspace allocations that make some cards run out of memory part way through a clip.
donor_latentoptLATENTWhere the smooth half of the blend comes from. Leave it unconnected and the node interpolates the input latent. Connect a second latent of the same batch and channel shape, a version sampled at a higher resolution, say, and its detail is what gets laid into the edges.

Outputs (2)

NameTypeDescription
latentLATENTThe enlarged latent, ready for a sampler or a decode.
edge_maskMASKWhere the smooth enlargement was used: white along the edges the node found, black elsewhere. Watch it while setting the two Canny levels, or reuse it to treat the same areas downstream.