PBandDev commited on
Commit
4a02569
·
unverified ·
1 Parent(s): 7533dde

Expose SmartSeed selected seed outputs (#17)

Browse files

Adds selected_seed outputs for CADE Easy and MG_SuperSimple while preserving existing output slot order.
Adds regression coverage for output contracts.

README.md CHANGED
@@ -56,7 +56,7 @@ Photo Dog
56
  - MGHybrid scheduler: hybrid Karras/Beta sigma stack with smooth tail blending and tiny schedule jitter (ZeSmart-inspired) for more stable, detail-friendly denoising; used by CADE and SuperSimple by default
57
  - Seed Latent (MG_SeedLatent): fast, deterministic latent initializer aligned to VAE stride; supports pure-noise starts or image-mixed starts (encode + noise) to gently bias content; batch-ready and resolution-agnostic, pairs well with SuperSimple recommended latent sizes for reproducible pipelines
58
  - Muse Blend and Polish: directional post-mix and final low-frequency-preserving clean-up
59
- - SmartSeed (CADE Easy and SuperSimple): set `seed = 0` to auto-pick a good seed from a tiny low-step probe. Uses a low-discrepancy sweep, avoids speckles/overexposure, and, if available, leverages CLIP-Vision (with `reference_image`) and CLIPSeg focus text to favor semantically aligned candidates. Logs `Smart_seed_random: Start/End`.
60
  <b>I highly recommend working with SmartSeed.</b>
61
  - CADE2.5 pipeline does not just upscale the image, it iterates and adds small details, doing it carefully, at every stage.
62
 
@@ -141,7 +141,7 @@ Notes:
141
 
142
  12) Very often, the image in `step 3 is of very good quality`, but it usually lacks sharpness. But if you have a `weak system`, you can `limit yourself to 3 steps`.
143
 
144
- 13) SmartSeed (auto seed pick): set `seed = 0` in Easy or SuperSimple. The node will sample several candidate seeds and do a quick low‑step probe to choose a balanced one. You’ll see logs `Smart_seed_random: Start` and `Smart_seed_random: End. Seed is: <number>`. Use any non‑zero seed for fully deterministic runs.
145
 
146
  14) The 4th step sometimes saves the image for a long time, just wait for the end of the process, it depends on the initial resolution you set.
147
 
@@ -222,6 +222,7 @@ MagicNodes/
222
 
223
  ## CADE 2.5 (mg_cade25.py, mg_cade25_easy.py)
224
  - Deterministic preflight: CLIPSeg pinned to CPU; preview mask reset; noise tied to `iter_seed`
 
225
  - Encode/Decode: stride-aligned, with larger overlap for >2K to avoid artifacts
226
  - Polish mode (final hi-res refinement):
227
  - `polish_enable`, `polish_keep_low` (global form from reference), `polish_edge_lock`, `polish_sigma`
 
56
  - MGHybrid scheduler: hybrid Karras/Beta sigma stack with smooth tail blending and tiny schedule jitter (ZeSmart-inspired) for more stable, detail-friendly denoising; used by CADE and SuperSimple by default
57
  - Seed Latent (MG_SeedLatent): fast, deterministic latent initializer aligned to VAE stride; supports pure-noise starts or image-mixed starts (encode + noise) to gently bias content; batch-ready and resolution-agnostic, pairs well with SuperSimple recommended latent sizes for reproducible pipelines
58
  - Muse Blend and Polish: directional post-mix and final low-frequency-preserving clean-up
59
+ - SmartSeed (CADE Easy and SuperSimple): set `seed = 0` to auto-pick a good seed from a tiny low-step probe. Uses a low-discrepancy sweep, avoids speckles/overexposure, and, if available, leverages CLIP-Vision (with `reference_image`) and CLIPSeg focus text to favor semantically aligned candidates. Logs `Smart_seed_random: Start/End` and exposes the final choice on the `selected_seed` output.
60
  <b>I highly recommend working with SmartSeed.</b>
61
  - CADE2.5 pipeline does not just upscale the image, it iterates and adds small details, doing it carefully, at every stage.
62
 
 
141
 
142
  12) Very often, the image in `step 3 is of very good quality`, but it usually lacks sharpness. But if you have a `weak system`, you can `limit yourself to 3 steps`.
143
 
144
+ 13) SmartSeed (auto seed pick): set `seed = 0` in Easy or SuperSimple. The node will sample several candidate seeds and do a quick low‑step probe to choose a balanced one. You’ll see logs `Smart_seed_random: Start` and `Smart_seed_random: End. Seed is: <number>`, and the chosen value is available from the `selected_seed` output. Use any non‑zero seed for fully deterministic runs.
145
 
