Nodes/WAS Node Suite v3/Affine Options
ComfyUI Node Runs on cloud

Affine Options

26 masks, one settings drawer, and why nothing happens when you run it

By WASasquatch·Created 3 years ago·Updated 4 days ago· 1,844
Affine Options
    • affine_options
    patternwhite_noise
    green_center_frac0.35
    green_bandwidth_frac0.15
    black_bins_per_kpx512
    hatch_freq_cyc_px0.45
    hatch_angle1_deg0
    hatch_angle2_deg90
    hatch_squarefalse
    hatch_phase_jitter0.00
    hatch_supersample1
    highpass_cutoff_frac0.70
    highpass_order2
    ring_center_frac0.90
    ring_bandwidth_frac0.050
    poisson_radius_px8.0
    poisson_softness6.0
    worley_points_per_kpx2.0
    worley_metricL2
    worley_edge_sharpness1.0
    tile_line_tile_size32
    tile_line_freq_cyc_px0.40
    tile_line_jitter0.25
    dot_cell_size12
    dot_jitter_px1.5
    dot_fill_ratio0.30
    velvet_taps_per_kpx10
    perlin_scale64
    perlin_octaves3
    perlin_persistence0.50
    perlin_lacunarity2.0
    checker_size8
    bayer_size8
    solid_alpha1.000
    content_window7
    mask_strength1.000
    threshold0.000
    invert_maskfalse
    mask_blur0.0
    mask_sharpen0.00
    sharpen_radius0.80
    sharpen_threshold0.00
    clampfalse
    clamp_min-10.0
    clamp_max10.0
    frame_seed_stride9973
    drift_speed0.35
    drift_angle_deg0
    drift_renew0.00
    bias_fieldconstant
    content_gateoff

    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_noise keeps a chosen number of frequencies alive, green_noise sits in a band, and highpass_white rolls 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_hatch above 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 by solid_alpha) and external_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.

    CategoryWAS Suite/Latent/Transform

    Inputs (50)

    NameTypeDefaultDescription
    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.
    green_center_fracFLOAT0.350–1Where 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_fracFLOAT0.150.01–1How wide that band is. 0.05 = one grain size, 0.4 = a broad mix.
    black_bins_per_kpxINT5121–500000How many frequencies black noise keeps alive, per thousand samples. 16 = a few standing ripples, 512 = a busy weave.
    hatch_freq_cyc_pxFLOAT0.450.01–2Cycles 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_degFLOAT00–179Angle of the first set of lines, in degrees. 0 = horizontal.
    hatch_angle2_degFLOAT900–179Angle of the second set. 90 crosses the first at a right angle.
    hatch_squareBOOLEANfalse`true` squares the waves off into hard bars; `false` leaves them smooth sinusoids.
    hatch_phase_jitterFLOAT0.000–1How far the seed may slide the lines. 0.0 = fixed, 1.0 = anywhere.
    hatch_supersampleINT11–8How many times over the lines are drawn before averaging down. 1 = fast and jagged, 4 = smooth edges at four times the cost.
    highpass_cutoff_fracFLOAT0.700.01–1Below 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_orderINT21–10How sharply that roll-off bites. 1 = gentle, 8 = a hard edge.
    ring_center_fracFLOAT0.900–1Which single grain size the ring keeps. 0.9 is close to the finest.
    ring_bandwidth_fracFLOAT0.0500.005–1How pure that grain is. 0.01 = one size only, 0.2 = a small spread.
    poisson_radius_pxFLOAT8.01–256Closest two points may sit, in latent samples. 4 = a dense stipple, 24 = widely spaced dots with broad space between them.
    poisson_softnessFLOAT6.00.1–256How fast the field brightens away from a point. 1 = tight dots, 20 = soft cells.
    worley_points_per_kpxFLOAT2.00.1–200Cell seeds per thousand samples. 0.5 = a few large cells, 20 = a fine mesh.
    worley_metricCOMBOL2'L2' grows round cells, 'L1' grows diamond ones with straight edges.
    worley_edge_sharpnessFLOAT1.00.1–8How thin the boundaries are drawn. 0.5 = broad seams, 4 = hairlines.
    tile_line_tile_sizeINT324–512Tile side in latent samples. 8 = a fine weave, 64 = large panels.
    tile_line_freq_cyc_pxFLOAT0.400.01–2Cycles per sample inside a tile. 0.1 = wide bands, 0.4 = fine lines.
    tile_line_jitterFLOAT0.250–1How far a tile's lines may slide. 0.0 lines the tiles up, 1.0 breaks them apart.
    dot_cell_sizeINT122–256Halftone cell side in latent samples. 4 = a fine screen, 32 = a coarse one.
    dot_jitter_pxFLOAT1.50–10How far a dot strays from its cell centre, in samples. 0 = a rigid grid.
    dot_fill_ratioFLOAT0.300.01–0.95Roughly what share of a cell a dot covers. 0.1 = pinpricks, 0.8 = nearly solid.
    velvet_taps_per_kpxINT101–10000Impulses per thousand samples. 2 = sparse sparkle, 200 = dense speckle.
    perlin_scaleFLOAT644–1024Samples per blob. 16 = small blobs, 64 = medium, 256 = broad drifts.
    perlin_octavesINT31–8How many passes are summed. 1 = smooth blobs, 6 = detail at every size.
    perlin_persistenceFLOAT0.500.1–1How much strength each finer pass keeps. 0.3 = smooth, 0.8 = rough.
    perlin_lacunarityFLOAT2.01–4How much finer each pass is than the last. 2.0 doubles the detail each time.
    checker_sizeINT82–256Square side in latent samples. 4 = a tight grid, 32 = large blocks.
    bayer_sizeINT82–64Dither tile side. 2, 4, 8 and 16 are the ones that tile without a seam.
    solid_alphaFLOAT1.0000–1How 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_windowINT73–63How wide a neighbourhood the detail and smooth patterns measure over, in latent samples. 3 = fine texture, 15 = whole regions.
    mask_strengthFLOAT1.0000–2Multiplies 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.
    thresholdFLOAT0.0000–1Cuts the mask into hard on and off at this level. 0.0 = off, leaving the mask smooth; 0.5 keeps the brighter half.
    invert_maskBOOLEANfalse`true` swaps where the affine lands for where it does not; `false` leaves the mask as drawn.
    mask_blurFLOAT0.00–16Softens the mask's edges. 0.0 = off, 1.0 = a gentle feather, 6.0 = smears fine grain into broad patches.
    mask_sharpenFLOAT0.00-5–5Raises the mask's contrast before anything else touches it. 0.0 = off, 0.3 = subtle, 1.0 = strong, negative softens instead.
    sharpen_radiusFLOAT0.800–8How wide the detail that sharpening lifts is. 0.5 = fine, 3.0 = broad.
    sharpen_thresholdFLOAT0.000–1Detail weaker than this is left alone. 0.0 sharpens everything.
    clampBOOLEANfalse`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_minFLOAT-10.0-100–0Lowest value the latent may hold once clamping is on. -10 is wide enough for any ordinary latent; -4 is a tight leash.
    clamp_maxFLOAT10.00–100Highest value the latent may hold once clamping is on. 10 is wide enough for any ordinary latent; 4 is a tight leash.
    frame_seed_strideINT99731–100000How 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_speedFLOAT0.350–16How 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_degFLOAT00–359Which way it slides, in degrees. 0 = right, 90 = down, 180 = left, 270 = up.
    drift_renewFLOAT0.000–1How 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_fieldCOMBOconstantWhat 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_gateCOMBOoffHold 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)

    NameTypeDescription
    affine_optionsDICTThe pattern and its settings, for the affine_options socket of Latent Affine or any Affine sampler.