Affine Options
26 masks, one settings drawer, and why nothing happens when you run it
- affine_options
Affine Options is the one node in this family that does no work. It builds a settings dictionary - the pattern an affine masks through, plus the numbers that shape that pattern - and hands it to a sampler or to Latent Affine through an affine_options socket. Half the questions about it are really "I ran the graph and nothing changed," and the answer is almost always that the node was never wired to anything. Wire it or don't. It changes nothing on its own.
It exists because of a real problem: the affine transform multiplies a latent and adds an offset to it, and if you just do that everywhere, you cook the whole image. Distilled and flow-matching models come back a little flat - that's the tradeoff of squeezing a model into eight steps, and distillation docs are blunt about it. The community's answer to flatness for years has been Detail Daemon-style nodes that leave noise behind during the middle steps. The affine nodes do the same trick, but through a mask, which is the interesting part: grain in the sky and not on the face, texture on fabric and not on skin.
What the pattern does
The pattern widget picks one of 26 fields. Broadly:
- The noise family -
white_noise,pink_noise,brown_noise,red_noise,blue_noise,violet_noise,purple_noise,green_noise,black_noise. These are grain at different coarseness. The coloured ones are spectral shapes:black_noisekeeps a chosen number of frequencies alive,green_noisesits in a band, andhighpass_whiterolls off the low end. - Shapes -
perlin,checker,bayer,cross_hatch,tile_oriented_lines,dot_screen_jitter,poisson_blue_mask,worley_edges,ring_noise,velvet_noise. Blobs, grids, halftone screens, stipple, cell walls. Some of these are drawn flat on purpose -cross_hatchabove 0.5 cycles per sample aliases into moiré, and the tooltip says so. - Read off the latent -
detail_region,smooth_region,edges_sobel,edges_laplacian. These don't exist until there's a picture to measure, which is why they're the four that a starting-noise pattern can't offer. solid(everything, scaled bysolid_alpha) andexternal_mask(your own MASK, if you've wired one into the sampler).
Only the chosen pattern's own settings are drawn on the node, which is the design decision you'll either love or curse. Love: the node stays short. Curse: you tune six sliders for perlin, switch to dot_screen_jitter to compare, and your work is invisible - it's still there, it's just not on screen.
The inputs you'll actually touch
content_gate is the sleeper hit. Set it to smooth_region and a generated pattern only lands where the picture is flat, so the grain follows the subject instead of sitting over it; detail_region does the opposite, and edges_sobel/edges_laplacian pin it to edges. content_window decides how wide a neighbourhood it reads - 3 for fine texture, 15 for whole regions.
mask_strength multiplies the mask before the affine reads it, and here's the trap: 2.0 doesn't just mean "more mask", it pushes past the max_scale you set on the sampler. threshold, invert_mask, mask_blur and mask_sharpen (with sharpen_radius and sharpen_threshold) shape the mask first, then it's used. clamp with clamp_min/clamp_max is worth turning on the moment you push scale or bias hard - it keeps the latent inside numbers the model has actually seen, and it's cheap insurance.
bias_field is the other one people miss. constant adds one flat offset wherever the mask is white, which shifts colour and tone. gaussian makes it a noise field, one value per element - 0.02 is a gentle drift, 0.1 is strong.
Two groups are only for video latents and only when the sampler's temporal_mode is per_frame or drift: frame_seed_stride (how far the seed moves between frames - a big prime like 9973 makes each frame independent) and drift_speed, drift_angle_deg, drift_renew (sliding one mask across the clip). If you're running stills, they do nothing.
Installation
ComfyUI Manager, search WAS Node Suite v3, install, restart. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/was-node-suite-comfyui.git
Requires ComfyUI 0.14.0+ and Python 3.10+. The pack installs no packages - its v3 rewrite moved everything onto torch on ComfyUI's own device. On first start it writes config.yaml under your ComfyUI user dir in was-node-suite/, and that first start takes a second or two longer.
Things that bite
If you came from a 2023-era tutorial: that WAS Node Suite is a different thing. The v2 line fought with your ComfyUI updates over pinned OpenCV and InsightFace and threw import failures after unrelated installs, and the author was open at the time about not having the hours for it. v3 installs nothing, so that whole class of breakage is gone. If you're seeing it, you have the old pack.
Otherwise: the pattern set here wins over the sampler's own pattern widget, so a stale options node quietly overrides what you typed downstream. And the node won't tell you it's working - the affine is silent unless you flip debug on the sampler.
Inputs (50)
| Name | Type | Default | Description |
|---|---|---|---|
| pattern | COMBO | white_noise | Which 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. |
| green_center_frac | FLOAT | 0.350–1 | Where the green noise band sits, as a share of the finest detail the latent can hold. 0.1 = coarse blotches, 0.35 = mid, 0.8 = fine. |
| green_bandwidth_frac | FLOAT | 0.150.01–1 | How wide that band is. 0.05 = one grain size, 0.4 = a broad mix. |
| black_bins_per_kpx | INT | 5121–500000 | How many frequencies black noise keeps alive, per thousand samples. 16 = a few standing ripples, 512 = a busy weave. |
| hatch_freq_cyc_px | FLOAT | 0.450.01–2 | Cycles per sample in each hatch line. 0.1 = wide bars, 0.45 = fine lines, above 0.5 the lines alias into moire on purpose. |
| hatch_angle1_deg | FLOAT | 00–179 | Angle of the first set of lines, in degrees. 0 = horizontal. |
| hatch_angle2_deg | FLOAT | 900–179 | Angle of the second set. 90 crosses the first at a right angle. |
| hatch_square | BOOLEAN | false | `true` squares the waves off into hard bars; `false` leaves them smooth sinusoids. |
| hatch_phase_jitter | FLOAT | 0.000–1 | How far the seed may slide the lines. 0.0 = fixed, 1.0 = anywhere. |
| hatch_supersample | INT | 11–8 | How many times over the lines are drawn before averaging down. 1 = fast and jagged, 4 = smooth edges at four times the cost. |
| highpass_cutoff_frac | FLOAT | 0.700.01–1 | Below this share of the finest detail the latent can hold, the noise is rolled off. 0.3 keeps most of it, 0.9 keeps only the finest grain. |
| highpass_order | INT | 21–10 | How sharply that roll-off bites. 1 = gentle, 8 = a hard edge. |
| ring_center_frac | FLOAT | 0.900–1 | Which single grain size the ring keeps. 0.9 is close to the finest. |
| ring_bandwidth_frac | FLOAT | 0.0500.005–1 | How pure that grain is. 0.01 = one size only, 0.2 = a small spread. |
| poisson_radius_px | FLOAT | 8.01–256 | Closest two points may sit, in latent samples. 4 = a dense stipple, 24 = widely spaced dots with broad space between them. |
| poisson_softness | FLOAT | 6.00.1–256 | How fast the field brightens away from a point. 1 = tight dots, 20 = soft cells. |
| worley_points_per_kpx | FLOAT | 2.00.1–200 | Cell seeds per thousand samples. 0.5 = a few large cells, 20 = a fine mesh. |
| worley_metric | COMBO | L2 | 'L2' grows round cells, 'L1' grows diamond ones with straight edges. |
| worley_edge_sharpness | FLOAT | 1.00.1–8 | How thin the boundaries are drawn. 0.5 = broad seams, 4 = hairlines. |
| tile_line_tile_size | INT | 324–512 | Tile side in latent samples. 8 = a fine weave, 64 = large panels. |
| tile_line_freq_cyc_px | FLOAT | 0.400.01–2 | Cycles per sample inside a tile. 0.1 = wide bands, 0.4 = fine lines. |
| tile_line_jitter | FLOAT | 0.250–1 | How far a tile's lines may slide. 0.0 lines the tiles up, 1.0 breaks them apart. |
| dot_cell_size | INT | 122–256 | Halftone cell side in latent samples. 4 = a fine screen, 32 = a coarse one. |
| dot_jitter_px | FLOAT | 1.50–10 | How far a dot strays from its cell centre, in samples. 0 = a rigid grid. |
| dot_fill_ratio | FLOAT | 0.300.01–0.95 | Roughly what share of a cell a dot covers. 0.1 = pinpricks, 0.8 = nearly solid. |
| velvet_taps_per_kpx | INT | 101–10000 | Impulses per thousand samples. 2 = sparse sparkle, 200 = dense speckle. |
| perlin_scale | FLOAT | 644–1024 | Samples per blob. 16 = small blobs, 64 = medium, 256 = broad drifts. |
| perlin_octaves | INT | 31–8 | How many passes are summed. 1 = smooth blobs, 6 = detail at every size. |
| perlin_persistence | FLOAT | 0.500.1–1 | How much strength each finer pass keeps. 0.3 = smooth, 0.8 = rough. |
| perlin_lacunarity | FLOAT | 2.01–4 | How much finer each pass is than the last. 2.0 doubles the detail each time. |
| checker_size | INT | 82–256 | Square side in latent samples. 4 = a tight grid, 32 = large blocks. |
| bayer_size | INT | 82–64 | Dither tile side. 2, 4, 8 and 16 are the ones that tile without a seam. |
| solid_alpha | FLOAT | 1.0000–1 | How much of the affine a solid mask lets through. 1.0 = the whole latent at full strength, 0.25 = a quarter of the way there. |
| content_window | INT | 73–63 | How wide a neighbourhood the detail and smooth patterns measure over, in latent samples. 3 = fine texture, 15 = whole regions. |
| mask_strength | FLOAT | 1.0000–2 | Multiplies the mask before the affine reads it. 0.5 = half the effect everywhere, 1.0 = as drawn, 2.0 = double, which pushes past the scale and bias that were asked for. |
| threshold | FLOAT | 0.0000–1 | Cuts the mask into hard on and off at this level. 0.0 = off, leaving the mask smooth; 0.5 keeps the brighter half. |
| invert_mask | BOOLEAN | false | `true` swaps where the affine lands for where it does not; `false` leaves the mask as drawn. |
| mask_blur | FLOAT | 0.00–16 | Softens the mask's edges. 0.0 = off, 1.0 = a gentle feather, 6.0 = smears fine grain into broad patches. |
| mask_sharpen | FLOAT | 0.00-5–5 | Raises the mask's contrast before anything else touches it. 0.0 = off, 0.3 = subtle, 1.0 = strong, negative softens instead. |
| sharpen_radius | FLOAT | 0.800–8 | How wide the detail that sharpening lifts is. 0.5 = fine, 3.0 = broad. |
| sharpen_threshold | FLOAT | 0.000–1 | Detail weaker than this is left alone. 0.0 sharpens everything. |
| clamp | BOOLEAN | false | `true` holds the transformed latent inside clamp_min and clamp_max; `false` lets it go anywhere. Worth turning on where a large scale or bias sends values far past what the model has seen. |
| clamp_min | FLOAT | -10.0-100–0 | Lowest value the latent may hold once clamping is on. -10 is wide enough for any ordinary latent; -4 is a tight leash. |
| clamp_max | FLOAT | 10.00–100 | Highest value the latent may hold once clamping is on. 10 is wide enough for any ordinary latent; 4 is a tight leash. |
| frame_seed_stride | INT | 99731–100000 | How far the seed moves between frames on 'per_frame' and 'drift'. 1 makes neighbouring frames similar; a large prime such as 9973 makes each frame independent. |
| drift_speed | FLOAT | 0.350–16 | How far the mask slides each frame on 'drift', in latent samples. 0.0 holds it still, 0.35 is a slow crawl, 2.0 sweeps across a short clip. |
| drift_angle_deg | FLOAT | 00–359 | Which way it slides, in degrees. 0 = right, 90 = down, 180 = left, 270 = up. |
| drift_renew | FLOAT | 0.000–1 | How much of the mask is replaced each frame on 'drift', on top of the slide. 0.0 slides one mask unchanged, 0.15 lets it turn over as well, 1.0 matches 'per_frame'. |
| bias_field | COMBO | constant | What the bias adds where the mask is white. 'constant' = one offset everywhere, which shifts colour and tone. 'gaussian' = a noise field, one value per latent element. On 'gaussian' a bias of 0.02 is gentle and 0.1 is strong. |
| content_gate | COMBO | off | Hold a generated pattern back to where the picture allows it. 'off' lets it cover the frame; 'smooth_region' keeps it to flat areas and off detail; 'detail_region' does the opposite; 'edges_sobel' and 'edges_laplacian' keep it to edges. Read off each frame, so the grain follows the subject. content_window sets how wide it reads. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| affine_options | DICT | The pattern and its settings, for the affine_options socket of Latent Affine or any Affine sampler. |