146
  14) The 4th step sometimes saves the image for a long time, just wait for the end of the process, it depends on the initial resolution you set.
147
 
 
222
 
223
  ## CADE 2.5 (mg_cade25.py, mg_cade25_easy.py)
224
  - Deterministic preflight: CLIPSeg pinned to CPU; preview mask reset; noise tied to `iter_seed`
225
+ - CADE Easy appends a `selected_seed` INT output after `mask_preview`; existing LATENT, IMAGE, and mask slots keep their order.
226
  - Encode/Decode: stride-aligned, with larger overlap for >2K to avoid artifacts
227
  - Polish mode (final hi-res refinement):
228
  - `polish_enable`, `polish_keep_low` (global form from reference), `polish_edge_lock`, `polish_sigma`
docs/EasyNodes.md CHANGED
@@ -40,13 +40,14 @@ Behavior
40
  - ControlFusion inside this node always relies on presets (no additional CF UI here) to keep the surface minimal.
41
 
42
  Outputs
43
- - `(LATENT, IMAGE)` from the final executed step (e.g., step 2 if `step_count=2`). No preview outputs.
 
44
 
45
  Quickstart
46
  1) Drop `MG_SuperSimple` into your graph under `MagicNodes/Easy`.
47
  2) Connect `model/positive/negative/vae/latent`, and a `control_net` module; optionally connect `reference_image` and `clip_vision`.
48
  3) Choose `step_count` (2/3/4). Leave `custom` Off to use pure presets, or enable it to apply your `seed/steps/cfg/denoise/sampler/scheduler/clipseg_text` across all steps (with Step 1 `denoise=1.0`).
49
- 4) Run. The node returns the final `(LATENT, IMAGE)` for the chosen depth.
50
 
51
  Notes
52
  - Presets are read from `pressets/mg_cade25.cfg` and `pressets/mg_controlfusion.cfg`. Keep them in UTF‑8 and prefer `$(ROOT)` over absolute paths.
 
40
  - ControlFusion inside this node always relies on presets (no additional CF UI here) to keep the surface minimal.
41
 
42
  Outputs
43
+ - `(LATENT, IMAGE, selected_seed)` from the final executed step (e.g., step 2 if `step_count=2`). No preview outputs.
44
+ - `selected_seed` is the seed CADE actually used after SmartSeed selection. With a non-zero fixed seed, it matches the requested seed.
45
 
46
  Quickstart
47
  1) Drop `MG_SuperSimple` into your graph under `MagicNodes/Easy`.
48
  2) Connect `model/positive/negative/vae/latent`, and a `control_net` module; optionally connect `reference_image` and `clip_vision`.
49
  3) Choose `step_count` (2/3/4). Leave `custom` Off to use pure presets, or enable it to apply your `seed/steps/cfg/denoise/sampler/scheduler/clipseg_text` across all steps (with Step 1 `denoise=1.0`).
50
+ 4) Run. The node returns the final `(LATENT, IMAGE, selected_seed)` for the chosen depth.
51
 
52
  Notes
53
  - Presets are read from `pressets/mg_cade25.cfg` and `pressets/mg_controlfusion.cfg`. Keep them in UTF‑8 and prefer `$(ROOT)` over absolute paths.
mod/easy/mg_cade25_easy.py CHANGED
@@ -2260,8 +2260,8 @@ class ComfyAdaptiveDetailEnhancer25:
2260
  },
2261
  }
2262
 
2263
- RETURN_TYPES = ("LATENT", "IMAGE", "INT", "FLOAT", "FLOAT", "IMAGE")
2264
- RETURN_NAMES = ("LATENT", "IMAGE", "steps", "cfg", "denoise", "mask_preview")
2265
  FUNCTION = "apply_cade2"
2266
  CATEGORY = "MagicNodes"
2267
 
@@ -3428,7 +3428,7 @@ class ComfyAdaptiveDetailEnhancer25:
3428
  except Exception:
3429
  pass
3430
 
3431
- return current_latent, image, int(current_steps), float(current_cfg), float(current_denoise), onnx_mask_img
3432
 
3433
 
3434
  # === Easy UI wrapper: show only top-level controls ===
@@ -3461,8 +3461,8 @@ class CADEEasyUI(ComfyAdaptiveDetailEnhancer25):
3461
  }
3462
 
3463
  # Easy outputs (hide steps/cfg/denoise)
