33 Commits
Author SHA1 Message Date
capacap 2793129820 Clarify that the mask attenuates rather than protects smooth regions
Top blurb claimed smooth areas were left at the normal sigma and that the
node preserves those regions outright. The mask is a soft weighting, so
smooth regions receive an attenuated shift rather than full protection.
The examples section further down already says this; align the blurb.
2026-05-10 13:26:19 +02:00
capacap 156b6651da Add install section to README
Manager path first (with Registry link), manual git clone as fallback.
Notes there are no extra dependencies beyond ComfyUI itself.
2026-05-10 03:54:24 +02:00
capacap 93385201fe Add GitHub Actions workflow for Comfy Registry auto-publish
Triggers on push to master when pyproject.toml changes (so version
bumps publish automatically) plus a manual workflow_dispatch escape
hatch. Uses the official Comfy-Org/publish-node-action with the
REGISTRY_ACCESS_TOKEN repo secret.
2026-04-30 03:11:21 +02:00
capacap 661d8fce82 Add Registry icon and banner, bump to 0.1.1
Crops a square focal point and a 21:9 strip from the anime strength
sweep so the Registry listing has visible artwork instead of falling
back to the no-image placeholder.
2026-04-30 03:02:20 +02:00
capacap a2f887336a Add example workflows and anime/realistic sweep pairs
Publish three example workflows (simple, hiresfix, debug) under
example_workflows/ and link them from the README, in preparation for
ComfyUI Manager / Comfy Registry registration. Replace the single
Juggernaut sweep pair with realistic and anime (Illustrious) pairs to
show the wrapper is architecture-level rather than tuned to one style,
and soften the mask-effect wording to reflect that selectivity is a
soft weighting, not a binary gate.
2026-04-30 02:36:40 +02:00
capacap 2f8a7418d3 Mention IMAGE output in Debug Preview description
Surfaces the new downstream wiring path (comparison grids) in the node
tooltip so users discover it without reading the source.
2026-04-25 18:43:27 +02:00
capacap 7ae9e420f8 Add coverage and strength sweep examples to README
Drops in two SDXL/Juggernaut portrait sweeps showing the coverage and
strength knobs against a stylized prompt. The strength sweep is held
at coverage=0.5 to demonstrate the selective behavior: outlines and
fine detail sharpen while the low-density background stays clean.
2026-04-25 18:18:20 +02:00
capacap 2f269471b4 Add image output to Debug Preview and seed full-coverage mask placeholder
Restores the IMAGE socket on the preview node so the rendered mask can
be wired into image grids alongside outputs. Also seeds mask_ref with
an all-ones placeholder when the Debug sampler short-circuits at
strength=0 + coverage>=1, matching the non-zero-strength full-coverage
behavior and avoiding a black gap in mask comparisons.
2026-04-25 18:18:12 +02:00
capacap bfaab9cb96 Reconcile stale comments and populate mask_ref at full coverage
Post-session audit. Comment drift from the CFG++ reorder:
- Top docstring and _MIN_MASK_ACTIVITY comment implied normal-first
  ordering and forward-saving short-circuits; neither is true after the
  reorder. Both forwards now run before the mask is available.
- build_sampler's coverage<=0 docstring described a branch that's never
  reached (both nodes short-circuit at go() for coverage<=0).
- _mask_delta's coverage<=0 check was dead code with a matching stale
  comment claiming it lets the wrapper avoid the second model call.

Also: the debug preview rendered an empty mask on the coverage=1 fast
path because full_coverage skips the mask_fn entirely and never writes
to mask_ref. Populate mask_ref with a ones tensor once at sampler_function
entry so the preview reflects what the wrapper is actually doing.
2026-04-25 01:34:09 +02:00
capacap ac15b1b8d0 Reorder blend and attenuate strength for CFG++ compatibility
CFG++ samplers capture uncond_denoised via a post_cfg_function hook and
use it as a separate side-channel for the noise direction. The wrapper's
two forwards both fire the hook, leaving the captured uncond tied to
whichever sigma ran last. Reorder so the original sigma runs last, which
keeps the side-channel aligned with the sigma the sampler uses in its
step formula. For non-CFG++ samplers the order is irrelevant (blend math
is commutative) so this is safe as the single path. Cost: the activity
and first-step short-circuits no longer save a forward.

