Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17e0a05769 | ||
|
|
b3a815b43d | ||
|
|
8ab2e168f7 | ||
|
|
b980035588 | ||
|
|
df330c1b06 | ||
|
|
6d438d8b5d | ||
|
|
7d565d1f7a | ||
|
|
27c5368bcc | ||
|
|
705698faf2 | ||
|
|
cf43fc4e3c | ||
|
|
4c292e684e | ||
|
|
65f1363a10 | ||
|
|
f57d309932 | ||
|
|
6ccf9bab68 | ||
|
|
ac3668d946 | ||
|
|
78d3793a77 | ||
|
|
2346b67766 | ||
|
|
025db4b581 | ||
|
|
f8e16df2be | ||
|
|
7df60b2107 | ||
|
|
b394c158ea | ||
|
|
3e3cf3a5b4 | ||
|
|
93fc248503 | ||
|
|
16ffa7d462 | ||
|
|
cd34cfdd63 | ||
|
|
38bb9ffdf6 | ||
|
|
f939e66e1c | ||
|
|
b22aa90cf4 | ||
|
|
d18aaecb93 | ||
|
|
a7840b4fbf | ||
|
|
efa0cb66b2 | ||
|
|
0b94005b1b | ||
|
|
bfcb8674b3 | ||
|
|
839e5a9f90 | ||
|
|
5a2adda580 | ||
|
|
d05882a9e0 | ||
|
|
d900939861 | ||
|
|
cd2696f6fd | ||
|
|
0b1ac0f1c5 | ||
|
|
216d7fd60c | ||
|
|
28cd2f70b2 | ||
|
|
2708eba825 | ||
|
|
c19ab92172 | ||
|
|
6e3d07277b | ||
|
|
782c6f439e | ||
|
|
0e3e6a193a | ||
|
|
798776838e | ||
|
|
66493d8cb4 | ||
|
|
b4fd0834e0 | ||
|
|
808b0dedf0 | ||
|
|
c6056b132d | ||
|
|
1ae7cae2df | ||
|
|
ccb6285548 | ||
|
|
092310bc8f | ||
|
|
5c530eb32e | ||
|
|
e35bc23fd1 | ||
|
|
869ac6fd1f | ||
|
|
cb168d64ab | ||
|
|
a89e9e01a6 | ||
|
|
e70b4df9b5 | ||
|
|
af1ef7e441 | ||
|
|
d8738eee2f | ||
|
|
c397c68ca3 | ||
|
|
0e0722ec08 | ||
|
|
70d0540895 | ||
|
|
7330577a0f | ||
|
|
b1d760291f | ||
|
|
12e838a320 | ||
|
|
8e8621df49 | ||
|
|
cdb7b4d3b0 | ||
|
|
21eecb0c03 | ||
|
|
9402ecf4f9 | ||
|
|
c21b361e2a | ||
|
|
8f04714145 |
@@ -7,15 +7,19 @@ on:
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'ltdrdata' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
|
||||
@@ -7,3 +7,5 @@ subpack
|
||||
impact_subpack
|
||||
*.txt
|
||||
*.yaml
|
||||
!requirements.txt
|
||||
!LICENSE.txt
|
||||
@@ -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.
|
||||
@@ -32,9 +34,35 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* With the addition of wildcard support in FaceDetailer, the structure of DETAILER_PIPE-related nodes and Detailer nodes has changed. There may be malfunctions when using the existing workflow.
|
||||
|
||||
|
||||
## How To Install
|
||||
|
||||
### **Recommended**
|
||||
* Install via [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager).
|
||||
|
||||
### **Manual**
|
||||
* Navigate to `ComfyUI/custom_nodes` in your terminal (cmd).
|
||||
* Clone the repository under the `custom_nodes` directory using the following command:
|
||||
```
|
||||
git clone https://github.com/ltdrdata/ComfyUI-Impact-Pack comfyui-impact-pack
|
||||
cd comfyui-impact-pack
|
||||
```
|
||||
* Install dependencies in your Python environment.
|
||||
* For Windows Portable, run the following command inside `ComfyUI\custom_nodes\comfyui-impact-pack`:
|
||||
```
|
||||
..\..\..\python_embeded\python.exe -m pip install -r requirements.txt
|
||||
```
|
||||
* If using venv or conda, activate your Python environment first, then run:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 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.
|
||||
@@ -45,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.
|
||||
@@ -54,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.
|
||||
@@ -71,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.
|
||||
@@ -93,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.
|
||||
@@ -107,6 +142,8 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `SEGS Filter (label)` - This node filters SEGS based on the label of the detected areas.
|
||||
* `SEGS Filter (ordered)` - This node sorts SEGS based on size and position and retrieves SEGs within a certain range.
|
||||
* `SEGS Filter (range)` - This node retrieves only SEGs from SEGS that have a size and position within a certain range.
|
||||
* `SEGS Filter (non max suppression)` - This node filters SEGS by removing those with high overlap based on the Intersection over Union (IoU) threshold, keeping only the most confident detections.
|
||||
* `SEGS Filter (intersection)` - This node filters segs1, keeping only the SEGS that do not significantly overlap with any SEGS in segs2, based on the Intersection over Area (IoA) threshold.
|
||||
* `SEGS Assign (label)` - Assign labels sequentially to SEGS. This node is useful when used with `[LAB]` of FaceDetailer.
|
||||
* `SEGSConcat` - Concatenate segs1 and segs2. If source shape of segs1 and segs2 are different from segs2 will be ignored.
|
||||
* `SEGS Merge` - SEGS contains multiple SEGs. SEGS Merge integrates several SEGs into a single merged SEG. The label is changed to `merged` and the confidence becomes the minimum confidence. The applied controlnet and cropped_image are removed.
|
||||
@@ -127,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.
|
||||
@@ -139,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.
|
||||
@@ -152,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.
|
||||
@@ -162,6 +202,11 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `PreviewDetailerHook` - Connecting this hook node helps provide assistance for viewing previews whenever SEGS Detailing tasks are completed. When working with a large number of SEGS, such as Make Tile SEGS, it allows for monitoring the situation as improvements progress incrementally.
|
||||
* Since this is the hook applied when pasting onto the original image, it has no effect on nodes like `SEGSDetailer`.
|
||||
* `VariationNoiseDetailerHookProvider` - Apply variation seed to the detailer. It can be applied in multiple stages through combine.
|
||||
* `CustomSamplerDetailerHookProvider` - Apply a hook that allows you to use a custom sampler in the Detailer nodes. When using `DetailerHookCombine`, the sampler from the first hook is applied.
|
||||
* `LamaRemoverDetailerHookProvider` – Applies Lama Remover to the upscaled image during the detailing stage. If `skip_sampling` is set to True, Lama Remover can be used alone without the detailing stage, allowing it to simply remove detected regions.
|
||||
* Not applicable for **AnimateDiff** detailers. When using `DetailerHookCombine`, `skip_sampling` is only applied if it is set to `True` for all hooks.
|
||||
* To use this node, the node pack at [Layer-norm/comfyui-lama-remover](https://github.com/Layer-norm/comfyui-lama-remover) must be installed.
|
||||
|
||||
|
||||
### Iterative Upscale nodes
|
||||
* `Iterative Upscale (Latent/on Pixel Space)` - The upscaler takes the input upscaler and splits the scale_factor into steps, then iteratively performs upscaling.
|
||||
@@ -169,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.
|
||||
@@ -183,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.
|
||||
@@ -195,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.
|
||||
@@ -212,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`.
|
||||
@@ -224,7 +274,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
|
||||
|
||||
### Impact KSampler
|
||||
* These samplers support basic_pipe and AYS scheduler
|
||||
* These samplers support basic_pipe and AYS/OSS/GITS scheduler
|
||||
* `KSampler (pipe)` - pipe version of KSampler
|
||||
* `KSampler (advanced/pipe)` - pipe version of KSamplerAdvacned
|
||||
* When converting the scheduler widget to input, refer to the `Impact Scheduler Adapter` node to resolve compatibility issues.
|
||||
@@ -241,6 +291,8 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `Masks to Mask List`, `Mask List to Masks`, `Make Mask List`, `Make Mask Batch` - It has the same functionality as the nodes above, but uses mask as input instead of image.
|
||||
* `Flatten Mask Batch` - Flattens a Mask Batch into a single Mask. Normal operation is not guaranteed for non-binary masks.
|
||||
* `Make List (Any)` - Create a list with arbitrary values.
|
||||
* `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.
|
||||
@@ -263,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.
|
||||
@@ -339,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
|
||||
```
|
||||
@@ -368,7 +418,6 @@ sam_editor_model = sam_vit_b_01ec64.pth
|
||||

|
||||
* The face that has been damaged due to low resolution is restored with high resolution by generating and synthesizing it, in order to restore the details.
|
||||
* The FaceDetailer node is a combination of a Detector node for face detection and a Detailer node for image enhancement. See the [Advanced Tutorial](https://github.com/ltdrdata/ComfyUI-extension-tutorials/raw/Main/ComfyUI-Impact-Pack/tutorial/advanced.md) for a more detailed explanation.
|
||||
* Pass the MMDetLoader 's bbox model and the detection model loaded by SAMLoader to FaceDetailer . Since it performs the function of KSampler for image enhancement, it overlaps with KSampler's options.
|
||||
* The MASK output of FaceDetailer provides a visualization of where the detected and enhanced areas are.
|
||||
|
||||
 
|
||||
@@ -460,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`.
|
||||
|
||||
@@ -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,29 +17,23 @@ modules_path = os.path.join(os.path.dirname(__file__), "modules")
|
||||
sys.path.append(modules_path)
|
||||
|
||||
import impact.config
|
||||
import impact.sample_error_enhancer
|
||||
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!!!!")
|
||||
@@ -49,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
|
||||
|
||||
@@ -69,228 +62,234 @@ threading.Thread(target=impact.wildcards.wildcard_load).start()
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SAMLoader": SAMLoader,
|
||||
"CLIPSegDetectorProvider": CLIPSegDetectorProvider,
|
||||
"ONNXDetectorProvider": ONNXDetectorProvider,
|
||||
"SAMLoader": SAMLoader, # noqa: F405
|
||||
"CLIPSegDetectorProvider": CLIPSegDetectorProvider, # noqa: F405
|
||||
"ONNXDetectorProvider": ONNXDetectorProvider, # noqa: F405
|
||||
|
||||
"BitwiseAndMaskForEach": BitwiseAndMaskForEach,
|
||||
"SubtractMaskForEach": SubtractMaskForEach,
|
||||
"BitwiseAndMaskForEach": BitwiseAndMaskForEach, # noqa: F405
|
||||
"SubtractMaskForEach": SubtractMaskForEach, # noqa: F405
|
||||
|
||||
"DetailerForEach": DetailerForEach,
|
||||
"DetailerForEachDebug": DetailerForEachTest,
|
||||
"DetailerForEachPipe": DetailerForEachPipe,
|
||||
"DetailerForEachDebugPipe": DetailerForEachTestPipe,
|
||||
"DetailerForEachPipeForAnimateDiff": DetailerForEachPipeForAnimateDiff,
|
||||
"DetailerForEach": DetailerForEach, # noqa: F405
|
||||
"DetailerForEachDebug": DetailerForEachTest, # noqa: F405
|
||||
"DetailerForEachPipe": DetailerForEachPipe, # noqa: F405
|
||||
"DetailerForEachDebugPipe": DetailerForEachTestPipe, # noqa: F405
|
||||
"DetailerForEachPipeForAnimateDiff": DetailerForEachPipeForAnimateDiff, # noqa: F405
|
||||
|
||||
"SAMDetectorCombined": SAMDetectorCombined,
|
||||
"SAMDetectorSegmented": SAMDetectorSegmented,
|
||||
"SAMDetectorCombined": SAMDetectorCombined, # noqa: F405
|
||||
"SAMDetectorSegmented": SAMDetectorSegmented, # noqa: F405
|
||||
|
||||
"FaceDetailer": FaceDetailer,
|
||||
"FaceDetailerPipe": FaceDetailerPipe,
|
||||
"MaskDetailerPipe": MaskDetailerPipe,
|
||||
"FaceDetailer": FaceDetailer, # noqa: F405
|
||||
"FaceDetailerPipe": FaceDetailerPipe, # noqa: F405
|
||||
"MaskDetailerPipe": MaskDetailerPipe, # noqa: F405
|
||||
|
||||
"ToDetailerPipe": ToDetailerPipe,
|
||||
"ToDetailerPipeSDXL": ToDetailerPipeSDXL,
|
||||
"FromDetailerPipe": FromDetailerPipe,
|
||||
"FromDetailerPipe_v2": FromDetailerPipe_v2,
|
||||
"FromDetailerPipeSDXL": FromDetailerPipe_SDXL,
|
||||
"AnyPipeToBasic": AnyPipeToBasic,
|
||||
"ToBasicPipe": ToBasicPipe,
|
||||
"FromBasicPipe": FromBasicPipe,
|
||||
"FromBasicPipe_v2": FromBasicPipe_v2,
|
||||
"BasicPipeToDetailerPipe": BasicPipeToDetailerPipe,
|
||||
"BasicPipeToDetailerPipeSDXL": BasicPipeToDetailerPipeSDXL,
|
||||
"DetailerPipeToBasicPipe": DetailerPipeToBasicPipe,
|
||||
"EditBasicPipe": EditBasicPipe,
|
||||
"EditDetailerPipe": EditDetailerPipe,
|
||||
"EditDetailerPipeSDXL": EditDetailerPipeSDXL,
|
||||
"ToDetailerPipe": ToDetailerPipe, # noqa: F405
|
||||
"ToDetailerPipeSDXL": ToDetailerPipeSDXL, # noqa: F405
|
||||
"FromDetailerPipe": FromDetailerPipe, # noqa: F405
|
||||
"FromDetailerPipe_v2": FromDetailerPipe_v2, # noqa: F405
|
||||
"FromDetailerPipeSDXL": FromDetailerPipe_SDXL, # noqa: F405
|
||||
"AnyPipeToBasic": AnyPipeToBasic, # noqa: F405
|
||||
"ToBasicPipe": ToBasicPipe, # noqa: F405
|
||||
"FromBasicPipe": FromBasicPipe, # noqa: F405
|
||||
"FromBasicPipe_v2": FromBasicPipe_v2, # noqa: F405
|
||||
"BasicPipeToDetailerPipe": BasicPipeToDetailerPipe, # noqa: F405
|
||||
"BasicPipeToDetailerPipeSDXL": BasicPipeToDetailerPipeSDXL, # noqa: F405
|
||||
"DetailerPipeToBasicPipe": DetailerPipeToBasicPipe, # noqa: F405
|
||||
"EditBasicPipe": EditBasicPipe, # noqa: F405
|
||||
"EditDetailerPipe": EditDetailerPipe, # noqa: F405
|
||||
"EditDetailerPipeSDXL": EditDetailerPipeSDXL, # noqa: F405
|
||||
|
||||
"LatentPixelScale": LatentPixelScale,
|
||||
"PixelKSampleUpscalerProvider": PixelKSampleUpscalerProvider,
|
||||
"PixelKSampleUpscalerProviderPipe": PixelKSampleUpscalerProviderPipe,
|
||||
"IterativeLatentUpscale": IterativeLatentUpscale,
|
||||
"IterativeImageUpscale": IterativeImageUpscale,
|
||||
"PixelTiledKSampleUpscalerProvider": PixelTiledKSampleUpscalerProvider,
|
||||
"PixelTiledKSampleUpscalerProviderPipe": PixelTiledKSampleUpscalerProviderPipe,
|
||||
"TwoSamplersForMaskUpscalerProvider": TwoSamplersForMaskUpscalerProvider,
|
||||
"TwoSamplersForMaskUpscalerProviderPipe": TwoSamplersForMaskUpscalerProviderPipe,
|
||||
"LatentPixelScale": LatentPixelScale, # noqa: F405
|
||||
"PixelKSampleUpscalerProvider": PixelKSampleUpscalerProvider, # noqa: F405
|
||||
"PixelKSampleUpscalerProviderPipe": PixelKSampleUpscalerProviderPipe, # noqa: F405
|
||||
"IterativeLatentUpscale": IterativeLatentUpscale, # noqa: F405
|
||||
"IterativeImageUpscale": IterativeImageUpscale, # noqa: F405
|
||||
"PixelTiledKSampleUpscalerProvider": PixelTiledKSampleUpscalerProvider, # noqa: F405
|
||||
"PixelTiledKSampleUpscalerProviderPipe": PixelTiledKSampleUpscalerProviderPipe, # noqa: F405
|
||||
"TwoSamplersForMaskUpscalerProvider": TwoSamplersForMaskUpscalerProvider, # noqa: F405
|
||||
"TwoSamplersForMaskUpscalerProviderPipe": TwoSamplersForMaskUpscalerProviderPipe, # noqa: F405
|
||||
|
||||
"PixelKSampleHookCombine": PixelKSampleHookCombine,
|
||||
"DenoiseScheduleHookProvider": DenoiseScheduleHookProvider,
|
||||
"StepsScheduleHookProvider": StepsScheduleHookProvider,
|
||||
"CfgScheduleHookProvider": CfgScheduleHookProvider,
|
||||
"NoiseInjectionHookProvider": NoiseInjectionHookProvider,
|
||||
"UnsamplerHookProvider": UnsamplerHookProvider,
|
||||
"CoreMLDetailerHookProvider": CoreMLDetailerHookProvider,
|
||||
"PreviewDetailerHookProvider": PreviewDetailerHookProvider,
|
||||
"PixelKSampleHookCombine": PixelKSampleHookCombine, # noqa: F405
|
||||
"DenoiseScheduleHookProvider": DenoiseScheduleHookProvider, # noqa: F405
|
||||
"StepsScheduleHookProvider": StepsScheduleHookProvider, # noqa: F405
|
||||
"CfgScheduleHookProvider": CfgScheduleHookProvider, # noqa: F405
|
||||
"NoiseInjectionHookProvider": NoiseInjectionHookProvider, # noqa: F405
|
||||
"UnsamplerHookProvider": UnsamplerHookProvider, # noqa: F405
|
||||
"CoreMLDetailerHookProvider": CoreMLDetailerHookProvider, # noqa: F405
|
||||
"PreviewDetailerHookProvider": PreviewDetailerHookProvider, # noqa: F405
|
||||
"CustomSamplerDetailerHookProvider": CustomSamplerDetailerHookProvider, # noqa: F405
|
||||
"LamaRemoverDetailerHookProvider": LamaRemoverDetailerHookProvider, # noqa: F405
|
||||
|
||||
"DetailerHookCombine": DetailerHookCombine,
|
||||
"NoiseInjectionDetailerHookProvider": NoiseInjectionDetailerHookProvider,
|
||||
"UnsamplerDetailerHookProvider": UnsamplerDetailerHookProvider,
|
||||
"DenoiseSchedulerDetailerHookProvider": DenoiseSchedulerDetailerHookProvider,
|
||||
"SEGSOrderedFilterDetailerHookProvider": SEGSOrderedFilterDetailerHookProvider,
|
||||
"SEGSRangeFilterDetailerHookProvider": SEGSRangeFilterDetailerHookProvider,
|
||||
"SEGSLabelFilterDetailerHookProvider": SEGSLabelFilterDetailerHookProvider,
|
||||
"VariationNoiseDetailerHookProvider": VariationNoiseDetailerHookProvider,
|
||||
"DetailerHookCombine": DetailerHookCombine, # noqa: F405
|
||||
"NoiseInjectionDetailerHookProvider": NoiseInjectionDetailerHookProvider, # noqa: F405
|
||||
"UnsamplerDetailerHookProvider": UnsamplerDetailerHookProvider, # noqa: F405
|
||||
"DenoiseSchedulerDetailerHookProvider": DenoiseSchedulerDetailerHookProvider, # noqa: F405
|
||||
"SEGSOrderedFilterDetailerHookProvider": SEGSOrderedFilterDetailerHookProvider, # noqa: F405
|
||||
"SEGSRangeFilterDetailerHookProvider": SEGSRangeFilterDetailerHookProvider, # noqa: F405
|
||||
"SEGSLabelFilterDetailerHookProvider": SEGSLabelFilterDetailerHookProvider, # noqa: F405
|
||||
"VariationNoiseDetailerHookProvider": VariationNoiseDetailerHookProvider, # noqa: F405
|
||||
# "CustomNoiseDetailerHookProvider": CustomNoiseDetailerHookProvider,
|
||||
|
||||
"BitwiseAndMask": BitwiseAndMask,
|
||||
"SubtractMask": SubtractMask,
|
||||
"AddMask": AddMask,
|
||||
"MaskRectArea": MaskRectArea,
|
||||
"MaskRectAreaAdvanced": MaskRectAreaAdvanced,
|
||||
"ImpactSegsAndMask": SegsBitwiseAndMask,
|
||||
"ImpactSegsAndMaskForEach": SegsBitwiseAndMaskForEach,
|
||||
"EmptySegs": EmptySEGS,
|
||||
"ImpactFlattenMask": FlattenMask,
|
||||
"BitwiseAndMask": BitwiseAndMask, # noqa: F405
|
||||
"SubtractMask": SubtractMask, # noqa: F405
|
||||
"AddMask": AddMask, # noqa: F405
|
||||
"MaskRectArea": MaskRectArea, # noqa: F405
|
||||
"MaskRectAreaAdvanced": MaskRectAreaAdvanced, # noqa: F405
|
||||
"ImpactSegsAndMask": SegsBitwiseAndMask, # noqa: F405
|
||||
"ImpactSegsAndMaskForEach": SegsBitwiseAndMaskForEach, # noqa: F405
|
||||
"EmptySegs": EmptySEGS, # noqa: F405
|
||||
"ImpactFlattenMask": FlattenMask, # noqa: F405
|
||||
|
||||
"MediaPipeFaceMeshToSEGS": MediaPipeFaceMeshToSEGS,
|
||||
"MaskToSEGS": MaskToSEGS,
|
||||
"MaskToSEGS_for_AnimateDiff": MaskToSEGS_for_AnimateDiff,
|
||||
"ToBinaryMask": ToBinaryMask,
|
||||
"MasksToMaskList": MasksToMaskList,
|
||||
"MaskListToMaskBatch": MaskListToMaskBatch,
|
||||
"ImageListToImageBatch": ImageListToImageBatch,
|
||||
"SetDefaultImageForSEGS": DefaultImageForSEGS,
|
||||
"RemoveImageFromSEGS": RemoveImageFromSEGS,
|
||||
"MediaPipeFaceMeshToSEGS": MediaPipeFaceMeshToSEGS, # noqa: F405
|
||||
"MaskToSEGS": MaskToSEGS, # noqa: F405
|
||||
"MaskToSEGS_for_AnimateDiff": MaskToSEGS_for_AnimateDiff, # noqa: F405
|
||||
"ToBinaryMask": ToBinaryMask, # noqa: F405
|
||||
"MasksToMaskList": MasksToMaskList, # noqa: F405
|
||||
"MaskListToMaskBatch": MaskListToMaskBatch, # noqa: F405
|
||||
"ImageListToImageBatch": ImageListToImageBatch, # noqa: F405
|
||||
"SetDefaultImageForSEGS": DefaultImageForSEGS, # noqa: F405
|
||||
"RemoveImageFromSEGS": RemoveImageFromSEGS, # noqa: F405
|
||||
|
||||
"BboxDetectorSEGS": BboxDetectorForEach,
|
||||
"SegmDetectorSEGS": SegmDetectorForEach,
|
||||
"ONNXDetectorSEGS": BboxDetectorForEach,
|
||||
"ImpactSimpleDetectorSEGS_for_AD": SimpleDetectorForAnimateDiff,
|
||||
"ImpactSimpleDetectorSEGS": SimpleDetectorForEach,
|
||||
"ImpactSimpleDetectorSEGSPipe": SimpleDetectorForEachPipe,
|
||||
"ImpactControlNetApplySEGS": ControlNetApplySEGS,
|
||||
"ImpactControlNetApplyAdvancedSEGS": ControlNetApplyAdvancedSEGS,
|
||||
"ImpactControlNetClearSEGS": ControlNetClearSEGS,
|
||||
"ImpactIPAdapterApplySEGS": IPAdapterApplySEGS,
|
||||
"BboxDetectorSEGS": BboxDetectorForEach, # noqa: F405
|
||||
"SegmDetectorSEGS": SegmDetectorForEach, # noqa: F405
|
||||
"ONNXDetectorSEGS": BboxDetectorForEach, # noqa: F405
|
||||
"ImpactSimpleDetectorSEGS_for_AD": SimpleDetectorForAnimateDiff, # noqa: F405
|
||||
"ImpactSAM2VideoDetectorSEGS": SAM2VideoDetectorSEGS, # noqa: F405
|
||||
"ImpactSimpleDetectorSEGS": SimpleDetectorForEach, # noqa: F405
|
||||
"ImpactSimpleDetectorSEGSPipe": SimpleDetectorForEachPipe, # noqa: F405
|
||||
"ImpactControlNetApplySEGS": ControlNetApplySEGS, # noqa: F405
|
||||
"ImpactControlNetApplyAdvancedSEGS": ControlNetApplyAdvancedSEGS, # noqa: F405
|
||||
"ImpactControlNetClearSEGS": ControlNetClearSEGS, # noqa: F405
|
||||
"ImpactIPAdapterApplySEGS": IPAdapterApplySEGS, # noqa: F405
|
||||
|
||||
"ImpactDecomposeSEGS": DecomposeSEGS,
|
||||
"ImpactAssembleSEGS": AssembleSEGS,
|
||||
"ImpactFrom_SEG_ELT": From_SEG_ELT,
|
||||
"ImpactEdit_SEG_ELT": Edit_SEG_ELT,
|
||||
"ImpactDilate_Mask_SEG_ELT": Dilate_SEG_ELT,
|
||||
"ImpactDilateMask": DilateMask,
|
||||
"ImpactGaussianBlurMask": GaussianBlurMask,
|
||||
"ImpactDilateMaskInSEGS": DilateMaskInSEGS,
|
||||
"ImpactGaussianBlurMaskInSEGS": GaussianBlurMaskInSEGS,
|
||||
"ImpactScaleBy_BBOX_SEG_ELT": SEG_ELT_BBOX_ScaleBy,
|
||||
"ImpactFrom_SEG_ELT_bbox": From_SEG_ELT_bbox,
|
||||
"ImpactFrom_SEG_ELT_crop_region": From_SEG_ELT_crop_region,
|
||||
"ImpactCount_Elts_in_SEGS": Count_Elts_in_SEGS,
|
||||
"ImpactDecomposeSEGS": DecomposeSEGS, # noqa: F405
|
||||
"ImpactAssembleSEGS": AssembleSEGS, # noqa: F405
|
||||
"ImpactFrom_SEG_ELT": From_SEG_ELT, # noqa: F405
|
||||
"ImpactEdit_SEG_ELT": Edit_SEG_ELT, # noqa: F405
|
||||
"ImpactDilate_Mask_SEG_ELT": Dilate_SEG_ELT, # noqa: F405
|
||||
"ImpactDilateMask": DilateMask, # noqa: F405
|
||||
"ImpactGaussianBlurMask": GaussianBlurMask, # noqa: F405
|
||||
"ImpactDilateMaskInSEGS": DilateMaskInSEGS, # noqa: F405
|
||||
"ImpactGaussianBlurMaskInSEGS": GaussianBlurMaskInSEGS, # noqa: F405
|
||||
"ImpactScaleBy_BBOX_SEG_ELT": SEG_ELT_BBOX_ScaleBy, # noqa: F405
|
||||
"ImpactFrom_SEG_ELT_bbox": From_SEG_ELT_bbox, # noqa: F405
|
||||
"ImpactFrom_SEG_ELT_crop_region": From_SEG_ELT_crop_region, # noqa: F405
|
||||
"ImpactCount_Elts_in_SEGS": Count_Elts_in_SEGS, # noqa: F405
|
||||
|
||||
"BboxDetectorCombined_v2": BboxDetectorCombined,
|
||||
"SegmDetectorCombined_v2": SegmDetectorCombined,
|
||||
"SegsToCombinedMask": SegsToCombinedMask,
|
||||
"BboxDetectorCombined_v2": BboxDetectorCombined, # noqa: F405
|
||||
"SegmDetectorCombined_v2": SegmDetectorCombined, # noqa: F405
|
||||
"SegsToCombinedMask": SegsToCombinedMask, # noqa: F405
|
||||
|
||||
"KSamplerProvider": KSamplerProvider,
|
||||
"TwoSamplersForMask": TwoSamplersForMask,
|
||||
"TiledKSamplerProvider": TiledKSamplerProvider,
|
||||
"KSamplerProvider": KSamplerProvider, # noqa: F405
|
||||
"TwoSamplersForMask": TwoSamplersForMask, # noqa: F405
|
||||
"TiledKSamplerProvider": TiledKSamplerProvider, # noqa: F405
|
||||
|
||||
"KSamplerAdvancedProvider": KSamplerAdvancedProvider,
|
||||
"TwoAdvancedSamplersForMask": TwoAdvancedSamplersForMask,
|
||||
"KSamplerAdvancedProvider": KSamplerAdvancedProvider, # noqa: F405
|
||||
"TwoAdvancedSamplersForMask": TwoAdvancedSamplersForMask, # noqa: F405
|
||||
|
||||
"ImpactNegativeConditioningPlaceholder": NegativeConditioningPlaceholder,
|
||||
"ImpactNegativeConditioningPlaceholder": NegativeConditioningPlaceholder, # noqa: F405
|
||||
|
||||
"PreviewBridge": PreviewBridge,
|
||||
"PreviewBridgeLatent": PreviewBridgeLatent,
|
||||
"ImageSender": ImageSender,
|
||||
"ImageReceiver": ImageReceiver,
|
||||
"LatentSender": LatentSender,
|
||||
"LatentReceiver": LatentReceiver,
|
||||
"ImageMaskSwitch": ImageMaskSwitch,
|
||||
"LatentSwitch": GeneralSwitch,
|
||||
"SEGSSwitch": GeneralSwitch,
|
||||
"ImpactSwitch": GeneralSwitch,
|
||||
"ImpactInversedSwitch": GeneralInversedSwitch,
|
||||
"PreviewBridge": PreviewBridge, # noqa: F405
|
||||
"PreviewBridgeLatent": PreviewBridgeLatent, # noqa: F405
|
||||
"ImageSender": ImageSender, # noqa: F405
|
||||
"ImageReceiver": ImageReceiver, # noqa: F405
|
||||
"LatentSender": LatentSender, # noqa: F405
|
||||
"LatentReceiver": LatentReceiver, # noqa: F405
|
||||
"ImageMaskSwitch": ImageMaskSwitch, # noqa: F405
|
||||
"LatentSwitch": GeneralSwitch, # noqa: F405
|
||||
"SEGSSwitch": GeneralSwitch, # noqa: F405
|
||||
"ImpactSwitch": GeneralSwitch, # noqa: F405
|
||||
"ImpactInversedSwitch": GeneralInversedSwitch, # noqa: F405
|
||||
|
||||
"ImpactWildcardProcessor": ImpactWildcardProcessor,
|
||||
"ImpactWildcardEncode": ImpactWildcardEncode,
|
||||
"ImpactWildcardProcessor": ImpactWildcardProcessor, # noqa: F405
|
||||
"ImpactWildcardEncode": ImpactWildcardEncode, # noqa: F405
|
||||
|
||||
"SEGSUpscaler": SEGSUpscaler,
|
||||
"SEGSUpscalerPipe": SEGSUpscalerPipe,
|
||||
"SEGSDetailer": SEGSDetailer,
|
||||
"SEGSPaste": SEGSPaste,
|
||||
"SEGSPreview": SEGSPreview,
|
||||
"SEGSPreviewCNet": SEGSPreviewCNet,
|
||||
"SEGSToImageList": SEGSToImageList,
|
||||
"ImpactSEGSToMaskList": SEGSToMaskList,
|
||||
"ImpactSEGSToMaskBatch": SEGSToMaskBatch,
|
||||
"ImpactSEGSConcat": SEGSConcat,
|
||||
"ImpactSEGSPicker": SEGSPicker,
|
||||
"ImpactMakeTileSEGS": MakeTileSEGS,
|
||||
"ImpactSEGSMerge": SEGSMerge,
|
||||
"SEGSUpscaler": SEGSUpscaler, # noqa: F405
|
||||
"SEGSUpscalerPipe": SEGSUpscalerPipe, # noqa: F405
|
||||
"SEGSDetailer": SEGSDetailer, # noqa: F405
|
||||
"SEGSPaste": SEGSPaste, # noqa: F405
|
||||
"SEGSPreview": SEGSPreview, # noqa: F405
|
||||
"SEGSPreviewCNet": SEGSPreviewCNet, # noqa: F405
|
||||
"SEGSToImageList": SEGSToImageList, # noqa: F405
|
||||
"ImpactSEGSToMaskList": SEGSToMaskList, # noqa: F405
|
||||
"ImpactSEGSToMaskBatch": SEGSToMaskBatch, # noqa: F405
|
||||
"ImpactSEGSConcat": SEGSConcat, # noqa: F405
|
||||
"ImpactSEGSPicker": SEGSPicker, # noqa: F405
|
||||
"ImpactMakeTileSEGS": MakeTileSEGS, # noqa: F405
|
||||
"ImpactSEGSMerge": SEGSMerge, # noqa: F405
|
||||
|
||||
"SEGSDetailerForAnimateDiff": SEGSDetailerForAnimateDiff,
|
||||
"SEGSDetailerForAnimateDiff": SEGSDetailerForAnimateDiff, # noqa: F405
|
||||
|
||||
"ImpactKSamplerBasicPipe": KSamplerBasicPipe,
|
||||
"ImpactKSamplerAdvancedBasicPipe": KSamplerAdvancedBasicPipe,
|
||||
"ImpactKSamplerBasicPipe": KSamplerBasicPipe, # noqa: F405
|
||||
"ImpactKSamplerAdvancedBasicPipe": KSamplerAdvancedBasicPipe, # noqa: F405
|
||||
|
||||
"ReencodeLatent": ReencodeLatent,
|
||||
"ReencodeLatentPipe": ReencodeLatentPipe,
|
||||
"ReencodeLatent": ReencodeLatent, # noqa: F405
|
||||
"ReencodeLatentPipe": ReencodeLatentPipe, # noqa: F405
|
||||
|
||||
"ImpactImageBatchToImageList": ImageBatchToImageList,
|
||||
"ImpactMakeImageList": MakeImageList,
|
||||
"ImpactMakeImageBatch": MakeImageBatch,
|
||||
"ImpactMakeAnyList": MakeAnyList,
|
||||
"ImpactMakeMaskList": MakeMaskList,
|
||||
"ImpactMakeMaskBatch": MakeMaskBatch,
|
||||
"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,
|
||||
"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
|
||||
}
|
||||
|
||||
|
||||
@@ -300,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",
|
||||
@@ -312,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)",
|
||||
@@ -327,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)",
|
||||
|
||||
@@ -363,6 +363,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSEGSLabelFilter": "SEGS Filter (label)",
|
||||
"ImpactSEGSRangeFilter": "SEGS Filter (range)",
|
||||
"ImpactSEGSOrderedFilter": "SEGS Filter (ordered)",
|
||||
"ImpactSEGSIntersectionFilter": "SEGS Filter (intersection)",
|
||||
"ImpactSEGSNMSFilter": "SEGS Filter (non max suppression)",
|
||||
"ImpactSEGSConcat": "SEGS Concat",
|
||||
"ImpactSEGSToMaskList": "SEGS to Mask List",
|
||||
"ImpactSEGSToMaskBatch": "SEGS to Mask Batch",
|
||||
@@ -404,6 +406,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactMakeMaskList": "Make Mask List",
|
||||
"ImpactMakeMaskBatch": "Make Mask Batch",
|
||||
"ImpactMakeAnyList": "Make List (Any)",
|
||||
"ImpactSelectNthItemOfAnyList": "Select Nth Item (Any list)",
|
||||
|
||||
"ImpactStringSelector": "String Selector",
|
||||
"StringListToString": "String List to String",
|
||||
@@ -439,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
|
||||
@@ -472,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,596 @@
|
||||
{
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 5,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
30,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
390,
|
||||
320
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"clipspace/clipspace-mask-609196.2000000011.png [input]",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1230,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "workflow>Impact::MAKE_BASIC_PIPE",
|
||||
"pos": [
|
||||
20,
|
||||
620
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "basic_pipe",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "workflow/Impact::MAKE_BASIC_PIPE"
|
||||
},
|
||||
"widgets_values": [
|
||||
"SD1.5/realcartoon3d_v13.safetensors",
|
||||
"(best quality:1.4), fox girl",
|
||||
"(worst quality:1.4), nsfw"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "MaskDetailerPipe",
|
||||
"pos": [
|
||||
530,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
569.4000244140625,
|
||||
850
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "basic_pipe",
|
||||
"type": "BASIC_PIPE",
|
||||
"link": 3,
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "refiner_basic_pipe_opt",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 7,
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "detailer_hook",
|
||||
"type": "DETAILER_HOOK",
|
||||
"shape": 7,
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "scheduler_func_opt",
|
||||
"type": "SCHEDULER_FUNC",
|
||||
"shape": 7,
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
5
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "cropped_refined",
|
||||
"type": "IMAGE",
|
||||
"shape": 6,
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "cropped_enhanced_alpha",
|
||||
"type": "IMAGE",
|
||||
"shape": 6,
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "basic_pipe",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 3,
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "refiner_basic_pipe_opt",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 3,
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "MaskDetailerPipe"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
true,
|
||||
1024,
|
||||
true,
|
||||
1003,
|
||||
"fixed",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
0.75,
|
||||
5,
|
||||
3,
|
||||
10,
|
||||
0.2,
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
20,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"color": "#322",
|
||||
"bgcolor": "#533"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1230,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
3,
|
||||
3,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
"BASIC_PIPE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
2,
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
80,
|
||||
-110
|
||||
]
|
||||
},
|
||||
"groupNodes": {
|
||||
"Impact::MAKE_BASIC_PIPE": {
|
||||
"author": "Dr.Lt.Data",
|
||||
"category": "",
|
||||
"config": {
|
||||
"1": {
|
||||
"input": {
|
||||
"text": {
|
||||
"name": "Positive prompt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"input": {
|
||||
"text": {
|
||||
"name": "Negative prompt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"datetime": 1708272471445,
|
||||
"external": [],
|
||||
"links": [
|
||||
[
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
1,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
"CONDITIONING"
|
||||
]
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"flags": {},
|
||||
"index": 0,
|
||||
"mode": 0,
|
||||
"order": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "MODEL",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "MODEL",
|
||||
"localized_name": "MODEL"
|
||||
},
|
||||
{
|
||||
"links": [],
|
||||
"name": "CLIP",
|
||||
"shape": 3,
|
||||
"slot_index": 1,
|
||||
"type": "CLIP",
|
||||
"localized_name": "CLIP"
|
||||
},
|
||||
{
|
||||
"links": [],
|
||||
"name": "VAE",
|
||||
"shape": 3,
|
||||
"slot_index": 2,
|
||||
"type": "VAE",
|
||||
"localized_name": "VAE"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
550,
|
||||
360
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"widgets_values": [
|
||||
"SDXL/sd_xl_base_1.0_0.9vae.safetensors"
|
||||
],
|
||||
"inputs": []
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 1,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"localized_name": "clip"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 1,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "CONDITIONING",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
940,
|
||||
480
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"size": {
|
||||
"0": 263,
|
||||
"1": 99
|
||||
},
|
||||
"title": "Positive",
|
||||
"type": "CLIPTextEncode",
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 2,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"localized_name": "clip"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 2,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "CONDITIONING",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
940,
|
||||
640
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"size": {
|
||||
"0": 263,
|
||||
"1": 99
|
||||
},
|
||||
"title": "Negative",
|
||||
"type": "CLIPTextEncode",
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 3,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"localized_name": "model"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"localized_name": "clip"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"localized_name": "vae"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "positive"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "negative"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 3,
|
||||
"outputs": [
|
||||
{
|
||||
"links": null,
|
||||
"name": "basic_pipe",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "BASIC_PIPE",
|
||||
"localized_name": "basic_pipe"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
1320,
|
||||
360
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ToBasicPipe"
|
||||
},
|
||||
"size": {
|
||||
"0": 241.79998779296875,
|
||||
"1": 106
|
||||
},
|
||||
"type": "ToBasicPipe"
|
||||
}
|
||||
],
|
||||
"packname": "Impact",
|
||||
"version": "1.0"
|
||||
}
|
||||
},
|
||||
"controller_panel": {
|
||||
"controllers": {},
|
||||
"hidden": true,
|
||||
"highlight": true,
|
||||
"version": 2,
|
||||
"default_order": []
|
||||
},
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.14",
|
||||
"comfyui-impact-pack": "1ae7cae2df8cca06027edfa3a24512671239d6c4"
|
||||
},
|
||||
"ue_links": [],
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 526 KiB |
@@ -68,8 +68,6 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None):
|
||||
|
||||
|
||||
try:
|
||||
import platform
|
||||
import folder_paths
|
||||
from torchvision.datasets.utils import download_url
|
||||
import impact.config
|
||||
|
||||
@@ -86,21 +84,10 @@ try:
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'skip_download_model')):
|
||||
try:
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
bbox_path = os.path.join(model_path, "mmdets", "bbox")
|
||||
if not os.path.exists(bbox_path):
|
||||
os.makedirs(bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.pth")):
|
||||
download_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth", bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.py")):
|
||||
download_url("https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py", bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(sam_path, "sam_vit_b_01ec64.pth")):
|
||||
download_url("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", sam_path)
|
||||
except:
|
||||
print(f"[Impact Pack] Failed to auto-download model files. Please download them manually.")
|
||||
print("[Impact Pack] Failed to auto-download model files. Please download them manually.")
|
||||
|
||||
if not os.path.exists(onnx_path):
|
||||
print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})")
|
||||
@@ -109,18 +96,21 @@ try:
|
||||
impact.config.write_config()
|
||||
|
||||
# Remove legacy subpack
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'impact_subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
try:
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'impact_subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
except:
|
||||
print(f"ERROT: Failed to delete legacy subpack '{subpack_path}'\nPlease delete the folder after terminate ComfyUI.")
|
||||
|
||||
install()
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
print("[ERROR] ComfyUI-Impact-Pack: Dependency installation has failed. Please install manually.")
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { ComfyApp, app } from "../../scripts/app.js";
|
||||
|
||||
let conflict_check = undefined;
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.impact.comboBoolMigration",
|
||||
|
||||
nodeCreated(node, app) {
|
||||
for(let i in node.widgets) {
|
||||
let widget = node.widgets[i];
|
||||
|
||||
if(conflict_check == undefined) {
|
||||
conflict_check = !!app.extensions.find((ext) => ext.name === "Comfy.comboBoolMigration");
|
||||
}
|
||||
|
||||
if(conflict_check)
|
||||
return;
|
||||
|
||||
if(widget.type == "toggle") {
|
||||
let value = widget.value;
|
||||
|
||||
var v = Object.getOwnPropertyDescriptor(widget, 'value');
|
||||
if(!v) {
|
||||
Object.defineProperty(widget, "value", {
|
||||
set: (value) => {
|
||||
delete widget.value;
|
||||
widget.value = value == true || value == widget.options.on;
|
||||
},
|
||||
get: () => { return value; }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -3,6 +3,48 @@ import { app } from "../../scripts/app.js";
|
||||
|
||||
let original_show = app.ui.dialog.show;
|
||||
|
||||
export function customAlert(message) {
|
||||
try {
|
||||
app.extensionManager.toast.addAlert(message);
|
||||
}
|
||||
catch {
|
||||
alert(message);
|
||||
}
|
||||
}
|
||||
|
||||
export function isBeforeFrontendVersion(compareVersion) {
|
||||
try {
|
||||
const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__'];
|
||||
if (typeof frontendVersion !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
function parseVersion(versionString) {
|
||||
const parts = versionString.split('.').map(Number);
|
||||
return parts.length === 3 && parts.every(part => !isNaN(part)) ? parts : null;
|
||||
}
|
||||
|
||||
const currentVersion = parseVersion(frontendVersion);
|
||||
const comparisonVersion = parseVersion(compareVersion);
|
||||
|
||||
if (!currentVersion || !comparisonVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (currentVersion[i] > comparisonVersion[i]) {
|
||||
return false;
|
||||
} else if (currentVersion[i] < comparisonVersion[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function dialog_show_wrapper(html) {
|
||||
if (typeof html === "string") {
|
||||
if(html.includes("IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE")) {
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { ComfyApp, app } from "../../scripts/app.js";
|
||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { customAlert, isBeforeFrontendVersion } from "./common.js";
|
||||
|
||||
const is_legacy_front = () => isBeforeFrontendVersion('1.16.9');
|
||||
|
||||
if(is_legacy_front()) {
|
||||
customAlert("An outdated version(<1.16.9) of the `comfyui-frontend-package` is installed. It is not compatible with the current version of the Impact Pack.");
|
||||
}
|
||||
|
||||
let wildcards_list = [];
|
||||
async function load_wildcards() {
|
||||
@@ -93,7 +100,7 @@ const input_dirty = {};
|
||||
const output_tracking = {};
|
||||
|
||||
function progressExecuteHandler(event) {
|
||||
if(event.detail.output.aux){
|
||||
if(event.detail?.output?.aux){
|
||||
const id = event.detail.node;
|
||||
if(input_tracking.hasOwnProperty(id)) {
|
||||
if(input_tracking.hasOwnProperty(id) && input_tracking[id][0] != event.detail.output.aux[0]) {
|
||||
@@ -222,6 +229,31 @@ api.addEventListener("executed", progressExecuteHandler);
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.Impack",
|
||||
|
||||
commands: [
|
||||
{
|
||||
id: 'refresh-impact-wildcard',
|
||||
label: 'Impact: Refresh Wildcard',
|
||||
function: async () => {
|
||||
await api.fetchApi('/impact/wildcards/refresh');
|
||||
await load_wildcards();
|
||||
app.extensionManager.toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Refreshed!',
|
||||
detail: 'Impact Wildcard List is refreshed!!',
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
menuCommands: [
|
||||
{
|
||||
path: ['Edit'],
|
||||
commands: ['refresh-impact-wildcard']
|
||||
}
|
||||
],
|
||||
|
||||
loadedGraphNode(node, app) {
|
||||
if (node.comfyClass == "MaskPainter") {
|
||||
input_dirty[node.id + ""] = true;
|
||||
@@ -248,7 +280,7 @@ app.registerExtension({
|
||||
}
|
||||
else {
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
slot_type = node.outputs[link_info.origin_slot].type;
|
||||
slot_type = node.outputs[link_info.origin_slot]?.type;
|
||||
}
|
||||
|
||||
this.inputs[0].type = slot_type;
|
||||
@@ -299,6 +331,32 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
if(nodeData.name == "ImpactSelectNthItemOfAnyList") {
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
|
||||
if(!link_info || this.inputs[0].type != '*')
|
||||
return;
|
||||
|
||||
if(index >= 2)
|
||||
return;
|
||||
|
||||
// assign type
|
||||
let slot_type = '*';
|
||||
|
||||
if(type == 2) {
|
||||
slot_type = link_info.type;
|
||||
}
|
||||
else {
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
slot_type = node.outputs[link_info.origin_slot].type;
|
||||
}
|
||||
|
||||
this.inputs[0].type = slot_type;
|
||||
this.outputs[0].type = slot_type;
|
||||
this.outputs[0].label = slot_type;
|
||||
}
|
||||
}
|
||||
|
||||
if(nodeData.name === 'ImpactInversedSwitch') {
|
||||
nodeData.output = ['*'];
|
||||
nodeData.output_is_list = [false];
|
||||
@@ -312,12 +370,12 @@ app.registerExtension({
|
||||
if(type == 2) {
|
||||
// connect output
|
||||
if(connected){
|
||||
if(app.graph._nodes_by_id[link_info.target_id].type == 'Reroute') {
|
||||
if(app.graph._nodes_by_id[link_info.target_id]?.type == 'Reroute') {
|
||||
app.graph._nodes_by_id[link_info.target_id].disconnectInput(link_info.target_slot);
|
||||
}
|
||||
|
||||
if(this.outputs[0].type == '*'){
|
||||
if(link_info.type == '*') {
|
||||
if(link_info.type == '*' && app.graph.getNodeById(link_info.target_id).slots[link_info.target_slot].type != '*') {
|
||||
app.graph._nodes_by_id[link_info.target_id].disconnectInput(link_info.target_slot);
|
||||
}
|
||||
else {
|
||||
@@ -334,15 +392,19 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(app.graph._nodes_by_id[link_info.origin_id].type == 'Reroute')
|
||||
if(app.graph._nodes_by_id[link_info.origin_id]?.type == 'Reroute')
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
|
||||
// connect input
|
||||
if(this.inputs[0].type == '*'){
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
let origin_type = node.outputs[link_info.origin_slot].type;
|
||||
let origin_type = node.outputs[link_info.origin_slot]?.type;
|
||||
|
||||
if(origin_type == '*') {
|
||||
if(origin_type==undefined) {
|
||||
return; // fallback
|
||||
}
|
||||
|
||||
if(origin_type == '*' && app.graph.getNodeById(link_info.origin_id).slots[link_info.origin_slot].type != '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
return;
|
||||
}
|
||||
@@ -366,20 +428,27 @@ app.registerExtension({
|
||||
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
|
||||
!stackTrace.includes('LGraphNode.connect') && // for mouse device
|
||||
!stackTrace.includes('loadGraphData')) {
|
||||
if(this.outputs[link_info.origin_slot].links.length == 0)
|
||||
if(this.outputs[link_info.origin_slot].links.length == 0) {
|
||||
this.removeOutput(link_info.origin_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let slot_i = 1;
|
||||
for (let i = 0; i < this.outputs.length; i++) {
|
||||
this.outputs[i].name = `output${slot_i}`
|
||||
if (this.outputs[i].slot_index === undefined) {
|
||||
this.outputs[i].slot_index = i;
|
||||
}
|
||||
slot_i++;
|
||||
}
|
||||
|
||||
let last_slot = this.outputs[this.outputs.length - 1];
|
||||
if (last_slot.slot_index == link_info.origin_slot) {
|
||||
this.addOutput(`output${slot_i}`, this.outputs[0].type);
|
||||
if(connected) {
|
||||
// NOTE: node.slot_index is different with link_info.origin_slot
|
||||
let last_slot_index = this.outputs.length - 1;
|
||||
if (last_slot_index == link_info.origin_slot) {
|
||||
this.addOutput(`output${slot_i}`, this.outputs[0].type);
|
||||
}
|
||||
}
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
@@ -442,6 +511,23 @@ app.registerExtension({
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('loadGraphData')) {
|
||||
if(this.widgets?.[0]) {
|
||||
this.widgets[0].options.max = this.inputs.length-3;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(stackTrace.includes('pasteFromClipboard')) {
|
||||
if(this.widgets?.[0]) {
|
||||
this.widgets[0].options.max = this.inputs.length-3;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!link_info)
|
||||
return;
|
||||
|
||||
@@ -453,7 +539,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
if(this.outputs[0].type == '*'){
|
||||
if(link_info.type == '*') {
|
||||
if(link_info.type == '*' && app.graph.getNodeById(link_info.target_id).slots[link_info.target_slot].type != '*') {
|
||||
app.graph._nodes_by_id[link_info.target_id].disconnectInput(link_info.target_slot);
|
||||
}
|
||||
else {
|
||||
@@ -484,12 +570,12 @@ app.registerExtension({
|
||||
if(this.inputs[0].type == '*'){
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
let origin_type = node.outputs[link_info.origin_slot]?.type;
|
||||
if(link_info.target_slot == 0 && this.inputs.length > 1) {
|
||||
if(link_info.target_slot == 0 && this.inputs.length > 3) { // NOTE: widgets are regarded as input since new front
|
||||
origin_type = this.inputs[1].type;
|
||||
node.connect(link_info.origin_slot, node.id, 'input1');
|
||||
}
|
||||
|
||||
if(origin_type == '*') {
|
||||
if(origin_type == '*' && app.graph.getNodeById(link_info.origin_id).slots[link_info.origin_slot].type != '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
return;
|
||||
}
|
||||
@@ -507,15 +593,8 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
let mode_slot = this.inputs.find(x => x.name == "sel_mode");
|
||||
|
||||
let converted_count = 0;
|
||||
converted_count += select_slot?1:0;
|
||||
converted_count += mode_slot?1:0;
|
||||
|
||||
if (!connected && (this.inputs.length > 1+converted_count)) {
|
||||
const stackTrace = new Error().stack;
|
||||
|
||||
if (!connected && (this.inputs.length > 3)) {
|
||||
if(
|
||||
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
|
||||
!stackTrace.includes('LGraphNode.connect') && // for mouse device
|
||||
@@ -525,6 +604,7 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let slot_i = 1;
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
let input_i = this.inputs[i];
|
||||
@@ -534,18 +614,13 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
let last_slot = this.inputs[this.inputs.length - 1];
|
||||
if (
|
||||
(last_slot.name == 'select' && last_slot.name != 'sel_mode' && this.inputs[this.inputs.length - 2].link != undefined)
|
||||
|| (last_slot.name != 'select' && last_slot.name != 'sel_mode' && last_slot.link != undefined)) {
|
||||
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
|
||||
if(connected) {
|
||||
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
|
||||
}
|
||||
|
||||
if(this.widgets?.length) {
|
||||
this.widgets[0].options.max = select_slot?this.inputs.length-1:this.inputs.length;
|
||||
if(this.widgets?.[0]) {
|
||||
this.widgets[0].options.max = this.inputs.length-3;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
if(this.widgets[0].options.max > 0 && this.widgets[0].value == 0)
|
||||
this.widgets[0].value = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -588,12 +663,14 @@ app.registerExtension({
|
||||
|
||||
if(node.comfyClass == "ImpactSEGSLabelFilter" || node.comfyClass == "SEGSLabelFilterDetailerHookProvider") {
|
||||
node.widgets[0].callback = (value, canvas, node, pos, e) => {
|
||||
if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(","))
|
||||
node.widgets[1].value += ", "
|
||||
if(node) {
|
||||
if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(","))
|
||||
node.widgets[1].value += ", "
|
||||
|
||||
node.widgets[1].value += value;
|
||||
if(node.widgets_values)
|
||||
node.widgets_values[1] = node.widgets[1].value;
|
||||
node.widgets[1].value += value;
|
||||
if(node.widgets_values)
|
||||
node.widgets_values[1] = node.widgets[1].value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[0], "value", {
|
||||
@@ -667,18 +744,20 @@ app.registerExtension({
|
||||
break;
|
||||
}
|
||||
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
if(node.widgets[tbox_id].value != '')
|
||||
node.widgets[tbox_id].value += ', '
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
if(node) {
|
||||
if(node.widgets[tbox_id].value != '')
|
||||
node.widgets[tbox_id].value += ', '
|
||||
|
||||
node.widgets[tbox_id].value += node._wildcard_value;
|
||||
}
|
||||
node.widgets[tbox_id].value += node._wildcard_value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id+1], "value", {
|
||||
set: (value) => {
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
},
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
},
|
||||
get: () => { return "Select the Wildcard to add to the text"; }
|
||||
});
|
||||
|
||||
@@ -691,14 +770,16 @@ app.registerExtension({
|
||||
|
||||
if(has_lora) {
|
||||
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
|
||||
let lora_name = node._value;
|
||||
if(lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
if(node) {
|
||||
let lora_name = node._value;
|
||||
if(lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
|
||||
node.widgets[tbox_id].value += `<lora:${lora_name}>`;
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[tbox_id] = node.widgets[tbox_id].value;
|
||||
node.widgets[tbox_id].value += `<lora:${lora_name}>`;
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[tbox_id] = node.widgets[tbox_id].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,14 +811,20 @@ app.registerExtension({
|
||||
// mode combo
|
||||
Object.defineProperty(mode_widget, "value", {
|
||||
set: (value) => {
|
||||
node._mode_value = value == true || value == "Populate";
|
||||
populated_text_widget.inputEl.disabled = value == true || value == "Populate";
|
||||
if(value == true)
|
||||
node._mode_value = "populate";
|
||||
else if(value == false)
|
||||
node._mode_value = "fixed";
|
||||
else
|
||||
node._mode_value = value; // combo value
|
||||
|
||||
populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
|
||||
},
|
||||
get: () => {
|
||||
if(node._mode_value != undefined)
|
||||
return node._mode_value;
|
||||
else
|
||||
return true;
|
||||
return 'populate';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ComfyApp, app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
let refresh_btn = document.getElementById('comfy-refresh-button');
|
||||
let refresh_btn2 = document.querySelector('button[title="Refresh widgets in nodes to find new models or files"]');
|
||||
|
||||
let orig = refresh_btn.onclick;
|
||||
|
||||
if(refresh_btn) {
|
||||
refresh_btn.onclick = function() {
|
||||
orig();
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
};
|
||||
}
|
||||
|
||||
if(refresh_btn2) {
|
||||
refresh_btn2?.addEventListener('click', function() {
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import subprocess
|
||||
|
||||
def ensure_onnx_package():
|
||||
try:
|
||||
import onnxruntime
|
||||
import onnxruntime # noqa: F401
|
||||
except Exception:
|
||||
if "python_embeded" in sys.executable or "python_embedded" in sys.executable:
|
||||
subprocess.check_call([sys.executable, '-s', '-m', 'pip', 'install', 'onnxruntime'])
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from nodes import MAX_RESOLUTION
|
||||
from impact.utils import *
|
||||
import impact.core as core
|
||||
from impact.core import SEG
|
||||
from impact.segs_nodes import SEGSPaste
|
||||
|
||||
import comfy
|
||||
from impact import utils
|
||||
import torch
|
||||
import nodes
|
||||
import logging
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
logging.warning("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -27,7 +30,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
||||
"scheduler": (core.SCHEDULERS,),
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"basic_pipe": ("BASIC_PIPE",),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -45,6 +48,8 @@ class SEGSDetailerForAnimateDiff:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size.\nThis node is applied specifically to SEGS rather than the entire image. To apply it to the entire image, use the 'SEGS Paste' node.\nAs a specialized detailer node for improving video details, such as in AnimateDiff, this node can handle cases where the masks contained in SEGS serve as batch masks spanning multiple frames."
|
||||
|
||||
@staticmethod
|
||||
def do_detail(image_frames, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, basic_pipe, refiner_ratio=None, refiner_basic_pipe_opt=None, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
@@ -60,7 +65,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
new_segs = []
|
||||
cnet_image_list = []
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
for seg in segs[1]:
|
||||
@@ -68,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:
|
||||
@@ -94,13 +99,18 @@ class SEGSDetailerForAnimateDiff:
|
||||
for condition, details in negative
|
||||
]
|
||||
|
||||
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, seg.cropped_mask,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
if not (isinstance(model, str) and model == "DUMMY"):
|
||||
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, seg.cropped_mask,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
else:
|
||||
enhanced_image_tensor = cropped_image_frames
|
||||
cnet_images = None
|
||||
|
||||
if cnet_images is not None:
|
||||
cnet_image_list.extend(cnet_images)
|
||||
|
||||
@@ -122,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)
|
||||
|
||||
@@ -143,7 +153,7 @@ class DetailerForEachPipeForAnimateDiff:
|
||||
"scheduler": (core.SCHEDULERS,),
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
||||
"basic_pipe": ("BASIC_PIPE", ),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -161,6 +171,8 @@ class DetailerForEachPipeForAnimateDiff:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size.\nThis node is a specialized detailer node for enhancing video details, such as in AnimateDiff. It can handle cases where the masks contained in SEGS serve as batch masks spanning multiple frames."
|
||||
|
||||
@staticmethod
|
||||
def doit(image_frames, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, basic_pipe, refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import configparser
|
||||
import os
|
||||
import logging
|
||||
|
||||
version_code = [8, 1, 1]
|
||||
|
||||
version_code = [8, 20, 1]
|
||||
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")),
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import copy
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import numpy
|
||||
import torch
|
||||
from segment_anything import SamPredictor
|
||||
|
||||
from comfy_extras.nodes_custom_sampler import Noise_RandomNoise
|
||||
from impact.utils import *
|
||||
from collections import namedtuple
|
||||
import numpy as np
|
||||
from skimage.measure import label
|
||||
from PIL import ImageOps
|
||||
from PIL import ImageOps, Image
|
||||
|
||||
import nodes
|
||||
import comfy_extras.nodes_upscale_model as model_upscale
|
||||
@@ -26,12 +22,25 @@ from impact import utils
|
||||
from impact import impact_sampling
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import inspect
|
||||
from collections import OrderedDict
|
||||
import torch.nn.functional as F
|
||||
import logging
|
||||
import sys
|
||||
import importlib
|
||||
|
||||
|
||||
is_sam2_available = importlib.util.find_spec("sam2")
|
||||
sam2_unavailable_message = f"\n----------------------------------------------------------------------------\n[Impact Pack] The SAM2 functionality is unavailable because the `facebook/sam2` dependency is not installed.\n\nInstallation command:\n{sys.executable} -m pip install git+https://github.com/facebookresearch/sam2\n----------------------------------------------------------------------------\n"
|
||||
if is_sam2_available:
|
||||
from sam2.sam2_image_predictor import SAM2ImagePredictor
|
||||
from sam2.build_sam import build_sam2, build_sam2_video_predictor
|
||||
else:
|
||||
logging.warning(sam2_unavailable_message)
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
logging.warning("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -48,14 +57,14 @@ preview_bridge_last_mask_cache = {}
|
||||
|
||||
current_prompt = None
|
||||
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]']
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan', 'OSS Chroma']
|
||||
|
||||
|
||||
def is_execution_model_version_supported():
|
||||
try:
|
||||
import comfy_execution
|
||||
import comfy_execution # noqa: F401
|
||||
return True
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
@@ -83,7 +92,7 @@ def set_previewbridge_image(node_id, file, item):
|
||||
|
||||
|
||||
def erosion_mask(mask, grow_mask_by):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
w = mask.shape[1]
|
||||
h = mask.shape[0]
|
||||
@@ -139,7 +148,7 @@ def mix_noise(from_noise, to_noise, strength, variation_method):
|
||||
|
||||
class REGIONAL_PROMPT:
|
||||
def __init__(self, mask, sampler, variation_seed=0, variation_strength=0.0, variation_method='linear'):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
self.mask = mask
|
||||
self.sampler = sampler
|
||||
@@ -199,7 +208,7 @@ def create_segmasks(results):
|
||||
|
||||
|
||||
def gen_detection_hints_from_mask_area(x, y, mask, threshold, use_negative):
|
||||
mask = make_2d_mask(mask)
|
||||
mask = utils.make_2d_mask(mask)
|
||||
|
||||
points = []
|
||||
plabs = []
|
||||
@@ -244,7 +253,8 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
detailer_hook=None,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None,
|
||||
refiner_negative=None, control_net_wrapper=None, cycle=1,
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func=None):
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func=None,
|
||||
vae_tiled_encode=False, vae_tiled_decode=False):
|
||||
|
||||
if noise_mask is not None:
|
||||
noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
@@ -274,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"
|
||||
@@ -298,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
|
||||
@@ -314,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:
|
||||
@@ -326,61 +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)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
latent_image = utils.to_latent_image(upscaled_image, vae, vae_tiled_encode=vae_tiled_encode)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
|
||||
if detailer_hook is not None:
|
||||
latent_image = detailer_hook.post_encode(latent_image)
|
||||
|
||||
refined_latent = latent_image
|
||||
|
||||
# ksampler
|
||||
for i in range(0, cycle):
|
||||
if detailer_hook is not None:
|
||||
latent_image = detailer_hook.post_encode(latent_image)
|
||||
|
||||
refined_latent = latent_image
|
||||
|
||||
sampler_opt=None
|
||||
if detailer_hook is not None:
|
||||
sampler_opt = detailer_hook.get_custom_sampler()
|
||||
|
||||
# ksampler
|
||||
for i in range(0, cycle):
|
||||
if detailer_hook is not None:
|
||||
detailer_hook.set_steps((i, cycle))
|
||||
if detailer_hook is not None:
|
||||
detailer_hook.set_steps((i, cycle))
|
||||
|
||||
refined_latent = detailer_hook.cycle_latent(refined_latent)
|
||||
refined_latent = detailer_hook.cycle_latent(refined_latent)
|
||||
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, upscaled_latent2, denoise2 = \
|
||||
detailer_hook.pre_ksample(model, seed+i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise)
|
||||
noise, is_touched = detailer_hook.get_custom_noise(seed+i, torch.zeros(latent_image['samples'].size()), is_touched=False)
|
||||
if not is_touched:
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, upscaled_latent2, denoise2 = \
|
||||
detailer_hook.pre_ksample(model, seed+i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise)
|
||||
noise, is_touched = detailer_hook.get_custom_noise(seed+i, torch.zeros(latent_image['samples'].size()), is_touched=False)
|
||||
if not is_touched:
|
||||
noise = None
|
||||
else:
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, _, denoise2 = \
|
||||
model, seed + i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise
|
||||
noise = None
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2,
|
||||
refined_latent, denoise2, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative,
|
||||
noise=noise, scheduler_func=scheduler_func, sampler_opt=sampler_opt)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
|
||||
# non-latent downscale - latent downscale cause bad quality
|
||||
start = time.time()
|
||||
if vae_tiled_decode:
|
||||
(refined_image,) = nodes.VAEDecodeTiled().decode(vae, refined_latent, 512) # using default settings
|
||||
logging.info(f"[Impact Pack] vae decoded (tiled) in {time.time() - start:.1f}s")
|
||||
else:
|
||||
model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2, upscaled_latent2, denoise2 = \
|
||||
model, seed + i, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise
|
||||
noise = None
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2,
|
||||
refined_latent, denoise2, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative,
|
||||
noise=noise, scheduler_func=scheduler_func)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
|
||||
# non-latent downscale - latent downscale cause bad quality
|
||||
try:
|
||||
# try to decode image normally
|
||||
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
|
||||
refined_image = vae.decode_tiled(refined_latent["samples"], tile_x=64, tile_y=64, )
|
||||
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:
|
||||
# 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()
|
||||
@@ -439,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
|
||||
@@ -447,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):
|
||||
@@ -475,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
|
||||
@@ -490,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:]:
|
||||
@@ -506,11 +533,16 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
'samples': latent_frames
|
||||
}
|
||||
|
||||
|
||||
sampler_opt=None
|
||||
if detailer_hook is not None:
|
||||
sampler_opt = detailer_hook.get_custom_sampler()
|
||||
|
||||
if detailer_hook is not None:
|
||||
latent = detailer_hook.post_encode(latent)
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
latent, denoise, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative, scheduler_func=scheduler_func)
|
||||
latent, denoise, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative, scheduler_func=scheduler_func, sampler_opt=sampler_opt)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
@@ -598,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
|
||||
@@ -623,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:
|
||||
@@ -644,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
|
||||
@@ -659,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)
|
||||
@@ -705,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]
|
||||
|
||||
@@ -726,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]
|
||||
@@ -784,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]
|
||||
|
||||
@@ -874,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)
|
||||
@@ -914,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
|
||||
@@ -929,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])
|
||||
@@ -946,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()
|
||||
@@ -955,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:
|
||||
@@ -972,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 = []
|
||||
@@ -998,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 = []
|
||||
@@ -1025,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 = []
|
||||
@@ -1054,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)
|
||||
|
||||
@@ -1070,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]
|
||||
@@ -1086,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)
|
||||
@@ -1096,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)
|
||||
@@ -1110,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))
|
||||
@@ -1138,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):
|
||||
@@ -1147,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 = []
|
||||
@@ -1171,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
|
||||
@@ -1205,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
|
||||
)
|
||||
|
||||
@@ -1239,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}")
|
||||
|
||||
@@ -1279,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
|
||||
@@ -1373,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]
|
||||
@@ -1384,9 +1536,14 @@ def vae_decode(vae, samples, use_tile, hook, tile_size=512, overlap=64):
|
||||
return pixels
|
||||
|
||||
|
||||
def vae_encode(vae, pixels, use_tile, hook, tile_size=512):
|
||||
def vae_encode(vae, pixels, use_tile, hook, tile_size=512, overlap=64):
|
||||
if use_tile:
|
||||
samples = nodes.VAEEncodeTiled().encode(vae, pixels, tile_size)[0]
|
||||
encoder = nodes.VAEEncodeTiled()
|
||||
if 'overlap' in inspect.signature(encoder.encode).parameters:
|
||||
samples = encoder.encode(vae, pixels, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
logging.warning("[Impact Pack] Your ComfyUI is outdated.")
|
||||
samples = encoder.encode(vae, pixels, tile_size)[0]
|
||||
else:
|
||||
samples = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
|
||||
@@ -1412,7 +1569,7 @@ def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_t
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
@@ -1433,7 +1590,7 @@ def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_with_model_shape(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
@@ -1454,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
|
||||
@@ -1464,7 +1621,7 @@ def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upsca
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_with_model(samples, scale_method, upscale_model, scale_factor, vae, use_tile=False,
|
||||
@@ -1490,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
|
||||
@@ -1500,7 +1657,7 @@ def latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_mod
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
class TwoSamplersForMaskUpscaler:
|
||||
@@ -1509,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]))
|
||||
|
||||
@@ -1527,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)
|
||||
|
||||
@@ -1557,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)
|
||||
|
||||
@@ -1613,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]
|
||||
@@ -1670,8 +1827,14 @@ class PixelKSampleUpscaler:
|
||||
preprocessor = nodes.NODE_CLASS_MAPPINGS['TilePreprocessor']()
|
||||
# might add capacity to set pyrUp_iters later, not needed for now though
|
||||
preprocessed = preprocessor.execute(images, pyrUp_iters=3, resolution=min(image_w, image_h))[0]
|
||||
apply_cnet = getattr(nodes.ControlNetApply(), nodes.ControlNetApply.FUNCTION)
|
||||
positive = apply_cnet(positive, self.tile_cnet, preprocessed, strength=self.tile_cnet_strength)[0]
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive=positive,
|
||||
negative=negative,
|
||||
control_net=self.tile_cnet,
|
||||
image=preprocessed,
|
||||
strength=self.tile_cnet_strength,
|
||||
start_percent=0,
|
||||
end_percent=1.0,
|
||||
vae=self.vae)
|
||||
|
||||
refined_latent = impact_sampling.impact_sample(model, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, upscaled_latent, denoise, scheduler_func=self.scheduler_func)
|
||||
@@ -1765,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']
|
||||
|
||||
@@ -1903,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)
|
||||
@@ -1942,7 +2105,7 @@ class PixelTiledKSampleUpscaler:
|
||||
def __init__(self, scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
denoise,
|
||||
tile_width, tile_height, tiling_strategy,
|
||||
upscale_model_opt=None, hook_opt=None, tile_cnet_opt=None, tile_size=512, tile_cnet_strength=1.0):
|
||||
upscale_model_opt=None, hook_opt=None, tile_cnet_opt=None, tile_size=512, tile_cnet_strength=1.0, overlap=64):
|
||||
self.params = scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise
|
||||
self.vae = vae
|
||||
self.tile_params = tile_width, tile_height, tiling_strategy
|
||||
@@ -1952,6 +2115,7 @@ class PixelTiledKSampleUpscaler:
|
||||
self.tile_size = tile_size
|
||||
self.is_tiled = True
|
||||
self.tile_cnet_strength = tile_cnet_strength
|
||||
self.overlap = overlap
|
||||
|
||||
def tiled_ksample(self, latent, images):
|
||||
if "BNK_TiledKSampler" in nodes.NODE_CLASS_MAPPINGS:
|
||||
@@ -1974,8 +2138,14 @@ class PixelTiledKSampleUpscaler:
|
||||
preprocessor = nodes.NODE_CLASS_MAPPINGS['TilePreprocessor']()
|
||||
# might add capacity to set pyrUp_iters later, not needed for now though
|
||||
preprocessed = preprocessor.execute(images, pyrUp_iters=3, resolution=min(image_w, image_h))[0]
|
||||
apply_cnet = getattr(nodes.ControlNetApply(), nodes.ControlNetApply.FUNCTION)
|
||||
positive = apply_cnet(positive, self.tile_cnet, preprocessed, strength=self.tile_cnet_strength)[0]
|
||||
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive=positive,
|
||||
negative=negative,
|
||||
control_net=self.tile_cnet,
|
||||
image=preprocessed,
|
||||
strength=self.tile_cnet_strength,
|
||||
start_percent=0, end_percent=1.0,
|
||||
vae=self.vae)
|
||||
|
||||
return TiledKSampler().sample(model, seed, tile_width, tile_height, tiling_strategy, steps, cfg, sampler_name,
|
||||
scheduler, positive, negative, latent, denoise)[0]
|
||||
@@ -2044,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)
|
||||
|
||||
@@ -2074,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):
|
||||
@@ -2160,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:
|
||||
@@ -2176,10 +2346,15 @@ class SafeToGPU:
|
||||
if model_management.get_free_memory(device) > self.size * 1.3:
|
||||
try:
|
||||
obj.to(device)
|
||||
except:
|
||||
print(f"WARN: The model is not moved to the '{device}' due to insufficient memory. [1]")
|
||||
except Exception:
|
||||
logging.warning(f"[Impact Pack] The model is not moved to the '{device}' due to insufficient memory. [1]")
|
||||
else:
|
||||
print(f"WARN: The model is not moved to the '{device}' due to insufficient memory. [2]")
|
||||
logging.warning(f"[Impact Pack] The model is not moved to the '{device}' due to insufficient memory. [2]")
|
||||
|
||||
|
||||
class SafeToGPU_stub():
|
||||
def to_device(self, obj, device):
|
||||
pass
|
||||
|
||||
|
||||
from comfy.cli_args import args, LatentPreviewMethod
|
||||
@@ -2213,14 +2388,14 @@ try:
|
||||
taesd = TAESD(None, taesd_decoder_path, latent_channels=latent_format.latent_channels).to(device)
|
||||
previewer = TAESDPreviewerImpl(taesd)
|
||||
else:
|
||||
print("Warning: TAESD previews enabled, but could not find models/vae_approx/{}".format(
|
||||
logging.warning("[Impact Pack] TAESD previews enabled, but could not find models/vae_approx/{}".format(
|
||||
latent_format.taesd_decoder_name))
|
||||
|
||||
if previewer is None:
|
||||
previewer = Latent2RGBPreviewer(latent_format.latent_rgb_factors)
|
||||
return previewer
|
||||
|
||||
except:
|
||||
print(f"#########################################################################")
|
||||
print(f"[ERROR] ComfyUI-Impact-Pack: Please update ComfyUI to the latest version.")
|
||||
print(f"#########################################################################")
|
||||
except Exception:
|
||||
logging.error("#########################################################################")
|
||||
logging.error("[ERROR] ComfyUI-Impact-Pack: Please update ComfyUI to the latest version.")
|
||||
logging.error("#########################################################################")
|
||||
|
||||
@@ -14,4 +14,4 @@ detection_labels = [
|
||||
"tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven",
|
||||
"toaster", "sink", "refrigerator", "book", "clock", "vase", "scissors", "teddy bear",
|
||||
"hair drier", "toothbrush"
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
import impact.core as core
|
||||
from nodes import MAX_RESOLUTION
|
||||
import impact.segs_nodes as segs_nodes
|
||||
@@ -163,7 +165,7 @@ class SegmDetectorCombined:
|
||||
mask = segm_detector.detect_combined(image, threshold, dilation)
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((image.shape[2], image.shape[1]), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
|
||||
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
@@ -183,7 +185,7 @@ class BboxDetectorCombined(SegmDetectorCombined):
|
||||
mask = bbox_detector.detect_combined(image, threshold, dilation)
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((image.shape[2], image.shape[1]), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
|
||||
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
@@ -298,6 +300,68 @@ class SimpleDetectorForEachPipe:
|
||||
sam_mask_hint_threshold, post_dilation=post_dilation, sam_model_opt=sam_model_opt, segm_detector_opt=segm_detector_opt,
|
||||
detailer_hook=detailer_hook)
|
||||
|
||||
class SAM2VideoDetectorSEGS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image_frames": ("IMAGE", ),
|
||||
|
||||
"bbox_detector": ("BBOX_DETECTOR", ),
|
||||
"sam2_model": ("SAM_MODEL", ),
|
||||
|
||||
"bbox_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"sam2_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
|
||||
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
||||
"drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Detector"
|
||||
|
||||
@staticmethod
|
||||
def doit(bbox_detector, sam2_model, image_frames, bbox_threshold, sam2_threshold, crop_factor, drop_size):
|
||||
if not isinstance(sam2_model, core.SAM2Wrapper):
|
||||
logging.error("[Impact Pack] To use the SAM2VideoDetectorSEGS node, a SAM2 model must be provided as input to `sam2_model`.")
|
||||
raise Exception("To use the SAM2VideoDetectorSEGS node, a SAM2 model must be provided as input to `sam2_model`.")
|
||||
|
||||
segs = bbox_detector.detect(image_frames[0].unsqueeze(0), bbox_threshold, 0, 0, drop_size)
|
||||
segs_masks = sam2_model.predict_video_segs(image_frames, segs)
|
||||
|
||||
def get_whole_merged_mask(all_masks):
|
||||
merged_mask = (all_masks[0] * 255).to(torch.uint8)
|
||||
for mask in all_masks[1:]:
|
||||
merged_mask |= (mask * 255).to(torch.uint8)
|
||||
|
||||
merged_mask = (merged_mask / 255.0).to(torch.float32)
|
||||
merged_mask = utils.to_binary_mask(merged_mask, 0.1)[0]
|
||||
return merged_mask
|
||||
|
||||
new_segs = []
|
||||
for k, v in segs_masks.items():
|
||||
v = v.squeeze(3)
|
||||
m = get_whole_merged_mask(v)
|
||||
seg = segs_nodes.MaskToSEGS.doit(m, False, crop_factor, False, drop_size, contour_fill=True)[0][1]
|
||||
|
||||
if len(seg) == 0:
|
||||
continue
|
||||
|
||||
seg = seg[0]
|
||||
|
||||
x1, y1, x2, y2 = seg.crop_region
|
||||
masks = []
|
||||
for mask in v:
|
||||
masks.append(mask[y1:y2, x1:x2])
|
||||
cropped_mask = torch.stack(masks)
|
||||
cropped_mask = (cropped_mask >= (sam2_threshold*100-50)).to(torch.uint8).cpu()
|
||||
new_seg = SEG(seg.cropped_image, cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
return ((segs[0], new_segs), )
|
||||
|
||||
|
||||
class SimpleDetectorForAnimateDiff:
|
||||
@classmethod
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import comfy
|
||||
import re
|
||||
from impact.utils import *
|
||||
from impact import utils
|
||||
|
||||
|
||||
hf_transformer_model_urls = [
|
||||
"rizvandwiki/gender-classification-2",
|
||||
@@ -138,10 +139,10 @@ class SEGS_Classify:
|
||||
cropped_image = seg.cropped_image
|
||||
elif ref_image_opt is not None:
|
||||
# take from original image
|
||||
cropped_image = crop_image(ref_image_opt, seg.crop_region)
|
||||
cropped_image = utils.crop_image(ref_image_opt, seg.crop_region)
|
||||
|
||||
if cropped_image is not None:
|
||||
cropped_image = to_pil(cropped_image)
|
||||
cropped_image = utils.to_pil(cropped_image)
|
||||
res = classifier(cropped_image)
|
||||
classified.append((seg, res))
|
||||
|
||||
|
||||
@@ -83,3 +83,24 @@ class PreviewDetailerHookProvider:
|
||||
def doit(self, quality, unique_id):
|
||||
hook = hooks.PreviewDetailerHook(unique_id, quality)
|
||||
return hook, hook
|
||||
|
||||
|
||||
class LamaRemoverDetailerHookProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"mask_threshold":("INT", {"default": 250, "min": 0, "max": 255, "step": 1, "display": "slider"}),
|
||||
"gaussblur_radius": ("INT", {"default": 8, "min": 0, "max": 20, "step": 1, "display": "slider"}),
|
||||
"skip_sampling": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DETAILER_HOOK", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, mask_threshold, gaussblur_radius, skip_sampling):
|
||||
hook = hooks.LamaRemoverDetailerHook(mask_threshold, gaussblur_radius, skip_sampling)
|
||||
return (hook, )
|
||||
|
||||
@@ -10,6 +10,7 @@ import folder_paths
|
||||
import os
|
||||
from comfy_extras import nodes_custom_sampler
|
||||
import math
|
||||
import logging
|
||||
|
||||
|
||||
class PixelKSampleHook:
|
||||
@@ -25,7 +26,7 @@ class PixelKSampleHook:
|
||||
def post_decode(self, pixels):
|
||||
return pixels
|
||||
|
||||
def post_upscale(self, pixels):
|
||||
def post_upscale(self, pixels, mask=None):
|
||||
return pixels
|
||||
|
||||
def post_encode(self, samples):
|
||||
@@ -64,8 +65,8 @@ class PixelKSampleHookCombine(PixelKSampleHook):
|
||||
def post_decode(self, pixels):
|
||||
return self.hook2.post_decode(self.hook1.post_decode(pixels))
|
||||
|
||||
def post_upscale(self, pixels):
|
||||
return self.hook2.post_upscale(self.hook1.post_upscale(pixels))
|
||||
def post_upscale(self, pixels, mask=None):
|
||||
return self.hook2.post_upscale(self.hook1.post_upscale(pixels, mask), mask)
|
||||
|
||||
def post_encode(self, samples):
|
||||
return self.hook2.post_encode(self.hook1.post_encode(samples))
|
||||
@@ -109,6 +110,15 @@ class DetailerHookCombine(PixelKSampleHookCombine):
|
||||
noise_2nd, is_touched = self.hook2.get_custom_noise(seed, noise, is_touched)
|
||||
return noise, is_touched
|
||||
|
||||
def get_custom_sampler(self):
|
||||
if self.hook1.get_custom_sampler() is not None:
|
||||
return self.hook1.get_custom_sampler()
|
||||
else:
|
||||
return self.hook2.get_custom_sampler()
|
||||
|
||||
def get_skip_sampling(self):
|
||||
return self.hook1.get_skip_sampling() and self.hook2.get_skip_sampling()
|
||||
|
||||
|
||||
class SimpleCfgScheduleHook(PixelKSampleHook):
|
||||
target_cfg = 0
|
||||
@@ -173,6 +183,21 @@ class DetailerHook(PixelKSampleHook):
|
||||
def get_custom_noise(self, seed, noise, is_touched):
|
||||
return noise, is_touched
|
||||
|
||||
def get_custom_sampler(self):
|
||||
return None
|
||||
|
||||
def get_skip_sampling(self):
|
||||
return False
|
||||
|
||||
|
||||
class CustomSamplerDetailerHookProvider(DetailerHook):
|
||||
def __init__(self, sampler):
|
||||
super().__init__()
|
||||
self.sampler = sampler
|
||||
|
||||
def get_custom_sampler(self):
|
||||
return self.sampler
|
||||
|
||||
|
||||
# class CustomNoiseDetailerHookProvider(DetailerHook):
|
||||
# def __init__(self, noise):
|
||||
@@ -315,7 +340,7 @@ class InjectNoiseHook(PixelKSampleHook):
|
||||
|
||||
strength = self.start_strength + (self.end_strength - self.start_strength) * cur_step / self.total_step
|
||||
samples = InjectNoise().inject_noise(samples, strength, noise, mask)[0]
|
||||
print(f"[Impact Pack] InjectNoiseHook: strength = {strength}")
|
||||
logging.info(f"[Impact Pack] InjectNoiseHook: strength = {strength}")
|
||||
|
||||
if mask is not None:
|
||||
samples['noise_mask'] = mask
|
||||
@@ -346,7 +371,7 @@ class UnsamplerHook(PixelKSampleHook):
|
||||
end_at_step = self.start_end_at_step + (self.end_end_at_step - self.start_end_at_step) * cur_step / self.total_step
|
||||
end_at_step = int(end_at_step)
|
||||
|
||||
print(f"[Impact Pack] UnsamplerHook: end_at_step = {end_at_step}")
|
||||
logging.info(f"[Impact Pack] UnsamplerHook: end_at_step = {end_at_step}")
|
||||
|
||||
# inj noise
|
||||
mask = None
|
||||
@@ -486,6 +511,27 @@ class SEGSLabelFilterDetailerHook(DetailerHook):
|
||||
return segs_nodes.SEGSLabelFilter().doit(segs, "", self.labels)[0]
|
||||
|
||||
|
||||
class LamaRemoverDetailerHook(DetailerHook):
|
||||
def __init__(self, mask_threshold, gaussblur_radius, skip_sampling):
|
||||
super().__init__()
|
||||
self.mask_threshold = mask_threshold
|
||||
self.gaussblur_radius = gaussblur_radius
|
||||
self.skip_sampling = skip_sampling
|
||||
|
||||
def post_upscale(self, img, mask=None):
|
||||
if "LamaRemover" in nodes.NODE_CLASS_MAPPINGS:
|
||||
lama_remover_obj = nodes.NODE_CLASS_MAPPINGS['LamaRemover']()
|
||||
else:
|
||||
utils.try_install_custom_node('https://github.com/Layer-norm/comfyui-lama-remover',
|
||||
"To use 'LAMARemoverDetailerHookProvider', 'comfyui-lama-remover' nodepack is required.")
|
||||
raise Exception("'LamaRemover' node is not installed.")
|
||||
|
||||
return lama_remover_obj.lama_remover(img, masks=mask, mask_threshold=self.mask_threshold, gaussblur_radius=self.gaussblur_radius, invert_mask=False)[0]
|
||||
|
||||
def get_skip_sampling(self):
|
||||
return self.skip_sampling
|
||||
|
||||
|
||||
class PreviewDetailerHook(DetailerHook):
|
||||
def __init__(self, node_id, quality):
|
||||
super().__init__()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import impact.additional_dependencies
|
||||
from impact.utils import *
|
||||
import numpy as np
|
||||
from impact import utils
|
||||
import logging
|
||||
|
||||
impact.additional_dependencies.ensure_onnx_package()
|
||||
|
||||
@@ -8,7 +10,7 @@ try:
|
||||
|
||||
def onnx_inference(image, onnx_model):
|
||||
# prepare image
|
||||
pil = tensor2pil(image)
|
||||
pil = utils.tensor2pil(image)
|
||||
image = np.ascontiguousarray(pil)
|
||||
image = image[:, :, ::-1] # to BGR image
|
||||
image = image.astype(np.float32)
|
||||
@@ -33,6 +35,5 @@ try:
|
||||
boxes = boxes[0][:idx].astype(np.uint32)
|
||||
|
||||
return labels, scores, boxes
|
||||
except Exception as e:
|
||||
print("[ERROR] ComfyUI-Impact-Pack: 'onnxruntime' package doesn't support 'python 3.11', yet.")
|
||||
print(f"\t{e}")
|
||||
except Exception:
|
||||
logging.error("[Impact Pack] ComfyUI-Impact-Pack: 'onnxruntime' package doesn't support 'python 3.11', yet.\t{e}")
|
||||
@@ -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
|
||||
@@ -28,12 +27,18 @@ import base64
|
||||
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.")
|
||||
|
||||
|
||||
@@ -43,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
|
||||
@@ -88,16 +90,32 @@ 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')
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (models + ['ESAM'], {"tooltip": "The detection accuracy varies depending on the SAM model. ESAM can only be used if ComfyUI-YoloWorld-EfficientSAM is installed."}),
|
||||
"model_name": (models, {"tooltip": "The detection accuracy varies depending on the SAM model. ESAM can only be used if ComfyUI-YoloWorld-EfficientSAM is installed."}),
|
||||
"device_mode": (["AUTO", "Prefer GPU", "CPU"], {"tooltip": "AUTO: Only applicable when a GPU is available. It temporarily loads the SAM_MODEL into VRAM only when the detection function is used.\n"
|
||||
"Prefer GPU: Tries to keep the SAM_MODEL on the GPU whenever possible. This can be used when there is sufficient VRAM available.\n"
|
||||
"CPU: Always loads only on the CPU."}),
|
||||
@@ -114,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.")
|
||||
|
||||
@@ -128,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)
|
||||
|
||||
@@ -153,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, )
|
||||
|
||||
|
||||
@@ -191,7 +218,7 @@ class DetailerForEach:
|
||||
return {"required": {
|
||||
"image": ("IMAGE", ),
|
||||
"segs": ("SEGS", ),
|
||||
"model": ("MODEL",),
|
||||
"model": ("MODEL", {"tooltip": "If the `ImpactDummyInput` is connected to the model, the inference stage is skipped."}),
|
||||
"clip": ("CLIP",),
|
||||
"vae": ("VAE",),
|
||||
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
@@ -217,6 +244,8 @@ class DetailerForEach:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +254,8 @@ class DetailerForEach:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "It enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size."
|
||||
|
||||
@staticmethod
|
||||
def get_core_module():
|
||||
return core
|
||||
@@ -233,7 +264,7 @@ class DetailerForEach:
|
||||
def do_detail(image, segs, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, denoise, feather, noise_mask, force_inpaint, wildcard_opt=None, detailer_hook=None,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -269,18 +300,18 @@ class DetailerForEach:
|
||||
else:
|
||||
ordered_segs = segs[1]
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
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:
|
||||
@@ -297,13 +328,16 @@ class DetailerForEach:
|
||||
|
||||
seg_seed = seed + i if seg_seed is None else seg_seed
|
||||
|
||||
cropped_positive = [
|
||||
[condition, {
|
||||
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
||||
for k, v in details.items()
|
||||
}]
|
||||
for condition, details in positive
|
||||
]
|
||||
if not isinstance(positive, str):
|
||||
cropped_positive = [
|
||||
[condition, {
|
||||
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
||||
for k, v in details.items()
|
||||
}]
|
||||
for condition, details in positive
|
||||
]
|
||||
else:
|
||||
cropped_positive = positive
|
||||
|
||||
if not isinstance(negative, str):
|
||||
cropped_negative = [
|
||||
@@ -324,39 +358,44 @@ class DetailerForEach:
|
||||
break
|
||||
|
||||
orig_cropped_image = cropped_image.clone()
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size,
|
||||
seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
wildcard_opt=wildcard_item, wildcard_opt_concat_mode=wildcard_concat_mode,
|
||||
detailer_hook=detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func=scheduler_func_opt)
|
||||
if not (isinstance(model, str) and model == "DUMMY"):
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size,
|
||||
seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
wildcard_opt=wildcard_item, wildcard_opt_concat_mode=wildcard_concat_mode,
|
||||
detailer_hook=detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func=scheduler_func_opt, vae_tiled_encode=tiled_encode,
|
||||
vae_tiled_decode=tiled_decode)
|
||||
else:
|
||||
enhanced_image = cropped_image
|
||||
cnet_pils = None
|
||||
|
||||
if cnet_pils is not None:
|
||||
cnet_pil_list.extend(cnet_pils)
|
||||
|
||||
if not (enhanced_image is None):
|
||||
if enhanced_image is not None:
|
||||
# don't latent composite-> converting to latent caused poor quality
|
||||
# use image paste
|
||||
image = image.cpu()
|
||||
enhanced_image = enhanced_image.cpu()
|
||||
tensor_paste(image, enhanced_image, (seg.crop_region[0], seg.crop_region[1]), mask) # this code affecting to `cropped_image`.
|
||||
utils.tensor_paste(image, enhanced_image, (seg.crop_region[0], seg.crop_region[1]), mask) # this code affecting to `cropped_image`.
|
||||
enhanced_list.append(enhanced_image)
|
||||
|
||||
if detailer_hook is not None:
|
||||
image = detailer_hook.post_paste(image)
|
||||
|
||||
if not (enhanced_image is None):
|
||||
if enhanced_image is not None:
|
||||
# Convert enhanced_pil_alpha to RGBA mode
|
||||
enhanced_image_alpha = tensor_convert_rgba(enhanced_image)
|
||||
enhanced_image_alpha = utils.tensor_convert_rgba(enhanced_image)
|
||||
new_seg_image = enhanced_image.numpy() # alpha should not be applied to seg_image
|
||||
|
||||
# Apply the mask
|
||||
mask = tensor_resize(mask, *tensor_get_size(enhanced_image))
|
||||
tensor_putalpha(enhanced_image_alpha, mask)
|
||||
mask = utils.tensor_resize(mask, *utils.tensor_get_size(enhanced_image))
|
||||
utils.tensor_putalpha(enhanced_image_alpha, mask)
|
||||
enhanced_alpha_list.append(enhanced_image_alpha)
|
||||
else:
|
||||
new_seg_image = None
|
||||
@@ -366,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)
|
||||
@@ -376,13 +415,15 @@ class DetailerForEach:
|
||||
|
||||
def doit(self, 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, cycle=1,
|
||||
detailer_hook=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
detailer_hook=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None,
|
||||
tiled_encode=False, tiled_decode=False):
|
||||
|
||||
enhanced_img, *_ = \
|
||||
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, scheduler_func_opt=scheduler_func_opt)
|
||||
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, )
|
||||
|
||||
@@ -405,7 +446,7 @@ class DetailerForEachPipe:
|
||||
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
||||
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"basic_pipe": ("BASIC_PIPE", ),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
|
||||
@@ -417,6 +458,8 @@ class DetailerForEachPipe:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,10 +470,13 @@ class DetailerForEachPipe:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = DetailerForEach.DESCRIPTION
|
||||
|
||||
def doit(self, image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, noise_mask, force_inpaint, basic_pipe, wildcard,
|
||||
refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None,
|
||||
tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -448,11 +494,12 @@ class DetailerForEachPipe:
|
||||
force_inpaint, wildcard, detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
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(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
|
||||
|
||||
@@ -462,7 +509,7 @@ class FaceDetailer:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE", ),
|
||||
"model": ("MODEL",),
|
||||
"model": ("MODEL", {"tooltip": "If the `ImpactDummyInput` is connected to the model, the inference stage is skipped."}),
|
||||
"clip": ("CLIP",),
|
||||
"vae": ("VAE",),
|
||||
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
@@ -505,6 +552,8 @@ class FaceDetailer:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK", "DETAILER_PIPE", "IMAGE")
|
||||
@@ -514,6 +563,8 @@ class FaceDetailer:
|
||||
|
||||
CATEGORY = "ImpactPack/Simple"
|
||||
|
||||
DESCRIPTION = "This node enhances details by automatically detecting specific objects in the input image using detection models (bbox, segm, sam) and regenerating the image by enlarging the detected area based on the guide size.\nAlthough this node is specialized to simplify the commonly used facial detail enhancement workflow, it can also be used for various automatic inpainting purposes depending on the detection model."
|
||||
|
||||
@staticmethod
|
||||
def enhance_face(image, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, denoise, feather, noise_mask, force_inpaint,
|
||||
@@ -522,7 +573,7 @@ class FaceDetailer:
|
||||
sam_mask_hint_use_negative, drop_size,
|
||||
bbox_detector, segm_detector=None, sam_model_opt=None, wildcard_opt=None, detailer_hook=None,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, cycle=1,
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
# make default prompt as 'face' if empty prompt for CLIPSeg
|
||||
bbox_detector.setAux('face')
|
||||
@@ -554,7 +605,8 @@ class FaceDetailer:
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
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)
|
||||
else:
|
||||
enhanced_img = image
|
||||
cropped_enhanced = []
|
||||
@@ -565,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
|
||||
|
||||
@@ -580,7 +632,8 @@ class FaceDetailer:
|
||||
bbox_threshold, bbox_dilation, bbox_crop_factor,
|
||||
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold,
|
||||
sam_mask_hint_use_negative, drop_size, bbox_detector, wildcard, cycle=1,
|
||||
sam_model_opt=None, segm_detector_opt=None, detailer_hook=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
sam_model_opt=None, segm_detector_opt=None, detailer_hook=None, inpaint_model=False, noise_mask_feather=0,
|
||||
scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
result_img = None
|
||||
result_mask = None
|
||||
@@ -589,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(
|
||||
@@ -598,7 +651,8 @@ class FaceDetailer:
|
||||
bbox_threshold, bbox_dilation, bbox_crop_factor,
|
||||
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold,
|
||||
sam_mask_hint_use_negative, drop_size, bbox_detector, segm_detector_opt, sam_model_opt, wildcard, detailer_hook,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
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)
|
||||
|
||||
result_img = torch.cat((result_img, enhanced_img), dim=0) if result_img is not None else enhanced_img
|
||||
result_mask = torch.cat((result_mask, mask), dim=0) if result_mask is not None else mask
|
||||
@@ -618,7 +672,7 @@ class LatentPixelScale:
|
||||
return {"required": {
|
||||
"samples": ("LATENT", ),
|
||||
"scale_method": (s.upscale_methods,),
|
||||
"scale_factor": ("FLOAT", {"default": 1.5, "min": 0.1, "max": 10000, "step": 0.1}),
|
||||
"scale_factor": ("FLOAT", {"default": 1.5, "min": 0.1, "max": 10000, "step": 0.05}),
|
||||
"vae": ("VAE", ),
|
||||
"use_tiled_vae": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
},
|
||||
@@ -665,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:
|
||||
@@ -738,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
|
||||
|
||||
|
||||
@@ -779,6 +830,26 @@ class CoreMLDetailerHookProvider:
|
||||
return (hook, )
|
||||
|
||||
|
||||
class CustomSamplerDetailerHookProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"sampler": ("SAMPLER", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DETAILER_HOOK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "Apply a hook that allows you to use a custom sampler in the Detailer nodes. When using `DetailerHookCombine`, the sampler from the first hook is applied."
|
||||
|
||||
def doit(self, sampler):
|
||||
hook = hooks.CustomSamplerDetailerHookProvider(sampler)
|
||||
return (hook, )
|
||||
|
||||
|
||||
class CfgScheduleHookProvider:
|
||||
schedules = ["simple"]
|
||||
|
||||
@@ -837,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:
|
||||
@@ -869,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:
|
||||
@@ -986,6 +1053,7 @@ class PixelTiledKSampleUpscalerProvider:
|
||||
"pk_hook_opt": ("PK_HOOK", ),
|
||||
"tile_cnet_opt": ("CONTROL_NET", ),
|
||||
"tile_cnet_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -995,11 +1063,11 @@ class PixelTiledKSampleUpscalerProvider:
|
||||
CATEGORY = "ImpactPack/Upscale"
|
||||
|
||||
def doit(self, scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, tile_width, tile_height, tiling_strategy, upscale_model_opt=None,
|
||||
pk_hook_opt=None, tile_cnet_opt=None, tile_cnet_strength=1.0):
|
||||
pk_hook_opt=None, tile_cnet_opt=None, tile_cnet_strength=1.0, overlap=64):
|
||||
if "BNK_TiledKSampler" in nodes.NODE_CLASS_MAPPINGS:
|
||||
upscaler = core.PixelTiledKSampleUpscaler(scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise,
|
||||
tile_width, tile_height, tiling_strategy, upscale_model_opt, pk_hook_opt, tile_cnet_opt,
|
||||
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength)
|
||||
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength, overlap=overlap)
|
||||
return (upscaler, )
|
||||
else:
|
||||
utils.try_install_custom_node('https://github.com/BlenderNeko/ComfyUI_TiledKSampler',
|
||||
@@ -1048,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:
|
||||
@@ -1212,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,
|
||||
@@ -1266,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:
|
||||
@@ -1276,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)
|
||||
|
||||
@@ -1312,7 +1381,11 @@ class IterativeImageUpscale:
|
||||
|
||||
core.update_node_status(unique_id, "VAEEncode (first)", 0)
|
||||
if upscaler.is_tiled:
|
||||
latent = nodes.VAEEncodeTiled().encode(vae, pixels, upscaler.tile_size)[0]
|
||||
encoder = nodes.VAEEncodeTiled()
|
||||
if 'overlap' in inspect.signature(encoder.encode).parameters:
|
||||
latent = encoder.encode(vae, pixels, upscaler.tile_size, overlap=upscaler.overlap)[0]
|
||||
else:
|
||||
latent = encoder.encode(vae, pixels, upscaler.tile_size)[0]
|
||||
else:
|
||||
latent = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
|
||||
@@ -1334,7 +1407,7 @@ class FaceDetailerPipe:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE", ),
|
||||
"detailer_pipe": ("DETAILER_PIPE",),
|
||||
"detailer_pipe": ("DETAILER_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the detailer_pipe, the inference stage is skipped."}),
|
||||
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}),
|
||||
"max_size": ("FLOAT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
@@ -1368,6 +1441,8 @@ class FaceDetailerPipe:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1378,11 +1453,14 @@ class FaceDetailerPipe:
|
||||
|
||||
CATEGORY = "ImpactPack/Simple"
|
||||
|
||||
DESCRIPTION = FaceDetailer.DESCRIPTION
|
||||
|
||||
def doit(self, image, detailer_pipe, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, noise_mask, force_inpaint, bbox_threshold, bbox_dilation, bbox_crop_factor,
|
||||
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion,
|
||||
sam_mask_hint_threshold, sam_mask_hint_use_negative, drop_size, refiner_ratio=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None,
|
||||
tiled_encode=False, tiled_decode=False):
|
||||
|
||||
result_img = None
|
||||
result_mask = None
|
||||
@@ -1391,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
|
||||
@@ -1405,7 +1483,8 @@ class FaceDetailerPipe:
|
||||
sam_mask_hint_use_negative, drop_size, bbox_detector, segm_detector, sam_model_opt, wildcard, detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
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)
|
||||
|
||||
result_img = torch.cat((result_img, enhanced_img), dim=0) if result_img is not None else enhanced_img
|
||||
result_mask = torch.cat((result_mask, mask), dim=0) if result_mask is not None else mask
|
||||
@@ -1414,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
|
||||
|
||||
@@ -1471,6 +1550,8 @@ class MaskDetailerPipe:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = ""
|
||||
|
||||
def doit(self, image, mask, basic_pipe, guide_size, guide_size_for, max_size, mask_mode,
|
||||
seed, steps, cfg, sampler_name, scheduler, denoise,
|
||||
feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1,
|
||||
@@ -1489,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]), [])
|
||||
@@ -1520,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
|
||||
|
||||
@@ -1539,7 +1620,7 @@ class DetailerForEachTest(DetailerForEach):
|
||||
|
||||
def doit(self, 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=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -1548,20 +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, scheduler_func_opt=scheduler_func_opt)
|
||||
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
|
||||
|
||||
@@ -1575,9 +1657,12 @@ class DetailerForEachTestPipe(DetailerForEachPipe):
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = DetailerForEach.DESCRIPTION
|
||||
|
||||
def doit(self, image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, noise_mask, force_inpaint, basic_pipe, wildcard, cycle=1,
|
||||
refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None, inpaint_model=False, noise_mask_feather=0,
|
||||
scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -1596,20 +1681,21 @@ class DetailerForEachTestPipe(DetailerForEachPipe):
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
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, new_segs, basic_pipe, cropped, cropped_enhanced, cropped_enhanced_alpha, cnet_pil_list
|
||||
|
||||
@@ -1669,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)
|
||||
|
||||
@@ -1692,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), )
|
||||
|
||||
|
||||
@@ -1711,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,)
|
||||
|
||||
|
||||
@@ -1749,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,)
|
||||
|
||||
|
||||
@@ -1768,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,)
|
||||
|
||||
|
||||
@@ -1787,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
|
||||
@@ -1848,7 +1931,7 @@ class MaskRectArea:
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
FUNCTION = "create_mask"
|
||||
|
||||
@@ -1856,7 +1939,7 @@ class MaskRectArea:
|
||||
# search for node
|
||||
node_found = False
|
||||
for node in extra_pnginfo["workflow"]["nodes"]:
|
||||
if node["id"] == int(unique_id):
|
||||
if str(node["id"]) == unique_id:
|
||||
min_x = node["properties"].get("x", 0) / 100
|
||||
min_y = node["properties"].get("y", 0) / 100
|
||||
width = node["properties"].get("w", 0) / 100
|
||||
@@ -1864,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))
|
||||
@@ -1913,7 +1996,7 @@ class MaskRectAreaAdvanced:
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
FUNCTION = "create_mask_advanced"
|
||||
|
||||
@@ -1931,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}.")
|
||||
|
||||
@@ -1997,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)
|
||||
|
||||
@@ -2232,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
|
||||
|
||||
@@ -2309,7 +2392,11 @@ class ImpactWildcardProcessor:
|
||||
return {"required": {
|
||||
"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 'ImpactWildcardProcessor' is what is shown here. The behavior varies slightly depending on the mode. Wildcard syntax can also be used in 'populated_text'."}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed", "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.\nFixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode."}),
|
||||
"mode": (["populate", "fixed", "reproduce"], {"default": "populate", "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."
|
||||
}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Determines the random seed to be used for wildcard processing."}),
|
||||
"Select to add Wildcard": (["Select the Wildcard to add to the text"],),
|
||||
},
|
||||
@@ -2318,9 +2405,10 @@ class ImpactWildcardProcessor:
|
||||
CATEGORY = "ImpactPack/Prompt"
|
||||
|
||||
DESCRIPTION = ("The 'ImpactWildcardProcessor' processes text prompts written in wildcard syntax and outputs the processed text prompt.\n\n"
|
||||
"TIP: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'Fixed'.")
|
||||
"TIP: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'fixed'.")
|
||||
|
||||
RETURN_TYPES = ("STRING", )
|
||||
RETURN_NAMES = ("processed text",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
@staticmethod
|
||||
@@ -2340,8 +2428,10 @@ 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": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed", "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."}),
|
||||
"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."}),
|
||||
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"), ),
|
||||
"Select to add Wildcard": (["Select the Wildcard to add to the text"], ),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Determines the random seed to be used for wildcard processing."}),
|
||||
@@ -2351,7 +2441,7 @@ class ImpactWildcardEncode:
|
||||
CATEGORY = "ImpactPack/Prompt"
|
||||
|
||||
DESCRIPTION = ("The 'ImpactWildcardEncode' node processes text prompts written in wildcard syntax and outputs them as conditioning. It also supports LoRA syntax, with the applied LoRA reflected in the model's output.\n\n"
|
||||
"TIP1: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'Fixed'.\n"
|
||||
"TIP1: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'fixed'.\n"
|
||||
"TIP2: If the 'Inspire Pack' is installed, LBW(LoRA Block Weight) syntax can also be applied.")
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP", "CONDITIONING", "STRING")
|
||||
@@ -2378,7 +2468,7 @@ class ImpactSchedulerAdapter:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"defaultInput": True, }),
|
||||
"extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]'],),
|
||||
"extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan', 'OSS Chroma'],),
|
||||
}}
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
import nodes
|
||||
from comfy.k_diffusion import sampling as k_diffusion_sampling
|
||||
from comfy import samplers
|
||||
@@ -12,8 +14,8 @@ import comfy.model_management as mm
|
||||
try:
|
||||
from comfy_extras.nodes_custom_sampler import Noise_EmptyNoise, Noise_RandomNoise
|
||||
import node_helpers
|
||||
except:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
except Exception:
|
||||
logging.warning("\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
@@ -29,6 +31,8 @@ def calculate_sigmas(model, sampler, scheduler, steps):
|
||||
sigmas = nodes.NODE_CLASS_MAPPINGS['GITSScheduler']().get_sigmas(float(scheduler[11:-1]), steps, denoise=1.0)[0]
|
||||
elif scheduler == 'LTXV[default]':
|
||||
sigmas = nodes.NODE_CLASS_MAPPINGS['LTXVScheduler']().get_sigmas(20, 2.05, 0.95, True, 0.1)[0]
|
||||
elif scheduler.startswith('OSS'):
|
||||
sigmas = nodes.NODE_CLASS_MAPPINGS['OptimalStepsScheduler']().get_sigmas(scheduler[4:], steps, denoise=1.0)[0]
|
||||
else:
|
||||
sigmas = samplers.calculate_sigmas(model.get_model_object("model_sampling"), scheduler, steps)
|
||||
|
||||
@@ -46,65 +50,27 @@ def get_noise_sampler(x, cpu, total_sigmas, **kwargs):
|
||||
|
||||
|
||||
def ksampler(sampler_name, total_sigmas, extra_options={}, inpaint_options={}):
|
||||
if sampler_name == "dpmpp_sde":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
if sampler_name in ["dpmpp_sde", "dpmpp_sde_gpu", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu"]:
|
||||
if sampler_name == "dpmpp_sde":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_sde
|
||||
elif sampler_name == "dpmpp_sde_gpu":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_sde_gpu
|
||||
elif sampler_name == "dpmpp_2m_sde":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_2m_sde
|
||||
elif sampler_name == "dpmpp_2m_sde_gpu":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_2m_sde_gpu
|
||||
elif sampler_name == "dpmpp_3m_sde":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_3m_sde
|
||||
elif sampler_name == "dpmpp_3m_sde_gpu":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_3m_sde_gpu
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_sde(model, x, sigmas, **kwargs)
|
||||
def sampler_function_wrapper(model, x, sigmas, **kwargs):
|
||||
if 'noise_sampler' not in kwargs:
|
||||
kwargs['noise_sampler'] = get_noise_sampler(x, 'gpu' not in sampler_name, total_sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
return orig_sampler_function(model, x, sigmas, **kwargs)
|
||||
|
||||
elif sampler_name == "dpmpp_sde_gpu":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, False, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_sde_gpu(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_2m_sde":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_2m_sde(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_2m_sde_gpu":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, False, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_2m_sde_gpu(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_3m_sde":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_3m_sde(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_3m_sde_gpu":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, False, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_3m_sde_gpu(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
sampler_function = sampler_function_wrapper
|
||||
|
||||
else:
|
||||
return comfy.samplers.sampler_object(sampler_name)
|
||||
@@ -212,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:
|
||||
@@ -230,7 +196,7 @@ def impact_sample(model, seed, steps, cfg, sampler_name, scheduler, positive, ne
|
||||
|
||||
|
||||
def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0, noise=None, scheduler_func=None):
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0, noise=None, scheduler_func=None, sampler_opt=None):
|
||||
|
||||
if refiner_ratio is None or refiner_model is None or refiner_clip is None or refiner_positive is None or refiner_negative is None:
|
||||
# Use separated_sample instead of KSampler for `AYS scheduler`
|
||||
@@ -242,7 +208,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
|
||||
refined_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, False,
|
||||
sigma_ratio=sigma_factor, noise=noise, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, noise=noise, scheduler_func=scheduler_func)
|
||||
else:
|
||||
advanced_steps = math.floor(steps / denoise)
|
||||
start_at_step = advanced_steps - steps
|
||||
@@ -251,7 +217,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
# print(f"pre: {start_at_step} .. {end_at_step} / {advanced_steps}")
|
||||
temp_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, True,
|
||||
sigma_ratio=sigma_factor, noise=noise, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, noise=noise, scheduler_func=scheduler_func)
|
||||
|
||||
if 'noise_mask' in latent_image:
|
||||
# noise_latent = \
|
||||
@@ -265,7 +231,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
# print(f"post: {end_at_step} .. {advanced_steps + 1} / {advanced_steps}")
|
||||
refined_latent = separated_sample(refiner_model, False, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
refiner_positive, refiner_negative, temp_latent, end_at_step, advanced_steps + 1, False,
|
||||
sigma_ratio=sigma_factor, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, scheduler_func=scheduler_func)
|
||||
|
||||
return refined_latent
|
||||
|
||||
@@ -311,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
|
||||
@@ -335,7 +301,7 @@ class KSamplerAdvancedWrapper:
|
||||
sigma_ratio=recovery_sigma_ratio * sigma_factor, sampler_opt=self.sampler_opt, scheduler_func=self.scheduler_func)
|
||||
except ValueError as e:
|
||||
if str(e) == 'sigma_min and sigma_max must not be 0':
|
||||
print(f"\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
logging.warning("\nWARN: sampling skipped - sigma_min and sigma_max are 0")
|
||||
|
||||
return latent_image
|
||||
|
||||
|
||||
@@ -12,16 +12,17 @@ import torchvision
|
||||
import impact.core as core
|
||||
import impact.impact_pack as impact_pack
|
||||
from impact.utils import to_tensor
|
||||
import impact.utils as utils
|
||||
from segment_anything import SamPredictor, sam_model_registry
|
||||
import numpy as np
|
||||
import nodes
|
||||
from PIL import Image
|
||||
import io
|
||||
import impact.wildcards as wildcards
|
||||
import comfy
|
||||
from io import BytesIO
|
||||
import random
|
||||
from server import PromptServer
|
||||
import logging
|
||||
|
||||
|
||||
sam_predictor = None
|
||||
@@ -77,9 +78,13 @@ async def sam_prepare(request):
|
||||
if data['sam_model_name'] == 'auto':
|
||||
model_name = impact.config.get_config()['sam_editor_model']
|
||||
|
||||
model_name = os.path.join(impact_pack.model_path, "sams", model_name)
|
||||
model_path = folder_paths.get_full_path("sams", model_name)
|
||||
|
||||
print(f"[INFO] ComfyUI-Impact-Pack: Loading SAM model '{impact_pack.model_path}'")
|
||||
if model_path is None:
|
||||
logging.error(f"[Impact Pack] The '{model_name}' model file cannot be found in any sams model path.")
|
||||
return web.Response(status=400)
|
||||
|
||||
logging.info(f"[Impact Pack] Loading SAM model '{model_path}'")
|
||||
|
||||
filename, image_dir = folder_paths.annotated_filepath(data["filename"])
|
||||
|
||||
@@ -92,10 +97,10 @@ async def sam_prepare(request):
|
||||
if image_dir is None:
|
||||
return web.Response(status=400)
|
||||
|
||||
thread = threading.Thread(target=async_prepare_sam, args=(image_dir, model_name, filename,))
|
||||
thread = threading.Thread(target=async_prepare_sam, args=(image_dir, model_path, filename,))
|
||||
thread.start()
|
||||
|
||||
print(f"[INFO] ComfyUI-Impact-Pack: SAM model loaded. ")
|
||||
logging.info("[Impact Pack] SAM model loaded. ")
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@@ -104,10 +109,11 @@ async def release_sam(request):
|
||||
global sam_predictor
|
||||
|
||||
with sam_lock:
|
||||
del sam_predictor
|
||||
temp = sam_predictor
|
||||
del temp
|
||||
sam_predictor = None
|
||||
|
||||
print(f"[INFO] ComfyUI-Impact-Pack: unloading SAM model")
|
||||
logging.info("[Impact Pack]: unloading SAM model")
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/sam/detect")
|
||||
@@ -140,7 +146,7 @@ async def sam_detect(request):
|
||||
plabs.append(0)
|
||||
|
||||
detected_masks = core.sam_predict(sam_predictor, points, plabs, None, threshold)
|
||||
mask = core.combine_masks2(detected_masks)
|
||||
mask = utils.combine_masks2(detected_masks)
|
||||
|
||||
if mask is None:
|
||||
return web.Response(status=400)
|
||||
@@ -178,7 +184,7 @@ async def wildcards_list(request):
|
||||
@PromptServer.instance.routes.post("/impact/wildcards")
|
||||
async def populate_wildcards(request):
|
||||
data = await request.json()
|
||||
populated = wildcards.process(data['text'], data.get('seed', None))
|
||||
populated = impact.wildcards.process(data['text'], data.get('seed', None))
|
||||
return web.json_response({"text": populated})
|
||||
|
||||
|
||||
@@ -234,7 +240,7 @@ async def view_validate(request):
|
||||
|
||||
|
||||
@PromptServer.instance.routes.get("/impact/validate/pb_id_image")
|
||||
async def view_validate(request):
|
||||
async def view_pb_id_image(request):
|
||||
if "id" in request.rel_url.query:
|
||||
pb_id = request.rel_url.query["id"]
|
||||
|
||||
@@ -304,7 +310,7 @@ async def view_previewbridge_image(request):
|
||||
if pb_id in core.preview_bridge_image_id_map:
|
||||
file = core.preview_bridge_image_id_map[pb_id]
|
||||
|
||||
with Image.open(file) as img:
|
||||
with Image.open(file):
|
||||
filename = os.path.basename(file)
|
||||
return web.FileResponse(file, headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
|
||||
@@ -324,20 +330,24 @@ def onprompt_for_switch(json_data):
|
||||
|
||||
cls = v['class_type']
|
||||
if cls == 'ImpactInversedSwitch':
|
||||
# if 'sel_mode' is 'select_on_prompt'
|
||||
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
|
||||
select_input = v['inputs']['select']
|
||||
# if 'select' is converted input
|
||||
if isinstance(select_input, list) and len(select_input) == 2:
|
||||
input_node = json_data['prompt'][select_input[0]]
|
||||
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
|
||||
inversed_switch_info[k] = input_node['inputs']['value']
|
||||
else:
|
||||
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactInversedSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
logging.warning(f"\n##### ##### #####\n[Impact Pack] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactInversedSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
else:
|
||||
inversed_switch_info[k] = select_input
|
||||
|
||||
elif cls in ['ImpactSwitch', 'LatentSwitch', 'SEGSSwitch', 'ImpactMakeImageList']:
|
||||
# if 'sel_mode' is 'select_on_prompt'
|
||||
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
|
||||
select_input = v['inputs']['select']
|
||||
# if 'select' is converted input
|
||||
if isinstance(select_input, list) and len(select_input) == 2:
|
||||
input_node = json_data['prompt'][select_input[0]]
|
||||
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
|
||||
@@ -346,7 +356,7 @@ def onprompt_for_switch(json_data):
|
||||
if isinstance(input_node['inputs']['select'], int):
|
||||
onprompt_switch_info[k] = input_node['inputs']['select']
|
||||
else:
|
||||
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
logging.warning(f"\n##### ##### #####\n[Impact Pack] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
else:
|
||||
onprompt_switch_info[k] = select_input
|
||||
|
||||
@@ -364,7 +374,7 @@ def onprompt_for_switch(json_data):
|
||||
if 'BOOLEAN' == input_node['inputs']['typ']:
|
||||
try:
|
||||
onprompt_cond_branch_info[k] = input_node['inputs']['value'].lower() == "true"
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
onprompt_cond_branch_info[k] = cond_input
|
||||
@@ -377,6 +387,8 @@ def onprompt_for_switch(json_data):
|
||||
if vv[0] in inversed_switch_info:
|
||||
if vv[1] + 1 != inversed_switch_info[vv[0]]:
|
||||
disable_targets.add(kk)
|
||||
else:
|
||||
del inversed_switch_info[k]
|
||||
|
||||
if vv[0] in disabled_switch:
|
||||
disable_targets.add(kk)
|
||||
@@ -396,6 +408,11 @@ def onprompt_for_switch(json_data):
|
||||
for kk in disable_targets:
|
||||
del v['inputs'][kk]
|
||||
|
||||
# inversed_switch - select out of range
|
||||
for target in inversed_switch_info.keys():
|
||||
del json_data['prompt'][target]['inputs']['input']
|
||||
|
||||
|
||||
def onprompt_for_pickers(json_data):
|
||||
detected_pickers = set()
|
||||
|
||||
@@ -466,7 +483,17 @@ def onprompt_populate_wildcards(json_data):
|
||||
for k, v in prompt.items():
|
||||
if 'class_type' in v and (v['class_type'] == 'ImpactWildcardEncode' or v['class_type'] == 'ImpactWildcardProcessor'):
|
||||
inputs = v['inputs']
|
||||
if inputs['mode'] and isinstance(inputs['populated_text'], str):
|
||||
|
||||
# legacy adapter
|
||||
if isinstance(inputs['mode'], bool):
|
||||
if inputs['mode']:
|
||||
new_mode = 'populate'
|
||||
else:
|
||||
new_mode = 'fixed'
|
||||
|
||||
inputs['mode'] = new_mode
|
||||
|
||||
if inputs['mode'] == 'populate' and isinstance(inputs['populated_text'], str):
|
||||
if isinstance(inputs['seed'], list):
|
||||
try:
|
||||
input_node = prompt[inputs['seed'][0]]
|
||||
@@ -479,25 +506,30 @@ def onprompt_populate_wildcards(json_data):
|
||||
if not isinstance(input_seed, int):
|
||||
continue
|
||||
else:
|
||||
print(f"[Impact Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
logging.info(f"[Impact Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
continue
|
||||
except:
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
input_seed = int(inputs['seed'])
|
||||
|
||||
inputs['populated_text'] = wildcards.process(inputs['wildcard_text'], input_seed)
|
||||
inputs['mode'] = False
|
||||
inputs['populated_text'] = impact.wildcards.process(inputs['wildcard_text'], input_seed)
|
||||
inputs['mode'] = 'reproduce'
|
||||
|
||||
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'})
|
||||
|
||||
|
||||
|
||||
if 'extra_data' in json_data and 'extra_pnginfo' in json_data['extra_data']:
|
||||
for node in json_data['extra_data']['extra_pnginfo']['workflow']['nodes']:
|
||||
key = str(node['id'])
|
||||
if key in updated_widget_values:
|
||||
node['widgets_values'][1] = updated_widget_values[key]
|
||||
node['widgets_values'][2] = False
|
||||
node['widgets_values'][2] = 'reproduce'
|
||||
|
||||
|
||||
def onprompt_for_remote(json_data):
|
||||
@@ -542,7 +574,7 @@ def onprompt(json_data):
|
||||
regional_sampler_seed_update(json_data)
|
||||
core.current_prompt = json_data
|
||||
except Exception as e:
|
||||
print(f"[WARN] ComfyUI-Impact-Pack: Error on prompt - several features will not work.\n{e}")
|
||||
logging.warning(f"[Impact Pack] ComfyUI-Impact-Pack: Error on prompt - several features will not work.\n{e}")
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
import folder_paths
|
||||
|
||||
import impact.mmdet_nodes as mmdet_nodes
|
||||
from impact.utils import *
|
||||
from impact.core import SEG
|
||||
import impact.core as core
|
||||
import nodes
|
||||
|
||||
class NO_BBOX_MODEL:
|
||||
pass
|
||||
|
||||
|
||||
class NO_SEGM_MODEL:
|
||||
pass
|
||||
|
||||
|
||||
class MMDetLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
bboxs = ["bbox/"+x for x in folder_paths.get_filename_list("mmdets_bbox")]
|
||||
segms = ["segm/"+x for x in folder_paths.get_filename_list("mmdets_segm")]
|
||||
return {"required": {"model_name": (bboxs + segms, )}}
|
||||
RETURN_TYPES = ("BBOX_MODEL", "SEGM_MODEL")
|
||||
FUNCTION = "load_mmdet"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def load_mmdet(self, model_name):
|
||||
mmdet_path = folder_paths.get_full_path("mmdets", model_name)
|
||||
model = mmdet_nodes.load_mmdet(mmdet_path)
|
||||
|
||||
if model_name.startswith("bbox"):
|
||||
return model, NO_SEGM_MODEL()
|
||||
else:
|
||||
return NO_BBOX_MODEL(), model
|
||||
|
||||
|
||||
class BboxDetectorForEach:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"bbox_model": ("BBOX_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 10, "min": 0, "max": 255, "step": 1}),
|
||||
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
@staticmethod
|
||||
def detect(bbox_model, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
mmdet_results = mmdet_nodes.inference_bbox(bbox_model, image, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
y1, x1, y2, x2 = item_bbox
|
||||
|
||||
if x2 - x1 > drop_size and y2 - y1 > drop_size:
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
# bbox_size = (item_bbox[2]-item_bbox[0],item_bbox[3]-item_bbox[1]) # (w,h)
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
items.append(item)
|
||||
|
||||
shape = h, w
|
||||
return shape, items
|
||||
|
||||
def doit(self, bbox_model, image, threshold, dilation, crop_factor):
|
||||
return (BboxDetectorForEach.detect(bbox_model, image, threshold, dilation, crop_factor), )
|
||||
|
||||
|
||||
class SegmDetectorCombined:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segm_model": ("SEGM_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def doit(self, segm_model, image, threshold, dilation):
|
||||
mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
mask = combine_masks(segmasks)
|
||||
return (mask,)
|
||||
|
||||
|
||||
class BboxDetectorCombined(SegmDetectorCombined):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"bbox_model": ("BBOX_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 4, "min": 0, "max": 255, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
def doit(self, bbox_model, image, threshold, dilation):
|
||||
mmdet_results = mmdet_nodes.inference_bbox(bbox_model, image, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
mask = combine_masks(segmasks)
|
||||
return (mask,)
|
||||
|
||||
|
||||
class SegmDetectorForEach:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segm_model": ("SEGM_MODEL", ),
|
||||
"image": ("IMAGE", ),
|
||||
"threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"dilation": ("INT", {"default": 10, "min": 0, "max": 255, "step": 1}),
|
||||
"crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 100, "step": 0.1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def doit(self, segm_model, image, threshold, dilation, crop_factor):
|
||||
mmdet_results = mmdet_nodes.inference_segm(image, segm_model, threshold)
|
||||
segmasks = core.create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
items.append(item)
|
||||
|
||||
shape = h,w
|
||||
return ((shape, items), )
|
||||
|
||||
|
||||
class SegsMaskCombine:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
"image": ("IMAGE", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
@staticmethod
|
||||
def combine(segs, image):
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
|
||||
mask = np.zeros((h, w), dtype=np.uint8)
|
||||
|
||||
for seg in segs[1]:
|
||||
cropped_mask = seg.cropped_mask
|
||||
crop_region = seg.crop_region
|
||||
mask[crop_region[1]:crop_region[3], crop_region[0]:crop_region[2]] |= (cropped_mask * 255).astype(np.uint8)
|
||||
|
||||
return torch.from_numpy(mask.astype(np.float32) / 255.0)
|
||||
|
||||
def doit(self, segs, image):
|
||||
return (SegsMaskCombine.combine(segs, image), )
|
||||
|
||||
|
||||
class MaskPainter(nodes.PreviewImage):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"images": ("IMAGE",), },
|
||||
"hidden": {
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
"optional": {"mask_image": ("IMAGE_PATH",), },
|
||||
"optional": {"image": (["#placeholder"], )},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
FUNCTION = "save_painted_images"
|
||||
|
||||
CATEGORY = "ImpactPack/Legacy"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
def save_painted_images(self, images, filename_prefix="impact-mask",
|
||||
prompt=None, extra_pnginfo=None, mask_image=None, image=None):
|
||||
if image == "#placeholder" or image['image_hash'] != id(images):
|
||||
# new input image
|
||||
res = self.save_images(images, filename_prefix, prompt, extra_pnginfo)
|
||||
|
||||
item = res['ui']['images'][0]
|
||||
|
||||
if not item['filename'].endswith(']'):
|
||||
filepath = f"{item['filename']} [{item['type']}]"
|
||||
else:
|
||||
filepath = item['filename']
|
||||
|
||||
_, mask = nodes.LoadImage().load_image(filepath)
|
||||
|
||||
res['ui']['aux'] = [id(images), res['ui']['images']]
|
||||
res['result'] = (mask, )
|
||||
|
||||
return res
|
||||
|
||||
else:
|
||||
# new mask
|
||||
if '0' in image: # fallback
|
||||
image = image['0']
|
||||
|
||||
forward = {'filename': image['forward_filename'],
|
||||
'subfolder': image['forward_subfolder'],
|
||||
'type': image['forward_type'], }
|
||||
|
||||
res = {'ui': {'images': [forward]}}
|
||||
|
||||
imgpath = ""
|
||||
if 'subfolder' in image and image['subfolder'] != "":
|
||||
imgpath = image['subfolder'] + "/"
|
||||
|
||||
imgpath += f"{image['filename']}"
|
||||
|
||||
if 'type' in image and image['type'] != "":
|
||||
imgpath += f" [{image['type']}]"
|
||||
|
||||
res['ui']['aux'] = [id(images), [forward]]
|
||||
_, mask = nodes.LoadImage().load_image(imgpath)
|
||||
res['result'] = (mask, )
|
||||
|
||||
return res
|
||||
@@ -8,7 +8,8 @@ from impact.utils import any_typ
|
||||
import impact.core as core
|
||||
import re
|
||||
import nodes
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
|
||||
class ImpactCompare:
|
||||
@classmethod
|
||||
@@ -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:
|
||||
@@ -574,27 +574,6 @@ class ImpactSleep:
|
||||
return (signal,)
|
||||
|
||||
|
||||
error_skip_flag = False
|
||||
try:
|
||||
import cm_global
|
||||
def filter_message(str):
|
||||
global error_skip_flag
|
||||
|
||||
if "IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE" in str:
|
||||
return True
|
||||
elif error_skip_flag and "ERROR:root:!!! Exception during processing !!!\n" == str:
|
||||
error_skip_flag = False
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
cm_global.try_call(api='cm.register_message_collapse', f=filter_message)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[WARN] ComfyUI-Impact-Pack: `ComfyUI` or `ComfyUI-Manager` is an outdated version.")
|
||||
pass
|
||||
|
||||
|
||||
def workflow_to_map(workflow):
|
||||
nodes = {}
|
||||
links = {}
|
||||
@@ -675,8 +654,8 @@ class ImpactControlBridge:
|
||||
# so extra_pnginfo is useless in here
|
||||
try:
|
||||
workflow = core.current_prompt['extra_data']['extra_pnginfo']['workflow']
|
||||
except:
|
||||
print(f"[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']")
|
||||
except Exception:
|
||||
logging.info("[Impact Pack] core.current_prompt['extra_data']['extra_pnginfo']['workflow']")
|
||||
return 0
|
||||
|
||||
nodes, links = workflow_to_map(workflow)
|
||||
@@ -694,13 +673,16 @@ 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:
|
||||
return (value, )
|
||||
else:
|
||||
return (ExecutionBlocker(None), )
|
||||
elif extra_pnginfo is None:
|
||||
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'])
|
||||
|
||||
@@ -731,7 +713,7 @@ class ImpactControlBridge:
|
||||
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'actives': list(should_be_active_nodes)})
|
||||
nodes.interrupt_processing()
|
||||
|
||||
elif behavior == "Mute" or behavior == True:
|
||||
elif behavior == "Mute" or behavior == True: # noqa: E712
|
||||
# mute
|
||||
should_be_mute_nodes = active_nodes + bypass_nodes
|
||||
if len(should_be_mute_nodes) > 0:
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
import folder_paths
|
||||
from impact.core import *
|
||||
import os
|
||||
|
||||
import mmcv
|
||||
from mmdet.apis import (inference_detector, init_detector)
|
||||
from mmdet.evaluation import get_classes
|
||||
|
||||
|
||||
def load_mmdet(model_path):
|
||||
model_config = os.path.splitext(model_path)[0] + ".py"
|
||||
model = init_detector(model_config, model_path, device="cpu")
|
||||
return model
|
||||
|
||||
|
||||
def inference_segm_old(model, image, conf_threshold):
|
||||
image = image.numpy()[0] * 255
|
||||
mmdet_results = inference_detector(model, image)
|
||||
|
||||
bbox_results, segm_results = mmdet_results
|
||||
label = "A"
|
||||
|
||||
classes = get_classes("coco")
|
||||
labels = [
|
||||
np.full(bbox.shape[0], i, dtype=np.int32)
|
||||
for i, bbox in enumerate(bbox_results)
|
||||
]
|
||||
n, m = bbox_results[0].shape
|
||||
if n == 0:
|
||||
return [[], [], []]
|
||||
labels = np.concatenate(labels)
|
||||
bboxes = np.vstack(bbox_results)
|
||||
segms = mmcv.concat_list(segm_results)
|
||||
filter_idxs = np.where(bboxes[:, -1] > conf_threshold)[0]
|
||||
results = [[], [], []]
|
||||
for i in filter_idxs:
|
||||
results[0].append(label + "-" + classes[labels[i]])
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def inference_segm(image, modelname, conf_thres, lab="A"):
|
||||
image = image.numpy()[0] * 255
|
||||
mmdet_results = inference_detector(modelname, image).pred_instances
|
||||
bboxes = mmdet_results.bboxes.numpy()
|
||||
segms = mmdet_results.masks.numpy()
|
||||
scores = mmdet_results.scores.numpy()
|
||||
|
||||
classes = get_classes("coco")
|
||||
|
||||
n, m = bboxes.shape
|
||||
if n == 0:
|
||||
return [[], [], [], []]
|
||||
labels = mmdet_results.labels
|
||||
filter_inds = np.where(mmdet_results.scores > conf_thres)[0]
|
||||
results = [[], [], [], []]
|
||||
for i in filter_inds:
|
||||
results[0].append(lab + "-" + classes[labels[i]])
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
results[3].append(scores[i])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def inference_bbox(modelname, image, conf_threshold):
|
||||
image = image.numpy()[0] * 255
|
||||
label = "A"
|
||||
output = inference_detector(modelname, image).pred_instances
|
||||
cv2_image = np.array(image)
|
||||
cv2_image = cv2_image[:, :, ::-1].copy()
|
||||
cv2_gray = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2GRAY)
|
||||
|
||||
segms = []
|
||||
for x0, y0, x1, y1 in output.bboxes:
|
||||
cv2_mask = np.zeros(cv2_gray.shape, np.uint8)
|
||||
cv2.rectangle(cv2_mask, (int(x0), int(y0)), (int(x1), int(y1)), 255, -1)
|
||||
cv2_mask_bool = cv2_mask.astype(bool)
|
||||
segms.append(cv2_mask_bool)
|
||||
|
||||
n, m = output.bboxes.shape
|
||||
if n == 0:
|
||||
return [[], [], [], []]
|
||||
|
||||
bboxes = output.bboxes.numpy()
|
||||
scores = output.scores.numpy()
|
||||
filter_idxs = np.where(scores > conf_threshold)[0]
|
||||
results = [[], [], [], []]
|
||||
for i in filter_idxs:
|
||||
results[0].append(label)
|
||||
results[1].append(bboxes[i])
|
||||
results[2].append(segms[i])
|
||||
results[3].append(scores[i])
|
||||
|
||||
return results
|
||||
|
||||
|
||||
class BBoxDetector:
|
||||
bbox_model = None
|
||||
|
||||
def __init__(self, bbox_model):
|
||||
self.bbox_model = bbox_model
|
||||
|
||||
def detect(self, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
drop_size = max(drop_size, 1)
|
||||
mmdet_results = inference_bbox(self.bbox_model, image, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
y1, x1, y2, x2 = item_bbox
|
||||
|
||||
if x2 - x1 > drop_size and y2 - y1 > drop_size: # minimum dimension must be (2,2) to avoid squeeze issue
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
# bbox_size = (item_bbox[2]-item_bbox[0],item_bbox[3]-item_bbox[1]) # (w,h)
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
|
||||
items.append(item)
|
||||
|
||||
shape = image.shape[1], image.shape[2]
|
||||
return shape, items
|
||||
|
||||
def detect_combined(self, image, threshold, dilation):
|
||||
mmdet_results = inference_bbox(self.bbox_model, image, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
return combine_masks(segmasks)
|
||||
|
||||
def setAux(self, x):
|
||||
pass
|
||||
|
||||
|
||||
class SegmDetector(BBoxDetector):
|
||||
segm_model = None
|
||||
|
||||
def __init__(self, segm_model):
|
||||
self.segm_model = segm_model
|
||||
|
||||
def detect(self, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
drop_size = max(drop_size, 1)
|
||||
mmdet_results = inference_segm(image, self.segm_model, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
items = []
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
for x in segmasks:
|
||||
item_bbox = x[0]
|
||||
item_mask = x[1]
|
||||
|
||||
y1, x1, y2, x2 = item_bbox
|
||||
|
||||
if x2 - x1 > drop_size and y2 - y1 > drop_size: # minimum dimension must be (2,2) to avoid squeeze issue
|
||||
crop_region = make_crop_region(w, h, item_bbox, crop_factor)
|
||||
cropped_image = crop_image(image, crop_region)
|
||||
cropped_mask = crop_ndarray2(item_mask, crop_region)
|
||||
confidence = x[2]
|
||||
|
||||
item = SEG(cropped_image, cropped_mask, confidence, crop_region, item_bbox, None, None)
|
||||
items.append(item)
|
||||
|
||||
segs = image.shape, items
|
||||
|
||||
if detailer_hook is not None and hasattr(detailer_hook, "post_detection"):
|
||||
segs = detailer_hook.post_detection(segs)
|
||||
|
||||
return segs
|
||||
|
||||
def detect_combined(self, image, threshold, dilation):
|
||||
mmdet_results = inference_bbox(self.bbox_model, image, threshold)
|
||||
segmasks = create_segmasks(mmdet_results)
|
||||
if dilation > 0:
|
||||
segmasks = dilate_masks(segmasks, dilation)
|
||||
|
||||
return combine_masks(segmasks)
|
||||
|
||||
def setAux(self, x):
|
||||
pass
|
||||
|
||||
|
||||
class MMDetDetectorProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
bboxs = ["bbox/"+x for x in folder_paths.get_filename_list("mmdets_bbox")]
|
||||
segms = ["segm/"+x for x in folder_paths.get_filename_list("mmdets_segm")]
|
||||
return {"required": {"model_name": (bboxs + segms, )}}
|
||||
RETURN_TYPES = ("BBOX_DETECTOR", "SEGM_DETECTOR")
|
||||
FUNCTION = "load_mmdet"
|
||||
|
||||
CATEGORY = "ImpactPack"
|
||||
|
||||
def load_mmdet(self, model_name):
|
||||
mmdet_path = folder_paths.get_full_path("mmdets", model_name)
|
||||
model = load_mmdet(mmdet_path)
|
||||
|
||||
if model_name.startswith("bbox"):
|
||||
return BBoxDetector(model), NO_SEGM_DETECTOR()
|
||||
else:
|
||||
return NO_BBOX_DETECTOR(), model
|
||||
@@ -1,5 +1,4 @@
|
||||
import folder_paths
|
||||
import impact.wildcards
|
||||
from impact.utils import any_typ
|
||||
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import comfy.sample
|
||||
import traceback
|
||||
|
||||
original_sample = comfy.sample.sample
|
||||
|
||||
|
||||
def informative_sample(*args, **kwargs):
|
||||
try:
|
||||
return original_sample(*args, **kwargs) # This code helps interpret error messages that occur within exceptions but does not have any impact on other operations.
|
||||
except RuntimeError as e:
|
||||
is_model_mix_issue = False
|
||||
try:
|
||||
if 'mat1 and mat2 shapes cannot be multiplied' in e.args[0]:
|
||||
if 'torch.nn.functional.linear' in traceback.format_exc().strip().split('\n')[-3]:
|
||||
is_model_mix_issue = True
|
||||
except:
|
||||
pass
|
||||
|
||||
if is_model_mix_issue:
|
||||
raise RuntimeError("\n\n#### It seems that models and clips are mixed and interconnected between SDXL Base, SDXL Refiner, SD1.x, and SD2.x. Please verify. ####\n\n")
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
comfy.sample.sample = informative_sample
|
||||
@@ -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,12 +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.")
|
||||
|
||||
|
||||
@@ -38,7 +45,7 @@ class SEGSDetailer:
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"basic_pipe": ("BASIC_PIPE",),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 100}),
|
||||
|
||||
@@ -60,6 +67,8 @@ class SEGSDetailer:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size.\nThis node is applied specifically to SEGS rather than the entire image. To apply it to the entire image, use the 'SEGS Paste' node."
|
||||
|
||||
@staticmethod
|
||||
def do_detail(image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, noise_mask, force_inpaint, basic_pipe, refiner_ratio=None, batch_size=1, cycle=1,
|
||||
@@ -76,19 +85,19 @@ class SEGSDetailer:
|
||||
new_segs = []
|
||||
cnet_pil_list = []
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
for i in range(batch_size):
|
||||
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
|
||||
|
||||
@@ -113,13 +122,17 @@ class SEGSDetailer:
|
||||
for condition, details in negative
|
||||
]
|
||||
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
control_net_wrapper=seg.control_net_wrapper, cycle=cycle,
|
||||
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
if not (isinstance(model, str) and model == "DUMMY"):
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
control_net_wrapper=seg.control_net_wrapper, cycle=cycle,
|
||||
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
else:
|
||||
enhanced_image = cropped_image
|
||||
cnet_pils = None
|
||||
|
||||
if cnet_pils is not None:
|
||||
cnet_pil_list.extend(cnet_pils)
|
||||
@@ -129,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
|
||||
@@ -148,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
|
||||
|
||||
@@ -170,6 +183,8 @@ class SEGSPaste:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node provides a function to paste the enhanced SEGS, improved through the SEGS detailer, back onto the original image."
|
||||
|
||||
@staticmethod
|
||||
def doit(image, segs, feather, alpha=255, ref_image_opt=None):
|
||||
|
||||
@@ -188,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:]:
|
||||
@@ -204,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
|
||||
@@ -255,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))
|
||||
@@ -363,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):
|
||||
@@ -473,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 = []
|
||||
|
||||
@@ -496,7 +511,7 @@ class SEGSOrderedFilter:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
"target": (["area(=w*h)", "width", "height", "x1", "y1", "x2", "y2", "confidence"],),
|
||||
"target": (["area(=w*h)", "width", "height", "x1", "y1", "x2", "y2", "confidence", "none"],),
|
||||
"order": ("BOOLEAN", {"default": True, "label_on": "descending", "label_off": "ascending"}),
|
||||
"take_start": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
"take_count": ("INT", {"default": 1, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
@@ -509,51 +524,35 @@ class SEGSOrderedFilter:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
if target == "area(=w*h)": return (x2 - x1) * (y2 - y1)
|
||||
if target == "width": return x2 - x1
|
||||
if target == "height": return y2 - y1
|
||||
if target == "x1": return x1
|
||||
if target == "y1": return y1
|
||||
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):
|
||||
segs_with_order = []
|
||||
sort_key_fn = SEGSOrderedFilter.get_sort_key_fn(target)
|
||||
|
||||
for seg in segs[1]:
|
||||
x1 = seg.crop_region[0]
|
||||
y1 = seg.crop_region[1]
|
||||
x2 = seg.crop_region[2]
|
||||
y2 = seg.crop_region[3]
|
||||
sorted_list = list(segs[1]) # make a shallow copy, so it does not mutate the original list when sort
|
||||
if sort_key_fn is not None:
|
||||
sorted_list.sort(key=sort_key_fn, reverse=order)
|
||||
|
||||
if target == "area(=w*h)":
|
||||
value = (y2 - y1) * (x2 - x1)
|
||||
elif target == "width":
|
||||
value = x2 - x1
|
||||
elif target == "height":
|
||||
value = y2 - y1
|
||||
elif target == "x1":
|
||||
value = x1
|
||||
elif target == "x2":
|
||||
value = x2
|
||||
elif target == "y1":
|
||||
value = y1
|
||||
elif target == "y2":
|
||||
value = y2
|
||||
elif target == "confidence":
|
||||
value = seg.confidence
|
||||
else:
|
||||
raise Exception(f"[Impact Pack] SEGSOrderedFilter - Unexpected target '{target}'")
|
||||
|
||||
segs_with_order.append((value, seg))
|
||||
|
||||
if order:
|
||||
sorted_list = sorted(segs_with_order, key=lambda x: x[0], reverse=True)
|
||||
else:
|
||||
sorted_list = sorted(segs_with_order, key=lambda x: x[0], reverse=False)
|
||||
|
||||
result_list = []
|
||||
remained_list = []
|
||||
|
||||
for i, item in enumerate(sorted_list):
|
||||
if take_start <= i < take_start + take_count:
|
||||
result_list.append(item[1])
|
||||
else:
|
||||
remained_list.append(item[1])
|
||||
|
||||
return (segs[0], result_list), (segs[0], remained_list),
|
||||
take_stop = take_start + take_count
|
||||
return (segs[0], sorted_list[take_start:take_stop]), \
|
||||
(segs[0], sorted_list[:take_start] + sorted_list[take_stop:]),
|
||||
|
||||
|
||||
class SEGSRangeFilter:
|
||||
@@ -590,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":
|
||||
@@ -609,18 +607,123 @@ 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),
|
||||
|
||||
|
||||
class SEGSIntersectionFilter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs1": ("SEGS", ),
|
||||
"segs2": ("SEGS", ),
|
||||
"ioa_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
RETURN_NAMES = ("filtered_SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
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()
|
||||
|
||||
return inter_area / area1 if area1 > 0 else 0
|
||||
|
||||
def doit(self, segs1, segs2, ioa_threshold):
|
||||
"""Remove segments from segs1 if their IoA with any segment in segs2 exceeds the threshold."""
|
||||
# Extract bounding boxes for all segments in segs1 and segs2
|
||||
keep = []
|
||||
|
||||
# Iterate over all segments in segs1
|
||||
for idx1, seg1 in enumerate(segs1[1]):
|
||||
keep_segment = True # Assume the segment should be kept
|
||||
mask1 = core.segs_to_combined_mask((segs1[0], [seg1]))
|
||||
|
||||
# Compare with every segment in segs2
|
||||
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
|
||||
|
||||
# Keep the segment if it did not exceed the threshold with any other segment
|
||||
if keep_segment:
|
||||
keep.append(segs1[1][idx1])
|
||||
|
||||
return (segs1[0], keep), # Return the updated SEGS
|
||||
|
||||
|
||||
class SEGSNMSFilter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"segs": ("SEGS",),
|
||||
"iou_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
RETURN_NAMES = ("filtered_SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def compute_iou(self, mask1, mask2):
|
||||
"""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()
|
||||
|
||||
return inter_area / union_area if union_area > 0 else 0
|
||||
|
||||
def doit(self, segs, iou_threshold):
|
||||
"""Perform NMS to filter overlapping segments."""
|
||||
confidences = np.ndarray.flatten(np.array([seg.confidence for seg in segs[1]]))
|
||||
|
||||
# Sort boxes by confidence (high to low)
|
||||
sorted_indices = np.argsort(confidences)[::-1].tolist()
|
||||
keep = []
|
||||
|
||||
while len(sorted_indices) > 0:
|
||||
idx = sorted_indices[0]
|
||||
mask1 = core.segs_to_combined_mask((segs[0], [segs[1][idx]]))
|
||||
keep.append(idx)
|
||||
sorted_indices = sorted_indices[1:]
|
||||
|
||||
# Filter indices only contain the indices where the bbox does not intersect
|
||||
filtered_indices = []
|
||||
for i in sorted_indices:
|
||||
mask2 = core.segs_to_combined_mask((segs[0], [segs[1][i]]))
|
||||
iou = self.compute_iou(mask1, mask2)
|
||||
if iou < iou_threshold:
|
||||
filtered_indices.append(i)
|
||||
|
||||
sorted_indices = np.array(filtered_indices)
|
||||
|
||||
filtered_segs = [segs[1][i] for i in keep]
|
||||
return (segs[0], filtered_segs),
|
||||
|
||||
|
||||
class SEGSToImageList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -646,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,)
|
||||
|
||||
@@ -754,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)
|
||||
@@ -764,7 +867,7 @@ class SEGSMerge:
|
||||
|
||||
seg = SEG(None, cropped_mask, min_confidence, crop_region, bbox, 'merged', None)
|
||||
return ((segs[0], [seg]),)
|
||||
|
||||
|
||||
|
||||
class SEGSConcat:
|
||||
@classmethod
|
||||
@@ -794,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), [])
|
||||
@@ -876,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:
|
||||
@@ -980,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, )
|
||||
@@ -1003,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)
|
||||
@@ -1027,7 +1130,7 @@ class DilateMaskInSEGS:
|
||||
def doit(self, segs, dilation):
|
||||
new_segs = []
|
||||
for seg in segs[1]:
|
||||
mask = core.dilate_mask(seg.cropped_mask, dilation)
|
||||
mask = utils.dilate_mask(seg.cropped_mask, dilation)
|
||||
seg = SEG(seg.cropped_image, mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
new_segs.append(seg)
|
||||
|
||||
@@ -1075,7 +1178,7 @@ class Dilate_SEG_ELT:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, seg, dilation):
|
||||
mask = core.dilate_mask(seg.cropped_mask, dilation)
|
||||
mask = utils.dilate_mask(seg.cropped_mask, dilation)
|
||||
seg = SEG(seg.cropped_image, mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, seg.control_net_wrapper)
|
||||
return (seg,)
|
||||
|
||||
@@ -1244,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, )
|
||||
@@ -1271,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]
|
||||
|
||||
@@ -1323,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 = []
|
||||
@@ -1331,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)
|
||||
@@ -1459,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'],)
|
||||
|
||||
|
||||
@@ -1482,6 +1585,8 @@ class SEGSPicker:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "This node provides a function to select only the chosen SEGS from the input SEGS."
|
||||
|
||||
@staticmethod
|
||||
def doit(picks, segs, fallback_image_opt=None, unique_id=None):
|
||||
if fallback_image_opt is not None:
|
||||
@@ -1494,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
|
||||
@@ -1538,6 +1643,8 @@ class DefaultImageForSEGS:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "If the SEGS have not passed through the detailer, they contain only detection area information without an image. This node sets a default image for the SEGS."
|
||||
|
||||
@staticmethod
|
||||
def doit(segs, image, override):
|
||||
results = []
|
||||
@@ -1558,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
|
||||
@@ -1625,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()
|
||||
@@ -1655,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
|
||||
|
||||
@@ -1663,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:
|
||||
@@ -1681,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))
|
||||
@@ -1729,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)
|
||||
@@ -1838,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
|
||||
@@ -1856,17 +1963,17 @@ class SEGSUpscaler:
|
||||
positive, negative, denoise,
|
||||
noise_mask=cropped_mask, control_net_wrapper=seg.control_net_wrapper,
|
||||
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
if not (enhanced_image is None):
|
||||
if enhanced_image is not None:
|
||||
new_image = new_image.cpu()
|
||||
enhanced_image = enhanced_image.cpu()
|
||||
left = seg.crop_region[0]
|
||||
top = seg.crop_region[1]
|
||||
tensor_paste(new_image, enhanced_image, (left, top), mask)
|
||||
utils.tensor_paste(new_image, enhanced_image, (left, top), mask)
|
||||
|
||||
if upscaler_hook_opt is not None:
|
||||
new_image = upscaler_hook_opt.post_paste(new_image)
|
||||
|
||||
enhanced_img = tensor_convert_rgb(new_image)
|
||||
enhanced_img = utils.tensor_convert_rgb(new_image)
|
||||
|
||||
return (enhanced_img,)
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
from impact.utils import *
|
||||
from impact import impact_sampling
|
||||
from comfy import model_management
|
||||
from comfy.cli_args import args
|
||||
from impact import utils
|
||||
from PIL import Image
|
||||
import nodes
|
||||
import torch
|
||||
import inspect
|
||||
import logging
|
||||
import comfy
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"[Impact Pack] ComfyUI is an outdated version. The DifferentialDiffusion feature will be disabled.")
|
||||
logging.info("[Impact Pack] ComfyUI is an outdated version. The DifferentialDiffusion feature will be disabled.")
|
||||
|
||||
|
||||
# Implementation based on `https://github.com/lingondricka2/Upscaler-Detailer`
|
||||
@@ -19,7 +23,6 @@ def upscale_with_model(upscale_model, image):
|
||||
device = model_management.get_torch_device()
|
||||
upscale_model.to(device)
|
||||
in_img = image.movedim(-1, -3).to(device)
|
||||
free_memory = model_management.get_free_memory(device)
|
||||
|
||||
tile = 512
|
||||
overlap = 32
|
||||
@@ -72,9 +75,9 @@ def upscaler(image, upscale_model, rescale_factor, resampling_method, supersampl
|
||||
else:
|
||||
up_image = image
|
||||
|
||||
pil_img = tensor2pil(image)
|
||||
pil_img = utils.tensor2pil(image)
|
||||
original_width, original_height = pil_img.size
|
||||
scaled_image = pil2tensor(apply_resize_image(tensor2pil(up_image), original_width, original_height, rounding_modulus, 'rescale',
|
||||
scaled_image = utils.pil2tensor(apply_resize_image(utils.tensor2pil(up_image), original_width, original_height, rounding_modulus, 'rescale',
|
||||
supersample, rescale_factor, 1024, resampling_method))
|
||||
return scaled_image
|
||||
|
||||
@@ -92,10 +95,10 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
scale = 8/min(original_image_size[0], original_image_size[1]) + 1
|
||||
w = int(original_image_size[1] * scale)
|
||||
h = int(original_image_size[0] * scale)
|
||||
image = tensor_resize(image, w, h)
|
||||
image = utils.tensor_resize(image, w, h)
|
||||
|
||||
if noise_mask is not None:
|
||||
noise_mask = tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
noise_mask = noise_mask.squeeze(3)
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
@@ -110,10 +113,10 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
if 'noise_mask' in inspect.signature(imc_encode).parameters:
|
||||
positive, negative, latent_image = imc_encode(positive, negative, image, vae, mask=noise_mask, noise_mask=True)
|
||||
else:
|
||||
print(f"[Impact Pack] ComfyUI is an outdated version.")
|
||||
logging.info("[Impact Pack] ComfyUI is an outdated version.")
|
||||
positive, negative, latent_image = imc_encode(positive, negative, image, vae, noise_mask)
|
||||
else:
|
||||
latent_image = to_latent_image(image, vae)
|
||||
latent_image = utils.to_latent_image(image, vae)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
|
||||
@@ -130,7 +133,7 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
|
||||
# Match to original image size
|
||||
if refined_image.shape[1:3] != original_image_size:
|
||||
refined_image = tensor_resize(refined_image, original_image_size[1], original_image_size[0])
|
||||
refined_image = utils.tensor_resize(refined_image, original_image_size[1], original_image_size[0])
|
||||
|
||||
# don't convert to latent - latent break image
|
||||
# preserving pil is much better
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import math
|
||||
import impact.core as core
|
||||
from comfy_extras.nodes_custom_sampler import Noise_RandomNoise
|
||||
from impact.utils import *
|
||||
from nodes import MAX_RESOLUTION
|
||||
import nodes
|
||||
from impact.impact_sampling import KSamplerWrapper, KSamplerAdvancedWrapper, separated_sample, impact_sample
|
||||
import comfy
|
||||
import torch
|
||||
import numpy as np
|
||||
import logging
|
||||
|
||||
|
||||
class TiledKSamplerProvider:
|
||||
@classmethod
|
||||
@@ -239,7 +242,7 @@ class CombineConditionings:
|
||||
res += v
|
||||
|
||||
return (res, )
|
||||
|
||||
|
||||
|
||||
class ConcatConditionings:
|
||||
@classmethod
|
||||
@@ -263,7 +266,7 @@ class ConcatConditionings:
|
||||
for k, conditioning_from in list(kwargs.items())[1:]:
|
||||
out = []
|
||||
if len(conditioning_from) > 1:
|
||||
print("Warning: ConcatConditionings {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
logging.warning("Warning: ConcatConditionings {k} contains more than 1 cond, only the first one will actually be applied to conditioning1.")
|
||||
|
||||
cond_from = conditioning_from[0][0]
|
||||
|
||||
@@ -276,8 +279,8 @@ class ConcatConditionings:
|
||||
conditioning_to = out
|
||||
|
||||
return (out, )
|
||||
|
||||
|
||||
|
||||
|
||||
class RegionalSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -425,7 +428,7 @@ class RegionalSampler:
|
||||
add_noise = False
|
||||
|
||||
# finalize
|
||||
core.update_node_status(unique_id, f"finalize")
|
||||
core.update_node_status(unique_id, "finalize")
|
||||
if base_latent_image is not None:
|
||||
new_latent_image = base_latent_image
|
||||
else:
|
||||
@@ -546,7 +549,7 @@ class RegionalSamplerAdvanced:
|
||||
j += 1
|
||||
|
||||
# finalize
|
||||
core.update_node_status(unique_id, f"finalize")
|
||||
core.update_node_status(unique_id, "finalize")
|
||||
if base_latent_image is not None:
|
||||
new_latent_image = base_latent_image
|
||||
else:
|
||||
|
||||
@@ -9,6 +9,7 @@ import re
|
||||
import impact.core as core
|
||||
from server import PromptServer
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
|
||||
class GeneralSwitch:
|
||||
@@ -50,7 +51,7 @@ class GeneralSwitch:
|
||||
selected_index = int(kwargs['select'])
|
||||
input_name = f"input{selected_index}"
|
||||
|
||||
print(f"SELECTED: {input_name}")
|
||||
logging.info(f"SELECTED: {input_name}")
|
||||
|
||||
if input_name in kwargs:
|
||||
return [input_name]
|
||||
@@ -77,12 +78,12 @@ class GeneralSwitch:
|
||||
|
||||
break
|
||||
else:
|
||||
print(f"[Impact-Pack] The switch node does not guarantee proper functioning in API mode.")
|
||||
logging.info("[Impact-Pack] The switch node does not guarantee proper functioning in API mode.")
|
||||
|
||||
if input_name in kwargs:
|
||||
return kwargs[input_name], selected_label, selected_index
|
||||
else:
|
||||
print(f"ImpactSwitch: invalid select index (ignored)")
|
||||
logging.info("ImpactSwitch: invalid select index (ignored)")
|
||||
return None, "", selected_index
|
||||
|
||||
class LatentSwitch:
|
||||
@@ -108,7 +109,7 @@ class LatentSwitch:
|
||||
if input_name in kwargs:
|
||||
return (kwargs[input_name],)
|
||||
else:
|
||||
print(f"LatentSwitch: invalid select index ('latent1' is selected)")
|
||||
logging.info("LatentSwitch: invalid select index ('latent1' is selected)")
|
||||
return (kwargs['latent1'],)
|
||||
|
||||
|
||||
@@ -176,7 +177,7 @@ class GeneralInversedSwitch:
|
||||
if core.is_execution_model_version_supported():
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
else:
|
||||
print("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
|
||||
logging.warning("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
|
||||
|
||||
res = []
|
||||
|
||||
@@ -264,9 +265,9 @@ class ImpactLogger:
|
||||
if hasattr(data, "shape"):
|
||||
shape = f"{data.shape} / "
|
||||
|
||||
print(f"[IMPACT LOGGER]: {shape}{data}")
|
||||
logging.info(f"[IMPACT LOGGER]: {shape}{data}")
|
||||
|
||||
print(f" PROMPT: {prompt}")
|
||||
logging.info(f" PROMPT: {prompt}")
|
||||
|
||||
# for x in prompt:
|
||||
# if 'inputs' in x and 'populated_text' in x['inputs']:
|
||||
@@ -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, )
|
||||
@@ -446,6 +445,31 @@ class MakeMaskList:
|
||||
return (masks, )
|
||||
|
||||
|
||||
class NthItemOfAnyList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"any_list": (any_typ,),
|
||||
"index": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1, "tooltip": "The index of the item you want to select from the list."}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ,)
|
||||
INPUT_IS_LIST = True
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "Selects the Nth item from a list. If the index is out of range, it returns the last item in the list."
|
||||
|
||||
def doit(self, any_list, index):
|
||||
i = index[0]
|
||||
if i >= len(any_list):
|
||||
return (any_list[-1],)
|
||||
else:
|
||||
return (any_list[i],)
|
||||
|
||||
|
||||
class MakeImageList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -526,6 +550,9 @@ class ReencodeLatent:
|
||||
"output_vae": ("VAE", ),
|
||||
"tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64}),
|
||||
},
|
||||
"optional": {
|
||||
"overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32, "tooltip": "This setting applies when 'tile_mode' is enabled."}),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
@@ -533,14 +560,22 @@ class ReencodeLatent:
|
||||
RETURN_TYPES = ("LATENT", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
def doit(self, samples, tile_mode, input_vae, output_vae, tile_size=512):
|
||||
def doit(self, samples, tile_mode, input_vae, output_vae, tile_size=512, overlap=64):
|
||||
if tile_mode in ["Both", "Decode(input) only"]:
|
||||
pixels = nodes.VAEDecodeTiled().decode(input_vae, samples, tile_size)[0]
|
||||
decoder = nodes.VAEDecodeTiled()
|
||||
if 'overlap' in inspect.signature(decoder.decode).parameters:
|
||||
pixels = decoder.decode(input_vae, samples, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
pixels = decoder.decode(input_vae, samples, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
pixels = nodes.VAEDecode().decode(input_vae, samples)[0]
|
||||
|
||||
if tile_mode in ["Both", "Encode(output) only"]:
|
||||
return nodes.VAEEncodeTiled().encode(output_vae, pixels, tile_size)
|
||||
encoder = nodes.VAEEncodeTiled()
|
||||
if 'overlap' in inspect.signature(encoder.encode).parameters:
|
||||
return encoder.encode(output_vae, pixels, tile_size, overlap=overlap)
|
||||
else:
|
||||
return encoder.encode(output_vae, pixels, tile_size)
|
||||
else:
|
||||
return nodes.VAEEncode().encode(output_vae, pixels)
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import nodes
|
||||
from . import config
|
||||
from PIL import Image
|
||||
import comfy
|
||||
import time
|
||||
import logging
|
||||
|
||||
|
||||
class TensorBatchBuilder:
|
||||
@@ -66,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)
|
||||
@@ -141,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)
|
||||
@@ -177,19 +225,22 @@ def tensor2numpy(image):
|
||||
|
||||
|
||||
def tensor_paste(image1, image2, left_top, mask):
|
||||
"""Mask and image2 has to be the same size"""
|
||||
"""
|
||||
Pastes image2 onto image1 at position left_top using mask.
|
||||
Supports both RGB and RGBA images.
|
||||
"""
|
||||
_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)
|
||||
# raise ValueError(f"Inconsistent size: Image ({image2.shape[1:3]}) != Mask ({mask.shape[1:3]})")
|
||||
|
||||
x, y = left_top
|
||||
_, h1, w1, _ = image1.shape
|
||||
_, h2, w2, _ = image2.shape
|
||||
_, h1, w1, c1 = image1.shape
|
||||
_, h2, w2, c2 = image2.shape
|
||||
|
||||
# calculate image patch size
|
||||
# Calculate image patch size
|
||||
w = min(w1, x + w2) - x
|
||||
h = min(h1, y + h2) - y
|
||||
|
||||
@@ -198,10 +249,47 @@ def tensor_paste(image1, image2, left_top, mask):
|
||||
return
|
||||
|
||||
mask = mask[:, :h, :w, :]
|
||||
image1[:, y:y+h, x:x+w, :] = (
|
||||
(1 - mask) * image1[:, y:y+h, x:x+w, :] +
|
||||
mask * image2[:, :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
|
||||
image1[:, y:y+h, x:x+w, :3] = (
|
||||
(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] = (
|
||||
(1 - mask) * region1[:, :, :, :3] +
|
||||
mask * region2
|
||||
)
|
||||
# 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]
|
||||
image1[:, y:y+h, x:x+w, :] = (
|
||||
(1 - effective_mask) * region1 +
|
||||
effective_mask * region2[:, :, :, :3]
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -501,15 +589,21 @@ def crop_image(image, crop_region):
|
||||
return crop_tensor4(image, crop_region)
|
||||
|
||||
|
||||
def to_latent_image(pixels, vae):
|
||||
def to_latent_image(pixels, vae, vae_tiled_encode=False):
|
||||
x = pixels.shape[1]
|
||||
y = pixels.shape[2]
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
pixels = pixels[:, :x, :y, :]
|
||||
|
||||
vae_encode = nodes.VAEEncode()
|
||||
start = time.time()
|
||||
if vae_tiled_encode:
|
||||
encoded = nodes.VAEEncodeTiled().encode(vae, pixels, 512, overlap=64)[0] # using default settings
|
||||
logging.info(f"[Impact Pack] vae encoded (tiled) in {time.time() - start:.1f}s")
|
||||
else:
|
||||
encoded = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
logging.info(f"[Impact Pack] vae encoded in {time.time() - start:.1f}s")
|
||||
|
||||
return vae_encode.encode(vae, pixels)[0]
|
||||
return encoded
|
||||
|
||||
|
||||
def empty_pil_tensor(w=64, h=64):
|
||||
@@ -592,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 --->
|
||||
|
||||
@@ -8,6 +8,7 @@ import numpy as np
|
||||
import threading
|
||||
from impact import utils
|
||||
from impact import config
|
||||
import logging
|
||||
|
||||
|
||||
wildcards_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "wildcards"))
|
||||
@@ -44,7 +45,9 @@ def read_wildcard(k, v):
|
||||
elif isinstance(v, str):
|
||||
k = wildcard_normalize(k)
|
||||
wildcard_dict[k] = [v]
|
||||
|
||||
elif isinstance(v, (int, float)):
|
||||
k = wildcard_normalize(k)
|
||||
wildcard_dict[k] = [str(v)]
|
||||
|
||||
def read_wildcard_dict(wildcard_path):
|
||||
global wildcard_dict
|
||||
@@ -63,13 +66,13 @@ def read_wildcard_dict(wildcard_path):
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
lines = f.read().splitlines()
|
||||
wildcard_dict[key] = [x for x in lines if not x.strip().startswith('#')]
|
||||
elif file.endswith('.yaml'):
|
||||
elif file.endswith('.yaml') or file.endswith('.yml'):
|
||||
file_path = os.path.join(root, file)
|
||||
|
||||
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)
|
||||
|
||||
@@ -135,7 +138,9 @@ def process(text, seed=None):
|
||||
b = r.group(3)
|
||||
if b is not None:
|
||||
b = b.strip()
|
||||
|
||||
else:
|
||||
b = a
|
||||
|
||||
if r is not None:
|
||||
if b is not None and is_numeric_string(a) and is_numeric_string(b):
|
||||
# PATTERN: num1-num2
|
||||
@@ -145,26 +150,32 @@ def process(text, seed=None):
|
||||
x = int(a)
|
||||
select_range = (x, x)
|
||||
|
||||
# Expand wildcard path or return the string after $$
|
||||
def expand_wildcard_or_return_string(options, pattern, wildcard_pattern):
|
||||
matches = re.findall(wildcard_pattern, pattern)
|
||||
if len(options) == 1 and matches:
|
||||
# $$<single wildcard>
|
||||
return get_wildcard_options(pattern)
|
||||
else:
|
||||
# $$opt1|opt2|...
|
||||
options[0] = pattern
|
||||
return options
|
||||
|
||||
if select_range is not None and len(multi_select_pattern) == 2:
|
||||
# PATTERN: count$$
|
||||
matches = re.findall(wildcard_pattern, multi_select_pattern[1])
|
||||
if len(options) == 1 and matches:
|
||||
# count$$<single wildcard>
|
||||
options = get_wildcard_options(multi_select_pattern[1])
|
||||
else:
|
||||
# count$$opt1|opt2|...
|
||||
options[0] = multi_select_pattern[1]
|
||||
options = expand_wildcard_or_return_string(options, multi_select_pattern[1], wildcard_pattern )
|
||||
elif select_range is not None and len(multi_select_pattern) == 3:
|
||||
# PATTERN: count$$ sep $$
|
||||
select_sep = multi_select_pattern[1]
|
||||
options[0] = multi_select_pattern[2]
|
||||
options = expand_wildcard_or_return_string(options, multi_select_pattern[2], wildcard_pattern )
|
||||
|
||||
adjusted_probabilities = []
|
||||
|
||||
total_prob = 0
|
||||
|
||||
for option in options:
|
||||
parts = option.split('::', 1)
|
||||
parts = option.split('::', 1) if isinstance(option, str) else f"{option}".split('::', 1)
|
||||
|
||||
if len(parts) == 2 and is_numeric_string(parts[0].strip()):
|
||||
config_value = float(parts[0].strip())
|
||||
else:
|
||||
@@ -178,15 +189,30 @@ def process(text, seed=None):
|
||||
if select_range is None:
|
||||
select_count = 1
|
||||
else:
|
||||
select_count = random_gen.integers(low=select_range[0], high=select_range[1]+1, size=1)
|
||||
def calculate_max(_options_length, _max_select_range):
|
||||
return min(_max_select_range + 1, _options_length + 1) if _max_select_range > 0 else _options_length + 1
|
||||
|
||||
if select_count > len(options):
|
||||
def calculate_select_count(_max_value, _min_select_range, random_gen):
|
||||
if max(_max_value, _min_select_range) <= 0:
|
||||
return 0
|
||||
# fix: low >= high
|
||||
elif _max_value == _min_select_range:
|
||||
return _max_value
|
||||
else:
|
||||
# fix: low >= high
|
||||
_low_value = min(_min_select_range, _max_value)
|
||||
_high_value = max(_min_select_range, _max_value)
|
||||
return random_gen.integers(low=_low_value, high=_high_value, size=1)
|
||||
select_count = calculate_select_count(calculate_max(len(options), select_range[1]), select_range[0], random_gen)
|
||||
|
||||
if select_count > len(options) or total_prob <= 1:
|
||||
random_gen.shuffle(options)
|
||||
selected_items = options
|
||||
else:
|
||||
selected_items = random_gen.choice(options, p=normalized_probabilities, size=select_count, replace=False)
|
||||
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', x, 1) for x in selected_items]
|
||||
# x may be numpy.int32, convert to string
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', str(x), count=1) for x in selected_items]
|
||||
replacement = select_sep.join(selected_items2)
|
||||
if '::' in replacement:
|
||||
pass
|
||||
@@ -194,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
|
||||
@@ -237,7 +263,23 @@ def process(text, seed=None):
|
||||
keyword = match.lower()
|
||||
keyword = wildcard_normalize(keyword)
|
||||
if keyword in local_wildcard_dict:
|
||||
replacement = random_gen.choice(local_wildcard_dict[keyword])
|
||||
# look for adjusted probability
|
||||
adjusted_probabilities = []
|
||||
total_prob = 0
|
||||
options=local_wildcard_dict[keyword]
|
||||
for option in options:
|
||||
parts = option.split('::', 1)
|
||||
if len(parts) == 2 and is_numeric_string(parts[0].strip()):
|
||||
config_value = float(parts[0].strip())
|
||||
else:
|
||||
config_value = 1 # Default value if no configuration is provided
|
||||
|
||||
adjusted_probabilities.append(config_value)
|
||||
total_prob += config_value
|
||||
|
||||
normalized_probabilities = [prob / total_prob for prob in adjusted_probabilities]
|
||||
selected_item = random_gen.choice(options, p=normalized_probabilities, replace=False)
|
||||
replacement = re.sub(r'^\s*[0-9.]+::', '', selected_item, count=1)
|
||||
replacements_found = True
|
||||
string = string.replace(f"__{match}__", replacement, 1)
|
||||
elif '*' in keyword:
|
||||
@@ -263,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()
|
||||
@@ -317,6 +359,7 @@ def extract_lora_values(string):
|
||||
lbw = None
|
||||
lbw_a = None
|
||||
lbw_b = None
|
||||
loader = None
|
||||
|
||||
if len(item) > 0:
|
||||
lora = item[0]
|
||||
@@ -335,6 +378,8 @@ def extract_lora_values(string):
|
||||
lbw_b = safe_float(lbw_item[2:].strip())
|
||||
elif lbw_item.strip() != '':
|
||||
lbw = lbw_item
|
||||
elif sub_item.startswith("LOADER="):
|
||||
loader = sub_item[7:]
|
||||
|
||||
if a is None:
|
||||
a = 1.0
|
||||
@@ -342,7 +387,7 @@ def extract_lora_values(string):
|
||||
b = a
|
||||
|
||||
if lora is not None and lora not in added:
|
||||
result.append((lora, a, b, lbw, lbw_a, lbw_b))
|
||||
result.append((lora, a, b, lbw, lbw_a, lbw_b, loader))
|
||||
added.add(lora)
|
||||
|
||||
return result
|
||||
@@ -366,6 +411,8 @@ def resolve_lora_name(lora_name_cache, name):
|
||||
if x.endswith(name):
|
||||
return x
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None, processed=None):
|
||||
"""
|
||||
@@ -386,7 +433,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
loras = extract_lora_values(pass1)
|
||||
pass2 = remove_lora_tags(pass1)
|
||||
|
||||
for lora_name, model_weight, clip_weight, lbw, lbw_a, lbw_b in loras:
|
||||
for lora_name, model_weight, clip_weight, lbw, lbw_a, lbw_b, loader in loras:
|
||||
lora_name_ext = lora_name.split('.')
|
||||
if ('.'+lora_name_ext[-1]) not in folder_paths.supported_pt_extensions:
|
||||
lora_name = lora_name+".safetensors"
|
||||
@@ -400,26 +447,36 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
path = None
|
||||
|
||||
if path is not None:
|
||||
print(f"LOAD LORA: {lora_name}: {model_weight}, {clip_weight}, LBW={lbw}, A={lbw_a}, B={lbw_b}")
|
||||
logging.info(f"LOAD LORA: {lora_name}: {model_weight}, {clip_weight}, LBW={lbw}, A={lbw_a}, B={lbw_b}, LOADER={loader}")
|
||||
|
||||
def default_lora():
|
||||
return nodes.LoraLoader().load_lora(model, clip, lora_name, model_weight, clip_weight)
|
||||
|
||||
if lbw is not None:
|
||||
if 'LoraLoaderBlockWeight //Inspire' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node(
|
||||
'https://github.com/ltdrdata/ComfyUI-Inspire-Pack',
|
||||
"To use 'LBW=' syntax in wildcards, 'Inspire Pack' extension is required.")
|
||||
|
||||
print(f"'LBW(Lora Block Weight)' is given, but the 'Inspire Pack' is not installed. The LBW= attribute is being ignored.")
|
||||
model, clip = default_lora()
|
||||
if loader is not None:
|
||||
if loader == 'nunchaku':
|
||||
if 'NunchakuFluxLoraLoader' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
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:
|
||||
cls = nodes.NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
|
||||
model, clip, _ = cls().doit(model, clip, lora_name, model_weight, clip_weight, False, 0, lbw_a, lbw_b, "", lbw)
|
||||
logging.warning(f"LORA LOADER NOT FOUND: '{loader}'")
|
||||
else:
|
||||
model, clip = default_lora()
|
||||
def default_lora():
|
||||
return nodes.LoraLoader().load_lora(model, clip, lora_name, model_weight, clip_weight)
|
||||
|
||||
if lbw is not None:
|
||||
if 'LoraLoaderBlockWeight //Inspire' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node(
|
||||
'https://github.com/ltdrdata/ComfyUI-Inspire-Pack',
|
||||
"To use 'LBW=' syntax in wildcards, 'Inspire Pack' extension is required.")
|
||||
|
||||
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']
|
||||
model, clip, _ = cls().doit(model, clip, lora_name, model_weight, clip_weight, False, 0, lbw_a, lbw_b, "", lbw)
|
||||
|
||||
else:
|
||||
model, clip = default_lora()
|
||||
else:
|
||||
print(f"LORA NOT FOUND: {orig_lora_name}")
|
||||
logging.warning(f"LORA NOT FOUND: {orig_lora_name}")
|
||||
|
||||
pass3 = [x.strip() for x in pass2.split("BREAK")]
|
||||
pass3 = [x for x in pass3 if x != '']
|
||||
@@ -428,7 +485,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
pass3 = ['']
|
||||
|
||||
pass3_str = [f'[{x}]' for x in pass3]
|
||||
print(f"CLIP: {str.join(' + ', pass3_str)}")
|
||||
logging.info(f"CLIP: {str.join(' + ', pass3_str)}")
|
||||
|
||||
result = None
|
||||
|
||||
@@ -515,7 +572,7 @@ def split_string_with_sep(input_string):
|
||||
else:
|
||||
try:
|
||||
seed = int(matches[i][5:-1])
|
||||
except:
|
||||
except Exception:
|
||||
seed = None
|
||||
result_list.append(seed)
|
||||
|
||||
@@ -561,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.")
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
import comfy
|
||||
import torch
|
||||
|
||||
from comfy import sampler_helpers
|
||||
|
||||
|
||||
class Unsampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
@@ -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.1.1"
|
||||
version = "8.20.1"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ piexif
|
||||
transformers
|
||||
opencv-python-headless
|
||||
scipy>=1.11.4
|
||||
numpy<2
|
||||
numpy
|
||||
dill
|
||||
matplotlib
|
||||
matplotlib
|
||||
git+https://github.com/facebookresearch/sam2
|
||||
@@ -0,0 +1,3 @@
|
||||
[lint]
|
||||
ignore = ["E402","E701"]
|
||||
exclude = ["install.py", "*.ipynb"]
|
||||
@@ -1,38 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
comfy_path = '../..'
|
||||
|
||||
def rmtree(path):
|
||||
retry_count = 3
|
||||
|
||||
while True:
|
||||
try:
|
||||
retry_count -= 1
|
||||
|
||||
if platform.system() == "Windows":
|
||||
subprocess.check_call(['attrib', '-R', path + '\\*', '/S'])
|
||||
|
||||
shutil.rmtree(path)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as ex:
|
||||
print(f"ex: {ex}")
|
||||
time.sleep(3)
|
||||
|
||||
if retry_count < 0:
|
||||
raise ex
|
||||
|
||||
print(f"Uninstall retry({retry_count})")
|
||||
|
||||
js_dest_path = os.path.join(comfy_path, "web", "extensions", "impact-pack")
|
||||
|
||||
if os.path.exists(js_dest_path):
|
||||
rmtree(js_dest_path)
|
||||
|
||||
|
||||