3464
- RETURN_TYPES = ("LATENT", "IMAGE", "IMAGE")
3465
- RETURN_NAMES = ("LATENT", "IMAGE", "mask_preview")
3466
  FUNCTION = "apply_easy"
3467
 
3468
  def apply_easy(self,
@@ -3470,7 +3470,7 @@ class CADEEasyUI(ComfyAdaptiveDetailEnhancer25):
3470
  model, positive, negative, vae, latent,
3471
  seed, steps, cfg, denoise, sampler_name, scheduler,
3472
  clipseg_text="", reference_image=None, clip_vision=None, custom=False):
3473
- lat, img, _s, _c, _d, mask = super().apply_cade2(
3474
  model, vae, positive, negative, latent,
3475
  int(seed), int(steps), float(cfg), float(denoise),
3476
  str(sampler_name), str(scheduler), 0.0,
@@ -3478,11 +3478,11 @@ class CADEEasyUI(ComfyAdaptiveDetailEnhancer25):
3478
  reference_image=reference_image,
3479
  clip_vision=clip_vision,
3480
  )
3481
- return lat, img, mask
3482
 
3483
  # Show simpler outputs in Easy variant
3484
- RETURN_TYPES = ("LATENT", "IMAGE", "IMAGE")
3485
- RETURN_NAMES = ("LATENT", "IMAGE", "mask_preview")
3486
  FUNCTION = "apply_easy"
3487
 
3488
  def apply_easy(self,
@@ -3490,13 +3490,13 @@ class CADEEasyUI(ComfyAdaptiveDetailEnhancer25):
3490
  model, positive, negative, vae, latent,
3491
  seed, steps, cfg, denoise, sampler_name, scheduler,
3492
  clipseg_text=""):
3493
- lat, img, _s, _c, _d, mask = super().apply_cade2(
3494
  model, vae, positive, negative, latent,
3495
  int(seed), int(steps), float(cfg), float(denoise),
3496
  str(sampler_name), str(scheduler), 0.0,
3497
  preset_step=str(preset_step), custom_override=bool(custom), clipseg_text=str(clipseg_text),
3498
  )
3499
- return lat, img, mask
3500
 
3501
 
3502
 
 
2260
  },
2261
  }
2262
 
2263
+ RETURN_TYPES = ("LATENT", "IMAGE", "INT", "FLOAT", "FLOAT", "IMAGE", "INT")
2264
+ RETURN_NAMES = ("LATENT", "IMAGE", "steps", "cfg", "denoise", "mask_preview", "selected_seed")
2265
  FUNCTION = "apply_cade2"
2266
  CATEGORY = "MagicNodes"
2267
 
 
3428
  except Exception:
3429
  pass
3430
 
3431
+ return current_latent, image, int(current_steps), float(current_cfg), float(current_denoise), onnx_mask_img, int(seed)
3432
 
3433
 
3434
  # === Easy UI wrapper: show only top-level controls ===
 
3461
  }
3462
 
3463
  # Easy outputs (hide steps/cfg/denoise)
3464
+ RETURN_TYPES = ("LATENT", "IMAGE", "IMAGE", "INT")
3465
+ RETURN_NAMES = ("LATENT", "IMAGE", "mask_preview", "selected_seed")
3466
  FUNCTION = "apply_easy"
3467
 
3468
  def apply_easy(self,
 
3470
  model, positive, negative, vae, latent,
3471
  seed, steps, cfg, denoise, sampler_name, scheduler,
3472
  clipseg_text="", reference_image=None, clip_vision=None, custom=False):
3473
+ lat, img, _s, _c, _d, mask, selected_seed = super().apply_cade2(
3474
  model, vae, positive, negative, latent,
3475
  int(seed), int(steps), float(cfg), float(denoise),
3476
  str(sampler_name), str(scheduler), 0.0,
 
3478
  reference_image=reference_image,
3479
  clip_vision=clip_vision,
3480
  )
3481
+ return lat, img, mask, selected_seed
3482
 
3483
  # Show simpler outputs in Easy variant
3484
+ RETURN_TYPES = ("LATENT", "IMAGE", "IMAGE", "INT")
3485
+ RETURN_NAMES = ("LATENT", "IMAGE", "mask_preview", "selected_seed")
3486
  FUNCTION = "apply_easy"
3487
 
3488
  def apply_easy(self,
 
3490
  model, positive, negative, vae, latent,
3491
  seed, steps, cfg, denoise, sampler_name, scheduler,
3492
  clipseg_text=""):