Reorder alone isn't sufficient because our returned denoised still mixes
x0 across two sigmas while the uncond stays at one, which introduces a
cross-sigma term in the implicit guidance signal. Empirically this
amplifies effective strength ~6-7x at mid-mask on CFG++ samplers.
Detect the hook at run start and attenuate the per-step adjustment by
0.15 when present. Calibration is exact only at mid-mask; full-strength
detail on CFG++ is better served by coverage=1.

Also drops the mask_sharpness experiment. Sharpening mid-mask values
toward hard {0,1} was tried as a fix for the same pathology before we
understood it was a CFG++ side-channel issue; it didn't help and isn't
useful as a standalone knob.
2026-04-25 00:02:18 +02:00
capacap 88c5ac8154 Expose end knob on Debug node and guarantee clean tail step
The schedule's active region is now [start, end] with start=0.1, end=0.9 on
the main node. make_schedule enforces a clean step at both head and tail
regardless of the passed values, giving the sampler composition headroom
and room to clean residual noise after the last detail step.

Debug node exposes start and end as sliders. _TAIL_TAPER_START/END collapse
to a single _TAIL_TAPER_WIDTH derived relative to end.
2026-04-24 21:37:23 +02:00
capacap e0acf28435 Rename intensity to strength and short-circuit no-op paths
strength is the peak per-step sigma-reduction fraction applied directly,
replacing the intensity/_INTENSITY_REFERENCE/_REFERENCE_PER_STEP_SHIFT
indirection. Breaking change: old intensity=16 maps to strength=0.1.

Add fast paths so degenerate configurations skip work: strength==0 or
coverage<=0 returns the input sampler unwrapped; coverage>=1 saturates the
mask and runs only the adjusted-sigma forward per active step.
2026-04-24 21:28:14 +02:00
capacap 94fdfee2b0 Rewrite README against current node surface
The previous README described a variance-based mask and a schedule-curve
parameter set that no longer exist. Replaces it with a walkthrough of the
current delta-mask two-pass design, an explanation of why spatial
selectivity matters (clean backgrounds, bokeh, flat colors breaking under
global sigma modulation), a realistic picture of the 2x inference cost
and its mitigations, and listings of the three current nodes.
2026-04-21 19:38:39 +02:00
capacap 3d4283dd43 Taper schedule tail and log per-run pass stats
Linearly decays intensity across the last ~30% of steps and zeroes the
final ~15%, so those steps skip the second model call via the existing
adjustment==0 fast path. Saves 14-17% wallclock on typical workloads and
softens the over-sharpening that showed up at higher step counts.

Also adds a per-run stats line logging detail vs. skip counts so the
impact of future schedule/threshold tweaks is visible. Includes a
mask-activity floor (currently 0.02) as a secondary skip path; not
tripping in practice but kept as a safety net for degenerate masks.
2026-04-21 18:13:03 +02:00
capacap 7a8b49cde2 Document core logic and rename sigma adjustment constants
Splits _PER_STEP_ADJUSTMENT_SCALE into _INTENSITY_REFERENCE and
_REFERENCE_PER_STEP_SHIFT so the calibration anchor (intensity=16) is
explicit at the call site. Adds module/function docstrings and targeted
inline comments explaining the two-pass blend, why the shift is not
divided by step count, the coverage post-EMA ordering, and other
non-obvious invariants.
2026-04-21 18:11:42 +02:00
capacap 6bbda4ad30 Raise default intensity to 16
After the CFG decoupling and step-count normalization fixes the old
default of 2.0 produces very mild output. 16 works across 8/16/32
step runs as a sensible starting point.
2026-04-21 15:50:44 +02:00
capacap f7a08712a5 Drop vestigial DeltaV2 from class and registry names
The V2 suffix came from earlier iterations that also had V1 variants.
Only one SAMPLER node remains; the version marker is noise.

