Nodes/WAS Node Suite v3/Custom Sampler Affine Advanced
ComfyUI Node Runs on cloud

Custom Sampler Affine Advanced

The affine for people who already build their own sampler stack

By WASasquatch·Created 3 years ago·Updated 4 days ago· 1,844
Custom Sampler Affine Advanced
  • noise
  • guider
  • sampler
  • sigmas
  • latent_image
  • affine_schedule
  • external_mask
  • affine_options
  • output
  • denoised_output
  • mask
affine_interval1
max_scale1.020
max_bias0.000
patternwhite_noise
affine_seed0
affine_seed_incrementfalse
temporal_modestatic
affine_streamsvideo
affine_spacelatent
debugfalse
affine_acts_oncontent

Three nodes in this pack do the same transform. KSampler Affine Advanced is the one-node version. Affine Sampler hands you back a wrapped SAMPLER to drop into an existing custom-sampling graph. This one - Custom Sampler Affine Advanced - replaces SamplerCustomAdvanced itself, taking the noise, guider, sampler and sigmas you already built and denoising with the affine running inside the loop. If your graph has a guider you care about, or noise you chose deliberately, this is the node.

That last part is the actual reason to be here. The affine family is a texture/detail injector: it scales the latent and adds an offset through a mask while the sampler runs, mid-run where detail gets established. The stock way to get similar texture is a node that leaves extra noise behind during sampling, and it works - but the only real control is "how much". Affine gives you a mask (26 patterns, from grain to perlin to sobel edges read off the latent), a schedule, and a spatial gate. And it doesn't restart the sampler to do it, so multistep history, noise sequences and packed audio/video latents all survive untouched.

The reason it's worth combining with your own noise: the pack also ships Temporal Noise Hold and Affine Pattern Noise, both of which are NOISE sources. Put one of those on this node's noise socket and you're shaping the starting draw and the trajectory in the same graph, which is the closest thing here to a proper video-texture pipeline.

What goes in

noise, guider, sampler, sigmas, latent_image - the standard five. sigmas is worth a note: the affine curve is read over the sigmas you feed it, so the step count is one fewer than the number of entries. Get that wrong and your carefully designed schedule lands on the wrong steps.

Then the affine block, identical to its sibling nodes: affine_interval (apply every Nth step; 1 = every step), max_scale (what the latent is multiplied by at the peak - 1.0 is nothing, 1.02 is visible texture over a 20-step run, 1.05 is strong, past 1.08 the image breaks up), max_bias (added beside the scale, not through it - 0.005 is a drift, 0.02 shifts colour), pattern, affine_seed, affine_seed_increment and temporal_mode.

temporal_mode is the video one: static, per_frame, or drift (slid by the drift_speed/drift_angle_deg/drift_renew values on Affine Options). Content-aware patterns ignore it.

Optional inputs: affine_schedule (from Affine Schedule), affine_options (from Affine Options - and its pattern beats the widget here), external_mask, affine_streams (video, audio, both for packed latents), affine_space (latent to match the Latent Affine node's scale, model for the sampler's internal one), debug (logs step, sigma, strength, resolved scale and bias - genuinely the fastest way to find out whether anything is happening), and affine_acts_on.

Careful with that last one. content multiplies only the picture the model has resolved; latent multiplies the whole thing including the sampler's own noise, which prints as grain fixed in the frame on a model that holds high noise for most of its run. Default is content. Change it deliberately.

