aleph65's picture
Archive flux-redux experiment: final writeup, workflows, inputs, scripts, research, v4 outputs, comparison sheets, Claude memories
478cb8f verified
Raw
History Blame Contribute Delete
11.1 kB
#!/usr/bin/env python3
"""Generate flux-v2 workflow JSONs: portrait pre-pass, saved crops, hires group, bfl guidance fix."""
import build_workflows as B
from build_workflows import G, DEFS, CORE, BYPASS, flux_loaders, redux_branch
ESSENTIALS = {"cnr_id": "comfyui_essentials"}
DEFS["ImageResize+"] = ([("image", "IMAGE")],
[("IMAGE", "IMAGE"), ("width", "INT"), ("height", "INT")],
ESSENTIALS, [310, 170])
DEFS["LatentUpscaleBy"] = ([("samples", "LATENT")], [("LATENT", "LATENT")], CORE, [290, 82])
W, H = 896, 1152 # portrait bucket, both /64
HW, HH = 1344, 1728 # hires 1.5x
INPUT_SPEC = """
**Optimal inputs** (both refs): 3:4 portrait, 1152x1536, sRGB 8-bit JPEG q90+/PNG, no
watermarks/text/borders, EXIF baked. Style ref: keep the style-defining content in the vertical
middle (sigclip center-crops to a square β€” top/bottom ~12% are discarded). Composition ref:
clear fg/bg depth separation. Any other input still works β€” the pre-pass normalizes to 896x1152
(fill/crop) and the exact crop + depth map are saved next to the outputs for auditing."""
def comp_prepass(g, x, y, save_prefix):
"""LoadImage -> ImageResize+ 896x1152 fill/crop -> depth; saves crop + depth map."""
ci = g.add("LoadImage", ["composition_ref.png", "image"], pos=(x, y),
title="Composition reference (Image B)")
rs = g.add("ImageResize+", [W, H, "lanczos", "fill / crop", "always", 0],
inputs={"image": (ci, 0)}, pos=(x + 420, y + 30), title="Normalize to 896x1152")
g.add("SaveImage", [f"{save_prefix}/inputs/comp-crop"], inputs={"images": (rs, 0)},
pos=(x + 420, y + 240), size=[260, 270], title="Save crop (audit)")
depth = g.add("DepthAnythingV2Preprocessor", ["depth_anything_v2_vitl.pth", W],
inputs={"image": (rs, 0)}, pos=(x + 840, y + 30))
g.add("SaveImage", [f"{save_prefix}/inputs/depth"], inputs={"images": (depth, 0)},
pos=(x + 840, y + 240), size=[260, 270], title="Save depth map (audit)")
return ci, rs, depth
def build_v2_style_composition():
g = G()
g.add("MarkdownNote", [f"""## FLUX Redux + ControlNet β€” style + composition (v2)
v2 changes vs v1: portrait pre-pass (any input β†’ exact **896x1152** fill/crop, so depth map ==
latent, zero padding), saved input crops + depth maps for auditing, steps 32, and a bypassed
**HIRES** group (1.5x latent upscale β†’ second pass at denoise 0.30 with Redux conditioning
carried, ControlNet released β€” enable for ~1344x1728 finals; keeps aesthetic, adds detail).
Models & knobs: unchanged from v1 (see `flux-redux-style-composition.json` notes). Redux **multiply 0.35** β€” NEVER attn_bias with a ControlNet: its attention mask never reaches
the CN branch (code-verified), so base and CN fight and bodies deform. Union-Pro-2.0 depth
0.7 / end 0.8 (drop end to 0.6 for more freedom), FluxGuidance **3.0** (sweep-validated; 2.5 softer/filmic, 3.5 punchier), euler/simple/32/CFG 1.
Bypassed groups: CANNY stack (0.35), ReduxAdvanced alt, turbo fast-preview, HIRES.
{INPUT_SPEC}"""], pos=(-1340, -180), size=[580, 620])
unet, clip, vae = flux_loaders(g)
s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420)
txt = g.add("CLIPTextEncode", ['A photograph of a beautiful woman posing naturally. Her body is anatomically correct, with well-proportioned limbs, natural relaxed hands, and a balanced, graceful posture. The image is sharp and coherent, with realistic skin texture and lighting that matches the scene.'], inputs={"clip": (clip, 0)}, pos=(-300, 60),
title="Prompt (generic anatomy default β€” edit or clear)")
guid = g.add("FluxGuidance", [3.0], inputs={"conditioning": (txt, 0)}, pos=(140, 60))
neg = g.add("CLIPTextEncode", ['deformed anatomy, extra limbs, missing limbs, fused or extra fingers, malformed hands, twisted joints, distorted face, mutated body, disfigured, blurry, lowres, jpeg artifacts, watermark, text, logo, oversaturated, plastic skin'], inputs={"clip": (clip, 0)}, pos=(-300, 250),
title="Negative (INERT at CFG 1.0 β€” activates only if CFG > 1)")
apply_style = g.add("StyleModelApply", [0.35, "multiply"],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision_output": (cv_enc, 0)}, pos=(140, 300))
radv = g.add("ReduxAdvanced", [3, "area", "center crop (square)", 1.0, 0.1],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision": (cv_loader, 0), "image": (s_img, 0)},
pos=(140, 520), mode=BYPASS, title="ALT: ReduxAdvanced (rewire to use)")
ci, rs, depth = comp_prepass(g, -720, 1000, "flux-v2/style-comp")
canny = g.add("Canny", [0.2, 0.5], inputs={"image": (rs, 0)}, pos=(-300, 1560),
mode=BYPASS, title="OPTIONAL: canny edges")
cn_loader = g.add("ControlNetLoader", ["FLUX.1-dev-ControlNet-Union-Pro-2.0.safetensors"],
pos=(300, 950))
cn_depth = g.add("ControlNetApplySD3", [0.7, 0.0, 0.8],
inputs={"positive": (apply_style, 0), "negative": (neg, 0),
"control_net": (cn_loader, 0), "vae": (vae, 0), "image": (depth, 0)},
pos=(700, 300), title="Apply ControlNet β€” DEPTH")
cn_canny = g.add("ControlNetApplySD3", [0.35, 0.0, 0.6],
inputs={"positive": (cn_depth, 0), "negative": (cn_depth, 1),
"control_net": (cn_loader, 0), "vae": (vae, 0), "image": (canny, 0)},
pos=(700, 560), mode=BYPASS, title="OPTIONAL: Apply ControlNet β€” CANNY (stack)")
turbo = g.add("LoraLoaderModelOnly", ["flux1-turbo-alpha.safetensors", 1.0],
inputs={"model": (unet, 0)}, pos=(-300, -160), mode=BYPASS,
title="FAST PREVIEW: turbo LoRA (set steps 8)")
msf = g.add("ModelSamplingFlux", [1.15, 0.5, W, H], inputs={"model": (turbo, 0)}, pos=(140, -160))
latent = g.add("EmptySD3LatentImage", [W, H, 1], pos=(700, 950))
ks = g.add("KSampler", [0, "randomize", 32, 1.0, "euler", "simple", 1.0],
inputs={"model": (msf, 0), "positive": (cn_canny, 0), "negative": (cn_canny, 1),
"latent_image": (latent, 0)}, pos=(1120, 300))
dec = g.add("VAEDecode", inputs={"samples": (ks, 0), "vae": (vae, 0)}, pos=(1460, 300))
g.add("SaveImage", ["flux-v2/style-comp"], inputs={"images": (dec, 0)}, pos=(1460, 420))
# HIRES group (all bypassed): 1.5x latent -> refine at denoise 0.30, Redux kept, CN released
up = g.add("LatentUpscaleBy", ["bislerp", 1.5], inputs={"samples": (ks, 0)},
pos=(1120, 660), mode=BYPASS, title="HIRES: 1.5x latent")
msf2 = g.add("ModelSamplingFlux", [1.15, 0.5, HW, HH], inputs={"model": (turbo, 0)},
pos=(1120, 800), mode=BYPASS, title="HIRES: shift @1344x1728")
ks2 = g.add("KSampler", [0, "randomize", 32, 1.0, "euler", "simple", 0.30],
inputs={"model": (msf2, 0), "positive": (apply_style, 0), "negative": (neg, 0),
"latent_image": (up, 0)}, pos=(1460, 660), mode=BYPASS,
title="HIRES: refine pass (denoise 0.30)")
dec2 = g.add("VAEDecode", inputs={"samples": (ks2, 0), "vae": (vae, 0)},
pos=(1800, 660), mode=BYPASS)
g.add("SaveImage", ["flux-v2/style-comp-hires"], inputs={"images": (dec2, 0)},
pos=(1800, 780), mode=BYPASS, title="HIRES: save")
g.group("STYLE REFERENCE β€” Redux", -740, 340, 1220, 700, "#3f789e")
g.group("COMPOSITION REFERENCE β€” 896x1152 pre-pass", -740, 900, 1500, 840, "#8f5b34")
g.group("SAMPLING", 1080, 200, 800, 380, "#444")
g.group("HIRES (bypassed β€” enable all 5)", 1080, 600, 1100, 520, "#4a6b4a")
g.dump(f"{B.OUT}/flux-v2-redux-style-composition.json")
def build_v2_bfl_lora():
g = G()
g.add("MarkdownNote", [f"""## FLUX Redux + official BFL Depth LoRA (v2 β€” A/B variant)
v2 changes vs v1: portrait pre-pass (896x1152 fill/crop β€” critical here: with latent-concat
conditioning any padding becomes image content), saved crop + depth map, steps 32, and
**FluxGuidance 10.0 + LoRA 0.85** (BFL spec β€” the LoRA is distilled at guidance 10; earlier
lower-guidance results were an artifact of the attn_bias bug). Redux multiply 0.35, never
attn_bias (see style-composition notes). No canny stacking possible in this method.
{INPUT_SPEC}"""], pos=(-1340, -180), size=[580, 560])
unet, clip, vae = flux_loaders(g)
s_img, cv_loader, cv_enc, sm_loader = redux_branch(g, -720, 420)
txt = g.add("CLIPTextEncode", ['A photograph of a beautiful woman posing naturally. Her body is anatomically correct, with well-proportioned limbs, natural relaxed hands, and a balanced, graceful posture. The image is sharp and coherent, with realistic skin texture and lighting that matches the scene.'], inputs={"clip": (clip, 0)}, pos=(-300, 60),
title="Prompt (generic anatomy default β€” edit or clear)")
guid = g.add("FluxGuidance", [10.0], inputs={"conditioning": (txt, 0)}, pos=(140, 60))
neg = g.add("CLIPTextEncode", ['deformed anatomy, extra limbs, missing limbs, fused or extra fingers, malformed hands, twisted joints, distorted face, mutated body, disfigured, blurry, lowres, jpeg artifacts, watermark, text, logo, oversaturated, plastic skin'], inputs={"clip": (clip, 0)}, pos=(-300, 250),
title="Negative (INERT at CFG 1.0 β€” activates only if CFG > 1)")
apply_style = g.add("StyleModelApply", [0.35, "multiply"],
inputs={"conditioning": (guid, 0), "style_model": (sm_loader, 0),
"clip_vision_output": (cv_enc, 0)}, pos=(140, 300))
ci, rs, depth = comp_prepass(g, -720, 1000, "flux-v2/style-comp-bfl")
ip2p = g.add("InstructPixToPixConditioning",
inputs={"positive": (apply_style, 0), "negative": (neg, 0), "vae": (vae, 0),
"pixels": (depth, 0)}, pos=(700, 300))
dlora = g.add("LoraLoaderModelOnly", ["flux1-depth-dev-lora.safetensors", 0.85],
inputs={"model": (unet, 0)}, pos=(-300, -160), title="BFL Depth LoRA")
msf = g.add("ModelSamplingFlux", [1.15, 0.5, W, H], inputs={"model": (dlora, 0)}, pos=(140, -160))
ks = g.add("KSampler", [0, "randomize", 32, 1.0, "euler", "simple", 1.0],
inputs={"model": (msf, 0), "positive": (ip2p, 0), "negative": (ip2p, 1),
"latent_image": (ip2p, 2)}, pos=(1120, 300))
dec = g.add("VAEDecode", inputs={"samples": (ks, 0), "vae": (vae, 0)}, pos=(1460, 300))
g.add("SaveImage", ["flux-v2/style-comp-bfl"], inputs={"images": (dec, 0)}, pos=(1460, 420))
g.group("STYLE REFERENCE β€” Redux", -740, 340, 1220, 700, "#3f789e")
g.group("COMPOSITION β€” 896x1152 pre-pass + BFL Depth LoRA", -740, 900, 1500, 620, "#8f5b34")
g.dump(f"{B.OUT}/flux-v2-redux-style-composition-bfl-lora.json")
build_v2_style_composition()
build_v2_bfl_lora()