Breaking change: saved workflows referencing the old key
SelectiveSigmaDetailerDeltaV2Node will need to be re-wired to
SelectiveSigmaDetailerNode.
2026-04-21 15:26:33 +02:00
capacap 20a2ebcd4c Clean up prior mask previews before writing new one
The preview node wrote a fresh timestamped PNG per run without ever
deleting the previous ones, so the temp directory would accumulate a
file per preview call over a session. Delete prior ssd_mask_*.png
files at the start of each preview so at most one mask image exists
on disk at a time. Keep the timestamped filename so the browser does
not cache a stale image.
2026-04-21 15:25:42 +02:00
capacap 3904d877f3 Clear mask_ref at start of each sampler run
ComfyUI caches the SAMPLER object across runs with matching inputs,
so its captured mask_ref dict persists. If a later run skipped the
mask path (short schedule, coverage=0, etc.) the debug preview would
display the prior run's mask. Clear at the top of sampler_function so
stale masks never survive into a new run.
2026-04-21 15:25:19 +02:00
capacap 9a935431be Skip second model call when coverage is zero
At coverage=0 the mask shift zeroes out the blend factor, so the
detail call's output is discarded anyway. Return None early so the
sampler wrapper skips the second apply_model call entirely.
2026-04-21 15:25:08 +02:00
capacap 46ccfc0ab0 Drop model-patch variant
The patch node hooked set_model_unet_function_wrapper to inject sigma
adjustments, but that wrapper sits below apply_model's input scaling
and timestep transformation. The second forward pass therefore ran
with input prepped for the original sigma while the outer layer's
calculate_denoised also used the original sigma, so the adjustment
produced near-zero visible effect regardless of intensity.

Keep only the SAMPLER variant (used via SamplerCustom), which operates
at the correct layer.
2026-04-21 15:21:41 +02:00
capacap 044dc031b3 Decouple sigma adjustment from CFG scale
The SAMPLER path multiplied the per-step adjustment by the model's
cfg_scale, which made intensity a moving target across CFG values and
diverged from the patch path's behavior. Drop the cfg_scale factor so
intensity maps directly to sigma shift.
2026-04-21 15:21:10 +02:00
capacap 18b826541f Make intensity step-count invariant via fixed reference
Per-step sigma adjustment was divided by active_count, which
double-counted the step-size normalization the sampler's integration
already applies. Net effect scaled inversely with step count, so short
runs felt much stronger than long runs. Replace the per-run divisor
with a fixed 16-step reference constant, matching the patch variant's
calibration. Removes the now-unused normalize_by_active_steps flag
from build_sampler.
2026-04-21 02:22:53 +02:00
capacap ef390a3d0d Split debug surface from main nodes
Main SAMPLER and model-patch nodes now return only their primary
output. A new Debug node exposes the hardcoded constants (start, ema,
mask_clip_percentile) plus the mask_ref output; the preview node is
renamed to "Debug Preview" and stripped of its upscale/image outputs.
2026-04-21 01:40:17 +02:00
capacap dcd4eafdf1 Add model-patch variant compatible with KSampler
The existing node outputs a SAMPLER and only works with SamplerCustom.
The new SelectiveSigmaDetailerPatchModelNode takes MODEL in and out,
attaching the mask-and-blend logic via set_model_unet_function_wrapper
so the same behavior works inside any sampler node.

