Compare commits

...
12 Commits
13 changed files with 375 additions and 390 deletions
+47 -9
View File
@@ -19,6 +19,9 @@ class PreviewBridge:
"images": ("IMAGE",), "images": ("IMAGE",),
"image": ("STRING", {"default": ""}), "image": ("STRING", {"default": ""}),
}, },
"optional": {
"block": ("BOOLEAN", {"default": True, "label_on": "if_empty_mask", "label_off": "never", "tooltip": "is_empty_mask: If the mask is empty, the execution is stopped.\nnever: The execution is never stopped."})
},
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"}, "hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
} }
@@ -30,6 +33,8 @@ class PreviewBridge:
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
DESCRIPTION = "This is a feature that allows you to edit and send a Mask over a image.\nIf the block is set to 'is_empty_mask', the execution is stopped when the mask is empty."
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.output_dir = folder_paths.get_temp_directory() self.output_dir = folder_paths.get_temp_directory()
@@ -70,7 +75,7 @@ class PreviewBridge:
return image, mask.unsqueeze(0), ui_item return image, mask.unsqueeze(0), ui_item
def doit(self, images, image, unique_id, prompt=None, extra_pnginfo=None): def doit(self, images, image, unique_id, block=False, prompt=None, extra_pnginfo=None):
need_refresh = False need_refresh = False
if unique_id not in core.preview_bridge_cache: if unique_id not in core.preview_bridge_cache:
@@ -96,9 +101,20 @@ class PreviewBridge:
image = image2 image = image2
is_empty_mask = torch.all(mask == 0)
if block and is_empty_mask and core.is_execution_model_version_supported:
from comfy_execution.graph import ExecutionBlocker
result = ExecutionBlocker(None), ExecutionBlocker(None)
elif block and is_empty_mask:
print(f"[Impact Pack] PreviewBridge: ComfyUI is outdated - blocking feature is disabled.")
result = pixels, mask
else:
result = pixels, mask
return { return {
"ui": {"images": image}, "ui": {"images": image},
"result": (pixels, mask, ), "result": result,
} }
@@ -119,6 +135,8 @@ def decode_latent(latent, preview_method, vae_opt=None):
decoder_name = "taesdxl" decoder_name = "taesdxl"
elif preview_method == 'TAESD3': elif preview_method == 'TAESD3':
decoder_name = "taesd3" decoder_name = "taesd3"
elif preview_method == 'TAEF1':
decoder_name = "taef1"
if decoder_name: if decoder_name:
vae = nodes.VAELoader().load_vae(decoder_name)[0] vae = nodes.VAELoader().load_vae(decoder_name)[0]
@@ -170,14 +188,15 @@ class PreviewBridgeLatent:
return {"required": { return {"required": {
"latent": ("LATENT",), "latent": ("LATENT",),
"image": ("STRING", {"default": ""}), "image": ("STRING", {"default": ""}),
"preview_method": (["Latent2RGB-SD3", "Latent2RGB-SDXL", "Latent2RGB-SD15", "preview_method": (["Latent2RGB-FLUX.1",
"Latent2RGB-SDXL", "Latent2RGB-SD15", "Latent2RGB-SD3",
"Latent2RGB-SD-X4", "Latent2RGB-Playground-2.5", "Latent2RGB-SD-X4", "Latent2RGB-Playground-2.5",
"Latent2RGB-SC-Prior", "Latent2RGB-SC-B", "Latent2RGB-SC-Prior", "Latent2RGB-SC-B",
"Latent2RGB-FLUX.1", "TAEF1", "TAESDXL", "TAESD15", "TAESD3"],),
"TAESD3", "TAESDXL", "TAESD15"],),
}, },
"optional": { "optional": {
"vae_opt": ("VAE", ) "vae_opt": ("VAE", ),
"block": ("BOOLEAN", {"default": True, "label_on": "if_empty_mask", "label_off": "never", "tooltip": "is_empty_mask: If the mask is empty, the execution is stopped.\nnever: The execution is never stopped. Instead, it returns a white mask."})
}, },
"hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, "hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
} }
@@ -190,6 +209,8 @@ class PreviewBridgeLatent:
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
DESCRIPTION = "This is a feature that allows you to edit and send a Mask over a latent image.\nIf the block is set to 'is_empty_mask', the execution is stopped when the mask is empty."
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.output_dir = folder_paths.get_temp_directory() self.output_dir = folder_paths.get_temp_directory()
@@ -231,9 +252,9 @@ class PreviewBridgeLatent:
return image, mask, ui_item return image, mask, ui_item
def doit(self, latent, image, preview_method, vae_opt=None, unique_id=None, prompt=None, extra_pnginfo=None): def doit(self, latent, image, preview_method, vae_opt=None, block=False, unique_id=None, prompt=None, extra_pnginfo=None):
latent_channels = latent['samples'].shape[1] latent_channels = latent['samples'].shape[1]
preview_method_channels = 16 if 'SD3' in preview_method or 'SC-Prior' in preview_method or 'FLUX.1' in preview_method else 4 preview_method_channels = 16 if 'SD3' in preview_method or 'SC-Prior' in preview_method or 'FLUX.1' in preview_method or 'TAEF1' == preview_method else 4
if vae_opt is None and latent_channels != preview_method_channels: if vae_opt is None and latent_channels != preview_method_channels:
print(f"[PreviewBridgeLatent] The version of latent is not compatible with preview_method.\nSD3, SD1/SD2, SDXL, SC-Prior, SC-B and FLUX.1 are not compatible with each other.") print(f"[PreviewBridgeLatent] The version of latent is not compatible with preview_method.\nSD3, SD1/SD2, SDXL, SC-Prior, SC-B and FLUX.1 are not compatible with each other.")
@@ -260,10 +281,14 @@ class PreviewBridgeLatent:
del res_latent['noise_mask'] del res_latent['noise_mask']
else: else:
res_latent = latent res_latent = latent
is_empty_mask = True
else: else:
res_latent = latent.copy() res_latent = latent.copy()
res_latent['noise_mask'] = mask res_latent['noise_mask'] = mask
is_empty_mask = torch.all(mask == 1)
res_image = [path_item] res_image = [path_item]
else: else:
decoded_image = decode_latent(latent, preview_method, vae_opt) decoded_image = decode_latent(latent, preview_method, vae_opt)
@@ -285,11 +310,15 @@ class PreviewBridgeLatent:
'subfolder': 'PreviewBridge', 'subfolder': 'PreviewBridge',
'type': 'temp', 'type': 'temp',
}] }]
is_empty_mask = torch.all(mask == 1)
else: else:
mask = torch.ones(latent['samples'].shape[2:], dtype=torch.float32, device="cpu").unsqueeze(0) mask = torch.ones(latent['samples'].shape[2:], dtype=torch.float32, device="cpu").unsqueeze(0)
res = nodes.PreviewImage().save_images(decoded_image, filename_prefix="PreviewBridge/PBL-", prompt=prompt, extra_pnginfo=extra_pnginfo) res = nodes.PreviewImage().save_images(decoded_image, filename_prefix="PreviewBridge/PBL-", prompt=prompt, extra_pnginfo=extra_pnginfo)
res_image = res['ui']['images'] res_image = res['ui']['images']
is_empty_mask = True
path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', res_image[0]['filename']) path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', res_image[0]['filename'])
core.set_previewbridge_image(unique_id, path, res_image[0]) core.set_previewbridge_image(unique_id, path, res_image[0])
core.preview_bridge_image_id_map[image] = (path, res_image[0]) core.preview_bridge_image_id_map[image] = (path, res_image[0])
@@ -298,7 +327,16 @@ class PreviewBridgeLatent:
res_latent = latent res_latent = latent
if block and is_empty_mask and core.is_execution_model_version_supported:
from comfy_execution.graph import ExecutionBlocker
result = ExecutionBlocker(None), ExecutionBlocker(None)
elif block and is_empty_mask:
print(f"[Impact Pack] PreviewBridgeLatent: ComfyUI is outdated - blocking feature is disabled.")
result = res_latent, mask
else:
result = res_latent, mask
return { return {
"ui": {"images": res_image}, "ui": {"images": res_image},
"result": (res_latent, mask, ), "result": result,
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import configparser import configparser
import os import os
version_code = [7, 0] version_code = [7, 4, 2]
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
dependency_version = 22 dependency_version = 22
+2 -2
View File
@@ -237,7 +237,7 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather) noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
noise_mask = noise_mask.squeeze(3) noise_mask = noise_mask.squeeze(3)
if noise_mask_feather > 0: if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0] model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
if wildcard_opt is not None and wildcard_opt != "": if wildcard_opt is not None and wildcard_opt != "":
@@ -383,7 +383,7 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather) noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
noise_mask = noise_mask.squeeze(3) noise_mask = noise_mask.squeeze(3)
if noise_mask_feather > 0: if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0] model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
if wildcard_opt is not None and wildcard_opt != "": if wildcard_opt is not None and wildcard_opt != "":
+29 -19
View File
@@ -5,21 +5,31 @@ import impact.utils as utils
import torch import torch
from impact.core import SEG from impact.core import SEG
SAM_MODEL_TOOLTIP = {"tooltip": "Segment Anything Model for Silhouette Detection.\nBe sure to use the SAM_MODEL loaded through the SAMLoader (Impact) node as input."}
SAM_MODEL_TOOLTIP_OPTIONAL = {"tooltip": "[OPTIONAL]\nSegment Anything Model for Silhouette Detection.\nBe sure to use the SAM_MODEL loaded through the SAMLoader (Impact) node as input.\nGiven this input, it refines the rectangular areas detected by BBOX_DETECTOR into silhouette shapes through SAM.\nsam_model_opt takes priority over segm_detector_opt."}
MASK_HINT_THRESHOLD_TOOLTIP = "When detection_hint is mask-area, the mask of SEGS is used as a point hint for SAM (Segment Anything).\nIn this case, only the areas of the mask with brightness values equal to or greater than mask_hint_threshold are used as hints."
MASK_HINT_USE_NEGATIVE_TOOLTIP = "When detecting with SAM (Segment Anything), negative hints are applied as follows:\nSmall: When the SEGS is smaller than 10 pixels in size\nOuter: Sampling the image area outside the SEGS region at regular intervals"
DILATION_TOOLTIP = "Set the value to dilate the result mask. If the value is negative, it erodes the mask."
DETECTION_HINT_TOOLTIP = {"tooltip": "It is recommended to use only center-1.\nWhen refining the mask of SEGS with the SAM (Segment Anything) model, center-1 uses only the rectangular area of SEGS and a single point at the exact center as hints.\nOther options were added during the experimental stage and do not work well."}
BBOX_EXPANSION_TOOLTIP = "When performing SAM (Segment Anything) detection within the SEGS area, the rectangular area of SEGS is expanded and used as a hint."
class SAMDetectorCombined: class SAMDetectorCombined:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"sam_model": ("SAM_MODEL", ), "sam_model": ("SAM_MODEL", SAM_MODEL_TOOLTIP),
"segs": ("SEGS", ), "segs": ("SEGS", {"tooltip": "This is the segment information detected by the detector.\nIt refines the Mask through the SAM (Segment Anything) detector for all areas pointed to by SEGS, and combines all Masks to return as a single Mask."}),
"image": ("IMAGE", ), "image": ("IMAGE", {"tooltip": "It is assumed that segs contains only the information about the detected areas, and does not include the image. SAM (Segment Anything) operates by referencing this image."}),
"detection_hint": (["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area", "detection_hint": (["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area",
"mask-points", "mask-point-bbox", "none"],), "mask-points", "mask-point-bbox", "none"], DETECTION_HINT_TOOLTIP),
"dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), "dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1, "tooltip": DILATION_TOOLTIP}),
"threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01}), "threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Set the sensitivity threshold for the mask detected by SAM (Segment Anything). A higher value generates a more specific mask with a narrower range. For example, when pointing to a person's area, it might detect clothes, which is a narrower range, instead of the entire person."}),
"bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}), "bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1, "tooltip": BBOX_EXPANSION_TOOLTIP}),
"mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}), "mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": MASK_HINT_THRESHOLD_TOOLTIP}),
"mask_hint_use_negative": (["False", "Small", "Outter"], ) "mask_hint_use_negative": (["False", "Small", "Outter"], {"tooltip": MASK_HINT_USE_NEGATIVE_TOOLTIP})
} }
} }
@@ -38,16 +48,16 @@ class SAMDetectorSegmented:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"sam_model": ("SAM_MODEL", ), "sam_model": ("SAM_MODEL", SAM_MODEL_TOOLTIP),
"segs": ("SEGS", ), "segs": ("SEGS", {"tooltip": "This is the segment information detected by the detector.\nFor the SEGS region, the masks detected by SAM (Segment Anything) are created as a unified mask and a batch of individual masks."}),
"image": ("IMAGE", ), "image": ("IMAGE", {"tooltip": "It is assumed that segs contains only the information about the detected areas, and does not include the image. SAM (Segment Anything) operates by referencing this image."}),
"detection_hint": (["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area", "detection_hint": (["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area",
"mask-points", "mask-point-bbox", "none"],), "mask-points", "mask-point-bbox", "none"], DETECTION_HINT_TOOLTIP),
"dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), "dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1, "tooltip": DILATION_TOOLTIP}),
"threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01}), "threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01}),
"bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}), "bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1, "tooltip": BBOX_EXPANSION_TOOLTIP}),
"mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}), "mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": MASK_HINT_THRESHOLD_TOOLTIP}),
"mask_hint_use_negative": (["False", "Small", "Outter"], ) "mask_hint_use_negative": (["False", "Small", "Outter"], {"tooltip": MASK_HINT_USE_NEGATIVE_TOOLTIP})
} }
} }
@@ -199,7 +209,7 @@ class SimpleDetectorForEach:
}, },
"optional": { "optional": {
"post_dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), "post_dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}),
"sam_model_opt": ("SAM_MODEL", ), "sam_model_opt": ("SAM_MODEL", SAM_MODEL_TOOLTIP_OPTIONAL),
"segm_detector_opt": ("SEGM_DETECTOR", ), "segm_detector_opt": ("SEGM_DETECTOR", ),
} }
} }
@@ -311,7 +321,7 @@ class SimpleDetectorForAnimateDiff:
"optional": { "optional": {
"masking_mode": (["Pivot SEGS", "Combine neighboring frames", "Don't combine"],), "masking_mode": (["Pivot SEGS", "Combine neighboring frames", "Don't combine"],),
"segs_pivot": (["Combined mask", "1st frame mask"],), "segs_pivot": (["Combined mask", "1st frame mask"],),
"sam_model_opt": ("SAM_MODEL", ), "sam_model_opt": ("SAM_MODEL", SAM_MODEL_TOOLTIP_OPTIONAL),
"segm_detector_opt": ("SEGM_DETECTOR", ), "segm_detector_opt": ("SEGM_DETECTOR", ),
} }
} }
+3 -1
View File
@@ -78,6 +78,8 @@ class PreviewDetailerHookProvider:
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
NOT_IDEMPOTENT = True
def doit(self, quality, unique_id): def doit(self, quality, unique_id):
hook = hooks.PreviewDetailerHook(unique_id, quality) hook = hooks.PreviewDetailerHook(unique_id, quality)
return (hook, hook) return hook, hook
+29 -8
View File
@@ -62,10 +62,10 @@ class CLIPSegDetectorProvider:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"text": ("STRING", {"multiline": False}), "text": ("STRING", {"multiline": False, "tooltip": "Enter the targets to be detected, separated by commas"}),
"blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 7}), "blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 7, "tooltip": "Blurs the detected mask"}),
"threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.4}), "threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.4, "tooltip": "Detects only areas that are certain above the threshold."}),
"dilation_factor": ("INT", {"min": 0, "max": 10, "step": 1, "default": 4}), "dilation_factor": ("INT", {"min": 0, "max": 10, "step": 1, "default": 4, "tooltip": "Dilates the detected mask."}),
} }
} }
@@ -74,6 +74,8 @@ class CLIPSegDetectorProvider:
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
DESCRIPTION = "Provides a detection function using CLIPSeg, which generates masks based on text prompts.\nTo use this node, the CLIPSeg custom node must be installed."
def doit(self, text, blur, threshold, dilation_factor): def doit(self, text, blur, threshold, dilation_factor):
if "CLIPSeg" in nodes.NODE_CLASS_MAPPINGS: if "CLIPSeg" in nodes.NODE_CLASS_MAPPINGS:
return (core.BBoxDetectorBasedOnCLIPSeg(text, blur, threshold, dilation_factor), ) return (core.BBoxDetectorBasedOnCLIPSeg(text, blur, threshold, dilation_factor), )
@@ -87,8 +89,10 @@ class SAMLoader:
models = [x for x in folder_paths.get_filename_list("sams") if 'hq' not in x] models = [x for x in folder_paths.get_filename_list("sams") if 'hq' not in x]
return { return {
"required": { "required": {
"model_name": (models + ['ESAM'], ), "model_name": (models + ['ESAM'], {"tooltip": "The detection accuracy varies depending on the SAM model. ESAM can only be used if ComfyUI-YoloWorld-EfficientSAM is installed."}),
"device_mode": (["AUTO", "Prefer GPU", "CPU"],), "device_mode": (["AUTO", "Prefer GPU", "CPU"], {"tooltip": "AUTO: Only applicable when a GPU is available. It temporarily loads the SAM_MODEL into VRAM only when the detection function is used.\n"
"Prefer GPU: Tries to keep the SAM_MODEL on the GPU whenever possible. This can be used when there is sufficient VRAM available.\n"
"CPU: Always loads only on the CPU."}),
} }
} }
@@ -97,6 +101,8 @@ class SAMLoader:
CATEGORY = "ImpactPack" CATEGORY = "ImpactPack"
DESCRIPTION = "Load the SAM (Segment Anything) model. This can be used in places that utilize SAM detection functionality, such as SAMDetector or SimpleDetector.\nThe SAM detection functionality in Impact Pack must use the SAM_MODEL loaded through this node."
def load_model(self, model_name, device_mode="auto"): def load_model(self, model_name, device_mode="auto"):
if model_name == 'ESAM': if model_name == 'ESAM':
if 'ESAM_ModelLoader_Zho' not in nodes.NODE_CLASS_MAPPINGS: if 'ESAM_ModelLoader_Zho' not in nodes.NODE_CLASS_MAPPINGS:
@@ -238,11 +244,16 @@ class DetailerForEach:
else: else:
wmode, wildcard_chooser = None, None wmode, wildcard_chooser = None, None
if wmode in ['ASC', 'DSC']: if wmode in ['ASC', 'DSC', 'ASC-SIZE', 'DSC-SIZE']:
if wmode == 'ASC': if wmode == 'ASC':
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1])) ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1]))
else: elif wmode == 'DSC':
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1]), reverse=True) ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1]), reverse=True)
elif wmode == 'ASC-SIZE':
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]))
else: # wmode == 'DSC-SIZE'
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]), reverse=True)
else: else:
ordered_segs = segs[1] ordered_segs = segs[1]
@@ -291,6 +302,12 @@ class DetailerForEach:
# Negative Conditioning is placeholder such as FLUX.1 # Negative Conditioning is placeholder such as FLUX.1
cropped_negative = negative cropped_negative = negative
if wildcard_item and wildcard_item.strip() == '[SKIP]':
continue
if wildcard_item and wildcard_item.strip() == '[STOP]':
break
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size, enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size,
seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler, seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler,
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint, cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
@@ -1629,6 +1646,8 @@ class BitwiseAndMaskForEach:
CATEGORY = "ImpactPack/Operation" CATEGORY = "ImpactPack/Operation"
DESCRIPTION = "Retains only the overlapping areas between the masks included in base_segs and the mask regions of mask_segs. SEGS with no overlapping mask areas are filtered out."
def doit(self, base_segs, mask_segs): def doit(self, base_segs, mask_segs):
mask = core.segs_to_combined_mask(mask_segs) mask = core.segs_to_combined_mask(mask_segs)
mask = make_3d_mask(mask) mask = make_3d_mask(mask)
@@ -1650,6 +1669,8 @@ class SubtractMaskForEach:
CATEGORY = "ImpactPack/Operation" CATEGORY = "ImpactPack/Operation"
DESCRIPTION = "Removes only the overlapping areas between the masks included in base_segs and the mask regions of mask_segs. SEGS with no overlapping mask areas are filtered out."
def doit(self, base_segs, mask_segs): def doit(self, base_segs, mask_segs):
mask = core.segs_to_combined_mask(mask_segs) mask = core.segs_to_combined_mask(mask_segs)
mask = make_3d_mask(mask) mask = make_3d_mask(mask)
+12
View File
@@ -25,6 +25,8 @@ class MMDetLoader:
CATEGORY = "ImpactPack/Legacy" CATEGORY = "ImpactPack/Legacy"
DEPRECATED = True
def load_mmdet(self, model_name): def load_mmdet(self, model_name):
mmdet_path = folder_paths.get_full_path("mmdets", model_name) mmdet_path = folder_paths.get_full_path("mmdets", model_name)
model = mmdet_nodes.load_mmdet(mmdet_path) model = mmdet_nodes.load_mmdet(mmdet_path)
@@ -52,6 +54,8 @@ class BboxDetectorForEach:
CATEGORY = "ImpactPack/Legacy" CATEGORY = "ImpactPack/Legacy"
DEPRECATED = True
@staticmethod @staticmethod
def detect(bbox_model, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None): def detect(bbox_model, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
mmdet_results = mmdet_nodes.inference_bbox(bbox_model, image, threshold) mmdet_results = mmdet_nodes.inference_bbox(bbox_model, image, threshold)
@@ -102,6 +106,8 @@ class SegmDetectorCombined:
CATEGORY = "ImpactPack/Legacy" CATEGORY = "ImpactPack/Legacy"
DEPRECATED = True
def doit(self, segm_model, image, threshold, dilation): def doit(self, segm_model, image, threshold, dilation):
mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold) mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold)
segmasks = core.create_segmasks(mmdet_results) segmasks = core.create_segmasks(mmdet_results)
@@ -150,6 +156,8 @@ class SegmDetectorForEach:
CATEGORY = "ImpactPack/Legacy" CATEGORY = "ImpactPack/Legacy"
DEPRECATED = True
def doit(self, segm_model, image, threshold, dilation, crop_factor): def doit(self, segm_model, image, threshold, dilation, crop_factor):
mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold) mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold)
segmasks = core.create_segmasks(mmdet_results) segmasks = core.create_segmasks(mmdet_results)
@@ -190,6 +198,8 @@ class SegsMaskCombine:
CATEGORY = "ImpactPack/Legacy" CATEGORY = "ImpactPack/Legacy"
DEPRECATED = True
@staticmethod @staticmethod
def combine(segs, image): def combine(segs, image):
h = image.shape[1] h = image.shape[1]
@@ -226,6 +236,8 @@ class MaskPainter(nodes.PreviewImage):
CATEGORY = "ImpactPack/Legacy" CATEGORY = "ImpactPack/Legacy"
DEPRECATED = True
def save_painted_images(self, images, filename_prefix="impact-mask", def save_painted_images(self, images, filename_prefix="impact-mask",
prompt=None, extra_pnginfo=None, mask_image=None, image=None): prompt=None, extra_pnginfo=None, mask_image=None, image=None):
if image == "#placeholder" or image['image_hash'] != id(images): if image == "#placeholder" or image['image_hash'] != id(images):
+87 -63
View File
@@ -10,7 +10,6 @@ import re
import nodes import nodes
import traceback import traceback
class ImpactCompare: class ImpactCompare:
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
@@ -66,8 +65,8 @@ class ImpactConditionalBranch:
return { return {
"required": { "required": {
"cond": ("BOOLEAN",), "cond": ("BOOLEAN",),
"tt_value": (any_typ,), "tt_value": (any_typ,{"lazy": True}),
"ff_value": (any_typ,), "ff_value": (any_typ,{"lazy": True}),
}, },
} }
@@ -76,7 +75,13 @@ class ImpactConditionalBranch:
RETURN_TYPES = (any_typ, ) RETURN_TYPES = (any_typ, )
def doit(self, cond, tt_value, ff_value): def check_lazy_status(self, cond, tt_value=None, ff_value=None):
if cond and tt_value is None:
return ["tt_value"]
if not cond and ff_value is None:
return ["ff_value"]
def doit(self, cond, tt_value=None, ff_value=None):
if cond: if cond:
return (tt_value,) return (tt_value,)
else: else:
@@ -625,85 +630,104 @@ class ImpactControlBridge:
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
return {"required": { return {"required": {
"value": (any_typ,), "value": (any_typ,),
"mode": ("BOOLEAN", {"default": True, "label_on": "Active", "label_off": "Mute/Bypass"}), "mode": ("BOOLEAN", {"default": True, "label_on": "Active", "label_off": "Stop/Mute/Bypass"}),
"behavior": ("BOOLEAN", {"default": True, "label_on": "Mute", "label_off": "Bypass"}), "behavior": (["Stop", "Mute", "Bypass"], ),
}, },
"hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"} "hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}
} }
FUNCTION = "doit" FUNCTION = "doit"
CATEGORY = "ImpactPack/Logic/_for_test" CATEGORY = "ImpactPack/Logic"
RETURN_TYPES = (any_typ,) RETURN_TYPES = (any_typ,)
RETURN_NAMES = ("value",) RETURN_NAMES = ("value",)
OUTPUT_NODE = True OUTPUT_NODE = True
DESCRIPTION = ("When behavior is Stop and mode is active, the input value is passed directly to the output.\n"
"When behavior is Mute/Bypass and mode is active, the node connected to the output is changed to active state.\n"
"When behavior is Stop and mode is Stop/Mute/Bypass, the workflow execution of the current node is halted.\n"
"When behavior is Mute/Bypass and mode is Stop/Mute/Bypass, the node connected to the output is changed to Mute/Bypass state.")
@classmethod @classmethod
def IS_CHANGED(self, value, mode, behavior=True, unique_id=None, prompt=None, extra_pnginfo=None): def IS_CHANGED(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
# NOTE: extra_pnginfo is not populated for IS_CHANGED. if behavior == "Stop":
# so extra_pnginfo is useless in here return value, mode, behavior
try: else:
workflow = core.current_prompt['extra_data']['extra_pnginfo']['workflow'] # NOTE: extra_pnginfo is not populated for IS_CHANGED.
except: # so extra_pnginfo is useless in here
print(f"[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']") try:
return 0 workflow = core.current_prompt['extra_data']['extra_pnginfo']['workflow']
except:
print(f"[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']")
return 0
nodes, links = workflow_to_map(workflow) nodes, links = workflow_to_map(workflow)
next_nodes = [] next_nodes = []
for link in nodes[unique_id]['outputs'][0]['links']: for link in nodes[unique_id]['outputs'][0]['links']:
node_id = str(links[link][2]) node_id = str(links[link][2])
impact.utils.collect_non_reroute_nodes(nodes, links, next_nodes, node_id) impact.utils.collect_non_reroute_nodes(nodes, links, next_nodes, node_id)
return next_nodes return next_nodes
def doit(self, value, mode, behavior=True, unique_id=None, prompt=None, extra_pnginfo=None): def doit(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
global error_skip_flag global error_skip_flag
workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow']) if core.is_execution_model_version_supported:
from comfy_execution.graph import ExecutionBlocker
active_nodes = []
mute_nodes = []
bypass_nodes = []
for link in workflow_nodes[unique_id]['outputs'][0]['links']:
node_id = str(links[link][2])
next_nodes = []
impact.utils.collect_non_reroute_nodes(workflow_nodes, links, next_nodes, node_id)
for next_node_id in next_nodes:
node_mode = workflow_nodes[next_node_id]['mode']
if node_mode == 0:
active_nodes.append(next_node_id)
elif node_mode == 2:
mute_nodes.append(next_node_id)
elif node_mode == 4:
bypass_nodes.append(next_node_id)
if mode:
# active
should_be_active_nodes = mute_nodes + bypass_nodes
if len(should_be_active_nodes) > 0:
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'actives': list(should_be_active_nodes)})
nodes.interrupt_processing()
elif behavior:
# mute
should_be_mute_nodes = active_nodes + bypass_nodes
if len(should_be_mute_nodes) > 0:
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'mutes': list(should_be_mute_nodes)})
nodes.interrupt_processing()
else: else:
# bypass print("[Impact Pack] ImpactControlBridge: ComfyUI is outdated. The 'Stop' behavior cannot function properly.")
should_be_bypass_nodes = active_nodes + mute_nodes
if len(should_be_bypass_nodes) > 0:
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'bypasses': list(should_be_bypass_nodes)})
nodes.interrupt_processing()
return (value, ) if behavior == "Stop":
if mode:
return (value, )
else:
return (ExecutionBlocker(None), )
else:
workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow'])
active_nodes = []
mute_nodes = []
bypass_nodes = []
for link in workflow_nodes[unique_id]['outputs'][0]['links']:
node_id = str(links[link][2])
next_nodes = []
impact.utils.collect_non_reroute_nodes(workflow_nodes, links, next_nodes, node_id)
for next_node_id in next_nodes:
node_mode = workflow_nodes[next_node_id]['mode']
if node_mode == 0:
active_nodes.append(next_node_id)
elif node_mode == 2:
mute_nodes.append(next_node_id)
elif node_mode == 4:
bypass_nodes.append(next_node_id)
if mode:
# active
should_be_active_nodes = mute_nodes + bypass_nodes
if len(should_be_active_nodes) > 0:
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'actives': list(should_be_active_nodes)})
nodes.interrupt_processing()
elif behavior == "Mute" or behavior == True:
# mute
should_be_mute_nodes = active_nodes + bypass_nodes
if len(should_be_mute_nodes) > 0:
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'mutes': list(should_be_mute_nodes)})
nodes.interrupt_processing()
else:
# bypass
should_be_bypass_nodes = active_nodes + mute_nodes
if len(should_be_bypass_nodes) > 0:
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'bypasses': list(should_be_bypass_nodes)})
nodes.interrupt_processing()
return (value, )
class ImpactExecutionOrderController: class ImpactExecutionOrderController:
+1 -1
View File
@@ -98,7 +98,7 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
noise_mask = tensor_gaussian_blur_mask(noise_mask, noise_mask_feather) noise_mask = tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
noise_mask = noise_mask.squeeze(3) noise_mask = noise_mask.squeeze(3)
if noise_mask_feather > 0: if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0] model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
if control_net_wrapper is not None: if control_net_wrapper is not None:
+119 -265
View File
@@ -5,39 +5,25 @@ from impact.utils import *
from nodes import MAX_RESOLUTION from nodes import MAX_RESOLUTION
import nodes import nodes
from impact.impact_sampling import KSamplerWrapper, KSamplerAdvancedWrapper, separated_sample, impact_sample from impact.impact_sampling import KSamplerWrapper, KSamplerAdvancedWrapper, separated_sample, impact_sample
import comfy
class TiledKSamplerProvider: class TiledKSamplerProvider:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed to use for generating CPU noise for sampling."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "total sampling steps"}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "tooltip": "classifier free guidance value"}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"tooltip": "sampler"}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ), "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"tooltip": "noise schedule"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned."}),
"tile_width": ("INT", {"default": 512, "min": 320, "max": MAX_RESOLUTION, "step": 64}), "tile_width": ("INT", {"default": 512, "min": 320, "max": MAX_RESOLUTION, "step": 64, "tooltip": "Sets the width of the tile to be used in TiledKSampler."}),
"tile_height": ("INT", {"default": 512, "min": 320, "max": MAX_RESOLUTION, "step": 64}), "tile_height": ("INT", {"default": 512, "min": 320, "max": MAX_RESOLUTION, "step": 64, "tooltip": "Sets the height of the tile to be used in TiledKSampler."}),
"tiling_strategy": (["random", "padded", 'simple'], ), "tiling_strategy": (["random", "padded", 'simple'], {"tooltip": "Sets the tiling strategy for TiledKSampler."} ),
"basic_pipe": ("BASIC_PIPE", ) "basic_pipe": ("BASIC_PIPE", {"tooltip": "basic_pipe input for sampling"})
}} }}
TOOLTIPS = { OUTPUT_TOOLTIPS = ("sampler wrapper. (Can be used when generating a regional_prompt.)", )
"input": {
"seed": "Random seed to use for generating CPU noise for sampling.",
"steps": "total sampling steps",
"cfg": "classifier free guidance value",
"sampler_name": "sampler",
"scheduler": "noise schedule",
"denoise": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned.",
"tile_width": "Sets the width of the tile to be used in TiledKSampler.",
"tile_height": "Sets the height of the tile to be used in TiledKSampler.",
"tiling_strategy": "Sets the tiling strategy for TiledKSampler.",
"basic_pipe": "basic_pipe input for sampling",
},
"output": ("sampler wrapper. (Can be used when generating a regional_prompt.)", )
}
RETURN_TYPES = ("KSAMPLER",) RETURN_TYPES = ("KSAMPLER",)
FUNCTION = "doit" FUNCTION = "doit"
@@ -57,32 +43,20 @@ class KSamplerProvider:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed to use for generating CPU noise for sampling."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "total sampling steps"}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "tooltip": "classifier free guidance value"}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"tooltip": "sampler"}),
"scheduler": (core.SCHEDULERS, ), "scheduler": (core.SCHEDULERS, {"tooltip": "noise schedule"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned."}),
"basic_pipe": ("BASIC_PIPE", ) "basic_pipe": ("BASIC_PIPE", {"tooltip": "basic_pipe input for sampling"})
}, },
"optional": { "optional": {
"scheduler_func_opt": ("SCHEDULER_FUNC",), "scheduler_func_opt": ("SCHEDULER_FUNC", {"tooltip": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored."}),
} }
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("sampler wrapper. (Can be used when generating a regional_prompt.)",)
"input": {
"seed": "Random seed to use for generating CPU noise for sampling.",
"steps": "total sampling steps",
"cfg": "classifier free guidance value",
"sampler_name": "sampler",
"scheduler": "noise schedule",
"denoise": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned.",
"basic_pipe": "basic_pipe input for sampling",
"scheduler_func_opt": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored.",
},
"output": ("sampler wrapper. (Can be used when generating a regional_prompt.)", )
}
RETURN_TYPES = ("KSAMPLER",) RETURN_TYPES = ("KSAMPLER",)
FUNCTION = "doit" FUNCTION = "doit"
@@ -100,30 +74,19 @@ class KSamplerAdvancedProvider:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "toolip": "classifier free guidance value"}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"toolip": "sampler"}),
"scheduler": (core.SCHEDULERS, ), "scheduler": (core.SCHEDULERS, {"toolip": "noise schedule"}),
"sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), "sigma_factor": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "toolip": "Multiplier of noise schedule"}),
"basic_pipe": ("BASIC_PIPE", ) "basic_pipe": ("BASIC_PIPE", {"toolip": "basic_pipe input for sampling"})
}, },
"optional": { "optional": {
"sampler_opt": ("SAMPLER", ), "sampler_opt": ("SAMPLER", {"toolip": "[OPTIONAL] Uses the passed sampler instead of internal impact_sampler."}),
"scheduler_func_opt": ("SCHEDULER_FUNC",), "scheduler_func_opt": ("SCHEDULER_FUNC", {"toolip": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored."}),
} }
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("sampler wrapper. (Can be used when generating a regional_prompt.)", )
"input": {
"cfg": "classifier free guidance value",
"sampler_name": "sampler",
"scheduler": "noise schedule",
"sigma_factor": "Multiplier of noise schedule",
"basic_pipe": "basic_pipe input for sampling",
"sampler_opt": "[OPTIONAL] Uses the passed sampler instead of internal impact_sampler.",
"scheduler_func_opt": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored.",
},
"output": ("sampler wrapper. (Can be used when generating a regional_prompt.)", )
}
RETURN_TYPES = ("KSAMPLER_ADVANCED",) RETURN_TYPES = ("KSAMPLER_ADVANCED",)
FUNCTION = "doit" FUNCTION = "doit"
@@ -141,22 +104,14 @@ class TwoSamplersForMask:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"latent_image": ("LATENT", ), "latent_image": ("LATENT", {"tooltip": "input latent image"}),
"base_sampler": ("KSAMPLER", ), "base_sampler": ("KSAMPLER", {"tooltip": "Sampler to apply to the region outside the mask."}),
"mask_sampler": ("KSAMPLER", ), "mask_sampler": ("KSAMPLER", {"tooltip": "Sampler to apply to the masked region."}),
"mask": ("MASK", ) "mask": ("MASK", {"tooltip": "region mask"})
}, },
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("result latent", )
"input": {
"latent_image": "input latent image",
"base_sampler": "Sampler to apply to the region outside the mask.",
"mask_sampler": "Sampler to apply to the masked region.",
"mask": "region mask",
},
"output": ("result latent", )
}
RETURN_TYPES = ("LATENT", ) RETURN_TYPES = ("LATENT", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -182,30 +137,18 @@ class TwoAdvancedSamplersForMask:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed to use for generating CPU noise for sampling."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "total sampling steps"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned."}),
"samples": ("LATENT", ), "samples": ("LATENT", {"tooltip": "input latent image"}),
"base_sampler": ("KSAMPLER_ADVANCED", ), "base_sampler": ("KSAMPLER_ADVANCED", {"tooltip": "Sampler to apply to the region outside the mask."}),
"mask_sampler": ("KSAMPLER_ADVANCED", ), "mask_sampler": ("KSAMPLER_ADVANCED", {"tooltip": "Sampler to apply to the masked region."}),
"mask": ("MASK", ), "mask": ("MASK", {"tooltip": "region mask"}),
"overlap_factor": ("INT", {"default": 10, "min": 0, "max": 10000}) "overlap_factor": ("INT", {"default": 10, "min": 0, "max": 10000, "tooltip": "To smooth the seams of the region boundaries, expand the mask by the overlap_factor amount to overlap with other regions."})
}, },
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("result latent", )
"input": {
"seed": "Random seed to use for generating CPU noise for sampling.",
"steps": "total sampling steps",
"denoise": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned.",
"samples": "input latent image",
"base_sampler": "Sampler to apply to the region outside the mask.",
"mask_sampler": "Sampler to apply to the masked region.",
"mask": "region mask",
"overlap_factor": "To smooth the seams of the region boundaries, expand the mask by the overlap_factor amount to overlap with other regions.",
},
"output": ("result latent", )
}
RETURN_TYPES = ("LATENT", ) RETURN_TYPES = ("LATENT", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -227,23 +170,17 @@ class RegionalPrompt:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"mask": ("MASK", ), "mask": ("MASK", {"tooltip": "region mask"}),
"advanced_sampler": ("KSAMPLER_ADVANCED", ), "advanced_sampler": ("KSAMPLER_ADVANCED", {"tooltip": "sampler for specified region"}),
}, },
"optional": { "optional": {
"variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "variation_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Sets the extra seed to be used for noise variation."}),
"variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}), "variation_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Sets the strength of the noise variation."}),
"variation_method": (["linear", "slerp"],), "variation_method": (["linear", "slerp"], {"tooltip": "Sets how the original noise and extra noise are blended together."}),
} }
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("regional prompts. (Can be used in the RegionalSampler.)", )
"input": {
"mask": "region mask",
"advanced_sampler": "sampler for specified region",
},
"output": ("regional prompts. (Can be used in the RegionalSampler.)", )
}
RETURN_TYPES = ("REGIONAL_PROMPTS", ) RETURN_TYPES = ("REGIONAL_PROMPTS", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -260,16 +197,11 @@ class CombineRegionalPrompts:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"regional_prompts1": ("REGIONAL_PROMPTS", ), "regional_prompts1": ("REGIONAL_PROMPTS", {"tooltip": "input regional_prompts. (Connecting to the input slot increases the number of additional slots.)"}),
}, },
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("Combined REGIONAL_PROMPTS", )
"input": {
"regional_prompts1": "input regional_prompts. (Connecting to the input slot increases the number of additional slots.)",
},
"output": ("Combined REGIONAL_PROMPTS", )
}
RETURN_TYPES = ("REGIONAL_PROMPTS", ) RETURN_TYPES = ("REGIONAL_PROMPTS", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -289,16 +221,11 @@ class CombineConditionings:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"conditioning1": ("CONDITIONING", ), "conditioning1": ("CONDITIONING", { "tooltip": "input conditionings. (Connecting to the input slot increases the number of additional slots.)" }),
}, },
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("Combined conditioning", )
"input": {
"conditioning1": "input conditionings. (Connecting to the input slot increases the number of additional slots.)",
},
"output": ("Combined conditioning", )
}
RETURN_TYPES = ("CONDITIONING", ) RETURN_TYPES = ("CONDITIONING", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -318,16 +245,11 @@ class ConcatConditionings:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"conditioning1": ("CONDITIONING", ), "conditioning1": ("CONDITIONING", { "tooltip": "input conditionings. (Connecting to the input slot increases the number of additional slots.)" }),
}, },
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("Concatenated conditioning", )
"input": {
"conditioning1": "input conditionings. (Connecting to the input slot increases the number of additional slots.)",
},
"output": ("Concatenated conditioning", )
}
RETURN_TYPES = ("CONDITIONING", ) RETURN_TYPES = ("CONDITIONING", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -360,43 +282,25 @@ class RegionalSampler:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed to use for generating CPU noise for sampling."}),
"seed_2nd": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "seed_2nd": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Additional noise seed. The behavior is determined by seed_2nd_mode."}),
"seed_2nd_mode": (["ignore", "fixed", "seed+seed_2nd", "seed-seed_2nd", "increment", "decrement", "randomize"], ), "seed_2nd_mode": (["ignore", "fixed", "seed+seed_2nd", "seed-seed_2nd", "increment", "decrement", "randomize"], {"tooltip": "application method of seed_2nd. 1) ignore: Do not use seed_2nd. In the base only sampling stage, the seed is applied as a noise seed, and in the regional sampling stage, denoising is performed as it is without additional noise. 2) Others: In the base only sampling stage, the seed is applied as a noise seed, and once it is closed so that there is no leftover noise, new noise is added with seed_2nd and the regional samping stage is performed. a) fixed: Use seed_2nd as it is as an additional noise seed. b) seed+seed_2nd: Apply the value of seed+seed_2nd as an additional noise seed. c) seed-seed_2nd: Apply the value of seed-seed_2nd as an additional noise seed. d) increment: Not implemented yet. Same with fixed. e) decrement: Not implemented yet. Same with fixed. f) randomize: Not implemented yet. Same with fixed."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "total sampling steps"}),
"base_only_steps": ("INT", {"default": 2, "min": 0, "max": 10000}), "base_only_steps": ("INT", {"default": 2, "min": 0, "max": 10000, "tooltip": "total sampling steps"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned."}),
"samples": ("LATENT", ), "samples": ("LATENT", {"tooltip": "input latent image"}),
"base_sampler": ("KSAMPLER_ADVANCED", ), "base_sampler": ("KSAMPLER_ADVANCED", {"tooltip": "The sampler applied outside the area set by the regional_prompt."}),
"regional_prompts": ("REGIONAL_PROMPTS", ), "regional_prompts": ("REGIONAL_PROMPTS", {"tooltip": "The prompt applied to each region"}),
"overlap_factor": ("INT", {"default": 10, "min": 0, "max": 10000}), "overlap_factor": ("INT", {"default": 10, "min": 0, "max": 10000, "tooltip": "To smooth the seams of the region boundaries, expand the mask set in regional_prompts by the overlap_factor amount to overlap with other regions."}),
"restore_latent": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), "restore_latent": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled", "tooltip": "At each step, restore the noise outside the mask area to its original state, as per the principle of inpainting. This option is provided for backward compatibility, and it is recommended to always set it to true."}),
"additional_mode": (["DISABLE", "ratio additional", "ratio between"], {"default": "ratio between"}), "additional_mode": (["DISABLE", "ratio additional", "ratio between"], {"default": "ratio between", "tooltip": "..._sde or uni_pc and other special samplers are used, the region is not properly denoised, and it causes a phenomenon that destroys the overall harmony. To compensate for this, a recovery operation is performed using another sampler. This requires a longer time for sampling because a second sampling is performed at each step in each region using a special sampler. 1) DISABLE: Disable this feature. 2) ratio additional: After performing the denoise amount to be performed in the step with the sampler set in the region, the recovery sampler is additionally applied by the additional_sigma_ratio. If you use this option, the total denoise amount increases by additional_sigma_ratio. 3) ratio between: The denoise amount to be performed in the step with the sampler set in the region and the denoise amount to be applied to the recovery sampler are divided by additional_sigma_ratio, and denoise is performed for each denoise amount. If you use this option, the total denoise amount does not change."}),
"additional_sampler": (["AUTO", "euler", "heun", "heunpp2", "dpm_2", "dpm_fast", "dpmpp_2m", "ddpm"],), "additional_sampler": (["AUTO", "euler", "heun", "heunpp2", "dpm_2", "dpm_fast", "dpmpp_2m", "ddpm"], {"tooltip": "1) AUTO: Automatically set the recovery sampler. If the sampler is uni_pc, uni_pc_bh2, dpmpp_sde, dpmpp_sde_gpu, the dpm_fast sampler is selected If the sampler is dpmpp_2m_sde, dpmpp_2m_sde_gpu, dpmpp_3m_sde, dpmpp_3m_sde_gpu, the dpmpp_2m sampler is selected. 2) Others: Manually set the recovery sampler."}),
"additional_sigma_ratio": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01}), "additional_sigma_ratio": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Multiplier of noise schedule to be applied according to additional_mode."}),
}, },
"hidden": {"unique_id": "UNIQUE_ID"}, "hidden": {"unique_id": "UNIQUE_ID"},
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("result latent", )
"input": {
"seed": "Random seed to use for generating CPU noise for sampling.",
"seed_2nd": "Additional noise seed. The behavior is determined by seed_2nd_mode.",
"seed_2nd_mode": "application method of seed_2nd. 1) ignore: Do not use seed_2nd. In the base only sampling stage, the seed is applied as a noise seed, and in the regional sampling stage, denoising is performed as it is without additional noise. 2) Others: In the base only sampling stage, the seed is applied as a noise seed, and once it is closed so that there is no leftover noise, new noise is added with seed_2nd and the regional samping stage is performed. a) fixed: Use seed_2nd as it is as an additional noise seed. b) seed+seed_2nd: Apply the value of seed+seed_2nd as an additional noise seed. c) seed-seed_2nd: Apply the value of seed-seed_2nd as an additional noise seed. d) increment: Not implemented yet. Same with fixed. e) decrement: Not implemented yet. Same with fixed. f) randomize: Not implemented yet. Same with fixed.",
"steps": "total sampling steps",
"base_only_steps": "total sampling steps",
"denoise": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned.",
"samples": "input latent image",
"base_sampler": "The sampler applied outside the area set by the regional_prompt.",
"regional_prompts": "The prompt applied to each region",
"overlap_factor": "To smooth the seams of the region boundaries, expand the mask set in regional_prompts by the overlap_factor amount to overlap with other regions.",
"restore_latent": "At each step, restore the noise outside the mask area to its original state, as per the principle of inpainting. This option is provided for backward compatibility, and it is recommended to always set it to true.",
"additional_mode": "..._sde or uni_pc and other special samplers are used, the region is not properly denoised, and it causes a phenomenon that destroys the overall harmony. To compensate for this, a recovery operation is performed using another sampler. This requires a longer time for sampling because a second sampling is performed at each step in each region using a special sampler. 1) DISABLE: Disable this feature. 2) ratio additional: After performing the denoise amount to be performed in the step with the sampler set in the region, the recovery sampler is additionally applied by the additional_sigma_ratio. If you use this option, the total denoise amount increases by additional_sigma_ratio. 3) ratio between: The denoise amount to be performed in the step with the sampler set in the region and the denoise amount to be applied to the recovery sampler are divided by additional_sigma_ratio, and denoise is performed for each denoise amount. If you use this option, the total denoise amount does not change.",
"additional_sampler": "1) AUTO: Automatically set the recovery sampler. If the sampler is uni_pc, uni_pc_bh2, dpmpp_sde, dpmpp_sde_gpu, the dpm_fast sampler is selected If the sampler is dpmpp_2m_sde, dpmpp_2m_sde_gpu, dpmpp_3m_sde, dpmpp_3m_sde_gpu, the dpmpp_2m sampler is selected. 2) Others: Manually set the recovery sampler.",
"additional_sigma_ratio": "Multiplier of noise schedule to be applied according to additional_mode.",
},
"output": ("result latent", )
}
RETURN_TYPES = ("LATENT", ) RETURN_TYPES = ("LATENT", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -428,6 +332,10 @@ class RegionalSampler:
@staticmethod @staticmethod
def doit(seed, seed_2nd, seed_2nd_mode, steps, base_only_steps, denoise, samples, base_sampler, regional_prompts, overlap_factor, restore_latent, def doit(seed, seed_2nd, seed_2nd_mode, steps, base_only_steps, denoise, samples, base_sampler, regional_prompts, overlap_factor, restore_latent,
additional_mode, additional_sampler, additional_sigma_ratio, unique_id=None): additional_mode, additional_sampler, additional_sigma_ratio, unique_id=None):
samples = samples.copy()
samples['samples'] = comfy.sample.fix_empty_latent_channels(base_sampler.params[0], samples['samples'])
if restore_latent: if restore_latent:
latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']() latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']()
else: else:
@@ -543,44 +451,25 @@ class RegionalSamplerAdvanced:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), "add_noise": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled", "tooltip": "Whether to add noise"}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed to use for generating CPU noise for sampling."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "total sampling steps"}),
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000, "tooltip": "The starting step of the sampling to be applied at this node within the range of 'steps'."}),
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}), "end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000, "tooltip": "The step at which sampling applied at this node will stop within the range of steps (if greater than steps, sampling will continue only up to steps)."}),
"overlap_factor": ("INT", {"default": 10, "min": 0, "max": 10000}), "overlap_factor": ("INT", {"default": 10, "min": 0, "max": 10000, "tooltip": "To smooth the seams of the region boundaries, expand the mask set in regional_prompts by the overlap_factor amount to overlap with other regions."}),
"restore_latent": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), "restore_latent": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled", "tooltip": "At each step, restore the noise outside the mask area to its original state, as per the principle of inpainting. This option is provided for backward compatibility, and it is recommended to always set it to true."}),
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}), "return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled", "tooltip": "Whether to return the latent with noise remaining if the noise has not been completely removed according to the noise schedule, or to completely remove the noise before returning it."}),
"latent_image": ("LATENT", ), "latent_image": ("LATENT", {"tooltip": "input latent image"}),
"base_sampler": ("KSAMPLER_ADVANCED", ), "base_sampler": ("KSAMPLER_ADVANCED", {"tooltip": "The sampler applied outside the area set by the regional_prompt."}),
"regional_prompts": ("REGIONAL_PROMPTS", ), "regional_prompts": ("REGIONAL_PROMPTS", {"tooltip": "The prompt applied to each region"}),
"additional_mode": (["DISABLE", "ratio additional", "ratio between"], {"default": "ratio between"}), "additional_mode": (["DISABLE", "ratio additional", "ratio between"], {"default": "ratio between", "tooltip": "..._sde or uni_pc and other special samplers are used, the region is not properly denoised, and it causes a phenomenon that destroys the overall harmony. To compensate for this, a recovery operation is performed using another sampler. This requires a longer time for sampling because a second sampling is performed at each step in each region using a special sampler. 1) DISABLE: Disable this feature. 2) ratio additional: After performing the denoise amount to be performed in the step with the sampler set in the region, the recovery sampler is additionally applied by the additional_sigma_ratio. If you use this option, the total denoise amount increases by additional_sigma_ratio. 3) ratio between: The denoise amount to be performed in the step with the sampler set in the region and the denoise amount to be applied to the recovery sampler are divided by additional_sigma_ratio, and denoise is performed for each denoise amount. If you use this option, the total denoise amount does not change."}),
"additional_sampler": (["AUTO", "euler", "heun", "heunpp2", "dpm_2", "dpm_fast", "dpmpp_2m", "ddpm"],), "additional_sampler": (["AUTO", "euler", "heun", "heunpp2", "dpm_2", "dpm_fast", "dpmpp_2m", "ddpm"], {"tooltip": "1) AUTO: Automatically set the recovery sampler. If the sampler is uni_pc, uni_pc_bh2, dpmpp_sde, dpmpp_sde_gpu, the dpm_fast sampler is selected If the sampler is dpmpp_2m_sde, dpmpp_2m_sde_gpu, dpmpp_3m_sde, dpmpp_3m_sde_gpu, the dpmpp_2m sampler is selected. 2) Others: Manually set the recovery sampler."}),
"additional_sigma_ratio": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01}), "additional_sigma_ratio": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "Multiplier of noise schedule to be applied according to additional_mode."}),
}, },
"hidden": {"unique_id": "UNIQUE_ID"}, "hidden": {"unique_id": "UNIQUE_ID"},
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("result latent", )
"input": {
"add_noise": "Whether to add noise",
"noise_seed": "Random seed to use for generating CPU noise for sampling.",
"steps": "total sampling steps",
"start_at_step": "The starting step of the sampling to be applied at this node within the range of 'steps'.",
"end_at_step": "The step at which sampling applied at this node will stop within the range of steps (if greater than steps, sampling will continue only up to steps).",
"overlap_factor": "To smooth the seams of the region boundaries, expand the mask set in regional_prompts by the overlap_factor amount to overlap with other regions.",
"restore_latent": "At each step, restore the noise outside the mask area to its original state, as per the principle of inpainting. This option is provided for backward compatibility, and it is recommended to always set it to true.",
"return_with_leftover_noise": "Whether to return the latent with noise remaining if the noise has not been completely removed according to the noise schedule, or to completely remove the noise before returning it.",
"latent_image": "input latent image",
"base_sampler": "The sampler applied outside the area set by the regional_prompt.",
"regional_prompts": "The prompt applied to each region",
"additional_mode": "..._sde or uni_pc and other special samplers are used, the region is not properly denoised, and it causes a phenomenon that destroys the overall harmony. To compensate for this, a recovery operation is performed using another sampler. This requires a longer time for sampling because a second sampling is performed at each step in each region using a special sampler. 1) DISABLE: Disable this feature. 2) ratio additional: After performing the denoise amount to be performed in the step with the sampler set in the region, the recovery sampler is additionally applied by the additional_sigma_ratio. If you use this option, the total denoise amount increases by additional_sigma_ratio. 3) ratio between: The denoise amount to be performed in the step with the sampler set in the region and the denoise amount to be applied to the recovery sampler are divided by additional_sigma_ratio, and denoise is performed for each denoise amount. If you use this option, the total denoise amount does not change.",
"additional_sampler": "1) AUTO: Automatically set the recovery sampler. If the sampler is uni_pc, uni_pc_bh2, dpmpp_sde, dpmpp_sde_gpu, the dpm_fast sampler is selected If the sampler is dpmpp_2m_sde, dpmpp_2m_sde_gpu, dpmpp_3m_sde, dpmpp_3m_sde_gpu, the dpmpp_2m sampler is selected. 2) Others: Manually set the recovery sampler.",
"additional_sigma_ratio": "Multiplier of noise schedule to be applied according to additional_mode.",
},
"output": ("result latent", )
}
RETURN_TYPES = ("LATENT", ) RETURN_TYPES = ("LATENT", )
FUNCTION = "doit" FUNCTION = "doit"
@@ -591,6 +480,9 @@ class RegionalSamplerAdvanced:
def doit(add_noise, noise_seed, steps, start_at_step, end_at_step, overlap_factor, restore_latent, return_with_leftover_noise, latent_image, base_sampler, regional_prompts, def doit(add_noise, noise_seed, steps, start_at_step, end_at_step, overlap_factor, restore_latent, return_with_leftover_noise, latent_image, base_sampler, regional_prompts,
additional_mode, additional_sampler, additional_sigma_ratio, unique_id): additional_mode, additional_sampler, additional_sigma_ratio, unique_id):
new_latent_image = latent_image.copy()
new_latent_image['samples'] = comfy.sample.fix_empty_latent_channels(base_sampler.params[0], new_latent_image['samples'])
if restore_latent: if restore_latent:
latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']() latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']()
else: else:
@@ -606,7 +498,6 @@ class RegionalSamplerAdvanced:
end_at_step = min(steps, end_at_step) end_at_step = min(steps, end_at_step)
total = (end_at_step - start_at_step) * region_len total = (end_at_step - start_at_step) * region_len
new_latent_image = latent_image.copy()
base_latent_image = None base_latent_image = None
region_masks = {} region_masks = {}
@@ -681,35 +572,22 @@ class KSamplerBasicPipe:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": return {"required":
{"basic_pipe": ("BASIC_PIPE",), {"basic_pipe": ("BASIC_PIPE", {"tooltip": "basic_pipe input for sampling"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed to use for generating CPU noise for sampling."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "total sampling steps"}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "tooltip": "classifier free guidance value"}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"tooltip": "sampler"}),
"scheduler": (core.SCHEDULERS, ), "scheduler": (core.SCHEDULERS, {"tooltip": "noise schedule"}),
"latent_image": ("LATENT", ), "latent_image": ("LATENT", {"tooltip": "input latent image"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned."}),
}, },
"optional": "optional":
{ {
"scheduler_func_opt": ("SCHEDULER_FUNC", ), "scheduler_func_opt": ("SCHEDULER_FUNC", {"tooltip": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored."}),
} }
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("passthrough input basic_pipe", "result latent", "VAE in basic_pipe")
"input": {
"basic_pipe": "basic_pipe input for sampling",
"seed": "Random seed to use for generating CPU noise for sampling.",
"steps": "total sampling steps",
"cfg": "classifier free guidance value",
"sampler_name": "sampler",
"scheduler": "noise schedule",
"latent_image": "input latent image",
"denoise": "The amount of noise to remove. This amount is the noise added at the start, and the higher it is, the more the input latent will be modified before being returned.",
"scheduler_func_opt": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored.",
},
"output": ("passthrough input basic_pipe", "result latent", "VAE in basic_pipe")
}
RETURN_TYPES = ("BASIC_PIPE", "LATENT", "VAE") RETURN_TYPES = ("BASIC_PIPE", "LATENT", "VAE")
FUNCTION = "sample" FUNCTION = "sample"
@@ -727,41 +605,25 @@ class KSamplerAdvancedBasicPipe:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": return {"required":
{"basic_pipe": ("BASIC_PIPE",), {"basic_pipe": ("BASIC_PIPE", {"tooltip": "basic_pipe input for sampling"}),
"add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable"}), "add_noise": ("BOOLEAN", {"default": True, "label_on": "enable", "label_off": "disable", "tooltip": "Whether to add noise"}),
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Random seed to use for generating CPU noise for sampling."}),
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "total sampling steps"}),
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "tooltip": "classifier free guidance value"}),
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, ), "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"tooltip": "sampler"}),
"scheduler": (core.SCHEDULERS, ), "scheduler": (core.SCHEDULERS, {"tooltip": "noise schedule"}),
"latent_image": ("LATENT", ), "latent_image": ("LATENT", {"tooltip": "input latent image"}),
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000, "tooltip": "The starting step of the sampling to be applied at this node within the range of 'steps'."}),
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}), "end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000, "tooltip": "The step at which sampling applied at this node will stop within the range of steps (if greater than steps, sampling will continue only up to steps)."}),
"return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable"}), "return_with_leftover_noise": ("BOOLEAN", {"default": False, "label_on": "enable", "label_off": "disable", "tooltip": "Whether to return the latent with noise remaining if the noise has not been completely removed according to the noise schedule, or to completely remove the noise before returning it."}),
}, },
"optional": "optional":
{ {
"scheduler_func_opt": ("SCHEDULER_FUNC", ), "scheduler_func_opt": ("SCHEDULER_FUNC", {"tooltip": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored."}),
} }
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("passthrough input basic_pipe", "result latent", "VAE in basic_pipe")
"input": {
"basic_pipe": "basic_pipe input for sampling",
"add_noise": "Whether to add noise",
"noise_seed": "Random seed to use for generating CPU noise for sampling.",
"steps": "total sampling steps",
"cfg": "classifier free guidance value",
"sampler_name": "sampler",
"scheduler": "noise schedule",
"latent_image": "input latent image",
"start_at_step": "The starting step of the sampling to be applied at this node within the range of 'steps'.",
"end_at_step": "The step at which sampling applied at this node will stop within the range of steps (if greater than steps, sampling will continue only up to steps).",
"return_with_leftover_noise": "Whether to return the latent with noise remaining if the noise has not been completely removed according to the noise schedule, or to completely remove the noise before returning it.",
"scheduler_func_opt": "[OPTIONAL] Noise schedule generation function. If this is set, the scheduler widget will be ignored.",
},
"output": ("passthrough input basic_pipe", "result latent", "VAE in basic_pipe")
}
RETURN_TYPES = ("BASIC_PIPE", "LATENT", "VAE") RETURN_TYPES = ("BASIC_PIPE", "LATENT", "VAE")
FUNCTION = "sample" FUNCTION = "sample"
@@ -780,18 +642,12 @@ class GITSSchedulerFuncProvider:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"coeff": ("FLOAT", {"default": 1.20, "min": 0.80, "max": 1.50, "step": 0.05}), "coeff": ("FLOAT", {"default": 1.20, "min": 0.80, "max": 1.50, "step": 0.05, "tooltip": "coeff factor of GITS Scheduler"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "denoise amount for noise schedule"}),
} }
} }
TOOLTIPS = { OUTPUT_TOOLTIPS = ("Returns a function that generates a noise schedule using GITSScheduler. This can be used in place of a predetermined noise schedule to dynamically generate a noise schedule based on the steps.",)
"input": {
"coeff": "coeff factor of GITS Scheduler",
"denoise": "denoise amount for noise schedule",
},
"output": ("Returns a function that generates a noise schedule using GITSScheduler. This can be used in place of a predetermined noise schedule to dynamically generate a noise schedule based on the steps.",)
}
RETURN_TYPES = ("SCHEDULER_FUNC",) RETURN_TYPES = ("SCHEDULER_FUNC",)
CATEGORY = "ImpactPack/sampling" CATEGORY = "ImpactPack/sampling"
@@ -815,9 +671,7 @@ class NegativeConditioningPlaceholder:
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": {}} return {"required": {}}
TOOLTIPS = { OUTPUT_TOOLTIPS = ("This is a Placeholder for the FLUX model that does not use Negative Conditioning.",)
"output": ("This is a Placeholder for the FLUX model that does not use Negative Conditioning.",)
}
RETURN_TYPES = ("CONDITIONING",) RETURN_TYPES = ("CONDITIONING",)
CATEGORY = "ImpactPack/sampling" CATEGORY = "ImpactPack/sampling"
+29 -8
View File
@@ -14,7 +14,7 @@ import inspect
class GeneralSwitch: class GeneralSwitch:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
dyn_inputs = {"input1": (any_typ, {"lazy": True}), } dyn_inputs = {"input1": (any_typ, {"lazy": True, "tooltip": "Any input. When connected, one more input slot is added."}), }
if core.is_execution_model_version_supported(): if core.is_execution_model_version_supported():
stack = inspect.stack() stack = inspect.stack()
if stack[2].function == 'get_input_info' and stack[3].function == 'add_node': if stack[2].function == 'get_input_info' and stack[3].function == 'add_node':
@@ -22,8 +22,9 @@ class GeneralSwitch:
dyn_inputs[f"input{x}"] = (any_typ, {"lazy": True}) dyn_inputs[f"input{x}"] = (any_typ, {"lazy": True})
inputs = {"required": { inputs = {"required": {
"select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1}), "select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1, "tooltip": "The input number you want to output among the inputs"}),
"sel_mode": ("BOOLEAN", {"default": False, "label_on": "select_on_prompt", "label_off": "select_on_execution", "forceInput": False}), "sel_mode": ("BOOLEAN", {"default": False, "label_on": "select_on_prompt", "label_off": "select_on_execution", "forceInput": False,
"tooltip": "In the case of 'select_on_execution', the selection is dynamically determined at the time of workflow execution. 'select_on_prompt' is an option that exists for older versions of ComfyUI, and it makes the decision before the workflow execution."}),
}, },
"optional": dyn_inputs, "optional": dyn_inputs,
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"} "hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"}
@@ -33,6 +34,7 @@ class GeneralSwitch:
RETURN_TYPES = (any_typ, "STRING", "INT") RETURN_TYPES = (any_typ, "STRING", "INT")
RETURN_NAMES = ("selected_value", "selected_label", "selected_index") RETURN_NAMES = ("selected_value", "selected_label", "selected_index")
OUTPUT_TOOLTIPS = ("Output is generated only from the input chosen by the 'select' value.", "Slot label of the selected input slot", "Outputs the select value as is")
FUNCTION = "doit" FUNCTION = "doit"
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
@@ -143,25 +145,44 @@ class GeneralInversedSwitch:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
return {"required": { return {"required": {
"select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1}), "select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1, "tooltip": "The output number you want to send from the input"}),
"input": (any_typ,), "input": (any_typ, {"tooltip": "Any input. When connected, one more input slot is added."}),
}, },
"optional": { "optional": {
"sel_mode": ("BOOLEAN", {"default": False, "label_on": "select_on_prompt", "label_off": "select_on_execution", "forceInput": False}), "sel_mode": ("BOOLEAN", {"default": False, "label_on": "select_on_prompt", "label_off": "select_on_execution", "forceInput": False,
"tooltip": "In the case of 'select_on_execution', the selection is dynamically determined at the time of workflow execution. 'select_on_prompt' is an option that exists for older versions of ComfyUI, and it makes the decision before the workflow execution."}),
}, },
"hidden": {"prompt": "PROMPT", "unique_id": "UNIQUE_ID"},
} }
RETURN_TYPES = ByPassTypeTuple((any_typ, )) RETURN_TYPES = ByPassTypeTuple((any_typ, ))
OUTPUT_TOOLTIPS = ("Output occurs only from the output selected by the 'select' value.\nWhen slots are connected, additional slots are created.", )
FUNCTION = "doit" FUNCTION = "doit"
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
def doit(self, select, input, **kwargs): def doit(self, select, prompt, unique_id, input, **kwargs):
if core.is_execution_model_version_supported:
from comfy_execution.graph import ExecutionBlocker
else:
print("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
res = [] res = []
for i in range(0, select): # search max output count in prompt
cnt = 0
for x in prompt.values():
for y in x.get('inputs', {}).values():
if isinstance(y, list) and len(y) == 2:
if y[0] == unique_id:
cnt = max(cnt, y[1])
for i in range(0, cnt + 1):
if select == i+1: if select == i+1:
res.append(input) res.append(input)
elif core.is_execution_model_version_supported:
res.append(ExecutionBlocker(None))
else: else:
res.append(None) res.append(None)
+15 -12
View File
@@ -425,7 +425,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
def starts_with_regex(pattern, text): def starts_with_regex(pattern, text):
regex = re.compile(pattern) regex = re.compile(pattern)
return bool(regex.match(text)) return regex.match(text)
def split_to_dict(text): def split_to_dict(text):
@@ -507,18 +507,21 @@ def process_wildcard_for_segs(wildcard):
return 'LAB', WildcardChooserDict(items) return 'LAB', WildcardChooserDict(items)
elif starts_with_regex(r"\[(ASC|DSC|RND)\]", wildcard):
mode = wildcard[1:4]
items = split_string_with_sep(wildcard[5:])
if mode == 'RND':
random.shuffle(items)
return mode, WildcardChooser(items, True)
else:
return mode, WildcardChooser(items, False)
else: else:
return None, WildcardChooser([(None, wildcard)], False) match = starts_with_regex(r"\[(ASC-SIZE|DSC-SIZE|ASC|DSC|RND)\]", wildcard)
if match:
mode = match[1]
items = split_string_with_sep(wildcard[len(match[0]):])
if mode == 'RND':
random.shuffle(items)
return mode, WildcardChooser(items, True)
else:
return mode, WildcardChooser(items, False)
else:
return None, WildcardChooser([(None, wildcard)], False)
def wildcard_load(): def wildcard_load():
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-impact-pack" name = "comfyui-impact-pack"
description = "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler." description = "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
version = "7.0" version = "7.4.2"
license = { file = "LICENSE.txt" } license = { file = "LICENSE.txt" }
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"] dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]