Three outputs: output (the latent the sampler ended on), denoised_output (the model's own estimate of the clean latent at the last step), and mask - the mask the last application ran through, at latent resolution, all zero if nothing was applied. That mask output is the debugger nobody uses. Wire it to a Preview Mask node and look at it: if your grain isn't landing where you thought, you'll see why in one render instead of five.

Limits worth knowing before you fight them

Four stock samplers can't carry the affine: dpm_fast, dpm_adaptive, uni_pc and uni_pc_bh2. They drive their own solver or rescale the latent before every model call, so there's no clean step boundary to hook. They don't crash; the console says so and they sample normally. If you ignored that line, you'll conclude the feature is broken.

Install

Ships in WAS Node Suite v3. Manager, search WAS Node Suite v3, install, restart. Manually:

cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/was-node-suite-comfyui.git

Needs ComfyUI 0.14.0+ and Python 3.10+. No packages get installed, now or later - the v3 rewrite dropped every default dependency, so if you're following a v2-era install guide that has you fighting pinned OpenCV or InsightFace, you're reading about a different version of this pack. Optional groups live behind features: keys in <ComfyUI user dir>/was-node-suite/config.yaml, written on first start; nothing relevant to sampling is off by default.

One more thing if you're porting an old graph: this node has no legacy equivalent. It's new with v3, and any 2024 workflow that used WAS sampling nodes will be wired to something else entirely.

CategoryWAS Suite/Sampling

Inputs (19)

NameTypeDefaultDescription
noiseNOISEWhere the starting noise comes from.
guiderGUIDERWhat steers the denoising.
samplerSAMPLERThe sampler to wrap, from KSamplerSelect or any other SAMPLER source. Every stock sampler carries the affine except dpm_fast, dpm_adaptive, uni_pc and uni_pc_bh2, which is reported in the console rather than failing the run.
sigmasSIGMASThe noise levels to step through. The affine curve is read over these, so one entry fewer than the length is the step count.
latent_imageLATENTThe latent to denoise. Image, video and packed audio and video latents are all handled.
affine_intervalINT11–100Apply on every Nth step of the schedule. 1 = every step, 4 = every fourth, which leaves the sampler more room to settle between applications.
max_scaleFLOAT1.0200–2What the latent is multiplied by at the peak of the schedule, compounding over every step it lands on. 1.0 = no change, 1.02 adds visible texture over a 20 step run, 1.05 is strong, and past 1.08 the picture breaks up. Below 1.0 softens instead.
max_biasFLOAT0.000-2–2What is added at the peak of the schedule, beside max_scale rather than through it. 0.0 = nothing, 0.005 = a gentle drift, 0.02 shifts the whole colour. bias_field on Affine Options decides whether that is one flat offset or a noise field.
patternCOMBOwhite_noiseWhich mask decides where the affine lands. 'solid' covers everything; 'white_noise' and the coloured noises are grain of different coarseness; 'perlin', 'checker', 'bayer', 'cross_hatch', 'worley_edges' and the rest are shapes; 'detail_region', 'smooth_region', 'edges_sobel' and 'edges_laplacian' are read off the latent itself; 'external_mask' uses the mask wired in.
affine_seedINT00–2147483647Seeds the mask. The same seed always draws the same mask, so change it to move the grain without changing anything else. Ignored by the content-aware patterns and by 'external_mask', which read what they are given.
affine_seed_incrementBOOLEANfalse`true` advances the seed on every application, so the grain moves from step to step. `false` holds one mask for the whole run, which keeps the affine landing in the same places.
temporal_modeCOMBOstaticHow a video latent's mask varies over time. 'static' = one mask on every frame, in the same place all clip. 'per_frame' = an unrelated mask each frame. 'drift' = one mask slid across the frame, set by drift_speed, drift_angle_deg and drift_renew on Affine Options. The content-aware patterns ignore this, and so does an image latent.
affine_scheduleoptDICTThe per-step strength curve from an Affine Schedule node. Left unwired the affine ramps up over the middle of the run, from a fifth of the way in to four fifths.
affine_streamsoptCOMBOvideoWhich streams of a packed audio and video latent the affine reaches. 'video' = stream 0, 'audio' = the rest, 'both' = all of them. An ordinary latent has only a video stream, so 'audio' does nothing to it.
affine_spaceoptCOMBOlatentWhich latent max_scale and max_bias are measured against. 'latent' = the same scale Latent Affine uses, so a value means the same thing in both places; 'model' = the sampler's own internal latent, which some models hold at a very different magnitude.
external_maskoptMASKA mask of your own, resized onto the latent. On pattern 'external_mask' it is the mask; on any other pattern it gates the generated one, so the affine reaches only where this is white. One mask covers every frame, or one per frame.
affine_optionsoptDICTPattern parameters and mask shaping from an Affine Options node. Leave it unwired and every value takes its default. A pattern set there wins over the pattern widget.
debugoptBOOLEANfalse`true` logs every application to the console: the step, its sigma, the strength, the resolved scale and bias, and which streams were touched. `false` stays quiet.
affine_acts_onoptCOMBOcontentWhat max_scale multiplies. 'content' = only the picture the model has resolved, so the sampler's own noise is left alone. 'latent' = the whole latent, which amplifies that noise as well and prints it as fixed grain on a model that holds noise for most of its run.

Outputs (3)

NameTypeDescription
outputLATENTThe latent the sampler ended on.
denoised_outputLATENTThe model's own estimate of the clean latent at the last step.
maskMASKThe mask the last application ran through, at latent resolution. All zero where no affine was applied.