Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff9c30787c | ||
|
|
64f709741a | ||
|
|
455e993354 | ||
|
|
ce23d436fc | ||
|
|
5f630466fb | ||
|
|
81045cc845 | ||
|
|
20f2cac6f0 | ||
|
|
348c3dcb6b | ||
|
|
1ec3037613 | ||
|
|
d4136bc955 | ||
|
|
c9b3aecd1a | ||
|
|
a2bb6f7c91 | ||
|
|
e1e408d8c9 | ||
|
|
f7686845c6 | ||
|
|
37f465d4a9 | ||
|
|
48b4254e81 | ||
|
|
24de5a846b | ||
|
|
6fe85ed6bf | ||
|
|
216f4660e1 | ||
|
|
314b676cc3 | ||
|
|
cd89590a7d | ||
|
|
dc70f40eff | ||
|
|
66bf101341 | ||
|
|
825296c3e0 | ||
|
|
5c0535942a | ||
|
|
bf0e86a10d | ||
|
|
6be257254e | ||
|
|
727295b52e | ||
|
|
6c23f7691d | ||
|
|
6248f31402 | ||
|
|
cb0b652703 | ||
|
|
9c4fa5fb27 | ||
|
|
759b4d0dc9 | ||
|
|
a65fd56b7f | ||
|
|
47d981eca2 | ||
|
|
b2f382776f | ||
|
|
0e4e439d39 | ||
|
|
18d25a29a0 | ||
|
|
2b724e5ed2 | ||
|
|
96cc242f78 | ||
|
|
28267da071 | ||
|
|
86d7f73981 | ||
|
|
f428182ddf | ||
|
|
a43dae373e | ||
|
|
1087f2ee06 | ||
|
|
841a245cd4 | ||
|
|
aecc925630 | ||
|
|
b5abf19a1b | ||
|
|
9775a98f03 | ||
|
|
ac288bbcb9 | ||
|
|
fd69570977 | ||
|
|
1c488eec11 | ||
|
|
c3eed0936f | ||
|
|
44adbbe282 | ||
|
|
940ad41b15 | ||
|
|
e848a9dfb7 | ||
|
|
76653016dc | ||
|
|
9ed160123d | ||
|
|
ffbd4ae116 | ||
|
|
5757c533e6 | ||
|
|
e99e9aaec2 | ||
|
|
690b0a0f61 | ||
|
|
84fbb3fa2e | ||
|
|
b2c7e06e79 | ||
|
|
a500aff476 |
@@ -2,11 +2,14 @@
|
||||
|
||||
# ComfyUI-Impact-Pack
|
||||
|
||||
**Custom nodes pack for ComfyUI**
|
||||
This custom node helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more.
|
||||
**Custom node pack for ComfyUI**
|
||||
This node pack helps to conveniently enhance images through Detector, Detailer, Upscaler, Pipe, and more.
|
||||
|
||||
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.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.
|
||||
* V6.0: Supports FLUX.1 model in Impact KSampler, Detailers, PreviewBridgeLatent
|
||||
* V5.0: It is no longer compatible with versions of ComfyUI before 2024.04.08.
|
||||
@@ -32,9 +35,6 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
## Custom Nodes
|
||||
### [Detector nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detectors.md)
|
||||
* `SAMLoader` - Loads the SAM model.
|
||||
* `UltralyticsDetectorProvider` - Loads the Ultralystics model to provide SEGM_DETECTOR, BBOX_DETECTOR.
|
||||
- Unlike `MMDetDetectorProvider`, for segm models, `BBOX_DETECTOR` is also provided.
|
||||
- The various models available in UltralyticsDetectorProvider can be downloaded through **ComfyUI-Manager**.
|
||||
* `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.
|
||||
@@ -48,7 +48,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
|
||||
### 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.
|
||||
* `segs_preprocessor` and `control_image` can be selectively applied. If an `control_image` is given, `segs_preprocessor` will be ignored.
|
||||
* `segs_preprocessor` and `control_image` can be selectively applied. If a `control_image` is given, `segs_preprocessor` will be ignored.
|
||||
* If set to `control_image`, you can preview the cropped cnet image through `SEGSPreview (CNET Image)`. Images generated by `segs_preprocessor` should be verified through the `cnet_images` output of each Detailer.
|
||||
* The `segs_preprocessor` operates by applying preprocessing on-the-fly based on the cropped image during the detailing process, while `control_image` will be cropped and used as input to `ControlNetApply (SEGS)`.
|
||||
* `ControlNetClear (SEGS)` - Clear applied ControlNet in SEGS
|
||||
@@ -89,6 +89,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `MaskDetailer (pipe)` - This is a simple inpaint node that applies the Detailer to the mask area.
|
||||
|
||||
* `FromDetailer (SDXL/pipe)`, `BasicPipe -> DetailerPipe (SDXL)`, `Edit DetailerPipe (SDXL)` - These are pipe functions used in Detailer for utilizing the refiner model of SDXL.
|
||||
* `Any PIPE -> BasicPipe` - Convert the PIPE Value of other custom nodes that are not BASIC_PIPE but internally have the same structure as BASIC_PIPE to BASIC_PIPE. If an incompatible type is applied, it may cause runtime errors.
|
||||
|
||||
### SEGS Manipulation nodes
|
||||
* `SEGSDetailer` - Performs detailed work on SEGS without pasting it back onto the original image.
|
||||
@@ -106,6 +107,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `SEGS Filter (range)` - This node retrieves only SEGs from SEGS that have a size and position within a certain range.
|
||||
* `SEGS Assign (label)` - Assign labels sequentially to SEGS. This node is useful when used with `[LAB]` of FaceDetailer.
|
||||
* `SEGSConcat` - Concatenate segs1 and segs2. If source shape of segs1 and segs2 are different from segs2 will be ignored.
|
||||
* `SEGS Merge` - SEGS contains multiple SEGs. SEGS Merge integrates several SEGs into a single merged SEG. The label is changed to `merged` and the confidence becomes the minimum confidence. The applied controlnet and cropped_image are removed.
|
||||
* `Picker (SEGS)` - Among the input SEGS, you can select a specific SEG through a dialog. If no SEG is selected, it outputs an empty SEGS. Increasing the batch_size of SEGSDetailer can be used for the purpose of selecting from the candidates.
|
||||
* `Set Default Image For SEGS` - Set a default image for SEGS. SEGS with images set this way do not need to have a fallback image set. When override is set to false, the original image is preserved.
|
||||
* `Remove Image from SEGS` - Remove the image set for the SEGS that has been configured by "Set Default Image for SEGS" or SEGSDetailer. When the image for the SEGS is removed, the Detailer node will operate based on the currently processed image instead of the SEGS.
|
||||
@@ -228,16 +230,19 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
|
||||
|
||||
### Batch/List Util
|
||||
* `Image batch To Image List` - Convert Image batch to Image List
|
||||
* `Image Batch to Image List` - Convert Image batch to Image List
|
||||
- You can use images generated in a multi batch to handle them
|
||||
* `Image List to Image Batch` - Convert Image List to Image Batch
|
||||
* `Make Image List` - Convert multiple images into a single image list
|
||||
* `Make Image Batch` - Convert multiple images into a single image batch
|
||||
- The input of images can be scaled up as needed
|
||||
|
||||
* `Masks to Mask List`, `Mask List to Masks`, `Make Mask List`, `Make Mask Batch` - It has the same functionality as the nodes above, but uses mask as input instead of image.
|
||||
* `Flatten Mask Batch` - Flattens a Mask Batch into a single Mask. Normal operation is not guaranteed for non-binary masks.
|
||||
* `Make List (Any)` - Create a list with arbitrary values.
|
||||
|
||||
### Logics (experimental)
|
||||
* These nodes are experimental nodes designed to implement the logic for loops and dynamic switching.
|
||||
* `ImpactCompare`, `ImpactConditionalBranch`, `ImpactConditionalBranchSelMode`, `ImpactInt`, `ImpactValueSender`, `ImpactValueReceiver`, `ImpactImageInfo`, `ImpactMinMax`, `ImpactNeg`, `ImpactConditionalStopIteration`
|
||||
* `ImpactCompare`, `ImpactConditionalBranch`, `ImpactConditionalBranchSelMode`, `ImpactInt`, `ImpactBoolean`, `ImpactValueSender`, `ImpactValueReceiver`, `ImpactImageInfo`, `ImpactMinMax`, `ImpactNeg`, `ImpactConditionalStopIteration`
|
||||
* `ImpactIsNotEmptySEGS` - This node returns `true` only if the input SEGS is not empty.
|
||||
* `ImpactIfNone` - Returns `true` if any_input is None, and returns `false` if it is not None.
|
||||
* `Queue Trigger` - When this node is executed, it adds a new queue to assist with repetitive tasks. It will only execute if the signal's status changes.
|
||||
@@ -280,6 +285,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `Negative Cond Placeholder` - Models like FLUX.1 do not use Negative Conditioning. This is a placeholder node for them. You can use FLUX.1 by replacing the Negative Conditioning used in Impact KSampler, KSampler (Inspire), and Detailer with this node.
|
||||
* `Execution Order Controller` - A helper node that can forcibly control the execution order of nodes.
|
||||
* Connect the output of the node that should be executed first to the signal, and make the input of the node that should be executed later pass through this node.
|
||||
* `List Bridge` - When passing the list output through this node, it collects and organizes the data before forwarding it, which ensures that the previous stage's sub-workflow has been completed.
|
||||
|
||||
|
||||
## MMDet nodes (DEPRECATED) - Don't use these nodes
|
||||
@@ -305,11 +311,6 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* To use the existing deprecated legacy nodes, you need to enable the MMDet usage configuration.
|
||||
|
||||
|
||||
## Ultralytics models
|
||||
* huggingface.co/Bingsu/[adetailer](https://github.com/ultralytics/assets/releases/) - You can download face, people detection models, and clothing detection models.
|
||||
* ultralytics/[assets](https://github.com/ultralytics/assets/releases/) - You can download various types of detection models other than faces or people.
|
||||
* civitai/[adetailer](https://civitai.com/search/models?sortBy=models_v5&query=adetailer) - You can download various types detection models....Many models are associated with NSFW content.
|
||||
|
||||
## How to activate 'MMDet usage' (DEPRECATED)
|
||||
* Upon the initial execution, an `impact-pack.ini` file will be generated in the custom_nodes/ComfyUI-Impact-Pack directory.
|
||||
```
|
||||
@@ -328,32 +329,39 @@ mmdet_skip = False
|
||||
|
||||
## Installation
|
||||
|
||||
### Install via ComfyUI-Manager (Recommended)
|
||||
* Search `ComfyUI Impact Pack` in ComfyUI-Manager and click `Install` button.
|
||||
|
||||
### Manual Install (Not Recommended)
|
||||
1. `cd custom_nodes`
|
||||
2. `git clone https://github.com/ltdrdata/ComfyUI-Impact-Pack.git`
|
||||
3. `cd ComfyUI-Impact-Pack`
|
||||
4. (optional) `git clone https://github.com/ltdrdata/ComfyUI-Impact-Subpack impact_subpack`
|
||||
* Impact Pack will automatically download subpack during its initial launch.
|
||||
5. (optional) `python install.py`
|
||||
5. (optional) `python install-manual.py`
|
||||
* Impact Pack will automatically install its dependencies during its initial launch.
|
||||
* For the portable version, you should execute the command `..\..\..\python_embeded\python.exe install.py` to run the installation script.
|
||||
|
||||
|
||||
* For the portable version, you should execute the command `..\..\..\python_embeded\python.exe install-manual.py` to run the installation script.
|
||||
6. Restart ComfyUI
|
||||
|
||||
* NOTE1: If an error occurs during the installation process, please refer to [Troubleshooting Page](troubleshooting/TROUBLESHOOTING.md) for assistance.
|
||||
* NOTE2: You can use this colab notebook [colab notebook](https://colab.research.google.com/github/ltdrdata/ComfyUI-Impact-Pack/blob/Main/notebook/comfyui_colab_impact_pack.ipynb) to launch it. This notebook automatically downloads the impact pack to the custom_nodes directory, installs the tested dependencies, and runs it.
|
||||
* NOTE3: If you create an empty file named `skip_download_model` in the `ComfyUI/custom_nodes/` directory, it will skip the model download step during the installation of the impact pack.
|
||||
|
||||
|
||||
## Package Dependencies (If you need to manual setup.)
|
||||
|
||||
* pip install
|
||||
* openmim
|
||||
* segment-anything
|
||||
* ultralytics
|
||||
* scikit-image
|
||||
* piexif
|
||||
* (optional) pycocotools
|
||||
* piexif
|
||||
* opencv-python
|
||||
* scipy
|
||||
* numpy<2
|
||||
* dill
|
||||
* matplotlib
|
||||
* (optional) onnxruntime
|
||||
* (deprecated) openmim # for mim
|
||||
* (deprecated) pycocotools # for mim
|
||||
|
||||
* mim install (deprecated)
|
||||
* mmcv==2.0.0, mmdet==3.0.0, mmengine==0.7.2
|
||||
|
||||
+26
-44
@@ -13,7 +13,6 @@ import traceback
|
||||
|
||||
comfy_path = os.path.dirname(folder_paths.__file__)
|
||||
impact_path = os.path.join(os.path.dirname(__file__))
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
|
||||
modules_path = os.path.join(os.path.dirname(__file__), "modules")
|
||||
|
||||
sys.path.append(modules_path)
|
||||
@@ -22,30 +21,9 @@ import impact.config
|
||||
import impact.sample_error_enhancer
|
||||
print(f"### Loading: ComfyUI-Impact-Pack ({impact.config.version})")
|
||||
|
||||
|
||||
def do_install():
|
||||
import importlib
|
||||
spec = importlib.util.spec_from_file_location('impact_install', os.path.join(os.path.dirname(__file__), 'install.py'))
|
||||
impact_install = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(impact_install)
|
||||
|
||||
|
||||
# ensure dependency
|
||||
if not os.path.exists(os.path.join(subpack_path, ".git")) and os.path.exists(subpack_path):
|
||||
print(f"### CompfyUI-Impact-Pack: corrupted subpack detected.")
|
||||
shutil.rmtree(subpack_path)
|
||||
|
||||
if impact.config.get_config()['dependency_version'] < impact.config.dependency_version or not os.path.exists(subpack_path):
|
||||
print(f"### ComfyUI-Impact-Pack: Updating dependencies [{impact.config.get_config()['dependency_version']} -> {impact.config.dependency_version}]")
|
||||
do_install()
|
||||
|
||||
sys.path.append(subpack_path)
|
||||
|
||||
# Core
|
||||
# recheck dependencies for colab
|
||||
try:
|
||||
import impact.subpack_nodes # This import must be done before cv2.
|
||||
|
||||
import folder_paths
|
||||
import torch
|
||||
import cv2
|
||||
@@ -63,10 +41,10 @@ try:
|
||||
import mmcv
|
||||
from mmdet.apis import (inference_detector, init_detector)
|
||||
from mmdet.evaluation import get_classes
|
||||
except:
|
||||
import importlib
|
||||
print("### ComfyUI-Impact-Pack: Reinstall dependencies (several dependencies are missing.)")
|
||||
do_install()
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.error("[Impact Pack] Failed to import due to several dependencies are missing!!!!")
|
||||
raise e
|
||||
|
||||
|
||||
import impact.impact_server # to load server api
|
||||
@@ -116,6 +94,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"FromDetailerPipe": FromDetailerPipe,
|
||||
"FromDetailerPipe_v2": FromDetailerPipe_v2,
|
||||
"FromDetailerPipeSDXL": FromDetailerPipe_SDXL,
|
||||
"AnyPipeToBasic": AnyPipeToBasic,
|
||||
"ToBasicPipe": ToBasicPipe,
|
||||
"FromBasicPipe": FromBasicPipe,
|
||||
"FromBasicPipe_v2": FromBasicPipe_v2,
|
||||
@@ -161,6 +140,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactSegsAndMask": SegsBitwiseAndMask,
|
||||
"ImpactSegsAndMaskForEach": SegsBitwiseAndMaskForEach,
|
||||
"EmptySegs": EmptySEGS,
|
||||
"ImpactFlattenMask": FlattenMask,
|
||||
|
||||
"MediaPipeFaceMeshToSEGS": MediaPipeFaceMeshToSEGS,
|
||||
"MaskToSEGS": MaskToSEGS,
|
||||
@@ -237,6 +217,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactSEGSConcat": SEGSConcat,
|
||||
"ImpactSEGSPicker": SEGSPicker,
|
||||
"ImpactMakeTileSEGS": MakeTileSEGS,
|
||||
"ImpactSEGSMerge": SEGSMerge,
|
||||
|
||||
"SEGSDetailerForAnimateDiff": SEGSDetailerForAnimateDiff,
|
||||
|
||||
@@ -249,6 +230,9 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactImageBatchToImageList": ImageBatchToImageList,
|
||||
"ImpactMakeImageList": MakeImageList,
|
||||
"ImpactMakeImageBatch": MakeImageBatch,
|
||||
"ImpactMakeAnyList": MakeAnyList,
|
||||
"ImpactMakeMaskList": MakeMaskList,
|
||||
"ImpactMakeMaskBatch": MakeMaskBatch,
|
||||
|
||||
"RegionalSampler": RegionalSampler,
|
||||
"RegionalSamplerAdvanced": RegionalSamplerAdvanced,
|
||||
@@ -271,6 +255,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactLogicalOperators": ImpactLogicalOperators,
|
||||
"ImpactInt": ImpactInt,
|
||||
"ImpactFloat": ImpactFloat,
|
||||
"ImpactBoolean": ImpactBoolean,
|
||||
"ImpactValueSender": ImpactValueSender,
|
||||
"ImpactValueReceiver": ImpactValueReceiver,
|
||||
"ImpactImageInfo": ImpactImageInfo,
|
||||
@@ -282,6 +267,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"StringListToString": StringListToString,
|
||||
"WildcardPromptFromString": WildcardPromptFromString,
|
||||
"ImpactExecutionOrderController": ImpactExecutionOrderController,
|
||||
"ImpactListBridge": ImpactListBridge,
|
||||
|
||||
"RemoveNoiseMask": RemoveNoiseMask,
|
||||
|
||||
@@ -315,8 +301,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSimpleDetectorSEGS_for_AD": "Simple Detector for AnimateDiff (SEGS)",
|
||||
"ImpactSimpleDetectorSEGS": "Simple Detector (SEGS)",
|
||||
"ImpactSimpleDetectorSEGSPipe": "Simple Detector (SEGS/pipe)",
|
||||
"ImpactControlNetApplySEGS": "ControlNetApply (SEGS)",
|
||||
"ImpactControlNetApplyAdvancedSEGS": "ControlNetApplyAdvanced (SEGS)",
|
||||
"ImpactControlNetApplySEGS": "ControlNetApply (SEGS) - DEPRECATED",
|
||||
"ImpactControlNetApplyAdvancedSEGS": "ControlNetApply (SEGS)",
|
||||
"ImpactIPAdapterApplySEGS": "IPAdapterApply (SEGS)",
|
||||
|
||||
"BboxDetectorCombined_v2": "BBOX Detector (combined)",
|
||||
@@ -332,6 +318,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"BitwiseAndMask": "Pixelwise(MASK & MASK)",
|
||||
"SubtractMask": "Pixelwise(MASK - MASK)",
|
||||
"AddMask": "Pixelwise(MASK + MASK)",
|
||||
"ImpactFlattenMask": "Flatten Mask Batch",
|
||||
"DetailerForEach": "Detailer (SEGS)",
|
||||
"DetailerForEachPipe": "Detailer (SEGS/pipe)",
|
||||
"DetailerForEachDebug": "DetailerDebug (SEGS)",
|
||||
@@ -354,6 +341,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"DetailerPipeToBasicPipe": "DetailerPipe -> BasicPipe",
|
||||
"EditBasicPipe": "Edit BasicPipe",
|
||||
"EditDetailerPipe": "Edit DetailerPipe",
|
||||
"AnyPipeToBasic": "Any PIPE -> BasicPipe",
|
||||
|
||||
"LatentPixelScale": "Latent Scale (on Pixel Space)",
|
||||
"IterativeLatentUpscale": "Iterative Upscale (Latent/on Pixel Space)",
|
||||
@@ -376,6 +364,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSEGSToMaskBatch": "SEGS to Mask Batch",
|
||||
"ImpactSEGSPicker": "Picker (SEGS)",
|
||||
"ImpactMakeTileSEGS": "Make Tile SEGS",
|
||||
"ImpactSEGSMerge": "SEGS Merge",
|
||||
|
||||
"ImpactDecomposeSEGS": "Decompose (SEGS)",
|
||||
"ImpactAssembleSEGS": "Assemble (SEGS)",
|
||||
@@ -399,13 +388,19 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSwitch": "Switch (Any)",
|
||||
"ImpactInversedSwitch": "Inversed Switch (Any)",
|
||||
"ImpactExecutionOrderController": "Execution Order Controller",
|
||||
"ImpactListBridge": "List Bridge",
|
||||
|
||||
"MasksToMaskList": "Masks to Mask List",
|
||||
"MaskListToMaskBatch": "Mask List to Masks",
|
||||
"ImpactImageBatchToImageList": "Image batch to Image List",
|
||||
"MasksToMaskList": "Mask Batch to Mask List",
|
||||
"MaskListToMaskBatch": "Mask List to Mask Batch",
|
||||
"ImpactImageBatchToImageList": "Image Batch to Image List",
|
||||
"ImageListToImageBatch": "Image List to Image Batch",
|
||||
|
||||
"ImpactMakeImageList": "Make Image List",
|
||||
"ImpactMakeImageBatch": "Make Image Batch",
|
||||
"ImpactMakeMaskList": "Make Mask List",
|
||||
"ImpactMakeMaskBatch": "Make Mask Batch",
|
||||
"ImpactMakeAnyList": "Make List (Any)",
|
||||
|
||||
"ImpactStringSelector": "String Selector",
|
||||
"StringListToString": "String List to String",
|
||||
"WildcardPromptFromString": "Wildcard Prompt from String",
|
||||
@@ -464,19 +459,6 @@ if not impact.config.get_config()['mmdet_skip']:
|
||||
"SegmDetectorCombined": "SegmDetectorCombined (Legacy)",
|
||||
})
|
||||
|
||||
try:
|
||||
import impact.subpack_nodes
|
||||
|
||||
NODE_CLASS_MAPPINGS.update(impact.subpack_nodes.NODE_CLASS_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(impact.subpack_nodes.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
except Exception as e:
|
||||
print("### ComfyUI-Impact-Pack: (IMPORT FAILED) Subpack\n")
|
||||
print(" The module at the `custom_nodes/ComfyUI-Impact-Pack/impact_subpack` path appears to be incomplete.")
|
||||
print(" Recommended to delete the path and restart ComfyUI.")
|
||||
print(" If the issue persists, please report it to https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues.")
|
||||
print("\n---------------------------------")
|
||||
traceback.print_exc()
|
||||
print("---------------------------------\n")
|
||||
|
||||
# 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
|
||||
|
||||
+26
-199
@@ -5,7 +5,6 @@ import subprocess
|
||||
import threading
|
||||
import locale
|
||||
import traceback
|
||||
import re
|
||||
|
||||
|
||||
if sys.argv[0] == 'install.py':
|
||||
@@ -13,14 +12,11 @@ if sys.argv[0] == 'install.py':
|
||||
|
||||
|
||||
impact_path = os.path.join(os.path.dirname(__file__), "modules")
|
||||
old_subpack_path = os.path.join(os.path.dirname(__file__), "subpack")
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
|
||||
subpack_repo = "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
|
||||
|
||||
|
||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||
if comfy_path is None:
|
||||
print(f"\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
||||
print(f"\nWARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.", file=sys.stderr)
|
||||
comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
model_path = os.environ.get('COMFYUI_MODEL_PATH')
|
||||
@@ -33,7 +29,7 @@ if model_path is None:
|
||||
|
||||
if model_path is None:
|
||||
model_path = os.path.abspath(os.path.join(comfy_path, 'models'))
|
||||
print(f"\n[bold yellow]WARN: The `COMFYUI_MODEL_PATH` environment variable is not set. Assuming `{model_path}` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
||||
print(f"\nWARN: The `COMFYUI_MODEL_PATH` environment variable is not set. Assuming `{model_path}` as the ComfyUI path.", file=sys.stderr)
|
||||
|
||||
|
||||
sys.path.append(impact_path)
|
||||
@@ -71,219 +67,40 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None):
|
||||
# ---
|
||||
|
||||
|
||||
pip_list = None
|
||||
|
||||
|
||||
def get_installed_packages():
|
||||
global pip_list
|
||||
|
||||
if pip_list is None:
|
||||
try:
|
||||
result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True)
|
||||
pip_list = set([line.split()[0].lower() for line in result.split('\n') if line.strip()])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
|
||||
return set()
|
||||
|
||||
return pip_list
|
||||
|
||||
|
||||
def is_installed(name):
|
||||
name = name.strip()
|
||||
pattern = r'([^<>!=]+)([<>!=]=?)'
|
||||
match = re.search(pattern, name)
|
||||
|
||||
if match:
|
||||
name = match.group(1)
|
||||
|
||||
result = name.lower() in get_installed_packages()
|
||||
return result
|
||||
|
||||
|
||||
def is_requirements_installed(file_path):
|
||||
print(f"req_path: {file_path}")
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r') as file:
|
||||
lines = file.readlines()
|
||||
for line in lines:
|
||||
if not is_installed(line):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
try:
|
||||
import platform
|
||||
import folder_paths
|
||||
from torchvision.datasets.utils import download_url
|
||||
import impact.config
|
||||
|
||||
|
||||
print("### ComfyUI-Impact-Pack: Check dependencies")
|
||||
|
||||
if "python_embeded" in sys.executable or "python_embedded" in sys.executable:
|
||||
pip_install = [sys.executable, '-s', '-m', 'pip', 'install']
|
||||
pip_upgrade = [sys.executable, '-s', '-m', 'pip', 'install', '-U']
|
||||
mim_install = [sys.executable, '-s', '-m', 'mim', 'install']
|
||||
else:
|
||||
pip_install = [sys.executable, '-m', 'pip', 'install']
|
||||
pip_upgrade = [sys.executable, '-m', 'pip', 'install', '-U']
|
||||
mim_install = [sys.executable, '-m', 'mim', 'install']
|
||||
|
||||
|
||||
def ensure_subpack():
|
||||
import git
|
||||
if os.path.exists(subpack_path):
|
||||
try:
|
||||
repo = git.Repo(subpack_path)
|
||||
repo.remotes.origin.pull()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
if platform.system() == 'Windows':
|
||||
print(f"[ComfyUI-Impact-Pack] Please turn off ComfyUI and remove '{subpack_path}' and restart ComfyUI.")
|
||||
else:
|
||||
shutil.rmtree(subpack_path)
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
else:
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
|
||||
if os.path.exists(old_subpack_path):
|
||||
shutil.rmtree(old_subpack_path)
|
||||
|
||||
|
||||
def ensure_pip_packages_first():
|
||||
subpack_req = os.path.join(subpack_path, "requirements.txt")
|
||||
if os.path.exists(subpack_req) and not is_requirements_installed(subpack_req):
|
||||
process_wrap(pip_install + ['-r', 'requirements.txt'], cwd=subpack_path)
|
||||
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
process_wrap(pip_install + ['openmim'])
|
||||
|
||||
try:
|
||||
import pycocotools
|
||||
except Exception:
|
||||
if platform.system() not in ["Windows"] or platform.machine() not in ["AMD64", "x86_64"]:
|
||||
print(f"Your system is {platform.system()}; !! You need to install 'libpython3-dev' for this step. !!")
|
||||
|
||||
process_wrap(pip_install + ['pycocotools'])
|
||||
else:
|
||||
pycocotools = {
|
||||
(3, 8): "https://github.com/Bing-su/dddetailer/releases/download/pycocotools/pycocotools-2.0.6-cp38-cp38-win_amd64.whl",
|
||||
(3, 9): "https://github.com/Bing-su/dddetailer/releases/download/pycocotools/pycocotools-2.0.6-cp39-cp39-win_amd64.whl",
|
||||
(3, 10): "https://github.com/Bing-su/dddetailer/releases/download/pycocotools/pycocotools-2.0.6-cp310-cp310-win_amd64.whl",
|
||||
(3, 11): "https://github.com/Bing-su/dddetailer/releases/download/pycocotools/pycocotools-2.0.6-cp311-cp311-win_amd64.whl",
|
||||
}
|
||||
|
||||
version = sys.version_info[:2]
|
||||
url = pycocotools[version]
|
||||
process_wrap(pip_install + [url])
|
||||
|
||||
|
||||
def ensure_pip_packages_last():
|
||||
my_path = os.path.dirname(__file__)
|
||||
requirements_path = os.path.join(my_path, "requirements.txt")
|
||||
|
||||
if not is_requirements_installed(requirements_path):
|
||||
process_wrap(pip_install + ['-r', requirements_path])
|
||||
|
||||
# fallback
|
||||
try:
|
||||
import segment_anything
|
||||
from skimage.measure import label, regionprops
|
||||
import piexif
|
||||
except Exception:
|
||||
process_wrap(pip_install + ['-r', requirements_path])
|
||||
|
||||
# !! cv2 importing test must be very last !!
|
||||
try:
|
||||
from cv2 import setNumThreads
|
||||
except Exception:
|
||||
try:
|
||||
is_open_cv_installed = False
|
||||
|
||||
# upgrade if opencv is installed already
|
||||
if is_installed('opencv-python'):
|
||||
process_wrap(pip_upgrade + ['opencv-python'])
|
||||
is_open_cv_installed = True
|
||||
|
||||
if is_installed('opencv-python-headless'):
|
||||
process_wrap(pip_upgrade + ['opencv-python-headless'])
|
||||
is_open_cv_installed = True
|
||||
|
||||
if is_installed('opencv-contrib-python'):
|
||||
process_wrap(pip_upgrade + ['opencv-contrib-python'])
|
||||
is_open_cv_installed = True
|
||||
|
||||
if is_installed('opencv-contrib-python-headless'):
|
||||
process_wrap(pip_upgrade + ['opencv-contrib-python-headless'])
|
||||
is_open_cv_installed = True
|
||||
|
||||
# if opencv is not installed install `opencv-python-headless`
|
||||
if not is_open_cv_installed:
|
||||
process_wrap(pip_install + ['opencv-python-headless'])
|
||||
except:
|
||||
print(f"[ERROR] ComfyUI-Impact-Pack: failed to install 'opencv-python'. Please, install manually.")
|
||||
|
||||
def ensure_mmdet_package():
|
||||
try:
|
||||
import mmcv
|
||||
import mmdet
|
||||
from mmdet.evaluation import get_classes
|
||||
except Exception:
|
||||
process_wrap(pip_install + ['opendatalab==0.0.9'])
|
||||
process_wrap(pip_install + ['-U', 'openmim'])
|
||||
process_wrap(mim_install + ['mmcv>=2.0.0rc4, <2.1.0'])
|
||||
process_wrap(mim_install + ['mmdet==3.0.0'])
|
||||
process_wrap(mim_install + ['mmengine==0.7.4'])
|
||||
|
||||
|
||||
def install():
|
||||
subpack_install_script = os.path.join(subpack_path, "install.py")
|
||||
|
||||
print(f"### ComfyUI-Impact-Pack: Updating subpack")
|
||||
try:
|
||||
import git
|
||||
except Exception:
|
||||
if not is_installed('GitPython'):
|
||||
process_wrap(pip_install + ['GitPython'])
|
||||
|
||||
ensure_subpack() # The installation of the subpack must take place before ensure_pip. cv2 triggers a permission error.
|
||||
|
||||
new_env = os.environ.copy()
|
||||
new_env["COMFYUI_PATH"] = comfy_path
|
||||
new_env["COMFYUI_MODEL_PATH"] = model_path
|
||||
|
||||
if os.path.exists(subpack_install_script):
|
||||
process_wrap([sys.executable, 'install.py'], cwd=subpack_path, env=new_env)
|
||||
if not is_requirements_installed(os.path.join(subpack_path, 'requirements.txt')):
|
||||
process_wrap(pip_install + ['-r', 'requirements.txt'], cwd=subpack_path)
|
||||
else:
|
||||
print(f"### ComfyUI-Impact-Pack: (Install Failed) Subpack\nFile not found: `{subpack_install_script}`")
|
||||
|
||||
ensure_pip_packages_first()
|
||||
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
ensure_mmdet_package()
|
||||
|
||||
ensure_pip_packages_last()
|
||||
|
||||
# Download model
|
||||
print("### ComfyUI-Impact-Pack: Check basic models")
|
||||
bbox_path = os.path.join(model_path, "mmdets", "bbox")
|
||||
sam_path = os.path.join(model_path, "sams")
|
||||
onnx_path = os.path.join(model_path, "onnx")
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'skip_download_model')):
|
||||
if not os.path.exists(bbox_path):
|
||||
os.makedirs(bbox_path)
|
||||
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 impact.config.get_config()['mmdet_skip']:
|
||||
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.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(bbox_path, "mmdet_anime-face_yolov3.py")):
|
||||
download_url("https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py", bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(sam_path, "sam_vit_b_01ec64.pth")):
|
||||
download_url("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", sam_path)
|
||||
if not os.path.exists(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.")
|
||||
|
||||
if not os.path.exists(onnx_path):
|
||||
print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})")
|
||||
@@ -291,6 +108,16 @@ 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.")
|
||||
|
||||
install()
|
||||
|
||||
|
||||
+54
-42
@@ -237,7 +237,7 @@ app.registerExtension({
|
||||
if(nodeData.name == "ImpactControlBridge") {
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
|
||||
if(!link_info || this.inputs[0].type != '*')
|
||||
if(index != 0 || !link_info || this.inputs[0].type != '*')
|
||||
return;
|
||||
|
||||
// assign type
|
||||
@@ -353,7 +353,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
this.outputs[0].type = origin_type;
|
||||
this.outputs[0].name = origin_type;
|
||||
this.outputs[0].name = 'output1';
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -383,7 +383,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
if(this.widgets) {
|
||||
if(this.widgets?.length) {
|
||||
this.widgets[0].options.max = select_slot?this.outputs.length-1:this.outputs.length;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
if(this.widgets[0].options.max > 0 && this.widgets[0].value == 0)
|
||||
@@ -393,7 +393,8 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
if (nodeData.name === 'ImpactMakeImageList' || nodeData.name === 'ImpactMakeImageBatch' ||
|
||||
nodeData.name === 'CombineRegionalPrompts' ||
|
||||
nodeData.name === 'ImpactMakeMaskList' || nodeData.name === 'ImpactMakeMaskBatch' ||
|
||||
nodeData.name === 'ImpactMakeAnyList' || nodeData.name === 'CombineRegionalPrompts' ||
|
||||
nodeData.name === 'ImpactCombineConditionings' || nodeData.name === 'ImpactConcatConditionings' ||
|
||||
nodeData.name === 'ImpactSEGSConcat' ||
|
||||
nodeData.name === 'ImpactSwitch' || nodeData.name === 'LatentSwitch' || nodeData.name == 'SEGSSwitch') {
|
||||
@@ -405,6 +406,15 @@ app.registerExtension({
|
||||
input_name = "image";
|
||||
break;
|
||||
|
||||
case 'ImpactMakeMaskList':
|
||||
case 'ImpactMakeMaskBatch':
|
||||
input_name = "mask";
|
||||
break;
|
||||
|
||||
case 'ImpactMakeAnyList':
|
||||
input_name = "value";
|
||||
break;
|
||||
|
||||
case 'ImpactSEGSConcat':
|
||||
input_name = "segs";
|
||||
break;
|
||||
@@ -473,8 +483,12 @@ app.registerExtension({
|
||||
|
||||
if(this.inputs[0].type == '*'){
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
let origin_type = node.outputs[link_info.origin_slot].type;
|
||||
|
||||
let origin_type = node.outputs[link_info.origin_slot]?.type;
|
||||
if(link_info.target_slot == 0 && this.inputs.length > 1) {
|
||||
origin_type = this.inputs[1].type;
|
||||
node.connect(link_info.origin_slot, node.id, 'input1');
|
||||
}
|
||||
|
||||
if(origin_type == '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
return;
|
||||
@@ -527,7 +541,7 @@ app.registerExtension({
|
||||
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
|
||||
}
|
||||
|
||||
if(this.widgets) {
|
||||
if(this.widgets?.length) {
|
||||
this.widgets[0].options.max = select_slot?this.inputs.length-1:this.inputs.length;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
if(this.widgets[0].options.max > 0 && this.widgets[0].value == 0)
|
||||
@@ -573,17 +587,17 @@ 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 += ", "
|
||||
|
||||
node.widgets[1].value += value;
|
||||
if(node.widgets_values)
|
||||
node.widgets_values[1] = node.widgets[1].value;
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[0], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(","))
|
||||
node.widgets[1].value += ", "
|
||||
|
||||
node.widgets[1].value += value;
|
||||
node.widgets_values[1] = node.widgets[1].value;
|
||||
}
|
||||
|
||||
node._value = value;
|
||||
},
|
||||
get: () => {
|
||||
@@ -653,18 +667,18 @@ 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[tbox_id].value += node._wildcard_value;
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id+1], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the Wildcard to add to the text") {
|
||||
if(node.widgets[tbox_id].value != '')
|
||||
node.widgets[tbox_id].value += ', '
|
||||
|
||||
node.widgets[tbox_id].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"; }
|
||||
});
|
||||
|
||||
@@ -676,24 +690,22 @@ 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);
|
||||
}
|
||||
|
||||
node.widgets[tbox_id].value += `<lora:${lora_name}>`;
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[tbox_id] = node.widgets[tbox_id].value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id], "value", {
|
||||
set: (value) => {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('inner_value_change')) {
|
||||
if(value != "Select the LoRA to add to the text") {
|
||||
let lora_name = 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._value = value;
|
||||
if (value !== "Select the LoRA to add to the text")
|
||||
node._value = value;
|
||||
},
|
||||
|
||||
get: () => { return "Select the LoRA to add to the text"; }
|
||||
|
||||
@@ -353,13 +353,16 @@ class ImpactSamEditorDialog extends ComfyDialog {
|
||||
imgCtx.drawImage(orig_image, 0, 0, drawWidth, drawHeight);
|
||||
|
||||
// update mask
|
||||
let w = (drawWidth * imgCanvas.clientWidth/imgCanvas.width) + "px";
|
||||
let h = (drawHeight * imgCanvas.clientHeight/imgCanvas.height) + "px";
|
||||
|
||||
pointsCanvas.width = drawWidth;
|
||||
pointsCanvas.height = drawHeight;
|
||||
pointsCanvas.style.top = imgCanvas.offsetTop + "px";
|
||||
pointsCanvas.style.left = imgCanvas.offsetLeft + "px";
|
||||
|
||||
maskCanvas.width = drawWidth;
|
||||
maskCanvas.height = drawHeight;
|
||||
maskCanvas.style.width = w;
|
||||
maskCanvas.style.height = h;
|
||||
maskCanvas.style.top = imgCanvas.offsetTop + "px";
|
||||
maskCanvas.style.left = imgCanvas.offsetLeft + "px";
|
||||
|
||||
|
||||
+11
-7
@@ -6,11 +6,15 @@ let refresh_btn2 = document.querySelector('button[title="Refresh widgets in node
|
||||
|
||||
let orig = refresh_btn.onclick;
|
||||
|
||||
refresh_btn.onclick = function() {
|
||||
orig();
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
};
|
||||
if(refresh_btn) {
|
||||
refresh_btn.onclick = function() {
|
||||
orig();
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
};
|
||||
}
|
||||
|
||||
refresh_btn2.addEventListener('click', function() {
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
});
|
||||
if(refresh_btn2) {
|
||||
refresh_btn2?.addEventListener('click', function() {
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,13 @@ from impact.core import SEG
|
||||
from impact.segs_nodes import SEGSPaste
|
||||
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
class SEGSDetailerForAnimateDiff:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -53,6 +60,9 @@ class SEGSDetailerForAnimateDiff:
|
||||
new_segs = []
|
||||
cnet_image_list = []
|
||||
|
||||
if 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]:
|
||||
cropped_image_frames = None
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ class PreviewBridge:
|
||||
"images": ("IMAGE",),
|
||||
"image": ("STRING", {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"block": ("BOOLEAN", {"default": False, "label_on": "if_empty_mask", "label_off": "never", "tooltip": "is_empty_mask: If the mask is empty, the execution is stopped.\nnever: The execution is never stopped."}),
|
||||
"restore_mask": (["never", "always", "if_same_size"], {"tooltip": "if_same_size: If the changed input image is the same size as the previous image, restore using the last saved mask\nalways: Whenever the input image changes, always restore using the last saved mask\nnever: Do not restore the mask.\n`restore_mask` has higher priority than `block`"}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
@@ -30,6 +34,8 @@ class PreviewBridge:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "This is a feature that allows you to edit and send a Mask over a image.\nIf the block is set to 'is_empty_mask', the execution is stopped when the mask is empty."
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.output_dir = folder_paths.get_temp_directory()
|
||||
@@ -70,7 +76,7 @@ class PreviewBridge:
|
||||
|
||||
return image, mask.unsqueeze(0), ui_item
|
||||
|
||||
def doit(self, images, image, unique_id, prompt=None, extra_pnginfo=None):
|
||||
def doit(self, images, image, unique_id, block=False, restore_mask="never", prompt=None, extra_pnginfo=None):
|
||||
need_refresh = False
|
||||
|
||||
if unique_id not in core.preview_bridge_cache:
|
||||
@@ -83,10 +89,25 @@ class PreviewBridge:
|
||||
pixels, mask, path_item = PreviewBridge.load_image(image)
|
||||
image = [path_item]
|
||||
else:
|
||||
res = nodes.PreviewImage().save_images(images, filename_prefix="PreviewBridge/PB-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
if restore_mask != "never":
|
||||
mask = core.preview_bridge_last_mask_cache.get(unique_id)
|
||||
if mask is None or (restore_mask != "always" and mask.shape[1:] != images.shape[1:3]):
|
||||
mask = None
|
||||
else:
|
||||
mask = None
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
res = nodes.PreviewImage().save_images(images, filename_prefix="PreviewBridge/PB-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
else:
|
||||
masked_images = tensor_convert_rgba(images)
|
||||
resized_mask = resize_mask(mask, (images.shape[1], images.shape[2])).unsqueeze(3)
|
||||
resized_mask = 1 - resized_mask
|
||||
tensor_putalpha(masked_images, resized_mask)
|
||||
res = nodes.PreviewImage().save_images(masked_images, filename_prefix="PreviewBridge/PB-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
|
||||
image2 = res['ui']['images']
|
||||
pixels = images
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
|
||||
path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', image2[0]['filename'])
|
||||
core.set_previewbridge_image(unique_id, path, image2[0])
|
||||
@@ -96,9 +117,23 @@ class PreviewBridge:
|
||||
|
||||
image = image2
|
||||
|
||||
is_empty_mask = torch.all(mask == 0)
|
||||
|
||||
if block and is_empty_mask and core.is_execution_model_version_supported():
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
result = ExecutionBlocker(None), ExecutionBlocker(None)
|
||||
elif block and is_empty_mask:
|
||||
print(f"[Impact Pack] PreviewBridge: ComfyUI is outdated - blocking feature is disabled.")
|
||||
result = pixels, mask
|
||||
else:
|
||||
result = pixels, mask
|
||||
|
||||
if not is_empty_mask:
|
||||
core.preview_bridge_last_mask_cache[unique_id] = mask
|
||||
|
||||
return {
|
||||
"ui": {"images": image},
|
||||
"result": (pixels, mask, ),
|
||||
"result": result,
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +186,9 @@ def decode_latent(latent, preview_method, vae_opt=None):
|
||||
elif preview_method == "Latent2RGB-FLUX.1":
|
||||
latent_format = latent_formats.Flux()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-LTXV":
|
||||
latent_format = latent_formats.LTXV()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
else:
|
||||
print(f"[Impact Pack] PreviewBridgeLatent: '{preview_method}' is unsupported preview method.")
|
||||
latent_format = latent_formats.SD15()
|
||||
@@ -176,10 +214,13 @@ class PreviewBridgeLatent:
|
||||
"Latent2RGB-SDXL", "Latent2RGB-SD15", "Latent2RGB-SD3",
|
||||
"Latent2RGB-SD-X4", "Latent2RGB-Playground-2.5",
|
||||
"Latent2RGB-SC-Prior", "Latent2RGB-SC-B",
|
||||
"Latent2RGB-LTXV",
|
||||
"TAEF1", "TAESDXL", "TAESD15", "TAESD3"],),
|
||||
},
|
||||
"optional": {
|
||||
"vae_opt": ("VAE", )
|
||||
"vae_opt": ("VAE", ),
|
||||
"block": ("BOOLEAN", {"default": False, "label_on": "if_empty_mask", "label_off": "never", "tooltip": "is_empty_mask: If the mask is empty, the execution is stopped.\nnever: The execution is never stopped. Instead, it returns a white mask."}),
|
||||
"restore_mask": (["never", "always", "if_same_size"], {"tooltip": "if_same_size: If the changed input latent is the same size as the previous latent, restore using the last saved mask\nalways: Whenever the input latent changes, always restore using the last saved mask\nnever: Do not restore the mask.\n`restore_mask` has higher priority than `block`\nIf the input latent already has a mask, do not restore mask."}),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
@@ -192,6 +233,8 @@ class PreviewBridgeLatent:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "This is a feature that allows you to edit and send a Mask over a latent image.\nIf the block is set to 'is_empty_mask', the execution is stopped when the mask is empty."
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.output_dir = folder_paths.get_temp_directory()
|
||||
@@ -233,9 +276,15 @@ class PreviewBridgeLatent:
|
||||
|
||||
return image, mask, ui_item
|
||||
|
||||
def doit(self, latent, image, preview_method, vae_opt=None, unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
def doit(self, latent, image, preview_method, vae_opt=None, block=False, unique_id=None, restore_mask='never', prompt=None, extra_pnginfo=None):
|
||||
latent_channels = latent['samples'].shape[1]
|
||||
preview_method_channels = 16 if 'SD3' in preview_method or 'SC-Prior' in preview_method or 'FLUX.1' in preview_method or 'TAEF1' == preview_method else 4
|
||||
|
||||
if 'SD3' in preview_method or 'SC-Prior' in preview_method or 'FLUX.1' in preview_method or 'TAEF1' == preview_method:
|
||||
preview_method_channels = 16
|
||||
elif 'LTXV' in preview_method:
|
||||
preview_method_channels = 128
|
||||
else:
|
||||
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.")
|
||||
@@ -262,10 +311,14 @@ class PreviewBridgeLatent:
|
||||
del res_latent['noise_mask']
|
||||
else:
|
||||
res_latent = latent
|
||||
|
||||
is_empty_mask = True
|
||||
else:
|
||||
res_latent = latent.copy()
|
||||
res_latent['noise_mask'] = mask
|
||||
|
||||
is_empty_mask = torch.all(mask == 1)
|
||||
|
||||
res_image = [path_item]
|
||||
else:
|
||||
decoded_image = decode_latent(latent, preview_method, vae_opt)
|
||||
@@ -287,11 +340,30 @@ class PreviewBridgeLatent:
|
||||
'subfolder': 'PreviewBridge',
|
||||
'type': 'temp',
|
||||
}]
|
||||
|
||||
is_empty_mask = False
|
||||
else:
|
||||
mask = torch.ones(latent['samples'].shape[2:], dtype=torch.float32, device="cpu").unsqueeze(0)
|
||||
res = nodes.PreviewImage().save_images(decoded_image, filename_prefix="PreviewBridge/PBL-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
if restore_mask != "never":
|
||||
mask = core.preview_bridge_last_mask_cache.get(unique_id)
|
||||
if mask is None or (restore_mask != "always" and mask.shape[1:] != decoded_image.shape[1:3]):
|
||||
mask = None
|
||||
else:
|
||||
mask = None
|
||||
|
||||
if mask is None:
|
||||
mask = torch.ones(latent['samples'].shape[2:], dtype=torch.float32, device="cpu").unsqueeze(0)
|
||||
res = nodes.PreviewImage().save_images(decoded_image, filename_prefix="PreviewBridge/PBL-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
else:
|
||||
masked_images = tensor_convert_rgba(decoded_image)
|
||||
resized_mask = resize_mask(mask, (decoded_image.shape[1], decoded_image.shape[2])).unsqueeze(3)
|
||||
resized_mask = 1 - resized_mask
|
||||
tensor_putalpha(masked_images, resized_mask)
|
||||
res = nodes.PreviewImage().save_images(masked_images, filename_prefix="PreviewBridge/PBL-", prompt=prompt, extra_pnginfo=extra_pnginfo)
|
||||
|
||||
res_image = res['ui']['images']
|
||||
|
||||
is_empty_mask = torch.all(mask == 1)
|
||||
|
||||
path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', res_image[0]['filename'])
|
||||
core.set_previewbridge_image(unique_id, path, res_image[0])
|
||||
core.preview_bridge_image_id_map[image] = (path, res_image[0])
|
||||
@@ -300,7 +372,19 @@ class PreviewBridgeLatent:
|
||||
|
||||
res_latent = latent
|
||||
|
||||
if block and is_empty_mask and core.is_execution_model_version_supported():
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
result = ExecutionBlocker(None), ExecutionBlocker(None)
|
||||
elif block and is_empty_mask:
|
||||
print(f"[Impact Pack] PreviewBridgeLatent: ComfyUI is outdated - blocking feature is disabled.")
|
||||
result = res_latent, mask
|
||||
else:
|
||||
result = res_latent, mask
|
||||
|
||||
if not is_empty_mask:
|
||||
core.preview_bridge_last_mask_cache[unique_id] = mask
|
||||
|
||||
return {
|
||||
"ui": {"images": res_image},
|
||||
"result": (res_latent, mask, ),
|
||||
"result": result,
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import configparser
|
||||
import os
|
||||
|
||||
version_code = [7, 2, 1]
|
||||
version_code = [8, 0, 1]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 22
|
||||
dependency_version = 24
|
||||
|
||||
my_path = os.path.dirname(__file__)
|
||||
old_config_path = os.path.join(my_path, "impact-pack.ini")
|
||||
|
||||
+64
-24
@@ -11,6 +11,7 @@ from impact.utils import *
|
||||
from collections import namedtuple
|
||||
import numpy as np
|
||||
from skimage.measure import label
|
||||
from PIL import ImageOps
|
||||
|
||||
import nodes
|
||||
import comfy_extras.nodes_upscale_model as model_upscale
|
||||
@@ -24,6 +25,8 @@ from comfy import model_management
|
||||
from impact import utils
|
||||
from impact import impact_sampling
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import inspect
|
||||
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
@@ -39,10 +42,13 @@ SEG = namedtuple("SEG",
|
||||
pb_id_cnt = time.time()
|
||||
preview_bridge_image_id_map = {}
|
||||
preview_bridge_image_name_map = {}
|
||||
|
||||
preview_bridge_cache = {}
|
||||
preview_bridge_last_mask_cache = {}
|
||||
|
||||
current_prompt = None
|
||||
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]']
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]']
|
||||
|
||||
|
||||
def is_execution_model_version_supported():
|
||||
@@ -64,6 +70,13 @@ def set_previewbridge_image(node_id, file, item):
|
||||
pb_id = f"${node_id}-{pb_id_cnt}"
|
||||
preview_bridge_image_id_map[pb_id] = (file, item)
|
||||
preview_bridge_image_name_map[node_id, file] = (pb_id, item)
|
||||
if os.path.isfile(file):
|
||||
i = Image.open(file)
|
||||
i = ImageOps.exif_transpose(i)
|
||||
if 'A' in i.getbands():
|
||||
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
|
||||
mask = 1. - torch.from_numpy(mask)
|
||||
preview_bridge_last_mask_cache[node_id] = mask.unsqueeze(0)
|
||||
pb_id_cnt += 1
|
||||
|
||||
return pb_id
|
||||
@@ -237,7 +250,7 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
noise_mask = noise_mask.squeeze(3)
|
||||
|
||||
if noise_mask_feather > 0:
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
if wildcard_opt is not None and wildcard_opt != "":
|
||||
@@ -314,7 +327,12 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
|
||||
# prepare mask
|
||||
if noise_mask is not None and inpaint_model:
|
||||
positive, negative, latent_image = nodes.InpaintModelConditioning().encode(positive, negative, upscaled_image, vae, noise_mask)
|
||||
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:
|
||||
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:
|
||||
@@ -383,7 +401,7 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
noise_mask = noise_mask.squeeze(3)
|
||||
|
||||
if noise_mask_feather > 0:
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
if wildcard_opt is not None and wildcard_opt != "":
|
||||
@@ -1349,9 +1367,14 @@ def segs_to_masklist(segs):
|
||||
return masks
|
||||
|
||||
|
||||
def vae_decode(vae, samples, use_tile, hook, tile_size=512):
|
||||
def vae_decode(vae, samples, use_tile, hook, tile_size=512, overlap=64):
|
||||
if use_tile:
|
||||
pixels = nodes.VAEDecodeTiled().decode(vae, samples, tile_size)[0]
|
||||
decoder = nodes.VAEDecodeTiled()
|
||||
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.")
|
||||
pixels = decoder.decode(vae, samples, tile_size)[0]
|
||||
else:
|
||||
pixels = nodes.VAEDecode().decode(vae, samples)[0]
|
||||
|
||||
@@ -1373,12 +1396,12 @@ def vae_encode(vae, pixels, use_tile, hook, tile_size=512):
|
||||
return samples
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_shape(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None):
|
||||
return latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile, tile_size, save_temp_prefix, hook)[0]
|
||||
def latent_upscale_on_pixel_space_shape(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
return latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0]
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size)
|
||||
def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap)
|
||||
|
||||
if save_temp_prefix is not None:
|
||||
nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix)
|
||||
@@ -1392,12 +1415,12 @@ def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_t
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), 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):
|
||||
return latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook)[0]
|
||||
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):
|
||||
return latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0]
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size)
|
||||
def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap)
|
||||
|
||||
if save_temp_prefix is not None:
|
||||
nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix)
|
||||
@@ -1413,12 +1436,12 @@ def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), 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):
|
||||
return latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile, tile_size, save_temp_prefix, hook)[0]
|
||||
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):
|
||||
return latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0]
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size)
|
||||
def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap)
|
||||
|
||||
if save_temp_prefix is not None:
|
||||
nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix)
|
||||
@@ -1445,12 +1468,12 @@ def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upsca
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_with_model(samples, scale_method, upscale_model, scale_factor, vae, use_tile=False,
|
||||
tile_size=512, save_temp_prefix=None, hook=None):
|
||||
return latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_model, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook)[0]
|
||||
tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
return latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_model, scale_factor, vae, use_tile, tile_size, save_temp_prefix, hook, overlap=overlap)[0]
|
||||
|
||||
def latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_model, scale_factor, vae, use_tile=False,
|
||||
tile_size=512, save_temp_prefix=None, hook=None):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size)
|
||||
tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
pixels = vae_decode(vae, samples, use_tile, hook, tile_size=tile_size, overlap=overlap)
|
||||
|
||||
if save_temp_prefix is not None:
|
||||
nodes.PreviewImage().save_images(pixels, filename_prefix=save_temp_prefix)
|
||||
@@ -1680,6 +1703,9 @@ class PixelKSampleUpscaler:
|
||||
self.hook.pre_ksample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
upscaled_latent, denoise)
|
||||
|
||||
if 'noise_mask' in samples:
|
||||
upscaled_latent['noise_mask'] = samples['noise_mask']
|
||||
|
||||
refined_latent = self.sample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise, upscaled_images)
|
||||
return refined_latent
|
||||
|
||||
@@ -1709,6 +1735,9 @@ class PixelKSampleUpscaler:
|
||||
self.hook.pre_ksample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
upscaled_latent, denoise)
|
||||
|
||||
if 'noise_mask' in samples:
|
||||
upscaled_latent['noise_mask'] = samples['noise_mask']
|
||||
|
||||
refined_latent = self.sample(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, upscaled_latent, denoise, upscaled_images)
|
||||
return refined_latent
|
||||
|
||||
@@ -1819,13 +1848,14 @@ class ControlNetWrapper:
|
||||
|
||||
class ControlNetAdvancedWrapper:
|
||||
def __init__(self, control_net, strength, start_percent, end_percent, preprocessor, prev_control_net=None,
|
||||
original_size=None, crop_region=None, control_image=None):
|
||||
original_size=None, crop_region=None, control_image=None, vae=None):
|
||||
self.control_net = control_net
|
||||
self.strength = strength
|
||||
self.preprocessor = preprocessor
|
||||
self.prev_control_net = prev_control_net
|
||||
self.start_percent = start_percent
|
||||
self.end_percent = end_percent
|
||||
self.vae = vae
|
||||
|
||||
if original_size is not None and crop_region is not None and control_image is not None:
|
||||
self.control_image = utils.tensor_resize(control_image, original_size[1], original_size[0])
|
||||
@@ -1866,7 +1896,17 @@ class ControlNetAdvancedWrapper:
|
||||
"To use 'ControlNetAdvancedWrapper' for AnimateDiff, 'ComfyUI-Advanced-ControlNet' extension is required.")
|
||||
raise Exception("'ACN_AdvancedControlNetApply' node isn't installed.")
|
||||
else:
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_image, self.strength, self.start_percent, self.end_percent)
|
||||
if self.vae is not None:
|
||||
apply_controlnet = nodes.ControlNetApplyAdvanced().apply_controlnet
|
||||
signature = inspect.signature(apply_controlnet)
|
||||
|
||||
if 'vae' in signature.parameters:
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_image, self.strength, self.start_percent, self.end_percent, vae=self.vae)
|
||||
else:
|
||||
print(f"[Impact Pack] ERROR: The ComfyUI version is outdated. VAE cannot be used in ApplyControlNet.")
|
||||
raise Exception("[Impact Pack] ERROR: The ComfyUI version is outdated. VAE cannot be used in ApplyControlNet.")
|
||||
else:
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive, negative, self.control_net, cnet_image, self.strength, self.start_percent, self.end_percent)
|
||||
|
||||
return positive, negative, cnet_image_list
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ class PreviewDetailerHookProvider:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
NOT_IDEMPOTENT = True
|
||||
|
||||
def doit(self, quality, unique_id):
|
||||
hook = hooks.PreviewDetailerHook(unique_id, quality)
|
||||
return (hook, hook)
|
||||
return hook, hook
|
||||
|
||||
+119
-28
@@ -29,6 +29,14 @@ import impact.wildcards as wildcards
|
||||
from . import hooks
|
||||
from . import utils
|
||||
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
warnings.filterwarnings('ignore', category=UserWarning, message='TypedStorage is deprecated')
|
||||
|
||||
model_path = folder_paths.models_dir
|
||||
@@ -62,10 +70,10 @@ class CLIPSegDetectorProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text": ("STRING", {"multiline": False}),
|
||||
"blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 7}),
|
||||
"threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.4}),
|
||||
"dilation_factor": ("INT", {"min": 0, "max": 10, "step": 1, "default": 4}),
|
||||
"text": ("STRING", {"multiline": False, "tooltip": "Enter the targets to be detected, separated by commas"}),
|
||||
"blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 7, "tooltip": "Blurs the detected mask"}),
|
||||
"threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.4, "tooltip": "Detects only areas that are certain above the threshold."}),
|
||||
"dilation_factor": ("INT", {"min": 0, "max": 10, "step": 1, "default": 4, "tooltip": "Dilates the detected mask."}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +82,8 @@ class CLIPSegDetectorProvider:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "Provides a detection function using CLIPSeg, which generates masks based on text prompts.\nTo use this node, the CLIPSeg custom node must be installed."
|
||||
|
||||
def doit(self, text, blur, threshold, dilation_factor):
|
||||
if "CLIPSeg" in nodes.NODE_CLASS_MAPPINGS:
|
||||
return (core.BBoxDetectorBasedOnCLIPSeg(text, blur, threshold, dilation_factor), )
|
||||
@@ -87,8 +97,10 @@ class SAMLoader:
|
||||
models = [x for x in folder_paths.get_filename_list("sams") if 'hq' not in x]
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (models + ['ESAM'], ),
|
||||
"device_mode": (["AUTO", "Prefer GPU", "CPU"],),
|
||||
"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."}),
|
||||
"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."}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +109,8 @@ class SAMLoader:
|
||||
|
||||
CATEGORY = "ImpactPack"
|
||||
|
||||
DESCRIPTION = "Load the SAM (Segment Anything) model. This can be used in places that utilize SAM detection functionality, such as SAMDetector or SimpleDetector.\nThe SAM detection functionality in Impact Pack must use the SAM_MODEL loaded through this node."
|
||||
|
||||
def load_model(self, model_name, device_mode="auto"):
|
||||
if model_name == 'ESAM':
|
||||
if 'ESAM_ModelLoader_Zho' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
@@ -211,6 +225,10 @@ class DetailerForEach:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
@staticmethod
|
||||
def get_core_module():
|
||||
return core
|
||||
|
||||
@staticmethod
|
||||
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,
|
||||
@@ -238,14 +256,22 @@ class DetailerForEach:
|
||||
else:
|
||||
wmode, wildcard_chooser = None, None
|
||||
|
||||
if wmode in ['ASC', 'DSC']:
|
||||
if wmode in ['ASC', 'DSC', 'ASC-SIZE', 'DSC-SIZE']:
|
||||
if wmode == 'ASC':
|
||||
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1]))
|
||||
else:
|
||||
elif wmode == 'DSC':
|
||||
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[0], x.bbox[1]), reverse=True)
|
||||
elif wmode == 'ASC-SIZE':
|
||||
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]))
|
||||
|
||||
else: # wmode == 'DSC-SIZE'
|
||||
ordered_segs = sorted(segs[1], key=lambda x: (x.bbox[2]-x.bbox[0]) * (x.bbox[3]-x.bbox[1]), reverse=True)
|
||||
else:
|
||||
ordered_segs = segs[1]
|
||||
|
||||
if 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)
|
||||
@@ -291,6 +317,13 @@ class DetailerForEach:
|
||||
# Negative Conditioning is placeholder such as FLUX.1
|
||||
cropped_negative = negative
|
||||
|
||||
if wildcard_item and wildcard_item.strip() == '[SKIP]':
|
||||
continue
|
||||
|
||||
if wildcard_item and wildcard_item.strip() == '[STOP]':
|
||||
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,
|
||||
@@ -310,7 +343,7 @@ class DetailerForEach:
|
||||
# use image paste
|
||||
image = image.cpu()
|
||||
enhanced_image = enhanced_image.cpu()
|
||||
tensor_paste(image, enhanced_image, (seg.crop_region[0], seg.crop_region[1]), mask)
|
||||
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:
|
||||
@@ -328,7 +361,7 @@ class DetailerForEach:
|
||||
else:
|
||||
new_seg_image = None
|
||||
|
||||
cropped_list.append(cropped_image)
|
||||
cropped_list.append(orig_cropped_image) # NOTE: Don't use `cropped_image`
|
||||
|
||||
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)
|
||||
@@ -1221,6 +1254,7 @@ class IterativeLatentUpscale:
|
||||
upscale_factor_unit = max(0, (upscale_factor - 1.0) / steps)
|
||||
|
||||
current_latent = samples
|
||||
noise_mask = current_latent.get('noise_mask')
|
||||
scale = 1
|
||||
|
||||
for i in range(steps-1):
|
||||
@@ -1235,6 +1269,8 @@ class IterativeLatentUpscale:
|
||||
print(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:
|
||||
current_latent['noise_mask'] = noise_mask
|
||||
|
||||
if scale < upscale_factor:
|
||||
new_w = w*upscale_factor
|
||||
@@ -1246,7 +1282,7 @@ class IterativeLatentUpscale:
|
||||
|
||||
core.update_node_status(unique_id, "", None)
|
||||
|
||||
return (current_latent, upscaler.vae)
|
||||
return current_latent, upscaler.vae
|
||||
|
||||
|
||||
class IterativeImageUpscale:
|
||||
@@ -1629,6 +1665,8 @@ class BitwiseAndMaskForEach:
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
|
||||
DESCRIPTION = "Retains only the overlapping areas between the masks included in base_segs and the mask regions of mask_segs. SEGS with no overlapping mask areas are filtered out."
|
||||
|
||||
def doit(self, base_segs, mask_segs):
|
||||
mask = core.segs_to_combined_mask(mask_segs)
|
||||
mask = make_3d_mask(mask)
|
||||
@@ -1650,6 +1688,8 @@ class SubtractMaskForEach:
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
|
||||
DESCRIPTION = "Removes only the overlapping areas between the masks included in base_segs and the mask regions of mask_segs. SEGS with no overlapping mask areas are filtered out."
|
||||
|
||||
def doit(self, base_segs, mask_segs):
|
||||
mask = core.segs_to_combined_mask(mask_segs)
|
||||
mask = make_3d_mask(mask)
|
||||
@@ -1675,6 +1715,25 @@ class ToBinaryMask:
|
||||
return (mask,)
|
||||
|
||||
|
||||
class FlattenMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"masks": ("MASK",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
|
||||
def doit(self, masks):
|
||||
masks = utils.make_3d_mask(masks)
|
||||
masks = utils.flatten_mask(masks)
|
||||
return (masks,)
|
||||
|
||||
|
||||
class BitwiseAndMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1972,7 +2031,12 @@ class LatentSender(nodes.SaveLatent):
|
||||
"samples": ("LATENT", ),
|
||||
"filename_prefix": ("STRING", {"default": "latents/LatentSender"}),
|
||||
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
"preview_method": (["Latent2RGB-SDXL", "Latent2RGB-SD15", "TAESDXL", "TAESD15"],)
|
||||
"preview_method": (["Latent2RGB-FLUX.1",
|
||||
"Latent2RGB-SDXL", "Latent2RGB-SD15", "Latent2RGB-SD3",
|
||||
"Latent2RGB-SD-X4", "Latent2RGB-Playground-2.5",
|
||||
"Latent2RGB-SC-Prior", "Latent2RGB-SC-B",
|
||||
"Latent2RGB-LTXV",
|
||||
"TAEF1", "TAESDXL", "TAESD15", "TAESD3"],)
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
@@ -2014,14 +2078,33 @@ class LatentSender(nodes.SaveLatent):
|
||||
if preview_method == "Latent2RGB-SD15":
|
||||
latent_format = latent_formats.SD15()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "TAESD15":
|
||||
elif preview_method == "Latent2RGB-SDXL":
|
||||
latent_format = latent_formats.SDXL()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-SD3":
|
||||
latent_format = latent_formats.SD3()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-SD-X4":
|
||||
latent_format = latent_formats.SD_X4()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-Playground-2.5":
|
||||
latent_format = latent_formats.SDXL_Playground_2_5()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-SC-Prior":
|
||||
latent_format = latent_formats.SC_Prior()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-SC-B":
|
||||
latent_format = latent_formats.SC_B()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-FLUX.1":
|
||||
latent_format = latent_formats.Flux()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
elif preview_method == "Latent2RGB-LTXV":
|
||||
latent_format = latent_formats.LTXV()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
else:
|
||||
print(f"[Impact Pack] LatentSender: '{preview_method}' is unsupported preview method.")
|
||||
latent_format = latent_formats.SD15()
|
||||
method = LatentPreviewMethod.TAESD
|
||||
elif preview_method == "TAESDXL":
|
||||
latent_format = latent_formats.SDXL()
|
||||
method = LatentPreviewMethod.TAESD
|
||||
else: # preview_method == "Latent2RGB-SDXL"
|
||||
latent_format = latent_formats.SDXL()
|
||||
method = LatentPreviewMethod.Latent2RGB
|
||||
|
||||
previewer = core.get_previewer("cpu", latent_format=latent_format, force=True, method=method)
|
||||
@@ -2095,16 +2178,19 @@ class ImpactWildcardProcessor:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||
"populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"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."}),
|
||||
"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"],),
|
||||
},
|
||||
}
|
||||
|
||||
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'.")
|
||||
|
||||
RETURN_TYPES = ("STRING", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
@@ -2123,17 +2209,22 @@ class ImpactWildcardEncode:
|
||||
return {"required": {
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP",),
|
||||
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||
"populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed"}),
|
||||
"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."}),
|
||||
"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}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Determines the random seed to be used for wildcard processing."}),
|
||||
},
|
||||
}
|
||||
|
||||
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"
|
||||
"TIP2: If the 'Inspire Pack' is installed, LBW(LoRA Block Weight) syntax can also be applied.")
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP", "CONDITIONING", "STRING")
|
||||
RETURN_NAMES = ("model", "clip", "conditioning", "populated_text")
|
||||
FUNCTION = "doit"
|
||||
@@ -2158,7 +2249,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]'],),
|
||||
"extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]'],),
|
||||
}}
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@@ -27,6 +27,8 @@ def calculate_sigmas(model, sampler, scheduler, steps):
|
||||
sigmas = nodes.NODE_CLASS_MAPPINGS['AlignYourStepsScheduler']().get_sigmas(scheduler[4:], steps, denoise=1.0)[0]
|
||||
elif scheduler.startswith('GITS[coeff='):
|
||||
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]
|
||||
else:
|
||||
sigmas = samplers.calculate_sigmas(model.get_model_object("model_sampling"), scheduler, steps)
|
||||
|
||||
|
||||
@@ -24,37 +24,6 @@ import random
|
||||
from server import PromptServer
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/upload/temp")
|
||||
async def upload_image(request):
|
||||
upload_dir = folder_paths.get_temp_directory()
|
||||
|
||||
if not os.path.exists(upload_dir):
|
||||
os.makedirs(upload_dir)
|
||||
|
||||
post = await request.post()
|
||||
image = post.get("image")
|
||||
|
||||
if image and image.file:
|
||||
filename = image.filename
|
||||
if not filename:
|
||||
return web.Response(status=400)
|
||||
|
||||
split = os.path.splitext(filename)
|
||||
i = 1
|
||||
while os.path.exists(os.path.join(upload_dir, filename)):
|
||||
filename = f"{split[0]} ({i}){split[1]}"
|
||||
i += 1
|
||||
|
||||
filepath = os.path.join(upload_dir, filename)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(image.file.read())
|
||||
|
||||
return web.json_response({"name": filename})
|
||||
else:
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
sam_predictor = None
|
||||
default_sam_model_name = os.path.join(impact_pack.model_path, "sams", "sam_vit_b_01ec64.pth")
|
||||
|
||||
@@ -346,6 +315,8 @@ def onprompt_for_switch(json_data):
|
||||
inversed_switch_info = {}
|
||||
onprompt_switch_info = {}
|
||||
onprompt_cond_branch_info = {}
|
||||
disabled_switch = set()
|
||||
|
||||
|
||||
for k, v in json_data['prompt'].items():
|
||||
if 'class_type' not in v:
|
||||
@@ -379,6 +350,10 @@ def onprompt_for_switch(json_data):
|
||||
else:
|
||||
onprompt_switch_info[k] = select_input
|
||||
|
||||
if k in onprompt_switch_info and f'input{onprompt_switch_info[k]}' not in v['inputs']:
|
||||
# disconnect output
|
||||
disabled_switch.add(k)
|
||||
|
||||
elif cls == 'ImpactConditionalBranchSelMode':
|
||||
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'cond' in v['inputs']:
|
||||
cond_input = v['inputs']['cond']
|
||||
@@ -403,6 +378,9 @@ def onprompt_for_switch(json_data):
|
||||
if vv[1] + 1 != inversed_switch_info[vv[0]]:
|
||||
disable_targets.add(kk)
|
||||
|
||||
if vv[0] in disabled_switch:
|
||||
disable_targets.add(kk)
|
||||
|
||||
if k in onprompt_switch_info:
|
||||
selected_slot_name = f"input{onprompt_switch_info[k]}"
|
||||
for kk, vv in v['inputs'].items():
|
||||
@@ -440,9 +418,14 @@ def gc_preview_bridge_cache(json_data):
|
||||
|
||||
for key in list(core.preview_bridge_cache.keys()):
|
||||
if key not in prompt_keys:
|
||||
print(f"key deleted: {key}")
|
||||
# print(f"key deleted [PB]: {key}")
|
||||
del core.preview_bridge_cache[key]
|
||||
|
||||
for key in list(core.preview_bridge_last_mask_cache.keys()):
|
||||
if key not in prompt_keys:
|
||||
# print(f"key deleted [PB_last_mask]: {key}")
|
||||
del core.preview_bridge_last_mask_cache[key]
|
||||
|
||||
|
||||
def workflow_imagereceiver_update(json_data):
|
||||
prompt = json_data['prompt']
|
||||
|
||||
+119
-60
@@ -10,7 +10,6 @@ import re
|
||||
import nodes
|
||||
import traceback
|
||||
|
||||
|
||||
class ImpactCompare:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -273,6 +272,24 @@ class ImpactFloat:
|
||||
return (value, )
|
||||
|
||||
|
||||
class ImpactBoolean:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
}
|
||||
|
||||
FUNCTION = "doit"
|
||||
CATEGORY = "ImpactPack/Logic"
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN", )
|
||||
|
||||
def doit(self, value):
|
||||
return (value, )
|
||||
|
||||
|
||||
class ImpactValueSender:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -631,85 +648,104 @@ class ImpactControlBridge:
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {
|
||||
"value": (any_typ,),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Active", "label_off": "Mute/Bypass"}),
|
||||
"behavior": ("BOOLEAN", {"default": True, "label_on": "Mute", "label_off": "Bypass"}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Active", "label_off": "Stop/Mute/Bypass"}),
|
||||
"behavior": (["Stop", "Mute", "Bypass"], ),
|
||||
},
|
||||
"hidden": {"unique_id": "UNIQUE_ID", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}
|
||||
}
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Logic/_for_test"
|
||||
CATEGORY = "ImpactPack/Logic"
|
||||
RETURN_TYPES = (any_typ,)
|
||||
RETURN_NAMES = ("value",)
|
||||
OUTPUT_NODE = True
|
||||
|
||||
DESCRIPTION = ("When behavior is Stop and mode is active, the input value is passed directly to the output.\n"
|
||||
"When behavior is Mute/Bypass and mode is active, the node connected to the output is changed to active state.\n"
|
||||
"When behavior is Stop and mode is Stop/Mute/Bypass, the workflow execution of the current node is halted.\n"
|
||||
"When behavior is Mute/Bypass and mode is Stop/Mute/Bypass, the node connected to the output is changed to Mute/Bypass state.")
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(self, value, mode, behavior=True, unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
# NOTE: extra_pnginfo is not populated for IS_CHANGED.
|
||||
# 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']")
|
||||
return 0
|
||||
def IS_CHANGED(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
if behavior == "Stop":
|
||||
return value, mode, behavior
|
||||
else:
|
||||
# NOTE: extra_pnginfo is not populated for IS_CHANGED.
|
||||
# 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']")
|
||||
return 0
|
||||
|
||||
nodes, links = workflow_to_map(workflow)
|
||||
next_nodes = []
|
||||
nodes, links = workflow_to_map(workflow)
|
||||
next_nodes = []
|
||||
|
||||
for link in nodes[unique_id]['outputs'][0]['links']:
|
||||
node_id = str(links[link][2])
|
||||
impact.utils.collect_non_reroute_nodes(nodes, links, next_nodes, node_id)
|
||||
for link in nodes[unique_id]['outputs'][0]['links']:
|
||||
node_id = str(links[link][2])
|
||||
impact.utils.collect_non_reroute_nodes(nodes, links, next_nodes, node_id)
|
||||
|
||||
return next_nodes
|
||||
|
||||
def doit(self, value, mode, behavior=True, unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
def doit(self, value, mode, behavior="Stop", unique_id=None, prompt=None, extra_pnginfo=None):
|
||||
global error_skip_flag
|
||||
|
||||
workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow'])
|
||||
|
||||
active_nodes = []
|
||||
mute_nodes = []
|
||||
bypass_nodes = []
|
||||
|
||||
for link in workflow_nodes[unique_id]['outputs'][0]['links']:
|
||||
node_id = str(links[link][2])
|
||||
|
||||
next_nodes = []
|
||||
impact.utils.collect_non_reroute_nodes(workflow_nodes, links, next_nodes, node_id)
|
||||
|
||||
for next_node_id in next_nodes:
|
||||
node_mode = workflow_nodes[next_node_id]['mode']
|
||||
|
||||
if node_mode == 0:
|
||||
active_nodes.append(next_node_id)
|
||||
elif node_mode == 2:
|
||||
mute_nodes.append(next_node_id)
|
||||
elif node_mode == 4:
|
||||
bypass_nodes.append(next_node_id)
|
||||
|
||||
if mode:
|
||||
# active
|
||||
should_be_active_nodes = mute_nodes + bypass_nodes
|
||||
if len(should_be_active_nodes) > 0:
|
||||
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'actives': list(should_be_active_nodes)})
|
||||
nodes.interrupt_processing()
|
||||
|
||||
elif behavior:
|
||||
# mute
|
||||
should_be_mute_nodes = active_nodes + bypass_nodes
|
||||
if len(should_be_mute_nodes) > 0:
|
||||
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'mutes': list(should_be_mute_nodes)})
|
||||
nodes.interrupt_processing()
|
||||
|
||||
if core.is_execution_model_version_supported():
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
else:
|
||||
# bypass
|
||||
should_be_bypass_nodes = active_nodes + mute_nodes
|
||||
if len(should_be_bypass_nodes) > 0:
|
||||
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'bypasses': list(should_be_bypass_nodes)})
|
||||
nodes.interrupt_processing()
|
||||
print("[Impact Pack] ImpactControlBridge: ComfyUI is outdated. The 'Stop' behavior cannot function properly.")
|
||||
|
||||
return (value, )
|
||||
if behavior == "Stop":
|
||||
if mode:
|
||||
return (value, )
|
||||
else:
|
||||
return (ExecutionBlocker(None), )
|
||||
else:
|
||||
workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow'])
|
||||
|
||||
active_nodes = []
|
||||
mute_nodes = []
|
||||
bypass_nodes = []
|
||||
|
||||
for link in workflow_nodes[unique_id]['outputs'][0]['links']:
|
||||
node_id = str(links[link][2])
|
||||
|
||||
next_nodes = []
|
||||
impact.utils.collect_non_reroute_nodes(workflow_nodes, links, next_nodes, node_id)
|
||||
|
||||
for next_node_id in next_nodes:
|
||||
node_mode = workflow_nodes[next_node_id]['mode']
|
||||
|
||||
if node_mode == 0:
|
||||
active_nodes.append(next_node_id)
|
||||
elif node_mode == 2:
|
||||
mute_nodes.append(next_node_id)
|
||||
elif node_mode == 4:
|
||||
bypass_nodes.append(next_node_id)
|
||||
|
||||
if mode:
|
||||
# active
|
||||
should_be_active_nodes = mute_nodes + bypass_nodes
|
||||
if len(should_be_active_nodes) > 0:
|
||||
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:
|
||||
# mute
|
||||
should_be_mute_nodes = active_nodes + bypass_nodes
|
||||
if len(should_be_mute_nodes) > 0:
|
||||
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'mutes': list(should_be_mute_nodes)})
|
||||
nodes.interrupt_processing()
|
||||
|
||||
else:
|
||||
# bypass
|
||||
should_be_bypass_nodes = active_nodes + mute_nodes
|
||||
if len(should_be_bypass_nodes) > 0:
|
||||
PromptServer.instance.send_sync("impact-bridge-continue", {"node_id": unique_id, 'bypasses': list(should_be_bypass_nodes)})
|
||||
nodes.interrupt_processing()
|
||||
|
||||
return (value, )
|
||||
|
||||
|
||||
class ImpactExecutionOrderController:
|
||||
@@ -730,6 +766,29 @@ class ImpactExecutionOrderController:
|
||||
return signal, value
|
||||
|
||||
|
||||
class ImpactListBridge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {
|
||||
"list_input": (any_typ,),
|
||||
}}
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
DESCRIPTION = "When passing the list output through this node, it collects and organizes the data before forwarding it, which ensures that the previous stage's sub-workflow has been completed."
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
RETURN_TYPES = (any_typ, )
|
||||
RETURN_NAMES = ("list_output", )
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, )
|
||||
|
||||
@staticmethod
|
||||
def doit(list_input):
|
||||
return (list_input,)
|
||||
|
||||
|
||||
original_handle_execution = execution.PromptExecutor.handle_execution_error
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import folder_paths
|
||||
import impact.wildcards
|
||||
from impact.utils import any_typ
|
||||
|
||||
|
||||
class ToDetailerPipe:
|
||||
@classmethod
|
||||
@@ -108,6 +110,23 @@ class FromDetailerPipe_SDXL:
|
||||
return detailer_pipe, model, clip, vae, positive, negative, bbox_detector, sam_model_opt, segm_detector_opt, detailer_hook, refiner_model, refiner_clip, refiner_positive, refiner_negative
|
||||
|
||||
|
||||
class AnyPipeToBasic:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {"any_pipe": (any_typ,)},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BASIC_PIPE", )
|
||||
RETURN_NAMES = ("basic_pipe", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Pipe"
|
||||
|
||||
def doit(self, any_pipe):
|
||||
return (any_pipe[:5], )
|
||||
|
||||
|
||||
class ToBasicPipe:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
@@ -14,6 +14,13 @@ from comfy.cli_args import args
|
||||
import math
|
||||
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
except Exception:
|
||||
print(f"\n#############################################\n[Impact Pack] ComfyUI is an outdated version.\n#############################################\n")
|
||||
raise Exception("[Impact Pack] ComfyUI is an outdated version.")
|
||||
|
||||
|
||||
class SEGSDetailer:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -69,6 +76,9 @@ class SEGSDetailer:
|
||||
new_segs = []
|
||||
cnet_pil_list = []
|
||||
|
||||
if 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]:
|
||||
@@ -694,6 +704,68 @@ class SEGSToMaskBatch:
|
||||
return (mask_batch,)
|
||||
|
||||
|
||||
class SEGSMerge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "SEGS contains multiple SEGs. SEGS Merge integrates several SEGs into a single merged SEG. The label is changed to `merged` and the confidence becomes the minimum confidence. The applied controlnet and cropped_image are removed."
|
||||
|
||||
def doit(self, segs):
|
||||
crop_left = sys.maxsize
|
||||
crop_right = 0
|
||||
crop_top = sys.maxsize
|
||||
crop_bottom = 0
|
||||
|
||||
bbox_left = sys.maxsize
|
||||
bbox_right = 0
|
||||
bbox_top = sys.maxsize
|
||||
bbox_bottom = 0
|
||||
|
||||
min_confidence = 1.0
|
||||
|
||||
for seg in segs[1]:
|
||||
cx1 = seg.crop_region[0]
|
||||
cy1 = seg.crop_region[1]
|
||||
cx2 = seg.crop_region[2]
|
||||
cy2 = seg.crop_region[3]
|
||||
|
||||
bx1 = seg.bbox[0]
|
||||
by1 = seg.bbox[1]
|
||||
bx2 = seg.bbox[2]
|
||||
by2 = seg.bbox[3]
|
||||
|
||||
crop_left = min(crop_left, cx1)
|
||||
crop_top = min(crop_top, cy1)
|
||||
crop_right = max(crop_right, cx2)
|
||||
crop_bottom = max(crop_bottom, cy2)
|
||||
|
||||
bbox_left = min(bbox_left, bx1)
|
||||
bbox_top = min(bbox_top, by1)
|
||||
bbox_right = max(bbox_right, bx2)
|
||||
bbox_bottom = max(bbox_bottom, by2)
|
||||
|
||||
min_confidence = min(min_confidence, seg.confidence)
|
||||
|
||||
combined_mask = core.segs_to_combined_mask(segs)
|
||||
cropped_mask = combined_mask[crop_top:crop_bottom, crop_left:crop_right]
|
||||
cropped_mask = cropped_mask.unsqueeze(0)
|
||||
|
||||
crop_region = [crop_left, crop_top, crop_right, crop_bottom]
|
||||
bbox = [bbox_left, bbox_top, bbox_right, bbox_bottom]
|
||||
|
||||
seg = SEG(None, cropped_mask, min_confidence, crop_region, bbox, 'merged', None)
|
||||
return ((segs[0], [seg]),)
|
||||
|
||||
|
||||
class SEGSConcat:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -824,7 +896,7 @@ class From_SEG_ELT_bbox:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, bbox):
|
||||
return bbox
|
||||
return [int(c) for c in bbox]
|
||||
|
||||
|
||||
class From_SEG_ELT_crop_region:
|
||||
@@ -1029,10 +1101,10 @@ class SEG_ELT_BBOX_ScaleBy:
|
||||
x1, y1, x2, y2 = x1-cx1, y1-cy1, x2-cx1, y2-cy1
|
||||
h, w = mask.shape
|
||||
|
||||
x1 = min(w-1, max(0, x1))
|
||||
x2 = min(w-1, max(0, x2))
|
||||
y1 = min(h-1, max(0, y1))
|
||||
y2 = min(h-1, max(0, y2))
|
||||
x1 = int(min(w-1, max(0, x1)))
|
||||
x2 = int(min(w-1, max(0, x2)))
|
||||
y1 = int(min(h-1, max(0, y1)))
|
||||
y2 = int(min(h-1, max(0, y2)))
|
||||
|
||||
mask_cropped = mask.copy()
|
||||
mask_cropped[:, :x1] = 0 # zero fill left side
|
||||
@@ -1290,6 +1362,8 @@ class ControlNetApplySEGS:
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
DEPRECATED = True
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
@@ -1317,7 +1391,8 @@ class ControlNetApplyAdvancedSEGS:
|
||||
},
|
||||
"optional": {
|
||||
"segs_preprocessor": ("SEGS_PREPROCESSOR",),
|
||||
"control_image": ("IMAGE",)
|
||||
"control_image": ("IMAGE",),
|
||||
"vae": ("VAE",)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1327,13 +1402,13 @@ class ControlNetApplyAdvancedSEGS:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
def doit(segs, control_net, strength, start_percent, end_percent, segs_preprocessor=None, control_image=None):
|
||||
def doit(segs, control_net, strength, start_percent, end_percent, segs_preprocessor=None, control_image=None, vae=None):
|
||||
new_segs = []
|
||||
|
||||
for seg in segs[1]:
|
||||
control_net_wrapper = core.ControlNetAdvancedWrapper(control_net, strength, start_percent, end_percent, segs_preprocessor,
|
||||
seg.control_net_wrapper, original_size=segs[0], crop_region=seg.crop_region,
|
||||
control_image=control_image)
|
||||
control_image=control_image, vae=vae)
|
||||
new_seg = SEG(seg.cropped_image, seg.cropped_mask, seg.confidence, seg.crop_region, seg.bbox, seg.label, control_net_wrapper)
|
||||
new_segs.append(new_seg)
|
||||
|
||||
@@ -1403,8 +1478,6 @@ class SEGSPicker:
|
||||
|
||||
RETURN_TYPES = ("SEGS", )
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@@ -98,7 +98,7 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
noise_mask = tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
noise_mask = noise_mask.squeeze(3)
|
||||
|
||||
if noise_mask_feather > 0:
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
if control_net_wrapper is not None:
|
||||
@@ -106,7 +106,12 @@ def img2img_segs(image, model, clip, vae, seed, steps, cfg, sampler_name, schedu
|
||||
|
||||
# prepare mask
|
||||
if noise_mask is not None and inpaint_model:
|
||||
positive, negative, latent_image = nodes.InpaintModelConditioning().encode(positive, negative, image, vae, noise_mask)
|
||||
imc_encode = nodes.InpaintModelConditioning().encode
|
||||
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.")
|
||||
positive, negative, latent_image = imc_encode(positive, negative, image, vae, noise_mask)
|
||||
else:
|
||||
latent_image = to_latent_image(image, vae)
|
||||
if noise_mask is not None:
|
||||
|
||||
@@ -5,7 +5,7 @@ from impact.utils import *
|
||||
from nodes import MAX_RESOLUTION
|
||||
import nodes
|
||||
from impact.impact_sampling import KSamplerWrapper, KSamplerAdvancedWrapper, separated_sample, impact_sample
|
||||
|
||||
import comfy
|
||||
|
||||
class TiledKSamplerProvider:
|
||||
@classmethod
|
||||
@@ -332,6 +332,10 @@ class RegionalSampler:
|
||||
@staticmethod
|
||||
def doit(seed, seed_2nd, seed_2nd_mode, steps, base_only_steps, denoise, samples, base_sampler, regional_prompts, overlap_factor, restore_latent,
|
||||
additional_mode, additional_sampler, additional_sigma_ratio, unique_id=None):
|
||||
|
||||
samples = samples.copy()
|
||||
samples['samples'] = comfy.sample.fix_empty_latent_channels(base_sampler.params[0], samples['samples'])
|
||||
|
||||
if restore_latent:
|
||||
latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']()
|
||||
else:
|
||||
@@ -476,6 +480,9 @@ class RegionalSamplerAdvanced:
|
||||
def doit(add_noise, noise_seed, steps, start_at_step, end_at_step, overlap_factor, restore_latent, return_with_leftover_noise, latent_image, base_sampler, regional_prompts,
|
||||
additional_mode, additional_sampler, additional_sigma_ratio, unique_id):
|
||||
|
||||
new_latent_image = latent_image.copy()
|
||||
new_latent_image['samples'] = comfy.sample.fix_empty_latent_channels(base_sampler.params[0], new_latent_image['samples'])
|
||||
|
||||
if restore_latent:
|
||||
latent_compositor = nodes.NODE_CLASS_MAPPINGS['LatentCompositeMasked']()
|
||||
else:
|
||||
@@ -491,7 +498,6 @@ class RegionalSamplerAdvanced:
|
||||
end_at_step = min(steps, end_at_step)
|
||||
total = (end_at_step - start_at_step) * region_len
|
||||
|
||||
new_latent_image = latent_image.copy()
|
||||
base_latent_image = None
|
||||
region_masks = {}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from impact.utils import any_typ, ByPassTypeTuple, make_3d_mask
|
||||
import comfy_extras.nodes_mask
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
from nodes import MAX_RESOLUTION
|
||||
import torch
|
||||
import comfy
|
||||
@@ -18,9 +17,16 @@ class GeneralSwitch:
|
||||
dyn_inputs = {"input1": (any_typ, {"lazy": True, "tooltip": "Any input. When connected, one more input slot is added."}), }
|
||||
if core.is_execution_model_version_supported():
|
||||
stack = inspect.stack()
|
||||
if stack[2].function == 'get_input_info' and stack[3].function == 'add_node':
|
||||
for x in range(2, 200):
|
||||
dyn_inputs[f"input{x}"] = (any_typ, {"lazy": True})
|
||||
if stack[2].function == 'get_input_info':
|
||||
# bypass validation
|
||||
class AllContainer:
|
||||
def __contains__(self, item):
|
||||
return True
|
||||
|
||||
def __getitem__(self, key):
|
||||
return any_typ, {"lazy": True}
|
||||
|
||||
dyn_inputs = AllContainer()
|
||||
|
||||
inputs = {"required": {
|
||||
"select": ("INT", {"default": 1, "min": 1, "max": 999999, "step": 1, "tooltip": "The input number you want to output among the inputs"}),
|
||||
@@ -46,7 +52,10 @@ class GeneralSwitch:
|
||||
|
||||
print(f"SELECTED: {input_name}")
|
||||
|
||||
return [input_name]
|
||||
if input_name in kwargs:
|
||||
return [input_name]
|
||||
else:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def doit(*args, **kwargs):
|
||||
@@ -164,8 +173,10 @@ class GeneralInversedSwitch:
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, select, prompt, unique_id, input, **kwargs):
|
||||
if core.is_execution_model_version_supported:
|
||||
if core.is_execution_model_version_supported():
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
else:
|
||||
print("[Impact Pack] InversedSwitch: ComfyUI is outdated. The 'select_on_execution' mode cannot function properly.")
|
||||
|
||||
res = []
|
||||
|
||||
@@ -180,7 +191,7 @@ class GeneralInversedSwitch:
|
||||
for i in range(0, cnt + 1):
|
||||
if select == i+1:
|
||||
res.append(input)
|
||||
elif core.is_execution_model_version_supported:
|
||||
elif core.is_execution_model_version_supported():
|
||||
res.append(ExecutionBlocker(None))
|
||||
else:
|
||||
res.append(None)
|
||||
@@ -365,7 +376,7 @@ class ImageListToImageBatch:
|
||||
|
||||
def doit(self, images):
|
||||
if len(images) <= 1:
|
||||
return (images,)
|
||||
return (images[0],)
|
||||
else:
|
||||
image1 = images[0]
|
||||
for image2 in images[1:]:
|
||||
@@ -391,6 +402,50 @@ class ImageBatchToImageList:
|
||||
return (images, )
|
||||
|
||||
|
||||
class MakeAnyList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {"value1": (any_typ,), }
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ,)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
values = []
|
||||
|
||||
for k, v in kwargs.items():
|
||||
if v is not None:
|
||||
values.append(v)
|
||||
|
||||
return (values, )
|
||||
|
||||
|
||||
class MakeMaskList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"mask1": ("MASK",), }}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
masks = []
|
||||
|
||||
for k, v in kwargs.items():
|
||||
masks.append(v)
|
||||
|
||||
return (masks, )
|
||||
|
||||
|
||||
class MakeImageList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -436,6 +491,31 @@ class MakeImageBatch:
|
||||
return (image1,)
|
||||
|
||||
|
||||
class MakeMaskBatch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"mask1": ("MASK",), }}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def doit(self, **kwargs):
|
||||
mask1 = kwargs['mask1']
|
||||
del kwargs['mask1']
|
||||
masks = [make_3d_mask(value) for value in kwargs.values()]
|
||||
|
||||
if len(masks) == 0:
|
||||
return (mask1,)
|
||||
else:
|
||||
for mask2 in masks:
|
||||
if mask1.shape[1:] != mask2.shape[1:]:
|
||||
mask2 = comfy.utils.common_upscale(mask2.movedim(-1, 1), mask1.shape[2], mask1.shape[1], "lanczos", "center").movedim(1, -1)
|
||||
mask1 = torch.cat((mask1, mask2), dim=0)
|
||||
return (mask1,)
|
||||
|
||||
|
||||
class ReencodeLatent:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
@@ -5,8 +5,7 @@ import numpy as np
|
||||
import folder_paths
|
||||
import nodes
|
||||
from . import config
|
||||
from PIL import Image, ImageFilter
|
||||
from scipy.ndimage import zoom
|
||||
from PIL import Image
|
||||
import comfy
|
||||
|
||||
|
||||
@@ -579,6 +578,14 @@ def apply_mask_alpha_to_pil(decoded_pil, mask):
|
||||
return decoded_rgba
|
||||
|
||||
|
||||
def flatten_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)
|
||||
|
||||
return merged_mask
|
||||
|
||||
|
||||
def try_install_custom_node(custom_node_url, msg):
|
||||
try:
|
||||
import cm_global
|
||||
|
||||
+50
-19
@@ -12,7 +12,7 @@ from impact import config
|
||||
|
||||
wildcards_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "wildcards"))
|
||||
|
||||
RE_WildCardQuantifier = re.compile(r"(?P<quantifier>\d+)#__(?P<keyword>[\w.\-+/*\\]+)__", re.IGNORECASE)
|
||||
RE_WildCardQuantifier = re.compile(r"(?P<quantifier>\d+)#__(?P<keyword>[\w.\-+/*\\]+?)__", re.IGNORECASE)
|
||||
wildcard_lock = threading.Lock()
|
||||
wildcard_dict = {}
|
||||
|
||||
@@ -58,11 +58,11 @@ def read_wildcard_dict(wildcard_path):
|
||||
try:
|
||||
with open(file_path, 'r', encoding="ISO-8859-1") as f:
|
||||
lines = f.read().splitlines()
|
||||
wildcard_dict[key] = lines
|
||||
wildcard_dict[key] = [x for x in lines if not x.strip().startswith('#')]
|
||||
except yaml.reader.ReaderError:
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
lines = f.read().splitlines()
|
||||
wildcard_dict[key] = lines
|
||||
wildcard_dict[key] = [x for x in lines if not x.strip().startswith('#')]
|
||||
elif file.endswith('.yaml'):
|
||||
file_path = os.path.join(root, file)
|
||||
|
||||
@@ -121,7 +121,7 @@ def process(text, seed=None):
|
||||
select_sep = ' '
|
||||
range_pattern = r'(\d+)(-(\d+))?'
|
||||
range_pattern2 = r'-(\d+)'
|
||||
wildcard_pattern = r"__([\w.\-+/*\\]+)__"
|
||||
wildcard_pattern = r"__([\w.\-+/*\\]+?)__"
|
||||
|
||||
if len(multi_select_pattern) > 1:
|
||||
r = re.match(range_pattern, options[0])
|
||||
@@ -150,7 +150,7 @@ def process(text, seed=None):
|
||||
matches = re.findall(wildcard_pattern, multi_select_pattern[1])
|
||||
if len(options) == 1 and matches:
|
||||
# count$$<single wildcard>
|
||||
options = local_wildcard_dict.get(matches[0])
|
||||
options = get_wildcard_options(multi_select_pattern[1])
|
||||
else:
|
||||
# count$$opt1|opt2|...
|
||||
options[0] = multi_select_pattern[1]
|
||||
@@ -199,8 +199,36 @@ def process(text, seed=None):
|
||||
|
||||
return replaced_string, replacements_found
|
||||
|
||||
def get_wildcard_options(string):
|
||||
pattern = r"__([\w.\-+/*\\]+?)__"
|
||||
matches = re.findall(pattern, string)
|
||||
|
||||
options = []
|
||||
|
||||
for match in matches:
|
||||
keyword = match.lower()
|
||||
keyword = wildcard_normalize(keyword)
|
||||
if keyword in local_wildcard_dict:
|
||||
options.extend(local_wildcard_dict[keyword])
|
||||
elif '*' in keyword:
|
||||
subpattern = keyword.replace('*', '.*').replace('+', '\\+')
|
||||
total_patterns = []
|
||||
found = False
|
||||
for k, v in local_wildcard_dict.items():
|
||||
if re.match(subpattern, k) is not None or re.match(subpattern, k+'/') is not None:
|
||||
total_patterns += v
|
||||
found = True
|
||||
|
||||
if found:
|
||||
options.extend(total_patterns)
|
||||
elif '/' not in keyword:
|
||||
string_fallback = string.replace(f"__{match}__", f"__*/{match}__", 1)
|
||||
options.extend(get_wildcard_options(string_fallback))
|
||||
|
||||
return options
|
||||
|
||||
def replace_wildcard(string):
|
||||
pattern = r"__([\w.\-+/*\\]+)__"
|
||||
pattern = r"__([\w.\-+/*\\]+?)__"
|
||||
matches = re.findall(pattern, string)
|
||||
|
||||
replacements_found = False
|
||||
@@ -259,7 +287,7 @@ def process(text, seed=None):
|
||||
|
||||
|
||||
def is_numeric_string(input_str):
|
||||
return re.match(r'^-?\d+(\.\d+)?$', input_str) is not None
|
||||
return re.match(r'^-?(\d*\.?\d+|\d+\.?\d*)$', input_str) is not None
|
||||
|
||||
|
||||
def safe_float(x):
|
||||
@@ -425,7 +453,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
|
||||
def starts_with_regex(pattern, text):
|
||||
regex = re.compile(pattern)
|
||||
return bool(regex.match(text))
|
||||
return regex.match(text)
|
||||
|
||||
|
||||
def split_to_dict(text):
|
||||
@@ -507,18 +535,21 @@ def process_wildcard_for_segs(wildcard):
|
||||
|
||||
return 'LAB', WildcardChooserDict(items)
|
||||
|
||||
elif starts_with_regex(r"\[(ASC|DSC|RND)\]", wildcard):
|
||||
mode = wildcard[1:4]
|
||||
items = split_string_with_sep(wildcard[5:])
|
||||
|
||||
if mode == 'RND':
|
||||
random.shuffle(items)
|
||||
return mode, WildcardChooser(items, True)
|
||||
else:
|
||||
return mode, WildcardChooser(items, False)
|
||||
|
||||
else:
|
||||
return None, WildcardChooser([(None, wildcard)], False)
|
||||
match = starts_with_regex(r"\[(ASC-SIZE|DSC-SIZE|ASC|DSC|RND)\]", wildcard)
|
||||
|
||||
if match:
|
||||
mode = match[1]
|
||||
items = split_string_with_sep(wildcard[len(match[0]):])
|
||||
|
||||
if mode == 'RND':
|
||||
random.shuffle(items)
|
||||
return mode, WildcardChooser(items, True)
|
||||
else:
|
||||
return mode, WildcardChooser(items, False)
|
||||
|
||||
else:
|
||||
return None, WildcardChooser([(None, wildcard)], False)
|
||||
|
||||
|
||||
def wildcard_load():
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-impact-pack"
|
||||
description = "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
|
||||
version = "7.2.1"
|
||||
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.0.1"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
|
||||
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ scikit-image
|
||||
piexif
|
||||
transformers
|
||||
opencv-python-headless
|
||||
GitPython
|
||||
scipy>=1.11.4
|
||||
numpy<2
|
||||
dill
|
||||
matplotlib
|
||||
Reference in New Issue
Block a user