Tradeoffs vs the SAMPLER variant: progress is measured in sigma-space
(1 - sigma/sigma_max) instead of step index since the full schedule is
not visible at the model layer; intensity is calibrated against a
fixed 16-active-step reference instead of the actual active-step
count; the cfg_scale coupling is dropped since it is not cleanly
accessible from the unet wrapper. Behavior should match the SAMPLER
variant closely near 20 steps on standard schedulers.
2026-04-21 01:11:14 +02:00
capacap 9a42555596 Collapse node to intensity/coverage two-slider surface
Parameter search converged on narrow working values across SDXL
workloads, so the previously-exposed knobs now live as hardcoded
constants: schedule start at 0.2 (skip composition-setting phase, min
1 step enforced), mask clip percentile at 0.2, and EMA at 0.9.

Coverage replaces the removed mask_floor/invert_mask experiments. It
is an additive threshold shift applied after EMA and state storage so
it does not compound across steps: 0 zeroes the mask, 0.5 is the raw
delta mask, 1.0 saturates to full. The `end` knob is also removed
since 1.0 was always the natural value.
2026-04-21 00:40:21 +02:00
capacap aa69032224 Drop cosine schedule curve, update defaults
Cosine ramping existed to dampen sigma modification at the start/end
of the active window, a defensive measure against composition drift.
With the delta mask providing spatial selectivity derived from the
emerging composition, this temporal dampening is redundant.

Schedule is now a flat rectangle of detail_amount across [start,
end]. Defaults updated to observed working values: detail_amount=2.0,
start=0.0, end=1.0, ema=0.75, mask_clip_percentile=0.2.
2026-04-20 22:38:33 +02:00
capacap cb563349f7 Strip node to minimal parameter set
Remove v1 delta node, redundant schedule parameters (bias, exponent,
smooth, start/end_offset, fade), cfg_scale_override, and the mask
blur/threshold/gamma post-processing. Defaults bake in the observed-
static values: cosine curve with bias=0.5, exponent=1.0, smooth=on,
offsets/fade at 0.

Node surface is now sampler + detail_amount + start + end + ema +
mask_clip_percentile. Renames postprocess_mask to normalize_mask
since only the percentile-clipped stretch remains.
2026-04-20 22:08:32 +02:00
capacap 979355b7df Expose mask_clip_percentile as v2 slider
Percentile-clipped min/max normalization on the delta mask replaces
min/max and rank normalization in the V2 node. Default 0.1; 2%
proved too mild, 10% is where the knob starts producing visible
change. A median-filter experiment was tried and reverted after the
resulting smoother mask produced no meaningful image difference.
2026-04-20 21:53:55 +02:00
capacap 9fd1ec988f Trim to delta-only nodes and add step-normalized V2 variant
Remove variance-snapshot, dynamic-variance, and edges experimental
nodes along with their unused helpers. Keep the delta node as v1.

Add SelectiveSigmaDetailerDeltaV2Node: divides the per-step sigma
adjustment by the count of active schedule steps, and rank-normalizes
the delta mask so detail_amount has direct leverage regardless of
mask distribution shape. Detail_amount cap raised to 100 to allow
for the larger values v2 requires.
2026-04-20 16:06:47 +02:00
capacap d02e85f267 Add experimental mask-derivation variants and preview node
Extract shared sampler-wrapping and mask postprocessing into ssd_core.py.
Add three experimental variants alongside the original (variance snapshot):
dynamic variance with EMA, delta (inter-step prediction difference) with
EMA, and edges (Sobel). Each sampler node now also emits a mask handle
consumed by a new mask-preview node that renders the captured mask inline
after sampling runs.

Use reflect-padding in variance, Sobel, and blur ops to eliminate the
border-aura artifact produced by zero-padded convolutions.
2026-04-19 22:57:13 +02:00
capacap 7094d59da5 Inline schedule helpers into node module 2026-04-19 18:08:02 +02:00
capacap 8a1c3e2d0f Initial commit: extract Selective Sigma Detailer as standalone node 2026-04-19 18:05:11 +02:00