3493
+ lat, img, _s, _c, _d, mask, selected_seed = super().apply_cade2(
3494
  model, vae, positive, negative, latent,
3495
  int(seed), int(steps), float(cfg), float(denoise),
3496
  str(sampler_name), str(scheduler), 0.0,
3497
  preset_step=str(preset_step), custom_override=bool(custom), clipseg_text=str(clipseg_text),
3498
  )
3499
+ return lat, img, mask, selected_seed
3500
 
3501
 
3502
 
mod/easy/mg_supersimple_easy.py CHANGED
@@ -13,7 +13,7 @@ control_net: ControlNet module for CF (required)
13
  reference_image/clip_vision: forwarded into CADE (optional)
14
  Outputs:
15
 
16
- (LATENT, IMAGE) from the final executed step
17
  """
18
 
19
 
@@ -62,8 +62,8 @@ class MG_SuperSimple:
62
  },
63
  }
64
 
65
- RETURN_TYPES = ("LATENT", "IMAGE")
66
- RETURN_NAMES = ("LATENT", "IMAGE")
67
  FUNCTION = "run"
68
 
69
  def _cade(self,
@@ -75,7 +75,7 @@ class MG_SuperSimple:
75
  clipseg_text: str,
76
  reference_image=None, clip_vision=None):
77
  # CADE core call mirrors CADEEasyUI -> apply_cade2
78
- lat, img, _s, _c, _d, _mask = _CADE().apply_cade2(
79
  model, vae, positive, negative, latent,
80
  int(seed), int(steps), float(cfg), float(denoise),
81
  str(sampler_name), str(scheduler), 0.0,
@@ -83,7 +83,7 @@ class MG_SuperSimple:
83
  clipseg_text=str(clipseg_text),
84
  reference_image=reference_image, clip_vision=clip_vision,
85
  )
86
- return lat, img
87
 
88
  def _cf(self,
89
  preset_step: str,
@@ -112,11 +112,12 @@ class MG_SuperSimple:
112
  cur_image = None
113
  cur_pos = positive
114
  cur_neg = negative
 
115
 
116
  try:
117
  # Step 1: CADE with Step 1 preset, denoise forced to 1.0
118
  denoise_step1 = 1.0
119
- lat1, img1 = self._cade(
120
  preset_step="Step 1",
121
  custom_override=bool(custom),
122
  model=model, vae=vae, positive=cur_pos, negative=cur_neg, latent=cur_latent,
@@ -141,7 +142,7 @@ class MG_SuperSimple:
141
  # If no external reference_image is provided, use the previous step image
142
  # so that reference_clean / CLIP-Vision gating can take effect.
143
  ref_img = reference_image if (reference_image is not None) else cur_image
144
- lat_i, img_i = self._cade(
145
  preset_step=f"Step {i}",
146
  custom_override=bool(custom),
147
  model=model, vae=vae, positive=cur_pos, negative=cur_neg, latent=cur_latent,
@@ -151,7 +152,7 @@ class MG_SuperSimple:
151
  reference_image=ref_img, clip_vision=clip_vision,
152
  )
153
  cur_latent, cur_image = lat_i, img_i
154
- return (cur_latent, cur_image)
155
  finally:
156
  # try to free memory on cancel or normal exit
157
  try:
 
13
  reference_image/clip_vision: forwarded into CADE (optional)
14
  Outputs:
15
 
16
+ (LATENT, IMAGE, selected_seed) from the final executed step
17
  """
18
 
19
 
 
62
  },
63
  }
64
 
65
+ RETURN_TYPES = ("LATENT", "IMAGE", "INT")
66
+ RETURN_NAMES = ("LATENT", "IMAGE", "selected_seed")
67
  FUNCTION = "run"
68
 
69
  def _cade(self,
 
75
  clipseg_text: str,
76
  reference_image=None, clip_vision=None):
77
  # CADE core call mirrors CADEEasyUI -> apply_cade2
78
+ lat, img, _s, _c, _d, _mask, selected_seed = _CADE().apply_cade2(
79
  model, vae, positive, negative, latent,
80
  int(seed), int(steps), float(cfg), float(denoise),
81
  str(sampler_name), str(scheduler), 0.0,
 
83
  clipseg_text=str(clipseg_text),
84
  reference_image=reference_image, clip_vision=clip_vision,
85
  )
86
+ return lat, img, selected_seed
87
 
