Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06b428ec8c | ||
|
|
94f30ef317 | ||
|
|
6c45402b2b | ||
|
|
3423c10225 | ||
|
|
182ff558be | ||
|
|
44c48f11ef | ||
|
|
adb2a97646 | ||
|
|
b2d12d04b4 | ||
|
|
26c7419553 | ||
|
|
d4d6c1b02f | ||
|
|
f8723ab7ac | ||
|
|
83ad10cdde | ||
|
|
573ddd8755 |
@@ -7,6 +7,8 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
|
||||
|
||||
## NOTICE
|
||||
* V7.0: Supports Switch based on Execution Model Inversion.
|
||||
* V6.0: Supports FLUX.1 model in Impact KSampler, Detailers, PreviewBridgeLatent
|
||||
* V5.0: It is no longer compatible with versions of ComfyUI before 2024.04.08.
|
||||
* V4.87.4: Update to a version of ComfyUI after 2024.04.08 for proper functionality.
|
||||
* V4.85: Incompatible with the outdated **ComfyUI IPAdapter Plus**. (A version dated March 24th or later is required.)
|
||||
@@ -189,15 +191,11 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* Furthermore, LatentSender is implemented with PreviewLatent, which stores the latent in payload form within the image thumbnail.
|
||||
* Due to the current structure of ComfyUI, it is unable to distinguish between SDXL latent and SD1.5/SD2.1 latent. Therefore, it generates thumbnails by decoding them using the SD1.5 method.
|
||||
|
||||
|
||||
### Switch nodes
|
||||
* `Switch (image,mask)`, `Switch (latent)`, `Switch (SEGS)` - Among multiple inputs, it selects the input designated by the selector and outputs it. The first input must be provided, while the others are optional. However, if the input specified by the selector is not connected, an error may occur.
|
||||
* `Switch (Any)` - This is a Switch node that takes an arbitrary number of inputs and produces a single output. Its type is determined when connected to any node, and connecting inputs increases the available slots for connections.
|
||||
* `Inversed Switch (Any)` - In contrast to `Switch (Any)`, it takes a single input and outputs one of many. Due to ComfyUI's functional limitations, the value of `select` must be determined at the time of queuing a prompt, and while it can serve as a `Primitive Node` or `ImpactInt`, it cannot function properly when connected through other nodes.
|
||||
* Guide
|
||||
* When the `Switch (Any)` and `Inversed Switch (Any)` selects are transformed into primitives, it's important to be cautious because the select range is not appropriately constrained, potentially leading to unintended behavior.
|
||||
* `Switch (image,mask)`, `Switch (latent)`, `Switch (SEGS)`, `Switch (Any)` supports `sel_mode` param. The `sel_mode` sets the moment at which the `select` parameter is determined. `select_on_prompt` determines the `select` at the time of queuing the prompt, while `select_on_execution` determines it during the execution of the workflow. While `select_on_execution` offers more flexibility, it can potentially trigger workflow execution errors due to running nodes that may be impossible to execute within the limitations of ComfyUI. `select_on_prompt` bypasses this constraint by treating any inputs not selected as if they were disconnected. However, please note that when using `select_on_prompt`, the `select` can only be used with widgets or `Primitive Nodes` determined at the queue prompt.
|
||||
* There is an issue when connecting the built-in reroute node with the switch's input/output slots. it can lead to forced disconnections during workflow loading. Therefore, it is advisable not to use reroute for making connections in such cases. However, there are no issues when using the reroute node in Pythongossss.
|
||||
* `Inversed Switch (Any)` - In contrast to `Switch (Any)`, it takes a single input and outputs one of many.
|
||||
* NOTE: See this [tutorial](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/switch.md)
|
||||
|
||||
### [Wildcards](http://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/ImpactWildcard.md) nodes
|
||||
* These are nodes that supports syntax in the form of `__wildcard-name__` and dynamic prompt syntax like `{a|b|c}`.
|
||||
@@ -220,6 +218,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `RegionalSamplerAdvanced` - This is the Advanced version of the RegionalSampler. You can control it using `step` instead of `denoise`.
|
||||
> NOTE: The `sde` sampler and `uni_pc` sampler introduce additional noise during each step of the sampling process. To mitigate this, when sampling each region, the `uni_pc` sampler applies additional `dpmpp_fast`, and the sde sampler applies the `dpmpp_2m` sampler as an additional measure.
|
||||
|
||||
|
||||
### Impact KSampler
|
||||
* These samplers support basic_pipe and AYS scheduler
|
||||
* `KSampler (pipe)` - pipe version of KSampler
|
||||
@@ -278,6 +277,9 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `String Selector` - It selects and returns a portion of the string. When `multiline` mode is disabled, it simply returns the string of the line pointed to by the selector. When `multiline` mode is enabled, it divides the string based on lines that start with `#` and returns them. If the `select` value is larger than the number of items, it will start counting from the first line again and return accordingly.
|
||||
* `Combine Conditionings` - It takes multiple conditionings as input and combines them into a single conditioning.
|
||||
* `Concat Conditionings` - It takes multiple conditionings as input and concat them into a single conditioning.
|
||||
* `Negative Cond Placeholder` - Models like FLUX.1 do not use Negative Conditioning. This is a placeholder node for them. You can use FLUX.1 by replacing the Negative Conditioning used in Impact KSampler, KSampler (Inspire), and Detailer with this node.
|
||||
* `Execution Order Controller` - A helper node that can forcibly control the execution order of nodes.
|
||||
* Connect the output of the node that should be executed first to the signal, and make the input of the node that should be executed later pass through this node.
|
||||
|
||||
|
||||
## MMDet nodes (DEPRECATED) - Don't use these nodes
|
||||
|
||||
@@ -208,6 +208,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"KSamplerAdvancedProvider": KSamplerAdvancedProvider,
|
||||
"TwoAdvancedSamplersForMask": TwoAdvancedSamplersForMask,
|
||||
|
||||
"ImpactNegativeConditioningPlaceholder": NegativeConditioningPlaceholder,
|
||||
|
||||
"PreviewBridge": PreviewBridge,
|
||||
"PreviewBridgeLatent": PreviewBridgeLatent,
|
||||
"ImageSender": ImageSender,
|
||||
@@ -279,6 +281,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactStringSelector": StringSelector,
|
||||
"StringListToString": StringListToString,
|
||||
"WildcardPromptFromString": WildcardPromptFromString,
|
||||
"ImpactExecutionOrderController": ImpactExecutionOrderController,
|
||||
|
||||
"RemoveNoiseMask": RemoveNoiseMask,
|
||||
|
||||
@@ -395,6 +398,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImageMaskSwitch": "Switch (images, mask)",
|
||||
"ImpactSwitch": "Switch (Any)",
|
||||
"ImpactInversedSwitch": "Inversed Switch (Any)",
|
||||
"ImpactExecutionOrderController": "Execution Order Controller",
|
||||
|
||||
"MasksToMaskList": "Masks to Mask List",
|
||||
"MaskListToMaskBatch": "Mask List to Masks",
|
||||
@@ -433,6 +437,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
|
||||
"ImpactSchedulerAdapter": "Impact Scheduler Adapter",
|
||||
"GITSSchedulerFuncProvider": "GITSScheduler Func Provider",
|
||||
"ImpactNegativeConditioningPlaceholder": "Negative Cond Placeholder"
|
||||
}
|
||||
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
|
||||
+1
-1
@@ -507,7 +507,7 @@ app.registerExtension({
|
||||
!stackTrace.includes('LGraphNode.connect') && // for mouse device
|
||||
!stackTrace.includes('loadGraphData') &&
|
||||
this.inputs[index].name != 'select') {
|
||||
this.removeInput(index);
|
||||
this.removeInput(index);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from PIL import ImageOps
|
||||
from impact.utils import *
|
||||
import latent_preview
|
||||
|
||||
|
||||
# NOTE: this should not be `from . import core`.
|
||||
# I don't know why but... 'from .' and 'from impact' refer to different core modules.
|
||||
# This separates global variables of the core module and breaks the preview bridge.
|
||||
@@ -18,7 +19,7 @@ class PreviewBridge:
|
||||
"images": ("IMAGE",),
|
||||
"image": ("STRING", {"default": ""}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK", )
|
||||
@@ -69,7 +70,7 @@ class PreviewBridge:
|
||||
|
||||
return image, mask.unsqueeze(0), ui_item
|
||||
|
||||
def doit(self, images, image, unique_id):
|
||||
def doit(self, images, image, unique_id, prompt=None, extra_pnginfo=None):
|
||||
need_refresh = False
|
||||
|
||||
if unique_id not in core.preview_bridge_cache:
|
||||
@@ -82,7 +83,7 @@ class PreviewBridge:
|
||||
pixels, mask, path_item = PreviewBridge.load_image(image)
|
||||
image = [path_item]
|
||||
else:
|
||||
res = nodes.PreviewImage().save_images(images, filename_prefix="PreviewBridge/PB-")
|
||||
res = nodes.PreviewImage().save_images(images, filename_prefix="PreviewBridge/PB-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
image2 = res['ui']['images']
|
||||
pixels = images
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
@@ -145,6 +146,9 @@ def decode_latent(latent, preview_method, vae_opt=None):
|
||||
elif preview_method == "Latent2RGB-SC-B":
|
||||
latent_format = latent_formats.SC_B()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-FLUX.1":
|
||||
latent_format = latent_formats.Flux()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
else:
|
||||
print(f"[Impact Pack] PreviewBridgeLatent: '{preview_method}' is unsupported preview method.")
|
||||
latent_format = latent_formats.SD15()
|
||||
@@ -169,12 +173,13 @@ class PreviewBridgeLatent:
|
||||
"preview_method": (["Latent2RGB-SD3", "Latent2RGB-SDXL", "Latent2RGB-SD15",
|
||||
"Latent2RGB-SD-X4", "Latent2RGB-Playground-2.5",
|
||||
"Latent2RGB-SC-Prior", "Latent2RGB-SC-B",
|
||||
"Latent2RGB-FLUX.1",
|
||||
"TAESD3", "TAESDXL", "TAESD15"],),
|
||||
},
|
||||
"optional": {
|
||||
"vae_opt": ("VAE", )
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "MASK", )
|
||||
@@ -226,13 +231,13 @@ class PreviewBridgeLatent:
|
||||
|
||||
return image, mask, ui_item
|
||||
|
||||
def doit(self, latent, image, preview_method, vae_opt=None, unique_id=None):
|
||||
def doit(self, latent, image, preview_method, vae_opt=None, unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
latent_channels = latent['samples'].shape[1]
|
||||
preview_method_channels = 16 if 'SD3' in preview_method or 'SC-Prior' 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 else 4
|
||||
|
||||
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, and SC-B are not compatible with each other.")
|
||||
raise Exception("The version of latent is not compatible with preview_method.<BR>SD3, SD1/SD2, SDXL, SC-Prior, and SC-B 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.")
|
||||
raise Exception("The version of latent is not compatible with preview_method.<BR>SD3, SD1/SD2, SDXL, SC-Prior, SC-B and FLUX.1 are not compatible with each other.")
|
||||
|
||||
need_refresh = False
|
||||
|
||||
@@ -282,7 +287,7 @@ class PreviewBridgeLatent:
|
||||
}]
|
||||
else:
|
||||
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-")
|
||||
res = nodes.PreviewImage().save_images(decoded_image, filename_prefix="PreviewBridge/PBL-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
res_image = res['ui']['images']
|
||||
|
||||
path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', res_image[0]['filename'])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import configparser
|
||||
import os
|
||||
|
||||
version_code = [5, 18, 14]
|
||||
version_code = [7, 0]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 22
|
||||
|
||||
@@ -45,6 +45,14 @@ current_prompt = None
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]']
|
||||
|
||||
|
||||
def is_execution_model_version_supported():
|
||||
try:
|
||||
import comfy_execution
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def set_previewbridge_image(node_id, file, item):
|
||||
global pb_id_cnt
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import comfy.model_management
|
||||
import base64
|
||||
import impact.wildcards as wildcards
|
||||
from . import hooks
|
||||
from . import utils
|
||||
|
||||
warnings.filterwarnings('ignore', category=UserWarning, message='TypedStorage is deprecated')
|
||||
|
||||
@@ -278,13 +279,17 @@ class DetailerForEach:
|
||||
for condition, details in positive
|
||||
]
|
||||
|
||||
cropped_negative = [
|
||||
[condition, {
|
||||
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
||||
for k, v in details.items()
|
||||
}]
|
||||
for condition, details in negative
|
||||
]
|
||||
if not isinstance(negative, str):
|
||||
cropped_negative = [
|
||||
[condition, {
|
||||
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
||||
for k, v in details.items()
|
||||
}]
|
||||
for condition, details in negative
|
||||
]
|
||||
else:
|
||||
# Negative Conditioning is placeholder such as FLUX.1
|
||||
cropped_negative = negative
|
||||
|
||||
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,
|
||||
@@ -964,7 +969,10 @@ class PixelTiledKSampleUpscalerProvider:
|
||||
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength)
|
||||
return (upscaler, )
|
||||
else:
|
||||
print("[ERROR] PixelTiledKSampleUpscalerProvider: ComfyUI_TiledKSampler custom node isn't installed. You must install BlenderNeko/ComfyUI_TiledKSampler extension to use this node.")
|
||||
utils.try_install_custom_node('https://github.com/BlenderNeko/ComfyUI_TiledKSampler',
|
||||
"To use 'PixelTiledKSampleUpscalerProvider' node, 'BlenderNeko/ComfyUI_TiledKSampler' extension is required.")
|
||||
|
||||
raise Exception("[ERROR] PixelTiledKSampleUpscalerProvider: ComfyUI_TiledKSampler custom node isn't installed. You must install BlenderNeko/ComfyUI_TiledKSampler extension to use this node.")
|
||||
|
||||
|
||||
class PixelTiledKSampleUpscalerProviderPipe:
|
||||
|
||||
@@ -6,10 +6,12 @@ import latent_preview
|
||||
import comfy
|
||||
import torch
|
||||
import math
|
||||
import comfy.model_management as mm
|
||||
|
||||
|
||||
try:
|
||||
from comfy_extras.nodes_custom_sampler import Noise_EmptyNoise, Noise_RandomNoise
|
||||
import node_helpers
|
||||
except:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
@@ -140,7 +142,29 @@ def sample_with_custom_noise(model, add_noise, noise_seed, cfg, positive, negati
|
||||
touched_callback = preview_callback
|
||||
|
||||
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
|
||||
samples = comfy.sample.sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=noise_mask, callback=touched_callback, disable_pbar=disable_pbar, seed=noise_seed)
|
||||
|
||||
device = mm.get_torch_device()
|
||||
|
||||
noise = noise.to(device)
|
||||
latent_image = latent_image.to(device)
|
||||
if noise_mask is not None:
|
||||
noise_mask = noise_mask.to(device)
|
||||
|
||||
if negative != 'NegativePlaceholder':
|
||||
# This way is incompatible with Advanced ControlNet, yet.
|
||||
# guider = comfy.samplers.CFGGuider(model)
|
||||
# guider.set_conds(positive, negative)
|
||||
# guider.set_cfg(cfg)
|
||||
samples = comfy.sample.sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image,
|
||||
noise_mask=noise_mask, callback=touched_callback,
|
||||
disable_pbar=disable_pbar, seed=noise_seed)
|
||||
else:
|
||||
guider = nodes_custom_sampler.Guider_Basic(model)
|
||||
positive = node_helpers.conditioning_set_values(positive, {"guidance": cfg})
|
||||
guider.set_conds(positive)
|
||||
samples = guider.sample(noise, latent_image, sampler, sigmas, denoise_mask=noise_mask, callback=touched_callback, disable_pbar=disable_pbar, seed=noise_seed)
|
||||
|
||||
samples = samples.to(comfy.model_management.intermediate_device())
|
||||
|
||||
out["samples"] = samples
|
||||
if "x0" in x0_output:
|
||||
|
||||
@@ -353,13 +353,16 @@ def onprompt_for_switch(json_data):
|
||||
|
||||
cls = v['class_type']
|
||||
if cls == 'ImpactInversedSwitch':
|
||||
select_input = v['inputs']['select']
|
||||
if isinstance(select_input, list) and len(select_input) == 2:
|
||||
input_node = json_data['prompt'][select_input[0]]
|
||||
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
|
||||
inversed_switch_info[k] = input_node['inputs']['value']
|
||||
else:
|
||||
inversed_switch_info[k] = select_input
|
||||
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
|
||||
select_input = v['inputs']['select']
|
||||
if isinstance(select_input, list) and len(select_input) == 2:
|
||||
input_node = json_data['prompt'][select_input[0]]
|
||||
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
|
||||
inversed_switch_info[k] = input_node['inputs']['value']
|
||||
else:
|
||||
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactInversedSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
else:
|
||||
inversed_switch_info[k] = select_input
|
||||
|
||||
elif cls in ['ImpactSwitch', 'LatentSwitch', 'SEGSSwitch', 'ImpactMakeImageList']:
|
||||
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
|
||||
@@ -372,7 +375,7 @@ def onprompt_for_switch(json_data):
|
||||
if isinstance(input_node['inputs']['select'], int):
|
||||
onprompt_switch_info[k] = input_node['inputs']['select']
|
||||
else:
|
||||
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs.\n##### ##### #####\n")
|
||||
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
else:
|
||||
onprompt_switch_info[k] = select_input
|
||||
|
||||
|
||||
@@ -86,11 +86,18 @@ class ImpactConditionalBranch:
|
||||
class ImpactConditionalBranchSelMode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
if not core.is_execution_model_version_supported():
|
||||
required_inputs = {
|
||||
"cond": ("BOOLEAN",),
|
||||
"sel_mode": ("BOOLEAN", {"default": True, "label_on": "select_on_prompt", "label_off": "select_on_execution"}),
|
||||
},
|
||||
}
|
||||
else:
|
||||
required_inputs = {
|
||||
"cond": ("BOOLEAN",),
|
||||
}
|
||||
|
||||
return {
|
||||
"required": required_inputs,
|
||||
"optional": {
|
||||
"tt_value": (any_typ,),
|
||||
"ff_value": (any_typ,),
|
||||
@@ -102,7 +109,7 @@ class ImpactConditionalBranchSelMode:
|
||||
|
||||
RETURN_TYPES = (any_typ, )
|
||||
|
||||
def doit(self, cond, sel_mode, tt_value=None, ff_value=None):
|
||||
def doit(self, cond, tt_value=None, ff_value=None, **kwargs):
|
||||
print(f'tt={tt_value is None}\nff={ff_value is None}')
|
||||
if cond:
|
||||
return (tt_value,)
|
||||
@@ -699,6 +706,24 @@ class ImpactControlBridge:
|
||||
return (value, )
|
||||
|
||||
|
||||
class ImpactExecutionOrderController:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {
|
||||
"signal": (any_typ,),
|
||||
"value": (any_typ,),
|
||||
}}
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
RETURN_TYPES = (any_typ, any_typ)
|
||||
RETURN_NAMES = ("signal", "value")
|
||||
|
||||
def doit(self, signal, value):
|
||||
return signal, value
|
||||
|
||||
|
||||
original_handle_execution = execution.PromptExecutor.handle_execution_error
|
||||
|
||||
|
||||
|
||||
@@ -212,49 +212,15 @@ class TwoAdvancedSamplersForMask:
|
||||
|
||||
CATEGORY = "ImpactPack/Sampler"
|
||||
|
||||
@staticmethod
|
||||
def mask_erosion(samples, mask, grow_mask_by):
|
||||
mask = mask.clone()
|
||||
|
||||
w = samples['samples'].shape[3]
|
||||
h = samples['samples'].shape[2]
|
||||
|
||||
mask2 = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(w, h), mode="bilinear")
|
||||
if grow_mask_by == 0:
|
||||
mask_erosion = mask2
|
||||
else:
|
||||
kernel_tensor = torch.ones((1, 1, grow_mask_by, grow_mask_by))
|
||||
padding = math.ceil((grow_mask_by - 1) / 2)
|
||||
|
||||
mask_erosion = torch.clamp(torch.nn.functional.conv2d(mask2.round(), kernel_tensor, padding=padding), 0, 1)
|
||||
|
||||
return mask_erosion[:, :, :w, :h].round()
|
||||
|
||||
@staticmethod
|
||||
def doit(seed, steps, denoise, samples, base_sampler, mask_sampler, mask, overlap_factor):
|
||||
regional_prompts = RegionalPrompt().doit(mask=mask, advanced_sampler=mask_sampler)[0]
|
||||
|
||||
inv_mask = torch.where(mask != 1.0, torch.tensor(1.0), torch.tensor(0.0))
|
||||
|
||||
adv_steps = int(steps / denoise)
|
||||
start_at_step = adv_steps - steps
|
||||
|
||||
new_latent_image = samples.copy()
|
||||
|
||||
mask_erosion = TwoAdvancedSamplersForMask.mask_erosion(samples, mask, overlap_factor)
|
||||
|
||||
for i in range(start_at_step, adv_steps):
|
||||
add_noise = "enable" if i == start_at_step else "disable"
|
||||
return_with_leftover_noise = "enable" if i+1 != adv_steps else "disable"
|
||||
|
||||
new_latent_image['noise_mask'] = inv_mask
|
||||
new_latent_image = base_sampler.sample_advanced(add_noise, seed, adv_steps, new_latent_image, i, i + 1, "enable", recovery_mode="ratio additional")
|
||||
|
||||
new_latent_image['noise_mask'] = mask_erosion
|
||||
new_latent_image = mask_sampler.sample_advanced("disable", seed, adv_steps, new_latent_image, i, i + 1, return_with_leftover_noise, recovery_mode="ratio additional")
|
||||
|
||||
del new_latent_image['noise_mask']
|
||||
|
||||
return (new_latent_image, )
|
||||
return RegionalSampler().doit(seed=seed, seed_2nd=0, seed_2nd_mode="ignore", steps=steps, base_only_steps=1,
|
||||
denoise=denoise, samples=samples, base_sampler=base_sampler,
|
||||
regional_prompts=regional_prompts, overlap_factor=overlap_factor,
|
||||
restore_latent=True, additional_mode="ratio between",
|
||||
additional_sampler="AUTO", additional_sigma_ratio=0.3)
|
||||
|
||||
|
||||
class RegionalPrompt:
|
||||
@@ -409,7 +375,7 @@ class RegionalSampler:
|
||||
"additional_sampler": (["AUTO", "euler", "heun", "heunpp2", "dpm_2", "dpm_fast", "dpmpp_2m", "ddpm"],),
|
||||
"additional_sigma_ratio": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
TOOLTIPS = {
|
||||
@@ -519,7 +485,8 @@ class RegionalSampler:
|
||||
core.update_node_status(unique_id, f"{i}/{steps} steps | ", ((i-start_at_step)*region_len)/total)
|
||||
|
||||
new_latent_image['noise_mask'] = inv_mask
|
||||
new_latent_image = base_sampler.sample_advanced(add_noise, seed, adv_steps, new_latent_image, i, i + 1, True,
|
||||
new_latent_image = base_sampler.sample_advanced(add_noise, seed, adv_steps, new_latent_image,
|
||||
start_at_step=i, end_at_step=i + 1, return_with_leftover_noise=True,
|
||||
recovery_mode=additional_mode, recovery_sampler=additional_sampler, recovery_sigma_ratio=additional_sigma_ratio, noise=noise)
|
||||
|
||||
if restore_latent:
|
||||
@@ -831,7 +798,8 @@ class GITSSchedulerFuncProvider:
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
def doit(self, coeff, denoise):
|
||||
@staticmethod
|
||||
def doit(coeff, denoise):
|
||||
def f(model, sampler, steps):
|
||||
if 'GITSScheduler' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version. Cannot use GITSScheduler.")
|
||||
@@ -840,3 +808,22 @@ class GITSSchedulerFuncProvider:
|
||||
return scheduler.get_sigmas(coeff, steps, denoise)[0]
|
||||
|
||||
return (f, )
|
||||
|
||||
|
||||
class NegativeConditioningPlaceholder:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}}
|
||||
|
||||
TOOLTIPS = {
|
||||
"output": ("This is a Placeholder for the FLUX model that does not use Negative Conditioning.",)
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
CATEGORY = "ImpactPack/sampling"
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
@staticmethod
|
||||
def doit():
|
||||
return ("NegativePlaceholder", )
|
||||
|
||||
@@ -6,29 +6,47 @@ import comfy
|
||||
import sys
|
||||
import nodes
|
||||
import re
|
||||
import impact.core as core
|
||||
from server import PromptServer
|
||||
import inspect
|
||||
|
||||
|
||||
class GeneralSwitch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
dyn_inputs = {"input1": (any_typ, {"lazy": True}), }
|
||||
if core.is_execution_model_version_supported():
|
||||
stack = inspect.stack()
|
||||
if stack[2].function == 'get_input_info' and stack[3].function == 'add_node':
|
||||
for x in range(2, 200):
|
||||
dyn_inputs[f"input{x}"] = (any_typ, {"lazy": True})
|
||||
|
||||
inputs = {"required": {
|
||||
"select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1}),
|
||||
"sel_mode": ("BOOLEAN", {"default": True, "label_on": "select_on_prompt", "label_off": "select_on_execution", "forceInput": False}),
|
||||
},
|
||||
"optional": {
|
||||
"input1": (any_typ,),
|
||||
"sel_mode": ("BOOLEAN", {"default": False, "label_on": "select_on_prompt", "label_off": "select_on_execution", "forceInput": False}),
|
||||
},
|
||||
"optional": dyn_inputs,
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"}
|
||||
}
|
||||
|
||||
return inputs
|
||||
|
||||
RETURN_TYPES = (any_typ, "STRING", "INT")
|
||||
RETURN_NAMES = ("selected_value", "selected_label", "selected_index")
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, *args, **kwargs):
|
||||
def check_lazy_status(self, *args, **kwargs):
|
||||
selected_index = int(kwargs['select'])
|
||||
input_name = f"input{selected_index}"
|
||||
|
||||
print(f"SELECTED: {input_name}")
|
||||
|
||||
return [input_name]
|
||||
|
||||
@staticmethod
|
||||
def doit(*args, **kwargs):
|
||||
selected_index = int(kwargs['select'])
|
||||
input_name = f"input{selected_index}"
|
||||
|
||||
@@ -50,11 +68,10 @@ class GeneralSwitch:
|
||||
print(f"[Impact-Pack] The switch node does not guarantee proper functioning in API mode.")
|
||||
|
||||
if input_name in kwargs:
|
||||
return (kwargs[input_name], selected_label, selected_index)
|
||||
return kwargs[input_name], selected_label, selected_index
|
||||
else:
|
||||
print(f"ImpactSwitch: invalid select index (ignored)")
|
||||
return (None, "", selected_index)
|
||||
|
||||
return None, "", selected_index
|
||||
|
||||
class LatentSwitch:
|
||||
@classmethod
|
||||
@@ -129,7 +146,9 @@ class GeneralInversedSwitch:
|
||||
"select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1}),
|
||||
"input": (any_typ,),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID"},
|
||||
"optional": {
|
||||
"sel_mode": ("BOOLEAN", {"default": False, "label_on": "select_on_prompt", "label_off": "select_on_execution", "forceInput": False}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ByPassTypeTuple((any_typ, ))
|
||||
@@ -137,7 +156,7 @@ class GeneralInversedSwitch:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, select, input, unique_id):
|
||||
def doit(self, select, input, **kwargs):
|
||||
res = []
|
||||
|
||||
for i in range(0, select):
|
||||
|
||||
+12
-1
@@ -537,6 +537,16 @@ def make_3d_mask(mask):
|
||||
return mask
|
||||
|
||||
|
||||
def make_4d_mask(mask):
|
||||
if len(mask.shape) == 3:
|
||||
return mask.unsqueeze(0)
|
||||
|
||||
elif len(mask.shape) == 2:
|
||||
return mask.unsqueeze(0).unsqueeze(0)
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def is_same_device(a, b):
|
||||
a_device = torch.device(a) if isinstance(a, str) else a
|
||||
b_device = torch.device(b) if isinstance(b, str) else b
|
||||
@@ -556,7 +566,8 @@ from torchvision.transforms.functional import to_pil_image
|
||||
|
||||
|
||||
def resize_mask(mask, size):
|
||||
resized_mask = torch.nn.functional.interpolate(mask.unsqueeze(0), size=size, mode='bilinear', align_corners=False)
|
||||
mask = make_4d_mask(mask)
|
||||
resized_mask = torch.nn.functional.interpolate(mask, size=size, mode='bilinear', align_corners=False)
|
||||
return resized_mask.squeeze(0)
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ def get_wildcard_dict():
|
||||
|
||||
|
||||
def wildcard_normalize(x):
|
||||
return x.replace("\\", "/").lower()
|
||||
return x.replace("\\", "/").replace(' ', '-').lower()
|
||||
|
||||
|
||||
def read_wildcard(k, v):
|
||||
@@ -53,23 +53,28 @@ def read_wildcard_dict(wildcard_path):
|
||||
if file.endswith('.txt'):
|
||||
file_path = os.path.join(root, file)
|
||||
rel_path = os.path.relpath(file_path, wildcard_path)
|
||||
key = os.path.splitext(rel_path)[0].replace('\\', '/').lower()
|
||||
key = wildcard_normalize(os.path.splitext(rel_path)[0])
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding="ISO-8859-1") as f:
|
||||
lines = f.read().splitlines()
|
||||
wildcard_dict[key] = lines
|
||||
except UnicodeDecodeError:
|
||||
except yaml.reader.ReaderError:
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
lines = f.read().splitlines()
|
||||
wildcard_dict[key] = lines
|
||||
elif file.endswith('.yaml'):
|
||||
file_path = os.path.join(root, file)
|
||||
with open(file_path, 'r') as f:
|
||||
yaml_data = yaml.load(f, Loader=yaml.FullLoader)
|
||||
|
||||
for k, v in yaml_data.items():
|
||||
read_wildcard(k, v)
|
||||
try:
|
||||
with open(file_path, 'r', encoding="ISO-8859-1") as f:
|
||||
yaml_data = yaml.load(f, Loader=yaml.FullLoader)
|
||||
except yaml.reader.ReaderError as e:
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
yaml_data = yaml.load(f, Loader=yaml.FullLoader)
|
||||
|
||||
for k, v in yaml_data.items():
|
||||
read_wildcard(k, v)
|
||||
|
||||
return wildcard_dict
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
[project]
|
||||
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."
|
||||
version = "5.18.14"
|
||||
license = "LICENSE"
|
||||
version = "7.0"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
|
||||
|
||||
[project.urls]
|
||||
|
||||
Reference in New Issue
Block a user