Compare commits

...
4 Commits
Author SHA1 Message Date
Dr.Lt.Data 690b0a0f61 fix: PreviewDetailerHookProvider - invalid behavior since PR2666 2024-08-29 03:38:19 +09:00
Dr.Lt.Data 84fbb3fa2e fix: proper error message for outdated ComfyUI instead of import fail
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/722
2024-08-27 01:03:12 +09:00
Dr.Lt.Data b2c7e06e79 improve: ControlBridge - add Stop behavior
improve: Detailer - new detailer wildcard syntax
- [ASC-SIZE], [DSC-SIZE], [SKIP], [STOP]
2024-08-27 00:06:00 +09:00
Dr.Lt.Data a500aff476 fix: RegionalPrompt compatibility with FLUX.1 2024-08-25 16:25:30 +09:00
8 changed files with 121 additions and 80 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
import configparser import configparser
import os import os
version_code = [7, 2, 1] version_code = [7, 3, 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
+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
+13 -2
View File
@@ -238,11 +238,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 +296,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,
+78 -60
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):
@@ -631,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:
+8 -2
View File
@@ -5,7 +5,7 @@ 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
@@ -332,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:
@@ -476,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:
@@ -491,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 = {}
+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 = []
+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.2.1" version = "7.3.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"]