147 lines
6.1 KiB
Python
147 lines
6.1 KiB
Python
"""Optional post-processing for Flake Generate: ADetailer (Face Detailer) and
|
||
upscaling (#287, #288).
|
||
|
||
These features soft-depend on community/core node packs. Rather than importing
|
||
those packages directly (their import paths differ across installs), we resolve
|
||
the node classes from ComfyUI's global ``NODE_CLASS_MAPPINGS`` at call time and
|
||
raise a clear, actionable error if they are missing. Nothing here is imported at
|
||
module load, so Flakes still loads fine without the optional packs.
|
||
|
||
NOTE: external node signatures (especially Impact Pack's ``FaceDetailer.doit``)
|
||
vary between versions. The calls below target current mainline signatures and
|
||
may need adjustment for your installed versions — verify in a running ComfyUI.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from typing import Any
|
||
|
||
|
||
def _node_classes(*names: str) -> dict[str, Any]:
|
||
import nodes
|
||
ncm = getattr(nodes, "NODE_CLASS_MAPPINGS", {})
|
||
return {n: ncm.get(n) for n in names}
|
||
|
||
|
||
def run_face_detailer(
|
||
images,
|
||
model,
|
||
clip,
|
||
vae,
|
||
positive,
|
||
negative,
|
||
seed: int,
|
||
steps: int,
|
||
cfg: float,
|
||
sampler_name: str,
|
||
scheduler: str,
|
||
denoise: float = 0.4,
|
||
bbox_model: str = "bbox/face_yolov8m.pt",
|
||
):
|
||
"""Run an ADetailer-style face-detail pass using ComfyUI-Impact-Pack.
|
||
|
||
Returns the enhanced IMAGE tensor batch, or the input unchanged if the pass
|
||
cannot run. Requires ComfyUI-Impact-Pack (FaceDetailer) and Impact Subpack
|
||
(UltralyticsDetectorProvider).
|
||
"""
|
||
cls = _node_classes("FaceDetailer", "UltralyticsDetectorProvider", "SAMLoader")
|
||
FaceDetailer = cls["FaceDetailer"]
|
||
UltralyticsDetectorProvider = cls["UltralyticsDetectorProvider"]
|
||
SAMLoader = cls["SAMLoader"]
|
||
if FaceDetailer is None or UltralyticsDetectorProvider is None:
|
||
raise RuntimeError(
|
||
"ADetailer requires ComfyUI-Impact-Pack (FaceDetailer) and Impact "
|
||
"Subpack (UltralyticsDetectorProvider). Install them and restart ComfyUI."
|
||
)
|
||
|
||
bbox_detector = UltralyticsDetectorProvider().doit(bbox_model)[0]
|
||
sam_model_opt = None
|
||
if SAMLoader is not None:
|
||
try:
|
||
sam_model_opt = SAMLoader().load_model("sam_vit_b_01ec64.pth", "AUTO")[0]
|
||
except Exception:
|
||
sam_model_opt = None
|
||
|
||
result = FaceDetailer().doit(
|
||
images, model, clip, vae,
|
||
guide_size=512, guide_size_for=True, max_size=1024,
|
||
seed=seed, steps=steps, cfg=cfg, sampler_name=sampler_name, scheduler=scheduler,
|
||
positive=positive, negative=negative, denoise=denoise,
|
||
feather=5, noise_mask=True, force_inpaint=True,
|
||
bbox_threshold=0.5, bbox_dilation=10, bbox_crop_factor=3.0,
|
||
sam_detection_hint="center-1", sam_dilation=0, sam_threshold=0.93,
|
||
sam_bbox_expansion=0, sam_mask_hint_threshold=0.7,
|
||
sam_mask_hint_use_negative="False", drop_size=10,
|
||
bbox_detector=bbox_detector, wildcard="", cycle=1,
|
||
sam_model_opt=sam_model_opt,
|
||
)
|
||
# FaceDetailer.doit returns (image, cropped, cropped_enhanced,
|
||
# cropped_enhanced_alpha, mask, detailer_pipe, cnet_images). [0] is the
|
||
# composited result image.
|
||
enhanced = result[0] if isinstance(result, (tuple, list)) else result
|
||
logging.info("[flakes] ADetailer (FaceDetailer) pass complete")
|
||
return enhanced
|
||
|
||
|
||
def upscale_images(images, upscale_model_name: str = "", factor: float = 1.5):
|
||
"""Upscale an IMAGE batch (#288).
|
||
|
||
If ``upscale_model_name`` is set, run it through the core ESRGAN-style model
|
||
upscaler first; then rescale so the final size is ``factor`` × the original
|
||
(so the requested factor holds regardless of the model's native scale). Uses
|
||
core ComfyUI nodes; raises a clear error if they are unavailable.
|
||
"""
|
||
import comfy.utils
|
||
|
||
out = images
|
||
name = (upscale_model_name or "").strip()
|
||
if name and name.lower() not in ("(none)", "none", ""):
|
||
cls = _node_classes("UpscaleModelLoader", "ImageUpscaleWithModel")
|
||
loader = cls["UpscaleModelLoader"]
|
||
applier = cls["ImageUpscaleWithModel"]
|
||
if loader is None or applier is None:
|
||
raise RuntimeError(
|
||
"Upscaling with a model requires the core ComfyUI upscale nodes "
|
||
"(UpscaleModelLoader / ImageUpscaleWithModel)."
|
||
)
|
||
upscale_model = loader().load_model(name)[0]
|
||
out = applier().upscale(upscale_model, out)[0]
|
||
|
||
if factor and abs(float(factor) - 1.0) > 1e-3:
|
||
h, w = int(images.shape[1]), int(images.shape[2])
|
||
target_w = max(1, int(round(w * float(factor))))
|
||
target_h = max(1, int(round(h * float(factor))))
|
||
samples = out.movedim(-1, 1)
|
||
samples = comfy.utils.common_upscale(samples, target_w, target_h, "lanczos", "disabled")
|
||
out = samples.movedim(1, -1)
|
||
|
||
logging.info("[flakes] upscale pass complete (model=%s factor=%.2f)", name or "none", float(factor))
|
||
return out
|
||
|
||
|
||
def apply_ipadapter_faceid(model, image, weight: float = 0.8, preset: str = "FACEID PLUS V2"):
|
||
"""Apply IPAdapter FaceID to a model for consistent-face generation (#289).
|
||
|
||
Soft-depends on ComfyUI_IPAdapter_plus, resolved from NODE_CLASS_MAPPINGS.
|
||
Returns the patched model. SDXL-first; requires FaceID models + insightface.
|
||
"""
|
||
cls = _node_classes(
|
||
"IPAdapterUnifiedLoaderFaceID", "IPAdapterUnifiedLoader",
|
||
"IPAdapterFaceID", "IPAdapterAdvanced", "IPAdapter",
|
||
)
|
||
loader = cls["IPAdapterUnifiedLoaderFaceID"] or cls["IPAdapterUnifiedLoader"]
|
||
applier = cls["IPAdapterFaceID"] or cls["IPAdapterAdvanced"] or cls["IPAdapter"]
|
||
if loader is None or applier is None:
|
||
raise RuntimeError(
|
||
"Flake IPAdapter requires ComfyUI_IPAdapter_plus (and FaceID models + "
|
||
"insightface). Install it and restart ComfyUI."
|
||
)
|
||
|
||
loaded = loader().load_models(model, preset)
|
||
patched_model, ipadapter = loaded[0], loaded[1]
|
||
|
||
applied = applier().apply_ipadapter(patched_model, ipadapter, image=image, weight=weight)
|
||
new_model = applied[0] if isinstance(applied, (tuple, list)) else applied
|
||
logging.info("[flakes] IPAdapter FaceID applied (preset=%s weight=%.2f)", preset, float(weight))
|
||
return new_model
|