88
  def _cf(self,
89
  preset_step: str,
 
112
  cur_image = None
113
  cur_pos = positive
114
  cur_neg = negative
115
+ selected_seed = int(seed)
116
 
117
  try:
118
  # Step 1: CADE with Step 1 preset, denoise forced to 1.0
119
  denoise_step1 = 1.0
120
+ lat1, img1, selected_seed = self._cade(
121
  preset_step="Step 1",
122
  custom_override=bool(custom),
123
  model=model, vae=vae, positive=cur_pos, negative=cur_neg, latent=cur_latent,
 
142
  # If no external reference_image is provided, use the previous step image
143
  # so that reference_clean / CLIP-Vision gating can take effect.
144
  ref_img = reference_image if (reference_image is not None) else cur_image
145
+ lat_i, img_i, selected_seed = self._cade(
146
  preset_step=f"Step {i}",
147
  custom_override=bool(custom),
148
  model=model, vae=vae, positive=cur_pos, negative=cur_neg, latent=cur_latent,
 
152
  reference_image=ref_img, clip_vision=clip_vision,
153
  )
154
  cur_latent, cur_image = lat_i, img_i
155
+ return (cur_latent, cur_image, selected_seed)
156
  finally:
157
  # try to free memory on cancel or normal exit
158
  try:
tests/test_smartseed_outputs.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ from pathlib import Path
3
+ import unittest
4
+
5
+
6
+ ROOT = Path(__file__).resolve().parents[1]
7
+
8
+
9
+ def _module_tree(relative_path: str) -> ast.Module:
10
+ return ast.parse((ROOT / relative_path).read_text(encoding="utf-8"))
11
+
12
+
13
+ def _class_node(tree: ast.Module, class_name: str) -> ast.ClassDef:
14
+ for node in tree.body:
15
+ if isinstance(node, ast.ClassDef) and node.name == class_name:
16
+ return node
17
+ raise AssertionError(f"class {class_name} not found")
18
+
19
+
20
+ def _assigned_tuple(class_node: ast.ClassDef, name: str) -> tuple[str, ...]:
21
+ for node in class_node.body:
22
+ if (
23
+ isinstance(node, ast.Assign)
24
+ and any(isinstance(target, ast.Name) and target.id == name for target in node.targets)
25
+ and isinstance(node.value, ast.Tuple)
26
+ ):
27
+ return tuple(
28
+ item.value
29
+ for item in node.value.elts
30
+ if isinstance(item, ast.Constant) and isinstance(item.value, str)
31
+ )
32
+ raise AssertionError(f"{name} tuple not found on {class_node.name}")
33
+
34
+
35
+ class SmartSeedOutputTests(unittest.TestCase):
36
+ def test_easy_cade_exposes_selected_seed_without_reordering_existing_outputs(self):
37
+ tree = _module_tree("mod/easy/mg_cade25_easy.py")
38
+ cade_easy = _class_node(tree, "CADEEasyUI")
39
+
40
+ self.assertEqual(
41
+ _assigned_tuple(cade_easy, "RETURN_NAMES"),
42
+ ("LATENT", "IMAGE", "mask_preview", "selected_seed"),
43
+ )
44
+ self.assertEqual(
45
+ _assigned_tuple(cade_easy, "RETURN_TYPES"),
46
+ ("LATENT", "IMAGE", "IMAGE", "INT"),
47
+ )
48
+
49
+ def test_base_cade_returns_selected_seed_for_advanced_wiring(self):
50
+ tree = _module_tree("mod/easy/mg_cade25_easy.py")
51
+ base_cade = _class_node(tree, "ComfyAdaptiveDetailEnhancer25")
52
+
53
+ self.assertEqual(
54
+ _assigned_tuple(base_cade, "RETURN_NAMES"),
55
+ ("LATENT", "IMAGE", "steps", "cfg", "denoise", "mask_preview", "selected_seed"),
56
+ )
57
+ self.assertEqual(
58
+ _assigned_tuple(base_cade, "RETURN_TYPES"),
59
+ ("LATENT", "IMAGE", "INT", "FLOAT", "FLOAT", "IMAGE", "INT"),
60
+ )
61
+
62
+ def test_supersimple_exposes_final_step_selected_seed(self):
63
+ tree = _module_tree("mod/easy/mg_supersimple_easy.py")
64
+ super_simple = _class_node(tree, "MG_SuperSimple")
65
+
66
+ self.assertEqual(
67
+ _assigned_tuple(super_simple, "RETURN_NAMES"),
68
+ ("LATENT", "IMAGE", "selected_seed"),
69
+ )
70
+ self.assertEqual(
71
+ _assigned_tuple(super_simple, "RETURN_TYPES"),
72
+ ("LATENT", "IMAGE", "INT"),
73
+ )
74
+
75
+
76
+ if __name__ == "__main__":
77
+ unittest.main()