Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6248f31402 | ||
|
|
cb0b652703 | ||
|
|
9c4fa5fb27 | ||
|
|
759b4d0dc9 | ||
|
|
a65fd56b7f | ||
|
|
47d981eca2 | ||
|
|
b2f382776f | ||
|
|
0e4e439d39 | ||
|
|
18d25a29a0 | ||
|
|
2b724e5ed2 | ||
|
|
96cc242f78 | ||
|
|
28267da071 | ||
|
|
86d7f73981 | ||
|
|
f428182ddf |
@@ -90,6 +90,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `MaskDetailer (pipe)` - This is a simple inpaint node that applies the Detailer to the mask area.
|
||||
|
||||
* `FromDetailer (SDXL/pipe)`, `BasicPipe -> DetailerPipe (SDXL)`, `Edit DetailerPipe (SDXL)` - These are pipe functions used in Detailer for utilizing the refiner model of SDXL.
|
||||
* `Any PIPE -> BasicPipe` - Convert the PIPE Value of other custom nodes that are not BASIC_PIPE but internally have the same structure as BASIC_PIPE to BASIC_PIPE. If an incompatible type is applied, it may cause runtime errors.
|
||||
|
||||
### SEGS Manipulation nodes
|
||||
* `SEGSDetailer` - Performs detailed work on SEGS without pasting it back onto the original image.
|
||||
@@ -107,6 +108,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `SEGS Filter (range)` - This node retrieves only SEGs from SEGS that have a size and position within a certain range.
|
||||
* `SEGS Assign (label)` - Assign labels sequentially to SEGS. This node is useful when used with `[LAB]` of FaceDetailer.
|
||||
* `SEGSConcat` - Concatenate segs1 and segs2. If source shape of segs1 and segs2 are different from segs2 will be ignored.
|
||||
* `SEGS Merge` - SEGS contains multiple SEGs. SEGS Merge integrates several SEGs into a single merged SEG. The label is changed to `merged` and the confidence becomes the minimum confidence. The applied controlnet and cropped_image are removed.
|
||||
* `Picker (SEGS)` - Among the input SEGS, you can select a specific SEG through a dialog. If no SEG is selected, it outputs an empty SEGS. Increasing the batch_size of SEGSDetailer can be used for the purpose of selecting from the candidates.
|
||||
* `Set Default Image For SEGS` - Set a default image for SEGS. SEGS with images set this way do not need to have a fallback image set. When override is set to false, the original image is preserved.
|
||||
* `Remove Image from SEGS` - Remove the image set for the SEGS that has been configured by "Set Default Image for SEGS" or SEGSDetailer. When the image for the SEGS is removed, the Detailer node will operate based on the currently processed image instead of the SEGS.
|
||||
@@ -237,11 +239,11 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
- The input of images can be scaled up as needed
|
||||
* `Masks to Mask List`, `Mask List to Masks`, `Make Mask List`, `Make Mask Batch` - It has the same functionality as the nodes above, but uses mask as input instead of image.
|
||||
* `Flatten Mask Batch` - Flattens a Mask Batch into a single Mask. Normal operation is not guaranteed for non-binary masks.
|
||||
|
||||
* `Make List (Any)` - Create a list with arbitrary values.
|
||||
|
||||
### Logics (experimental)
|
||||
* These nodes are experimental nodes designed to implement the logic for loops and dynamic switching.
|
||||
* `ImpactCompare`, `ImpactConditionalBranch`, `ImpactConditionalBranchSelMode`, `ImpactInt`, `ImpactValueSender`, `ImpactValueReceiver`, `ImpactImageInfo`, `ImpactMinMax`, `ImpactNeg`, `ImpactConditionalStopIteration`
|
||||
* `ImpactCompare`, `ImpactConditionalBranch`, `ImpactConditionalBranchSelMode`, `ImpactInt`, `ImpactBoolean`, `ImpactValueSender`, `ImpactValueReceiver`, `ImpactImageInfo`, `ImpactMinMax`, `ImpactNeg`, `ImpactConditionalStopIteration`
|
||||
* `ImpactIsNotEmptySEGS` - This node returns `true` only if the input SEGS is not empty.
|
||||
* `ImpactIfNone` - Returns `true` if any_input is None, and returns `false` if it is not None.
|
||||
* `Queue Trigger` - When this node is executed, it adds a new queue to assist with repetitive tasks. It will only execute if the signal's status changes.
|
||||
@@ -342,9 +344,9 @@ mmdet_skip = False
|
||||
3. `cd ComfyUI-Impact-Pack`
|
||||
4. (optional) `git clone https://github.com/ltdrdata/ComfyUI-Impact-Subpack impact_subpack`
|
||||
* Impact Pack will automatically download subpack during its initial launch.
|
||||
5. (optional) `python install.py`
|
||||
5. (optional) `python install-manual.py`
|
||||
* Impact Pack will automatically install its dependencies during its initial launch.
|
||||
* For the portable version, you should execute the command `..\..\..\python_embeded\python.exe install.py` to run the installation script.
|
||||
* For the portable version, you should execute the command `..\..\..\python_embeded\python.exe install-manual.py` to run the installation script.
|
||||
6. Restart ComfyUI
|
||||
|
||||
* NOTE1: If an error occurs during the installation process, please refer to [Troubleshooting Page](troubleshooting/TROUBLESHOOTING.md) for assistance.
|
||||
|
||||
+9
-2
@@ -100,6 +100,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"FromDetailerPipe": FromDetailerPipe,
|
||||
"FromDetailerPipe_v2": FromDetailerPipe_v2,
|
||||
"FromDetailerPipeSDXL": FromDetailerPipe_SDXL,
|
||||
"AnyPipeToBasic": AnyPipeToBasic,
|
||||
"ToBasicPipe": ToBasicPipe,
|
||||
"FromBasicPipe": FromBasicPipe,
|
||||
"FromBasicPipe_v2": FromBasicPipe_v2,
|
||||
@@ -222,6 +223,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactSEGSConcat": SEGSConcat,
|
||||
"ImpactSEGSPicker": SEGSPicker,
|
||||
"ImpactMakeTileSEGS": MakeTileSEGS,
|
||||
"ImpactSEGSMerge": SEGSMerge,
|
||||
|
||||
"SEGSDetailerForAnimateDiff": SEGSDetailerForAnimateDiff,
|
||||
|
||||
@@ -234,6 +236,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactImageBatchToImageList": ImageBatchToImageList,
|
||||
"ImpactMakeImageList": MakeImageList,
|
||||
"ImpactMakeImageBatch": MakeImageBatch,
|
||||
"ImpactMakeAnyList": MakeAnyList,
|
||||
"ImpactMakeMaskList": MakeMaskList,
|
||||
"ImpactMakeMaskBatch": MakeMaskBatch,
|
||||
|
||||
@@ -258,6 +261,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactLogicalOperators": ImpactLogicalOperators,
|
||||
"ImpactInt": ImpactInt,
|
||||
"ImpactFloat": ImpactFloat,
|
||||
"ImpactBoolean": ImpactBoolean,
|
||||
"ImpactValueSender": ImpactValueSender,
|
||||
"ImpactValueReceiver": ImpactValueReceiver,
|
||||
"ImpactImageInfo": ImpactImageInfo,
|
||||
@@ -302,8 +306,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSimpleDetectorSEGS_for_AD": "Simple Detector for AnimateDiff (SEGS)",
|
||||
"ImpactSimpleDetectorSEGS": "Simple Detector (SEGS)",
|
||||
"ImpactSimpleDetectorSEGSPipe": "Simple Detector (SEGS/pipe)",
|
||||
"ImpactControlNetApplySEGS": "ControlNetApply (SEGS)",
|
||||
"ImpactControlNetApplyAdvancedSEGS": "ControlNetApplyAdvanced (SEGS)",
|
||||
"ImpactControlNetApplySEGS": "ControlNetApply (SEGS) - DEPRECATED",
|
||||
"ImpactControlNetApplyAdvancedSEGS": "ControlNetApply (SEGS)",
|
||||
"ImpactIPAdapterApplySEGS": "IPAdapterApply (SEGS)",
|
||||
|
||||
"BboxDetectorCombined_v2": "BBOX Detector (combined)",
|
||||
@@ -342,6 +346,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"DetailerPipeToBasicPipe": "DetailerPipe -> BasicPipe",
|
||||
"EditBasicPipe": "Edit BasicPipe",
|
||||
"EditDetailerPipe": "Edit DetailerPipe",
|
||||
"AnyPipeToBasic": "Any PIPE -> BasicPipe",
|
||||
|
||||
"LatentPixelScale": "Latent Scale (on Pixel Space)",
|
||||
"IterativeLatentUpscale": "Iterative Upscale (Latent/on Pixel Space)",
|
||||
@@ -364,6 +369,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSEGSToMaskBatch": "SEGS to Mask Batch",
|
||||
"ImpactSEGSPicker": "Picker (SEGS)",
|
||||
"ImpactMakeTileSEGS": "Make Tile SEGS",
|
||||
"ImpactSEGSMerge": "SEGS Merge",
|
||||
|
||||
"ImpactDecomposeSEGS": "Decompose (SEGS)",
|
||||
"ImpactAssembleSEGS": "Assemble (SEGS)",
|
||||
@@ -397,6 +403,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactMakeImageBatch": "Make Image Batch",
|
||||
"ImpactMakeMaskList": "Make Mask List",
|
||||
"ImpactMakeMaskBatch": "Make Mask Batch",
|
||||
"ImpactMakeAnyList": "Make List (Any)",
|
||||
|
||||
"ImpactStringSelector": "String Selector",
|
||||
"StringListToString": "String List to String",
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import subprocess
|
||||
import threading
|
||||
import locale
|
||||
import traceback
|
||||
|
||||
|
||||
if sys.argv[0] == 'install.py':
|
||||
sys.path.append('.') # for portable version
|
||||
|
||||
|
||||
impact_path = os.path.join(os.path.dirname(__file__), "modules")
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
|
||||
subpack_repo = "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
|
||||
|
||||
|
||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||
if comfy_path is None:
|
||||
print(f"\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
||||
comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
model_path = os.environ.get('COMFYUI_MODEL_PATH')
|
||||
if model_path is None:
|
||||
try:
|
||||
import folder_paths
|
||||
model_path = folder_paths.models_dir
|
||||
except:
|
||||
pass
|
||||
|
||||
if model_path is None:
|
||||
model_path = os.path.abspath(os.path.join(comfy_path, 'models'))
|
||||
print(f"\n[bold yellow]WARN: The `COMFYUI_MODEL_PATH` environment variable is not set. Assuming `{model_path}` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
||||
|
||||
|
||||
sys.path.append(impact_path)
|
||||
sys.path.append(comfy_path)
|
||||
|
||||
|
||||
# ---
|
||||
def handle_stream(stream, is_stdout):
|
||||
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
||||
|
||||
for msg in stream:
|
||||
if is_stdout:
|
||||
print(msg, end="", file=sys.stdout)
|
||||
else:
|
||||
print(msg, end="", file=sys.stderr)
|
||||
|
||||
|
||||
def process_wrap(cmd_str, cwd=None, handler=None, env=None):
|
||||
print(f"[Impact Pack] EXECUTE: {cmd_str} in '{cwd}'")
|
||||
process = subprocess.Popen(cmd_str, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, text=True, bufsize=1)
|
||||
|
||||
if handler is None:
|
||||
handler = handle_stream
|
||||
|
||||
stdout_thread = threading.Thread(target=handler, args=(process.stdout, True))
|
||||
stderr_thread = threading.Thread(target=handler, args=(process.stderr, False))
|
||||
|
||||
stdout_thread.start()
|
||||
stderr_thread.start()
|
||||
|
||||
stdout_thread.join()
|
||||
stderr_thread.join()
|
||||
|
||||
return process.wait()
|
||||
# ---
|
||||
|
||||
|
||||
try:
|
||||
import platform
|
||||
import folder_paths
|
||||
from torchvision.datasets.utils import download_url
|
||||
import impact.config
|
||||
|
||||
print("### ComfyUI-Impact-Pack: Check dependencies")
|
||||
def ensure_subpack():
|
||||
import git
|
||||
if os.path.exists(subpack_path):
|
||||
try:
|
||||
repo = git.Repo(subpack_path)
|
||||
repo.remotes.origin.pull()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
if platform.system() == 'Windows':
|
||||
print(f"[ComfyUI-Impact-Pack] Please turn off ComfyUI and remove '{subpack_path}' and restart ComfyUI.")
|
||||
else:
|
||||
shutil.rmtree(subpack_path)
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
else:
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
|
||||
|
||||
def install():
|
||||
subpack_install_script = os.path.join(subpack_path, "install.py")
|
||||
|
||||
print(f"### ComfyUI-Impact-Pack: Updating subpack")
|
||||
ensure_subpack() # The installation of the subpack must take place before ensure_pip. cv2 triggers a permission error.
|
||||
|
||||
new_env = os.environ.copy()
|
||||
new_env["COMFYUI_PATH"] = comfy_path
|
||||
new_env["COMFYUI_MODEL_PATH"] = model_path
|
||||
|
||||
if os.path.exists(subpack_install_script):
|
||||
if not is_requirements_installed(os.path.join(subpack_path, 'requirements.txt')):
|
||||
process_wrap(pip_install + ['-r', 'requirements.txt'], cwd=subpack_path)
|
||||
|
||||
process_wrap([sys.executable, 'install.py'], cwd=subpack_path, env=new_env)
|
||||
else:
|
||||
print(f"### ComfyUI-Impact-Pack: (Install Failed) Subpack\nFile not found: `{subpack_install_script}`")
|
||||
|
||||
# Download model
|
||||
print("### ComfyUI-Impact-Pack: Check basic models")
|
||||
sam_path = os.path.join(model_path, "sams")
|
||||
onnx_path = os.path.join(model_path, "onnx")
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'skip_download_model')):
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
bbox_path = os.path.join(model_path, "mmdets", "bbox")
|
||||
if not os.path.exists(bbox_path):
|
||||
os.makedirs(bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.pth")):
|
||||
download_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth", bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.py")):
|
||||
download_url("https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py", bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(sam_path, "sam_vit_b_01ec64.pth")):
|
||||
download_url("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", sam_path)
|
||||
|
||||
if not os.path.exists(onnx_path):
|
||||
print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})")
|
||||
os.mkdir(onnx_path)
|
||||
|
||||
impact.config.write_config()
|
||||
|
||||
install()
|
||||
|
||||
except Exception as e:
|
||||
print("[ERROR] ComfyUI-Impact-Pack: Dependency installation has failed. Please install manually.")
|
||||
traceback.print_exc()
|
||||
+14
-6
@@ -353,7 +353,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
this.outputs[0].type = origin_type;
|
||||
this.outputs[0].name = origin_type;
|
||||
this.outputs[0].name = 'output1';
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -383,7 +383,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
if(this.widgets) {
|
||||
if(this.widgets?.length) {
|
||||
this.widgets[0].options.max = select_slot?this.outputs.length-1:this.outputs.length;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
if(this.widgets[0].options.max > 0 && this.widgets[0].value == 0)
|
||||
@@ -394,7 +394,7 @@ app.registerExtension({
|
||||
|
||||
if (nodeData.name === 'ImpactMakeImageList' || nodeData.name === 'ImpactMakeImageBatch' ||
|
||||
nodeData.name === 'ImpactMakeMaskList' || nodeData.name === 'ImpactMakeMaskBatch' ||
|
||||
nodeData.name === 'CombineRegionalPrompts' ||
|
||||
nodeData.name === 'ImpactMakeAnyList' || nodeData.name === 'CombineRegionalPrompts' ||
|
||||
nodeData.name === 'ImpactCombineConditionings' || nodeData.name === 'ImpactConcatConditionings' ||
|
||||
nodeData.name === 'ImpactSEGSConcat' ||
|
||||
nodeData.name === 'ImpactSwitch' || nodeData.name === 'LatentSwitch' || nodeData.name == 'SEGSSwitch') {
|
||||
@@ -411,6 +411,10 @@ app.registerExtension({
|
||||
input_name = "mask";
|
||||
break;
|
||||
|
||||
case 'ImpactMakeAnyList':
|
||||
input_name = "value";
|
||||
break;
|
||||
|
||||
case 'ImpactSEGSConcat':
|
||||
input_name = "segs";
|
||||
break;
|
||||
@@ -479,8 +483,12 @@ app.registerExtension({
|
||||
|
||||
if(this.inputs[0].type == '*'){
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
let origin_type = node.outputs[link_info.origin_slot].type;
|
||||
|
||||
let origin_type = node.outputs[link_info.origin_slot]?.type;
|
||||
if(link_info.target_slot == 0 && this.inputs.length > 1) {
|
||||
origin_type = this.inputs[1].type;
|
||||
node.connect(link_info.origin_slot, node.id, 'input1');
|
||||
}
|
||||
|
||||
if(origin_type == '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
return;
|
||||
@@ -533,7 +541,7 @@ app.registerExtension({
|
||||
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
|
||||
}
|
||||
|
||||
if(this.widgets) {
|
||||
if(this.widgets?.length) {
|
||||
this.widgets[0].options.max = select_slot?this.inputs.length-1:this.inputs.length;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
if(this.widgets[0].options.max > 0 && this.widgets[0].value == 0)
|
||||
|
||||
@@ -20,7 +20,8 @@ class PreviewBridge:
|
||||
"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."})
|
||||
"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."}),
|
||||
"restore_mask": (["never", "always", "if_same_size"], {"tooltip": "if_same_size: If the changed input image is the same size as the previous image, restore using the last saved mask\nalways: Whenever the input image changes, always restore using the last saved mask\nnever: Do not restore the mask.\n`restore_mask` has higher priority than `block`"}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
@@ -75,7 +76,7 @@ class PreviewBridge:
|
||||
|
||||
return image, mask.unsqueeze(0), ui_item
|
||||
|
||||
def doit(self, images, image, unique_id, block=False, prompt=None, extra_pnginfo=None):
|
||||
def doit(self, images, image, unique_id, block=False, restore_mask="never", prompt=None, extra_pnginfo=None):
|
||||
need_refresh = False
|
||||
|
||||
if unique_id not in core.preview_bridge_cache:
|
||||
@@ -88,10 +89,25 @@ class PreviewBridge:
|
||||
pixels, mask, path_item = PreviewBridge.load_image(image)
|
||||
image = [path_item]
|
||||
else:
|
||||
res = nodes.PreviewImage().save_images(images, filename_prefix="PreviewBridge/PB-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
if restore_mask != "never":
|
||||
mask = core.preview_bridge_last_mask_cache.get(unique_id)
|
||||
if mask is None or (restore_mask != "always" and mask.shape[1:] != images.shape[1:3]):
|
||||
mask = None
|
||||
else:
|
||||
mask = None
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
res = nodes.PreviewImage().save_images(images, filename_prefix="PreviewBridge/PB-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
else:
|
||||
masked_images = tensor_convert_rgba(images)
|
||||
resized_mask = resize_mask(mask, (images.shape[1], images.shape[2])).unsqueeze(3)
|
||||
resized_mask = 1 - resized_mask
|
||||
tensor_putalpha(masked_images, resized_mask)
|
||||
res = nodes.PreviewImage().save_images(masked_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")
|
||||
|
||||
path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', image2[0]['filename'])
|
||||
core.set_previewbridge_image(unique_id, path, image2[0])
|
||||
@@ -103,7 +119,7 @@ class PreviewBridge:
|
||||
|
||||
is_empty_mask = torch.all(mask == 0)
|
||||
|
||||
if block and is_empty_mask and core.is_execution_model_version_supported:
|
||||
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:
|
||||
@@ -112,6 +128,9 @@ class PreviewBridge:
|
||||
else:
|
||||
result = pixels, mask
|
||||
|
||||
if not is_empty_mask:
|
||||
core.preview_bridge_last_mask_cache[unique_id] = mask
|
||||
|
||||
return {
|
||||
"ui": {"images": image},
|
||||
"result": result,
|
||||
@@ -196,7 +215,8 @@ class PreviewBridgeLatent:
|
||||
},
|
||||
"optional": {
|
||||
"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."})
|
||||
"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."}),
|
||||
"restore_mask": (["never", "always", "if_same_size"], {"tooltip": "if_same_size: If the changed input latent is the same size as the previous latent, restore using the last saved mask\nalways: Whenever the input latent changes, always restore using the last saved mask\nnever: Do not restore the mask.\n`restore_mask` has higher priority than `block`\nIf the input latent already has a mask, do not restore mask."}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
@@ -252,7 +272,7 @@ class PreviewBridgeLatent:
|
||||
|
||||
return image, mask, ui_item
|
||||
|
||||
def doit(self, latent, image, preview_method, vae_opt=None, block=False, unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
def doit(self, latent, image, preview_method, vae_opt=None, block=False, unique_id=None, restore_mask='never', 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 or 'FLUX.1' in preview_method or 'TAEF1' == preview_method else 4
|
||||
|
||||
@@ -311,13 +331,28 @@ class PreviewBridgeLatent:
|
||||
'type': 'temp',
|
||||
}]
|
||||
|
||||
is_empty_mask = torch.all(mask == 1)
|
||||
is_empty_mask = False
|
||||
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-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
if restore_mask != "never":
|
||||
mask = core.preview_bridge_last_mask_cache.get(unique_id)
|
||||
if mask is None or (restore_mask != "always" and mask.shape[1:] != decoded_image.shape[1:3]):
|
||||
mask = None
|
||||
else:
|
||||
mask = None
|
||||
|
||||
if mask is None:
|
||||
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)
|
||||
else:
|
||||
masked_images = tensor_convert_rgba(decoded_image)
|
||||
resized_mask = resize_mask(mask, (decoded_image.shape[1], decoded_image.shape[2])).unsqueeze(3)
|
||||
resized_mask = 1 - resized_mask
|
||||
tensor_putalpha(masked_images, resized_mask)
|
||||
res = nodes.PreviewImage().save_images(masked_images, filename_prefix="PreviewBridge/PBL-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
|
||||
res_image = res['ui']['images']
|
||||
|
||||
is_empty_mask = True
|
||||
is_empty_mask = torch.all(mask == 1)
|
||||
|
||||
path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', res_image[0]['filename'])
|
||||
core.set_previewbridge_image(unique_id, path, res_image[0])
|
||||
@@ -327,7 +362,7 @@ class PreviewBridgeLatent:
|
||||
|
||||
res_latent = latent
|
||||
|
||||
if block and is_empty_mask and core.is_execution_model_version_supported:
|
||||
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:
|
||||
@@ -336,6 +371,9 @@ class PreviewBridgeLatent:
|
||||
else:
|
||||
result = res_latent, mask
|
||||
|
||||
if not is_empty_mask:
|
||||
core.preview_bridge_last_mask_cache[unique_id] = mask
|
||||
|
||||
return {
|
||||
"ui": {"images": res_image},
|
||||
"result": result,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import configparser
|
||||
import os
|
||||
|
||||
version_code = [7, 6, 2]
|
||||
version_code = [7, 10, 4]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 22
|
||||
dependency_version = 23
|
||||
|
||||
my_path = os.path.dirname(__file__)
|
||||
old_config_path = os.path.join(my_path, "impact-pack.ini")
|
||||
|
||||
+18
-2
@@ -24,6 +24,8 @@ from comfy import model_management
|
||||
from impact import utils
|
||||
from impact import impact_sampling
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import inspect
|
||||
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
@@ -39,7 +41,10 @@ SEG = namedtuple("SEG",
|
||||
pb_id_cnt = time.time()
|
||||
preview_bridge_image_id_map = {}
|
||||
preview_bridge_image_name_map = {}
|
||||
|
||||
preview_bridge_cache = {}
|
||||
preview_bridge_last_mask_cache = {}
|
||||
|
||||
current_prompt = None
|
||||
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]']
|
||||
@@ -1825,13 +1830,14 @@ class ControlNetWrapper:
|
||||
|
||||
class ControlNetAdvancedWrapper:
|
||||
def __init__(self, control_net, strength, start_percent, end_percent, preprocessor, prev_control_net=None,
|
||||
original_size=None, crop_region=None, control_image=None):
|
||||
original_size=None, crop_region=None, control_image=None, vae=None):
|
||||
self.control_net = control_net
|
||||
self.strength = strength
|
||||
self.preprocessor = preprocessor
|
||||
self.prev_control_net = prev_control_net
|
||||
self.start_percent = start_percent
|
||||
self.end_percent = end_percent
|
||||
self.vae = vae
|
||||
|
||||
if original_size is not None and crop_region is not None and control_image is not None:
|
||||
self.control_image = utils.tensor_resize(control_image, original_size[1], original_size[0])
|
||||
@@ -1872,7 +1878,17 @@ class ControlNetAdvancedWrapper:
|
||||
"To use 'ControlNetAdvancedWrapper' for AnimateDiff, 'ComfyUI-Advanced-ControlNet' extension is required.")
|
||||
raise Exception("'ACN_AdvancedControlNetApply' node isn't installed.")
|
||||
else:
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_image, self.strength, self.start_percent, self.end_percent)
|
||||
if self.vae is not None:
|
||||
apply_controlnet = nodes.ControlNetApplyAdvanced().apply_controlnet
|
||||
signature = inspect.signature(apply_controlnet)
|
||||
|
||||
if 'vae' in signature.parameters:
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_image, self.strength, self.start_percent, self.end_percent, vae=self.vae)
|
||||
else:
|
||||
print(f"[Impact Pack] ERROR: The ComfyUI version is outdated. VAE cannot be used in ApplyControlNet.")
|
||||
raise Exception("[Impact Pack] ERROR: The ComfyUI version is outdated. VAE cannot be used in ApplyControlNet.")
|
||||
else:
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_image, self.strength, self.start_percent, self.end_percent)
|
||||
|
||||
return positive, negative, cnet_image_list
|
||||
|
||||
|
||||
@@ -409,9 +409,14 @@ def gc_preview_bridge_cache(json_data):
|
||||
|
||||
for key in list(core.preview_bridge_cache.keys()):
|
||||
if key not in prompt_keys:
|
||||
print(f"key deleted: {key}")
|
||||
# print(f"key deleted [PB]: {key}")
|
||||
del core.preview_bridge_cache[key]
|
||||
|
||||
for key in list(core.preview_bridge_last_mask_cache.keys()):
|
||||
if key not in prompt_keys:
|
||||
# print(f"key deleted [PB_last_mask]: {key}")
|
||||
del core.preview_bridge_last_mask_cache[key]
|
||||
|
||||
|
||||
def workflow_imagereceiver_update(json_data):
|
||||
prompt = json_data['prompt']
|
||||
|
||||
@@ -272,6 +272,24 @@ class ImpactFloat:
|
||||
return (value, )
|
||||
|
||||
|
||||
class ImpactBoolean:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
}
|
||||
|
||||
FUNCTION = "doit"
|
||||
CATEGORY = "ImpactPack/Logic"
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN", )
|
||||
|
||||
def doit(self, value):
|
||||
return (value, )
|
||||
|
||||
|
||||
class ImpactValueSender:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -673,7 +691,7 @@ class ImpactControlBridge:
|
||||
def doit(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
global error_skip_flag
|
||||
|
||||
if core.is_execution_model_version_supported:
|
||||
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.")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import folder_paths
|
||||
import impact.wildcards
|
||||
from impact.utils import any_typ
|
||||
|
||||
|
||||
class ToDetailerPipe:
|
||||
@classmethod
|
||||
@@ -108,6 +110,23 @@ class FromDetailerPipe_SDXL:
|
||||
return detailer_pipe, model, clip, vae, positive, negative, bbox_detector, sam_model_opt, segm_detector_opt, detailer_hook, refiner_model, refiner_clip, refiner_positive, refiner_negative
|
||||
|
||||
|
||||
class AnyPipeToBasic:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {"any_pipe": (any_typ,)},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BASIC_PIPE", )
|
||||
RETURN_NAMES = ("basic_pipe", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Pipe"
|
||||
|
||||
def doit(self, any_pipe):
|
||||
return (any_pipe[:5], )
|
||||
|
||||
|
||||
class ToBasicPipe:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
@@ -704,6 +704,68 @@ class SEGSToMaskBatch:
|
||||
return (mask_batch,)
|
||||
|
||||
|
||||
class SEGSMerge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "SEGS contains multiple SEGs. SEGS Merge integrates several SEGs into a single merged SEG. The label is changed to `merged` and the confidence becomes the minimum confidence. The applied controlnet and cropped_image are removed."
|
||||
|
||||
def doit(self, segs):
|
||||
crop_left = sys.maxsize
|
||||
crop_right = 0
|
||||
crop_top = sys.maxsize
|
||||
crop_bottom = 0
|
||||
|
||||
bbox_left = sys.maxsize
|
||||
bbox_right = 0
|
||||
bbox_top = sys.maxsize
|
||||
bbox_bottom = 0
|
||||
|
||||
min_confidence = 1.0
|
||||
|
||||
for seg in segs[1]:
|
||||
cx1 = seg.crop_region[0]
|
||||
cy1 = seg.crop_region[1]
|
||||
cx2 = seg.crop_region[2]
|
||||
cy2 = seg.crop_region[3]
|
||||
|
||||
bx1 = seg.bbox[0]
|
||||
by1 = seg.bbox[1]
|
||||
bx2 = seg.bbox[2]
|
||||
by2 = seg.bbox[3]
|
||||
|
||||
crop_left = min(crop_left, cx1)
|
||||
crop_top = min(crop_top, cy1)
|
||||
crop_right = max(crop_right, cx2)
|
||||
crop_bottom = max(crop_bottom, cy2)
|
||||
|
||||
bbox_left = min(bbox_left, bx1)
|
||||
bbox_top = min(bbox_top, by1)
|
||||
bbox_right = max(bbox_right, bx2)
|
||||
bbox_bottom = max(bbox_bottom, by2)
|
||||
|
||||
min_confidence = min(min_confidence, seg.confidence)
|
||||
|
||||
combined_mask = core.segs_to_combined_mask(segs)
|
||||
cropped_mask = combined_mask[crop_top:crop_bottom, crop_left:crop_right]
|
||||
cropped_mask = cropped_mask.unsqueeze(0)
|
||||
|
||||
crop_region = [crop_left, crop_top, crop_right, crop_bottom]
|
||||
bbox = [bbox_left, bbox_top, bbox_right, bbox_bottom]
|
||||
|
||||
seg = SEG(None, cropped_mask, min_confidence, crop_region, bbox, 'merged', None)
|
||||
return ((segs[0], [seg]),)
|
||||
|
||||
|
||||
class SEGSConcat:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1300,6 +1362,8 @@ class ControlNetApplySEGS:
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
@@ -1327,7 +1391,8 @@ class ControlNetApplyAdvancedSEGS:
|
||||
},
|
||||
"optional": {
|
||||
"segs_preprocessor": ("SEGS_PREPROCESSOR",),
|
||||
"control_image": ("IMAGE",)
|
||||
"control_image": ("IMAGE",),
|
||||
"vae": ("VAE",)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1337,13 +1402,13 @@ class ControlNetApplyAdvancedSEGS:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
def doit(segs, control_net, strength, start_percent, end_percent, segs_preprocessor=None, control_image=None):
|
||||
def doit(segs, control_net, strength, start_percent, end_percent, segs_preprocessor=None, control_image=None, vae=None):
|
||||
new_segs = []
|
||||
|
||||
for seg in segs[1]:
|
||||
control_net_wrapper = core.ControlNetAdvancedWrapper(control_net, strength, start_percent, end_percent, segs_preprocessor,
|
||||
seg.control_net_wrapper, original_size=segs[0], crop_region=seg.crop_region,
|
||||
control_image=control_image)
|
||||
control_image=control_image, vae=vae)
|
||||
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, control_net_wrapper)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
|
||||
@@ -17,9 +17,16 @@ class GeneralSwitch:
|
||||
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():
|
||||
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})
|
||||
if stack[2].function == 'get_input_info':
|
||||
# bypass validation
|
||||
class AllContainer:
|
||||
def __contains__(self, item):
|
||||
return True
|
||||
|
||||
def __getitem__(self, key):
|
||||
return any_typ, {"lazy": True}
|
||||
|
||||
dyn_inputs = AllContainer()
|
||||
|
||||
inputs = {"required": {
|
||||
"select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1, "tooltip": "The input number you want to output among the inputs"}),
|
||||
@@ -163,7 +170,7 @@ class GeneralInversedSwitch:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
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
|
||||
else:
|
||||
print("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
|
||||
@@ -181,7 +188,7 @@ class GeneralInversedSwitch:
|
||||
for i in range(0, cnt + 1):
|
||||
if select == i+1:
|
||||
res.append(input)
|
||||
elif core.is_execution_model_version_supported:
|
||||
elif core.is_execution_model_version_supported():
|
||||
res.append(ExecutionBlocker(None))
|
||||
else:
|
||||
res.append(None)
|
||||
@@ -392,6 +399,30 @@ class ImageBatchToImageList:
|
||||
return (images, )
|
||||
|
||||
|
||||
class MakeAnyList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {"value1": (any_typ,), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ,)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
values = []
|
||||
|
||||
for k, v in kwargs.items():
|
||||
if v is not None:
|
||||
values.append(v)
|
||||
|
||||
return (values, )
|
||||
|
||||
|
||||
class MakeMaskList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
@@ -259,7 +259,7 @@ def process(text, seed=None):
|
||||
|
||||
|
||||
def is_numeric_string(input_str):
|
||||
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None
|
||||
return re.match(r'^-?\d?+(\.\d+)?$', input_str) is not None
|
||||
|
||||
|
||||
def safe_float(x):
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[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 = "7.6.2"
|
||||
version = "7.10.4"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user