Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48a814315f | ||
|
|
3d90c579e8 | ||
|
|
f03dd5e79e | ||
|
|
e1e95c14d3 | ||
|
|
17e0a05769 | ||
|
|
b3a815b43d | ||
|
|
8ab2e168f7 | ||
|
|
b980035588 | ||
|
|
df330c1b06 | ||
|
|
6d438d8b5d | ||
|
|
7d565d1f7a | ||
|
|
27c5368bcc | ||
|
|
705698faf2 | ||
|
|
cf43fc4e3c | ||
|
|
4c292e684e | ||
|
|
65f1363a10 | ||
|
|
f57d309932 | ||
|
|
6ccf9bab68 | ||
|
|
ac3668d946 | ||
|
|
78d3793a77 | ||
|
|
2346b67766 | ||
|
|
025db4b581 | ||
|
|
f8e16df2be | ||
|
|
7df60b2107 | ||
|
|
b394c158ea |
@@ -8,6 +8,8 @@ This node pack helps to conveniently enhance images through Detector, Detailer,
|
||||
NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pack. To use the UltralyticsDetectorProvider node, please install the ComfyUI-Impact-Subpack separately.
|
||||
|
||||
## NOTICE
|
||||
* V8.19: legacy nodes (mmdet and etc.) are removed
|
||||
* V8.18: Support [facebookresearch/sam2](https://github.com/facebookresearch/sam2) models
|
||||
* V8.0: The `Impact Subpack` is no longer installed automatically. To use `UltralyticsDetectorProvider` nodes, please install the `Impact Subpack` separately.
|
||||
* V7.6: Automatic installation is no longer supported. Please install using ComfyUI-Manager, or manually install requirements.txt and run install.py to complete the installation.
|
||||
* V7.0: Supports Switch based on Execution Model Inversion.
|
||||
@@ -57,9 +59,10 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
### Companion Pack
|
||||
* If you need the `Ultralytics Detector Provider` to use various YOLO detection models, you should also install [ComfyUI-Impact-Subpack](https://github.com/ltdrdata/ComfyUI-Impact-Subpack).
|
||||
|
||||
|
||||
## Custom Nodes
|
||||
### [Detector nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detectors.md)
|
||||
* `SAMLoader` - Loads the SAM model.
|
||||
* `SAMLoader (Impact)` - Loads the SAM model.
|
||||
* `ONNXDetectorProvider` - Loads the ONNX model to provide BBOX_DETECTOR.
|
||||
* `CLIPSegDetectorProvider` - Wrapper for CLIPSeg to provide BBOX_DETECTOR.
|
||||
* You need to install the ComfyUI-CLIPSeg node extension.
|
||||
@@ -70,6 +73,10 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* As a result, it outputs the `combined_mask`, which is a unified mask, and `batch_masks`, which are multiple masks grouped together in batch form.
|
||||
* While `batch_masks` may not be completely separated, it provides functionality to perform some level of segmentation.
|
||||
* `Simple Detector (SEGS)` - Operating primarily with `BBOX_DETECTOR`, and with the additional provision of `SAM_MODEL` or `SEGM_DETECTOR`, this node internally generates improved SEGS through mask operations on both *bbox* and *silhouette*. It serves as a convenient tool to simplify a somewhat intricate workflow.
|
||||
* `Simple Detector for Video (SEGS)` – Performs detection on videos composed of image frames. Instead of using a single mask, it performs detection individually on each image frame and generates a SEGS object with a batch of masks.
|
||||
* `SAM2 Video Detector (SEGS)` – Similar to `Simple Detector for Video (SEGS)`, but utilizes SAM2’s video tracking technology to generate a SEGS object with a batch of masks.
|
||||
* To use this node, you must select a SAM2 model in the SAMLoader.
|
||||
|
||||
|
||||
### ControlNet, IPAdapter
|
||||
* `ControlNetApply (SEGS)` - To apply ControlNet in SEGS, you need to use the Preprocessor Provider node from the Inspire Pack to utilize this node.
|
||||
@@ -79,6 +86,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `ControlNetClear (SEGS)` - Clear applied ControlNet in SEGS
|
||||
* `IPAdapterApply (SEGS)` - To apply IPAdapter in SEGS, you need to use the Preprocessor Provider node from the Inspire Pack to utilize this node.
|
||||
|
||||
|
||||
### Mask operation
|
||||
* `Pixelwise(SEGS & SEGS)` - Performs a 'pixelwise and' operation between two SEGS.
|
||||
* `Pixelwise(SEGS - SEGS)` - Subtracts one SEGS from another.
|
||||
@@ -96,12 +104,13 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `Mask Rect Area` - Create a rectangular mask defined by percentages with preview canvas.
|
||||
* `Mask Rect Area (Advanced)` - Create a rectangular mask defined by pixels and image size.
|
||||
|
||||
|
||||
### [Detailer nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detailers.md)
|
||||
* `Detailer (SEGS)` - Refines the image based on SEGS.
|
||||
* `DetailerDebug (SEGS)` - Refines the image based on SEGS. Additionally, it provides the ability to monitor the cropped image and the refined image of the cropped image.
|
||||
* To prevent regeneration caused by the seed that does not change every time when using 'external_seed', please disable the 'seed random generate' option in the 'Detailer...' node.
|
||||
* `MASK to SEGS` - Generates SEGS based on the mask.
|
||||
* `MASK to SEGS For AnimateDiff` - Generates SEGS based on the mask for AnimateDiff.
|
||||
* `MASK to SEGS For Video` - Generates SEGS based on the mask for Video. (Renamed from `MASK to SEGS For AnimateDiff`)
|
||||
* When using a single mask, convert it to SEGS to apply it to the entire frame.
|
||||
* When using a batch mask, the contour fill feature is disabled.
|
||||
* `MediaPipe FaceMesh to SEGS` - Separate each landmark from the mediapipe facemesh image to create labeled SEGS.
|
||||
@@ -118,6 +127,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `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.
|
||||
* `SEGSPaste` - Pastes the results of SEGS onto the original image.
|
||||
@@ -154,6 +164,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `From SEG_ELT` crop_region - Extract coordinate from crop_region in SEG_ELT
|
||||
* `Count Elt in SEGS` - Number of Elts ins SEGS
|
||||
|
||||
|
||||
### Pipe nodes
|
||||
* `ToDetailerPipe`, `FromDetailerPipe` - These nodes are used to bundle multiple inputs used in the detailer, such as models and vae, ..., into a single DETAILER_PIPE or extract the elements that are bundled in the DETAILER_PIPE.
|
||||
* `ToBasicPipe`, `FromBasicPipe` - These nodes are used to bundle model, clip, vae, positive conditioning, and negative conditioning into a single BASIC_PIPE, or extract each element from the BASIC_PIPE.
|
||||
@@ -166,6 +177,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `PixelTiledKSampleUpscalerProvider` - It is similar to `PixelKSampleUpscalerProvider`, but it uses `ComfyUI_TiledKSampler` and Tiled VAE Decoder/Encoder to avoid GPU VRAM issues at high resolutions.
|
||||
* You need to install the [BlenderNeko/ComfyUI_TiledKSampler](https://github.com/BlenderNeko/ComfyUI_TiledKSampler) node extension.
|
||||
|
||||
|
||||
### PK_HOOK
|
||||
* `DenoiseScheduleHookProvider` - IterativeUpscale provides a hook that gradually changes the denoise to target_denoise as the iterative-step progresses.
|
||||
* `CfgScheduleHookProvider` - IterativeUpscale provides a hook that gradually changes the cfg to target_cfg as the iterative-step progresses.
|
||||
@@ -179,6 +191,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `PixelKSampleHookCombine` - This is used to connect two PK_HOOKs. hook1 is executed first and then hook2 is executed.
|
||||
* If you want to simultaneously change cfg and denoise, you can combine the PK_HOOKs of CfgScheduleHookProvider and PixelKSampleHookCombine.
|
||||
|
||||
|
||||
### DETAILER_HOOK
|
||||
* `NoiseInjectionDetailerHookProvider` - The `detailer_hook` is a hook in the `Detailer` that injects noise during the processing of each SEGS.
|
||||
* `UnsamplerDetailerHookProvider` - Apply Unsampler during each cycle. To use this node, ComfyUI_Noise must be installed.
|
||||
@@ -189,6 +202,11 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `PreviewDetailerHook` - Connecting this hook node helps provide assistance for viewing previews whenever SEGS Detailing tasks are completed. When working with a large number of SEGS, such as Make Tile SEGS, it allows for monitoring the situation as improvements progress incrementally.
|
||||
* Since this is the hook applied when pasting onto the original image, it has no effect on nodes like `SEGSDetailer`.
|
||||
* `VariationNoiseDetailerHookProvider` - Apply variation seed to the detailer. It can be applied in multiple stages through combine.
|
||||
* `CustomSamplerDetailerHookProvider` - Apply a hook that allows you to use a custom sampler in the Detailer nodes. When using `DetailerHookCombine`, the sampler from the first hook is applied.
|
||||
* `LamaRemoverDetailerHookProvider` – Applies Lama Remover to the upscaled image during the detailing stage. If `skip_sampling` is set to True, Lama Remover can be used alone without the detailing stage, allowing it to simply remove detected regions.
|
||||
* Not applicable for **AnimateDiff** detailers. When using `DetailerHookCombine`, `skip_sampling` is only applied if it is set to `True` for all hooks.
|
||||
* To use this node, the node pack at [Layer-norm/comfyui-lama-remover](https://github.com/Layer-norm/comfyui-lama-remover) must be installed.
|
||||
|
||||
|
||||
### Iterative Upscale nodes
|
||||
* `Iterative Upscale (Latent/on Pixel Space)` - The upscaler takes the input upscaler and splits the scale_factor into steps, then iteratively performs upscaling.
|
||||
@@ -196,6 +214,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `Iterative Upscale (Image)` - The upscaler takes the input upscaler and splits the scale_factor into steps, then iteratively performs upscaling. This takes image as input and outputs image as the result.
|
||||
* Internally, this node uses 'Iterative Upscale (Latent)'.
|
||||
|
||||
|
||||
### TwoSamplers nodes
|
||||
* `TwoSamplersForMask` - This node can apply two samplers depending on the mask area. The base_sampler is applied to the area where the mask is 0, while the mask_sampler is applied to the area where the mask is 1.
|
||||
* Note: The latent encoded through VAEEncodeForInpaint cannot be used.
|
||||
@@ -210,6 +229,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `TwoSamplersForMaskUpscalerProvider` - This is an Upscaler that extends TwoSamplersForMask to be used in Iterative Upscale.
|
||||
* TwoSamplersForMaskUpscalerProviderPipe - pipe version of TwoSamplersForMaskUpscalerProvider.
|
||||
|
||||
|
||||
### Image Utils
|
||||
* `PreviewBridge (image)` - This custom node can be used with a bridge for image when using the MaskEditor feature of Clipspace.
|
||||
* `PreviewBridge (latent)` - This custom node can be used with a bridge for latent image when using the MaskEditor feature of Clipspace.
|
||||
@@ -222,12 +242,14 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* 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.
|
||||
* 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}`.
|
||||
* Wildcard files can be used by placing `.txt` or `.yaml` files under either `ComfyUI-Impact-Pack/wildcards` or `ComfyUI-Impact-Pack/custom_wildcards` paths.
|
||||
@@ -239,6 +261,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* If the `Inspire Pack` is installed, you can use **Lora Block Weight** in the form of `LBW=lbw spec;`
|
||||
* `<lora:chunli:1.0:1.0:LBW=B11:0,0,0,0,0,0,0,0,0,0,A,0,0,0,0,0,0;A=0.;>`, `<lora:chunli:1.0:1.0:LBW=0,0,0,0,0,0,0,0,0,0,A,B,0,0,0,0,0;A=0.5;B=0.2;>`, `<lora:chunli:1.0:1.0:LBW=SD-MIDD;>`
|
||||
|
||||
|
||||
### Regional Sampling
|
||||
* These nodes offer the capability to divide regions and perform partial sampling using a mask. Unlike TwoSamplersForMask, sampling for each region is applied during each step.
|
||||
* `RegionalPrompt` - This node combines a **mask** for specifying regions and the **sampler** to apply to each region to create `REGIONAL_PROMPTS`.
|
||||
@@ -270,6 +293,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `Make List (Any)` - Create a list with arbitrary values.
|
||||
* `Select Nth Item (Any list)` - Selects the Nth item from a list. If the index is out of range, it returns the last item in the list.
|
||||
|
||||
|
||||
### Logics (experimental)
|
||||
* These nodes are experimental nodes designed to implement the logic for loops and dynamic switching.
|
||||
* `ImpactCompare`, `ImpactConditionalBranch`, `ImpactConditionalBranchSelMode`, `ImpactInt`, `ImpactBoolean`, `ImpactValueSender`, `ImpactValueReceiver`, `ImpactImageInfo`, `ImpactMinMax`, `ImpactNeg`, `ImpactConditionalStopIteration`
|
||||
@@ -291,6 +315,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* You can find the `node_id` by checking through [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) using the format `Badge: #ID Nickname`.
|
||||
* Experimental set of nodes for implementing loop functionality (tutorial to be prepared later / [example workflow](test/loop-test.json)).
|
||||
|
||||
|
||||
### HuggingFace nodes
|
||||
* These nodes provide functionalities based on HuggingFace repository models.
|
||||
* The path where the HuggingFace model cache is stored can be changed through the `HF_HOME` environment variable.
|
||||
@@ -367,15 +392,12 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
## Config example
|
||||
* Once you run the Impact Pack for the first time, an `impact-pack.ini` file will be automatically generated in the Impact Pack directory. You can modify this configuration file to customize the default behavior.
|
||||
* `dependency_version` - don't touch this
|
||||
* `mmdet_skip` - disable MMDet based nodes and legacy nodes if `True`
|
||||
* `sam_editor_cpu` - use cpu for `SAM editor` instead of gpu
|
||||
* sam_editor_model: Specify the SAM model for the SAM editor.
|
||||
* You can download various SAM models using ComfyUI-Manager.
|
||||
* Path to SAM model: `ComfyUI/models/sams`
|
||||
```
|
||||
[default]
|
||||
dependency_version = 9
|
||||
mmdet_skip = True
|
||||
sam_editor_cpu = False
|
||||
sam_editor_model = sam_vit_b_01ec64.pth
|
||||
```
|
||||
@@ -396,7 +418,6 @@ sam_editor_model = sam_vit_b_01ec64.pth
|
||||

|
||||
* The face that has been damaged due to low resolution is restored with high resolution by generating and synthesizing it, in order to restore the details.
|
||||
* The FaceDetailer node is a combination of a Detector node for face detection and a Detailer node for image enhancement. See the [Advanced Tutorial](https://github.com/ltdrdata/ComfyUI-extension-tutorials/raw/Main/ComfyUI-Impact-Pack/tutorial/advanced.md) for a more detailed explanation.
|
||||
* Pass the MMDetLoader 's bbox model and the detection model loaded by SAMLoader to FaceDetailer . Since it performs the function of KSampler for image enhancement, it overlaps with KSampler's options.
|
||||
* The MASK output of FaceDetailer provides a visualization of where the detected and enhanced areas are.
|
||||
|
||||
 
|
||||
@@ -488,3 +509,5 @@ BlenderNeok/[ComfyUI_Noise](https://github.com/BlenderNeko/ComfyUI_Noise) - The
|
||||
WASasquatch/[was-node-suite-comfyui](https://github.com/WASasquatch/was-node-suite-comfyui) - A powerful custom node extensions of ComfyUI.
|
||||
|
||||
Trung0246/[ComfyUI-0246](https://github.com/Trung0246/ComfyUI-0246) - Nice bypass hack!
|
||||
|
||||
Layer-norm/[comfyui-lama-remover](https://github.com/Layer-norm/comfyui-lama-remover) - Required for using `LamaRemoverDetailerHook`.
|
||||
|
||||
+224
-261
@@ -5,11 +5,10 @@
|
||||
@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.
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import folder_paths
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
comfy_path = os.path.dirname(folder_paths.__file__)
|
||||
impact_path = os.path.join(os.path.dirname(__file__))
|
||||
@@ -18,28 +17,23 @@ modules_path = os.path.join(os.path.dirname(__file__), "modules")
|
||||
sys.path.append(modules_path)
|
||||
|
||||
import impact.config
|
||||
print(f"### Loading: ComfyUI-Impact-Pack ({impact.config.version})")
|
||||
logging.info(f"### Loading: ComfyUI-Impact-Pack ({impact.config.version})")
|
||||
|
||||
# Core
|
||||
# recheck dependencies for colab
|
||||
try:
|
||||
import folder_paths
|
||||
import torch
|
||||
import cv2
|
||||
from cv2 import setNumThreads
|
||||
import numpy as np
|
||||
import torch # noqa: F401
|
||||
import cv2 # noqa: F401
|
||||
from cv2 import setNumThreads # noqa: F401
|
||||
import numpy as np # noqa: F401
|
||||
import comfy.samplers
|
||||
import comfy.sd
|
||||
import warnings
|
||||
from PIL import Image, ImageFilter
|
||||
from skimage.measure import label, regionprops
|
||||
from collections import namedtuple
|
||||
import piexif
|
||||
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
import mmcv
|
||||
from mmdet.apis import (inference_detector, init_detector)
|
||||
from mmdet.evaluation import get_classes
|
||||
import comfy.sd # noqa: F401
|
||||
from PIL import Image, ImageFilter # noqa: F401
|
||||
from skimage.measure import label, regionprops # noqa: F401
|
||||
from collections import namedtuple # noqa: F401
|
||||
import piexif # noqa: F401
|
||||
import nodes
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.error("[Impact Pack] Failed to import due to several dependencies are missing!!!!")
|
||||
@@ -48,18 +42,18 @@ except Exception as e:
|
||||
|
||||
import impact.impact_server # to load server api
|
||||
|
||||
from .modules.impact.impact_pack import *
|
||||
from .modules.impact.detectors import *
|
||||
from .modules.impact.pipe import *
|
||||
from .modules.impact.logics import *
|
||||
from .modules.impact.util_nodes import *
|
||||
from .modules.impact.segs_nodes import *
|
||||
from .modules.impact.special_samplers import *
|
||||
from .modules.impact.hf_nodes import *
|
||||
from .modules.impact.bridge_nodes import *
|
||||
from .modules.impact.hook_nodes import *
|
||||
from .modules.impact.animatediff_nodes import *
|
||||
from .modules.impact.segs_upscaler import *
|
||||
from .modules.impact.impact_pack import * # noqa: F403
|
||||
from .modules.impact.detectors import * # noqa: F403
|
||||
from .modules.impact.pipe import * # noqa: F403
|
||||
from .modules.impact.logics import * # noqa: F403
|
||||
from .modules.impact.util_nodes import * # noqa: F403
|
||||
from .modules.impact.segs_nodes import * # noqa: F403
|
||||
from .modules.impact.special_samplers import * # noqa: F403
|
||||
from .modules.impact.hf_nodes import * # noqa: F403
|
||||
from .modules.impact.bridge_nodes import * # noqa: F403
|
||||
from .modules.impact.hook_nodes import * # noqa: F403
|
||||
from .modules.impact.animatediff_nodes import * # noqa: F403
|
||||
from .modules.impact.segs_upscaler import * # noqa: F403
|
||||
|
||||
import threading
|
||||
|
||||
@@ -68,231 +62,234 @@ threading.Thread(target=impact.wildcards.wildcard_load).start()
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SAMLoader": SAMLoader,
|
||||
"CLIPSegDetectorProvider": CLIPSegDetectorProvider,
|
||||
"ONNXDetectorProvider": ONNXDetectorProvider,
|
||||
"SAMLoader": SAMLoader, # noqa: F405
|
||||
"CLIPSegDetectorProvider": CLIPSegDetectorProvider, # noqa: F405
|
||||
"ONNXDetectorProvider": ONNXDetectorProvider, # noqa: F405
|
||||
|
||||
"BitwiseAndMaskForEach": BitwiseAndMaskForEach,
|
||||
"SubtractMaskForEach": SubtractMaskForEach,
|
||||
"BitwiseAndMaskForEach": BitwiseAndMaskForEach, # noqa: F405
|
||||
"SubtractMaskForEach": SubtractMaskForEach, # noqa: F405
|
||||
|
||||
"DetailerForEach": DetailerForEach,
|
||||
"DetailerForEachDebug": DetailerForEachTest,
|
||||
"DetailerForEachPipe": DetailerForEachPipe,
|
||||
"DetailerForEachDebugPipe": DetailerForEachTestPipe,
|
||||
"DetailerForEachPipeForAnimateDiff": DetailerForEachPipeForAnimateDiff,
|
||||
"DetailerForEach": DetailerForEach, # noqa: F405
|
||||
"DetailerForEachDebug": DetailerForEachTest, # noqa: F405
|
||||
"DetailerForEachPipe": DetailerForEachPipe, # noqa: F405
|
||||
"DetailerForEachDebugPipe": DetailerForEachTestPipe, # noqa: F405
|
||||
"DetailerForEachPipeForAnimateDiff": DetailerForEachPipeForAnimateDiff, # noqa: F405
|
||||
|
||||
"SAMDetectorCombined": SAMDetectorCombined,
|
||||
"SAMDetectorSegmented": SAMDetectorSegmented,
|
||||
"SAMDetectorCombined": SAMDetectorCombined, # noqa: F405
|
||||
"SAMDetectorSegmented": SAMDetectorSegmented, # noqa: F405
|
||||
|
||||
"FaceDetailer": FaceDetailer,
|
||||
"FaceDetailerPipe": FaceDetailerPipe,
|
||||
"MaskDetailerPipe": MaskDetailerPipe,
|
||||
"FaceDetailer": FaceDetailer, # noqa: F405
|
||||
"FaceDetailerPipe": FaceDetailerPipe, # noqa: F405
|
||||
"MaskDetailerPipe": MaskDetailerPipe, # noqa: F405
|
||||
|
||||
"ToDetailerPipe": ToDetailerPipe,
|
||||
"ToDetailerPipeSDXL": ToDetailerPipeSDXL,
|
||||
"FromDetailerPipe": FromDetailerPipe,
|
||||
"FromDetailerPipe_v2": FromDetailerPipe_v2,
|
||||
"FromDetailerPipeSDXL": FromDetailerPipe_SDXL,
|
||||
"AnyPipeToBasic": AnyPipeToBasic,
|
||||
"ToBasicPipe": ToBasicPipe,
|
||||
"FromBasicPipe": FromBasicPipe,
|
||||
"FromBasicPipe_v2": FromBasicPipe_v2,
|
||||
"BasicPipeToDetailerPipe": BasicPipeToDetailerPipe,
|
||||
"BasicPipeToDetailerPipeSDXL": BasicPipeToDetailerPipeSDXL,
|
||||
"DetailerPipeToBasicPipe": DetailerPipeToBasicPipe,
|
||||
"EditBasicPipe": EditBasicPipe,
|
||||
"EditDetailerPipe": EditDetailerPipe,
|
||||
"EditDetailerPipeSDXL": EditDetailerPipeSDXL,
|
||||
"ToDetailerPipe": ToDetailerPipe, # noqa: F405
|
||||
"ToDetailerPipeSDXL": ToDetailerPipeSDXL, # noqa: F405
|
||||
"FromDetailerPipe": FromDetailerPipe, # noqa: F405
|
||||
"FromDetailerPipe_v2": FromDetailerPipe_v2, # noqa: F405
|
||||
"FromDetailerPipeSDXL": FromDetailerPipe_SDXL, # noqa: F405
|
||||
"AnyPipeToBasic": AnyPipeToBasic, # noqa: F405
|
||||
"ToBasicPipe": ToBasicPipe, # noqa: F405
|
||||
"FromBasicPipe": FromBasicPipe, # noqa: F405
|
||||
"FromBasicPipe_v2": FromBasicPipe_v2, # noqa: F405
|
||||
"BasicPipeToDetailerPipe": BasicPipeToDetailerPipe, # noqa: F405
|
||||
"BasicPipeToDetailerPipeSDXL": BasicPipeToDetailerPipeSDXL, # noqa: F405
|
||||
"DetailerPipeToBasicPipe": DetailerPipeToBasicPipe, # noqa: F405
|
||||
"EditBasicPipe": EditBasicPipe, # noqa: F405
|
||||
"EditDetailerPipe": EditDetailerPipe, # noqa: F405
|
||||
"EditDetailerPipeSDXL": EditDetailerPipeSDXL, # noqa: F405
|
||||
|
||||
"LatentPixelScale": LatentPixelScale,
|
||||
"PixelKSampleUpscalerProvider": PixelKSampleUpscalerProvider,
|
||||
"PixelKSampleUpscalerProviderPipe": PixelKSampleUpscalerProviderPipe,
|
||||
"IterativeLatentUpscale": IterativeLatentUpscale,
|
||||
"IterativeImageUpscale": IterativeImageUpscale,
|
||||
"PixelTiledKSampleUpscalerProvider": PixelTiledKSampleUpscalerProvider,
|
||||
"PixelTiledKSampleUpscalerProviderPipe": PixelTiledKSampleUpscalerProviderPipe,
|
||||
"TwoSamplersForMaskUpscalerProvider": TwoSamplersForMaskUpscalerProvider,
|
||||
"TwoSamplersForMaskUpscalerProviderPipe": TwoSamplersForMaskUpscalerProviderPipe,
|
||||
"LatentPixelScale": LatentPixelScale, # noqa: F405
|
||||
"PixelKSampleUpscalerProvider": PixelKSampleUpscalerProvider, # noqa: F405
|
||||
"PixelKSampleUpscalerProviderPipe": PixelKSampleUpscalerProviderPipe, # noqa: F405
|
||||
"IterativeLatentUpscale": IterativeLatentUpscale, # noqa: F405
|
||||
"IterativeImageUpscale": IterativeImageUpscale, # noqa: F405
|
||||
"PixelTiledKSampleUpscalerProvider": PixelTiledKSampleUpscalerProvider, # noqa: F405
|
||||
"PixelTiledKSampleUpscalerProviderPipe": PixelTiledKSampleUpscalerProviderPipe, # noqa: F405
|
||||
"TwoSamplersForMaskUpscalerProvider": TwoSamplersForMaskUpscalerProvider, # noqa: F405
|
||||
"TwoSamplersForMaskUpscalerProviderPipe": TwoSamplersForMaskUpscalerProviderPipe, # noqa: F405
|
||||
|
||||
"PixelKSampleHookCombine": PixelKSampleHookCombine,
|
||||
"DenoiseScheduleHookProvider": DenoiseScheduleHookProvider,
|
||||
"StepsScheduleHookProvider": StepsScheduleHookProvider,
|
||||
"CfgScheduleHookProvider": CfgScheduleHookProvider,
|
||||
"NoiseInjectionHookProvider": NoiseInjectionHookProvider,
|
||||
"UnsamplerHookProvider": UnsamplerHookProvider,
|
||||
"CoreMLDetailerHookProvider": CoreMLDetailerHookProvider,
|
||||
"PreviewDetailerHookProvider": PreviewDetailerHookProvider,
|
||||
"PixelKSampleHookCombine": PixelKSampleHookCombine, # noqa: F405
|
||||
"DenoiseScheduleHookProvider": DenoiseScheduleHookProvider, # noqa: F405
|
||||
"StepsScheduleHookProvider": StepsScheduleHookProvider, # noqa: F405
|
||||
"CfgScheduleHookProvider": CfgScheduleHookProvider, # noqa: F405
|
||||
"NoiseInjectionHookProvider": NoiseInjectionHookProvider, # noqa: F405
|
||||
"UnsamplerHookProvider": UnsamplerHookProvider, # noqa: F405
|
||||
"CoreMLDetailerHookProvider": CoreMLDetailerHookProvider, # noqa: F405
|
||||
"PreviewDetailerHookProvider": PreviewDetailerHookProvider, # noqa: F405
|
||||
"CustomSamplerDetailerHookProvider": CustomSamplerDetailerHookProvider, # noqa: F405
|
||||
"LamaRemoverDetailerHookProvider": LamaRemoverDetailerHookProvider, # noqa: F405
|
||||
|
||||
"DetailerHookCombine": DetailerHookCombine,
|
||||
"NoiseInjectionDetailerHookProvider": NoiseInjectionDetailerHookProvider,
|
||||
"UnsamplerDetailerHookProvider": UnsamplerDetailerHookProvider,
|
||||
"DenoiseSchedulerDetailerHookProvider": DenoiseSchedulerDetailerHookProvider,
|
||||
"SEGSOrderedFilterDetailerHookProvider": SEGSOrderedFilterDetailerHookProvider,
|
||||
"SEGSRangeFilterDetailerHookProvider": SEGSRangeFilterDetailerHookProvider,
|
||||
"SEGSLabelFilterDetailerHookProvider": SEGSLabelFilterDetailerHookProvider,
|
||||
"VariationNoiseDetailerHookProvider": VariationNoiseDetailerHookProvider,
|
||||
"DetailerHookCombine": DetailerHookCombine, # noqa: F405
|
||||
"NoiseInjectionDetailerHookProvider": NoiseInjectionDetailerHookProvider, # noqa: F405
|
||||
"UnsamplerDetailerHookProvider": UnsamplerDetailerHookProvider, # noqa: F405
|
||||
"DenoiseSchedulerDetailerHookProvider": DenoiseSchedulerDetailerHookProvider, # noqa: F405
|
||||
"SEGSOrderedFilterDetailerHookProvider": SEGSOrderedFilterDetailerHookProvider, # noqa: F405
|
||||
"SEGSRangeFilterDetailerHookProvider": SEGSRangeFilterDetailerHookProvider, # noqa: F405
|
||||
"SEGSLabelFilterDetailerHookProvider": SEGSLabelFilterDetailerHookProvider, # noqa: F405
|
||||
"VariationNoiseDetailerHookProvider": VariationNoiseDetailerHookProvider, # noqa: F405
|
||||
# "CustomNoiseDetailerHookProvider": CustomNoiseDetailerHookProvider,
|
||||
|
||||
"BitwiseAndMask": BitwiseAndMask,
|
||||
"SubtractMask": SubtractMask,
|
||||
"AddMask": AddMask,
|
||||
"MaskRectArea": MaskRectArea,
|
||||
"MaskRectAreaAdvanced": MaskRectAreaAdvanced,
|
||||
"ImpactSegsAndMask": SegsBitwiseAndMask,
|
||||
"ImpactSegsAndMaskForEach": SegsBitwiseAndMaskForEach,
|
||||
"EmptySegs": EmptySEGS,
|
||||
"ImpactFlattenMask": FlattenMask,
|
||||
"BitwiseAndMask": BitwiseAndMask, # noqa: F405
|
||||
"SubtractMask": SubtractMask, # noqa: F405
|
||||
"AddMask": AddMask, # noqa: F405
|
||||
"MaskRectArea": MaskRectArea, # noqa: F405
|
||||
"MaskRectAreaAdvanced": MaskRectAreaAdvanced, # noqa: F405
|
||||
"ImpactSegsAndMask": SegsBitwiseAndMask, # noqa: F405
|
||||
"ImpactSegsAndMaskForEach": SegsBitwiseAndMaskForEach, # noqa: F405
|
||||
"EmptySegs": EmptySEGS, # noqa: F405
|
||||
"ImpactFlattenMask": FlattenMask, # noqa: F405
|
||||
|
||||
"MediaPipeFaceMeshToSEGS": MediaPipeFaceMeshToSEGS,
|
||||
"MaskToSEGS": MaskToSEGS,
|
||||
"MaskToSEGS_for_AnimateDiff": MaskToSEGS_for_AnimateDiff,
|
||||
"ToBinaryMask": ToBinaryMask,
|
||||
"MasksToMaskList": MasksToMaskList,
|
||||
"MaskListToMaskBatch": MaskListToMaskBatch,
|
||||
"ImageListToImageBatch": ImageListToImageBatch,
|
||||
"SetDefaultImageForSEGS": DefaultImageForSEGS,
|
||||
"RemoveImageFromSEGS": RemoveImageFromSEGS,
|
||||
"MediaPipeFaceMeshToSEGS": MediaPipeFaceMeshToSEGS, # noqa: F405
|
||||
"MaskToSEGS": MaskToSEGS, # noqa: F405
|
||||
"MaskToSEGS_for_AnimateDiff": MaskToSEGS_for_AnimateDiff, # noqa: F405
|
||||
"ToBinaryMask": ToBinaryMask, # noqa: F405
|
||||
"MasksToMaskList": MasksToMaskList, # noqa: F405
|
||||
"MaskListToMaskBatch": MaskListToMaskBatch, # noqa: F405
|
||||
"ImageListToImageBatch": ImageListToImageBatch, # noqa: F405
|
||||
"SetDefaultImageForSEGS": DefaultImageForSEGS, # noqa: F405
|
||||
"RemoveImageFromSEGS": RemoveImageFromSEGS, # noqa: F405
|
||||
|
||||
"BboxDetectorSEGS": BboxDetectorForEach,
|
||||
"SegmDetectorSEGS": SegmDetectorForEach,
|
||||
"ONNXDetectorSEGS": BboxDetectorForEach,
|
||||
"ImpactSimpleDetectorSEGS_for_AD": SimpleDetectorForAnimateDiff,
|
||||
"ImpactSimpleDetectorSEGS": SimpleDetectorForEach,
|
||||
"ImpactSimpleDetectorSEGSPipe": SimpleDetectorForEachPipe,
|
||||
"ImpactControlNetApplySEGS": ControlNetApplySEGS,
|
||||
"ImpactControlNetApplyAdvancedSEGS": ControlNetApplyAdvancedSEGS,
|
||||
"ImpactControlNetClearSEGS": ControlNetClearSEGS,
|
||||
"ImpactIPAdapterApplySEGS": IPAdapterApplySEGS,
|
||||
"BboxDetectorSEGS": BboxDetectorForEach, # noqa: F405
|
||||
"SegmDetectorSEGS": SegmDetectorForEach, # noqa: F405
|
||||
"ONNXDetectorSEGS": BboxDetectorForEach, # noqa: F405
|
||||
"ImpactSimpleDetectorSEGS_for_AD": SimpleDetectorForAnimateDiff, # noqa: F405
|
||||
"ImpactSAM2VideoDetectorSEGS": SAM2VideoDetectorSEGS, # noqa: F405
|
||||
"ImpactSimpleDetectorSEGS": SimpleDetectorForEach, # noqa: F405
|
||||
"ImpactSimpleDetectorSEGSPipe": SimpleDetectorForEachPipe, # noqa: F405
|
||||
"ImpactControlNetApplySEGS": ControlNetApplySEGS, # noqa: F405
|
||||
"ImpactControlNetApplyAdvancedSEGS": ControlNetApplyAdvancedSEGS, # noqa: F405
|
||||
"ImpactControlNetClearSEGS": ControlNetClearSEGS, # noqa: F405
|
||||
"ImpactIPAdapterApplySEGS": IPAdapterApplySEGS, # noqa: F405
|
||||
|
||||
"ImpactDecomposeSEGS": DecomposeSEGS,
|
||||
"ImpactAssembleSEGS": AssembleSEGS,
|
||||
"ImpactFrom_SEG_ELT": From_SEG_ELT,
|
||||
"ImpactEdit_SEG_ELT": Edit_SEG_ELT,
|
||||
"ImpactDilate_Mask_SEG_ELT": Dilate_SEG_ELT,
|
||||
"ImpactDilateMask": DilateMask,
|
||||
"ImpactGaussianBlurMask": GaussianBlurMask,
|
||||
"ImpactDilateMaskInSEGS": DilateMaskInSEGS,
|
||||
"ImpactGaussianBlurMaskInSEGS": GaussianBlurMaskInSEGS,
|
||||
"ImpactScaleBy_BBOX_SEG_ELT": SEG_ELT_BBOX_ScaleBy,
|
||||
"ImpactFrom_SEG_ELT_bbox": From_SEG_ELT_bbox,
|
||||
"ImpactFrom_SEG_ELT_crop_region": From_SEG_ELT_crop_region,
|
||||
"ImpactCount_Elts_in_SEGS": Count_Elts_in_SEGS,
|
||||
"ImpactDecomposeSEGS": DecomposeSEGS, # noqa: F405
|
||||
"ImpactAssembleSEGS": AssembleSEGS, # noqa: F405
|
||||
"ImpactFrom_SEG_ELT": From_SEG_ELT, # noqa: F405
|
||||
"ImpactEdit_SEG_ELT": Edit_SEG_ELT, # noqa: F405
|
||||
"ImpactDilate_Mask_SEG_ELT": Dilate_SEG_ELT, # noqa: F405
|
||||
"ImpactDilateMask": DilateMask, # noqa: F405
|
||||
"ImpactGaussianBlurMask": GaussianBlurMask, # noqa: F405
|
||||
"ImpactDilateMaskInSEGS": DilateMaskInSEGS, # noqa: F405
|
||||
"ImpactGaussianBlurMaskInSEGS": GaussianBlurMaskInSEGS, # noqa: F405
|
||||
"ImpactScaleBy_BBOX_SEG_ELT": SEG_ELT_BBOX_ScaleBy, # noqa: F405
|
||||
"ImpactFrom_SEG_ELT_bbox": From_SEG_ELT_bbox, # noqa: F405
|
||||
"ImpactFrom_SEG_ELT_crop_region": From_SEG_ELT_crop_region, # noqa: F405
|
||||
"ImpactCount_Elts_in_SEGS": Count_Elts_in_SEGS, # noqa: F405
|
||||
|
||||
"BboxDetectorCombined_v2": BboxDetectorCombined,
|
||||
"SegmDetectorCombined_v2": SegmDetectorCombined,
|
||||
"SegsToCombinedMask": SegsToCombinedMask,
|
||||
"BboxDetectorCombined_v2": BboxDetectorCombined, # noqa: F405
|
||||
"SegmDetectorCombined_v2": SegmDetectorCombined, # noqa: F405
|
||||
"SegsToCombinedMask": SegsToCombinedMask, # noqa: F405
|
||||
|
||||
"KSamplerProvider": KSamplerProvider,
|
||||
"TwoSamplersForMask": TwoSamplersForMask,
|
||||
"TiledKSamplerProvider": TiledKSamplerProvider,
|
||||
"KSamplerProvider": KSamplerProvider, # noqa: F405
|
||||
"TwoSamplersForMask": TwoSamplersForMask, # noqa: F405
|
||||
"TiledKSamplerProvider": TiledKSamplerProvider, # noqa: F405
|
||||
|
||||
"KSamplerAdvancedProvider": KSamplerAdvancedProvider,
|
||||
"TwoAdvancedSamplersForMask": TwoAdvancedSamplersForMask,
|
||||
"KSamplerAdvancedProvider": KSamplerAdvancedProvider, # noqa: F405
|
||||
"TwoAdvancedSamplersForMask": TwoAdvancedSamplersForMask, # noqa: F405
|
||||
|
||||
"ImpactNegativeConditioningPlaceholder": NegativeConditioningPlaceholder,
|
||||
"ImpactNegativeConditioningPlaceholder": NegativeConditioningPlaceholder, # noqa: F405
|
||||
|
||||
"PreviewBridge": PreviewBridge,
|
||||
"PreviewBridgeLatent": PreviewBridgeLatent,
|
||||
"ImageSender": ImageSender,
|
||||
"ImageReceiver": ImageReceiver,
|
||||
"LatentSender": LatentSender,
|
||||
"LatentReceiver": LatentReceiver,
|
||||
"ImageMaskSwitch": ImageMaskSwitch,
|
||||
"LatentSwitch": GeneralSwitch,
|
||||
"SEGSSwitch": GeneralSwitch,
|
||||
"ImpactSwitch": GeneralSwitch,
|
||||
"ImpactInversedSwitch": GeneralInversedSwitch,
|
||||
"PreviewBridge": PreviewBridge, # noqa: F405
|
||||
"PreviewBridgeLatent": PreviewBridgeLatent, # noqa: F405
|
||||
"ImageSender": ImageSender, # noqa: F405
|
||||
"ImageReceiver": ImageReceiver, # noqa: F405
|
||||
"LatentSender": LatentSender, # noqa: F405
|
||||
"LatentReceiver": LatentReceiver, # noqa: F405
|
||||
"ImageMaskSwitch": ImageMaskSwitch, # noqa: F405
|
||||
"LatentSwitch": GeneralSwitch, # noqa: F405
|
||||
"SEGSSwitch": GeneralSwitch, # noqa: F405
|
||||
"ImpactSwitch": GeneralSwitch, # noqa: F405
|
||||
"ImpactInversedSwitch": GeneralInversedSwitch, # noqa: F405
|
||||
|
||||
"ImpactWildcardProcessor": ImpactWildcardProcessor,
|
||||
"ImpactWildcardEncode": ImpactWildcardEncode,
|
||||
"ImpactWildcardProcessor": ImpactWildcardProcessor, # noqa: F405
|
||||
"ImpactWildcardEncode": ImpactWildcardEncode, # noqa: F405
|
||||
|
||||
"SEGSUpscaler": SEGSUpscaler,
|
||||
"SEGSUpscalerPipe": SEGSUpscalerPipe,
|
||||
"SEGSDetailer": SEGSDetailer,
|
||||
"SEGSPaste": SEGSPaste,
|
||||
"SEGSPreview": SEGSPreview,
|
||||
"SEGSPreviewCNet": SEGSPreviewCNet,
|
||||
"SEGSToImageList": SEGSToImageList,
|
||||
"ImpactSEGSToMaskList": SEGSToMaskList,
|
||||
"ImpactSEGSToMaskBatch": SEGSToMaskBatch,
|
||||
"ImpactSEGSConcat": SEGSConcat,
|
||||
"ImpactSEGSPicker": SEGSPicker,
|
||||
"ImpactMakeTileSEGS": MakeTileSEGS,
|
||||
"ImpactSEGSMerge": SEGSMerge,
|
||||
"SEGSUpscaler": SEGSUpscaler, # noqa: F405
|
||||
"SEGSUpscalerPipe": SEGSUpscalerPipe, # noqa: F405
|
||||
"SEGSDetailer": SEGSDetailer, # noqa: F405
|
||||
"SEGSPaste": SEGSPaste, # noqa: F405
|
||||
"SEGSPreview": SEGSPreview, # noqa: F405
|
||||
"SEGSPreviewCNet": SEGSPreviewCNet, # noqa: F405
|
||||
"SEGSToImageList": SEGSToImageList, # noqa: F405
|
||||
"ImpactSEGSToMaskList": SEGSToMaskList, # noqa: F405
|
||||
"ImpactSEGSToMaskBatch": SEGSToMaskBatch, # noqa: F405
|
||||
"ImpactSEGSConcat": SEGSConcat, # noqa: F405
|
||||
"ImpactSEGSPicker": SEGSPicker, # noqa: F405
|
||||
"ImpactMakeTileSEGS": MakeTileSEGS, # noqa: F405
|
||||
"ImpactSEGSMerge": SEGSMerge, # noqa: F405
|
||||
|
||||
"SEGSDetailerForAnimateDiff": SEGSDetailerForAnimateDiff,
|
||||
"SEGSDetailerForAnimateDiff": SEGSDetailerForAnimateDiff, # noqa: F405
|
||||
|
||||
"ImpactKSamplerBasicPipe": KSamplerBasicPipe,
|
||||
"ImpactKSamplerAdvancedBasicPipe": KSamplerAdvancedBasicPipe,
|
||||
"ImpactKSamplerBasicPipe": KSamplerBasicPipe, # noqa: F405
|
||||
"ImpactKSamplerAdvancedBasicPipe": KSamplerAdvancedBasicPipe, # noqa: F405
|
||||
|
||||
"ReencodeLatent": ReencodeLatent,
|
||||
"ReencodeLatentPipe": ReencodeLatentPipe,
|
||||
"ReencodeLatent": ReencodeLatent, # noqa: F405
|
||||
"ReencodeLatentPipe": ReencodeLatentPipe, # noqa: F405
|
||||
|
||||
"ImpactImageBatchToImageList": ImageBatchToImageList,
|
||||
"ImpactMakeImageList": MakeImageList,
|
||||
"ImpactMakeImageBatch": MakeImageBatch,
|
||||
"ImpactMakeAnyList": MakeAnyList,
|
||||
"ImpactMakeMaskList": MakeMaskList,
|
||||
"ImpactMakeMaskBatch": MakeMaskBatch,
|
||||
"ImpactSelectNthItemOfAnyList": NthItemOfAnyList,
|
||||
"ImpactImageBatchToImageList": ImageBatchToImageList, # noqa: F405
|
||||
"ImpactMakeImageList": MakeImageList, # noqa: F405
|
||||
"ImpactMakeImageBatch": MakeImageBatch, # noqa: F405
|
||||
"ImpactMakeAnyList": MakeAnyList, # noqa: F405
|
||||
"ImpactMakeMaskList": MakeMaskList, # noqa: F405
|
||||
"ImpactMakeMaskBatch": MakeMaskBatch, # noqa: F405
|
||||
"ImpactSelectNthItemOfAnyList": NthItemOfAnyList, # noqa: F405
|
||||
|
||||
"RegionalSampler": RegionalSampler,
|
||||
"RegionalSamplerAdvanced": RegionalSamplerAdvanced,
|
||||
"CombineRegionalPrompts": CombineRegionalPrompts,
|
||||
"RegionalPrompt": RegionalPrompt,
|
||||
"RegionalSampler": RegionalSampler, # noqa: F405
|
||||
"RegionalSamplerAdvanced": RegionalSamplerAdvanced, # noqa: F405
|
||||
"CombineRegionalPrompts": CombineRegionalPrompts, # noqa: F405
|
||||
"RegionalPrompt": RegionalPrompt, # noqa: F405
|
||||
|
||||
"ImpactCombineConditionings": CombineConditionings,
|
||||
"ImpactConcatConditionings": ConcatConditionings,
|
||||
"ImpactCombineConditionings": CombineConditionings, # noqa: F405
|
||||
"ImpactConcatConditionings": ConcatConditionings, # noqa: F405
|
||||
|
||||
"ImpactSEGSLabelAssign": SEGSLabelAssign,
|
||||
"ImpactSEGSLabelFilter": SEGSLabelFilter,
|
||||
"ImpactSEGSRangeFilter": SEGSRangeFilter,
|
||||
"ImpactSEGSOrderedFilter": SEGSOrderedFilter,
|
||||
"ImpactSEGSIntersectionFilter": SEGSIntersectionFilter,
|
||||
"ImpactSEGSNMSFilter": SEGSNMSFilter,
|
||||
"ImpactSEGSLabelAssign": SEGSLabelAssign, # noqa: F405
|
||||
"ImpactSEGSLabelFilter": SEGSLabelFilter, # noqa: F405
|
||||
"ImpactSEGSRangeFilter": SEGSRangeFilter, # noqa: F405
|
||||
"ImpactSEGSOrderedFilter": SEGSOrderedFilter, # noqa: F405
|
||||
"ImpactSEGSIntersectionFilter": SEGSIntersectionFilter, # noqa: F405
|
||||
"ImpactSEGSNMSFilter": SEGSNMSFilter, # noqa: F405
|
||||
|
||||
"ImpactCompare": ImpactCompare,
|
||||
"ImpactConditionalBranch": ImpactConditionalBranch,
|
||||
"ImpactConditionalBranchSelMode": ImpactConditionalBranchSelMode,
|
||||
"ImpactIfNone": ImpactIfNone,
|
||||
"ImpactConvertDataType": ImpactConvertDataType,
|
||||
"ImpactLogicalOperators": ImpactLogicalOperators,
|
||||
"ImpactInt": ImpactInt,
|
||||
"ImpactFloat": ImpactFloat,
|
||||
"ImpactBoolean": ImpactBoolean,
|
||||
"ImpactValueSender": ImpactValueSender,
|
||||
"ImpactValueReceiver": ImpactValueReceiver,
|
||||
"ImpactImageInfo": ImpactImageInfo,
|
||||
"ImpactLatentInfo": ImpactLatentInfo,
|
||||
"ImpactMinMax": ImpactMinMax,
|
||||
"ImpactNeg": ImpactNeg,
|
||||
"ImpactConditionalStopIteration": ImpactConditionalStopIteration,
|
||||
"ImpactStringSelector": StringSelector,
|
||||
"StringListToString": StringListToString,
|
||||
"WildcardPromptFromString": WildcardPromptFromString,
|
||||
"ImpactExecutionOrderController": ImpactExecutionOrderController,
|
||||
"ImpactListBridge": ImpactListBridge,
|
||||
"ImpactCompare": ImpactCompare, # noqa: F405
|
||||
"ImpactConditionalBranch": ImpactConditionalBranch, # noqa: F405
|
||||
"ImpactConditionalBranchSelMode": ImpactConditionalBranchSelMode, # noqa: F405
|
||||
"ImpactIfNone": ImpactIfNone, # noqa: F405
|
||||
"ImpactConvertDataType": ImpactConvertDataType, # noqa: F405
|
||||
"ImpactLogicalOperators": ImpactLogicalOperators, # noqa: F405
|
||||
"ImpactInt": ImpactInt, # noqa: F405
|
||||
"ImpactFloat": ImpactFloat, # noqa: F405
|
||||
"ImpactBoolean": ImpactBoolean, # noqa: F405
|
||||
"ImpactValueSender": ImpactValueSender, # noqa: F405
|
||||
"ImpactValueReceiver": ImpactValueReceiver, # noqa: F405
|
||||
"ImpactImageInfo": ImpactImageInfo, # noqa: F405
|
||||
"ImpactLatentInfo": ImpactLatentInfo, # noqa: F405
|
||||
"ImpactMinMax": ImpactMinMax, # noqa: F405
|
||||
"ImpactNeg": ImpactNeg, # noqa: F405
|
||||
"ImpactConditionalStopIteration": ImpactConditionalStopIteration, # noqa: F405
|
||||
"ImpactStringSelector": StringSelector, # noqa: F405
|
||||
"StringListToString": StringListToString, # noqa: F405
|
||||
"WildcardPromptFromString": WildcardPromptFromString, # noqa: F405
|
||||
"ImpactExecutionOrderController": ImpactExecutionOrderController, # noqa: F405
|
||||
"ImpactListBridge": ImpactListBridge, # noqa: F405
|
||||
|
||||
"RemoveNoiseMask": RemoveNoiseMask,
|
||||
"RemoveNoiseMask": RemoveNoiseMask, # noqa: F405
|
||||
|
||||
"ImpactLogger": ImpactLogger,
|
||||
"ImpactDummyInput": ImpactDummyInput,
|
||||
"ImpactLogger": ImpactLogger, # noqa: F405
|
||||
"ImpactDummyInput": ImpactDummyInput, # noqa: F405
|
||||
|
||||
"ImpactQueueTrigger": ImpactQueueTrigger,
|
||||
"ImpactQueueTriggerCountdown": ImpactQueueTriggerCountdown,
|
||||
"ImpactSetWidgetValue": ImpactSetWidgetValue,
|
||||
"ImpactNodeSetMuteState": ImpactNodeSetMuteState,
|
||||
"ImpactControlBridge": ImpactControlBridge,
|
||||
"ImpactIsNotEmptySEGS": ImpactNotEmptySEGS,
|
||||
"ImpactSleep": ImpactSleep,
|
||||
"ImpactRemoteBoolean": ImpactRemoteBoolean,
|
||||
"ImpactRemoteInt": ImpactRemoteInt,
|
||||
"ImpactQueueTrigger": ImpactQueueTrigger, # noqa: F405
|
||||
"ImpactQueueTriggerCountdown": ImpactQueueTriggerCountdown, # noqa: F405
|
||||
"ImpactSetWidgetValue": ImpactSetWidgetValue, # noqa: F405
|
||||
"ImpactNodeSetMuteState": ImpactNodeSetMuteState, # noqa: F405
|
||||
"ImpactControlBridge": ImpactControlBridge, # noqa: F405
|
||||
"ImpactIsNotEmptySEGS": ImpactNotEmptySEGS, # noqa: F405
|
||||
"ImpactSleep": ImpactSleep, # noqa: F405
|
||||
"ImpactRemoteBoolean": ImpactRemoteBoolean, # noqa: F405
|
||||
"ImpactRemoteInt": ImpactRemoteInt, # noqa: F405
|
||||
|
||||
"ImpactHFTransformersClassifierProvider": HF_TransformersClassifierProvider,
|
||||
"ImpactSEGSClassify": SEGS_Classify,
|
||||
"ImpactHFTransformersClassifierProvider": HF_TransformersClassifierProvider, # noqa: F405
|
||||
"ImpactSEGSClassify": SEGS_Classify, # noqa: F405
|
||||
|
||||
"ImpactSchedulerAdapter": ImpactSchedulerAdapter,
|
||||
"GITSSchedulerFuncProvider": GITSSchedulerFuncProvider
|
||||
"ImpactSchedulerAdapter": ImpactSchedulerAdapter, # noqa: F405
|
||||
"GITSSchedulerFuncProvider": GITSSchedulerFuncProvider # noqa: F405
|
||||
}
|
||||
|
||||
|
||||
@@ -302,7 +299,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"BboxDetectorSEGS": "BBOX Detector (SEGS)",
|
||||
"SegmDetectorSEGS": "SEGM Detector (SEGS)",
|
||||
"ONNXDetectorSEGS": "ONNX Detector (SEGS/legacy) - use BBOXDetector",
|
||||
"ImpactSimpleDetectorSEGS_for_AD": "Simple Detector for AnimateDiff (SEGS)",
|
||||
"ImpactSimpleDetectorSEGS_for_AD": "Simple Detector for Video (SEGS)",
|
||||
"ImpactSAM2VideoDetectorSEGS": "SAM2 Video Detector (SEGS)",
|
||||
"ImpactSimpleDetectorSEGS": "Simple Detector (SEGS)",
|
||||
"ImpactSimpleDetectorSEGSPipe": "Simple Detector (SEGS/pipe)",
|
||||
"ImpactControlNetApplySEGS": "ControlNetApply (SEGS) - DEPRECATED",
|
||||
@@ -314,7 +312,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SegsToCombinedMask": "SEGS to MASK (combined)",
|
||||
"MediaPipeFaceMeshToSEGS": "MediaPipe FaceMesh to SEGS",
|
||||
"MaskToSEGS": "MASK to SEGS",
|
||||
"MaskToSEGS_for_AnimateDiff": "MASK to SEGS for AnimateDiff",
|
||||
"MaskToSEGS_for_AnimateDiff": "MASK to SEGS for Video",
|
||||
"BitwiseAndMaskForEach": "Pixelwise(SEGS & SEGS)",
|
||||
"SubtractMaskForEach": "Pixelwise(SEGS - SEGS)",
|
||||
"ImpactSegsAndMask": "Pixelwise(SEGS & MASK)",
|
||||
@@ -329,8 +327,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"DetailerForEachPipe": "Detailer (SEGS/pipe)",
|
||||
"DetailerForEachDebug": "DetailerDebug (SEGS)",
|
||||
"DetailerForEachDebugPipe": "DetailerDebug (SEGS/pipe)",
|
||||
"SEGSDetailerForAnimateDiff": "SEGSDetailer For AnimateDiff (SEGS/pipe)",
|
||||
"DetailerForEachPipeForAnimateDiff": "Detailer For AnimateDiff (SEGS/pipe)",
|
||||
"SEGSDetailerForAnimateDiff": "SEGSDetailer For Video (SEGS/pipe)",
|
||||
"DetailerForEachPipeForAnimateDiff": "Detailer For Video (SEGS/pipe)",
|
||||
"SEGSUpscaler": "Upscaler (SEGS)",
|
||||
"SEGSUpscalerPipe": "Upscaler (SEGS/pipe)",
|
||||
|
||||
@@ -444,30 +442,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactNegativeConditioningPlaceholder": "Negative Cond Placeholder"
|
||||
}
|
||||
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
from impact.mmdet_nodes import *
|
||||
import impact.legacy_nodes
|
||||
NODE_CLASS_MAPPINGS.update({
|
||||
"MMDetDetectorProvider": MMDetDetectorProvider,
|
||||
"MMDetLoader": impact.legacy_nodes.MMDetLoader,
|
||||
"MaskPainter": impact.legacy_nodes.MaskPainter,
|
||||
"SegsMaskCombine": impact.legacy_nodes.SegsMaskCombine,
|
||||
"BboxDetectorForEach": impact.legacy_nodes.BboxDetectorForEach,
|
||||
"SegmDetectorForEach": impact.legacy_nodes.SegmDetectorForEach,
|
||||
"BboxDetectorCombined": impact.legacy_nodes.BboxDetectorCombined,
|
||||
"SegmDetectorCombined": impact.legacy_nodes.SegmDetectorCombined,
|
||||
})
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update({
|
||||
"MaskPainter": "MaskPainter (Deprecated)",
|
||||
"MMDetLoader": "MMDetLoader (Legacy)",
|
||||
"SegsMaskCombine": "SegsMaskCombine (Legacy)",
|
||||
"BboxDetectorForEach": "BboxDetectorForEach (Legacy)",
|
||||
"SegmDetectorForEach": "SegmDetectorForEach (Legacy)",
|
||||
"BboxDetectorCombined": "BboxDetectorCombined (Legacy)",
|
||||
"SegmDetectorCombined": "SegmDetectorCombined (Legacy)",
|
||||
})
|
||||
|
||||
|
||||
# NOTE: Inject directly into EXTENSION_WEB_DIRS instead of WEB_DIRECTORY
|
||||
# Provide the js path fixed as ComfyUI-Impact-Pack instead of the path name, making it available for external use
|
||||
@@ -477,14 +451,3 @@ nodes.EXTENSION_WEB_DIRS["ComfyUI-Impact-Pack"] = os.path.join(os.path.dirname(o
|
||||
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
|
||||
|
||||
try:
|
||||
import cm_global
|
||||
cm_global.register_extension('ComfyUI-Impact-Pack',
|
||||
{'version': config.version_code,
|
||||
'name': 'Impact Pack',
|
||||
'nodes': set(NODE_CLASS_MAPPINGS.keys()),
|
||||
'description': 'This extension provides inpainting functionality based on the detector and detailer, along with convenient workflow features like wildcards and logics.', })
|
||||
except:
|
||||
pass
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
comfy_path = '../..'
|
||||
|
||||
def rmtree(path):
|
||||
retry_count = 3
|
||||
|
||||
while True:
|
||||
try:
|
||||
retry_count -= 1
|
||||
|
||||
if platform.system() == "Windows":
|
||||
subprocess.check_call(['attrib', '-R', path + '\\*', '/S'])
|
||||
|
||||
shutil.rmtree(path)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as ex:
|
||||
print(f"ex: {ex}")
|
||||
time.sleep(3)
|
||||
|
||||
if retry_count < 0:
|
||||
raise ex
|
||||
|
||||
print(f"Uninstall retry({retry_count})")
|
||||
|
||||
js_dest_path = os.path.join(comfy_path, "web", "extensions", "impact-pack")
|
||||
|
||||
if os.path.exists(js_dest_path):
|
||||
rmtree(js_dest_path)
|
||||
|
||||
|
||||
+14
-23
@@ -68,7 +68,6 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None):
|
||||
|
||||
|
||||
try:
|
||||
import platform
|
||||
from torchvision.datasets.utils import download_url
|
||||
import impact.config
|
||||
|
||||
@@ -85,21 +84,10 @@ try:
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'skip_download_model')):
|
||||
try:
|
||||
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)
|
||||
except:
|
||||
print(f"[Impact Pack] Failed to auto-download model files. Please download them manually.")
|
||||
print("[Impact Pack] Failed to auto-download model files. Please download them manually.")
|
||||
|
||||
if not os.path.exists(onnx_path):
|
||||
print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})")
|
||||
@@ -108,18 +96,21 @@ try:
|
||||
impact.config.write_config()
|
||||
|
||||
# Remove legacy subpack
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'impact_subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
try:
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'impact_subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
except:
|
||||
print(f"ERROT: Failed to delete legacy subpack '{subpack_path}'\nPlease delete the folder after terminate ComfyUI.")
|
||||
|
||||
install()
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
print("[ERROR] ComfyUI-Impact-Pack: Dependency installation has failed. Please install manually.")
|
||||
traceback.print_exc()
|
||||
|
||||
+14
-1
@@ -520,6 +520,14 @@ app.registerExtension({
|
||||
return;
|
||||
}
|
||||
|
||||
if(stackTrace.includes('pasteFromClipboard')) {
|
||||
if(this.widgets?.[0]) {
|
||||
this.widgets[0].options.max = this.inputs.length-3;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!link_info)
|
||||
return;
|
||||
|
||||
@@ -586,7 +594,12 @@ app.registerExtension({
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
|
||||
if (!connected && (this.inputs.length > 3)) {
|
||||
let widget_count = 0;
|
||||
if(nodeData.name == 'ImpactSwitch' || nodeData.name == 'LatentSwitch' || nodeData.name == 'SEGSSwitch') {
|
||||
widget_count += 1;
|
||||
}
|
||||
|
||||
if (!connected && (this.inputs.length > widget_count+1)) {
|
||||
if(
|
||||
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
|
||||
!stackTrace.includes('LGraphNode.connect') && // for mouse device
|
||||
|
||||
@@ -1196,7 +1196,7 @@
|
||||
|
||||
"ImpactWildcardEncode": {
|
||||
"description": "이 노드는 와일드카드 구문으로 작성된 텍스트 프롬프트를 처리하고 이를 조건으로 출력합니다. 또한 LoRA 구문을 지원하며, 적용된 LoRA는 모델 출력에 반영됩니다.\n\nTIP1: 워크플로가 실행되기 전에 '와일드카드 텍스트'의 처리 결과가 '채워진 텍스트'에 표시되며, 이 값은 워크플로와 함께 저장됩니다. 입력으로 변환된 시드를 사용하려면 '와일드카드 텍스트' 대신 '채워진 텍스트'에 직접 프롬프트를 작성하고, 모드를 '고정(fixed)'로 설정하세요.\nTIP2: 'Inspire Pack'이 설치되어 있으면 LBW(로라 블록 웨이트) 구문도 적용할 수 있습니다.",
|
||||
"display_name": "와일드카드 처리기 (Impact)",
|
||||
"display_name": "와일드카드 인코딩 (Impact)",
|
||||
"inputs": {
|
||||
"wildcard_text": {
|
||||
"name": "와일드카드 텍스트",
|
||||
|
||||
@@ -4,7 +4,7 @@ import subprocess
|
||||
|
||||
def ensure_onnx_package():
|
||||
try:
|
||||
import onnxruntime
|
||||
import onnxruntime # noqa: F401
|
||||
except Exception:
|
||||
if "python_embeded" in sys.executable or "python_embedded" in sys.executable:
|
||||
subprocess.check_call([sys.executable, '-s', '-m', 'pip', 'install', 'onnxruntime'])
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from nodes import MAX_RESOLUTION
|
||||
from impact.utils import *
|
||||
import impact.core as core
|
||||
from impact.core import SEG
|
||||
from impact.segs_nodes import SEGSPaste
|
||||
|
||||
import comfy
|
||||
from impact import utils
|
||||
import torch
|
||||
import nodes
|
||||
import logging
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
logging.warning("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -70,8 +73,8 @@ class SEGSDetailerForAnimateDiff:
|
||||
|
||||
for image in image_frames:
|
||||
image = image.unsqueeze(0)
|
||||
cropped_image = seg.cropped_image if seg.cropped_image is not None else crop_tensor4(image, seg.crop_region)
|
||||
cropped_image = to_tensor(cropped_image)
|
||||
cropped_image = seg.cropped_image if seg.cropped_image is not None else utils.crop_tensor4(image, seg.crop_region)
|
||||
cropped_image = utils.to_tensor(cropped_image)
|
||||
if cropped_image_frames is None:
|
||||
cropped_image_frames = cropped_image
|
||||
else:
|
||||
@@ -129,7 +132,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
|
||||
if len(cnet_images) == 0:
|
||||
cnet_images = [empty_pil_tensor()]
|
||||
cnet_images = [utils.empty_pil_tensor()]
|
||||
|
||||
return (segs, cnet_images)
|
||||
|
||||
|
||||
+135
-35
@@ -1,8 +1,12 @@
|
||||
import os
|
||||
from PIL import ImageOps
|
||||
from impact.utils import *
|
||||
import latent_preview
|
||||
|
||||
import logging
|
||||
import folder_paths
|
||||
import torch
|
||||
import nodes
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
from impact import utils
|
||||
|
||||
# NOTE: this should not be `from . import core`.
|
||||
# I don't know why but... 'from .' and 'from impact' refer to different core modules.
|
||||
@@ -48,10 +52,10 @@ class PreviewBridge:
|
||||
if pb_id not in core.preview_bridge_image_id_map:
|
||||
is_fail = True
|
||||
|
||||
image_path, ui_item = core.preview_bridge_image_id_map[pb_id]
|
||||
|
||||
if not os.path.isfile(image_path):
|
||||
is_fail = True
|
||||
if not is_fail:
|
||||
image_path, ui_item = core.preview_bridge_image_id_map[pb_id]
|
||||
if not os.path.isfile(image_path):
|
||||
is_fail = True
|
||||
|
||||
if not is_fail:
|
||||
i = Image.open(image_path)
|
||||
@@ -66,7 +70,7 @@ class PreviewBridge:
|
||||
else:
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
else:
|
||||
image = empty_pil_tensor()
|
||||
image = utils.empty_pil_tensor()
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
ui_item = {
|
||||
"filename": 'empty.png',
|
||||
@@ -76,23 +80,93 @@ class PreviewBridge:
|
||||
|
||||
return image, mask.unsqueeze(0), ui_item
|
||||
|
||||
@staticmethod
|
||||
def register_clipspace_image(clipspace_path, node_id):
|
||||
"""Register a clipspace image file in the preview bridge system.
|
||||
|
||||
This handles the case where ComfyUI's mask editor creates clipspace files
|
||||
that need to be integrated with the preview bridge system.
|
||||
"""
|
||||
# Remove [input] suffix if present
|
||||
clean_path = clipspace_path.replace(" [input]", "").replace("[input]", "")
|
||||
|
||||
# Try to find the actual clipspace file
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
potential_paths = [
|
||||
clean_path,
|
||||
os.path.join(input_dir, clean_path),
|
||||
os.path.join(input_dir, "clipspace", os.path.basename(clean_path)),
|
||||
os.path.abspath(clean_path),
|
||||
]
|
||||
|
||||
actual_file = None
|
||||
for path in potential_paths:
|
||||
if os.path.isfile(path):
|
||||
actual_file = path
|
||||
break
|
||||
|
||||
if not actual_file:
|
||||
return False
|
||||
|
||||
# Create ui_item for the clipspace file
|
||||
ui_item = {
|
||||
'filename': os.path.basename(actual_file),
|
||||
'subfolder': 'clipspace',
|
||||
'type': 'input'
|
||||
}
|
||||
|
||||
# Register it using the preview bridge system
|
||||
core.set_previewbridge_image(node_id, actual_file, ui_item)
|
||||
# Also register under the original clipspace path for compatibility
|
||||
core.preview_bridge_image_id_map[clipspace_path] = (actual_file, ui_item)
|
||||
|
||||
return True
|
||||
|
||||
def doit(self, images, image, unique_id, block=False, restore_mask="never", prompt=None, extra_pnginfo=None):
|
||||
need_refresh = False
|
||||
images_changed = False
|
||||
|
||||
# Check if images have changed (this determines if we start fresh)
|
||||
if unique_id not in core.preview_bridge_cache:
|
||||
need_refresh = True
|
||||
|
||||
images_changed = True
|
||||
elif core.preview_bridge_cache[unique_id][0] is not images:
|
||||
need_refresh = True
|
||||
images_changed = True
|
||||
|
||||
# If images changed, clear the mask cache to ensure fresh start behavior
|
||||
# This restores the original behavior where new images start with empty masks
|
||||
# unless restore_mask is set to "always" or "if_same_size"
|
||||
if images_changed and restore_mask not in ["always", "if_same_size"] and unique_id in core.preview_bridge_last_mask_cache:
|
||||
del core.preview_bridge_last_mask_cache[unique_id]
|
||||
|
||||
# Handle clipspace files that aren't registered in the preview bridge system
|
||||
# This only applies when images haven't changed (same image, new mask scenario)
|
||||
if not need_refresh and image not in core.preview_bridge_image_id_map:
|
||||
# Check if this is a clipspace file that needs to be registered
|
||||
is_clipspace = image and ("clipspace" in image.lower() or "[input]" in image)
|
||||
if is_clipspace:
|
||||
if not PreviewBridge.register_clipspace_image(image, unique_id):
|
||||
need_refresh = True
|
||||
else:
|
||||
need_refresh = True
|
||||
|
||||
if not need_refresh:
|
||||
pixels, mask, path_item = PreviewBridge.load_image(image)
|
||||
image = [path_item]
|
||||
else:
|
||||
if restore_mask != "never":
|
||||
# For new images (images_changed=True), we want to start fresh regardless of restore_mask
|
||||
# For same image with refresh needed, respect the restore_mask setting
|
||||
# Exception: when restore_mask is "always", restore even with new images
|
||||
# Exception: when restore_mask is "if_same_size", allow restoration to check size compatibility
|
||||
if restore_mask != "never" and (not images_changed or restore_mask in ["always", "if_same_size"]):
|
||||
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]):
|
||||
if mask is None:
|
||||
mask = None
|
||||
elif restore_mask == "if_same_size" and mask.shape[1:] != images.shape[1:3]:
|
||||
# For if_same_size, clear mask if dimensions don't match
|
||||
mask = None
|
||||
# For "always", keep the mask regardless of size
|
||||
else:
|
||||
mask = None
|
||||
|
||||
@@ -100,10 +174,10 @@ class PreviewBridge:
|
||||
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)
|
||||
masked_images = utils.tensor_convert_rgba(images)
|
||||
resized_mask = utils.resize_mask(mask, (images.shape[1], images.shape[2])).unsqueeze(3)
|
||||
resized_mask = 1 - resized_mask
|
||||
tensor_putalpha(masked_images, resized_mask)
|
||||
utils.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']
|
||||
@@ -123,7 +197,7 @@ class PreviewBridge:
|
||||
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.")
|
||||
logging.warning("[Impact Pack] PreviewBridge: ComfyUI is outdated - blocking feature is disabled.")
|
||||
result = pixels, mask
|
||||
else:
|
||||
result = pixels, mask
|
||||
@@ -190,7 +264,7 @@ def decode_latent(latent, preview_method, vae_opt=None):
|
||||
latent_format = latent_formats.LTXV()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
else:
|
||||
print(f"[Impact Pack] PreviewBridgeLatent: '{preview_method}' is unsupported preview method.")
|
||||
logging.warning(f"[Impact Pack] PreviewBridgeLatent: '{preview_method}' is unsupported preview method.")
|
||||
latent_format = latent_formats.SD15()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
|
||||
@@ -199,9 +273,9 @@ def decode_latent(latent, preview_method, vae_opt=None):
|
||||
|
||||
pil_image = previewer.decode_latent_to_preview(samples)
|
||||
pixels_size = pil_image.size[0]*8, pil_image.size[1]*8
|
||||
resized_image = pil_image.resize(pixels_size, resample=LANCZOS)
|
||||
resized_image = pil_image.resize(pixels_size, resample=utils.LANCZOS)
|
||||
|
||||
return to_tensor(resized_image).unsqueeze(0)
|
||||
return utils.to_tensor(resized_image).unsqueeze(0)
|
||||
|
||||
|
||||
class PreviewBridgeLatent:
|
||||
@@ -248,10 +322,10 @@ class PreviewBridgeLatent:
|
||||
if pb_id not in core.preview_bridge_image_id_map:
|
||||
is_fail = True
|
||||
|
||||
image_path, ui_item = core.preview_bridge_image_id_map[pb_id]
|
||||
|
||||
if not os.path.isfile(image_path):
|
||||
is_fail = True
|
||||
if not is_fail:
|
||||
image_path, ui_item = core.preview_bridge_image_id_map[pb_id]
|
||||
if not os.path.isfile(image_path):
|
||||
is_fail = True
|
||||
|
||||
if not is_fail:
|
||||
i = Image.open(image_path)
|
||||
@@ -266,7 +340,7 @@ class PreviewBridgeLatent:
|
||||
else:
|
||||
mask = None
|
||||
else:
|
||||
image = empty_pil_tensor()
|
||||
image = utils.empty_pil_tensor()
|
||||
mask = None
|
||||
ui_item = {
|
||||
"filename": 'empty.png',
|
||||
@@ -287,19 +361,37 @@ class PreviewBridgeLatent:
|
||||
preview_method_channels = 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, SC-B and FLUX.1 are not compatible with each other.")
|
||||
logging.warning("[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
|
||||
latent_changed = False
|
||||
|
||||
# Check if latent has changed
|
||||
if unique_id not in core.preview_bridge_cache:
|
||||
need_refresh = True
|
||||
|
||||
latent_changed = True
|
||||
elif (core.preview_bridge_cache[unique_id][0] is not latent
|
||||
or (vae_opt is None and core.preview_bridge_cache[unique_id][2] is not None)
|
||||
or (vae_opt is None and core.preview_bridge_cache[unique_id][1] != preview_method)
|
||||
or (vae_opt is not None and core.preview_bridge_cache[unique_id][2] is not vae_opt)):
|
||||
need_refresh = True
|
||||
latent_changed = True
|
||||
|
||||
# If latent changed, clear the mask cache to ensure fresh start behavior
|
||||
# unless restore_mask is set to "always" or "if_same_size"
|
||||
if latent_changed and restore_mask not in ["always", "if_same_size"] and unique_id in core.preview_bridge_last_mask_cache:
|
||||
del core.preview_bridge_last_mask_cache[unique_id]
|
||||
|
||||
# Handle clipspace files that aren't registered in the preview bridge system
|
||||
# This only applies when latent hasn't changed (same latent, new mask scenario)
|
||||
if not need_refresh and image not in core.preview_bridge_image_id_map:
|
||||
is_clipspace = image and ("clipspace" in image.lower() or "[input]" in image)
|
||||
if is_clipspace:
|
||||
if not PreviewBridge.register_clipspace_image(image, unique_id):
|
||||
need_refresh = True
|
||||
else:
|
||||
need_refresh = True
|
||||
|
||||
if not need_refresh:
|
||||
pixels, mask, path_item = PreviewBridge.load_image(image)
|
||||
@@ -326,11 +418,11 @@ class PreviewBridgeLatent:
|
||||
if 'noise_mask' in latent:
|
||||
mask = latent['noise_mask'].squeeze(0) # 4D mask -> 3D mask
|
||||
|
||||
decoded_pil = to_pil(decoded_image)
|
||||
decoded_pil = utils.to_pil(decoded_image)
|
||||
|
||||
inverted_mask = 1 - mask # invert
|
||||
resized_mask = resize_mask(inverted_mask, (decoded_image.shape[1], decoded_image.shape[2]))
|
||||
result_pil = apply_mask_alpha_to_pil(decoded_pil, resized_mask)
|
||||
resized_mask = utils.resize_mask(inverted_mask, (decoded_image.shape[1], decoded_image.shape[2]))
|
||||
result_pil = utils.apply_mask_alpha_to_pil(decoded_pil, resized_mask)
|
||||
|
||||
full_output_folder, filename, counter, _, _ = folder_paths.get_save_image_path("PreviewBridge/PBL-"+self.prefix_append, folder_paths.get_temp_directory(), result_pil.size[0], result_pil.size[1])
|
||||
file = f"{filename}_{counter}.png"
|
||||
@@ -343,10 +435,18 @@ class PreviewBridgeLatent:
|
||||
|
||||
is_empty_mask = False
|
||||
else:
|
||||
if restore_mask != "never":
|
||||
# For new latents (latent_changed=True), start fresh regardless of restore_mask
|
||||
# For same latent with refresh needed, respect the restore_mask setting
|
||||
# Exception: when restore_mask is "always", restore even with new latents
|
||||
# Exception: when restore_mask is "if_same_size", allow restoration to check size compatibility
|
||||
if restore_mask != "never" and (not latent_changed or restore_mask in ["always", "if_same_size"]):
|
||||
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]):
|
||||
if mask is None:
|
||||
mask = None
|
||||
elif restore_mask == "if_same_size" and mask.shape[1:] != decoded_image.shape[1:3]:
|
||||
# For if_same_size, clear mask if dimensions don't match
|
||||
mask = None
|
||||
# For "always", keep the mask regardless of size
|
||||
else:
|
||||
mask = None
|
||||
|
||||
@@ -354,10 +454,10 @@ class PreviewBridgeLatent:
|
||||
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)
|
||||
masked_images = utils.tensor_convert_rgba(decoded_image)
|
||||
resized_mask = utils.resize_mask(mask, (decoded_image.shape[1], decoded_image.shape[2])).unsqueeze(3)
|
||||
resized_mask = 1 - resized_mask
|
||||
tensor_putalpha(masked_images, resized_mask)
|
||||
utils.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']
|
||||
@@ -376,7 +476,7 @@ class PreviewBridgeLatent:
|
||||
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.")
|
||||
logging.warning("[Impact Pack] PreviewBridgeLatent: ComfyUI is outdated - blocking feature is disabled.")
|
||||
result = res_latent, mask
|
||||
else:
|
||||
result = res_latent, mask
|
||||
@@ -387,4 +487,4 @@ class PreviewBridgeLatent:
|
||||
return {
|
||||
"ui": {"images": res_image},
|
||||
"result": result,
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import configparser
|
||||
import os
|
||||
import logging
|
||||
|
||||
version_code = [8, 15]
|
||||
|
||||
version_code = [8, 21, 2]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 24
|
||||
|
||||
my_path = os.path.dirname(__file__)
|
||||
old_config_path = os.path.join(my_path, "impact-pack.ini")
|
||||
config_path = os.path.join(my_path, "..", "..", "impact-pack.ini")
|
||||
@@ -15,8 +15,6 @@ latent_letter_path = os.path.join(my_path, "..", "..", "latent.png")
|
||||
def write_config():
|
||||
config = configparser.ConfigParser()
|
||||
config['default'] = {
|
||||
'dependency_version': str(dependency_version),
|
||||
'mmdet_skip': str(get_config()['mmdet_skip']),
|
||||
'sam_editor_cpu': str(get_config()['sam_editor_cpu']),
|
||||
'sam_editor_model': get_config()['sam_editor_model'],
|
||||
'custom_wildcards': get_config()['custom_wildcards'],
|
||||
@@ -33,12 +31,10 @@ def read_config():
|
||||
default_conf = config['default']
|
||||
|
||||
if not os.path.exists(default_conf['custom_wildcards']):
|
||||
print(f"[WARN] ComfyUI-Impact-Pack: custom_wildcards path not found: {default_conf['custom_wildcards']}. Using default path.")
|
||||
logging.warning(f"[Impact Pack] custom_wildcards path not found: {default_conf['custom_wildcards']}. Using default path.")
|
||||
default_conf['custom_wildcards'] = os.path.join(my_path, "..", "..", "custom_wildcards")
|
||||
|
||||
return {
|
||||
'dependency_version': int(default_conf['dependency_version']),
|
||||
'mmdet_skip': default_conf['mmdet_skip'].lower() == 'true' if 'mmdet_skip' in default_conf else True,
|
||||
'sam_editor_cpu': default_conf['sam_editor_cpu'].lower() == 'true' if 'sam_editor_cpu' in default_conf else False,
|
||||
'sam_editor_model': default_conf['sam_editor_model'].lower() if 'sam_editor_model' else 'sam_vit_b_01ec64.pth',
|
||||
'custom_wildcards': default_conf['custom_wildcards'] if 'custom_wildcards' in default_conf else os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "custom_wildcards")),
|
||||
@@ -47,8 +43,6 @@ def read_config():
|
||||
|
||||
except Exception:
|
||||
return {
|
||||
'dependency_version': 0,
|
||||
'mmdet_skip': True,
|
||||
'sam_editor_cpu': False,
|
||||
'sam_editor_model': 'sam_vit_b_01ec64.pth',
|
||||
'custom_wildcards': os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "custom_wildcards")),
|
||||
|
||||
+282
-127
@@ -1,17 +1,13 @@
|
||||
import copy
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import numpy
|
||||
import torch
|
||||
from segment_anything import SamPredictor
|
||||
|
||||
from comfy_extras.nodes_custom_sampler import Noise_RandomNoise
|
||||
from impact.utils import *
|
||||
from collections import namedtuple
|
||||
import numpy as np
|
||||
from skimage.measure import label
|
||||
from PIL import ImageOps
|
||||
from PIL import ImageOps, Image
|
||||
|
||||
import nodes
|
||||
import comfy_extras.nodes_upscale_model as model_upscale
|
||||
@@ -26,12 +22,25 @@ from impact import utils
|
||||
from impact import impact_sampling
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import inspect
|
||||
from collections import OrderedDict
|
||||
import torch.nn.functional as F
|
||||
import logging
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
|
||||
is_sam2_available = importlib.util.find_spec("sam2")
|
||||
sam2_unavailable_message = f"\n----------------------------------------------------------------------------\n[Impact Pack] The SAM2 functionality is unavailable because the `facebook/sam2` dependency is not installed.\n\nInstallation command:\n{sys.executable} -m pip install git+https://github.com/facebookresearch/sam2\n----------------------------------------------------------------------------\n"
|
||||
if is_sam2_available:
|
||||
from sam2.sam2_image_predictor import SAM2ImagePredictor
|
||||
from sam2.build_sam import build_sam2, build_sam2_video_predictor
|
||||
else:
|
||||
logging.warning(sam2_unavailable_message)
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
logging.warning("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -48,14 +57,14 @@ preview_bridge_last_mask_cache = {}
|
||||
|
||||
current_prompt = None
|
||||
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan']
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan', 'OSS Chroma']
|
||||
|
||||
|
||||
def is_execution_model_version_supported():
|
||||
try:
|
||||
import comfy_execution
|
||||
import comfy_execution # noqa: F401
|
||||
return True
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@@ -83,7 +92,7 @@ def set_previewbridge_image(node_id, file, item):
|
||||
|
||||
|
||||
def erosion_mask(mask, grow_mask_by):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
w = mask.shape[1]
|
||||
h = mask.shape[0]
|
||||
@@ -139,7 +148,7 @@ def mix_noise(from_noise, to_noise, strength, variation_method):
|
||||
|
||||
class REGIONAL_PROMPT:
|
||||
def __init__(self, mask, sampler, variation_seed=0, variation_strength=0.0, variation_method='linear'):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
self.mask = mask
|
||||
self.sampler = sampler
|
||||
@@ -199,7 +208,7 @@ def create_segmasks(results):
|
||||
|
||||
|
||||
def gen_detection_hints_from_mask_area(x, y, mask, threshold, use_negative):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
points = []
|
||||
plabs = []
|
||||
@@ -275,7 +284,7 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
|
||||
# Skip processing if the detected bbox is already larger than the guide_size
|
||||
if not force_inpaint and bbox_h >= guide_size and bbox_w >= guide_size:
|
||||
print(f"Detailer: segment skip (enough big)")
|
||||
logging.info("Detailer: segment skip (enough big)")
|
||||
return None, None
|
||||
|
||||
if guide_size_for_bbox: # == "bbox"
|
||||
@@ -299,15 +308,15 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
|
||||
if not force_inpaint:
|
||||
if upscale <= 1.0:
|
||||
print(f"Detailer: segment skip [determined upscale factor={upscale}]")
|
||||
logging.info(f"Detailer: segment skip [determined upscale factor={upscale}]")
|
||||
return None, None
|
||||
|
||||
if new_w == 0 or new_h == 0:
|
||||
print(f"Detailer: segment skip [zero size={new_w, new_h}]")
|
||||
logging.info(f"Detailer: segment skip [zero size={new_w, new_h}]")
|
||||
return None, None
|
||||
else:
|
||||
if upscale <= 1.0 or new_w == 0 or new_h == 0:
|
||||
print(f"Detailer: force inpaint")
|
||||
logging.info("Detailer: force inpaint")
|
||||
upscale = 1.0
|
||||
new_w = w
|
||||
new_h = h
|
||||
@@ -315,10 +324,13 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
if detailer_hook is not None:
|
||||
new_w, new_h = detailer_hook.touch_scaled_size(new_w, new_h)
|
||||
|
||||
print(f"Detailer: segment upscale for ({bbox_w, bbox_h}) | crop region {w, h} x {upscale} -> {new_w, new_h}")
|
||||
logging.info(f"Detailer: segment upscale for ({bbox_w, bbox_h}) | crop region {w, h} x {upscale} -> {new_w, new_h}")
|
||||
|
||||
# upscale
|
||||
upscaled_image = tensor_resize(image, new_w, new_h)
|
||||
upscaled_image = utils.tensor_resize(image, new_w, new_h)
|
||||
|
||||
if detailer_hook is not None:
|
||||
upscaled_image = detailer_hook.post_upscale(upscaled_image, noise_mask)
|
||||
|
||||
cnet_pils = None
|
||||
if control_net_wrapper is not None:
|
||||
@@ -327,67 +339,80 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
cnet_pils.extend(cnet_pils2)
|
||||
|
||||
# prepare mask
|
||||
if noise_mask is not None and inpaint_model:
|
||||
imc_encode = nodes.InpaintModelConditioning().encode
|
||||
if 'noise_mask' in inspect.signature(imc_encode).parameters:
|
||||
positive, negative, latent_image = imc_encode(positive, negative, upscaled_image, vae, mask=noise_mask, noise_mask=True)
|
||||
if detailer_hook is None or not detailer_hook.get_skip_sampling():
|
||||
if noise_mask is not None and inpaint_model:
|
||||
imc_encode = nodes.InpaintModelConditioning().encode
|
||||
if 'noise_mask' in inspect.signature(imc_encode).parameters:
|
||||
positive, negative, latent_image = imc_encode(positive, negative, upscaled_image, vae, mask=noise_mask, noise_mask=True)
|
||||
else:
|
||||
logging.warning("[Impact Pack] ComfyUI is an outdated version.")
|
||||
positive, negative, latent_image = imc_encode(positive, negative, upscaled_image, vae, noise_mask)
|
||||
else:
|
||||
print(f"[Impact Pack] ComfyUI is an outdated version.")
|
||||
positive, negative, latent_image = imc_encode(positive, negative, upscaled_image, vae, noise_mask)
|
||||
else:
|
||||
latent_image = to_latent_image(upscaled_image, vae, vae_tiled_encode=vae_tiled_encode)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
latent_image = utils.to_latent_image(upscaled_image, vae, vae_tiled_encode=vae_tiled_encode)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
|
||||
if detailer_hook is not None:
|
||||
latent_image = detailer_hook.post_encode(latent_image)
|
||||
|
||||
refined_latent = latent_image
|
||||
|
||||
# ksampler
|
||||
for i in range(0, cycle):
|
||||
if detailer_hook is not None:
|
||||
latent_image = detailer_hook.post_encode(latent_image)
|
||||
|
||||
refined_latent = latent_image
|
||||
|
||||
sampler_opt=None
|
||||
if detailer_hook is not None:
|
||||
sampler_opt = detailer_hook.get_custom_sampler()
|
||||
|
||||
# ksampler
|
||||
for i in range(0, cycle):
|
||||
if detailer_hook is not None:
|
||||
detailer_hook.set_steps((i, cycle))
|
||||
if detailer_hook is not None:
|
||||
detailer_hook.set_steps((i, cycle))
|
||||
|
||||
refined_latent = detailer_hook.cycle_latent(refined_latent)
|
||||
refined_latent = detailer_hook.cycle_latent(refined_latent)
|
||||
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, upscaled_latent2, denoise2 = \
|
||||
detailer_hook.pre_ksample(model, seed+i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise)
|
||||
noise, is_touched = detailer_hook.get_custom_noise(seed+i, torch.zeros(latent_image['samples'].size()), is_touched=False)
|
||||
if not is_touched:
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, upscaled_latent2, denoise2 = \
|
||||
detailer_hook.pre_ksample(model, seed+i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise)
|
||||
noise, is_touched = detailer_hook.get_custom_noise(seed+i, torch.zeros(latent_image['samples'].size()), is_touched=False)
|
||||
if not is_touched:
|
||||
noise = None
|
||||
else:
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, _, denoise2 = \
|
||||
model, seed + i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise
|
||||
noise = None
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2,
|
||||
refined_latent, denoise2, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative,
|
||||
noise=noise, scheduler_func=scheduler_func, sampler_opt=sampler_opt)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
|
||||
# non-latent downscale - latent downscale cause bad quality
|
||||
start = time.time()
|
||||
if vae_tiled_decode:
|
||||
(refined_image,) = nodes.VAEDecodeTiled().decode(vae, refined_latent, 512) # using default settings
|
||||
logging.info(f"[Impact Pack] vae decoded (tiled) in {time.time() - start:.1f}s")
|
||||
else:
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, upscaled_latent2, denoise2 = \
|
||||
model, seed + i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise
|
||||
noise = None
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2,
|
||||
refined_latent, denoise2, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative,
|
||||
noise=noise, scheduler_func=scheduler_func)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
|
||||
# non-latent downscale - latent downscale cause bad quality
|
||||
start = time.time()
|
||||
if vae_tiled_decode:
|
||||
(refined_image,) = nodes.VAEDecodeTiled().decode(vae, refined_latent, 512) # using default settings
|
||||
print(f"[Impact Pack] vae decoded (tiled) in {time.time() - start:.1f}s")
|
||||
try:
|
||||
refined_image = vae.decode(refined_latent['samples'])
|
||||
except Exception:
|
||||
# usually an out-of-memory exception from the decode, so try a tiled approach
|
||||
logging.warning(f"[Impact Pack] failed after {time.time() - start:.1f}s, doing vae.decode_tiled 64...")
|
||||
refined_image = vae.decode_tiled(refined_latent["samples"], tile_x=64, tile_y=64, )
|
||||
logging.info(f"[Impact Pack] vae decoded in {time.time() - start:.1f}s")
|
||||
else:
|
||||
try:
|
||||
refined_image = vae.decode(refined_latent['samples'])
|
||||
except Exception as e:
|
||||
# usually an out-of-memory exception from the decode, so try a tiled approach
|
||||
print(f"[Impact Pack] failed after {time.time() - start:.1f}s, doing vae.decode_tiled 64...")
|
||||
refined_image = vae.decode_tiled(refined_latent["samples"], tile_x=64, tile_y=64, )
|
||||
print(f"[Impact Pack] vae decoded in {time.time() - start:.1f}s")
|
||||
# skipped
|
||||
refined_image = upscaled_image
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_image = detailer_hook.post_decode(refined_image)
|
||||
|
||||
# downscale
|
||||
refined_image = tensor_resize(refined_image, w, h)
|
||||
|
||||
# workaround: support WAN as an i2i model
|
||||
if len(refined_image.shape) == 5:
|
||||
refined_image = refined_image.squeeze(0)
|
||||
|
||||
refined_image = utils.tensor_resize(refined_image, w, h)
|
||||
|
||||
# prevent mixing of device
|
||||
refined_image = refined_image.cpu()
|
||||
@@ -446,7 +471,7 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
new_h = int(h * upscale)
|
||||
|
||||
if upscale <= 1.0 or new_w == 0 or new_h == 0:
|
||||
print(f"Detailer: force inpaint")
|
||||
logging.info("Detailer: force inpaint")
|
||||
upscale = 1.0
|
||||
new_w = w
|
||||
new_h = h
|
||||
@@ -454,7 +479,7 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
if detailer_hook is not None:
|
||||
new_w, new_h = detailer_hook.touch_scaled_size(new_w, new_h)
|
||||
|
||||
print(f"Detailer: segment upscale for ({bbox_w, bbox_h}) | crop region {w, h} x {upscale} -> {new_w, new_h}")
|
||||
logging.info(f"Detailer: segment upscale for ({bbox_w, bbox_h}) | crop region {w, h} x {upscale} -> {new_w, new_h}")
|
||||
|
||||
# upscale the mask tensor by a factor of 2 using bilinear interpolation
|
||||
if isinstance(noise_mask, np.ndarray):
|
||||
@@ -482,10 +507,10 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
image = torch.from_numpy(image).unsqueeze(0)
|
||||
|
||||
# upscale
|
||||
upscaled_image = tensor_resize(image, new_w, new_h)
|
||||
upscaled_image = utils.tensor_resize(image, new_w, new_h)
|
||||
|
||||
# ksampler
|
||||
samples = to_latent_image(upscaled_image, vae)['samples']
|
||||
samples = utils.to_latent_image(upscaled_image, vae)['samples']
|
||||
|
||||
if latent_frames is None:
|
||||
latent_frames = samples
|
||||
@@ -497,7 +522,7 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
positive, negative, cnet_images = control_net_wrapper.apply(positive, negative, torch.from_numpy(image_frames), noise_mask, use_acn=True)
|
||||
|
||||
if len(upscaled_mask) != len(image_frames) and len(upscaled_mask) > 1:
|
||||
print(f"[Impact Pack] WARN: DetailerForAnimateDiff - The number of the mask frames({len(upscaled_mask)}) and the image frames({len(image_frames)}) are different. Combine the mask frames and apply.")
|
||||
logging.warning(f"[Impact Pack] DetailerForAnimateDiff: The number of the mask frames({len(upscaled_mask)}) and the image frames({len(image_frames)}) are different. Combine the mask frames and apply.")
|
||||
combined_mask = upscaled_mask[0].to(torch.uint8)
|
||||
|
||||
for frame_mask in upscaled_mask[1:]:
|
||||
@@ -513,11 +538,16 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
'samples': latent_frames
|
||||
}
|
||||
|
||||
|
||||
sampler_opt=None
|
||||
if detailer_hook is not None:
|
||||
sampler_opt = detailer_hook.get_custom_sampler()
|
||||
|
||||
if detailer_hook is not None:
|
||||
latent = detailer_hook.post_encode(latent)
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
latent, denoise, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative, scheduler_func=scheduler_func)
|
||||
latent, denoise, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative, scheduler_func=scheduler_func, sampler_opt=sampler_opt)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
@@ -605,6 +635,122 @@ class SAMWrapper:
|
||||
return sam_predict(predictor, points, plabs, bbox, threshold)
|
||||
|
||||
|
||||
class SAM2Wrapper:
|
||||
def __init__(self, config, modelname, is_auto_mode, safe_to_gpu=None, device_mode="AUTO"):
|
||||
self.config = config
|
||||
self.modelname = modelname
|
||||
self.image_predictor = None
|
||||
self.video_predictor = None
|
||||
self.device_mode = device_mode
|
||||
self.safe_to_gpu = safe_to_gpu if safe_to_gpu is not None else SafeToGPU_stub()
|
||||
self.is_auto_mode = is_auto_mode
|
||||
|
||||
def prepare_device(self):
|
||||
pass
|
||||
|
||||
def prepare_image_device(self):
|
||||
if self.is_auto_mode:
|
||||
device = comfy.model_management.get_torch_device()
|
||||
self.safe_to_gpu.to_device(self.image_predictor.model, device=device)
|
||||
|
||||
def prepare_video_device(self):
|
||||
if self.is_auto_mode:
|
||||
device = comfy.model_management.get_torch_device()
|
||||
self.safe_to_gpu.to_device(self.video_predictor, device=device)
|
||||
|
||||
def release_device(self):
|
||||
if self.is_auto_mode:
|
||||
if self.image_predictor:
|
||||
self.image_predictor.model.to(device="cpu")
|
||||
if self.video_predictor:
|
||||
self.video_predictor.to(device="cpu")
|
||||
|
||||
def predict(self, image, points, plabs, bbox, threshold):
|
||||
if not is_sam2_available:
|
||||
raise Exception(sam2_unavailable_message)
|
||||
|
||||
if self.image_predictor is None:
|
||||
self.image_predictor = SAM2ImagePredictor(build_sam2(self.config, self.modelname))
|
||||
|
||||
self.prepare_image_device()
|
||||
|
||||
self.image_predictor.set_image(image)
|
||||
|
||||
return sam_predict(self.image_predictor, points, plabs, bbox, threshold)
|
||||
|
||||
def predict_video_segs(self, image_frames, segs):
|
||||
if not is_sam2_available:
|
||||
raise Exception(sam2_unavailable_message)
|
||||
|
||||
if self.video_predictor is None:
|
||||
self.video_predictor = build_sam2_video_predictor(self.config, self.modelname)
|
||||
|
||||
self.prepare_video_device()
|
||||
|
||||
orig_video_height = image_frames.shape[1]
|
||||
orig_video_width = image_frames.shape[2]
|
||||
|
||||
image_frames, padding = utils.resize_with_padding(image_frames, self.video_predictor.image_size, self.video_predictor.image_size)
|
||||
image_frames = image_frames.permute(0, 3, 1, 2)
|
||||
|
||||
inference_state = {}
|
||||
inference_state["images"] = image_frames
|
||||
inference_state["num_frames"] = len(image_frames)
|
||||
inference_state["video_height"] = self.video_predictor.image_size
|
||||
inference_state["video_width"] = self.video_predictor.image_size
|
||||
inference_state["offload_video_to_cpu"] = True
|
||||
inference_state["offload_state_to_cpu"] = self.device_mode == "CPU"
|
||||
inference_state["device"] = self.video_predictor.device
|
||||
|
||||
if inference_state["offload_state_to_cpu"]:
|
||||
inference_state["storage_device"] = torch.device("cpu")
|
||||
else:
|
||||
inference_state["storage_device"] = self.video_predictor.device
|
||||
|
||||
inference_state["point_inputs_per_obj"] = {}
|
||||
inference_state["mask_inputs_per_obj"] = {}
|
||||
inference_state["cached_features"] = {}
|
||||
inference_state["constants"] = {}
|
||||
|
||||
inference_state["obj_id_to_idx"] = OrderedDict()
|
||||
inference_state["obj_idx_to_id"] = OrderedDict()
|
||||
inference_state["obj_ids"] = []
|
||||
|
||||
inference_state["output_dict_per_obj"] = {}
|
||||
inference_state["temp_output_dict_per_obj"] = {}
|
||||
inference_state["frames_tracked_per_obj"] = {}
|
||||
self.video_predictor._get_image_feature(inference_state, frame_idx=0, batch_size=1)
|
||||
|
||||
temp_masks = {}
|
||||
for i in range(0, len(segs[1])):
|
||||
bbox = segs[1][i].bbox
|
||||
|
||||
adjusted_bbox = utils.adjust_bbox_after_resize(
|
||||
bbox,
|
||||
(orig_video_height, orig_video_width),
|
||||
(self.video_predictor.image_size, self.video_predictor.image_size),
|
||||
padding
|
||||
)
|
||||
|
||||
points = [utils.center_of_bbox(adjusted_bbox)]
|
||||
plabs = [1]
|
||||
self.video_predictor.add_new_points_or_box(inference_state=inference_state, frame_idx=0, obj_id=i, points=points, labels=plabs, box=adjusted_bbox)
|
||||
temp_masks[i] = []
|
||||
|
||||
for frame_idx, object_ids, masks in self.video_predictor.propagate_in_video(inference_state):
|
||||
for i in object_ids:
|
||||
m = masks[i]
|
||||
m = m.permute(1, 2, 0)
|
||||
temp_masks[i].append(m)
|
||||
|
||||
result = {}
|
||||
for k, v in temp_masks.items():
|
||||
m = torch.stack(v, dim=0)
|
||||
m = utils.remove_padding(m, padding)
|
||||
result[k] = utils.resize_with_padding(m, orig_video_width, orig_video_height)[0]
|
||||
|
||||
return result
|
||||
|
||||
class ESAMWrapper:
|
||||
def __init__(self, model, device):
|
||||
self.model = model
|
||||
@@ -630,10 +776,15 @@ class ESAMWrapper:
|
||||
def make_sam_mask(sam, segs, image, detection_hint, dilation,
|
||||
threshold, bbox_expansion, mask_hint_threshold, mask_hint_use_negative):
|
||||
|
||||
if not hasattr(sam, 'sam_wrapper'):
|
||||
if not hasattr(sam, 'sam_wrapper') and not isinstance(sam, SAM2Wrapper):
|
||||
raise Exception("[Impact Pack] Invalid SAMLoader is connected. Make sure 'SAMLoader (Impact)'.\nKnown issue: The ComfyUI-YOLO node overrides the SAMLoader (Impact), making it unusable. You need to uninstall ComfyUI-YOLO.\n\n\n")
|
||||
|
||||
sam_obj = sam.sam_wrapper
|
||||
|
||||
if isinstance(sam, SAM2Wrapper):
|
||||
sam_obj = sam
|
||||
else:
|
||||
sam_obj = sam.sam_wrapper
|
||||
|
||||
sam_obj.prepare_device()
|
||||
|
||||
try:
|
||||
@@ -651,7 +802,7 @@ def make_sam_mask(sam, segs, image, detection_hint, dilation,
|
||||
|
||||
for i in range(len(segs)):
|
||||
bbox = segs[i].bbox
|
||||
center = center_of_bbox(segs[i].bbox)
|
||||
center = utils.center_of_bbox(segs[i].bbox)
|
||||
points.append(center)
|
||||
|
||||
# small point is background, big point is foreground
|
||||
@@ -666,7 +817,7 @@ def make_sam_mask(sam, segs, image, detection_hint, dilation,
|
||||
else:
|
||||
for i in range(len(segs)):
|
||||
bbox = segs[i].bbox
|
||||
center = center_of_bbox(bbox)
|
||||
center = utils.center_of_bbox(bbox)
|
||||
|
||||
x1 = max(bbox[0] - bbox_expansion, 0)
|
||||
y1 = max(bbox[1] - bbox_expansion, 0)
|
||||
@@ -712,7 +863,7 @@ def make_sam_mask(sam, segs, image, detection_hint, dilation,
|
||||
plabs = [1, 1, 1, 1]
|
||||
|
||||
elif detection_hint == "mask-point-bbox":
|
||||
center = center_of_bbox(segs[i].bbox)
|
||||
center = utils.center_of_bbox(segs[i].bbox)
|
||||
points.append(center)
|
||||
plabs = [1]
|
||||
|
||||
@@ -733,14 +884,14 @@ def make_sam_mask(sam, segs, image, detection_hint, dilation,
|
||||
total_masks += detected_masks
|
||||
|
||||
# merge every collected masks
|
||||
mask = combine_masks2(total_masks)
|
||||
mask = utils.combine_masks2(total_masks)
|
||||
|
||||
finally:
|
||||
sam_obj.release_device()
|
||||
|
||||
if mask is not None:
|
||||
mask = mask.float()
|
||||
mask = dilate_mask(mask.cpu().numpy(), dilation)
|
||||
mask = utils.dilate_mask(mask.cpu().numpy(), dilation)
|
||||
mask = torch.from_numpy(mask)
|
||||
else:
|
||||
size = image.shape[0], image.shape[1]
|
||||
@@ -791,7 +942,7 @@ def generate_detection_hints(image, seg, center, detection_hint, dilated_bbox, m
|
||||
plabs = [1, 1, 1, 1]
|
||||
|
||||
elif detection_hint == "mask-point-bbox":
|
||||
center = center_of_bbox(seg.bbox)
|
||||
center = utils.center_of_bbox(seg.bbox)
|
||||
points.append(center)
|
||||
plabs = [1]
|
||||
|
||||
@@ -881,7 +1032,7 @@ def segs_scale_match(segs, target_shape):
|
||||
cropped_mask = cropped_mask.squeeze(0).squeeze(0).numpy()
|
||||
|
||||
if cropped_image is not None:
|
||||
cropped_image = tensor_resize(cropped_image if isinstance(cropped_image, torch.Tensor) else torch.from_numpy(cropped_image), new_w, new_h)
|
||||
cropped_image = utils.tensor_resize(cropped_image if isinstance(cropped_image, torch.Tensor) else torch.from_numpy(cropped_image), new_w, new_h)
|
||||
cropped_image = cropped_image.numpy()
|
||||
|
||||
new_seg = SEG(cropped_image, cropped_mask, seg.confidence, crop_region, bbox, seg.label, seg.control_net_wrapper)
|
||||
@@ -921,7 +1072,7 @@ def make_sam_mask_segmented(sam, segs, image, detection_hint, dilation,
|
||||
|
||||
for i in range(len(segs)):
|
||||
bbox = segs[i].bbox
|
||||
center = center_of_bbox(bbox)
|
||||
center = utils.center_of_bbox(bbox)
|
||||
points.append(center)
|
||||
|
||||
# small point is background, big point is foreground
|
||||
@@ -936,7 +1087,7 @@ def make_sam_mask_segmented(sam, segs, image, detection_hint, dilation,
|
||||
else:
|
||||
for i in range(len(segs)):
|
||||
bbox = segs[i].bbox
|
||||
center = center_of_bbox(bbox)
|
||||
center = utils.center_of_bbox(bbox)
|
||||
x1 = max(bbox[0] - bbox_expansion, 0)
|
||||
y1 = max(bbox[1] - bbox_expansion, 0)
|
||||
x2 = min(bbox[2] + bbox_expansion, image.shape[1])
|
||||
@@ -953,7 +1104,7 @@ def make_sam_mask_segmented(sam, segs, image, detection_hint, dilation,
|
||||
total_masks += detected_masks
|
||||
|
||||
# merge every collected masks
|
||||
mask = combine_masks2(total_masks)
|
||||
mask = utils.combine_masks2(total_masks)
|
||||
|
||||
finally:
|
||||
sam_obj.release_device()
|
||||
@@ -962,7 +1113,7 @@ def make_sam_mask_segmented(sam, segs, image, detection_hint, dilation,
|
||||
|
||||
if mask is not None:
|
||||
mask = mask.float()
|
||||
mask = dilate_mask(mask.cpu().numpy(), dilation)
|
||||
mask = utils.dilate_mask(mask.cpu().numpy(), dilation)
|
||||
mask = torch.from_numpy(mask)
|
||||
mask = mask.to(device=mask_working_device)
|
||||
else:
|
||||
@@ -979,10 +1130,10 @@ def make_sam_mask_segmented(sam, segs, image, detection_hint, dilation,
|
||||
|
||||
|
||||
def segs_bitwise_and_mask(segs, mask):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
if mask is None:
|
||||
print("[SegsBitwiseAndMask] Cannot operate: MASK is empty.")
|
||||
logging.warning("[SegsBitwiseAndMask] Cannot operate: MASK is empty.")
|
||||
return ([],)
|
||||
|
||||
items = []
|
||||
@@ -1005,10 +1156,10 @@ def segs_bitwise_and_mask(segs, mask):
|
||||
|
||||
|
||||
def segs_bitwise_subtract_mask(segs, mask):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
if mask is None:
|
||||
print("[SegsBitwiseSubtractMask] Cannot operate: MASK is empty.")
|
||||
logging.warning("[SegsBitwiseSubtractMask] Cannot operate: MASK is empty.")
|
||||
return ([],)
|
||||
|
||||
items = []
|
||||
@@ -1032,7 +1183,7 @@ def segs_bitwise_subtract_mask(segs, mask):
|
||||
|
||||
def apply_mask_to_each_seg(segs, masks):
|
||||
if masks is None:
|
||||
print("[SegsBitwiseAndMask] Cannot operate: MASK is empty.")
|
||||
logging.warning("[SegsBitwiseAndMask] Cannot operate: MASK is empty.")
|
||||
return (segs[0], [],)
|
||||
|
||||
items = []
|
||||
@@ -1061,7 +1212,7 @@ def dilate_segs(segs, factor):
|
||||
|
||||
new_segs = []
|
||||
for seg in segs[1]:
|
||||
new_mask = dilate_mask(seg.cropped_mask, factor)
|
||||
new_mask = utils.dilate_mask(seg.cropped_mask, factor)
|
||||
new_seg = SEG(seg.cropped_image, new_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
@@ -1077,7 +1228,7 @@ class ONNXDetector:
|
||||
def detect(self, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
drop_size = max(drop_size, 1)
|
||||
try:
|
||||
import impact.onnx as onnx
|
||||
import impact.impact_onnx as onnx
|
||||
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
@@ -1093,7 +1244,7 @@ class ONNXDetector:
|
||||
x1, y1, x2, y2 = item_bbox
|
||||
|
||||
if x2 - x1 > drop_size and y2 - y1 > drop_size: # minimum dimension must be (2,2) to avoid squeeze issue
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
crop_region = utils.make_crop_region(w, h, item_bbox, crop_factor)
|
||||
|
||||
if detailer_hook is not None:
|
||||
crop_region = item_bbox.post_crop_region(w, h, item_bbox, crop_region)
|
||||
@@ -1103,7 +1254,7 @@ class ONNXDetector:
|
||||
# prepare cropped mask
|
||||
cropped_mask = np.zeros((crop_y2 - crop_y1, crop_x2 - crop_x1))
|
||||
cropped_mask[y1 - crop_y1:y2 - crop_y1, x1 - crop_x1:x2 - crop_x1] = 1
|
||||
cropped_mask = dilate_mask(cropped_mask, dilation)
|
||||
cropped_mask = utils.dilate_mask(cropped_mask, dilation)
|
||||
|
||||
# make items. just convert the integer label to a string
|
||||
item = SEG(None, cropped_mask, scores[i], crop_region, item_bbox, str(labels[i]), None)
|
||||
@@ -1117,8 +1268,7 @@ class ONNXDetector:
|
||||
|
||||
return segs
|
||||
except Exception as e:
|
||||
print(f"ONNXDetector: unable to execute.\n{e}")
|
||||
pass
|
||||
logging.error(f"ONNXDetector: unable to execute.\n{e}")
|
||||
|
||||
def detect_combined(self, image, threshold, dilation):
|
||||
return segs_to_combined_mask(self.detect(image, threshold, dilation, 1))
|
||||
@@ -1145,7 +1295,7 @@ def batch_mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size=1, labe
|
||||
def mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size=1, label='A', crop_min_size=None, detailer_hook=None, is_contour=True):
|
||||
drop_size = max(drop_size, 1)
|
||||
if mask is None:
|
||||
print("[mask_to_segs] Cannot operate: MASK is empty.")
|
||||
logging.info("[mask_to_segs] Cannot operate: MASK is empty.")
|
||||
return ([],)
|
||||
|
||||
if isinstance(mask, np.ndarray):
|
||||
@@ -1154,11 +1304,11 @@ def mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size=1, label='A',
|
||||
try:
|
||||
mask = mask.numpy()
|
||||
except AttributeError:
|
||||
print("[mask_to_segs] Cannot operate: MASK is not a NumPy array or Tensor.")
|
||||
logging.info("[mask_to_segs] Cannot operate: MASK is not a NumPy array or Tensor.")
|
||||
return ([],)
|
||||
|
||||
if mask is None:
|
||||
print("[mask_to_segs] Cannot operate: MASK is empty.")
|
||||
logging.info("[mask_to_segs] Cannot operate: MASK is empty.")
|
||||
return ([],)
|
||||
|
||||
result = []
|
||||
@@ -1178,7 +1328,7 @@ def mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size=1, label='A',
|
||||
np.max(indices[1]),
|
||||
np.max(indices[0]),
|
||||
)
|
||||
crop_region = make_crop_region(
|
||||
crop_region = utils.make_crop_region(
|
||||
mask_i.shape[1], mask_i.shape[0], bbox, crop_factor
|
||||
)
|
||||
x1, y1, x2, y2 = crop_region
|
||||
@@ -1212,7 +1362,7 @@ def mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size=1, label='A',
|
||||
|
||||
x, y, w, h = cv2.boundingRect(contour)
|
||||
bbox = x, y, x + w, y + h
|
||||
crop_region = make_crop_region(
|
||||
crop_region = utils.make_crop_region(
|
||||
mask_i.shape[1], mask_i.shape[0], bbox, crop_factor, crop_min_size
|
||||
)
|
||||
|
||||
@@ -1246,9 +1396,9 @@ def mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size=1, label='A',
|
||||
result.append(item)
|
||||
|
||||
if not result:
|
||||
print(f"[mask_to_segs] Empty mask.")
|
||||
logging.info("[mask_to_segs] Empty mask.")
|
||||
|
||||
print(f"# of Detected SEGS: {len(result)}")
|
||||
logging.info(f"# of Detected SEGS: {len(result)}")
|
||||
# for r in result:
|
||||
# print(f"\tbbox={r.bbox}, crop={r.crop_region}, label={r.label}")
|
||||
|
||||
@@ -1286,7 +1436,7 @@ def mediapipe_facemesh_to_segs(image, crop_factor, bbox_fill, crop_min_size, dro
|
||||
tensor = torch.from_numpy(convex_segment)
|
||||
mask_tensor = torch.any(tensor != 0, dim=-1).float()
|
||||
mask_tensor = mask_tensor.squeeze(0)
|
||||
mask_tensor = torch.from_numpy(dilate_mask(mask_tensor.numpy(), dilation))
|
||||
mask_tensor = torch.from_numpy(utils.dilate_mask(mask_tensor.numpy(), dilation))
|
||||
mask_list.append(mask_tensor.unsqueeze(0))
|
||||
|
||||
return mask_list
|
||||
@@ -1380,7 +1530,7 @@ def vae_decode(vae, samples, use_tile, hook, tile_size=512, overlap=64):
|
||||
if 'overlap' in inspect.signature(decoder.decode).parameters:
|
||||
pixels = decoder.decode(vae, samples, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
print(f"[Impact Pack] Your ComfyUI is outdated.")
|
||||
logging.warning("[Impact Pack] Your ComfyUI is outdated.")
|
||||
pixels = decoder.decode(vae, samples, tile_size)[0]
|
||||
else:
|
||||
pixels = nodes.VAEDecode().decode(vae, samples)[0]
|
||||
@@ -1397,7 +1547,7 @@ def vae_encode(vae, pixels, use_tile, hook, tile_size=512, overlap=64):
|
||||
if 'overlap' in inspect.signature(encoder.encode).parameters:
|
||||
samples = encoder.encode(vae, pixels, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
print(f"[Impact Pack] Your ComfyUI is outdated.")
|
||||
logging.warning("[Impact Pack] Your ComfyUI is outdated.")
|
||||
samples = encoder.encode(vae, pixels, tile_size)[0]
|
||||
else:
|
||||
samples = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
@@ -1466,7 +1616,7 @@ def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upsca
|
||||
pixels = model_upscale.ImageUpscaleWithModel().upscale(upscale_model, pixels)[0]
|
||||
current_w = pixels.shape[2]
|
||||
if current_w == w:
|
||||
print(f"[latent_upscale_on_pixel_space_with_model] x1 upscale model selected")
|
||||
logging.info("[latent_upscale_on_pixel_space_with_model] x1 upscale model selected")
|
||||
break
|
||||
|
||||
# downscale to target scale
|
||||
@@ -1502,7 +1652,7 @@ def latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_mod
|
||||
pixels = model_upscale.ImageUpscaleWithModel().upscale(upscale_model, pixels)[0]
|
||||
current_w = pixels.shape[2]
|
||||
if current_w == w:
|
||||
print(f"[latent_upscale_on_pixel_space_with_model] x1 upscale model selected")
|
||||
logging.info("[latent_upscale_on_pixel_space_with_model] x1 upscale model selected")
|
||||
break
|
||||
|
||||
# downscale to target scale
|
||||
@@ -1521,7 +1671,7 @@ class TwoSamplersForMaskUpscaler:
|
||||
hook_full_opt=None,
|
||||
tile_size=512):
|
||||
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
mask = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1]))
|
||||
|
||||
@@ -1539,7 +1689,7 @@ class TwoSamplersForMaskUpscaler:
|
||||
def upscale(self, step_info, samples, upscale_factor, save_temp_prefix=None):
|
||||
scale_method, sample_schedule, use_tiled_vae, base_sampler, mask_sampler, mask, vae = self.params
|
||||
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
self.prepare_hook(step_info)
|
||||
|
||||
@@ -1569,7 +1719,7 @@ class TwoSamplersForMaskUpscaler:
|
||||
def upscale_shape(self, step_info, samples, w, h, save_temp_prefix=None):
|
||||
scale_method, sample_schedule, use_tiled_vae, base_sampler, mask_sampler, mask, vae = self.params
|
||||
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
self.prepare_hook(step_info)
|
||||
|
||||
@@ -1625,17 +1775,17 @@ class TwoSamplersForMaskUpscaler:
|
||||
return cur_step % 2 == 0 or cur_step >= total_step - 1
|
||||
|
||||
def do_samples(self, step_info, base_sampler, mask_sampler, sample_schedule, mask, upscaled_latent):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
if self.is_full_sample_time(step_info, sample_schedule):
|
||||
print(f"step_info={step_info} / full time")
|
||||
logging.info(f"step_info={step_info} / full time")
|
||||
|
||||
upscaled_latent = base_sampler.sample(upscaled_latent, self.hook_base)
|
||||
sampler = self.full_sampler if self.full_sampler is not None else base_sampler
|
||||
return sampler.sample(upscaled_latent, self.hook_full)
|
||||
|
||||
else:
|
||||
print(f"step_info={step_info} / non-full time")
|
||||
logging.info(f"step_info={step_info} / non-full time")
|
||||
# upscale mask
|
||||
if mask.ndim == 2:
|
||||
mask = mask[None, :, :, None]
|
||||
@@ -1783,11 +1933,11 @@ class IPAdapterWrapper:
|
||||
|
||||
if 'IPAdapterAdvanced' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
if 'IPAdapterApply' in nodes.NODE_CLASS_MAPPINGS:
|
||||
raise Exception(f"[ERROR] 'ComfyUI IPAdapter Plus' is outdated.")
|
||||
raise Exception("[ERROR] 'ComfyUI IPAdapter Plus' is outdated.")
|
||||
|
||||
utils.try_install_custom_node('https://github.com/cubiq/ComfyUI_IPAdapter_plus',
|
||||
"To use 'IPAdapterApplySEGS' node, 'ComfyUI IPAdapter Plus' extension is required.")
|
||||
raise Exception(f"[ERROR] To use IPAdapterApplySEGS, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
raise Exception("[ERROR] To use IPAdapterApplySEGS, you need to install 'ComfyUI IPAdapter Plus'")
|
||||
|
||||
obj = nodes.NODE_CLASS_MAPPINGS['IPAdapterAdvanced']
|
||||
|
||||
@@ -1921,7 +2071,7 @@ class ControlNetAdvancedWrapper:
|
||||
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.")
|
||||
logging.error("[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)
|
||||
@@ -2069,7 +2219,7 @@ class BBoxDetectorBasedOnCLIPSeg:
|
||||
def detect(self, image, bbox_threshold, bbox_dilation, bbox_crop_factor, drop_size=1, detailer_hook=None):
|
||||
mask = self.detect_combined(image, bbox_threshold, bbox_dilation)
|
||||
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
segs = mask_to_segs(mask, False, bbox_crop_factor, True, drop_size, detailer_hook=detailer_hook)
|
||||
|
||||
@@ -2099,7 +2249,7 @@ class BBoxDetectorBasedOnCLIPSeg:
|
||||
prompt = self.aux if self.prompt == '' and self.aux is not None else self.prompt
|
||||
|
||||
mask, _, _ = CLIPSeg().segment_image(image, prompt, self.blur, threshold, dilation_factor)
|
||||
mask = to_binary_mask(mask)
|
||||
mask = utils.to_binary_mask(mask)
|
||||
return mask
|
||||
|
||||
def setAux(self, x):
|
||||
@@ -2185,7 +2335,7 @@ def adaptive_mask_paste(dest_mask, src_mask, bbox):
|
||||
def crop_condition_mask(mask, image, crop_region):
|
||||
cond_scale = (mask.shape[1] / image.shape[1], mask.shape[2] / image.shape[2])
|
||||
mask_region = [round(v * cond_scale[i % 2]) for i, v in enumerate(crop_region)]
|
||||
return crop_ndarray3(mask, mask_region)
|
||||
return utils.crop_ndarray3(mask, mask_region)
|
||||
|
||||
|
||||
class SafeToGPU:
|
||||
@@ -2201,10 +2351,15 @@ class SafeToGPU:
|
||||
if model_management.get_free_memory(device) > self.size * 1.3:
|
||||
try:
|
||||
obj.to(device)
|
||||
except:
|
||||
print(f"WARN: The model is not moved to the '{device}' due to insufficient memory. [1]")
|
||||
except Exception:
|
||||
logging.warning(f"[Impact Pack] The model is not moved to the '{device}' due to insufficient memory. [1]")
|
||||
else:
|
||||
print(f"WARN: The model is not moved to the '{device}' due to insufficient memory. [2]")
|
||||
logging.warning(f"[Impact Pack] The model is not moved to the '{device}' due to insufficient memory. [2]")
|
||||
|
||||
|
||||
class SafeToGPU_stub():
|
||||
def to_device(self, obj, device):
|
||||
pass
|
||||
|
||||
|
||||
from comfy.cli_args import args, LatentPreviewMethod
|
||||
@@ -2238,14 +2393,14 @@ try:
|
||||
taesd = TAESD(None, taesd_decoder_path, latent_channels=latent_format.latent_channels).to(device)
|
||||
previewer = TAESDPreviewerImpl(taesd)
|
||||
else:
|
||||
print("Warning: TAESD previews enabled, but could not find models/vae_approx/{}".format(
|
||||
logging.warning("[Impact Pack] TAESD previews enabled, but could not find models/vae_approx/{}".format(
|
||||
latent_format.taesd_decoder_name))
|
||||
|
||||
if previewer is None:
|
||||
previewer = Latent2RGBPreviewer(latent_format.latent_rgb_factors)
|
||||
return previewer
|
||||
|
||||
except:
|
||||
print(f"#########################################################################")
|
||||
print(f"[ERROR] ComfyUI-Impact-Pack: Please update ComfyUI to the latest version.")
|
||||
print(f"#########################################################################")
|
||||
except Exception:
|
||||
logging.error("#########################################################################")
|
||||
logging.error("[ERROR] ComfyUI-Impact-Pack: Please update ComfyUI to the latest version.")
|
||||
logging.error("#########################################################################")
|
||||
|
||||
@@ -14,4 +14,4 @@ detection_labels = [
|
||||
"tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven",
|
||||
"toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
|
||||
"hair drier", "toothbrush"
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
import impact.core as core
|
||||
from nodes import MAX_RESOLUTION
|
||||
import impact.segs_nodes as segs_nodes
|
||||
@@ -163,7 +165,7 @@ class SegmDetectorCombined:
|
||||
mask = segm_detector.detect_combined(image, threshold, dilation)
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((image.shape[2], image.shape[1]), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
|
||||
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
@@ -183,7 +185,7 @@ class BboxDetectorCombined(SegmDetectorCombined):
|
||||
mask = bbox_detector.detect_combined(image, threshold, dilation)
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((image.shape[2], image.shape[1]), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
|
||||
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
@@ -298,6 +300,68 @@ class SimpleDetectorForEachPipe:
|
||||
sam_mask_hint_threshold, post_dilation=post_dilation, sam_model_opt=sam_model_opt, segm_detector_opt=segm_detector_opt,
|
||||
detailer_hook=detailer_hook)
|
||||
|
||||
class SAM2VideoDetectorSEGS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image_frames": ("IMAGE", ),
|
||||
|
||||
"bbox_detector": ("BBOX_DETECTOR", ),
|
||||
"sam2_model": ("SAM_MODEL", ),
|
||||
|
||||
"bbox_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"sam2_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
|
||||
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
||||
"drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Detector"
|
||||
|
||||
@staticmethod
|
||||
def doit(bbox_detector, sam2_model, image_frames, bbox_threshold, sam2_threshold, crop_factor, drop_size):
|
||||
if not isinstance(sam2_model, core.SAM2Wrapper):
|
||||
logging.error("[Impact Pack] To use the SAM2VideoDetectorSEGS node, a SAM2 model must be provided as input to `sam2_model`.")
|
||||
raise Exception("To use the SAM2VideoDetectorSEGS node, a SAM2 model must be provided as input to `sam2_model`.")
|
||||
|
||||
segs = bbox_detector.detect(image_frames[0].unsqueeze(0), bbox_threshold, 0, 0, drop_size)
|
||||
segs_masks = sam2_model.predict_video_segs(image_frames, segs)
|
||||
|
||||
def get_whole_merged_mask(all_masks):
|
||||
merged_mask = (all_masks[0] * 255).to(torch.uint8)
|
||||
for mask in all_masks[1:]:
|
||||
merged_mask |= (mask * 255).to(torch.uint8)
|
||||
|
||||
merged_mask = (merged_mask / 255.0).to(torch.float32)
|
||||
merged_mask = utils.to_binary_mask(merged_mask, 0.1)[0]
|
||||
return merged_mask
|
||||
|
||||
new_segs = []
|
||||
for k, v in segs_masks.items():
|
||||
v = v.squeeze(3)
|
||||
m = get_whole_merged_mask(v)
|
||||
seg = segs_nodes.MaskToSEGS.doit(m, False, crop_factor, False, drop_size, contour_fill=True)[0][1]
|
||||
|
||||
if len(seg) == 0:
|
||||
continue
|
||||
|
||||
seg = seg[0]
|
||||
|
||||
x1, y1, x2, y2 = seg.crop_region
|
||||
masks = []
|
||||
for mask in v:
|
||||
masks.append(mask[y1:y2, x1:x2])
|
||||
cropped_mask = torch.stack(masks)
|
||||
cropped_mask = (cropped_mask >= (sam2_threshold*100-50)).to(torch.uint8).cpu()
|
||||
new_seg = SEG(seg.cropped_image, cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
return ((segs[0], new_segs), )
|
||||
|
||||
|
||||
class SimpleDetectorForAnimateDiff:
|
||||
@classmethod
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import comfy
|
||||
import re
|
||||
from impact.utils import *
|
||||
from impact import utils
|
||||
|
||||
|
||||
hf_transformer_model_urls = [
|
||||
"rizvandwiki/gender-classification-2",
|
||||
@@ -138,10 +139,10 @@ class SEGS_Classify:
|
||||
cropped_image = seg.cropped_image
|
||||
elif ref_image_opt is not None:
|
||||
# take from original image
|
||||
cropped_image = crop_image(ref_image_opt, seg.crop_region)
|
||||
cropped_image = utils.crop_image(ref_image_opt, seg.crop_region)
|
||||
|
||||
if cropped_image is not None:
|
||||
cropped_image = to_pil(cropped_image)
|
||||
cropped_image = utils.to_pil(cropped_image)
|
||||
res = classifier(cropped_image)
|
||||
classified.append((seg, res))
|
||||
|
||||
|
||||
@@ -83,3 +83,24 @@ class PreviewDetailerHookProvider:
|
||||
def doit(self, quality, unique_id):
|
||||
hook = hooks.PreviewDetailerHook(unique_id, quality)
|
||||
return hook, hook
|
||||
|
||||
|
||||
class LamaRemoverDetailerHookProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"mask_threshold":("INT", {"default": 250, "min": 0, "max": 255, "step": 1, "display": "slider"}),
|
||||
"gaussblur_radius": ("INT", {"default": 8, "min": 0, "max": 20, "step": 1, "display": "slider"}),
|
||||
"skip_sampling": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DETAILER_HOOK", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, mask_threshold, gaussblur_radius, skip_sampling):
|
||||
hook = hooks.LamaRemoverDetailerHook(mask_threshold, gaussblur_radius, skip_sampling)
|
||||
return (hook, )
|
||||
|
||||
+51
-5
@@ -10,6 +10,7 @@ import folder_paths
|
||||
import os
|
||||
from comfy_extras import nodes_custom_sampler
|
||||
import math
|
||||
import logging
|
||||
|
||||
|
||||
class PixelKSampleHook:
|
||||
@@ -25,7 +26,7 @@ class PixelKSampleHook:
|
||||
def post_decode(self, pixels):
|
||||
return pixels
|
||||
|
||||
def post_upscale(self, pixels):
|
||||
def post_upscale(self, pixels, mask=None):
|
||||
return pixels
|
||||
|
||||
def post_encode(self, samples):
|
||||
@@ -64,8 +65,8 @@ class PixelKSampleHookCombine(PixelKSampleHook):
|
||||
def post_decode(self, pixels):
|
||||
return self.hook2.post_decode(self.hook1.post_decode(pixels))
|
||||
|
||||
def post_upscale(self, pixels):
|
||||
return self.hook2.post_upscale(self.hook1.post_upscale(pixels))
|
||||
def post_upscale(self, pixels, mask=None):
|
||||
return self.hook2.post_upscale(self.hook1.post_upscale(pixels, mask), mask)
|
||||
|
||||
def post_encode(self, samples):
|
||||
return self.hook2.post_encode(self.hook1.post_encode(samples))
|
||||
@@ -109,6 +110,15 @@ class DetailerHookCombine(PixelKSampleHookCombine):
|
||||
noise_2nd, is_touched = self.hook2.get_custom_noise(seed, noise, is_touched)
|
||||
return noise, is_touched
|
||||
|
||||
def get_custom_sampler(self):
|
||||
if self.hook1.get_custom_sampler() is not None:
|
||||
return self.hook1.get_custom_sampler()
|
||||
else:
|
||||
return self.hook2.get_custom_sampler()
|
||||
|
||||
def get_skip_sampling(self):
|
||||
return self.hook1.get_skip_sampling() and self.hook2.get_skip_sampling()
|
||||
|
||||
|
||||
class SimpleCfgScheduleHook(PixelKSampleHook):
|
||||
target_cfg = 0
|
||||
@@ -173,6 +183,21 @@ class DetailerHook(PixelKSampleHook):
|
||||
def get_custom_noise(self, seed, noise, is_touched):
|
||||
return noise, is_touched
|
||||
|
||||
def get_custom_sampler(self):
|
||||
return None
|
||||
|
||||
def get_skip_sampling(self):
|
||||
return False
|
||||
|
||||
|
||||
class CustomSamplerDetailerHookProvider(DetailerHook):
|
||||
def __init__(self, sampler):
|
||||
super().__init__()
|
||||
self.sampler = sampler
|
||||
|
||||
def get_custom_sampler(self):
|
||||
return self.sampler
|
||||
|
||||
|
||||
# class CustomNoiseDetailerHookProvider(DetailerHook):
|
||||
# def __init__(self, noise):
|
||||
@@ -315,7 +340,7 @@ class InjectNoiseHook(PixelKSampleHook):
|
||||
|
||||
strength = self.start_strength + (self.end_strength - self.start_strength) * cur_step / self.total_step
|
||||
samples = InjectNoise().inject_noise(samples, strength, noise, mask)[0]
|
||||
print(f"[Impact Pack] InjectNoiseHook: strength = {strength}")
|
||||
logging.info(f"[Impact Pack] InjectNoiseHook: strength = {strength}")
|
||||
|
||||
if mask is not None:
|
||||
samples['noise_mask'] = mask
|
||||
@@ -346,7 +371,7 @@ class UnsamplerHook(PixelKSampleHook):
|
||||
end_at_step = self.start_end_at_step + (self.end_end_at_step - self.start_end_at_step) * cur_step / self.total_step
|
||||
end_at_step = int(end_at_step)
|
||||
|
||||
print(f"[Impact Pack] UnsamplerHook: end_at_step = {end_at_step}")
|
||||
logging.info(f"[Impact Pack] UnsamplerHook: end_at_step = {end_at_step}")
|
||||
|
||||
# inj noise
|
||||
mask = None
|
||||
@@ -486,6 +511,27 @@ class SEGSLabelFilterDetailerHook(DetailerHook):
|
||||
return segs_nodes.SEGSLabelFilter().doit(segs, "", self.labels)[0]
|
||||
|
||||
|
||||
class LamaRemoverDetailerHook(DetailerHook):
|
||||
def __init__(self, mask_threshold, gaussblur_radius, skip_sampling):
|
||||
super().__init__()
|
||||
self.mask_threshold = mask_threshold
|
||||
self.gaussblur_radius = gaussblur_radius
|
||||
self.skip_sampling = skip_sampling
|
||||
|
||||
def post_upscale(self, img, mask=None):
|
||||
if "LamaRemover" in nodes.NODE_CLASS_MAPPINGS:
|
||||
lama_remover_obj = nodes.NODE_CLASS_MAPPINGS['LamaRemover']()
|
||||
else:
|
||||
utils.try_install_custom_node('https://github.com/Layer-norm/comfyui-lama-remover',
|
||||
"To use 'LAMARemoverDetailerHookProvider', 'comfyui-lama-remover' nodepack is required.")
|
||||
raise Exception("'LamaRemover' node is not installed.")
|
||||
|
||||
return lama_remover_obj.lama_remover(img, masks=mask, mask_threshold=self.mask_threshold, gaussblur_radius=self.gaussblur_radius, invert_mask=False)[0]
|
||||
|
||||
def get_skip_sampling(self):
|
||||
return self.skip_sampling
|
||||
|
||||
|
||||
class PreviewDetailerHook(DetailerHook):
|
||||
def __init__(self, node_id, quality):
|
||||
super().__init__()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import impact.additional_dependencies
|
||||
from impact.utils import *
|
||||
import numpy as np
|
||||
from impact import utils
|
||||
import logging
|
||||
|
||||
impact.additional_dependencies.ensure_onnx_package()
|
||||
|
||||
@@ -8,7 +10,7 @@ try:
|
||||
|
||||
def onnx_inference(image, onnx_model):
|
||||
# prepare image
|
||||
pil = tensor2pil(image)
|
||||
pil = utils.tensor2pil(image)
|
||||
image = np.ascontiguousarray(pil)
|
||||
image = image[:, :, ::-1] # to BGR image
|
||||
image = image.astype(np.float32)
|
||||
@@ -33,6 +35,5 @@ try:
|
||||
boxes = boxes[0][:idx].astype(np.uint32)
|
||||
|
||||
return labels, scores, boxes
|
||||
except Exception as e:
|
||||
print("[ERROR] ComfyUI-Impact-Pack: 'onnxruntime' package doesn't support 'python 3.11', yet.")
|
||||
print(f"\t{e}")
|
||||
except Exception:
|
||||
logging.error("[Impact Pack] ComfyUI-Impact-Pack: 'onnxruntime' package doesn't support 'python 3.11', yet.\t{e}")
|
||||
+129
-96
@@ -12,7 +12,6 @@ import re
|
||||
|
||||
import impact.wildcards
|
||||
|
||||
from impact.utils import *
|
||||
import impact.core as core
|
||||
from impact.core import SEG
|
||||
from impact.config import latent_letter_path
|
||||
@@ -29,12 +28,17 @@ import impact.wildcards as wildcards
|
||||
from . import hooks
|
||||
from . import utils
|
||||
import inspect
|
||||
import folder_paths
|
||||
import torch
|
||||
import nodes
|
||||
import cv2
|
||||
import logging
|
||||
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
logging.warning("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -44,11 +48,8 @@ model_path = folder_paths.models_dir
|
||||
|
||||
|
||||
# folder_paths.supported_pt_extensions
|
||||
add_folder_path_and_extensions("mmdets_bbox", [os.path.join(model_path, "mmdets", "bbox")], folder_paths.supported_pt_extensions)
|
||||
add_folder_path_and_extensions("mmdets_segm", [os.path.join(model_path, "mmdets", "segm")], folder_paths.supported_pt_extensions)
|
||||
add_folder_path_and_extensions("mmdets", [os.path.join(model_path, "mmdets")], folder_paths.supported_pt_extensions)
|
||||
add_folder_path_and_extensions("sams", [os.path.join(model_path, "sams")], folder_paths.supported_pt_extensions)
|
||||
add_folder_path_and_extensions("onnx", [os.path.join(model_path, "onnx")], {'.onnx'})
|
||||
utils.add_folder_path_and_extensions("sams", [os.path.join(model_path, "sams")], folder_paths.supported_pt_extensions)
|
||||
utils.add_folder_path_and_extensions("onnx", [os.path.join(model_path, "onnx")], {'.onnx'})
|
||||
|
||||
|
||||
# Nodes
|
||||
@@ -89,13 +90,25 @@ class CLIPSegDetectorProvider:
|
||||
if "CLIPSeg" in nodes.NODE_CLASS_MAPPINGS:
|
||||
return (core.BBoxDetectorBasedOnCLIPSeg(text, blur, threshold, dilation_factor), )
|
||||
else:
|
||||
print("[ERROR] CLIPSegToBboxDetector: CLIPSeg custom node isn't installed. You must install biegert/ComfyUI-CLIPSeg extension to use this node.")
|
||||
logging.error("[ERROR] CLIPSegToBboxDetector: CLIPSeg custom node isn't installed. You must install biegert/ComfyUI-CLIPSeg extension to use this node.")
|
||||
raise Exception("[ERROR] CLIPSegToBboxDetector: CLIPSeg custom node isn't installed. You must install biegert/ComfyUI-CLIPSeg extension to use this node.")
|
||||
|
||||
|
||||
sam2_config_table = {
|
||||
'sam2.1_hiera_base_plus.pt': 'configs/sam2.1/sam2.1_hiera_b+.yaml',
|
||||
'sam2.1_hiera_large.pt': 'configs/sam2.1/sam2.1_hiera_l.yaml',
|
||||
'sam2.1_hiera_small.pt': 'configs/sam2.1/sam2.1_hiera_s.yaml',
|
||||
'sam2.1_hiera_tiny.pt': 'configs/sam2.1/sam2.1_hiera_t.yaml',
|
||||
'sam2_hiera_tiny.pt': 'configs/sam2/sam2_hiera_t.yaml',
|
||||
'sam2_hiera_small.pt': 'configs/sam2/sam2_hiera_s.yaml',
|
||||
'sam2_hiera_base_plus.pt': 'configs/sam2/sam2_hiera_b+.yaml',
|
||||
'sam2_hiera_large.pt': 'configs/sam2/sam2_hiera_l.yaml'
|
||||
}
|
||||
|
||||
class SAMLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
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 and (x.endswith('.pt') or x.endswith('.pth') or x.endswith('.safetensors'))]
|
||||
|
||||
if 'ESAM_ModelLoader_Zho' in nodes.NODE_CLASS_MAPPINGS:
|
||||
models.append('ESAM')
|
||||
@@ -119,7 +132,7 @@ class SAMLoader:
|
||||
def load_model(self, model_name, device_mode="auto"):
|
||||
if model_name == 'ESAM':
|
||||
if 'ESAM_ModelLoader_Zho' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
try_install_custom_node('https://github.com/ZHO-ZHO-ZHO/ComfyUI-YoloWorld-EfficientSAM',
|
||||
utils.try_install_custom_node('https://github.com/ZHO-ZHO-ZHO/ComfyUI-YoloWorld-EfficientSAM',
|
||||
"To use 'ESAM' model, 'ComfyUI-YoloWorld-EfficientSAM' extension is required.")
|
||||
raise Exception("'ComfyUI-YoloWorld-EfficientSAM' node isn't installed.")
|
||||
|
||||
@@ -133,20 +146,25 @@ class SAMLoader:
|
||||
|
||||
sam_obj = core.ESAMWrapper(esam, device_mode)
|
||||
esam.sam_wrapper = sam_obj
|
||||
|
||||
print(f"Loads EfficientSAM model: (device:{device_mode})")
|
||||
|
||||
logging.info(f"Loads EfficientSAM model: (device:{device_mode})")
|
||||
return (esam, )
|
||||
|
||||
modelname = folder_paths.get_full_path("sams", model_name)
|
||||
|
||||
if 'vit_h' in model_name:
|
||||
model_kind = 'vit_h'
|
||||
elif 'vit_l' in model_name:
|
||||
model_kind = 'vit_l'
|
||||
elif model_name in sam2_config_table:
|
||||
model_kind = 'sam2'
|
||||
config = sam2_config_table[model_name]
|
||||
modelname = folder_paths.get_full_path("sams", model_name)
|
||||
else:
|
||||
model_kind = 'vit_b'
|
||||
modelname = folder_paths.get_full_path("sams", model_name)
|
||||
|
||||
if 'vit_h' in model_name:
|
||||
model_kind = 'vit_h'
|
||||
elif 'vit_l' in model_name:
|
||||
model_kind = 'vit_l'
|
||||
else:
|
||||
model_kind = 'vit_b'
|
||||
|
||||
sam = sam_model_registry[model_kind](checkpoint=modelname)
|
||||
|
||||
sam = sam_model_registry[model_kind](checkpoint=modelname)
|
||||
size = os.path.getsize(modelname)
|
||||
safe_to = core.SafeToGPU(size)
|
||||
|
||||
@@ -158,10 +176,14 @@ class SAMLoader:
|
||||
|
||||
is_auto_mode = device_mode == "AUTO"
|
||||
|
||||
sam_obj = core.SAMWrapper(sam, is_auto_mode=is_auto_mode, safe_to_gpu=safe_to)
|
||||
sam.sam_wrapper = sam_obj
|
||||
if model_kind == 'sam2':
|
||||
sam = core.SAM2Wrapper(config=config, modelname=modelname, is_auto_mode=is_auto_mode, safe_to_gpu=safe_to, device_mode=device_mode)
|
||||
logging.info(f"Loads SAM2 model: {modelname} (device:{device_mode})")
|
||||
else:
|
||||
sam_obj = core.SAMWrapper(sam, is_auto_mode=is_auto_mode, safe_to_gpu=safe_to)
|
||||
sam.sam_wrapper = sam_obj
|
||||
logging.info(f"Loads SAM model: {modelname} (device:{device_mode})")
|
||||
|
||||
print(f"Loads SAM model: {modelname} (device:{device_mode})")
|
||||
return (sam, )
|
||||
|
||||
|
||||
@@ -282,14 +304,14 @@ class DetailerForEach:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
for i, seg in enumerate(ordered_segs):
|
||||
cropped_image = crop_ndarray4(image.cpu().numpy(), seg.crop_region) # Never use seg.cropped_image to handle overlapping area
|
||||
cropped_image = to_tensor(cropped_image)
|
||||
mask = to_tensor(seg.cropped_mask)
|
||||
mask = tensor_gaussian_blur_mask(mask, feather)
|
||||
cropped_image = utils.crop_ndarray4(image.cpu().numpy(), seg.crop_region) # Never use seg.cropped_image to handle overlapping area
|
||||
cropped_image = utils.to_tensor(cropped_image)
|
||||
mask = utils.to_tensor(seg.cropped_mask)
|
||||
mask = utils.tensor_gaussian_blur_mask(mask, feather)
|
||||
|
||||
is_mask_all_zeros = (seg.cropped_mask == 0).all().item()
|
||||
if is_mask_all_zeros:
|
||||
print(f"Detailer: segment skip [empty mask]")
|
||||
logging.info("Detailer: segment skip [empty mask]")
|
||||
continue
|
||||
|
||||
if noise_mask:
|
||||
@@ -355,25 +377,25 @@ class DetailerForEach:
|
||||
if cnet_pils is not None:
|
||||
cnet_pil_list.extend(cnet_pils)
|
||||
|
||||
if not (enhanced_image is None):
|
||||
if enhanced_image is not None:
|
||||
# don't latent composite-> converting to latent caused poor quality
|
||||
# use image paste
|
||||
image = image.cpu()
|
||||
enhanced_image = enhanced_image.cpu()
|
||||
tensor_paste(image, enhanced_image, (seg.crop_region[0], seg.crop_region[1]), mask) # this code affecting to `cropped_image`.
|
||||
utils.tensor_paste(image, enhanced_image, (seg.crop_region[0], seg.crop_region[1]), mask) # this code affecting to `cropped_image`.
|
||||
enhanced_list.append(enhanced_image)
|
||||
|
||||
if detailer_hook is not None:
|
||||
image = detailer_hook.post_paste(image)
|
||||
|
||||
if not (enhanced_image is None):
|
||||
if enhanced_image is not None:
|
||||
# Convert enhanced_pil_alpha to RGBA mode
|
||||
enhanced_image_alpha = tensor_convert_rgba(enhanced_image)
|
||||
enhanced_image_alpha = utils.tensor_convert_rgba(enhanced_image)
|
||||
new_seg_image = enhanced_image.numpy() # alpha should not be applied to seg_image
|
||||
|
||||
# Apply the mask
|
||||
mask = tensor_resize(mask, *tensor_get_size(enhanced_image))
|
||||
tensor_putalpha(enhanced_image_alpha, mask)
|
||||
mask = utils.tensor_resize(mask, *utils.tensor_get_size(enhanced_image))
|
||||
utils.tensor_putalpha(enhanced_image_alpha, mask)
|
||||
enhanced_alpha_list.append(enhanced_image_alpha)
|
||||
else:
|
||||
new_seg_image = None
|
||||
@@ -383,7 +405,7 @@ class DetailerForEach:
|
||||
new_seg = SEG(new_seg_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
image_tensor = tensor_convert_rgb(image)
|
||||
image_tensor = utils.tensor_convert_rgb(image)
|
||||
|
||||
cropped_list.sort(key=lambda x: x.shape, reverse=True)
|
||||
enhanced_list.sort(key=lambda x: x.shape, reverse=True)
|
||||
@@ -400,7 +422,7 @@ class DetailerForEach:
|
||||
DetailerForEach.do_detail(image, segs, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps,
|
||||
cfg, sampler_name, scheduler, positive, negative, denoise, feather, noise_mask,
|
||||
force_inpaint, wildcard, detailer_hook,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func_opt=scheduler_func_opt, tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
return (enhanced_img, )
|
||||
@@ -477,7 +499,7 @@ class DetailerForEachPipe:
|
||||
|
||||
# set fallback image
|
||||
if len(cnet_pil_list) == 0:
|
||||
cnet_pil_list = [empty_pil_tensor()]
|
||||
cnet_pil_list = [utils.empty_pil_tensor()]
|
||||
|
||||
return enhanced_img, new_segs, basic_pipe, cnet_pil_list
|
||||
|
||||
@@ -595,13 +617,13 @@ class FaceDetailer:
|
||||
mask = core.segs_to_combined_mask(segs)
|
||||
|
||||
if len(cropped_enhanced) == 0:
|
||||
cropped_enhanced = [empty_pil_tensor()]
|
||||
cropped_enhanced = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cropped_enhanced_alpha) == 0:
|
||||
cropped_enhanced_alpha = [empty_pil_tensor()]
|
||||
cropped_enhanced_alpha = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cnet_pil_list) == 0:
|
||||
cnet_pil_list = [empty_pil_tensor()]
|
||||
cnet_pil_list = [utils.empty_pil_tensor()]
|
||||
|
||||
return enhanced_img, cropped_enhanced, cropped_enhanced_alpha, mask, cnet_pil_list
|
||||
|
||||
@@ -620,7 +642,7 @@ class FaceDetailer:
|
||||
result_cnet_images = []
|
||||
|
||||
if len(image) > 1:
|
||||
print(f"[Impact Pack] WARN: FaceDetailer is not a node designed for video detailing. If you intend to perform video detailing, please use Detailer For AnimateDiff.")
|
||||
logging.warning("[Impact Pack] WARN: FaceDetailer is not a node designed for video detailing. If you intend to perform video detailing, please use Detailer For AnimateDiff.")
|
||||
|
||||
for i, single_image in enumerate(image):
|
||||
enhanced_img, cropped_enhanced, cropped_enhanced_alpha, mask, cnet_pil_list = FaceDetailer.enhance_face(
|
||||
@@ -650,7 +672,7 @@ class LatentPixelScale:
|
||||
return {"required": {
|
||||
"samples": ("LATENT", ),
|
||||
"scale_method": (s.upscale_methods,),
|
||||
"scale_factor": ("FLOAT", {"default": 1.5, "min": 0.1, "max": 10000, "step": 0.1}),
|
||||
"scale_factor": ("FLOAT", {"default": 1.5, "min": 0.1, "max": 10000, "step": 0.05}),
|
||||
"vae": ("VAE", ),
|
||||
"use_tiled_vae": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
},
|
||||
@@ -697,9 +719,7 @@ class NoiseInjectionDetailerHookProvider:
|
||||
from_start=('from_start' in schedule_for_cycle))
|
||||
return (hook, )
|
||||
except Exception as e:
|
||||
print("[ERROR] NoiseInjectionDetailerHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.")
|
||||
print(f"\t{e}")
|
||||
pass
|
||||
logging.error(f"[Impact Pack] NoiseInjectionDetailerHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.\t{e}")
|
||||
|
||||
|
||||
# class CustomNoiseDetailerHookProvider:
|
||||
@@ -770,8 +790,7 @@ class UnsamplerDetailerHookProvider:
|
||||
|
||||
return (hook, )
|
||||
except Exception as e:
|
||||
print("[ERROR] UnsamplerDetailerHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.")
|
||||
print(f"\t{e}")
|
||||
logging.error(f"[Impact Pack] UnsamplerDetailerHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.\t{e}")
|
||||
pass
|
||||
|
||||
|
||||
@@ -811,6 +830,26 @@ class CoreMLDetailerHookProvider:
|
||||
return (hook, )
|
||||
|
||||
|
||||
class CustomSamplerDetailerHookProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"sampler": ("SAMPLER", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DETAILER_HOOK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "Apply a hook that allows you to use a custom sampler in the Detailer nodes. When using `DetailerHookCombine`, the sampler from the first hook is applied."
|
||||
|
||||
def doit(self, sampler):
|
||||
hook = hooks.CustomSamplerDetailerHookProvider(sampler)
|
||||
return (hook, )
|
||||
|
||||
|
||||
class CfgScheduleHookProvider:
|
||||
schedules = ["simple"]
|
||||
|
||||
@@ -869,9 +908,7 @@ class UnsamplerHookProvider:
|
||||
|
||||
return (hook, )
|
||||
except Exception as e:
|
||||
print("[ERROR] UnsamplerHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.")
|
||||
print(f"\t{e}")
|
||||
pass
|
||||
logging.error(f"[Impact Pack] UnsamplerHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.\t{e}")
|
||||
|
||||
|
||||
class NoiseInjectionHookProvider:
|
||||
@@ -901,9 +938,7 @@ class NoiseInjectionHookProvider:
|
||||
|
||||
return (hook, )
|
||||
except Exception as e:
|
||||
print("[ERROR] NoiseInjectionHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.")
|
||||
print(f"\t{e}")
|
||||
pass
|
||||
logging.error(f"[Impact Pack] NoiseInjectionHookProvider: 'ComfyUI Noise' custom node isn't installed. You must install 'BlenderNeko/ComfyUI Noise' extension to use this node.\t{e}")
|
||||
|
||||
|
||||
class DenoiseScheduleHookProvider:
|
||||
@@ -1081,7 +1116,8 @@ class PixelTiledKSampleUpscalerProviderPipe:
|
||||
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength)
|
||||
return (upscaler, )
|
||||
else:
|
||||
print("[ERROR] PixelTiledKSampleUpscalerProviderPipe: ComfyUI_TiledKSampler custom node isn't installed. You must install BlenderNeko/ComfyUI_TiledKSampler extension to use this node.")
|
||||
logging.error("[Impact Pack] PixelTiledKSampleUpscalerProviderPipe: ComfyUI_TiledKSampler custom node isn't installed. You must install BlenderNeko/ComfyUI_TiledKSampler extension to use this node.")
|
||||
raise Exception("[Impact Pack] PixelTiledKSampleUpscalerProviderPipe: ComfyUI_TiledKSampler custom node isn't installed. You must install BlenderNeko/ComfyUI_TiledKSampler extension to use this node.")
|
||||
|
||||
|
||||
class PixelKSampleUpscalerProvider:
|
||||
@@ -1245,7 +1281,7 @@ class TwoSamplersForMaskUpscalerProviderPipe:
|
||||
full_sampler_opt=None, upscale_model_opt=None,
|
||||
pk_hook_base_opt=None, pk_hook_mask_opt=None, pk_hook_full_opt=None, tile_size=512):
|
||||
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
_, _, vae, _, _ = basic_pipe
|
||||
upscaler = core.TwoSamplersForMaskUpscaler(scale_method, full_sample_schedule, use_tiled_vae,
|
||||
@@ -1299,7 +1335,7 @@ class IterativeLatentUpscale:
|
||||
new_w = w*scale
|
||||
new_h = h*scale
|
||||
core.update_node_status(unique_id, f"{i+1}/{steps} steps | x{scale:.2f}", (i+1)/steps)
|
||||
print(f"IterativeLatentUpscale[{i+1}/{steps}]: {new_w:.1f}x{new_h:.1f} (scale:{scale:.2f}) ")
|
||||
logging.info(f"IterativeLatentUpscale[{i+1}/{steps}]: {new_w:.1f}x{new_h:.1f} (scale:{scale:.2f}) ")
|
||||
step_info = i, steps
|
||||
current_latent = upscaler.upscale_shape(step_info, current_latent, new_w, new_h, temp_prefix)
|
||||
if noise_mask is not None:
|
||||
@@ -1309,7 +1345,7 @@ class IterativeLatentUpscale:
|
||||
new_w = w*upscale_factor
|
||||
new_h = h*upscale_factor
|
||||
core.update_node_status(unique_id, f"Final step | x{upscale_factor:.2f}", 1.0)
|
||||
print(f"IterativeLatentUpscale[Final]: {new_w:.1f}x{new_h:.1f} (scale:{upscale_factor:.2f}) ")
|
||||
logging.info(f"IterativeLatentUpscale[Final]: {new_w:.1f}x{new_h:.1f} (scale:{upscale_factor:.2f}) ")
|
||||
step_info = steps-1, steps
|
||||
current_latent = upscaler.upscale_shape(step_info, current_latent, new_w, new_h, temp_prefix)
|
||||
|
||||
@@ -1433,7 +1469,7 @@ class FaceDetailerPipe:
|
||||
result_cnet_images = []
|
||||
|
||||
if len(image) > 1:
|
||||
print(f"[Impact Pack] WARN: FaceDetailer is not a node designed for video detailing. If you intend to perform video detailing, please use Detailer For AnimateDiff.")
|
||||
logging.warning("[Impact Pack] WARN: FaceDetailer is not a node designed for video detailing. If you intend to perform video detailing, please use Detailer For AnimateDiff.")
|
||||
|
||||
model, clip, vae, positive, negative, wildcard, bbox_detector, segm_detector, sam_model_opt, detailer_hook, \
|
||||
refiner_model, refiner_clip, refiner_positive, refiner_negative = detailer_pipe
|
||||
@@ -1457,13 +1493,13 @@ class FaceDetailerPipe:
|
||||
result_cnet_images.extend(cnet_pil_list)
|
||||
|
||||
if len(result_cropped_enhanced) == 0:
|
||||
result_cropped_enhanced = [empty_pil_tensor()]
|
||||
result_cropped_enhanced = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(result_cropped_enhanced_alpha) == 0:
|
||||
result_cropped_enhanced_alpha = [empty_pil_tensor()]
|
||||
result_cropped_enhanced_alpha = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(result_cnet_images) == 0:
|
||||
result_cnet_images = [empty_pil_tensor()]
|
||||
result_cnet_images = [utils.empty_pil_tensor()]
|
||||
|
||||
return result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, detailer_pipe, result_cnet_images
|
||||
|
||||
@@ -1534,7 +1570,7 @@ class MaskDetailerPipe:
|
||||
|
||||
# create segs
|
||||
if mask is not None:
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
segs = core.mask_to_segs(mask, False, crop_factor, bbox_fill, drop_size, is_contour=contour_fill)
|
||||
else:
|
||||
segs = ((image.shape[1], image.shape[2]), [])
|
||||
@@ -1565,10 +1601,10 @@ class MaskDetailerPipe:
|
||||
|
||||
# set fallback image
|
||||
if len(cropped_enhanced_list) == 0:
|
||||
cropped_enhanced_list = [empty_pil_tensor()]
|
||||
cropped_enhanced_list = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cropped_enhanced_alpha_list) == 0:
|
||||
cropped_enhanced_alpha_list = [empty_pil_tensor()]
|
||||
cropped_enhanced_alpha_list = [utils.empty_pil_tensor()]
|
||||
|
||||
return enhanced_img_batch, cropped_enhanced_list, cropped_enhanced_alpha_list, basic_pipe, refiner_basic_pipe_opt
|
||||
|
||||
@@ -1593,21 +1629,21 @@ class DetailerForEachTest(DetailerForEach):
|
||||
DetailerForEach.do_detail(image, segs, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps,
|
||||
cfg, sampler_name, scheduler, positive, negative, denoise, feather, noise_mask,
|
||||
force_inpaint, wildcard, detailer_hook,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func_opt=scheduler_func_opt, tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
# set fallback image
|
||||
if len(cropped) == 0:
|
||||
cropped = [empty_pil_tensor()]
|
||||
cropped = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cropped_enhanced) == 0:
|
||||
cropped_enhanced = [empty_pil_tensor()]
|
||||
cropped_enhanced = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cropped_enhanced_alpha) == 0:
|
||||
cropped_enhanced_alpha = [empty_pil_tensor()]
|
||||
cropped_enhanced_alpha = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cnet_pil_list) == 0:
|
||||
cnet_pil_list = [empty_pil_tensor()]
|
||||
cnet_pil_list = [utils.empty_pil_tensor()]
|
||||
|
||||
return enhanced_img, cropped, cropped_enhanced, cropped_enhanced_alpha, cnet_pil_list
|
||||
|
||||
@@ -1650,16 +1686,16 @@ class DetailerForEachTestPipe(DetailerForEachPipe):
|
||||
|
||||
# set fallback image
|
||||
if len(cropped) == 0:
|
||||
cropped = [empty_pil_tensor()]
|
||||
cropped = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cropped_enhanced) == 0:
|
||||
cropped_enhanced = [empty_pil_tensor()]
|
||||
cropped_enhanced = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cropped_enhanced_alpha) == 0:
|
||||
cropped_enhanced_alpha = [empty_pil_tensor()]
|
||||
cropped_enhanced_alpha = [utils.empty_pil_tensor()]
|
||||
|
||||
if len(cnet_pil_list) == 0:
|
||||
cnet_pil_list = [empty_pil_tensor()]
|
||||
cnet_pil_list = [utils.empty_pil_tensor()]
|
||||
|
||||
return enhanced_img, new_segs, basic_pipe, cropped, cropped_enhanced, cropped_enhanced_alpha, cnet_pil_list
|
||||
|
||||
@@ -1719,7 +1755,7 @@ class BitwiseAndMaskForEach:
|
||||
|
||||
def doit(self, base_segs, mask_segs):
|
||||
mask = core.segs_to_combined_mask(mask_segs)
|
||||
mask = make_3d_mask(mask)
|
||||
mask = utils.make_3d_mask(mask)
|
||||
|
||||
return SegsBitwiseAndMask().doit(base_segs, mask)
|
||||
|
||||
@@ -1742,7 +1778,7 @@ class SubtractMaskForEach:
|
||||
|
||||
def doit(self, base_segs, mask_segs):
|
||||
mask = core.segs_to_combined_mask(mask_segs)
|
||||
mask = make_3d_mask(mask)
|
||||
mask = utils.make_3d_mask(mask)
|
||||
return (core.segs_bitwise_subtract_mask(base_segs, mask), )
|
||||
|
||||
|
||||
@@ -1761,7 +1797,7 @@ class ToBinaryMask:
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
|
||||
def doit(self, mask, threshold):
|
||||
mask = to_binary_mask(mask, threshold/255.0)
|
||||
mask = utils.to_binary_mask(mask, threshold/255.0)
|
||||
return (mask,)
|
||||
|
||||
|
||||
@@ -1799,7 +1835,7 @@ class BitwiseAndMask:
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
|
||||
def doit(self, mask1, mask2):
|
||||
mask = bitwise_and_masks(mask1, mask2)
|
||||
mask = utils.bitwise_and_masks(mask1, mask2)
|
||||
return (mask,)
|
||||
|
||||
|
||||
@@ -1818,7 +1854,7 @@ class SubtractMask:
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
|
||||
def doit(self, mask1, mask2):
|
||||
mask = subtract_masks(mask1, mask2)
|
||||
mask = utils.subtract_masks(mask1, mask2)
|
||||
return (mask,)
|
||||
|
||||
|
||||
@@ -1837,13 +1873,10 @@ class AddMask:
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
|
||||
def doit(self, mask1, mask2):
|
||||
mask = add_masks(mask1, mask2)
|
||||
mask = utils.add_masks(mask1, mask2)
|
||||
return (mask,)
|
||||
|
||||
|
||||
import nodes
|
||||
|
||||
|
||||
def get_image_hash(arr):
|
||||
split_index1 = arr.shape[0] // 2
|
||||
split_index2 = arr.shape[1] // 2
|
||||
@@ -1898,7 +1931,7 @@ class MaskRectArea:
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
FUNCTION = "create_mask"
|
||||
|
||||
@@ -1906,7 +1939,7 @@ class MaskRectArea:
|
||||
# search for node
|
||||
node_found = False
|
||||
for node in extra_pnginfo["workflow"]["nodes"]:
|
||||
if node["id"] == int(unique_id):
|
||||
if str(node["id"]) == unique_id:
|
||||
min_x = node["properties"].get("x", 0) / 100
|
||||
min_y = node["properties"].get("y", 0) / 100
|
||||
width = node["properties"].get("w", 0) / 100
|
||||
@@ -1914,10 +1947,10 @@ class MaskRectArea:
|
||||
blur_radius = node["properties"].get("blur_radius", 0)
|
||||
node_found = True
|
||||
break
|
||||
|
||||
|
||||
if not node_found:
|
||||
raise ValueError(f"No node found with unique_id {unique_id}.")
|
||||
|
||||
|
||||
# Create a mask with standard resolution (e.g., 512x512)
|
||||
resolution = 512
|
||||
mask = torch.zeros((resolution, resolution))
|
||||
@@ -1963,7 +1996,7 @@ class MaskRectAreaAdvanced:
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
FUNCTION = "create_mask_advanced"
|
||||
|
||||
@@ -1981,7 +2014,7 @@ class MaskRectAreaAdvanced:
|
||||
blur_radius = node["properties"]["blur_radius"]
|
||||
node_found = True
|
||||
break
|
||||
|
||||
|
||||
if not node_found:
|
||||
raise ValueError(f"No node found with unique_id {unique_id}.")
|
||||
|
||||
@@ -2047,11 +2080,11 @@ class ImageReceiver:
|
||||
mask = 1. - torch.from_numpy(mask)
|
||||
else:
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
return (image, mask.unsqueeze(0))
|
||||
except Exception as e:
|
||||
print(f"[WARN] ComfyUI-Impact-Pack: ImageReceiver - invalid 'image_data'")
|
||||
return image, mask.unsqueeze(0)
|
||||
except Exception:
|
||||
logging.warning("[WARN] ComfyUI-Impact-Pack: ImageReceiver - invalid 'image_data'")
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
return (empty_pil_tensor(64, 64), mask, )
|
||||
return utils.empty_pil_tensor(64, 64), mask
|
||||
else:
|
||||
return nodes.LoadImage().load_image(image)
|
||||
|
||||
@@ -2282,7 +2315,7 @@ class LatentSender(nodes.SaveLatent):
|
||||
latent_format = latent_formats.LTXV()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
else:
|
||||
print(f"[Impact Pack] LatentSender: '{preview_method}' is unsupported preview method.")
|
||||
logging.warning(f"[Impact Pack] LatentSender: '{preview_method}' is unsupported preview method.")
|
||||
latent_format = latent_formats.SD15()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
|
||||
@@ -2395,7 +2428,7 @@ class ImpactWildcardEncode:
|
||||
"clip": ("CLIP",),
|
||||
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "Enter a prompt using wildcard syntax."}),
|
||||
"populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "The actual value passed during the execution of 'ImpactWildcardEncode' is what is shown here. The behavior varies slightly depending on the mode. Wildcard syntax can also be used in 'populated_text'."}),
|
||||
"mode": (["populate", "fixed", "reproduce"], {"tooltip":
|
||||
"mode": (["populate", "fixed", "reproduce"], {"tooltip":
|
||||
"populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\n"
|
||||
"fixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode\n."
|
||||
"reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."}),
|
||||
@@ -2435,7 +2468,7 @@ class ImpactSchedulerAdapter:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"defaultInput": True, }),
|
||||
"extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan'],),
|
||||
"extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan', 'OSS Chroma'],),
|
||||
}}
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
import nodes
|
||||
from comfy.k_diffusion import sampling as k_diffusion_sampling
|
||||
from comfy import samplers
|
||||
@@ -12,8 +14,8 @@ 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")
|
||||
except Exception:
|
||||
logging.warning("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -176,7 +178,7 @@ def separated_sample(model, add_noise, seed, steps, cfg, sampler_name, scheduler
|
||||
|
||||
if len(sigmas) == 0 or (len(sigmas) == 1 and sigmas[0] == 0):
|
||||
return latent_image
|
||||
|
||||
|
||||
res = sample_with_custom_noise(model, add_noise, seed, cfg, positive, negative, impact_sampler, sigmas, latent_image, noise=noise, callback=callback)
|
||||
|
||||
if return_with_leftover_noise:
|
||||
@@ -194,7 +196,7 @@ def impact_sample(model, seed, steps, cfg, sampler_name, scheduler, positive, ne
|
||||
|
||||
|
||||
def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0, noise=None, scheduler_func=None):
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0, noise=None, scheduler_func=None, sampler_opt=None):
|
||||
|
||||
if refiner_ratio is None or refiner_model is None or refiner_clip is None or refiner_positive is None or refiner_negative is None:
|
||||
# Use separated_sample instead of KSampler for `AYS scheduler`
|
||||
@@ -206,7 +208,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
|
||||
refined_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, False,
|
||||
sigma_ratio=sigma_factor, noise=noise, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, noise=noise, scheduler_func=scheduler_func)
|
||||
else:
|
||||
advanced_steps = math.floor(steps / denoise)
|
||||
start_at_step = advanced_steps - steps
|
||||
@@ -215,7 +217,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
# print(f"pre: {start_at_step} .. {end_at_step} / {advanced_steps}")
|
||||
temp_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, True,
|
||||
sigma_ratio=sigma_factor, noise=noise, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, noise=noise, scheduler_func=scheduler_func)
|
||||
|
||||
if 'noise_mask' in latent_image:
|
||||
# noise_latent = \
|
||||
@@ -229,7 +231,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
# print(f"post: {end_at_step} .. {advanced_steps + 1} / {advanced_steps}")
|
||||
refined_latent = separated_sample(refiner_model, False, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
refiner_positive, refiner_negative, temp_latent, end_at_step, advanced_steps + 1, False,
|
||||
sigma_ratio=sigma_factor, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, scheduler_func=scheduler_func)
|
||||
|
||||
return refined_latent
|
||||
|
||||
@@ -275,7 +277,7 @@ class KSamplerAdvancedWrapper:
|
||||
sampler_opt=self.sampler_opt, noise=noise, scheduler_func=self.scheduler_func)
|
||||
except ValueError as e:
|
||||
if str(e) == 'sigma_min and sigma_max must not be 0':
|
||||
print(f"\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
logging.warning("\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
return latent_image
|
||||
|
||||
if (recovery_sigma_ratio > 0 and recovery_mode != 'DISABLE' and
|
||||
@@ -299,7 +301,7 @@ class KSamplerAdvancedWrapper:
|
||||
sigma_ratio=recovery_sigma_ratio * sigma_factor, sampler_opt=self.sampler_opt, scheduler_func=self.scheduler_func)
|
||||
except ValueError as e:
|
||||
if str(e) == 'sigma_min and sigma_max must not be 0':
|
||||
print(f"\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
logging.warning("\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
|
||||
return latent_image
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import torchvision
|
||||
import impact.core as core
|
||||
import impact.impact_pack as impact_pack
|
||||
from impact.utils import to_tensor
|
||||
import impact.utils as utils
|
||||
from segment_anything import SamPredictor, sam_model_registry
|
||||
import numpy as np
|
||||
import nodes
|
||||
@@ -108,7 +109,8 @@ async def release_sam(request):
|
||||
global sam_predictor
|
||||
|
||||
with sam_lock:
|
||||
del sam_predictor
|
||||
temp = sam_predictor
|
||||
del temp
|
||||
sam_predictor = None
|
||||
|
||||
logging.info("[Impact Pack]: unloading SAM model")
|
||||
@@ -144,7 +146,7 @@ async def sam_detect(request):
|
||||
plabs.append(0)
|
||||
|
||||
detected_masks = core.sam_predict(sam_predictor, points, plabs, None, threshold)
|
||||
mask = core.combine_masks2(detected_masks)
|
||||
mask = utils.combine_masks2(detected_masks)
|
||||
|
||||
if mask is None:
|
||||
return web.Response(status=400)
|
||||
@@ -238,7 +240,7 @@ async def view_validate(request):
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/impact/validate/pb_id_image")
|
||||
async def view_validate(request):
|
||||
async def view_pb_id_image(request):
|
||||
if "id" in request.rel_url.query:
|
||||
pb_id = request.rel_url.query["id"]
|
||||
|
||||
@@ -308,7 +310,7 @@ async def view_previewbridge_image(request):
|
||||
if pb_id in core.preview_bridge_image_id_map:
|
||||
file = core.preview_bridge_image_id_map[pb_id]
|
||||
|
||||
with Image.open(file) as img:
|
||||
with Image.open(file):
|
||||
filename = os.path.basename(file)
|
||||
return web.FileResponse(file, headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
|
||||
@@ -372,7 +374,7 @@ def onprompt_for_switch(json_data):
|
||||
if 'BOOLEAN' == input_node['inputs']['typ']:
|
||||
try:
|
||||
onprompt_cond_branch_info[k] = input_node['inputs']['value'].lower() == "true"
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
onprompt_cond_branch_info[k] = cond_input
|
||||
@@ -506,7 +508,7 @@ def onprompt_populate_wildcards(json_data):
|
||||
else:
|
||||
logging.info(f"[Impact Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
continue
|
||||
except:
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
input_seed = int(inputs['seed'])
|
||||
@@ -516,7 +518,7 @@ def onprompt_populate_wildcards(json_data):
|
||||
|
||||
PromptServer.instance.send_sync("impact-node-feedback", {"node_id": k, "widget_name": "populated_text", "type": "STRING", "value": inputs['populated_text']})
|
||||
updated_widget_values[k] = inputs['populated_text']
|
||||
|
||||
|
||||
if inputs['mode'] == 'reproduce':
|
||||
PromptServer.instance.send_sync("impact-node-feedback", {"node_id": k, "widget_name": "mode", "type": "STRING", "value": 'populate'})
|
||||
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
import folder_paths
|
||||
|
||||
import impact.mmdet_nodes as mmdet_nodes
|
||||
from impact.utils import *
|
||||
from impact.core import SEG
|
||||
import impact.core as core
|
||||
import nodes
|
||||
|
||||
class NO_BBOX_MODEL:
|
||||
pass
|
||||
|
||||
|
||||
class NO_SEGM_MODEL:
|
||||
pass
|
||||
|
||||
|
||||
class MMDetLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
bboxs = ["bbox/"+x for x in folder_paths.get_filename_list("mmdets_bbox")]
|
||||
segms = ["segm/"+x for x in folder_paths.get_filename_list("mmdets_segm")]
|
||||
return {"required": {"model_name": (bboxs + segms, )}}
|
||||
RETURN_TYPES = ("BBOX_MODEL", "SEGM_MODEL")
|
||||
FUNCTION = "load_mmdet"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def load_mmdet(self, model_name):
|
||||
mmdet_path = folder_paths.get_full_path("mmdets", model_name)
|
||||
model = mmdet_nodes.load_mmdet(mmdet_path)
|
||||
|
||||
if model_name.startswith("bbox"):
|
||||
return model, NO_SEGM_MODEL()
|
||||
else:
|
||||
return NO_BBOX_MODEL(), model
|
||||
|
||||
|
||||
class BboxDetectorForEach:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"bbox_model": ("BBOX_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 10, "min": 0, "max": 255, "step": 1}),
|
||||
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
@staticmethod
|
||||
def detect(bbox_model, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
mmdet_results = mmdet_nodes.inference_bbox(bbox_model, image, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
y1, x1, y2, x2 = item_bbox
|
||||
|
||||
if x2 - x1 > drop_size and y2 - y1 > drop_size:
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
# bbox_size = (item_bbox[2]-item_bbox[0],item_bbox[3]-item_bbox[1]) # (w,h)
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
items.append(item)
|
||||
|
||||
shape = h, w
|
||||
return shape, items
|
||||
|
||||
def doit(self, bbox_model, image, threshold, dilation, crop_factor):
|
||||
return (BboxDetectorForEach.detect(bbox_model, image, threshold, dilation, crop_factor), )
|
||||
|
||||
|
||||
class SegmDetectorCombined:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segm_model": ("SEGM_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def doit(self, segm_model, image, threshold, dilation):
|
||||
mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
mask = combine_masks(segmasks)
|
||||
return (mask,)
|
||||
|
||||
|
||||
class BboxDetectorCombined(SegmDetectorCombined):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"bbox_model": ("BBOX_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 4, "min": 0, "max": 255, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
def doit(self, bbox_model, image, threshold, dilation):
|
||||
mmdet_results = mmdet_nodes.inference_bbox(bbox_model, image, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
mask = combine_masks(segmasks)
|
||||
return (mask,)
|
||||
|
||||
|
||||
class SegmDetectorForEach:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segm_model": ("SEGM_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 10, "min": 0, "max": 255, "step": 1}),
|
||||
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def doit(self, segm_model, image, threshold, dilation, crop_factor):
|
||||
mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
items.append(item)
|
||||
|
||||
shape = h,w
|
||||
return ((shape, items), )
|
||||
|
||||
|
||||
class SegsMaskCombine:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
"image": ("IMAGE", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
@staticmethod
|
||||
def combine(segs, image):
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
|
||||
mask = np.zeros((h, w), dtype=np.uint8)
|
||||
|
||||
for seg in segs[1]:
|
||||
cropped_mask = seg.cropped_mask
|
||||
crop_region = seg.crop_region
|
||||
mask[crop_region[1]:crop_region[3], crop_region[0]:crop_region[2]] |= (cropped_mask * 255).astype(np.uint8)
|
||||
|
||||
return torch.from_numpy(mask.astype(np.float32) / 255.0)
|
||||
|
||||
def doit(self, segs, image):
|
||||
return (SegsMaskCombine.combine(segs, image), )
|
||||
|
||||
|
||||
class MaskPainter(nodes.PreviewImage):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"images": ("IMAGE",), },
|
||||
"hidden": {
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
"optional": {"mask_image": ("IMAGE_PATH",), },
|
||||
"optional": {"image": (["#placeholder"], )},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
FUNCTION = "save_painted_images"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def save_painted_images(self, images, filename_prefix="impact-mask",
|
||||
prompt=None, extra_pnginfo=None, mask_image=None, image=None):
|
||||
if image == "#placeholder" or image['image_hash'] != id(images):
|
||||
# new input image
|
||||
res = self.save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
|
||||
item = res['ui']['images'][0]
|
||||
|
||||
if not item['filename'].endswith(']'):
|
||||
filepath = f"{item['filename']} [{item['type']}]"
|
||||
else:
|
||||
filepath = item['filename']
|
||||
|
||||
_, mask = nodes.LoadImage().load_image(filepath)
|
||||
|
||||
res['ui']['aux'] = [id(images), res['ui']['images']]
|
||||
res['result'] = (mask, )
|
||||
|
||||
return res
|
||||
|
||||
else:
|
||||
# new mask
|
||||
if '0' in image: # fallback
|
||||
image = image['0']
|
||||
|
||||
forward = {'filename': image['forward_filename'],
|
||||
'subfolder': image['forward_subfolder'],
|
||||
'type': image['forward_type'], }
|
||||
|
||||
res = {'ui': {'images': [forward]}}
|
||||
|
||||
imgpath = ""
|
||||
if 'subfolder' in image and image['subfolder'] != "":
|
||||
imgpath = image['subfolder'] + "/"
|
||||
|
||||
imgpath += f"{image['filename']}"
|
||||
|
||||
if 'type' in image and image['type'] != "":
|
||||
imgpath += f" [{image['type']}]"
|
||||
|
||||
res['ui']['aux'] = [id(images), [forward]]
|
||||
_, mask = nodes.LoadImage().load_image(imgpath)
|
||||
res['result'] = (mask, )
|
||||
|
||||
return res
|
||||
@@ -8,6 +8,7 @@ from impact.utils import any_typ
|
||||
import impact.core as core
|
||||
import re
|
||||
import nodes
|
||||
import logging
|
||||
|
||||
|
||||
class ImpactCompare:
|
||||
@@ -115,7 +116,6 @@ class ImpactConditionalBranchSelMode:
|
||||
RETURN_TYPES = (any_typ, )
|
||||
|
||||
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,)
|
||||
else:
|
||||
@@ -654,8 +654,8 @@ class ImpactControlBridge:
|
||||
# so extra_pnginfo is useless in here
|
||||
try:
|
||||
workflow = core.current_prompt['extra_data']['extra_pnginfo']['workflow']
|
||||
except:
|
||||
print(f"[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']")
|
||||
except Exception:
|
||||
logging.info("[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']")
|
||||
return 0
|
||||
|
||||
nodes, links = workflow_to_map(workflow)
|
||||
@@ -673,7 +673,7 @@ class ImpactControlBridge:
|
||||
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.")
|
||||
logging.info("[Impact Pack] ImpactControlBridge: ComfyUI is outdated. The 'Stop' behavior cannot function properly.")
|
||||
|
||||
if behavior == "Stop":
|
||||
if mode:
|
||||
@@ -681,7 +681,7 @@ class ImpactControlBridge:
|
||||
else:
|
||||
return (ExecutionBlocker(None), )
|
||||
elif extra_pnginfo is None:
|
||||
logging.warn(f"[Impact Pack] limitation: '{behavior}' behavior cannot be used in API execution.")
|
||||
logging.warning(f"[Impact Pack] limitation: '{behavior}' behavior cannot be used in API execution.")
|
||||
return (value,)
|
||||
else:
|
||||
workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow'])
|
||||
@@ -713,7 +713,7 @@ class ImpactControlBridge:
|
||||
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:
|
||||
elif behavior == "Mute" or behavior == True: # noqa: E712
|
||||
# mute
|
||||
should_be_mute_nodes = active_nodes + bypass_nodes
|
||||
if len(should_be_mute_nodes) > 0:
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
import folder_paths
|
||||
from impact.core import *
|
||||
import os
|
||||
|
||||
import mmcv
|
||||
from mmdet.apis import (inference_detector, init_detector)
|
||||
from mmdet.evaluation import get_classes
|
||||
|
||||
|
||||
def load_mmdet(model_path):
|
||||
model_config = os.path.splitext(model_path)[0] + ".py"
|
||||
model = init_detector(model_config, model_path, device="cpu")
|
||||
return model
|
||||
|
||||
|
||||
def inference_segm_old(model, image, conf_threshold):
|
||||
image = image.numpy()[0] * 255
|
||||
mmdet_results = inference_detector(model, image)
|
||||
|
||||
bbox_results, segm_results = mmdet_results
|
||||
label = "A"
|
||||
|
||||
classes = get_classes("coco")
|
||||
labels = [
|
||||
np.full(bbox.shape[0], i, dtype=np.int32)
|
||||
for i, bbox in enumerate(bbox_results)
|
||||
]
|
||||
n, m = bbox_results[0].shape
|
||||
if n == 0:
|
||||
return [[], [], []]
|
||||
labels = np.concatenate(labels)
|
||||
bboxes = np.vstack(bbox_results)
|
||||
segms = mmcv.concat_list(segm_results)
|
||||
filter_idxs = np.where(bboxes[:, -1] > conf_threshold)[0]
|
||||
results = [[], [], []]
|
||||
for i in filter_idxs:
|
||||
results[0].append(label + "-" + classes[labels[i]])
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def inference_segm(image, modelname, conf_thres, lab="A"):
|
||||
image = image.numpy()[0] * 255
|
||||
mmdet_results = inference_detector(modelname, image).pred_instances
|
||||
bboxes = mmdet_results.bboxes.numpy()
|
||||
segms = mmdet_results.masks.numpy()
|
||||
scores = mmdet_results.scores.numpy()
|
||||
|
||||
classes = get_classes("coco")
|
||||
|
||||
n, m = bboxes.shape
|
||||
if n == 0:
|
||||
return [[], [], [], []]
|
||||
labels = mmdet_results.labels
|
||||
filter_inds = np.where(mmdet_results.scores > conf_thres)[0]
|
||||
results = [[], [], [], []]
|
||||
for i in filter_inds:
|
||||
results[0].append(lab + "-" + classes[labels[i]])
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
results[3].append(scores[i])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def inference_bbox(modelname, image, conf_threshold):
|
||||
image = image.numpy()[0] * 255
|
||||
label = "A"
|
||||
output = inference_detector(modelname, image).pred_instances
|
||||
cv2_image = np.array(image)
|
||||
cv2_image = cv2_image[:, :, ::-1].copy()
|
||||
cv2_gray = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
segms = []
|
||||
for x0, y0, x1, y1 in output.bboxes:
|
||||
cv2_mask = np.zeros(cv2_gray.shape, np.uint8)
|
||||
cv2.rectangle(cv2_mask, (int(x0), int(y0)), (int(x1), int(y1)), 255, -1)
|
||||
cv2_mask_bool = cv2_mask.astype(bool)
|
||||
segms.append(cv2_mask_bool)
|
||||
|
||||
n, m = output.bboxes.shape
|
||||
if n == 0:
|
||||
return [[], [], [], []]
|
||||
|
||||
bboxes = output.bboxes.numpy()
|
||||
scores = output.scores.numpy()
|
||||
filter_idxs = np.where(scores > conf_threshold)[0]
|
||||
results = [[], [], [], []]
|
||||
for i in filter_idxs:
|
||||
results[0].append(label)
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
results[3].append(scores[i])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
class BBoxDetector:
|
||||
bbox_model = None
|
||||
|
||||
def __init__(self, bbox_model):
|
||||
self.bbox_model = bbox_model
|
||||
|
||||
def detect(self, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
drop_size = max(drop_size, 1)
|
||||
mmdet_results = inference_bbox(self.bbox_model, image, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
y1, x1, y2, x2 = item_bbox
|
||||
|
||||
if x2 - x1 > drop_size and y2 - y1 > drop_size: # minimum dimension must be (2,2) to avoid squeeze issue
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
# bbox_size = (item_bbox[2]-item_bbox[0],item_bbox[3]-item_bbox[1]) # (w,h)
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
|
||||
items.append(item)
|
||||
|
||||
shape = image.shape[1], image.shape[2]
|
||||
return shape, items
|
||||
|
||||
def detect_combined(self, image, threshold, dilation):
|
||||
mmdet_results = inference_bbox(self.bbox_model, image, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
return combine_masks(segmasks)
|
||||
|
||||
def setAux(self, x):
|
||||
pass
|
||||
|
||||
|
||||
class SegmDetector(BBoxDetector):
|
||||
segm_model = None
|
||||
|
||||
def __init__(self, segm_model):
|
||||
self.segm_model = segm_model
|
||||
|
||||
def detect(self, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
drop_size = max(drop_size, 1)
|
||||
mmdet_results = inference_segm(image, self.segm_model, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
y1, x1, y2, x2 = item_bbox
|
||||
|
||||
if x2 - x1 > drop_size and y2 - y1 > drop_size: # minimum dimension must be (2,2) to avoid squeeze issue
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
items.append(item)
|
||||
|
||||
segs = image.shape, items
|
||||
|
||||
if detailer_hook is not None and hasattr(detailer_hook, "post_detection"):
|
||||
segs = detailer_hook.post_detection(segs)
|
||||
|
||||
return segs
|
||||
|
||||
def detect_combined(self, image, threshold, dilation):
|
||||
mmdet_results = inference_bbox(self.bbox_model, image, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
return combine_masks(segmasks)
|
||||
|
||||
def setAux(self, x):
|
||||
pass
|
||||
|
||||
|
||||
class MMDetDetectorProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
bboxs = ["bbox/"+x for x in folder_paths.get_filename_list("mmdets_bbox")]
|
||||
segms = ["segm/"+x for x in folder_paths.get_filename_list("mmdets_segm")]
|
||||
return {"required": {"model_name": (bboxs + segms, )}}
|
||||
RETURN_TYPES = ("BBOX_DETECTOR", "SEGM_DETECTOR")
|
||||
FUNCTION = "load_mmdet"
|
||||
|
||||
CATEGORY = "ImpactPack"
|
||||
|
||||
def load_mmdet(self, model_name):
|
||||
mmdet_path = folder_paths.get_full_path("mmdets", model_name)
|
||||
model = load_mmdet(mmdet_path)
|
||||
|
||||
if model_name.startswith("bbox"):
|
||||
return BBoxDetector(model), NO_SEGM_DETECTOR()
|
||||
else:
|
||||
return NO_BBOX_DETECTOR(), model
|
||||
@@ -1,5 +1,4 @@
|
||||
import folder_paths
|
||||
import impact.wildcards
|
||||
from impact.utils import any_typ
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import sys
|
||||
import impact.impact_server
|
||||
from nodes import MAX_RESOLUTION
|
||||
|
||||
from impact.utils import *
|
||||
from . import core
|
||||
from .core import SEG
|
||||
import impact.utils as utils
|
||||
@@ -12,13 +11,20 @@ from . import defs
|
||||
from . import segs_upscaler
|
||||
from comfy.cli_args import args
|
||||
import math
|
||||
from PIL import Image
|
||||
import comfy
|
||||
import numpy as np
|
||||
import torch
|
||||
import folder_paths
|
||||
import logging
|
||||
|
||||
|
||||
from typing import Callable, Union
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
logging.info("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -86,12 +92,12 @@ class SEGSDetailer:
|
||||
seed += 1
|
||||
for seg in segs[1]:
|
||||
cropped_image = seg.cropped_image if seg.cropped_image is not None \
|
||||
else crop_ndarray4(image.numpy(), seg.crop_region)
|
||||
cropped_image = to_tensor(cropped_image)
|
||||
else utils.crop_ndarray4(image.numpy(), seg.crop_region)
|
||||
cropped_image = utils.to_tensor(cropped_image)
|
||||
|
||||
is_mask_all_zeros = (seg.cropped_mask == 0).all().item()
|
||||
if is_mask_all_zeros:
|
||||
print(f"Detailer: segment skip [empty mask]")
|
||||
logging.info("Detailer: segment skip [empty mask]")
|
||||
new_segs.append(seg)
|
||||
continue
|
||||
|
||||
@@ -136,7 +142,7 @@ class SEGSDetailer:
|
||||
else:
|
||||
new_cropped_image = enhanced_image
|
||||
|
||||
new_seg = SEG(to_numpy(new_cropped_image), seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, None)
|
||||
new_seg = SEG(utils.to_numpy(new_cropped_image), seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, None)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
return (segs[0], new_segs), cnet_pil_list
|
||||
@@ -155,7 +161,7 @@ class SEGSDetailer:
|
||||
|
||||
# set fallback image
|
||||
if len(cnet_pil_list) == 0:
|
||||
cnet_pil_list = [empty_pil_tensor()]
|
||||
cnet_pil_list = [utils.empty_pil_tensor()]
|
||||
|
||||
return segs, cnet_pil_list
|
||||
|
||||
@@ -197,12 +203,12 @@ class SEGSPaste:
|
||||
ref_image = cropped_image[i].unsqueeze(0)
|
||||
elif ref_image_opt is not None:
|
||||
ref_tensor = ref_image_opt[i].unsqueeze(0)
|
||||
ref_image = crop_image(ref_tensor, seg.crop_region)
|
||||
ref_image = utils.crop_image(ref_tensor, seg.crop_region)
|
||||
if ref_image is not None:
|
||||
if seg.cropped_mask.ndim == 3 and len(seg.cropped_mask) == len(image):
|
||||
mask = seg.cropped_mask[i]
|
||||
elif seg.cropped_mask.ndim == 3 and len(seg.cropped_mask) > 1:
|
||||
print(f"[Impact Pack] WARN: SEGSPaste - The number of the mask batch({len(seg.cropped_mask)}) and the image batch({len(image)}) are different. Combine the mask frames and apply.")
|
||||
logging.warning(f"[Impact Pack] SEGSPaste: The number of the mask batch({len(seg.cropped_mask)}) and the image batch({len(image)}) are different. Combine the mask frames and apply.")
|
||||
combined_mask = (seg.cropped_mask[0] * 255).to(torch.uint8)
|
||||
|
||||
for frame_mask in seg.cropped_mask[1:]:
|
||||
@@ -213,14 +219,14 @@ class SEGSPaste:
|
||||
else: # ndim == 2
|
||||
mask = seg.cropped_mask
|
||||
|
||||
mask = tensor_gaussian_blur_mask(mask, feather) * (alpha/255)
|
||||
mask = utils.tensor_gaussian_blur_mask(mask, feather) * (alpha/255)
|
||||
x, y, *_ = seg.crop_region
|
||||
|
||||
# ensure same device
|
||||
mask = mask.to(image_i.device)
|
||||
ref_image = ref_image.to(image_i.device)
|
||||
|
||||
tensor_paste(image_i, ref_image, (x, y), mask)
|
||||
utils.tensor_paste(image_i, ref_image, (x, y), mask)
|
||||
|
||||
if result is None:
|
||||
result = image_i
|
||||
@@ -264,7 +270,7 @@ class SEGSPreviewCNet:
|
||||
cnet_image = seg.control_net_wrapper.control_image
|
||||
result_image_list.append(cnet_image)
|
||||
else:
|
||||
cnet_image = empty_pil_tensor(64, 64)
|
||||
cnet_image = utils.empty_pil_tensor(64, 64)
|
||||
|
||||
cnet_pil = utils.tensor2pil(cnet_image)
|
||||
cnet_pil.save(os.path.join(full_output_folder, file))
|
||||
@@ -372,14 +378,14 @@ class SEGSPreview:
|
||||
elif fallback_image_opt is not None:
|
||||
# take from original image
|
||||
ref_image = fallback_image_opt[i].unsqueeze(0)
|
||||
cropped_image = crop_image(ref_image, seg.crop_region)
|
||||
cropped_image = utils.crop_image(ref_image, seg.crop_region)
|
||||
|
||||
if cropped_image is not None:
|
||||
if isinstance(cropped_image, np.ndarray):
|
||||
cropped_image = torch.from_numpy(cropped_image)
|
||||
|
||||
cropped_image = cropped_image.clone()
|
||||
cropped_pil = to_pil(cropped_image)
|
||||
cropped_pil = utils.to_pil(cropped_image)
|
||||
|
||||
if alpha_mode:
|
||||
if isinstance(seg.cropped_mask, np.ndarray):
|
||||
@@ -482,7 +488,7 @@ class SEGSLabelAssign:
|
||||
labels = [label.strip() for label in labels]
|
||||
|
||||
if len(labels) != len(segs[1]):
|
||||
print(f'Warning (SEGSLabelAssign): length of labels ({len(labels)}) != length of segs ({len(segs[1])})')
|
||||
logging.warning(f'[Impact Pack] SEGSLabelAssign: length of labels ({len(labels)}) != length of segs ({len(segs[1])})')
|
||||
|
||||
labeled_segs = []
|
||||
|
||||
@@ -522,7 +528,7 @@ class SEGSOrderedFilter:
|
||||
def get_sort_key_fn(target: str) -> Union[Callable, None]:
|
||||
if target == "none":
|
||||
return None
|
||||
|
||||
|
||||
def sort_key_fn(seg):
|
||||
x1, y1, x2, y2 = seg.crop_region
|
||||
if target == "confidence": return seg.confidence
|
||||
@@ -534,7 +540,7 @@ class SEGSOrderedFilter:
|
||||
if target == "x2": return x2
|
||||
if target == "y2": return y2
|
||||
raise Exception(f"[Impact Pack] SEGSOrderedFilter - Unexpected target '{target}'")
|
||||
|
||||
|
||||
return sort_key_fn
|
||||
|
||||
def doit(self, segs, target, order, take_start, take_count):
|
||||
@@ -583,7 +589,6 @@ class SEGSRangeFilter:
|
||||
h = y2 - y1
|
||||
w = x2 - x1
|
||||
value = max(h/w, w/h)*100
|
||||
print(f"value={value}")
|
||||
elif target == "width":
|
||||
value = x2 - x1
|
||||
elif target == "height":
|
||||
@@ -602,14 +607,14 @@ class SEGSRangeFilter:
|
||||
raise Exception(f"[Impact Pack] SEGSRangeFilter - Unexpected target '{target}'")
|
||||
|
||||
if mode and min_value <= value <= max_value:
|
||||
print(f"[in] value={value} / {mode}, {min_value}, {max_value}")
|
||||
logging.info(f"[in] value={value} / {mode}, {min_value}, {max_value}")
|
||||
new_segs.append(seg)
|
||||
elif not mode and (value < min_value or value > max_value):
|
||||
print(f"[out] value={value} / {mode}, {min_value}, {max_value}")
|
||||
logging.info(f"[out] value={value} / {mode}, {min_value}, {max_value}")
|
||||
new_segs.append(seg)
|
||||
else:
|
||||
remained_segs.append(seg)
|
||||
print(f"[filter] value={value} / {mode}, {min_value}, {max_value}")
|
||||
logging.info(f"[filter] value={value} / {mode}, {min_value}, {max_value}")
|
||||
|
||||
return (segs[0], new_segs), (segs[0], remained_segs),
|
||||
|
||||
@@ -633,7 +638,7 @@ class SEGSIntersectionFilter:
|
||||
def compute_ioa(self, mask1, mask2):
|
||||
"""Compute Intersection over Area (IoA) between two boxes."""
|
||||
inter_mask = utils.bitwise_and_masks(mask1, mask2)
|
||||
|
||||
|
||||
inter_area = (inter_mask > 0).sum()
|
||||
area1 = (mask1 > 0).sum()
|
||||
|
||||
@@ -653,7 +658,7 @@ class SEGSIntersectionFilter:
|
||||
for seg2 in segs2[1]:
|
||||
mask2 = core.segs_to_combined_mask((segs2[0], [seg2]))
|
||||
ioa = self.compute_ioa(mask1, mask2) # IoA between segment 1 and segment 2
|
||||
|
||||
|
||||
if ioa > ioa_threshold: # If IoA exceeds the threshold, mark the segment for removal
|
||||
keep_segment = False
|
||||
break # If one overlap exceeds threshold, break early and mark for removal
|
||||
@@ -685,7 +690,7 @@ class SEGSNMSFilter:
|
||||
"""Compute IoU between two bounding boxes (x1, y1, x2, y2)."""
|
||||
inter_mask = utils.bitwise_and_masks(mask1, mask2)
|
||||
union_mask = utils.add_masks(mask1, mask2)
|
||||
|
||||
|
||||
inter_area = (inter_mask > 0).sum()
|
||||
union_area = (union_mask > 0).sum()
|
||||
|
||||
@@ -744,17 +749,17 @@ class SEGSToImageList:
|
||||
|
||||
for seg in segs[1]:
|
||||
if seg.cropped_image is not None:
|
||||
cropped_image = to_tensor(seg.cropped_image)
|
||||
cropped_image = utils.to_tensor(seg.cropped_image)
|
||||
elif fallback_image_opt is not None:
|
||||
# take from original image
|
||||
cropped_image = to_tensor(crop_image(fallback_image_opt, seg.crop_region))
|
||||
cropped_image = utils.to_tensor(utils.crop_image(fallback_image_opt, seg.crop_region))
|
||||
else:
|
||||
cropped_image = empty_pil_tensor()
|
||||
cropped_image = utils.empty_pil_tensor()
|
||||
|
||||
results.append(cropped_image)
|
||||
|
||||
if len(results) == 0:
|
||||
results.append(empty_pil_tensor())
|
||||
results.append(utils.empty_pil_tensor())
|
||||
|
||||
return (results,)
|
||||
|
||||
@@ -852,7 +857,7 @@ class SEGSMerge:
|
||||
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)
|
||||
@@ -862,7 +867,7 @@ class SEGSMerge:
|
||||
|
||||
seg = SEG(None, cropped_mask, min_confidence, crop_region, bbox, 'merged', None)
|
||||
return ((segs[0], [seg]),)
|
||||
|
||||
|
||||
|
||||
class SEGSConcat:
|
||||
@classmethod
|
||||
@@ -892,7 +897,7 @@ class SEGSConcat:
|
||||
if v[0] == dim:
|
||||
res = res + v[1]
|
||||
else:
|
||||
print(f"ERROR: source shape of 'segs1'{dim} and '{k}'{v[0]} are different. '{k}' will be ignored")
|
||||
logging.error(f"[Impact Pack] source shape of 'segs1'{dim} and '{k}'{v[0]} are different. '{k}' will be ignored")
|
||||
|
||||
if dim is None:
|
||||
empty_segs = ((0, 0), [])
|
||||
@@ -974,8 +979,8 @@ class From_SEG_ELT:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, seg_elt):
|
||||
cropped_image = to_tensor(seg_elt.cropped_image) if seg_elt.cropped_image is not None else None
|
||||
return (seg_elt, cropped_image, to_tensor(seg_elt.cropped_mask), seg_elt.crop_region, seg_elt.bbox, seg_elt.control_net_wrapper, seg_elt.confidence, seg_elt.label,)
|
||||
cropped_image = utils.to_tensor(seg_elt.cropped_image) if seg_elt.cropped_image is not None else None
|
||||
return (seg_elt, cropped_image, utils.to_tensor(seg_elt.cropped_mask), seg_elt.crop_region, seg_elt.bbox, seg_elt.control_net_wrapper, seg_elt.confidence, seg_elt.label,)
|
||||
|
||||
|
||||
class From_SEG_ELT_bbox:
|
||||
@@ -1078,7 +1083,7 @@ class DilateMask:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, mask, dilation):
|
||||
mask = core.dilate_mask(mask.numpy(), dilation)
|
||||
mask = utils.dilate_mask(mask.numpy(), dilation)
|
||||
mask = torch.from_numpy(mask)
|
||||
mask = utils.make_3d_mask(mask)
|
||||
return (mask, )
|
||||
@@ -1101,7 +1106,7 @@ class GaussianBlurMask:
|
||||
|
||||
def doit(self, mask, kernel_size, sigma):
|
||||
# Some custom nodes use abnormal 4-dimensional masks in the format of b, c, h, w. In the impact pack, internal 4-dimensional masks are required in the format of b, h, w, c. Therefore, normalization is performed using the normal mask format, which is 3-dimensional, before proceeding with the operation.
|
||||
mask = make_3d_mask(mask)
|
||||
mask = utils.make_3d_mask(mask)
|
||||
mask = torch.unsqueeze(mask, dim=-1)
|
||||
mask = utils.tensor_gaussian_blur_mask(mask, kernel_size, sigma)
|
||||
mask = torch.squeeze(mask, dim=-1)
|
||||
@@ -1125,7 +1130,7 @@ class DilateMaskInSEGS:
|
||||
def doit(self, segs, dilation):
|
||||
new_segs = []
|
||||
for seg in segs[1]:
|
||||
mask = core.dilate_mask(seg.cropped_mask, dilation)
|
||||
mask = utils.dilate_mask(seg.cropped_mask, dilation)
|
||||
seg = SEG(seg.cropped_image, mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
new_segs.append(seg)
|
||||
|
||||
@@ -1173,7 +1178,7 @@ class Dilate_SEG_ELT:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, seg, dilation):
|
||||
mask = core.dilate_mask(seg.cropped_mask, dilation)
|
||||
mask = utils.dilate_mask(seg.cropped_mask, dilation)
|
||||
seg = SEG(seg.cropped_image, mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
return (seg,)
|
||||
|
||||
@@ -1342,7 +1347,7 @@ class MaskToSEGS:
|
||||
|
||||
@staticmethod
|
||||
def doit(mask, combined, crop_factor, bbox_fill, drop_size, contour_fill=False):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
result = core.mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size, is_contour=contour_fill)
|
||||
|
||||
return (result, )
|
||||
@@ -1369,13 +1374,13 @@ class MaskToSEGS_for_AnimateDiff:
|
||||
@staticmethod
|
||||
def doit(mask, combined, crop_factor, bbox_fill, drop_size, contour_fill=False):
|
||||
if (len(mask.shape) == 4 and mask.shape[1] > 1) or (len(mask.shape) == 3 and mask.shape[0] > 1):
|
||||
mask = make_3d_mask(mask)
|
||||
mask = utils.make_3d_mask(mask)
|
||||
if contour_fill:
|
||||
print(f"[Impact Pack] MaskToSEGS_for_AnimateDiff: 'contour_fill' is ignored because batch mask 'contour_fill' is not supported.")
|
||||
logging.info("[Impact Pack] MaskToSEGS_for_AnimateDiff: 'contour_fill' is ignored because batch mask 'contour_fill' is not supported.")
|
||||
result = core.batch_mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size)
|
||||
return (result, )
|
||||
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
segs = core.mask_to_segs(mask, combined, crop_factor, bbox_fill, drop_size, is_contour=contour_fill)
|
||||
all_masks = SEGSToMaskList().doit(segs)[0]
|
||||
|
||||
@@ -1421,7 +1426,7 @@ class IPAdapterApplySEGS:
|
||||
def doit(segs, ipadapter_pipe, weight, noise, weight_type, start_at, end_at, unfold_batch, faceid_v2, weight_v2, context_crop_factor, reference_image, combine_embeds="concat", neg_image=None):
|
||||
|
||||
if len(ipadapter_pipe) == 4:
|
||||
print(f"[Impact Pack] IPAdapterApplySEGS: Installed Inspire Pack is outdated.")
|
||||
logging.info("[Impact Pack] IPAdapterApplySEGS: Installed Inspire Pack is outdated.")
|
||||
raise Exception("Inspire Pack is outdated.")
|
||||
|
||||
new_segs = []
|
||||
@@ -1429,12 +1434,12 @@ class IPAdapterApplySEGS:
|
||||
h, w = segs[0]
|
||||
|
||||
if reference_image.shape[2] != w or reference_image.shape[1] != h:
|
||||
reference_image = tensor_resize(reference_image, w, h)
|
||||
|
||||
reference_image = utils.tensor_resize(reference_image, w, h)
|
||||
|
||||
for seg in segs[1]:
|
||||
# The context_crop_region sets how much wider the IPAdapter context will reflect compared to the crop_region, not the bbox
|
||||
context_crop_region = make_crop_region(w, h, seg.crop_region, context_crop_factor)
|
||||
cropped_image = crop_image(reference_image, context_crop_region)
|
||||
context_crop_region = utils.make_crop_region(w, h, seg.crop_region, context_crop_factor)
|
||||
cropped_image = utils.crop_image(reference_image, context_crop_region)
|
||||
|
||||
control_net_wrapper = core.IPAdapterWrapper(ipadapter_pipe, weight, noise, weight_type, start_at, end_at, unfold_batch, weight_v2, cropped_image, neg_image=neg_image, prev_control_net=seg.control_net_wrapper, combine_embeds=combine_embeds)
|
||||
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, control_net_wrapper)
|
||||
@@ -1557,7 +1562,7 @@ class SEGSSwitch:
|
||||
if input_name in kwargs:
|
||||
return (kwargs[input_name],)
|
||||
else:
|
||||
print(f"SEGSSwitch: invalid select index ('segs1' is selected)")
|
||||
logging.info("SEGSSwitch: invalid select index ('segs1' is selected)")
|
||||
return (kwargs['segs1'],)
|
||||
|
||||
|
||||
@@ -1594,9 +1599,9 @@ class SEGSPicker:
|
||||
cropped_image = seg.cropped_image
|
||||
elif fallback_image_opt is not None:
|
||||
# take from original image
|
||||
cropped_image = crop_image(fallback_image_opt, seg.crop_region)
|
||||
cropped_image = utils.crop_image(fallback_image_opt, seg.crop_region)
|
||||
else:
|
||||
cropped_image = empty_pil_tensor()
|
||||
cropped_image = utils.empty_pil_tensor()
|
||||
|
||||
mask_array = seg.cropped_mask.copy()
|
||||
mask_array[mask_array < 0.3] = 0.3
|
||||
@@ -1660,7 +1665,7 @@ class DefaultImageForSEGS:
|
||||
for i in range(0, batch_count):
|
||||
# take from original image
|
||||
ref_image = image[i].unsqueeze(0)
|
||||
cropped_image2 = crop_image(ref_image, seg.crop_region)
|
||||
cropped_image2 = utils.crop_image(ref_image, seg.crop_region)
|
||||
|
||||
if cropped_image is None:
|
||||
cropped_image = cropped_image2
|
||||
@@ -1727,7 +1732,7 @@ class MakeTileSEGS:
|
||||
def doit(images, bbox_size, crop_factor, min_overlap, filter_segs_dilation, mask_irregularity=0, irregular_mask_mode="Reuse fast", filter_in_segs_opt=None, filter_out_segs_opt=None):
|
||||
if bbox_size <= 2*min_overlap:
|
||||
new_min_overlap = bbox_size / 2
|
||||
print(f"[MakeTileSEGS] min_overlap should be greater than bbox_size. (value changed: {min_overlap} => {new_min_overlap})")
|
||||
logging.info(f"[MakeTileSEGS] min_overlap should be greater than bbox_size. (value changed: {min_overlap} => {new_min_overlap})")
|
||||
min_overlap = new_min_overlap
|
||||
|
||||
_, ih, iw, _ = images.size()
|
||||
@@ -1757,7 +1762,7 @@ class MakeTileSEGS:
|
||||
exclusion_mask = core.segs_to_combined_mask(filter_out_segs_opt)
|
||||
exclusion_mask = utils.make_3d_mask(exclusion_mask)
|
||||
exclusion_mask = utils.resize_mask(exclusion_mask, (ih, iw))
|
||||
exclusion_mask = dilate_mask(exclusion_mask.cpu().numpy(), filter_segs_dilation)
|
||||
exclusion_mask = utils.dilate_mask(exclusion_mask.cpu().numpy(), filter_segs_dilation)
|
||||
else:
|
||||
exclusion_mask = None
|
||||
|
||||
@@ -1765,7 +1770,7 @@ class MakeTileSEGS:
|
||||
and_mask = core.segs_to_combined_mask(filter_in_segs_opt)
|
||||
and_mask = utils.make_3d_mask(and_mask)
|
||||
and_mask = utils.resize_mask(and_mask, (ih, iw))
|
||||
and_mask = dilate_mask(and_mask.cpu().numpy(), filter_segs_dilation)
|
||||
and_mask = utils.dilate_mask(and_mask.cpu().numpy(), filter_segs_dilation)
|
||||
|
||||
a, b = core.mask_to_segs(and_mask, True, 1.0, False, 0)
|
||||
if len(b) == 0:
|
||||
@@ -1783,7 +1788,7 @@ class MakeTileSEGS:
|
||||
# calculate tile factors
|
||||
if bbox_size > h or bbox_size > w:
|
||||
new_bbox_size = min(bbox_size, min(w, h))
|
||||
print(f"[MaskTileSEGS] bbox_size is greater than resolution (value changed: {bbox_size} => {new_bbox_size}")
|
||||
logging.info(f"[MaskTileSEGS] bbox_size is greater than resolution (value changed: {bbox_size} => {new_bbox_size}")
|
||||
bbox_size = new_bbox_size
|
||||
|
||||
n_horizontal = math.ceil(w / (bbox_size - min_overlap))
|
||||
@@ -1831,7 +1836,7 @@ class MakeTileSEGS:
|
||||
y1 = ih-bbox_size
|
||||
|
||||
bbox = x1, y1, x2, y2
|
||||
crop_region = make_crop_region(iw, ih, bbox, crop_factor)
|
||||
crop_region = utils.make_crop_region(iw, ih, bbox, crop_factor)
|
||||
cx1, cy1, cx2, cy2 = crop_region
|
||||
|
||||
mask = np.zeros((cy2 - cy1, cx2 - cx1)).astype(np.float32)
|
||||
@@ -1940,14 +1945,14 @@ class SEGSUpscaler:
|
||||
ordered_segs = segs[1]
|
||||
|
||||
for i, seg in enumerate(ordered_segs):
|
||||
cropped_image = crop_ndarray4(new_image.numpy(), seg.crop_region)
|
||||
cropped_image = to_tensor(cropped_image)
|
||||
mask = to_tensor(seg.cropped_mask)
|
||||
mask = tensor_gaussian_blur_mask(mask, feather)
|
||||
cropped_image = utils.crop_ndarray4(new_image.numpy(), seg.crop_region)
|
||||
cropped_image = utils.to_tensor(cropped_image)
|
||||
mask = utils.to_tensor(seg.cropped_mask)
|
||||
mask = utils.tensor_gaussian_blur_mask(mask, feather)
|
||||
|
||||
is_mask_all_zeros = (seg.cropped_mask == 0).all().item()
|
||||
if is_mask_all_zeros:
|
||||
print(f"SEGSUpscaler: segment skip [empty mask]")
|
||||
logging.info("SEGSUpscaler: segment skip [empty mask]")
|
||||
continue
|
||||
|
||||
cropped_mask = seg.cropped_mask
|
||||
@@ -1958,17 +1963,17 @@ class SEGSUpscaler:
|
||||
positive, negative, denoise,
|
||||
noise_mask=cropped_mask, control_net_wrapper=seg.control_net_wrapper,
|
||||
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
if not (enhanced_image is None):
|
||||
if enhanced_image is not None:
|
||||
new_image = new_image.cpu()
|
||||
enhanced_image = enhanced_image.cpu()
|
||||
left = seg.crop_region[0]
|
||||
top = seg.crop_region[1]
|
||||
tensor_paste(new_image, enhanced_image, (left, top), mask)
|
||||
utils.tensor_paste(new_image, enhanced_image, (left, top), mask)
|
||||
|
||||
if upscaler_hook_opt is not None:
|
||||
new_image = upscaler_hook_opt.post_paste(new_image)
|
||||
|
||||
enhanced_img = tensor_convert_rgb(new_image)
|
||||
enhanced_img = utils.tensor_convert_rgb(new_image)
|
||||
|
||||
return (enhanced_img,)
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
from impact.utils import *
|
||||
from impact import impact_sampling
|
||||
from comfy import model_management
|
||||
from comfy.cli_args import args
|
||||
from impact import utils
|
||||
from PIL import Image
|
||||
import nodes
|
||||
import torch
|
||||
import inspect
|
||||
import logging
|
||||
import comfy
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"[Impact Pack] ComfyUI is an outdated version. The DifferentialDiffusion feature will be disabled.")
|
||||
logging.info("[Impact Pack] ComfyUI is an outdated version. The DifferentialDiffusion feature will be disabled.")
|
||||
|
||||
|
||||
# Implementation based on `https://github.com/lingondricka2/Upscaler-Detailer`
|
||||
@@ -19,7 +23,6 @@ def upscale_with_model(upscale_model, image):
|
||||
device = model_management.get_torch_device()
|
||||
upscale_model.to(device)
|
||||
in_img = image.movedim(-1, -3).to(device)
|
||||
free_memory = model_management.get_free_memory(device)
|
||||
|
||||
tile = 512
|
||||
overlap = 32
|
||||
@@ -72,9 +75,9 @@ def upscaler(image, upscale_model, rescale_factor, resampling_method, supersampl
|
||||
else:
|
||||
up_image = image
|
||||
|
||||
pil_img = tensor2pil(image)
|
||||
pil_img = utils.tensor2pil(image)
|
||||
original_width, original_height = pil_img.size
|
||||
scaled_image = pil2tensor(apply_resize_image(tensor2pil(up_image), original_width, original_height, rounding_modulus, 'rescale',
|
||||
scaled_image = utils.pil2tensor(apply_resize_image(utils.tensor2pil(up_image), original_width, original_height, rounding_modulus, 'rescale',
|
||||
supersample, rescale_factor, 1024, resampling_method))
|
||||
return scaled_image
|
||||
|
||||
@@ -92,10 +95,10 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
scale = 8/min(original_image_size[0], original_image_size[1]) + 1
|
||||
w = int(original_image_size[1] * scale)
|
||||
h = int(original_image_size[0] * scale)
|
||||
image = tensor_resize(image, w, h)
|
||||
image = utils.tensor_resize(image, w, h)
|
||||
|
||||
if noise_mask is not None:
|
||||
noise_mask = 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)
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
@@ -110,10 +113,10 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
if 'noise_mask' in inspect.signature(imc_encode).parameters:
|
||||
positive, negative, latent_image = imc_encode(positive, negative, image, vae, mask=noise_mask, noise_mask=True)
|
||||
else:
|
||||
print(f"[Impact Pack] ComfyUI is an outdated version.")
|
||||
logging.info("[Impact Pack] ComfyUI is an outdated version.")
|
||||
positive, negative, latent_image = imc_encode(positive, negative, image, vae, noise_mask)
|
||||
else:
|
||||
latent_image = to_latent_image(image, vae)
|
||||
latent_image = utils.to_latent_image(image, vae)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
|
||||
@@ -130,7 +133,7 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
|
||||
# Match to original image size
|
||||
if refined_image.shape[1:3] != original_image_size:
|
||||
refined_image = tensor_resize(refined_image, original_image_size[1], original_image_size[0])
|
||||
refined_image = utils.tensor_resize(refined_image, original_image_size[1], original_image_size[0])
|
||||
|
||||
# don't convert to latent - latent break image
|
||||
# preserving pil is much better
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import math
|
||||
import impact.core as core
|
||||
from comfy_extras.nodes_custom_sampler import Noise_RandomNoise
|
||||
from impact.utils import *
|
||||
from nodes import MAX_RESOLUTION
|
||||
import nodes
|
||||
from impact.impact_sampling import KSamplerWrapper, KSamplerAdvancedWrapper, separated_sample, impact_sample
|
||||
import comfy
|
||||
import torch
|
||||
import numpy as np
|
||||
import logging
|
||||
|
||||
|
||||
class TiledKSamplerProvider:
|
||||
@classmethod
|
||||
@@ -239,7 +242,7 @@ class CombineConditionings:
|
||||
res += v
|
||||
|
||||
return (res, )
|
||||
|
||||
|
||||
|
||||
class ConcatConditionings:
|
||||
@classmethod
|
||||
@@ -263,7 +266,7 @@ class ConcatConditionings:
|
||||
for k, conditioning_from in list(kwargs.items())[1:]:
|
||||
out = []
|
||||
if len(conditioning_from) > 1:
|
||||
print("Warning: ConcatConditionings {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
logging.warning("Warning: ConcatConditionings {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
|
||||
cond_from = conditioning_from[0][0]
|
||||
|
||||
@@ -276,8 +279,8 @@ class ConcatConditionings:
|
||||
conditioning_to = out
|
||||
|
||||
return (out, )
|
||||
|
||||
|
||||
|
||||
|
||||
class RegionalSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -425,7 +428,7 @@ class RegionalSampler:
|
||||
add_noise = False
|
||||
|
||||
# finalize
|
||||
core.update_node_status(unique_id, f"finalize")
|
||||
core.update_node_status(unique_id, "finalize")
|
||||
if base_latent_image is not None:
|
||||
new_latent_image = base_latent_image
|
||||
else:
|
||||
@@ -546,7 +549,7 @@ class RegionalSamplerAdvanced:
|
||||
j += 1
|
||||
|
||||
# finalize
|
||||
core.update_node_status(unique_id, f"finalize")
|
||||
core.update_node_status(unique_id, "finalize")
|
||||
if base_latent_image is not None:
|
||||
new_latent_image = base_latent_image
|
||||
else:
|
||||
|
||||
@@ -9,6 +9,7 @@ import re
|
||||
import impact.core as core
|
||||
from server import PromptServer
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
|
||||
class GeneralSwitch:
|
||||
@@ -50,7 +51,7 @@ class GeneralSwitch:
|
||||
selected_index = int(kwargs['select'])
|
||||
input_name = f"input{selected_index}"
|
||||
|
||||
print(f"SELECTED: {input_name}")
|
||||
logging.info(f"SELECTED: {input_name}")
|
||||
|
||||
if input_name in kwargs:
|
||||
return [input_name]
|
||||
@@ -77,12 +78,12 @@ class GeneralSwitch:
|
||||
|
||||
break
|
||||
else:
|
||||
print(f"[Impact-Pack] The switch node does not guarantee proper functioning in API mode.")
|
||||
logging.info("[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
|
||||
else:
|
||||
print(f"ImpactSwitch: invalid select index (ignored)")
|
||||
logging.info("ImpactSwitch: invalid select index (ignored)")
|
||||
return None, "", selected_index
|
||||
|
||||
class LatentSwitch:
|
||||
@@ -108,7 +109,7 @@ class LatentSwitch:
|
||||
if input_name in kwargs:
|
||||
return (kwargs[input_name],)
|
||||
else:
|
||||
print(f"LatentSwitch: invalid select index ('latent1' is selected)")
|
||||
logging.info("LatentSwitch: invalid select index ('latent1' is selected)")
|
||||
return (kwargs['latent1'],)
|
||||
|
||||
|
||||
@@ -176,7 +177,7 @@ class GeneralInversedSwitch:
|
||||
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.")
|
||||
logging.warning("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
|
||||
|
||||
res = []
|
||||
|
||||
@@ -264,9 +265,9 @@ class ImpactLogger:
|
||||
if hasattr(data, "shape"):
|
||||
shape = f"{data.shape} / "
|
||||
|
||||
print(f"[IMPACT LOGGER]: {shape}{data}")
|
||||
logging.info(f"[IMPACT LOGGER]: {shape}{data}")
|
||||
|
||||
print(f" PROMPT: {prompt}")
|
||||
logging.info(f" PROMPT: {prompt}")
|
||||
|
||||
# for x in prompt:
|
||||
# if 'inputs' in x and 'populated_text' in x['inputs']:
|
||||
@@ -297,7 +298,7 @@ class ImpactDummyInput:
|
||||
class MasksToMaskList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
return {"optional": {
|
||||
"masks": ("MASK", ),
|
||||
}
|
||||
}
|
||||
@@ -318,8 +319,6 @@ class MasksToMaskList:
|
||||
for mask in masks:
|
||||
res.append(mask)
|
||||
|
||||
print(f"mask len: {len(res)}")
|
||||
|
||||
res = [make_3d_mask(x) for x in res]
|
||||
|
||||
return (res, )
|
||||
@@ -474,7 +473,7 @@ class NthItemOfAnyList:
|
||||
class MakeImageList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"image1": ("IMAGE",), }}
|
||||
return {"optional": {"image1": ("IMAGE",), }}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
@@ -494,7 +493,7 @@ class MakeImageList:
|
||||
class MakeImageBatch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"image1": ("IMAGE",), }}
|
||||
return {"optional": {"image1": ("IMAGE",), }}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "doit"
|
||||
@@ -502,14 +501,13 @@ class MakeImageBatch:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
image1 = kwargs['image1']
|
||||
del kwargs['image1']
|
||||
images = [value for value in kwargs.values()]
|
||||
|
||||
if len(images) == 0:
|
||||
return (image1,)
|
||||
if len(images) == 1:
|
||||
return (images[0],)
|
||||
else:
|
||||
for image2 in images:
|
||||
image1 = images[0]
|
||||
for image2 in images[1:]:
|
||||
if image1.shape[1:] != image2.shape[1:]:
|
||||
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "lanczos", "center").movedim(1, -1)
|
||||
image1 = torch.cat((image1, image2), dim=0)
|
||||
@@ -519,7 +517,7 @@ class MakeImageBatch:
|
||||
class MakeMaskBatch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"mask1": ("MASK",), }}
|
||||
return {"optional": {"mask1": ("MASK",), }}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
@@ -527,14 +525,13 @@ class MakeMaskBatch:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
mask1 = kwargs['mask1']
|
||||
del kwargs['mask1']
|
||||
masks = [make_3d_mask(value) for value in kwargs.values()]
|
||||
|
||||
if len(masks) == 0:
|
||||
return (mask1,)
|
||||
if len(masks) == 1:
|
||||
return (masks[0],)
|
||||
else:
|
||||
for mask2 in masks:
|
||||
mask1 = masks[0]
|
||||
for mask2 in masks[1:]:
|
||||
if mask1.shape[1:] != mask2.shape[1:]:
|
||||
mask2 = comfy.utils.common_upscale(mask2.movedim(-1, 1), mask1.shape[2], mask1.shape[1], "lanczos", "center").movedim(1, -1)
|
||||
mask1 = torch.cat((mask1, mask2), dim=0)
|
||||
|
||||
+65
-18
@@ -8,6 +8,7 @@ from . import config
|
||||
from PIL import Image
|
||||
import comfy
|
||||
import time
|
||||
import logging
|
||||
|
||||
|
||||
class TensorBatchBuilder:
|
||||
@@ -67,6 +68,54 @@ def tensor_convert_rgb(image, prefer_copy=True):
|
||||
raise ValueError(f"illegal conversion (channels: {n_channel} -> 3)")
|
||||
|
||||
|
||||
def resize_with_padding(image, target_w: int, target_h: int):
|
||||
_tensor_check_image(image)
|
||||
b, h, w, c = image.shape
|
||||
image = image.permute(0, 3, 1, 2) # B, C, H, W
|
||||
|
||||
scale = min(target_w / w, target_h / h)
|
||||
new_w, new_h = int(w * scale), int(h * scale)
|
||||
|
||||
image = F.interpolate(image, size=(new_h, new_w), mode="bilinear", align_corners=False)
|
||||
|
||||
pad_left = (target_w - new_w) // 2
|
||||
pad_right = target_w - new_w - pad_left
|
||||
pad_top = (target_h - new_h) // 2
|
||||
pad_bottom = target_h - new_h - pad_top
|
||||
|
||||
image = F.pad(image, (pad_left, pad_right, pad_top, pad_bottom), mode='constant', value=0)
|
||||
|
||||
image = image.permute(0, 2, 3, 1) # B, H, W, C
|
||||
return image, (pad_top, pad_bottom, pad_left, pad_right)
|
||||
|
||||
|
||||
def remove_padding(image, padding):
|
||||
pad_top, pad_bottom, pad_left, pad_right = padding
|
||||
return image[:, pad_top:image.shape[1] - pad_bottom, pad_left:image.shape[2] - pad_right, :]
|
||||
|
||||
|
||||
def adjust_bbox_after_resize(bbox, original_size, target_size, padding):
|
||||
"""
|
||||
bbox: (x1, y1, x2, y2) in original image
|
||||
original_size: (original_h, original_w)
|
||||
target_size: (target_h, target_w)
|
||||
padding: (pad_top, pad_bottom, pad_left, pad_right)
|
||||
"""
|
||||
orig_h, orig_w = original_size
|
||||
target_h, target_w = target_size
|
||||
pad_top, pad_bottom, pad_left, pad_right = padding
|
||||
|
||||
scale = min(target_w / orig_w, target_h / orig_h)
|
||||
|
||||
# Apply scale
|
||||
x1 = int(bbox[0] * scale + pad_left)
|
||||
y1 = int(bbox[1] * scale + pad_top)
|
||||
x2 = int(bbox[2] * scale + pad_left)
|
||||
y2 = int(bbox[3] * scale + pad_top)
|
||||
|
||||
return x1, y1, x2, y2
|
||||
|
||||
|
||||
def general_tensor_resize(image, w: int, h: int):
|
||||
_tensor_check_image(image)
|
||||
image = image.permute(0, 3, 1, 2)
|
||||
@@ -142,8 +191,6 @@ def to_numpy(image):
|
||||
if isinstance(image, np.ndarray):
|
||||
return image
|
||||
raise ValueError(f"Cannot convert {type(image)} to numpy.ndarray")
|
||||
|
||||
|
||||
|
||||
def tensor_putalpha(image, mask):
|
||||
_tensor_check_image(image)
|
||||
@@ -185,33 +232,33 @@ def tensor_paste(image1, image2, left_top, mask):
|
||||
_tensor_check_image(image1)
|
||||
_tensor_check_image(image2)
|
||||
_tensor_check_mask(mask)
|
||||
|
||||
|
||||
if image2.shape[1:3] != mask.shape[1:3]:
|
||||
mask = resize_mask(mask.squeeze(dim=3), image2.shape[1:3]).unsqueeze(dim=3)
|
||||
|
||||
|
||||
x, y = left_top
|
||||
_, h1, w1, c1 = image1.shape
|
||||
_, h2, w2, c2 = image2.shape
|
||||
|
||||
|
||||
# Calculate image patch size
|
||||
w = min(w1, x + w2) - x
|
||||
h = min(h1, y + h2) - y
|
||||
|
||||
|
||||
# If the patch is out of bound, nothing to do!
|
||||
if w <= 0 or h <= 0:
|
||||
return
|
||||
|
||||
|
||||
mask = mask[:, :h, :w, :]
|
||||
|
||||
|
||||
# Get the region to be modified
|
||||
region1 = image1[:, y:y+h, x:x+w, :]
|
||||
region2 = image2[:, :h, :w, :]
|
||||
|
||||
|
||||
# Handle RGB and RGBA cases
|
||||
if c1 == 3 and c2 == 3:
|
||||
# Both RGB - simple case
|
||||
image1[:, y:y+h, x:x+w, :] = (1 - mask) * region1 + mask * region2
|
||||
|
||||
|
||||
elif c1 == 4 and c2 == 4:
|
||||
# Both RGBA - need to handle alpha channel separately
|
||||
# RGB channels
|
||||
@@ -219,13 +266,13 @@ def tensor_paste(image1, image2, left_top, mask):
|
||||
(1 - mask) * region1[:, :, :, :3] +
|
||||
mask * region2[:, :, :, :3]
|
||||
)
|
||||
|
||||
|
||||
# Alpha channel - use "over" composition
|
||||
a1 = region1[:, :, :, 3:4]
|
||||
a2 = region2[:, :, :, 3:4] * mask
|
||||
new_alpha = a1 + a2 * (1 - a1)
|
||||
image1[:, y:y+h, x:x+w, 3:4] = new_alpha
|
||||
|
||||
|
||||
elif c1 == 4 and c2 == 3:
|
||||
# Target is RGBA, source is RGB - assume source is fully opaque
|
||||
image1[:, y:y+h, x:x+w, :3] = (
|
||||
@@ -234,7 +281,7 @@ def tensor_paste(image1, image2, left_top, mask):
|
||||
)
|
||||
# Alpha channel - reduce alpha where mask is applied
|
||||
image1[:, y:y+h, x:x+w, 3:4] = region1[:, :, :, 3:4] * (1 - mask) + mask
|
||||
|
||||
|
||||
elif c1 == 3 and c2 == 4:
|
||||
# Target is RGB, source is RGBA - apply source alpha to mask
|
||||
effective_mask = mask * region2[:, :, :, 3:4]
|
||||
@@ -242,7 +289,7 @@ def tensor_paste(image1, image2, left_top, mask):
|
||||
(1 - effective_mask) * region1 +
|
||||
effective_mask * region2[:, :, :, :3]
|
||||
)
|
||||
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -551,10 +598,10 @@ def to_latent_image(pixels, vae, vae_tiled_encode=False):
|
||||
start = time.time()
|
||||
if vae_tiled_encode:
|
||||
encoded = nodes.VAEEncodeTiled().encode(vae, pixels, 512, overlap=64)[0] # using default settings
|
||||
print(f"[Impact Pack] vae encoded (tiled) in {time.time() - start:.1f}s")
|
||||
logging.info(f"[Impact Pack] vae encoded (tiled) in {time.time() - start:.1f}s")
|
||||
else:
|
||||
encoded = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
print(f"[Impact Pack] vae encoded in {time.time() - start:.1f}s")
|
||||
logging.info(f"[Impact Pack] vae encoded in {time.time() - start:.1f}s")
|
||||
|
||||
return encoded
|
||||
|
||||
@@ -639,8 +686,8 @@ def try_install_custom_node(custom_node_url, msg):
|
||||
cm_global.try_call(api='cm.try-install-custom-node',
|
||||
sender="Impact Pack", custom_node_url=custom_node_url, msg=msg)
|
||||
except Exception:
|
||||
print(msg)
|
||||
print(f"[Impact Pack] ComfyUI-Manager is outdated. The custom node installation feature is not available.")
|
||||
logging.info(msg)
|
||||
logging.info("[Impact Pack] ComfyUI-Manager is outdated. The custom node installation feature is not available.")
|
||||
|
||||
|
||||
# author: Trung0246 --->
|
||||
|
||||
+13
-13
@@ -72,7 +72,7 @@ def read_wildcard_dict(wildcard_path):
|
||||
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:
|
||||
except yaml.reader.ReaderError:
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
yaml_data = yaml.load(f, Loader=yaml.FullLoader)
|
||||
|
||||
@@ -139,8 +139,8 @@ def process(text, seed=None):
|
||||
if b is not None:
|
||||
b = b.strip()
|
||||
else:
|
||||
b = "-1"
|
||||
|
||||
b = a
|
||||
|
||||
if r is not None:
|
||||
if b is not None and is_numeric_string(a) and is_numeric_string(b):
|
||||
# PATTERN: num1-num2
|
||||
@@ -212,7 +212,7 @@ def process(text, seed=None):
|
||||
selected_items = random_gen.choice(options, p=normalized_probabilities, size=select_count, replace=False)
|
||||
|
||||
# x may be numpy.int32, convert to string
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', str(x), 1) for x in selected_items]
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', str(x), count=1) for x in selected_items]
|
||||
replacement = select_sep.join(selected_items2)
|
||||
if '::' in replacement:
|
||||
pass
|
||||
@@ -220,7 +220,7 @@ def process(text, seed=None):
|
||||
replacements_found = True
|
||||
return replacement
|
||||
|
||||
pattern = r'{([^{}]*?)}'
|
||||
pattern = r'(?<!\\)\{((?:[^{}]|(?<=\\)[{}])*?)(?<!\\)\}'
|
||||
replaced_string = re.sub(pattern, replace_option, string)
|
||||
|
||||
return replaced_string, replacements_found
|
||||
@@ -279,7 +279,7 @@ def process(text, seed=None):
|
||||
|
||||
normalized_probabilities = [prob / total_prob for prob in adjusted_probabilities]
|
||||
selected_item = random_gen.choice(options, p=normalized_probabilities, replace=False)
|
||||
replacement = re.sub(r'^\s*[0-9.]+::', '', selected_item, 1)
|
||||
replacement = re.sub(r'^\s*[0-9.]+::', '', selected_item, count=1)
|
||||
replacements_found = True
|
||||
string = string.replace(f"__{match}__", replacement, 1)
|
||||
elif '*' in keyword:
|
||||
@@ -305,7 +305,7 @@ def process(text, seed=None):
|
||||
stop_unwrap = False
|
||||
while not stop_unwrap and replace_depth > 1:
|
||||
replace_depth -= 1 # prevent infinite loop
|
||||
|
||||
|
||||
option_quantifier = [e.groupdict() for e in RE_WildCardQuantifier.finditer(text)]
|
||||
for match in option_quantifier:
|
||||
keyword = match['keyword'].lower()
|
||||
@@ -452,7 +452,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
if loader is not None:
|
||||
if loader == 'nunchaku':
|
||||
if 'NunchakuFluxLoraLoader' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
logging.warning(f"To use `LOADER=nunchaku`, 'ComfyUI-nunchaku' is required. The LOADER= attribute is being ignored.")
|
||||
logging.warning("To use `LOADER=nunchaku`, 'ComfyUI-nunchaku' is required. The LOADER= attribute is being ignored.")
|
||||
cls = nodes.NODE_CLASS_MAPPINGS['NunchakuFluxLoraLoader']
|
||||
model = cls().load_lora(model, lora_name, model_weight)[0]
|
||||
else:
|
||||
@@ -467,7 +467,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
'https://github.com/ltdrdata/ComfyUI-Inspire-Pack',
|
||||
"To use 'LBW=' syntax in wildcards, 'Inspire Pack' extension is required.")
|
||||
|
||||
logging.warning(f"'LBW(Lora Block Weight)' is given, but the 'Inspire Pack' is not installed. The LBW= attribute is being ignored.")
|
||||
logging.warning("'LBW(Lora Block Weight)' is given, but the 'Inspire Pack' is not installed. The LBW= attribute is being ignored.")
|
||||
model, clip = default_lora()
|
||||
else:
|
||||
cls = nodes.NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
|
||||
@@ -572,7 +572,7 @@ def split_string_with_sep(input_string):
|
||||
else:
|
||||
try:
|
||||
seed = int(matches[i][5:-1])
|
||||
except:
|
||||
except Exception:
|
||||
seed = None
|
||||
result_list.append(seed)
|
||||
|
||||
@@ -618,7 +618,7 @@ def wildcard_load():
|
||||
|
||||
try:
|
||||
read_wildcard_dict(config.get_config()['custom_wildcards'])
|
||||
except Exception as e:
|
||||
print(f"[Impact Pack] Failed to load custom wildcards directory.")
|
||||
except Exception:
|
||||
logging.info("[Impact Pack] Failed to load custom wildcards directory.")
|
||||
|
||||
print(f"[Impact Pack] Wildcards loading done.")
|
||||
logging.info("[Impact Pack] Wildcards loading done.")
|
||||
|
||||
Vendored
-3
@@ -5,9 +5,6 @@
|
||||
import comfy
|
||||
import torch
|
||||
|
||||
from comfy import sampler_helpers
|
||||
|
||||
|
||||
class Unsampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-impact-pack"
|
||||
description = "This node pack offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
|
||||
version = "8.15"
|
||||
version = "8.21.2"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
|
||||
|
||||
|
||||
+3
-2
@@ -4,6 +4,7 @@ piexif
|
||||
transformers
|
||||
opencv-python-headless
|
||||
scipy>=1.11.4
|
||||
numpy<2
|
||||
numpy
|
||||
dill
|
||||
matplotlib
|
||||
matplotlib
|
||||
git+https://github.com/facebookresearch/sam2
|
||||
@@ -0,0 +1,3 @@
|
||||
[lint]
|
||||
ignore = ["E402","E701"]
|
||||
exclude = ["install.py", "*.ipynb"]
|
||||
@@ -1,38 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
comfy_path = '../..'
|
||||
|
||||
def rmtree(path):
|
||||
retry_count = 3
|
||||
|
||||
while True:
|
||||
try:
|
||||
retry_count -= 1
|
||||
|
||||
if platform.system() == "Windows":
|
||||
subprocess.check_call(['attrib', '-R', path + '\\*', '/S'])
|
||||
|
||||
shutil.rmtree(path)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as ex:
|
||||
print(f"ex: {ex}")
|
||||
time.sleep(3)
|
||||
|
||||
if retry_count < 0:
|
||||
raise ex
|
||||
|
||||
print(f"Uninstall retry({retry_count})")
|
||||
|
||||
js_dest_path = os.path.join(comfy_path, "web", "extensions", "impact-pack")
|
||||
|
||||
if os.path.exists(js_dest_path):
|
||||
rmtree(js_dest_path)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user