Compare commits

...
9 Commits
11 changed files with 84 additions and 27 deletions
+1 -1
View File
@@ -237,7 +237,7 @@ app.registerExtension({
if(nodeData.name == "ImpactControlBridge") { if(nodeData.name == "ImpactControlBridge") {
const onConnectionsChange = nodeType.prototype.onConnectionsChange; const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) { nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
if(!link_info || this.inputs[0].type != '*') if(index != 0 || !link_info || this.inputs[0].type != '*')
return; return;
// assign type // assign type
+11 -7
View File
@@ -6,11 +6,15 @@ let refresh_btn2 = document.querySelector('button[title="Refresh widgets in node
let orig = refresh_btn.onclick; let orig = refresh_btn.onclick;
refresh_btn.onclick = function() { if(refresh_btn) {
orig(); refresh_btn.onclick = function() {
api.fetchApi('/impact/wildcards/refresh'); orig();
}; api.fetchApi('/impact/wildcards/refresh');
};
}
refresh_btn2.addEventListener('click', function() { if(refresh_btn2) {
api.fetchApi('/impact/wildcards/refresh'); refresh_btn2?.addEventListener('click', function() {
}); api.fetchApi('/impact/wildcards/refresh');
});
}
+41 -5
View File
@@ -19,6 +19,9 @@ class PreviewBridge:
"images": ("IMAGE",), "images": ("IMAGE",),
"image": ("STRING", {"default": ""}), "image": ("STRING", {"default": ""}),
}, },
"optional": {
"block": ("BOOLEAN", {"default": False, "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,
} }
@@ -179,7 +195,8 @@ class PreviewBridgeLatent:
"TAEF1", "TAESDXL", "TAESD15", "TAESD3"],), "TAEF1", "TAESDXL", "TAESD15", "TAESD3"],),
}, },
"optional": { "optional": {
"vae_opt": ("VAE", ) "vae_opt": ("VAE", ),
"block": ("BOOLEAN", {"default": False, "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"},
} }
@@ -192,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()
@@ -233,7 +252,7 @@ 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 or 'TAEF1' == 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
@@ -262,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)
@@ -287,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])
@@ -300,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, 3] version_code = [7, 4, 5]
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 != "":
+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
+16 -6
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:
@@ -1640,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)
@@ -1661,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)
+5 -1
View File
@@ -9,7 +9,6 @@ import impact.core as core
import re import re
import nodes import nodes
import traceback import traceback
from comfy_execution.graph import ExecutionBlocker
class ImpactCompare: class ImpactCompare:
@classmethod @classmethod
@@ -674,6 +673,11 @@ class ImpactControlBridge:
def doit(self, value, mode, behavior="Stop", 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
if core.is_execution_model_version_supported:
from comfy_execution.graph import ExecutionBlocker
else:
print("[Impact Pack] ImpactControlBridge: ComfyUI is outdated. The 'Stop' behavior cannot function properly.")
if behavior == "Stop": if behavior == "Stop":
if mode: if mode:
return (value, ) return (value, )
+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:
+2 -1
View File
@@ -1,6 +1,5 @@
from impact.utils import any_typ, ByPassTypeTuple, make_3d_mask from impact.utils import any_typ, ByPassTypeTuple, make_3d_mask
import comfy_extras.nodes_mask import comfy_extras.nodes_mask
from comfy_execution.graph import ExecutionBlocker
from nodes import MAX_RESOLUTION from nodes import MAX_RESOLUTION
import torch import torch
import comfy import comfy
@@ -166,6 +165,8 @@ class GeneralInversedSwitch:
def doit(self, select, prompt, unique_id, input, **kwargs): def doit(self, select, prompt, unique_id, input, **kwargs):
if core.is_execution_model_version_supported: if core.is_execution_model_version_supported:
from comfy_execution.graph import ExecutionBlocker from comfy_execution.graph import ExecutionBlocker
else:
print("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
res = [] res = []
+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.3" version = "7.4.5"
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"]