Compare commits

..
9 Commits
33 changed files with 984 additions and 1264 deletions
+28 -6
View File
@@ -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.
@@ -190,6 +203,10 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
* 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.
@@ -197,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.
@@ -211,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.
@@ -223,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.
@@ -240,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`.
@@ -271,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`
@@ -292,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.
@@ -368,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
```
@@ -397,7 +418,6 @@ sam_editor_model = sam_vit_b_01ec64.pth
![simple](https://github.com/ltdrdata/ComfyUI-extension-tutorials/raw/Main/ComfyUI-Impact-Pack/images/simple.png)
* 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.
![simple-orig](https://github.com/ltdrdata/ComfyUI-extension-tutorials/raw/Main/ComfyUI-Impact-Pack/images/simple-original.png) ![simple-refined](https://github.com/ltdrdata/ComfyUI-extension-tutorials/raw/Main/ComfyUI-Impact-Pack/images/simple-refined.png)
@@ -489,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 -262
View File
@@ -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,232 +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,
"CustomSamplerDetailerHookProvider": CustomSamplerDetailerHookProvider,
"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
}
@@ -303,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",
@@ -315,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)",
@@ -330,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)",
@@ -445,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
@@ -478,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
View File
@@ -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)
-11
View File
@@ -84,17 +84,6 @@ 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:
+1 -1
View File
@@ -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": "와일드카드 텍스트",
+1 -1
View File
@@ -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'])
+9 -6
View File
@@ -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)
+24 -20
View File
@@ -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.
@@ -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',
@@ -100,10 +104,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 +127,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 +194,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 +203,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:
@@ -266,7 +270,7 @@ class PreviewBridgeLatent:
else:
mask = None
else:
image = empty_pil_tensor()
image = utils.empty_pil_tensor()
mask = None
ui_item = {
"filename": 'empty.png',
@@ -287,7 +291,7 @@ 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
@@ -326,11 +330,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"
@@ -354,10 +358,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 +380,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
+4 -10
View File
@@ -1,11 +1,11 @@
import configparser
import os
import logging
version_code = [8, 16]
version_code = [8, 19]
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")),
+267 -126
View File
@@ -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.")
@@ -53,7 +62,7 @@ SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS S
def is_execution_model_version_supported():
try:
import comfy_execution
import comfy_execution # noqa: F401
return True
except:
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,71 +339,75 @@ 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
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:
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, 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
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)
refined_image = utils.tensor_resize(refined_image, w, h)
# prevent mixing of device
refined_image = refined_image.cpu()
@@ -450,7 +466,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
@@ -458,7 +474,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):
@@ -486,10 +502,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
@@ -501,7 +517,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:]:
@@ -614,6 +630,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
@@ -639,10 +771,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:
@@ -660,7 +797,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
@@ -675,7 +812,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)
@@ -721,7 +858,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]
@@ -742,14 +879,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]
@@ -800,7 +937,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]
@@ -890,7 +1027,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)
@@ -930,7 +1067,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
@@ -945,7 +1082,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])
@@ -962,7 +1099,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()
@@ -971,7 +1108,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:
@@ -988,10 +1125,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 = []
@@ -1014,10 +1151,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 = []
@@ -1041,7 +1178,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 = []
@@ -1070,7 +1207,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)
@@ -1086,7 +1223,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]
@@ -1102,7 +1239,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)
@@ -1112,7 +1249,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)
@@ -1126,8 +1263,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))
@@ -1154,7 +1290,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):
@@ -1163,11 +1299,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 = []
@@ -1187,7 +1323,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
@@ -1221,7 +1357,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
)
@@ -1255,9 +1391,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}")
@@ -1295,7 +1431,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
@@ -1389,7 +1525,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]
@@ -1406,7 +1542,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]
@@ -1475,7 +1611,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
@@ -1511,7 +1647,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
@@ -1530,7 +1666,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]))
@@ -1548,7 +1684,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)
@@ -1578,7 +1714,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)
@@ -1634,17 +1770,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]
@@ -1792,11 +1928,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']
@@ -1930,7 +2066,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)
@@ -2078,7 +2214,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)
@@ -2108,7 +2244,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):
@@ -2194,7 +2330,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:
@@ -2211,9 +2347,14 @@ class SafeToGPU:
try:
obj.to(device)
except:
print(f"WARN: The model is not moved to the '{device}' due to insufficient memory. [1]")
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
@@ -2247,7 +2388,7 @@ 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:
@@ -2255,6 +2396,6 @@ try:
return previewer
except:
print(f"#########################################################################")
print(f"[ERROR] ComfyUI-Impact-Pack: Please update ComfyUI to the latest version.")
print(f"#########################################################################")
logging.error("#########################################################################")
logging.error("[ERROR] ComfyUI-Impact-Pack: Please update ComfyUI to the latest version.")
logging.error("#########################################################################")
+1 -1
View File
@@ -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"
]
]
+66 -2
View File
@@ -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
+4 -3
View File
@@ -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))
+21
View File
@@ -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, )
+34 -6
View File
@@ -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,12 +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():
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
@@ -182,6 +186,9 @@ class DetailerHook(PixelKSampleHook):
def get_custom_sampler(self):
return None
def get_skip_sampling(self):
return False
class CustomSamplerDetailerHookProvider(DetailerHook):
def __init__(self, sampler):
@@ -333,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
@@ -364,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
@@ -504,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}")
+105 -92
View File
@@ -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:
@@ -360,7 +382,7 @@ class DetailerForEach:
# 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:
@@ -368,12 +390,12 @@ class DetailerForEach:
if not (enhanced_image is 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(
@@ -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
@@ -823,7 +842,7 @@ class CustomSamplerDetailerHookProvider:
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):
@@ -889,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:
@@ -921,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:
@@ -1101,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:
@@ -1265,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,
@@ -1319,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:
@@ -1329,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)
@@ -1453,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
@@ -1477,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
@@ -1554,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]), [])
@@ -1585,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
@@ -1613,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
@@ -1670,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
@@ -1739,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)
@@ -1762,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), )
@@ -1781,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,)
@@ -1819,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,)
@@ -1838,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,)
@@ -1857,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
@@ -1918,7 +1931,7 @@ class MaskRectArea:
}
RETURN_TYPES = ("MASK",)
CATEGORY = "ImpactPack/Operation"
FUNCTION = "create_mask"
@@ -1934,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))
@@ -1983,7 +1996,7 @@ class MaskRectAreaAdvanced:
}
RETURN_TYPES = ("MASK",)
CATEGORY = "ImpactPack/Operation"
FUNCTION = "create_mask_advanced"
@@ -2001,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}.")
@@ -2067,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)
@@ -2302,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
@@ -2415,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."}),
+6 -4
View File
@@ -1,3 +1,5 @@
import logging
import nodes
from comfy.k_diffusion import sampling as k_diffusion_sampling
from comfy import samplers
@@ -13,7 +15,7 @@ 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")
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:
@@ -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
+5 -4
View File
@@ -108,7 +108,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")
@@ -238,7 +239,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 +309,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}\""})
@@ -516,7 +517,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'})
-285
View File
@@ -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
+4 -4
View File
@@ -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:
@@ -655,7 +655,7 @@ class ImpactControlBridge:
try:
workflow = core.current_prompt['extra_data']['extra_pnginfo']['workflow']
except:
print(f"[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']")
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'])
-219
View File
@@ -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
View File
@@ -1,5 +1,4 @@
import folder_paths
import impact.wildcards
from impact.utils import any_typ
+65 -60
View File
@@ -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)
@@ -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
@@ -1963,12 +1968,12 @@ class SEGSUpscaler:
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,)
+14 -11
View File
@@ -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
+10 -7
View File
@@ -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:
+8 -9
View File
@@ -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']:
@@ -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, )
+65 -18
View File
@@ -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 --->
+9 -9
View File
@@ -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)
@@ -140,7 +140,7 @@ def process(text, seed=None):
b = b.strip()
else:
b = "-1"
if r is not None:
if b is not None and is_numeric_string(a) and is_numeric_string(b):
# PATTERN: num1-num2
@@ -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
@@ -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']
@@ -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.")
-3
View File
@@ -5,9 +5,6 @@
import comfy
import torch
from comfy import sampler_helpers
class Unsampler:
@classmethod
def INPUT_TYPES(s):
+1 -1
View File
@@ -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.16"
version = "8.19"
license = { file = "LICENSE.txt" }
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
+2 -1
View File
@@ -6,4 +6,5 @@ opencv-python-headless
scipy>=1.11.4
numpy
dill
matplotlib
matplotlib
git+https://github.com/facebookresearch/sam2
-38
View File
@@ -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)