Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d900939861 | ||
|
|
cd2696f6fd | ||
|
|
0b1ac0f1c5 | ||
|
|
216d7fd60c | ||
|
|
28cd2f70b2 | ||
|
|
2708eba825 | ||
|
|
c19ab92172 | ||
|
|
6e3d07277b | ||
|
|
782c6f439e | ||
|
|
0e3e6a193a | ||
|
|
798776838e | ||
|
|
66493d8cb4 | ||
|
|
b4fd0834e0 | ||
|
|
808b0dedf0 | ||
|
|
c6056b132d | ||
|
|
1ae7cae2df | ||
|
|
ccb6285548 | ||
|
|
092310bc8f | ||
|
|
5c530eb32e | ||
|
|
e35bc23fd1 | ||
|
|
869ac6fd1f | ||
|
|
cb168d64ab | ||
|
|
a89e9e01a6 | ||
|
|
e70b4df9b5 | ||
|
|
af1ef7e441 | ||
|
|
d8738eee2f | ||
|
|
c397c68ca3 | ||
|
|
0e0722ec08 | ||
|
|
70d0540895 | ||
|
|
7330577a0f | ||
|
|
b1d760291f | ||
|
|
12e838a320 | ||
|
|
8e8621df49 | ||
|
|
cdb7b4d3b0 | ||
|
|
21eecb0c03 | ||
|
|
9402ecf4f9 | ||
|
|
c21b361e2a | ||
|
|
8f04714145 | ||
|
|
a311f9278d | ||
|
|
0ec21f1a4b | ||
|
|
40a3df3acd | ||
|
|
9ba92862c4 | ||
|
|
2c2e148205 | ||
|
|
ff9c30787c | ||
|
|
64f709741a | ||
|
|
455e993354 | ||
|
|
ce23d436fc | ||
|
|
5f630466fb | ||
|
|
66bf101341 | ||
|
|
5c0535942a | ||
|
|
6be257254e |
@@ -7,15 +7,19 @@ on:
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'ltdrdata' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
|
||||
@@ -7,3 +7,5 @@ subpack
|
||||
impact_subpack
|
||||
*.txt
|
||||
*.yaml
|
||||
!requirements.txt
|
||||
!LICENSE.txt
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
# 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
|
||||
@@ -30,12 +32,34 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* With the addition of wildcard support in FaceDetailer, the structure of DETAILER_PIPE-related nodes and Detailer nodes has changed. There may be malfunctions when using the existing workflow.
|
||||
|
||||
|
||||
## How To Install
|
||||
|
||||
### **Recommended**
|
||||
* Install via [ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager).
|
||||
|
||||
### **Manual**
|
||||
* Navigate to `ComfyUI/custom_nodes` in your terminal (cmd).
|
||||
* Clone the repository under the `custom_nodes` directory using the following command:
|
||||
```
|
||||
git clone https://github.com/ltdrdata/ComfyUI-Impact-Pack comfyui-impact-pack
|
||||
cd comfyui-impact-pack
|
||||
```
|
||||
* Install dependencies in your Python environment.
|
||||
* For Windows Portable, run the following command inside `ComfyUI\custom_nodes\comfyui-impact-pack`:
|
||||
```
|
||||
..\..\..\python_embeded\python.exe -m pip install -r requirements.txt
|
||||
```
|
||||
* If using venv or conda, activate your Python environment first, then run:
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Companion Pack
|
||||
* If you need the `Ultralytics Detector Provider` to use various YOLO detection models, you should also install [ComfyUI-Impact-Subpack](https://github.com/ltdrdata/ComfyUI-Impact-Subpack).
|
||||
|
||||
## Custom Nodes
|
||||
### [Detector nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detectors.md)
|
||||
* `SAMLoader` - Loads the SAM model.
|
||||
* `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.
|
||||
@@ -69,6 +93,8 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `Dilate Mask` - Dilate Mask.
|
||||
* Support erosion for negative value.
|
||||
* `Gaussian Blur Mask` - Apply Gaussian Blur to Mask. You can utilize this for mask feathering.
|
||||
* `Mask Rect Area` - Create a rectangular mask defined by percentages with preview canvas.
|
||||
* `Mask Rect Area (Advanced)` - Create a rectangular mask defined by pixels and image size.
|
||||
|
||||
### [Detailer nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detailers.md)
|
||||
* `Detailer (SEGS)` - Refines the image based on SEGS.
|
||||
@@ -106,6 +132,8 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `SEGS Filter (label)` - This node filters SEGS based on the label of the detected areas.
|
||||
* `SEGS Filter (ordered)` - This node sorts SEGS based on size and position and retrieves SEGs within a certain range.
|
||||
* `SEGS Filter (range)` - This node retrieves only SEGs from SEGS that have a size and position within a certain range.
|
||||
* `SEGS Filter (non max suppression)` - This node filters SEGS by removing those with high overlap based on the Intersection over Union (IoU) threshold, keeping only the most confident detections.
|
||||
* `SEGS Filter (intersection)` - This node filters segs1, keeping only the SEGS that do not significantly overlap with any SEGS in segs2, based on the Intersection over Area (IoA) threshold.
|
||||
* `SEGS Assign (label)` - Assign labels sequentially to SEGS. This node is useful when used with `[LAB]` of FaceDetailer.
|
||||
* `SEGSConcat` - Concatenate segs1 and segs2. If source shape of segs1 and segs2 are different from segs2 will be ignored.
|
||||
* `SEGS Merge` - SEGS contains multiple SEGs. SEGS Merge integrates several SEGs into a single merged SEG. The label is changed to `merged` and the confidence becomes the minimum confidence. The applied controlnet and cropped_image are removed.
|
||||
@@ -223,7 +251,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
|
||||
|
||||
### Impact KSampler
|
||||
* These samplers support basic_pipe and AYS scheduler
|
||||
* These samplers support basic_pipe and AYS/OSS/GITS scheduler
|
||||
* `KSampler (pipe)` - pipe version of KSampler
|
||||
* `KSampler (advanced/pipe)` - pipe version of KSamplerAdvacned
|
||||
* When converting the scheduler widget to input, refer to the `Impact Scheduler Adapter` node to resolve compatibility issues.
|
||||
@@ -274,6 +302,7 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* For supported labels, please refer to the `config.json` of the respective HuggingFace repository.
|
||||
* `#Female` and `#Male` are symbols that group multiple labels such as `Female, women, woman, ...`, for convenience, rather than being single labels.
|
||||
|
||||
|
||||
### Etc nodes
|
||||
* `Impact Scheduler Adapter` - With the addition of AYS to the scheduler of the Impact Pack and Inspire Pack, there is an issue of incompatibility when the existing scheduler widget is converted to input. The Impact Scheduler Adapter allows for an indirect connection to be possible.
|
||||
* `StringListToString` - Convert String List to String
|
||||
@@ -289,66 +318,25 @@ This custom node helps to conveniently enhance images through Detector, Detailer
|
||||
* `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
|
||||
* MMDetDetectorProvider - Loads the MMDet model to provide BBOX_DETECTOR and SEGM_DETECTOR.
|
||||
* To use the existing MMDetDetectorProvider, you need to enable the MMDet usage configuration.
|
||||
|
||||
|
||||
## Feature
|
||||
* `Interactive SAM Detector (Clipspace)` - When you right-click on a node that has 'MASK' and 'IMAGE' outputs, a context menu will open. From this menu, you can either open a dialog to create a SAM Mask using 'Open in SAM Detector', or copy the content (likely mask data) using 'Copy (Clipspace)' and generate a mask using 'Impact SAM Detector' from the clipspace menu, and then paste it using 'Paste (Clipspace)'.
|
||||
* Providing a feature to detect errors that occur when mixing models and clips from checkpoints such as `SDXL Base`, `SDXL Refiner`, `SD1.x`, `SD2.x` during sample execution, and reporting appropriate errors.
|
||||
|
||||
|
||||
## Deprecated
|
||||
* The following nodes have been kept only for compatibility with existing workflows, and are no longer supported. Please replace them with new nodes.
|
||||
* ONNX Detector (SEGS) - BBOX Detector (SEGS)
|
||||
* MMDetLoader -> MMDetDetectorProvider
|
||||
* SegsMaskCombine -> SEGS to MASK (combined)
|
||||
* BboxDetectorForEach -> BBOX Detector (SEGS)
|
||||
* SegmDetectorForEach -> SEGM Detector (SEGS)
|
||||
* BboxDetectorCombined -> BBOX Detector (combined)
|
||||
* SegmDetectorCombined -> SEGM Detector (combined)
|
||||
* MaskPainter -> PreviewBridge
|
||||
* To use the existing deprecated legacy nodes, you need to enable the MMDet usage configuration.
|
||||
|
||||
|
||||
## Ultralytics models
|
||||
* When using ultralytics models, save them separately in `models/ultralytics/bbox` and `models/ultralytics/segm` depending on the type of model. Many models can be downloaded by searching for `ultralytics` in the Model Manager of ComfyUI-Manager.
|
||||
* huggingface.co/Bingsu/[adetailer](https://huggingface.co/Bingsu/adetailer/tree/main) - 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.
|
||||
```
|
||||
[default]
|
||||
dependency_version = 2
|
||||
mmdet_skip = True
|
||||
```
|
||||
* Change `mmdet_skip = True` to `mmdet_skip = False`
|
||||
```
|
||||
[default]
|
||||
dependency_version = 2
|
||||
mmdet_skip = False
|
||||
```
|
||||
* Restart ComfyUI
|
||||
|
||||
|
||||
## Installation
|
||||
## How To Install?
|
||||
|
||||
### 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`
|
||||
2. `git clone https://github.com/ltdrdata/ComfyUI-Impact-Pack`
|
||||
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-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-manual.py` to run the installation script.
|
||||
6. Restart ComfyUI
|
||||
4. `pip install -r requirements.txt`
|
||||
* **IMPORTANT**:
|
||||
* You must install it within the Python environment where ComfyUI is running.
|
||||
* For the portable version, use `<installed path>\python_embeded\python.exe -m pip` instead of `pip`. For a `venv`, activate the `venv` first and then use `pip`.
|
||||
5. 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.
|
||||
@@ -359,11 +347,9 @@ mmdet_skip = False
|
||||
|
||||
* pip install
|
||||
* segment-anything
|
||||
* ultralytics
|
||||
* scikit-image
|
||||
* piexif
|
||||
* opencv-python
|
||||
* GitPython
|
||||
* scipy
|
||||
* numpy<2
|
||||
* dill
|
||||
@@ -372,9 +358,6 @@ mmdet_skip = False
|
||||
* (deprecated) openmim # for mim
|
||||
* (deprecated) pycocotools # for mim
|
||||
|
||||
* mim install (deprecated)
|
||||
* mmcv==2.0.0, mmdet==3.0.0, mmengine==0.7.2
|
||||
|
||||
* linux packages (ubuntu)
|
||||
* libgl1-mesa-glx
|
||||
* libglib2.0-0
|
||||
@@ -397,17 +380,16 @@ sam_editor_model = sam_vit_b_01ec64.pth
|
||||
```
|
||||
|
||||
|
||||
## Other Materials (auto-download on initial startup)
|
||||
## Other Materials (auto-download when installing)
|
||||
|
||||
* ComfyUI/models/mmdets/bbox <= https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth
|
||||
* ComfyUI/models/mmdets/bbox <= https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py
|
||||
* ComfyUI/models/sams <= https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth
|
||||
|
||||
|
||||
## Troubleshooting page
|
||||
* [Troubleshooting Page](troubleshooting/TROUBLESHOOTING.md)
|
||||
|
||||
|
||||
## How to use (DDetailer feature)
|
||||
## How To Use (DDetailer feature)
|
||||
|
||||
#### 1. Basic auto face detection and refine exapmle.
|
||||

|
||||
|
||||
@@ -13,23 +13,16 @@ 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)
|
||||
|
||||
import impact.config
|
||||
import impact.sample_error_enhancer
|
||||
print(f"### Loading: ComfyUI-Impact-Pack ({impact.config.version})")
|
||||
|
||||
|
||||
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
|
||||
@@ -143,6 +136,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"BitwiseAndMask": BitwiseAndMask,
|
||||
"SubtractMask": SubtractMask,
|
||||
"AddMask": AddMask,
|
||||
"MaskRectArea": MaskRectArea,
|
||||
"MaskRectAreaAdvanced": MaskRectAreaAdvanced,
|
||||
"ImpactSegsAndMask": SegsBitwiseAndMask,
|
||||
"ImpactSegsAndMaskForEach": SegsBitwiseAndMaskForEach,
|
||||
"EmptySegs": EmptySEGS,
|
||||
@@ -252,6 +247,8 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactSEGSLabelFilter": SEGSLabelFilter,
|
||||
"ImpactSEGSRangeFilter": SEGSRangeFilter,
|
||||
"ImpactSEGSOrderedFilter": SEGSOrderedFilter,
|
||||
"ImpactSEGSIntersectionFilter": SEGSIntersectionFilter,
|
||||
"ImpactSEGSNMSFilter": SEGSNMSFilter,
|
||||
|
||||
"ImpactCompare": ImpactCompare,
|
||||
"ImpactConditionalBranch": ImpactConditionalBranch,
|
||||
@@ -324,6 +321,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"BitwiseAndMask": "Pixelwise(MASK & MASK)",
|
||||
"SubtractMask": "Pixelwise(MASK - MASK)",
|
||||
"AddMask": "Pixelwise(MASK + MASK)",
|
||||
"MaskRectArea": "Mask Rect Area",
|
||||
"MaskRectAreaAdvanced": "Mask Rect Area (Advanced)",
|
||||
"ImpactFlattenMask": "Flatten Mask Batch",
|
||||
"DetailerForEach": "Detailer (SEGS)",
|
||||
"DetailerForEachPipe": "Detailer (SEGS/pipe)",
|
||||
@@ -365,6 +364,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactSEGSLabelFilter": "SEGS Filter (label)",
|
||||
"ImpactSEGSRangeFilter": "SEGS Filter (range)",
|
||||
"ImpactSEGSOrderedFilter": "SEGS Filter (ordered)",
|
||||
"ImpactSEGSIntersectionFilter": "SEGS Filter (intersection)",
|
||||
"ImpactSEGSNMSFilter": "SEGS Filter (non max suppression)",
|
||||
"ImpactSEGSConcat": "SEGS Concat",
|
||||
"ImpactSEGSToMaskList": "SEGS to Mask List",
|
||||
"ImpactSEGSToMaskBatch": "SEGS to Mask Batch",
|
||||
@@ -465,19 +466,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
|
||||
|
||||
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,596 @@
|
||||
{
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 5,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
30,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
390,
|
||||
320
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"clipspace/clipspace-mask-609196.2000000011.png [input]",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1230,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "workflow>Impact::MAKE_BASIC_PIPE",
|
||||
"pos": [
|
||||
20,
|
||||
620
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "basic_pipe",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "workflow/Impact::MAKE_BASIC_PIPE"
|
||||
},
|
||||
"widgets_values": [
|
||||
"SD1.5/realcartoon3d_v13.safetensors",
|
||||
"(best quality:1.4), fox girl",
|
||||
"(worst quality:1.4), nsfw"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "MaskDetailerPipe",
|
||||
"pos": [
|
||||
530,
|
||||
210
|
||||
],
|
||||
"size": [
|
||||
569.4000244140625,
|
||||
850
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "basic_pipe",
|
||||
"type": "BASIC_PIPE",
|
||||
"link": 3,
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "refiner_basic_pipe_opt",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 7,
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "detailer_hook",
|
||||
"type": "DETAILER_HOOK",
|
||||
"shape": 7,
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "scheduler_func_opt",
|
||||
"type": "SCHEDULER_FUNC",
|
||||
"shape": 7,
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"shape": 3,
|
||||
"links": [
|
||||
5
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "cropped_refined",
|
||||
"type": "IMAGE",
|
||||
"shape": 6,
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "cropped_enhanced_alpha",
|
||||
"type": "IMAGE",
|
||||
"shape": 6,
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "basic_pipe",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 3,
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "refiner_basic_pipe_opt",
|
||||
"type": "BASIC_PIPE",
|
||||
"shape": 3,
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "MaskDetailerPipe"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
true,
|
||||
1024,
|
||||
true,
|
||||
1003,
|
||||
"fixed",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
0.75,
|
||||
5,
|
||||
3,
|
||||
10,
|
||||
0.2,
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
20,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"color": "#322",
|
||||
"bgcolor": "#533"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1230,
|
||||
560
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
3,
|
||||
3,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
"BASIC_PIPE"
|
||||
],
|
||||
[
|
||||
4,
|
||||
2,
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
5,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
80,
|
||||
-110
|
||||
]
|
||||
},
|
||||
"groupNodes": {
|
||||
"Impact::MAKE_BASIC_PIPE": {
|
||||
"author": "Dr.Lt.Data",
|
||||
"category": "",
|
||||
"config": {
|
||||
"1": {
|
||||
"input": {
|
||||
"text": {
|
||||
"name": "Positive prompt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"input": {
|
||||
"text": {
|
||||
"name": "Negative prompt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"datetime": 1708272471445,
|
||||
"external": [],
|
||||
"links": [
|
||||
[
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
1,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
3,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
"CONDITIONING"
|
||||
]
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"flags": {},
|
||||
"index": 0,
|
||||
"mode": 0,
|
||||
"order": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "MODEL",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "MODEL",
|
||||
"localized_name": "MODEL"
|
||||
},
|
||||
{
|
||||
"links": [],
|
||||
"name": "CLIP",
|
||||
"shape": 3,
|
||||
"slot_index": 1,
|
||||
"type": "CLIP",
|
||||
"localized_name": "CLIP"
|
||||
},
|
||||
{
|
||||
"links": [],
|
||||
"name": "VAE",
|
||||
"shape": 3,
|
||||
"slot_index": 2,
|
||||
"type": "VAE",
|
||||
"localized_name": "VAE"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
550,
|
||||
360
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"widgets_values": [
|
||||
"SDXL/sd_xl_base_1.0_0.9vae.safetensors"
|
||||
],
|
||||
"inputs": []
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 1,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"localized_name": "clip"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 1,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "CONDITIONING",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
940,
|
||||
480
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"size": {
|
||||
"0": 263,
|
||||
"1": 99
|
||||
},
|
||||
"title": "Positive",
|
||||
"type": "CLIPTextEncode",
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 2,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"localized_name": "clip"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 2,
|
||||
"outputs": [
|
||||
{
|
||||
"links": [],
|
||||
"name": "CONDITIONING",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "CONDITIONING"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
940,
|
||||
640
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"size": {
|
||||
"0": 263,
|
||||
"1": 99
|
||||
},
|
||||
"title": "Negative",
|
||||
"type": "CLIPTextEncode",
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"flags": {},
|
||||
"index": 3,
|
||||
"inputs": [
|
||||
{
|
||||
"link": null,
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"localized_name": "model"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"localized_name": "clip"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"localized_name": "vae"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "positive"
|
||||
},
|
||||
{
|
||||
"link": null,
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"localized_name": "negative"
|
||||
}
|
||||
],
|
||||
"mode": 0,
|
||||
"order": 3,
|
||||
"outputs": [
|
||||
{
|
||||
"links": null,
|
||||
"name": "basic_pipe",
|
||||
"shape": 3,
|
||||
"slot_index": 0,
|
||||
"type": "BASIC_PIPE",
|
||||
"localized_name": "basic_pipe"
|
||||
}
|
||||
],
|
||||
"pos": [
|
||||
1320,
|
||||
360
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ToBasicPipe"
|
||||
},
|
||||
"size": {
|
||||
"0": 241.79998779296875,
|
||||
"1": 106
|
||||
},
|
||||
"type": "ToBasicPipe"
|
||||
}
|
||||
],
|
||||
"packname": "Impact",
|
||||
"version": "1.0"
|
||||
}
|
||||
},
|
||||
"controller_panel": {
|
||||
"controllers": {},
|
||||
"hidden": true,
|
||||
"highlight": true,
|
||||
"version": 2,
|
||||
"default_order": []
|
||||
},
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.14",
|
||||
"comfyui-impact-pack": "1ae7cae2df8cca06027edfa3a24512671239d6c4"
|
||||
},
|
||||
"ue_links": [],
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 526 KiB |
@@ -1,183 +0,0 @@
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import subprocess
|
||||
import threading
|
||||
import locale
|
||||
import traceback
|
||||
from typing import Set
|
||||
|
||||
|
||||
if sys.argv[0] == 'install.py':
|
||||
sys.path.append('.') # for portable version
|
||||
|
||||
|
||||
impact_path = os.path.join(os.path.dirname(__file__), "modules")
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
|
||||
subpack_repo = "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
|
||||
|
||||
|
||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||
if comfy_path is None:
|
||||
print(f"\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
||||
comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
|
||||
model_path = os.environ.get('COMFYUI_MODEL_PATH')
|
||||
if model_path is None:
|
||||
try:
|
||||
import folder_paths
|
||||
model_path = folder_paths.models_dir
|
||||
except:
|
||||
pass
|
||||
|
||||
if model_path is None:
|
||||
model_path = os.path.abspath(os.path.join(comfy_path, 'models'))
|
||||
print(f"\n[bold yellow]WARN: The `COMFYUI_MODEL_PATH` environment variable is not set. Assuming `{model_path}` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
||||
|
||||
|
||||
sys.path.append(impact_path)
|
||||
sys.path.append(comfy_path)
|
||||
|
||||
|
||||
# ---
|
||||
def handle_stream(stream, is_stdout):
|
||||
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
||||
|
||||
for msg in stream:
|
||||
if is_stdout:
|
||||
print(msg, end="", file=sys.stdout)
|
||||
else:
|
||||
print(msg, end="", file=sys.stderr)
|
||||
|
||||
|
||||
def process_wrap(cmd_str, cwd=None, handler=None, env=None):
|
||||
print(f"[Impact Pack] EXECUTE: {cmd_str} in '{cwd}'")
|
||||
process = subprocess.Popen(cmd_str, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, text=True, bufsize=1)
|
||||
|
||||
if handler is None:
|
||||
handler = handle_stream
|
||||
|
||||
stdout_thread = threading.Thread(target=handler, args=(process.stdout, True))
|
||||
stderr_thread = threading.Thread(target=handler, args=(process.stderr, False))
|
||||
|
||||
stdout_thread.start()
|
||||
stderr_thread.start()
|
||||
|
||||
stdout_thread.join()
|
||||
stderr_thread.join()
|
||||
|
||||
return process.wait()
|
||||
|
||||
|
||||
@functools.cache
|
||||
def get_installed_packages() -> Set[str]:
|
||||
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()])
|
||||
return pip_list
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise Exception(f"[ComfyUI-Impact-Pack] Failed to retrieve the information of installed pip packages.")
|
||||
|
||||
|
||||
def is_package_installed(name: str) -> bool:
|
||||
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: str) -> bool:
|
||||
print(f"Requirements file: {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_package_installed(line):
|
||||
return False
|
||||
|
||||
pip_install = [sys.executable, "-m", "pip", "install", "-U"]
|
||||
|
||||
# ---
|
||||
|
||||
|
||||
try:
|
||||
import platform
|
||||
import folder_paths
|
||||
from torchvision.datasets.utils import download_url
|
||||
import impact.config
|
||||
|
||||
print("### ComfyUI-Impact-Pack: Check dependencies")
|
||||
def ensure_subpack():
|
||||
import git
|
||||
if os.path.exists(subpack_path):
|
||||
try:
|
||||
repo = git.Repo(subpack_path)
|
||||
repo.remotes.origin.pull()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
if platform.system() == 'Windows':
|
||||
print(f"[ComfyUI-Impact-Pack] Please turn off ComfyUI and remove '{subpack_path}' and restart ComfyUI.")
|
||||
else:
|
||||
shutil.rmtree(subpack_path)
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
else:
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
|
||||
|
||||
def install():
|
||||
subpack_install_script = os.path.join(subpack_path, "install.py")
|
||||
|
||||
print(f"### ComfyUI-Impact-Pack: Updating subpack")
|
||||
ensure_subpack() # The installation of the subpack must take place before ensure_pip. cv2 triggers a permission error.
|
||||
|
||||
new_env = os.environ.copy()
|
||||
new_env["COMFYUI_PATH"] = comfy_path
|
||||
new_env["COMFYUI_MODEL_PATH"] = model_path
|
||||
|
||||
if os.path.exists(subpack_install_script):
|
||||
if not is_requirements_installed(os.path.join(subpack_path, 'requirements.txt')):
|
||||
process_wrap(pip_install + ['-r', 'requirements.txt'], cwd=subpack_path)
|
||||
|
||||
process_wrap([sys.executable, 'install.py'], cwd=subpack_path, env=new_env)
|
||||
else:
|
||||
print(f"### ComfyUI-Impact-Pack: (Install Failed) Subpack\nFile not found: `{subpack_install_script}`")
|
||||
|
||||
# Download model
|
||||
print("### ComfyUI-Impact-Pack: Check basic models")
|
||||
sam_path = os.path.join(model_path, "sams")
|
||||
onnx_path = os.path.join(model_path, "onnx")
|
||||
|
||||
if not os.path.exists(os.path.join(os.path.dirname(__file__), '..', 'skip_download_model')):
|
||||
if not impact.config.get_config()['mmdet_skip']:
|
||||
bbox_path = os.path.join(model_path, "mmdets", "bbox")
|
||||
if not os.path.exists(bbox_path):
|
||||
os.makedirs(bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.pth")):
|
||||
download_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth", bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(bbox_path, "mmdet_anime-face_yolov3.py")):
|
||||
download_url("https://raw.githubusercontent.com/Bing-su/dddetailer/master/config/mmdet_anime-face_yolov3.py", bbox_path)
|
||||
|
||||
if not os.path.exists(os.path.join(sam_path, "sam_vit_b_01ec64.pth")):
|
||||
download_url("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", sam_path)
|
||||
|
||||
if not os.path.exists(onnx_path):
|
||||
print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})")
|
||||
os.mkdir(onnx_path)
|
||||
|
||||
impact.config.write_config()
|
||||
|
||||
install()
|
||||
|
||||
except Exception as e:
|
||||
print("[ERROR] ComfyUI-Impact-Pack: Dependency installation has failed. Please install manually.")
|
||||
traceback.print_exc()
|
||||
@@ -12,13 +12,11 @@ if sys.argv[0] == 'install.py':
|
||||
|
||||
|
||||
impact_path = os.path.join(os.path.dirname(__file__), "modules")
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), "impact_subpack")
|
||||
subpack_repo = "https://github.com/ltdrdata/ComfyUI-Impact-Subpack"
|
||||
|
||||
|
||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||
if comfy_path is None:
|
||||
print(f"\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `{os.path.dirname(__file__)}/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
||||
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')
|
||||
@@ -31,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,43 +69,15 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None):
|
||||
|
||||
try:
|
||||
import platform
|
||||
import folder_paths
|
||||
from torchvision.datasets.utils import download_url
|
||||
import impact.config
|
||||
|
||||
print("### ComfyUI-Impact-Pack: Check dependencies")
|
||||
def ensure_subpack():
|
||||
import git
|
||||
if os.path.exists(subpack_path):
|
||||
try:
|
||||
repo = git.Repo(subpack_path)
|
||||
repo.remotes.origin.pull()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
if platform.system() == 'Windows':
|
||||
print(f"[ComfyUI-Impact-Pack] Please turn off ComfyUI and remove '{subpack_path}' and restart ComfyUI.")
|
||||
else:
|
||||
shutil.rmtree(subpack_path)
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
else:
|
||||
git.Repo.clone_from(subpack_repo, subpack_path)
|
||||
|
||||
|
||||
def install():
|
||||
subpack_install_script = os.path.join(subpack_path, "install.py")
|
||||
|
||||
print(f"### ComfyUI-Impact-Pack: Updating subpack")
|
||||
ensure_subpack() # The installation of the subpack must take place before ensure_pip. cv2 triggers a permission error.
|
||||
|
||||
new_env = os.environ.copy()
|
||||
new_env["COMFYUI_PATH"] = comfy_path
|
||||
new_env["COMFYUI_MODEL_PATH"] = model_path
|
||||
|
||||
if os.path.exists(subpack_install_script):
|
||||
process_wrap([sys.executable, 'install.py'], cwd=subpack_path, env=new_env)
|
||||
else:
|
||||
print(f"### ComfyUI-Impact-Pack: (Install Failed) Subpack\nFile not found: `{subpack_install_script}`")
|
||||
|
||||
# Download model
|
||||
print("### ComfyUI-Impact-Pack: Check basic models")
|
||||
sam_path = os.path.join(model_path, "sams")
|
||||
@@ -137,6 +107,17 @@ 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()
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { ComfyApp, app } from "../../scripts/app.js";
|
||||
|
||||
let conflict_check = undefined;
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.impact.comboBoolMigration",
|
||||
|
||||
nodeCreated(node, app) {
|
||||
for(let i in node.widgets) {
|
||||
let widget = node.widgets[i];
|
||||
|
||||
if(conflict_check == undefined) {
|
||||
conflict_check = !!app.extensions.find((ext) => ext.name === "Comfy.comboBoolMigration");
|
||||
}
|
||||
|
||||
if(conflict_check)
|
||||
return;
|
||||
|
||||
if(widget.type == "toggle") {
|
||||
let value = widget.value;
|
||||
|
||||
var v = Object.getOwnPropertyDescriptor(widget, 'value');
|
||||
if(!v) {
|
||||
Object.defineProperty(widget, "value", {
|
||||
set: (value) => {
|
||||
delete widget.value;
|
||||
widget.value = value == true || value == widget.options.on;
|
||||
},
|
||||
get: () => { return value; }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -93,7 +93,7 @@ const input_dirty = {};
|
||||
const output_tracking = {};
|
||||
|
||||
function progressExecuteHandler(event) {
|
||||
if(event.detail.output.aux){
|
||||
if(event.detail?.output?.aux){
|
||||
const id = event.detail.node;
|
||||
if(input_tracking.hasOwnProperty(id)) {
|
||||
if(input_tracking.hasOwnProperty(id) && input_tracking[id][0] != event.detail.output.aux[0]) {
|
||||
@@ -222,6 +222,31 @@ api.addEventListener("executed", progressExecuteHandler);
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.Impack",
|
||||
|
||||
commands: [
|
||||
{
|
||||
id: 'refresh-impact-wildcard',
|
||||
label: 'Impact: Refresh Wildcard',
|
||||
function: async () => {
|
||||
await api.fetchApi('/impact/wildcards/refresh');
|
||||
await load_wildcards();
|
||||
app.extensionManager.toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Refreshed!',
|
||||
detail: 'Impact Wildcard List is refreshed!!',
|
||||
life: 3000
|
||||
});
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
menuCommands: [
|
||||
{
|
||||
path: ['Edit'],
|
||||
commands: ['refresh-impact-wildcard']
|
||||
}
|
||||
],
|
||||
|
||||
loadedGraphNode(node, app) {
|
||||
if (node.comfyClass == "MaskPainter") {
|
||||
input_dirty[node.id + ""] = true;
|
||||
@@ -248,7 +273,7 @@ app.registerExtension({
|
||||
}
|
||||
else {
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
slot_type = node.outputs[link_info.origin_slot].type;
|
||||
slot_type = node.outputs[link_info.origin_slot]?.type;
|
||||
}
|
||||
|
||||
this.inputs[0].type = slot_type;
|
||||
@@ -340,7 +365,11 @@ app.registerExtension({
|
||||
// connect input
|
||||
if(this.inputs[0].type == '*'){
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
let origin_type = node.outputs[link_info.origin_slot].type;
|
||||
let origin_type = node.outputs[link_info.origin_slot]?.type;
|
||||
|
||||
if(origin_type==undefined) {
|
||||
return; // fallback
|
||||
}
|
||||
|
||||
if(origin_type == '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
@@ -374,6 +403,9 @@ app.registerExtension({
|
||||
let slot_i = 1;
|
||||
for (let i = 0; i < this.outputs.length; i++) {
|
||||
this.outputs[i].name = `output${slot_i}`
|
||||
if (this.outputs[i].slot_index === undefined) {
|
||||
this.outputs[i].slot_index = i;
|
||||
}
|
||||
slot_i++;
|
||||
}
|
||||
|
||||
@@ -588,12 +620,14 @@ app.registerExtension({
|
||||
|
||||
if(node.comfyClass == "ImpactSEGSLabelFilter" || node.comfyClass == "SEGSLabelFilterDetailerHookProvider") {
|
||||
node.widgets[0].callback = (value, canvas, node, pos, e) => {
|
||||
if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(","))
|
||||
node.widgets[1].value += ", "
|
||||
if(node) {
|
||||
if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(","))
|
||||
node.widgets[1].value += ", "
|
||||
|
||||
node.widgets[1].value += value;
|
||||
if(node.widgets_values)
|
||||
node.widgets_values[1] = node.widgets[1].value;
|
||||
node.widgets[1].value += value;
|
||||
if(node.widgets_values)
|
||||
node.widgets_values[1] = node.widgets[1].value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[0], "value", {
|
||||
@@ -667,18 +701,20 @@ app.registerExtension({
|
||||
break;
|
||||
}
|
||||
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
if(node.widgets[tbox_id].value != '')
|
||||
node.widgets[tbox_id].value += ', '
|
||||
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
|
||||
if(node) {
|
||||
if(node.widgets[tbox_id].value != '')
|
||||
node.widgets[tbox_id].value += ', '
|
||||
|
||||
node.widgets[tbox_id].value += node._wildcard_value;
|
||||
}
|
||||
node.widgets[tbox_id].value += node._wildcard_value;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(node.widgets[combo_id+1], "value", {
|
||||
set: (value) => {
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
},
|
||||
if (value !== "Select the Wildcard to add to the text")
|
||||
node._wildcard_value = value;
|
||||
},
|
||||
get: () => { return "Select the Wildcard to add to the text"; }
|
||||
});
|
||||
|
||||
@@ -691,14 +727,16 @@ app.registerExtension({
|
||||
|
||||
if(has_lora) {
|
||||
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
|
||||
let lora_name = node._value;
|
||||
if(lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
if(node) {
|
||||
let lora_name = node._value;
|
||||
if(lora_name.endsWith('.safetensors')) {
|
||||
lora_name = lora_name.slice(0, -12);
|
||||
}
|
||||
|
||||
node.widgets[tbox_id].value += `<lora:${lora_name}>`;
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[tbox_id] = node.widgets[tbox_id].value;
|
||||
node.widgets[tbox_id].value += `<lora:${lora_name}>`;
|
||||
if(node.widgets_values) {
|
||||
node.widgets_values[tbox_id] = node.widgets[tbox_id].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,14 +768,20 @@ app.registerExtension({
|
||||
// mode combo
|
||||
Object.defineProperty(mode_widget, "value", {
|
||||
set: (value) => {
|
||||
node._mode_value = value == true || value == "Populate";
|
||||
populated_text_widget.inputEl.disabled = value == true || value == "Populate";
|
||||
if(value == true)
|
||||
node._mode_value = "populate";
|
||||
else if(value == false)
|
||||
node._mode_value = "fixed";
|
||||
else
|
||||
node._mode_value = value; // combo value
|
||||
|
||||
populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
|
||||
},
|
||||
get: () => {
|
||||
if(node._mode_value != undefined)
|
||||
return node._mode_value;
|
||||
else
|
||||
return true;
|
||||
return 'populate';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ class ImpactSamEditorDialog extends ComfyDialog {
|
||||
const pointsCanvas = document.createElement('canvas');
|
||||
|
||||
imgCanvas.id = "imageCanvas";
|
||||
maskCanvas.id = "maskCanvas";
|
||||
maskCanvas.id = "samEditorMaskCanvas";
|
||||
pointsCanvas.id = "pointsCanvas";
|
||||
|
||||
this.setlayout(imgCanvas, maskCanvas, pointsCanvas);
|
||||
@@ -356,13 +356,13 @@ class ImpactSamEditorDialog extends ComfyDialog {
|
||||
let w = (drawWidth * imgCanvas.clientWidth/imgCanvas.width) + "px";
|
||||
let h = (drawHeight * imgCanvas.clientHeight/imgCanvas.height) + "px";
|
||||
|
||||
pointsCanvas.width = drawWidth;
|
||||
pointsCanvas.height = drawHeight;
|
||||
pointsCanvas.width = drawWidth * imgCanvas.clientWidth/imgCanvas.width;
|
||||
pointsCanvas.height = drawHeight * imgCanvas.clientHeight/imgCanvas.height;
|
||||
pointsCanvas.style.top = imgCanvas.offsetTop + "px";
|
||||
pointsCanvas.style.left = imgCanvas.offsetLeft + "px";
|
||||
|
||||
maskCanvas.style.width = w;
|
||||
maskCanvas.style.height = h;
|
||||
maskCanvas.width = pointsCanvas.width;
|
||||
maskCanvas.height = pointsCanvas.height;
|
||||
maskCanvas.style.top = imgCanvas.offsetTop + "px";
|
||||
maskCanvas.style.left = imgCanvas.offsetLeft + "px";
|
||||
|
||||
@@ -476,8 +476,9 @@ class ImpactSamEditorDialog extends ComfyDialog {
|
||||
for(const i in self.prompt_points) {
|
||||
const [is_positive, x, y] = self.prompt_points[i];
|
||||
const point = [x,y];
|
||||
if(is_positive)
|
||||
if(is_positive) {
|
||||
positive_points.push(point);
|
||||
}
|
||||
else
|
||||
negative_points.push(point);
|
||||
}
|
||||
@@ -511,8 +512,8 @@ class ImpactSamEditorDialog extends ComfyDialog {
|
||||
const x = event.offsetX || event.targetTouches[0].clientX - maskRect.left;
|
||||
const y = event.offsetY || event.targetTouches[0].clientY - maskRect.top;
|
||||
|
||||
const originalX = x * self.image.width / self.pointsCanvas.width;
|
||||
const originalY = y * self.image.height / self.pointsCanvas.height;
|
||||
const originalX = x * self.image.width / self.pointsCanvas.clientWidth;
|
||||
const originalY = y * self.image.height / self.pointsCanvas.clientHeight;
|
||||
|
||||
var point = null;
|
||||
if (event.button == 0) {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ComfyApp, app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
let refresh_btn = document.getElementById('comfy-refresh-button');
|
||||
let refresh_btn2 = document.querySelector('button[title="Refresh widgets in nodes to find new models or files"]');
|
||||
|
||||
let orig = refresh_btn.onclick;
|
||||
|
||||
if(refresh_btn) {
|
||||
refresh_btn.onclick = function() {
|
||||
orig();
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
};
|
||||
}
|
||||
|
||||
if(refresh_btn2) {
|
||||
refresh_btn2?.addEventListener('click', function() {
|
||||
api.fetchApi('/impact/wildcards/refresh');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
function showPreviewCanvas(node, app) {
|
||||
|
||||
const widget = {
|
||||
type: "customCanvas",
|
||||
name: "mask-rect-area-canvas",
|
||||
get value() {
|
||||
return this.canvas.value;
|
||||
},
|
||||
set value(x) {
|
||||
this.canvas.value = x;
|
||||
},
|
||||
draw: function (ctx, node, widgetWidth, widgetY) {
|
||||
|
||||
// If we are initially offscreen when created we wont have received a resize event
|
||||
// Calculate it here instead
|
||||
if (!node.canvasHeight) {
|
||||
computeCanvasSize(node, node.size);
|
||||
}
|
||||
|
||||
const visible = true;
|
||||
const t = ctx.getTransform();
|
||||
const margin = 12;
|
||||
const border = 2;
|
||||
const widgetHeight = node.canvasHeight;
|
||||
const width = Math.round(node.properties["width"]);
|
||||
const height = Math.round(node.properties["height"]);
|
||||
const scale = Math.min((widgetWidth - margin * 3) / width, (widgetHeight - margin * 3) / height);
|
||||
const blurRadius = node.properties["blur_radius"] || 0;
|
||||
const index = 0;
|
||||
|
||||
Object.assign(this.canvas.style, {
|
||||
left: `${t.e}px`,
|
||||
top: `${t.f + (widgetY * t.d)}px`,
|
||||
width: `${widgetWidth * t.a}px`,
|
||||
height: `${widgetHeight * t.d}px`,
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
fontSize: `${t.d * 10.0}px`,
|
||||
pointerEvents: "none"
|
||||
});
|
||||
|
||||
this.canvas.hidden = !visible;
|
||||
|
||||
let backgroundWidth = width * scale;
|
||||
let backgroundHeight = height * scale;
|
||||
|
||||
let xOffset = margin;
|
||||
if (backgroundWidth < widgetWidth) {
|
||||
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
|
||||
}
|
||||
let yOffset = (margin / 2);
|
||||
if (backgroundHeight < widgetHeight) {
|
||||
yOffset += (widgetHeight - backgroundHeight) / 2 - margin;
|
||||
}
|
||||
|
||||
let widgetX = xOffset;
|
||||
widgetY = widgetY + yOffset;
|
||||
|
||||
// Draw the background border
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.fillRect(widgetX - border, widgetY - border, backgroundWidth + border * 2, backgroundHeight + border * 2)
|
||||
|
||||
// Draw the main background area
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR;
|
||||
ctx.fillRect(widgetX, widgetY, backgroundWidth, backgroundHeight);
|
||||
|
||||
// Draw the conditioning zone
|
||||
let [x, y, w, h] = getDrawArea(node, backgroundWidth, backgroundHeight);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "80");
|
||||
ctx.fillRect(widgetX + x, widgetY + y, w, h);
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Draw grid lines
|
||||
for (let x = 0; x <= width / 64; x += 1) {
|
||||
ctx.moveTo(widgetX + x * 64 * scale, widgetY);
|
||||
ctx.lineTo(widgetX + x * 64 * scale, widgetY + backgroundHeight);
|
||||
}
|
||||
|
||||
for (let y = 0; y <= height / 64; y += 1) {
|
||||
ctx.moveTo(widgetX, widgetY + y * 64 * scale);
|
||||
ctx.lineTo(widgetX + backgroundWidth, widgetY + y * 64 * scale);
|
||||
}
|
||||
|
||||
ctx.strokeStyle = "#66666650";
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// Draw current zone
|
||||
let [sx, sy, sw, sh] = getDrawArea(node, backgroundWidth, backgroundHeight);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "80");
|
||||
ctx.fillRect(widgetX + sx, widgetY + sy, sw, sh);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "40");
|
||||
ctx.fillRect(widgetX + sx + border, widgetY + sy + border, sw - border * 2, sh - border * 2);
|
||||
|
||||
// Draw white border around the current zone
|
||||
ctx.strokeStyle = globalThis.LiteGraph.NODE_SELECTED_TITLE_COLOR;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(widgetX + sx, widgetY + sy, sw, sh);
|
||||
|
||||
// Display
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.arc(LiteGraph.NODE_SLOT_HEIGHT * 0.5, LiteGraph.NODE_SLOT_HEIGHT * (index + 0.5) + 4, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.stroke();
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
ctx.closePath();
|
||||
|
||||
// Draw progress bar canvas
|
||||
if (backgroundWidth < widgetWidth) {
|
||||
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
|
||||
}
|
||||
|
||||
// Ajustar las coordenadas X e Y
|
||||
const barHeight = 8;
|
||||
let widgetYBar = widgetY + backgroundHeight + margin;
|
||||
|
||||
// Dibujar el borde negro alrededor de la barra
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.fillRect(
|
||||
widgetX - border,
|
||||
widgetYBar - border,
|
||||
backgroundWidth + border * 2,
|
||||
barHeight + border * 2
|
||||
);
|
||||
|
||||
// Dibujar el área principal de la barra (fondo)
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR; // Mismo color de fondo que el canvas
|
||||
ctx.fillRect(
|
||||
widgetX,
|
||||
widgetYBar,
|
||||
backgroundWidth,
|
||||
barHeight
|
||||
);
|
||||
|
||||
|
||||
// Draw progress bar grid
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "#66666650";
|
||||
|
||||
// Calcular el número de líneas en función del tamaño de la barra
|
||||
const numLines = Math.floor(backgroundWidth / 64);
|
||||
|
||||
// Dibujar líneas del grid
|
||||
for (let x = 0; x <= width / 64; x += 1) {
|
||||
ctx.moveTo(widgetX + x * 64 * scale, widgetYBar);
|
||||
ctx.lineTo(widgetX + x * 64 * scale, widgetYBar + barHeight);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// Dibujar progreso (basado en blur_radius)
|
||||
const progress = Math.min(blurRadius / 255, 1);
|
||||
ctx.fillStyle = "rgba(0, 120, 255, 0.5)";
|
||||
|
||||
ctx.fillRect(
|
||||
widgetX,
|
||||
widgetYBar,
|
||||
backgroundWidth * progress,
|
||||
barHeight
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
widget.canvas = document.createElement("canvas");
|
||||
widget.canvas.className = "mask-rect-area-canvas";
|
||||
widget.parent = node;
|
||||
|
||||
document.body.appendChild(widget.canvas);
|
||||
node.addCustomWidget(widget);
|
||||
|
||||
app.canvas.onDrawBackground = function () {
|
||||
// Draw node isnt fired once the node is off the screen
|
||||
// if it goes off screen quickly, the input may not be removed
|
||||
// this shifts it off screen so it can be moved back if the node is visible.
|
||||
for (let n in app.graph._nodes) {
|
||||
n = app.graph._nodes[n];
|
||||
for (let w in n.widgets) {
|
||||
let wid = n.widgets[w];
|
||||
if (Object.hasOwn(wid, "canvas")) {
|
||||
wid.canvas.style.left = -8000 + "px";
|
||||
wid.canvas.style.position = "absolute";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
node.onResize = function (size) {
|
||||
computeCanvasSize(node, size);
|
||||
};
|
||||
|
||||
return {minWidth: 200, minHeight: 200, widget};
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'drltdata.MaskRectAreaAdvanced',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "MaskRectAreaAdvanced") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
|
||||
this.setProperty("width", 512);
|
||||
this.setProperty("height", 512);
|
||||
this.setProperty("x", 0);
|
||||
this.setProperty("y", 0);
|
||||
this.setProperty("w", 256);
|
||||
this.setProperty("h", 256);
|
||||
this.setProperty("blur_radius", 0);
|
||||
|
||||
this.selected = false;
|
||||
this.index = 3;
|
||||
this.serialize_widgets = true;
|
||||
|
||||
CUSTOM_INT(this, "x", 0, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["x"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "y", 0, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["y"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "width", 256, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["w"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "height", 256, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["h"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "image_width", 512, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["width"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "image_height", 512, function (v, _, node) {
|
||||
const s = this.options.step / 10;
|
||||
this.value = Math.round(v / s) * s;
|
||||
node.properties["height"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "blur_radius", 0, function (v, _, node) {
|
||||
this.value = Math.round(v) || 0;
|
||||
node.properties["blur_radius"] = this.value;
|
||||
},
|
||||
{"min": 0, "max": 255, "step": 10}
|
||||
);
|
||||
|
||||
showPreviewCanvas(this, app);
|
||||
|
||||
this.onSelected = function () {
|
||||
this.selected = true;
|
||||
};
|
||||
this.onDeselected = function () {
|
||||
this.selected = false;
|
||||
};
|
||||
|
||||
return r;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate the drawing area using individual properties.
|
||||
function getDrawArea(node, backgroundWidth, backgroundHeight) {
|
||||
let x = node.properties["x"] * backgroundWidth / node.properties["width"];
|
||||
let y = node.properties["y"] * backgroundHeight / node.properties["height"];
|
||||
let w = node.properties["w"] * backgroundWidth / node.properties["width"];
|
||||
let h = node.properties["h"] * backgroundHeight / node.properties["height"];
|
||||
|
||||
if (x > backgroundWidth) {
|
||||
x = backgroundWidth;
|
||||
}
|
||||
if (y > backgroundHeight) {
|
||||
y = backgroundHeight;
|
||||
}
|
||||
|
||||
if (x + w > backgroundWidth) {
|
||||
w = Math.max(0, backgroundWidth - x);
|
||||
}
|
||||
|
||||
if (y + h > backgroundHeight) {
|
||||
h = Math.max(0, backgroundHeight - y);
|
||||
}
|
||||
|
||||
return [x, y, w, h];
|
||||
}
|
||||
|
||||
function CUSTOM_INT(node, inputName, val, func, config = {}) {
|
||||
return {
|
||||
widget: node.addWidget(
|
||||
"number",
|
||||
inputName,
|
||||
val,
|
||||
func,
|
||||
Object.assign({}, {min: 0, max: 4096, step: 640, precision: 0}, config)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function getDrawColor(percent, alpha) {
|
||||
let h = 360 * percent;
|
||||
let s = 50;
|
||||
let l = 50;
|
||||
l /= 100;
|
||||
const a = s * Math.min(l, 1 - l) / 100;
|
||||
const f = n => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
|
||||
}
|
||||
|
||||
function computeCanvasSize(node, size) {
|
||||
if (node.widgets[0].last_y == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MIN_HEIGHT = 220;
|
||||
const MIN_WIDTH = 240;
|
||||
|
||||
let y = LiteGraph.NODE_WIDGET_HEIGHT * Math.max(node.inputs.length, node.outputs.length) + 5;
|
||||
let freeSpace = size[1] - y;
|
||||
|
||||
// Compute the height of all non-customCanvas widgets
|
||||
let widgetHeight = 0;
|
||||
for (let i = 0; i < node.widgets.length; i++) {
|
||||
const w = node.widgets[i];
|
||||
if (w.type !== "customCanvas") {
|
||||
if (w.computeSize) {
|
||||
widgetHeight += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there is enough vertical space
|
||||
freeSpace -= widgetHeight;
|
||||
|
||||
// Adjust the height of the node if needed
|
||||
if (freeSpace < MIN_HEIGHT) {
|
||||
freeSpace = MIN_HEIGHT;
|
||||
node.size[1] = y + widgetHeight + freeSpace;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Ensure the node width meets the minimum width requirement
|
||||
if (node.size[0] < MIN_WIDTH) {
|
||||
node.size[0] = MIN_WIDTH;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Position each of the widgets
|
||||
for (const w of node.widgets) {
|
||||
w.y = y;
|
||||
if (w.type === "customCanvas") {
|
||||
y += freeSpace;
|
||||
} else if (w.computeSize) {
|
||||
y += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
|
||||
}
|
||||
}
|
||||
|
||||
node.canvasHeight = freeSpace;
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
import { app } from "/scripts/app.js";
|
||||
function showPreviewCanvas(node, app) {
|
||||
|
||||
const widget = {
|
||||
type: "customCanvas",
|
||||
name: "mask-rect-area-canvas",
|
||||
get value() {
|
||||
return this.canvas.value;
|
||||
},
|
||||
set value(x) {
|
||||
this.canvas.value = x;
|
||||
},
|
||||
draw: function (ctx, node, widgetWidth, widgetY) {
|
||||
|
||||
// If we are initially offscreen when created we wont have received a resize event
|
||||
// Calculate it here instead
|
||||
if (!node.canvasHeight) {
|
||||
computeCanvasSize(node, node.size);
|
||||
}
|
||||
|
||||
const visible = true;
|
||||
const t = ctx.getTransform();
|
||||
const margin = 12;
|
||||
const border = 2;
|
||||
const widgetHeight = node.canvasHeight;
|
||||
const width = 512;
|
||||
const height = 512;
|
||||
const scale = Math.min((widgetWidth - margin * 3) / width, (widgetHeight - margin * 3) / height);
|
||||
const blurRadius = node.properties["blur_radius"] || 0;
|
||||
const index = 0;
|
||||
|
||||
Object.assign(this.canvas.style, {
|
||||
left: `${t.e}px`,
|
||||
top: `${t.f + (widgetY * t.d)}px`,
|
||||
width: `${widgetWidth * t.a}px`,
|
||||
height: `${widgetHeight * t.d}px`,
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
fontSize: `${t.d * 10.0}px`,
|
||||
pointerEvents: "none"
|
||||
});
|
||||
|
||||
this.canvas.hidden = !visible;
|
||||
|
||||
let backgroundWidth = width * scale;
|
||||
let backgroundHeight = height * scale;
|
||||
let xOffset = margin;
|
||||
if (backgroundWidth < widgetWidth) {
|
||||
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
|
||||
}
|
||||
let yOffset = (margin / 2);
|
||||
if (backgroundHeight < widgetHeight) {
|
||||
yOffset += (widgetHeight - backgroundHeight) / 2 - margin;
|
||||
}
|
||||
|
||||
let widgetX = xOffset;
|
||||
widgetY = widgetY + yOffset;
|
||||
|
||||
// Draw the background border
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.fillRect(widgetX - border, widgetY - border, backgroundWidth + border * 2, backgroundHeight + border * 2);
|
||||
|
||||
// Draw the main background area
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR;
|
||||
ctx.fillRect(widgetX, widgetY, backgroundWidth, backgroundHeight);
|
||||
|
||||
// Draw the conditioning zone
|
||||
let [x, y, w, h] = getDrawArea(node, backgroundWidth, backgroundHeight);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "80");
|
||||
ctx.fillRect(widgetX + x, widgetY + y, w, h);
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
// Draw grid lines
|
||||
for (let x = 0; x <= width / 64; x += 1) {
|
||||
ctx.moveTo(widgetX + x * 64 * scale, widgetY);
|
||||
ctx.lineTo(widgetX + x * 64 * scale, widgetY + backgroundHeight);
|
||||
}
|
||||
|
||||
for (let y = 0; y <= height / 64; y += 1) {
|
||||
ctx.moveTo(widgetX, widgetY + y * 64 * scale);
|
||||
ctx.lineTo(widgetX + backgroundWidth, widgetY + y * 64 * scale);
|
||||
}
|
||||
|
||||
ctx.strokeStyle = "#66666650";
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// Draw current zone
|
||||
let [sx, sy, sw, sh] = getDrawArea(node, backgroundWidth, backgroundHeight);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "80");
|
||||
ctx.fillRect(widgetX + sx, widgetY + sy, sw, sh);
|
||||
|
||||
ctx.fillStyle = getDrawColor(0, "40");
|
||||
ctx.fillRect(widgetX + sx + border, widgetY + sy + border, sw - border * 2, sh - border * 2);
|
||||
|
||||
// Draw white border around the current zone
|
||||
ctx.strokeStyle = globalThis.LiteGraph.NODE_SELECTED_TITLE_COLOR;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(widgetX + sx, widgetY + sy, sw, sh);
|
||||
//ctx.strokeRect(finalSX, finalSY, finalSW, finalSH);
|
||||
|
||||
// Display
|
||||
ctx.beginPath();
|
||||
|
||||
ctx.arc(LiteGraph.NODE_SLOT_HEIGHT * 0.5, LiteGraph.NODE_SLOT_HEIGHT * (index + 0.5) + 4, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.stroke();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.closePath();
|
||||
|
||||
// Draw progress bar canvas
|
||||
if (backgroundWidth < widgetWidth) {
|
||||
xOffset += (widgetWidth - backgroundWidth) / 2 - margin;
|
||||
}
|
||||
|
||||
const barHeight = 8;
|
||||
let widgetYBar = widgetY + backgroundHeight + margin;
|
||||
|
||||
// Draw progress bar border
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.fillRect(
|
||||
widgetX - border,
|
||||
widgetYBar - border,
|
||||
backgroundWidth + border * 2,
|
||||
barHeight + border * 2
|
||||
);
|
||||
|
||||
// Draw progress bar area
|
||||
ctx.fillStyle = globalThis.LiteGraph.WIDGET_BGCOLOR; // Mismo color de fondo que el canvas
|
||||
ctx.fillRect(
|
||||
widgetX,
|
||||
widgetYBar,
|
||||
backgroundWidth,
|
||||
barHeight
|
||||
);
|
||||
|
||||
// Draw progress bar grid
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = "#66666650";
|
||||
|
||||
// Determine max lines
|
||||
const numLines = Math.floor(backgroundWidth / 64);
|
||||
|
||||
// Draw progress bar grid
|
||||
for (let x = 0; x <= width / 64; x += 1) {
|
||||
ctx.moveTo(widgetX + x * 64 * scale, widgetYBar);
|
||||
ctx.lineTo(widgetX + x * 64 * scale, widgetYBar + barHeight);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// Draw progress bar
|
||||
const progress = Math.min(blurRadius / 255, 1);
|
||||
ctx.fillStyle = "rgba(0, 120, 255, 0.5)";
|
||||
|
||||
ctx.fillRect(
|
||||
widgetX,
|
||||
widgetYBar,
|
||||
backgroundWidth * progress,
|
||||
barHeight
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
widget.canvas = document.createElement("canvas");
|
||||
widget.canvas.className = "mask-rect-area-canvas";
|
||||
widget.parent = node;
|
||||
|
||||
document.body.appendChild(widget.canvas);
|
||||
node.addCustomWidget(widget);
|
||||
|
||||
app.canvas.onDrawBackground = function () {
|
||||
// Draw node isnt fired once the node is off the screen
|
||||
// if it goes off screen quickly, the input may not be removed
|
||||
// this shifts it off screen so it can be moved back if the node is visible.
|
||||
for (let n in app.graph._nodes) {
|
||||
n = app.graph._nodes[n];
|
||||
for (let w in n.widgets) {
|
||||
let wid = n.widgets[w];
|
||||
if (Object.hasOwn(wid, "canvas")) {
|
||||
wid.canvas.style.left = -8000 + "px";
|
||||
wid.canvas.style.position = "absolute";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
node.onResize = function (size) {
|
||||
computeCanvasSize(node, size);
|
||||
};
|
||||
|
||||
return {minWidth: 200, minHeight: 200, widget};
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'drltdata.MaskRectArea',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "MaskRectArea") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
|
||||
this.setProperty("width", 512);
|
||||
this.setProperty("height", 512);
|
||||
this.setProperty("x", 0);
|
||||
this.setProperty("y", 0);
|
||||
this.setProperty("w", 50);
|
||||
this.setProperty("h", 50);
|
||||
this.setProperty("blur_radius", 0);
|
||||
|
||||
this.selected = false;
|
||||
this.index = 3;
|
||||
this.serialize_widgets = true;
|
||||
|
||||
CUSTOM_INT(this, "x", 0, function (v, _, node) {
|
||||
this.value = Math.max(0, Math.min(100, Math.round(v))); // Limitar entre 0 y 100
|
||||
node.properties["x"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "y", 0, function (v, _, node) {
|
||||
this.value = Math.max(0, Math.min(100, Math.round(v)));
|
||||
node.properties["y"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "w", 50, function (v, _, node) {
|
||||
this.value = Math.max(0, Math.min(100, Math.round(v)));
|
||||
node.properties["w"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "h", 50, function (v, _, node) {
|
||||
this.value = Math.max(0, Math.min(100, Math.round(v)));
|
||||
node.properties["h"] = this.value;
|
||||
});
|
||||
CUSTOM_INT(this, "blur_radius", 0, function (v, _, node) {
|
||||
this.value = Math.round(v) || 0;
|
||||
node.properties["blur_radius"] = this.value;
|
||||
},
|
||||
{"min": 0, "max": 255, "step": 10}
|
||||
);
|
||||
|
||||
showPreviewCanvas(this, app);
|
||||
|
||||
this.onSelected = function () {
|
||||
this.selected = true;
|
||||
};
|
||||
this.onDeselected = function () {
|
||||
this.selected = false;
|
||||
};
|
||||
|
||||
return r;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate the drawing area using percentage-based properties.
|
||||
function getDrawArea(node, backgroundWidth, backgroundHeight) {
|
||||
// Convert percentages to actual pixel values based on the background dimensions
|
||||
let x = (node.properties["x"] / 100) * backgroundWidth;
|
||||
let y = (node.properties["y"] / 100) * backgroundHeight;
|
||||
let w = (node.properties["w"] / 100) * backgroundWidth;
|
||||
let h = (node.properties["h"] / 100) * backgroundHeight;
|
||||
|
||||
// Ensure the values do not exceed the background boundaries
|
||||
if (x > backgroundWidth) {
|
||||
x = backgroundWidth;
|
||||
}
|
||||
if (y > backgroundHeight) {
|
||||
y = backgroundHeight;
|
||||
}
|
||||
|
||||
// Adjust width and height to fit within the background dimensions
|
||||
if (x + w > backgroundWidth) {
|
||||
w = Math.max(0, backgroundWidth - x);
|
||||
}
|
||||
if (y + h > backgroundHeight) {
|
||||
h = Math.max(0, backgroundHeight - y);
|
||||
}
|
||||
|
||||
return [x, y, w, h];
|
||||
}
|
||||
|
||||
function CUSTOM_INT(node, inputName, val, func, config = {}) {
|
||||
return {
|
||||
widget: node.addWidget(
|
||||
"number",
|
||||
inputName,
|
||||
val,
|
||||
func,
|
||||
Object.assign({}, {min: 0, max: 100, step: 10, precision: 0}, config)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function getDrawColor(percent, alpha) {
|
||||
let h = 360 * percent;
|
||||
let s = 50;
|
||||
let l = 50;
|
||||
l /= 100;
|
||||
const a = s * Math.min(l, 1 - l) / 100;
|
||||
const f = n => {
|
||||
const k = (n + h / 30) % 12;
|
||||
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
||||
return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
|
||||
};
|
||||
return `#${f(0)}${f(8)}${f(4)}${alpha}`;
|
||||
}
|
||||
|
||||
function computeCanvasSize(node, size) {
|
||||
if (node.widgets[0].last_y == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const MIN_HEIGHT = 200;
|
||||
const MIN_WIDTH = 200;
|
||||
|
||||
let y = LiteGraph.NODE_WIDGET_HEIGHT * Math.max(node.inputs.length, node.outputs.length) + 5;
|
||||
let freeSpace = size[1] - y;
|
||||
|
||||
// Compute the height of all non-customCanvas widgets
|
||||
let widgetHeight = 0;
|
||||
for (let i = 0; i < node.widgets.length; i++) {
|
||||
const w = node.widgets[i];
|
||||
if (w.type !== "customCanvas") {
|
||||
if (w.computeSize) {
|
||||
widgetHeight += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure there is enough vertical space
|
||||
freeSpace -= widgetHeight;
|
||||
|
||||
// Adjust the height of the node if needed
|
||||
if (freeSpace < MIN_HEIGHT) {
|
||||
freeSpace = MIN_HEIGHT;
|
||||
node.size[1] = y + widgetHeight + freeSpace;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Ensure the node width meets the minimum width requirement
|
||||
if (node.size[0] < MIN_WIDTH) {
|
||||
node.size[0] = MIN_WIDTH;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Position each of the widgets
|
||||
for (const w of node.widgets) {
|
||||
w.y = y;
|
||||
if (w.type === "customCanvas") {
|
||||
y += freeSpace;
|
||||
} else if (w.computeSize) {
|
||||
y += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
|
||||
}
|
||||
}
|
||||
|
||||
node.canvasHeight = freeSpace;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
|
||||
"scheduler": (core.SCHEDULERS,),
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"basic_pipe": ("BASIC_PIPE",),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -45,6 +45,8 @@ class SEGSDetailerForAnimateDiff:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size.\nThis node is applied specifically to SEGS rather than the entire image. To apply it to the entire image, use the 'SEGS Paste' node.\nAs a specialized detailer node for improving video details, such as in AnimateDiff, this node can handle cases where the masks contained in SEGS serve as batch masks spanning multiple frames."
|
||||
|
||||
@staticmethod
|
||||
def do_detail(image_frames, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, basic_pipe, refiner_ratio=None, refiner_basic_pipe_opt=None, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
@@ -60,7 +62,7 @@ class SEGSDetailerForAnimateDiff:
|
||||
new_segs = []
|
||||
cnet_image_list = []
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
for seg in segs[1]:
|
||||
@@ -94,13 +96,18 @@ class SEGSDetailerForAnimateDiff:
|
||||
for condition, details in negative
|
||||
]
|
||||
|
||||
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, seg.cropped_mask,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
if not (isinstance(model, str) and model == "DUMMY"):
|
||||
enhanced_image_tensor, cnet_images = core.enhance_detail_for_animatediff(cropped_image_frames, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, seg.cropped_mask,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
else:
|
||||
enhanced_image_tensor = cropped_image_frames
|
||||
cnet_images = None
|
||||
|
||||
if cnet_images is not None:
|
||||
cnet_image_list.extend(cnet_images)
|
||||
|
||||
@@ -143,7 +150,7 @@ class DetailerForEachPipeForAnimateDiff:
|
||||
"scheduler": (core.SCHEDULERS,),
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
||||
"basic_pipe": ("BASIC_PIPE", ),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
},
|
||||
"optional": {
|
||||
@@ -161,6 +168,8 @@ class DetailerForEachPipeForAnimateDiff:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size.\nThis node is a specialized detailer node for enhancing video details, such as in AnimateDiff. It can handle cases where the masks contained in SEGS serve as batch masks spanning multiple frames."
|
||||
|
||||
@staticmethod
|
||||
def doit(image_frames, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, basic_pipe, refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import configparser
|
||||
import os
|
||||
|
||||
version_code = [7, 14, 1]
|
||||
version_code = [8, 11]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 24
|
||||
|
||||
@@ -48,7 +48,7 @@ preview_bridge_last_mask_cache = {}
|
||||
|
||||
current_prompt = None
|
||||
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]']
|
||||
SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS + ['AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan']
|
||||
|
||||
|
||||
def is_execution_model_version_supported():
|
||||
@@ -244,7 +244,8 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
detailer_hook=None,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None,
|
||||
refiner_negative=None, control_net_wrapper=None, cycle=1,
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func=None):
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func=None,
|
||||
vae_tiled_encode=False, vae_tiled_decode=False):
|
||||
|
||||
if noise_mask is not None:
|
||||
noise_mask = utils.tensor_gaussian_blur_mask(noise_mask, noise_mask_feather)
|
||||
@@ -334,7 +335,7 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
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)
|
||||
latent_image = to_latent_image(upscaled_image, vae, vae_tiled_encode=vae_tiled_encode)
|
||||
if noise_mask is not None:
|
||||
latent_image['noise_mask'] = noise_mask
|
||||
|
||||
@@ -369,12 +370,18 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
|
||||
# non-latent downscale - latent downscale cause bad quality
|
||||
try:
|
||||
# try to decode image normally
|
||||
refined_image = vae.decode(refined_latent['samples'])
|
||||
except Exception as e:
|
||||
#usually an out-of-memory exception from the decode, so try a tiled approach
|
||||
refined_image = vae.decode_tiled(refined_latent["samples"], tile_x=64, tile_y=64, )
|
||||
start = time.time()
|
||||
if vae_tiled_decode:
|
||||
(refined_image,) = nodes.VAEDecodeTiled().decode(vae, refined_latent, 512) # using default settings
|
||||
print(f"[Impact Pack] vae decoded (tiled) in {time.time() - start:.1f}s")
|
||||
else:
|
||||
try:
|
||||
refined_image = vae.decode(refined_latent['samples'])
|
||||
except Exception as e:
|
||||
# usually an out-of-memory exception from the decode, so try a tiled approach
|
||||
print(f"[Impact Pack] failed after {time.time() - start:.1f}s, doing vae.decode_tiled 64...")
|
||||
refined_image = vae.decode_tiled(refined_latent["samples"], tile_x=64, tile_y=64, )
|
||||
print(f"[Impact Pack] vae decoded in {time.time() - start:.1f}s")
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_image = detailer_hook.post_decode(refined_image)
|
||||
@@ -1384,9 +1391,14 @@ def vae_decode(vae, samples, use_tile, hook, tile_size=512, overlap=64):
|
||||
return pixels
|
||||
|
||||
|
||||
def vae_encode(vae, pixels, use_tile, hook, tile_size=512):
|
||||
def vae_encode(vae, pixels, use_tile, hook, tile_size=512, overlap=64):
|
||||
if use_tile:
|
||||
samples = nodes.VAEEncodeTiled().encode(vae, pixels, tile_size)[0]
|
||||
encoder = nodes.VAEEncodeTiled()
|
||||
if 'overlap' in inspect.signature(encoder.encode).parameters:
|
||||
samples = encoder.encode(vae, pixels, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
print(f"[Impact Pack] Your ComfyUI is outdated.")
|
||||
samples = encoder.encode(vae, pixels, tile_size)[0]
|
||||
else:
|
||||
samples = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
|
||||
@@ -1412,7 +1424,7 @@ def latent_upscale_on_pixel_space_shape2(samples, scale_method, w, h, vae, use_t
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space(samples, scale_method, scale_factor, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
@@ -1433,7 +1445,7 @@ def latent_upscale_on_pixel_space2(samples, scale_method, scale_factor, vae, use
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_with_model_shape(samples, scale_method, upscale_model, new_w, new_h, vae, use_tile=False, tile_size=512, save_temp_prefix=None, hook=None, overlap=64):
|
||||
@@ -1464,7 +1476,7 @@ def latent_upscale_on_pixel_space_with_model_shape2(samples, scale_method, upsca
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
def latent_upscale_on_pixel_space_with_model(samples, scale_method, upscale_model, scale_factor, vae, use_tile=False,
|
||||
@@ -1500,7 +1512,7 @@ def latent_upscale_on_pixel_space_with_model2(samples, scale_method, upscale_mod
|
||||
if hook is not None:
|
||||
pixels = hook.post_upscale(pixels)
|
||||
|
||||
return (vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size), old_pixels)
|
||||
return vae_encode(vae, pixels, use_tile, hook, tile_size=tile_size, overlap=overlap), old_pixels
|
||||
|
||||
|
||||
class TwoSamplersForMaskUpscaler:
|
||||
@@ -1670,8 +1682,14 @@ class PixelKSampleUpscaler:
|
||||
preprocessor = nodes.NODE_CLASS_MAPPINGS['TilePreprocessor']()
|
||||
# might add capacity to set pyrUp_iters later, not needed for now though
|
||||
preprocessed = preprocessor.execute(images, pyrUp_iters=3, resolution=min(image_w, image_h))[0]
|
||||
apply_cnet = getattr(nodes.ControlNetApply(), nodes.ControlNetApply.FUNCTION)
|
||||
positive = apply_cnet(positive, self.tile_cnet, preprocessed, strength=self.tile_cnet_strength)[0]
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive=positive,
|
||||
negative=negative,
|
||||
control_net=self.tile_cnet,
|
||||
image=preprocessed,
|
||||
strength=self.tile_cnet_strength,
|
||||
start_percent=0,
|
||||
end_percent=1.0,
|
||||
vae=self.vae)
|
||||
|
||||
refined_latent = impact_sampling.impact_sample(model, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, upscaled_latent, denoise, scheduler_func=self.scheduler_func)
|
||||
@@ -1942,7 +1960,7 @@ class PixelTiledKSampleUpscaler:
|
||||
def __init__(self, scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
denoise,
|
||||
tile_width, tile_height, tiling_strategy,
|
||||
upscale_model_opt=None, hook_opt=None, tile_cnet_opt=None, tile_size=512, tile_cnet_strength=1.0):
|
||||
upscale_model_opt=None, hook_opt=None, tile_cnet_opt=None, tile_size=512, tile_cnet_strength=1.0, overlap=64):
|
||||
self.params = scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise
|
||||
self.vae = vae
|
||||
self.tile_params = tile_width, tile_height, tiling_strategy
|
||||
@@ -1952,6 +1970,7 @@ class PixelTiledKSampleUpscaler:
|
||||
self.tile_size = tile_size
|
||||
self.is_tiled = True
|
||||
self.tile_cnet_strength = tile_cnet_strength
|
||||
self.overlap = overlap
|
||||
|
||||
def tiled_ksample(self, latent, images):
|
||||
if "BNK_TiledKSampler" in nodes.NODE_CLASS_MAPPINGS:
|
||||
@@ -1974,8 +1993,14 @@ class PixelTiledKSampleUpscaler:
|
||||
preprocessor = nodes.NODE_CLASS_MAPPINGS['TilePreprocessor']()
|
||||
# might add capacity to set pyrUp_iters later, not needed for now though
|
||||
preprocessed = preprocessor.execute(images, pyrUp_iters=3, resolution=min(image_w, image_h))[0]
|
||||
apply_cnet = getattr(nodes.ControlNetApply(), nodes.ControlNetApply.FUNCTION)
|
||||
positive = apply_cnet(positive, self.tile_cnet, preprocessed, strength=self.tile_cnet_strength)[0]
|
||||
|
||||
positive, negative = nodes.ControlNetApplyAdvanced().apply_controlnet(positive=positive,
|
||||
negative=negative,
|
||||
control_net=self.tile_cnet,
|
||||
image=preprocessed,
|
||||
strength=self.tile_cnet_strength,
|
||||
start_percent=0, end_percent=1.0,
|
||||
vae=self.vae)
|
||||
|
||||
return TiledKSampler().sample(model, seed, tile_width, tile_height, tiling_strategy, steps, cfg, sampler_name,
|
||||
scheduler, positive, negative, latent, denoise)[0]
|
||||
|
||||
@@ -28,6 +28,7 @@ import base64
|
||||
import impact.wildcards as wildcards
|
||||
from . import hooks
|
||||
from . import utils
|
||||
import inspect
|
||||
|
||||
|
||||
try:
|
||||
@@ -95,9 +96,13 @@ class SAMLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
models = [x for x in folder_paths.get_filename_list("sams") if 'hq' not in x]
|
||||
|
||||
if 'ESAM_ModelLoader_Zho' in nodes.NODE_CLASS_MAPPINGS:
|
||||
models.append('ESAM')
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"model_name": (models + ['ESAM'], {"tooltip": "The detection accuracy varies depending on the SAM model. ESAM can only be used if ComfyUI-YoloWorld-EfficientSAM is installed."}),
|
||||
"model_name": (models, {"tooltip": "The detection accuracy varies depending on the SAM model. ESAM can only be used if ComfyUI-YoloWorld-EfficientSAM is installed."}),
|
||||
"device_mode": (["AUTO", "Prefer GPU", "CPU"], {"tooltip": "AUTO: Only applicable when a GPU is available. It temporarily loads the SAM_MODEL into VRAM only when the detection function is used.\n"
|
||||
"Prefer GPU: Tries to keep the SAM_MODEL on the GPU whenever possible. This can be used when there is sufficient VRAM available.\n"
|
||||
"CPU: Always loads only on the CPU."}),
|
||||
@@ -191,7 +196,7 @@ class DetailerForEach:
|
||||
return {"required": {
|
||||
"image": ("IMAGE", ),
|
||||
"segs": ("SEGS", ),
|
||||
"model": ("MODEL",),
|
||||
"model": ("MODEL", {"tooltip": "If the `ImpactDummyInput` is connected to the model, the inference stage is skipped."}),
|
||||
"clip": ("CLIP",),
|
||||
"vae": ("VAE",),
|
||||
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
@@ -217,6 +222,8 @@ class DetailerForEach:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,11 +232,17 @@ class DetailerForEach:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "It enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size."
|
||||
|
||||
@staticmethod
|
||||
def get_core_module():
|
||||
return core
|
||||
|
||||
@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,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -265,7 +278,7 @@ class DetailerForEach:
|
||||
else:
|
||||
ordered_segs = segs[1]
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
for i, seg in enumerate(ordered_segs):
|
||||
@@ -293,13 +306,16 @@ class DetailerForEach:
|
||||
|
||||
seg_seed = seed + i if seg_seed is None else seg_seed
|
||||
|
||||
cropped_positive = [
|
||||
[condition, {
|
||||
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
||||
for k, v in details.items()
|
||||
}]
|
||||
for condition, details in positive
|
||||
]
|
||||
if not isinstance(positive, str):
|
||||
cropped_positive = [
|
||||
[condition, {
|
||||
k: core.crop_condition_mask(v, image, seg.crop_region) if k == "mask" else v
|
||||
for k, v in details.items()
|
||||
}]
|
||||
for condition, details in positive
|
||||
]
|
||||
else:
|
||||
cropped_positive = positive
|
||||
|
||||
if not isinstance(negative, str):
|
||||
cropped_negative = [
|
||||
@@ -320,16 +336,21 @@ class DetailerForEach:
|
||||
break
|
||||
|
||||
orig_cropped_image = cropped_image.clone()
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size,
|
||||
seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
wildcard_opt=wildcard_item, wildcard_opt_concat_mode=wildcard_concat_mode,
|
||||
detailer_hook=detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func=scheduler_func_opt)
|
||||
if not (isinstance(model, str) and model == "DUMMY"):
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for_bbox, max_size,
|
||||
seg.bbox, seg_seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
wildcard_opt=wildcard_item, wildcard_opt_concat_mode=wildcard_concat_mode,
|
||||
detailer_hook=detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative, control_net_wrapper=seg.control_net_wrapper,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func=scheduler_func_opt, vae_tiled_encode=tiled_encode,
|
||||
vae_tiled_decode=tiled_decode)
|
||||
else:
|
||||
enhanced_image = cropped_image
|
||||
cnet_pils = None
|
||||
|
||||
if cnet_pils is not None:
|
||||
cnet_pil_list.extend(cnet_pils)
|
||||
@@ -372,13 +393,15 @@ class DetailerForEach:
|
||||
|
||||
def doit(self, image, segs, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name,
|
||||
scheduler, positive, negative, denoise, feather, noise_mask, force_inpaint, wildcard, cycle=1,
|
||||
detailer_hook=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
detailer_hook=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None,
|
||||
tiled_encode=False, tiled_decode=False):
|
||||
|
||||
enhanced_img, *_ = \
|
||||
DetailerForEach.do_detail(image, segs, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps,
|
||||
cfg, sampler_name, scheduler, positive, negative, denoise, feather, noise_mask,
|
||||
force_inpaint, wildcard, detailer_hook,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func_opt=scheduler_func_opt, tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
return (enhanced_img, )
|
||||
|
||||
@@ -401,7 +424,7 @@ class DetailerForEachPipe:
|
||||
"feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}),
|
||||
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"basic_pipe": ("BASIC_PIPE", ),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
|
||||
@@ -413,6 +436,8 @@ class DetailerForEachPipe:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,10 +448,13 @@ class DetailerForEachPipe:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = DetailerForEach.DESCRIPTION
|
||||
|
||||
def doit(self, image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, noise_mask, force_inpaint, basic_pipe, wildcard,
|
||||
refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None,
|
||||
tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -444,7 +472,8 @@ class DetailerForEachPipe:
|
||||
force_inpaint, wildcard, detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt,
|
||||
tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
# set fallback image
|
||||
if len(cnet_pil_list) == 0:
|
||||
@@ -458,7 +487,7 @@ class FaceDetailer:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE", ),
|
||||
"model": ("MODEL",),
|
||||
"model": ("MODEL", {"tooltip": "If the `ImpactDummyInput` is connected to the model, the inference stage is skipped."}),
|
||||
"clip": ("CLIP",),
|
||||
"vae": ("VAE",),
|
||||
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
@@ -501,6 +530,8 @@ class FaceDetailer:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "MASK", "DETAILER_PIPE", "IMAGE")
|
||||
@@ -510,6 +541,8 @@ class FaceDetailer:
|
||||
|
||||
CATEGORY = "ImpactPack/Simple"
|
||||
|
||||
DESCRIPTION = "This node enhances details by automatically detecting specific objects in the input image using detection models (bbox, segm, sam) and regenerating the image by enlarging the detected area based on the guide size.\nAlthough this node is specialized to simplify the commonly used facial detail enhancement workflow, it can also be used for various automatic inpainting purposes depending on the detection model."
|
||||
|
||||
@staticmethod
|
||||
def enhance_face(image, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, denoise, feather, noise_mask, force_inpaint,
|
||||
@@ -518,7 +551,7 @@ class FaceDetailer:
|
||||
sam_mask_hint_use_negative, drop_size,
|
||||
bbox_detector, segm_detector=None, sam_model_opt=None, wildcard_opt=None, detailer_hook=None,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, cycle=1,
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
# make default prompt as 'face' if empty prompt for CLIPSeg
|
||||
bbox_detector.setAux('face')
|
||||
@@ -550,7 +583,8 @@ class FaceDetailer:
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func_opt=scheduler_func_opt, tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
else:
|
||||
enhanced_img = image
|
||||
cropped_enhanced = []
|
||||
@@ -576,7 +610,8 @@ class FaceDetailer:
|
||||
bbox_threshold, bbox_dilation, bbox_crop_factor,
|
||||
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold,
|
||||
sam_mask_hint_use_negative, drop_size, bbox_detector, wildcard, cycle=1,
|
||||
sam_model_opt=None, segm_detector_opt=None, detailer_hook=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
sam_model_opt=None, segm_detector_opt=None, detailer_hook=None, inpaint_model=False, noise_mask_feather=0,
|
||||
scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
result_img = None
|
||||
result_mask = None
|
||||
@@ -594,7 +629,8 @@ class FaceDetailer:
|
||||
bbox_threshold, bbox_dilation, bbox_crop_factor,
|
||||
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold,
|
||||
sam_mask_hint_use_negative, drop_size, bbox_detector, segm_detector_opt, sam_model_opt, wildcard, detailer_hook,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt,
|
||||
tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
result_img = torch.cat((result_img, enhanced_img), dim=0) if result_img is not None else enhanced_img
|
||||
result_mask = torch.cat((result_mask, mask), dim=0) if result_mask is not None else mask
|
||||
@@ -982,6 +1018,7 @@ class PixelTiledKSampleUpscalerProvider:
|
||||
"pk_hook_opt": ("PK_HOOK", ),
|
||||
"tile_cnet_opt": ("CONTROL_NET", ),
|
||||
"tile_cnet_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
"overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,11 +1028,11 @@ class PixelTiledKSampleUpscalerProvider:
|
||||
CATEGORY = "ImpactPack/Upscale"
|
||||
|
||||
def doit(self, scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, tile_width, tile_height, tiling_strategy, upscale_model_opt=None,
|
||||
pk_hook_opt=None, tile_cnet_opt=None, tile_cnet_strength=1.0):
|
||||
pk_hook_opt=None, tile_cnet_opt=None, tile_cnet_strength=1.0, overlap=64):
|
||||
if "BNK_TiledKSampler" in nodes.NODE_CLASS_MAPPINGS:
|
||||
upscaler = core.PixelTiledKSampleUpscaler(scale_method, model, vae, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise,
|
||||
tile_width, tile_height, tiling_strategy, upscale_model_opt, pk_hook_opt, tile_cnet_opt,
|
||||
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength)
|
||||
tile_size=max(tile_width, tile_height), tile_cnet_strength=tile_cnet_strength, overlap=overlap)
|
||||
return (upscaler, )
|
||||
else:
|
||||
utils.try_install_custom_node('https://github.com/BlenderNeko/ComfyUI_TiledKSampler',
|
||||
@@ -1308,7 +1345,11 @@ class IterativeImageUpscale:
|
||||
|
||||
core.update_node_status(unique_id, "VAEEncode (first)", 0)
|
||||
if upscaler.is_tiled:
|
||||
latent = nodes.VAEEncodeTiled().encode(vae, pixels, upscaler.tile_size)[0]
|
||||
encoder = nodes.VAEEncodeTiled()
|
||||
if 'overlap' in inspect.signature(encoder.encode).parameters:
|
||||
latent = encoder.encode(vae, pixels, upscaler.tile_size, overlap=upscaler.overlap)[0]
|
||||
else:
|
||||
latent = encoder.encode(vae, pixels, upscaler.tile_size)[0]
|
||||
else:
|
||||
latent = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
|
||||
@@ -1330,7 +1371,7 @@ class FaceDetailerPipe:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE", ),
|
||||
"detailer_pipe": ("DETAILER_PIPE",),
|
||||
"detailer_pipe": ("DETAILER_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the detailer_pipe, the inference stage is skipped."}),
|
||||
"guide_size": ("FLOAT", {"default": 512, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
"guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}),
|
||||
"max_size": ("FLOAT", {"default": 1024, "min": 64, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
@@ -1364,6 +1405,8 @@ class FaceDetailerPipe:
|
||||
"inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}),
|
||||
"scheduler_func_opt": ("SCHEDULER_FUNC",),
|
||||
"tiled_encode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"tiled_decode": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1374,11 +1417,14 @@ class FaceDetailerPipe:
|
||||
|
||||
CATEGORY = "ImpactPack/Simple"
|
||||
|
||||
DESCRIPTION = FaceDetailer.DESCRIPTION
|
||||
|
||||
def doit(self, image, detailer_pipe, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, noise_mask, force_inpaint, bbox_threshold, bbox_dilation, bbox_crop_factor,
|
||||
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion,
|
||||
sam_mask_hint_threshold, sam_mask_hint_use_negative, drop_size, refiner_ratio=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None,
|
||||
tiled_encode=False, tiled_decode=False):
|
||||
|
||||
result_img = None
|
||||
result_mask = None
|
||||
@@ -1401,7 +1447,8 @@ class FaceDetailerPipe:
|
||||
sam_mask_hint_use_negative, drop_size, bbox_detector, segm_detector, sam_model_opt, wildcard, detailer_hook,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt,
|
||||
tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
result_img = torch.cat((result_img, enhanced_img), dim=0) if result_img is not None else enhanced_img
|
||||
result_mask = torch.cat((result_mask, mask), dim=0) if result_mask is not None else mask
|
||||
@@ -1467,6 +1514,8 @@ class MaskDetailerPipe:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = ""
|
||||
|
||||
def doit(self, image, mask, basic_pipe, guide_size, guide_size_for, max_size, mask_mode,
|
||||
seed, steps, cfg, sampler_name, scheduler, denoise,
|
||||
feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1,
|
||||
@@ -1535,7 +1584,7 @@ class DetailerForEachTest(DetailerForEach):
|
||||
|
||||
def doit(self, image, segs, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name,
|
||||
scheduler, positive, negative, denoise, feather, noise_mask, force_inpaint, wildcard, detailer_hook=None,
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -1544,7 +1593,8 @@ class DetailerForEachTest(DetailerForEach):
|
||||
DetailerForEach.do_detail(image, segs, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps,
|
||||
cfg, sampler_name, scheduler, positive, negative, denoise, feather, noise_mask,
|
||||
force_inpaint, wildcard, detailer_hook,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func_opt=scheduler_func_opt, tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
# set fallback image
|
||||
if len(cropped) == 0:
|
||||
@@ -1571,9 +1621,12 @@ class DetailerForEachTestPipe(DetailerForEachPipe):
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = DetailerForEach.DESCRIPTION
|
||||
|
||||
def doit(self, image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, feather, noise_mask, force_inpaint, basic_pipe, wildcard, cycle=1,
|
||||
refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
|
||||
refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None, inpaint_model=False, noise_mask_feather=0,
|
||||
scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
|
||||
|
||||
if len(image) > 1:
|
||||
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
|
||||
@@ -1592,7 +1645,8 @@ class DetailerForEachTestPipe(DetailerForEachPipe):
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive,
|
||||
refiner_negative=refiner_negative,
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func_opt=scheduler_func_opt)
|
||||
cycle=cycle, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather,
|
||||
scheduler_func_opt=scheduler_func_opt, tiled_encode=tiled_encode, tiled_decode=tiled_decode)
|
||||
|
||||
# set fallback image
|
||||
if len(cropped) == 0:
|
||||
@@ -1830,6 +1884,135 @@ def get_file_item(base_type, path):
|
||||
}
|
||||
|
||||
|
||||
class MaskRectArea:
|
||||
# Creates a rectangle mask using percentage.
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
},
|
||||
"hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
FUNCTION = "create_mask"
|
||||
|
||||
def create_mask(self, extra_pnginfo, unique_id, **kwargs):
|
||||
# search for node
|
||||
node_found = False
|
||||
for node in extra_pnginfo["workflow"]["nodes"]:
|
||||
if node["id"] == int(unique_id):
|
||||
min_x = node["properties"].get("x", 0) / 100
|
||||
min_y = node["properties"].get("y", 0) / 100
|
||||
width = node["properties"].get("w", 0) / 100
|
||||
height = node["properties"].get("h", 0) / 100
|
||||
blur_radius = node["properties"].get("blur_radius", 0)
|
||||
node_found = True
|
||||
break
|
||||
|
||||
if not node_found:
|
||||
raise ValueError(f"No node found with unique_id {unique_id}.")
|
||||
|
||||
# Create a mask with standard resolution (e.g., 512x512)
|
||||
resolution = 512
|
||||
mask = torch.zeros((resolution, resolution))
|
||||
|
||||
# Calculate pixel coordinates
|
||||
min_x_px = int(min_x * resolution)
|
||||
min_y_px = int(min_y * resolution)
|
||||
max_x_px = int((min_x + width) * resolution)
|
||||
max_y_px = int((min_y + height) * resolution)
|
||||
|
||||
# Draw the rectangle on the mask
|
||||
mask[min_y_px:max_y_px, min_x_px:max_x_px] = 1
|
||||
|
||||
# Apply blur if the radii are greater than 0
|
||||
if blur_radius > 0:
|
||||
dx = blur_radius * 2 + 1
|
||||
dy = blur_radius * 2 + 1
|
||||
|
||||
# Convert the mask to a format compatible with OpenCV (numpy array)
|
||||
mask_np = mask.cpu().numpy().astype("float32")
|
||||
|
||||
# Apply Gaussian Blur
|
||||
blurred_mask = cv2.GaussianBlur(mask_np, (dx, dy), 0)
|
||||
|
||||
# Convert back to tensor
|
||||
mask = torch.from_numpy(blurred_mask)
|
||||
|
||||
# Return the mask as a tensor with an additional channel
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
|
||||
class MaskRectAreaAdvanced:
|
||||
# Creates a rectangle mask using pixels relative to image size.
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
},
|
||||
"hidden": {"extra_pnginfo": "EXTRA_PNGINFO", "unique_id": "UNIQUE_ID"}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
|
||||
CATEGORY = "ImpactPack/Operation"
|
||||
FUNCTION = "create_mask_advanced"
|
||||
|
||||
def create_mask_advanced(self, extra_pnginfo, unique_id, **kwargs):
|
||||
# search for node
|
||||
node_found = False
|
||||
for node in extra_pnginfo["workflow"]["nodes"]:
|
||||
if node["id"] == int(unique_id):
|
||||
min_x = node["properties"]["x"]
|
||||
min_y = node["properties"]["y"]
|
||||
width = node["properties"]["w"]
|
||||
height = node["properties"]["h"]
|
||||
image_width = node["properties"]["width"]
|
||||
image_height = node["properties"]["height"]
|
||||
blur_radius = node["properties"]["blur_radius"]
|
||||
node_found = True
|
||||
break
|
||||
|
||||
if not node_found:
|
||||
raise ValueError(f"No node found with unique_id {unique_id}.")
|
||||
|
||||
# Calculate maximum coordinates
|
||||
max_x = min_x + width
|
||||
max_y = min_y + height
|
||||
|
||||
# Create a mask with the image dimensions
|
||||
mask = torch.zeros((image_height, image_width))
|
||||
|
||||
# Draw the rectangle on the mask
|
||||
mask[int(min_y):int(max_y), int(min_x):int(max_x)] = 1
|
||||
|
||||
# Apply blur if the radii are greater than 0
|
||||
if blur_radius > 0:
|
||||
dx = blur_radius * 2 + 1
|
||||
dy = blur_radius * 2 + 1
|
||||
|
||||
# Convert the mask to a format compatible with OpenCV (numpy array)
|
||||
mask_np = mask.cpu().numpy().astype("float32")
|
||||
|
||||
# Apply Gaussian Blur
|
||||
blurred_mask = cv2.GaussianBlur(mask_np, (dx, dy), 0)
|
||||
|
||||
# Convert back to tensor
|
||||
mask = torch.from_numpy(blurred_mask)
|
||||
|
||||
# Return the mask as a tensor with an additional channel
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
|
||||
class ImageReceiver:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -2176,7 +2359,11 @@ class ImpactWildcardProcessor:
|
||||
return {"required": {
|
||||
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "Enter a prompt using wildcard syntax."}),
|
||||
"populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "The actual value passed during the execution of 'ImpactWildcardProcessor' is what is shown here. The behavior varies slightly depending on the mode. Wildcard syntax can also be used in 'populated_text'."}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed", "tooltip": "Populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\nFixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode."}),
|
||||
"mode": (["populate", "fixed", "reproduce"], {"default": "populate", "tooltip":
|
||||
"populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\n"
|
||||
"fixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode.\n"
|
||||
"reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."
|
||||
}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Determines the random seed to be used for wildcard processing."}),
|
||||
"Select to add Wildcard": (["Select the Wildcard to add to the text"],),
|
||||
},
|
||||
@@ -2185,9 +2372,10 @@ class ImpactWildcardProcessor:
|
||||
CATEGORY = "ImpactPack/Prompt"
|
||||
|
||||
DESCRIPTION = ("The 'ImpactWildcardProcessor' processes text prompts written in wildcard syntax and outputs the processed text prompt.\n\n"
|
||||
"TIP: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'Fixed'.")
|
||||
"TIP: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'fixed'.")
|
||||
|
||||
RETURN_TYPES = ("STRING", )
|
||||
RETURN_NAMES = ("processed text",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
@staticmethod
|
||||
@@ -2207,8 +2395,10 @@ class ImpactWildcardEncode:
|
||||
"clip": ("CLIP",),
|
||||
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "Enter a prompt using wildcard syntax."}),
|
||||
"populated_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "The actual value passed during the execution of 'ImpactWildcardEncode' is what is shown here. The behavior varies slightly depending on the mode. Wildcard syntax can also be used in 'populated_text'."}),
|
||||
"mode": ("BOOLEAN", {"default": True, "label_on": "Populate", "label_off": "Fixed", "tooltip": "Populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\n"
|
||||
"Fixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode."}),
|
||||
"mode": (["populate", "fixed", "reproduce"], {"tooltip":
|
||||
"populate: Before running the workflow, it overwrites the existing value of 'populated_text' with the prompt processed from 'wildcard_text'. In this mode, 'populated_text' cannot be edited.\n"
|
||||
"fixed: Ignores wildcard_text and keeps 'populated_text' as is. You can edit 'populated_text' in this mode\n."
|
||||
"reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."}),
|
||||
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"), ),
|
||||
"Select to add Wildcard": (["Select the Wildcard to add to the text"], ),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "Determines the random seed to be used for wildcard processing."}),
|
||||
@@ -2218,7 +2408,7 @@ class ImpactWildcardEncode:
|
||||
CATEGORY = "ImpactPack/Prompt"
|
||||
|
||||
DESCRIPTION = ("The 'ImpactWildcardEncode' node processes text prompts written in wildcard syntax and outputs them as conditioning. It also supports LoRA syntax, with the applied LoRA reflected in the model's output.\n\n"
|
||||
"TIP1: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'Fixed'.\n"
|
||||
"TIP1: Before the workflow is executed, the processing result of 'wildcard_text' is displayed in 'populated_text', and the populated text is saved along with the workflow. If you want to use a seed converted as input, write the prompt directly in 'populated_text' instead of 'wildcard_text', and set the mode to 'fixed'.\n"
|
||||
"TIP2: If the 'Inspire Pack' is installed, LBW(LoRA Block Weight) syntax can also be applied.")
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CLIP", "CONDITIONING", "STRING")
|
||||
@@ -2245,7 +2435,7 @@ class ImpactSchedulerAdapter:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"defaultInput": True, }),
|
||||
"extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]'],),
|
||||
"extra_scheduler": (['None', 'AYS SDXL', 'AYS SD1', 'AYS SVD', 'GITS[coeff=1.2]', 'LTXV[default]', 'OSS FLUX', 'OSS Wan'],),
|
||||
}}
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@@ -29,6 +29,8 @@ def calculate_sigmas(model, sampler, scheduler, steps):
|
||||
sigmas = nodes.NODE_CLASS_MAPPINGS['GITSScheduler']().get_sigmas(float(scheduler[11:-1]), steps, denoise=1.0)[0]
|
||||
elif scheduler == 'LTXV[default]':
|
||||
sigmas = nodes.NODE_CLASS_MAPPINGS['LTXVScheduler']().get_sigmas(20, 2.05, 0.95, True, 0.1)[0]
|
||||
elif scheduler.startswith('OSS'):
|
||||
sigmas = nodes.NODE_CLASS_MAPPINGS['OptimalStepsScheduler']().get_sigmas(scheduler[4:], steps, denoise=1.0)[0]
|
||||
else:
|
||||
sigmas = samplers.calculate_sigmas(model.get_model_object("model_sampling"), scheduler, steps)
|
||||
|
||||
@@ -46,65 +48,27 @@ def get_noise_sampler(x, cpu, total_sigmas, **kwargs):
|
||||
|
||||
|
||||
def ksampler(sampler_name, total_sigmas, extra_options={}, inpaint_options={}):
|
||||
if sampler_name == "dpmpp_sde":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
if sampler_name in ["dpmpp_sde", "dpmpp_sde_gpu", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu"]:
|
||||
if sampler_name == "dpmpp_sde":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_sde
|
||||
elif sampler_name == "dpmpp_sde_gpu":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_sde_gpu
|
||||
elif sampler_name == "dpmpp_2m_sde":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_2m_sde
|
||||
elif sampler_name == "dpmpp_2m_sde_gpu":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_2m_sde_gpu
|
||||
elif sampler_name == "dpmpp_3m_sde":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_3m_sde
|
||||
elif sampler_name == "dpmpp_3m_sde_gpu":
|
||||
orig_sampler_function = k_diffusion_sampling.sample_dpmpp_3m_sde_gpu
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_sde(model, x, sigmas, **kwargs)
|
||||
def sampler_function_wrapper(model, x, sigmas, **kwargs):
|
||||
if 'noise_sampler' not in kwargs:
|
||||
kwargs['noise_sampler'] = get_noise_sampler(x, 'gpu' not in sampler_name, total_sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
return orig_sampler_function(model, x, sigmas, **kwargs)
|
||||
|
||||
elif sampler_name == "dpmpp_sde_gpu":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, False, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_sde_gpu(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_2m_sde":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_2m_sde(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_2m_sde_gpu":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, False, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_2m_sde_gpu(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_3m_sde":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_3m_sde(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
|
||||
elif sampler_name == "dpmpp_3m_sde_gpu":
|
||||
def sample_dpmpp_sde(model, x, sigmas, **kwargs):
|
||||
noise_sampler = get_noise_sampler(x, False, total_sigmas, **kwargs)
|
||||
if noise_sampler is not None:
|
||||
kwargs['noise_sampler'] = noise_sampler
|
||||
|
||||
return k_diffusion_sampling.sample_dpmpp_3m_sde_gpu(model, x, sigmas, **kwargs)
|
||||
|
||||
sampler_function = sample_dpmpp_sde
|
||||
sampler_function = sampler_function_wrapper
|
||||
|
||||
else:
|
||||
return comfy.samplers.sampler_object(sampler_name)
|
||||
|
||||
@@ -22,6 +22,7 @@ import comfy
|
||||
from io import BytesIO
|
||||
import random
|
||||
from server import PromptServer
|
||||
import logging
|
||||
|
||||
|
||||
sam_predictor = None
|
||||
@@ -77,9 +78,13 @@ async def sam_prepare(request):
|
||||
if data['sam_model_name'] == 'auto':
|
||||
model_name = impact.config.get_config()['sam_editor_model']
|
||||
|
||||
model_name = os.path.join(impact_pack.model_path, "sams", model_name)
|
||||
model_path = folder_paths.get_full_path("sams", model_name)
|
||||
|
||||
print(f"[INFO] ComfyUI-Impact-Pack: Loading SAM model '{impact_pack.model_path}'")
|
||||
if model_path is None:
|
||||
logging.error(f"[Impact Pack] The '{model_name}' model file cannot be found in any sams model path.")
|
||||
return web.Response(status=400)
|
||||
|
||||
logging.info(f"[Impact Pack] Loading SAM model '{model_path}'")
|
||||
|
||||
filename, image_dir = folder_paths.annotated_filepath(data["filename"])
|
||||
|
||||
@@ -92,10 +97,10 @@ async def sam_prepare(request):
|
||||
if image_dir is None:
|
||||
return web.Response(status=400)
|
||||
|
||||
thread = threading.Thread(target=async_prepare_sam, args=(image_dir, model_name, filename,))
|
||||
thread = threading.Thread(target=async_prepare_sam, args=(image_dir, model_path, filename,))
|
||||
thread.start()
|
||||
|
||||
print(f"[INFO] ComfyUI-Impact-Pack: SAM model loaded. ")
|
||||
logging.info("[Impact Pack] SAM model loaded. ")
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@@ -107,7 +112,7 @@ async def release_sam(request):
|
||||
del sam_predictor
|
||||
sam_predictor = None
|
||||
|
||||
print(f"[INFO] ComfyUI-Impact-Pack: unloading SAM model")
|
||||
logging.info("[Impact Pack]: unloading SAM model")
|
||||
|
||||
|
||||
@PromptServer.instance.routes.post("/sam/detect")
|
||||
@@ -315,6 +320,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:
|
||||
@@ -322,20 +329,24 @@ def onprompt_for_switch(json_data):
|
||||
|
||||
cls = v['class_type']
|
||||
if cls == 'ImpactInversedSwitch':
|
||||
# if 'sel_mode' is 'select_on_prompt'
|
||||
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
|
||||
select_input = v['inputs']['select']
|
||||
# if 'select' is converted input
|
||||
if isinstance(select_input, list) and len(select_input) == 2:
|
||||
input_node = json_data['prompt'][select_input[0]]
|
||||
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
|
||||
inversed_switch_info[k] = input_node['inputs']['value']
|
||||
else:
|
||||
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactInversedSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
logging.warning(f"\n##### ##### #####\n[Impact Pack] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactInversedSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
else:
|
||||
inversed_switch_info[k] = select_input
|
||||
|
||||
elif cls in ['ImpactSwitch', 'LatentSwitch', 'SEGSSwitch', 'ImpactMakeImageList']:
|
||||
# if 'sel_mode' is 'select_on_prompt'
|
||||
if 'sel_mode' in v['inputs'] and v['inputs']['sel_mode'] and 'select' in v['inputs']:
|
||||
select_input = v['inputs']['select']
|
||||
# if 'select' is converted input
|
||||
if isinstance(select_input, list) and len(select_input) == 2:
|
||||
input_node = json_data['prompt'][select_input[0]]
|
||||
if input_node['class_type'] == 'ImpactInt' and 'inputs' in input_node and 'value' in input_node['inputs']:
|
||||
@@ -344,10 +355,14 @@ def onprompt_for_switch(json_data):
|
||||
if isinstance(input_node['inputs']['select'], int):
|
||||
onprompt_switch_info[k] = input_node['inputs']['select']
|
||||
else:
|
||||
print(f"\n##### ##### #####\n[WARN] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
logging.warning(f"\n##### ##### #####\n[Impact Pack] {cls}: For the 'select' operation, only 'select_index' of the 'ImpactSwitch', which is not an input, or 'ImpactInt' and 'Primitive' are allowed as inputs if 'select_on_prompt' is selected.\n##### ##### #####\n")
|
||||
else:
|
||||
onprompt_switch_info[k] = select_input
|
||||
|
||||
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']
|
||||
@@ -371,6 +386,11 @@ def onprompt_for_switch(json_data):
|
||||
if vv[0] in inversed_switch_info:
|
||||
if vv[1] + 1 != inversed_switch_info[vv[0]]:
|
||||
disable_targets.add(kk)
|
||||
else:
|
||||
del inversed_switch_info[k]
|
||||
|
||||
if vv[0] in disabled_switch:
|
||||
disable_targets.add(kk)
|
||||
|
||||
if k in onprompt_switch_info:
|
||||
selected_slot_name = f"input{onprompt_switch_info[k]}"
|
||||
@@ -387,6 +407,11 @@ def onprompt_for_switch(json_data):
|
||||
for kk in disable_targets:
|
||||
del v['inputs'][kk]
|
||||
|
||||
# inversed_switch - select out of range
|
||||
for target in inversed_switch_info.keys():
|
||||
del json_data['prompt'][target]['inputs']['input']
|
||||
|
||||
|
||||
def onprompt_for_pickers(json_data):
|
||||
detected_pickers = set()
|
||||
|
||||
@@ -457,7 +482,17 @@ def onprompt_populate_wildcards(json_data):
|
||||
for k, v in prompt.items():
|
||||
if 'class_type' in v and (v['class_type'] == 'ImpactWildcardEncode' or v['class_type'] == 'ImpactWildcardProcessor'):
|
||||
inputs = v['inputs']
|
||||
if inputs['mode'] and isinstance(inputs['populated_text'], str):
|
||||
|
||||
# legacy adapter
|
||||
if isinstance(inputs['mode'], bool):
|
||||
if inputs['mode']:
|
||||
new_mode = 'populate'
|
||||
else:
|
||||
new_mode = 'fixed'
|
||||
|
||||
inputs['mode'] = new_mode
|
||||
|
||||
if inputs['mode'] == 'populate' and isinstance(inputs['populated_text'], str):
|
||||
if isinstance(inputs['seed'], list):
|
||||
try:
|
||||
input_node = prompt[inputs['seed'][0]]
|
||||
@@ -470,7 +505,7 @@ def onprompt_populate_wildcards(json_data):
|
||||
if not isinstance(input_seed, int):
|
||||
continue
|
||||
else:
|
||||
print(f"[Impact Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
logging.info(f"[Impact Pack] Only `ImpactInt`, `Seed (rgthree)` and `Primitive` Node are allowed as the seed for '{v['class_type']}'. It will be ignored. ")
|
||||
continue
|
||||
except:
|
||||
continue
|
||||
@@ -478,17 +513,22 @@ def onprompt_populate_wildcards(json_data):
|
||||
input_seed = int(inputs['seed'])
|
||||
|
||||
inputs['populated_text'] = wildcards.process(inputs['wildcard_text'], input_seed)
|
||||
inputs['mode'] = False
|
||||
inputs['mode'] = 'reproduce'
|
||||
|
||||
PromptServer.instance.send_sync("impact-node-feedback", {"node_id": k, "widget_name": "populated_text", "type": "STRING", "value": inputs['populated_text']})
|
||||
updated_widget_values[k] = inputs['populated_text']
|
||||
|
||||
if inputs['mode'] == 'reproduce':
|
||||
PromptServer.instance.send_sync("impact-node-feedback", {"node_id": k, "widget_name": "mode", "type": "STRING", "value": 'populate'})
|
||||
|
||||
|
||||
|
||||
if 'extra_data' in json_data and 'extra_pnginfo' in json_data['extra_data']:
|
||||
for node in json_data['extra_data']['extra_pnginfo']['workflow']['nodes']:
|
||||
key = str(node['id'])
|
||||
if key in updated_widget_values:
|
||||
node['widgets_values'][1] = updated_widget_values[key]
|
||||
node['widgets_values'][2] = False
|
||||
node['widgets_values'][2] = 'reproduce'
|
||||
|
||||
|
||||
def onprompt_for_remote(json_data):
|
||||
@@ -533,7 +573,7 @@ def onprompt(json_data):
|
||||
regional_sampler_seed_update(json_data)
|
||||
core.current_prompt = json_data
|
||||
except Exception as e:
|
||||
print(f"[WARN] ComfyUI-Impact-Pack: Error on prompt - several features will not work.\n{e}")
|
||||
logging.warning(f"[Impact Pack] ComfyUI-Impact-Pack: Error on prompt - several features will not work.\n{e}")
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
@@ -701,6 +701,9 @@ class ImpactControlBridge:
|
||||
return (value, )
|
||||
else:
|
||||
return (ExecutionBlocker(None), )
|
||||
elif extra_pnginfo is None:
|
||||
logging.warn(f"[Impact Pack] limitation: '{behavior}' behavior cannot be used in API execution.")
|
||||
return (value,)
|
||||
else:
|
||||
workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow'])
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import comfy.sample
|
||||
import traceback
|
||||
|
||||
original_sample = comfy.sample.sample
|
||||
|
||||
|
||||
def informative_sample(*args, **kwargs):
|
||||
try:
|
||||
return original_sample(*args, **kwargs) # This code helps interpret error messages that occur within exceptions but does not have any impact on other operations.
|
||||
except RuntimeError as e:
|
||||
is_model_mix_issue = False
|
||||
try:
|
||||
if 'mat1 and mat2 shapes cannot be multiplied' in e.args[0]:
|
||||
if 'torch.nn.functional.linear' in traceback.format_exc().strip().split('\n')[-3]:
|
||||
is_model_mix_issue = True
|
||||
except:
|
||||
pass
|
||||
|
||||
if is_model_mix_issue:
|
||||
raise RuntimeError("\n\n#### It seems that models and clips are mixed and interconnected between SDXL Base, SDXL Refiner, SD1.x, and SD2.x. Please verify. ####\n\n")
|
||||
else:
|
||||
raise e
|
||||
|
||||
|
||||
comfy.sample.sample = informative_sample
|
||||
@@ -13,6 +13,7 @@ from . import segs_upscaler
|
||||
from comfy.cli_args import args
|
||||
import math
|
||||
|
||||
from typing import Callable, Union
|
||||
|
||||
try:
|
||||
from comfy_extras import nodes_differential_diffusion
|
||||
@@ -38,7 +39,7 @@ class SEGSDetailer:
|
||||
"denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}),
|
||||
"noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}),
|
||||
"basic_pipe": ("BASIC_PIPE",),
|
||||
"basic_pipe": ("BASIC_PIPE", {"tooltip": "If the `ImpactDummyInput` is connected to the model in the basic_pipe, the inference stage is skipped."}),
|
||||
"refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 100}),
|
||||
|
||||
@@ -60,6 +61,8 @@ class SEGSDetailer:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size.\nThis node is applied specifically to SEGS rather than the entire image. To apply it to the entire image, use the 'SEGS Paste' node."
|
||||
|
||||
@staticmethod
|
||||
def do_detail(image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler,
|
||||
denoise, noise_mask, force_inpaint, basic_pipe, refiner_ratio=None, batch_size=1, cycle=1,
|
||||
@@ -76,7 +79,7 @@ class SEGSDetailer:
|
||||
new_segs = []
|
||||
cnet_pil_list = []
|
||||
|
||||
if noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
if not (isinstance(model, str) and model == "DUMMY") and noise_mask_feather > 0 and 'denoise_mask_function' not in model.model_options:
|
||||
model = nodes_differential_diffusion.DifferentialDiffusion().apply(model)[0]
|
||||
|
||||
for i in range(batch_size):
|
||||
@@ -113,13 +116,17 @@ class SEGSDetailer:
|
||||
for condition, details in negative
|
||||
]
|
||||
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
control_net_wrapper=seg.control_net_wrapper, cycle=cycle,
|
||||
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
if not (isinstance(model, str) and model == "DUMMY"):
|
||||
enhanced_image, cnet_pils = core.enhance_detail(cropped_image, model, clip, vae, guide_size, guide_size_for, max_size,
|
||||
seg.bbox, seed, steps, cfg, sampler_name, scheduler,
|
||||
cropped_positive, cropped_negative, denoise, cropped_mask, force_inpaint,
|
||||
refiner_ratio=refiner_ratio, refiner_model=refiner_model,
|
||||
refiner_clip=refiner_clip, refiner_positive=refiner_positive, refiner_negative=refiner_negative,
|
||||
control_net_wrapper=seg.control_net_wrapper, cycle=cycle,
|
||||
inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather, scheduler_func=scheduler_func_opt)
|
||||
else:
|
||||
enhanced_image = cropped_image
|
||||
cnet_pils = None
|
||||
|
||||
if cnet_pils is not None:
|
||||
cnet_pil_list.extend(cnet_pils)
|
||||
@@ -170,6 +177,8 @@ class SEGSPaste:
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "This node provides a function to paste the enhanced SEGS, improved through the SEGS detailer, back onto the original image."
|
||||
|
||||
@staticmethod
|
||||
def doit(image, segs, feather, alpha=255, ref_image_opt=None):
|
||||
|
||||
@@ -496,7 +505,7 @@ class SEGSOrderedFilter:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs": ("SEGS", ),
|
||||
"target": (["area(=w*h)", "width", "height", "x1", "y1", "x2", "y2", "confidence"],),
|
||||
"target": (["area(=w*h)", "width", "height", "x1", "y1", "x2", "y2", "confidence", "none"],),
|
||||
"order": ("BOOLEAN", {"default": True, "label_on": "descending", "label_off": "ascending"}),
|
||||
"take_start": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
"take_count": ("INT", {"default": 1, "min": 0, "max": sys.maxsize, "step": 1}),
|
||||
@@ -509,51 +518,35 @@ class SEGSOrderedFilter:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
@staticmethod
|
||||
def get_sort_key_fn(target: str) -> Union[Callable, None]:
|
||||
if target == "none":
|
||||
return None
|
||||
|
||||
def sort_key_fn(seg):
|
||||
x1, y1, x2, y2 = seg.crop_region
|
||||
if target == "confidence": return seg.confidence
|
||||
if target == "area(=w*h)": return (x2 - x1) * (y2 - y1)
|
||||
if target == "width": return x2 - x1
|
||||
if target == "height": return y2 - y1
|
||||
if target == "x1": return x1
|
||||
if target == "y1": return y1
|
||||
if target == "x2": return x2
|
||||
if target == "y2": return y2
|
||||
raise Exception(f"[Impact Pack] SEGSOrderedFilter - Unexpected target '{target}'")
|
||||
|
||||
return sort_key_fn
|
||||
|
||||
def doit(self, segs, target, order, take_start, take_count):
|
||||
segs_with_order = []
|
||||
sort_key_fn = SEGSOrderedFilter.get_sort_key_fn(target)
|
||||
|
||||
for seg in segs[1]:
|
||||
x1 = seg.crop_region[0]
|
||||
y1 = seg.crop_region[1]
|
||||
x2 = seg.crop_region[2]
|
||||
y2 = seg.crop_region[3]
|
||||
sorted_list = list(segs[1]) # make a shallow copy, so it does not mutate the original list when sort
|
||||
if sort_key_fn is not None:
|
||||
sorted_list.sort(key=sort_key_fn, reverse=order)
|
||||
|
||||
if target == "area(=w*h)":
|
||||
value = (y2 - y1) * (x2 - x1)
|
||||
elif target == "width":
|
||||
value = x2 - x1
|
||||
elif target == "height":
|
||||
value = y2 - y1
|
||||
elif target == "x1":
|
||||
value = x1
|
||||
elif target == "x2":
|
||||
value = x2
|
||||
elif target == "y1":
|
||||
value = y1
|
||||
elif target == "y2":
|
||||
value = y2
|
||||
elif target == "confidence":
|
||||
value = seg.confidence
|
||||
else:
|
||||
raise Exception(f"[Impact Pack] SEGSOrderedFilter - Unexpected target '{target}'")
|
||||
|
||||
segs_with_order.append((value, seg))
|
||||
|
||||
if order:
|
||||
sorted_list = sorted(segs_with_order, key=lambda x: x[0], reverse=True)
|
||||
else:
|
||||
sorted_list = sorted(segs_with_order, key=lambda x: x[0], reverse=False)
|
||||
|
||||
result_list = []
|
||||
remained_list = []
|
||||
|
||||
for i, item in enumerate(sorted_list):
|
||||
if take_start <= i < take_start + take_count:
|
||||
result_list.append(item[1])
|
||||
else:
|
||||
remained_list.append(item[1])
|
||||
|
||||
return (segs[0], result_list), (segs[0], remained_list),
|
||||
take_stop = take_start + take_count
|
||||
return (segs[0], sorted_list[take_start:take_stop]), \
|
||||
(segs[0], sorted_list[:take_start] + sorted_list[take_stop:]),
|
||||
|
||||
|
||||
class SEGSRangeFilter:
|
||||
@@ -621,6 +614,111 @@ class SEGSRangeFilter:
|
||||
return (segs[0], new_segs), (segs[0], remained_segs),
|
||||
|
||||
|
||||
class SEGSIntersectionFilter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"segs1": ("SEGS", ),
|
||||
"segs2": ("SEGS", ),
|
||||
"ioa_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
RETURN_NAMES = ("filtered_SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def compute_ioa(self, mask1, mask2):
|
||||
"""Compute Intersection over Area (IoA) between two boxes."""
|
||||
inter_mask = utils.bitwise_and_masks(mask1, mask2)
|
||||
|
||||
inter_area = (inter_mask > 0).sum()
|
||||
area1 = (mask1 > 0).sum()
|
||||
|
||||
return inter_area / area1 if area1 > 0 else 0
|
||||
|
||||
def doit(self, segs1, segs2, ioa_threshold):
|
||||
"""Remove segments from segs1 if their IoA with any segment in segs2 exceeds the threshold."""
|
||||
# Extract bounding boxes for all segments in segs1 and segs2
|
||||
keep = []
|
||||
|
||||
# Iterate over all segments in segs1
|
||||
for idx1, seg1 in enumerate(segs1[1]):
|
||||
keep_segment = True # Assume the segment should be kept
|
||||
mask1 = core.segs_to_combined_mask((segs1[0], [seg1]))
|
||||
|
||||
# Compare with every segment in segs2
|
||||
for seg2 in segs2[1]:
|
||||
mask2 = core.segs_to_combined_mask((segs2[0], [seg2]))
|
||||
ioa = self.compute_ioa(mask1, mask2) # IoA between segment 1 and segment 2
|
||||
|
||||
if ioa > ioa_threshold: # If IoA exceeds the threshold, mark the segment for removal
|
||||
keep_segment = False
|
||||
break # If one overlap exceeds threshold, break early and mark for removal
|
||||
|
||||
# Keep the segment if it did not exceed the threshold with any other segment
|
||||
if keep_segment:
|
||||
keep.append(segs1[1][idx1])
|
||||
|
||||
return (segs1[0], keep), # Return the updated SEGS
|
||||
|
||||
|
||||
class SEGSNMSFilter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"segs": ("SEGS",),
|
||||
"iou_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SEGS",)
|
||||
RETURN_NAMES = ("filtered_SEGS",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
def compute_iou(self, mask1, mask2):
|
||||
"""Compute IoU between two bounding boxes (x1, y1, x2, y2)."""
|
||||
inter_mask = utils.bitwise_and_masks(mask1, mask2)
|
||||
union_mask = utils.add_masks(mask1, mask2)
|
||||
|
||||
inter_area = (inter_mask > 0).sum()
|
||||
union_area = (union_mask > 0).sum()
|
||||
|
||||
return inter_area / union_area if union_area > 0 else 0
|
||||
|
||||
def doit(self, segs, iou_threshold):
|
||||
"""Perform NMS to filter overlapping segments."""
|
||||
confidences = np.ndarray.flatten(np.array([seg.confidence for seg in segs[1]]))
|
||||
|
||||
# Sort boxes by confidence (high to low)
|
||||
sorted_indices = np.argsort(confidences)[::-1].tolist()
|
||||
keep = []
|
||||
|
||||
while len(sorted_indices) > 0:
|
||||
idx = sorted_indices[0]
|
||||
mask1 = core.segs_to_combined_mask((segs[0], [segs[1][idx]]))
|
||||
keep.append(idx)
|
||||
sorted_indices = sorted_indices[1:]
|
||||
|
||||
# Filter indices only contain the indices where the bbox does not intersect
|
||||
filtered_indices = []
|
||||
for i in sorted_indices:
|
||||
mask2 = core.segs_to_combined_mask((segs[0], [segs[1][i]]))
|
||||
iou = self.compute_iou(mask1, mask2)
|
||||
if iou < iou_threshold:
|
||||
filtered_indices.append(i)
|
||||
|
||||
sorted_indices = np.array(filtered_indices)
|
||||
|
||||
filtered_segs = [segs[1][i] for i in keep]
|
||||
return (segs[0], filtered_segs),
|
||||
|
||||
|
||||
class SEGSToImageList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -1482,6 +1580,8 @@ class SEGSPicker:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "This node provides a function to select only the chosen SEGS from the input SEGS."
|
||||
|
||||
@staticmethod
|
||||
def doit(picks, segs, fallback_image_opt=None, unique_id=None):
|
||||
if fallback_image_opt is not None:
|
||||
@@ -1538,6 +1638,8 @@ class DefaultImageForSEGS:
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "If the SEGS have not passed through the detailer, they contain only detection area information without an image. This node sets a default image for the SEGS."
|
||||
|
||||
@staticmethod
|
||||
def doit(segs, image, override):
|
||||
results = []
|
||||
|
||||
@@ -526,6 +526,9 @@ class ReencodeLatent:
|
||||
"output_vae": ("VAE", ),
|
||||
"tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64}),
|
||||
},
|
||||
"optional": {
|
||||
"overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32, "tooltip": "This setting applies when 'tile_mode' is enabled."}),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
@@ -533,14 +536,22 @@ class ReencodeLatent:
|
||||
RETURN_TYPES = ("LATENT", )
|
||||
FUNCTION = "doit"
|
||||
|
||||
def doit(self, samples, tile_mode, input_vae, output_vae, tile_size=512):
|
||||
def doit(self, samples, tile_mode, input_vae, output_vae, tile_size=512, overlap=64):
|
||||
if tile_mode in ["Both", "Decode(input) only"]:
|
||||
pixels = nodes.VAEDecodeTiled().decode(input_vae, samples, tile_size)[0]
|
||||
decoder = nodes.VAEDecodeTiled()
|
||||
if 'overlap' in inspect.signature(decoder.decode).parameters:
|
||||
pixels = decoder.decode(input_vae, samples, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
pixels = decoder.decode(input_vae, samples, tile_size, overlap=overlap)[0]
|
||||
else:
|
||||
pixels = nodes.VAEDecode().decode(input_vae, samples)[0]
|
||||
|
||||
if tile_mode in ["Both", "Encode(output) only"]:
|
||||
return nodes.VAEEncodeTiled().encode(output_vae, pixels, tile_size)
|
||||
encoder = nodes.VAEEncodeTiled()
|
||||
if 'overlap' in inspect.signature(encoder.encode).parameters:
|
||||
return encoder.encode(output_vae, pixels, tile_size, overlap=overlap)
|
||||
else:
|
||||
return encoder.encode(output_vae, pixels, tile_size)
|
||||
else:
|
||||
return nodes.VAEEncode().encode(output_vae, pixels)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import nodes
|
||||
from . import config
|
||||
from PIL import Image
|
||||
import comfy
|
||||
import time
|
||||
|
||||
|
||||
class TensorBatchBuilder:
|
||||
@@ -501,15 +502,21 @@ def crop_image(image, crop_region):
|
||||
return crop_tensor4(image, crop_region)
|
||||
|
||||
|
||||
def to_latent_image(pixels, vae):
|
||||
def to_latent_image(pixels, vae, vae_tiled_encode=False):
|
||||
x = pixels.shape[1]
|
||||
y = pixels.shape[2]
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
pixels = pixels[:, :x, :y, :]
|
||||
|
||||
vae_encode = nodes.VAEEncode()
|
||||
start = time.time()
|
||||
if vae_tiled_encode:
|
||||
encoded = nodes.VAEEncodeTiled().encode(vae, pixels, 512, overlap=64)[0] # using default settings
|
||||
print(f"[Impact Pack] vae encoded (tiled) in {time.time() - start:.1f}s")
|
||||
else:
|
||||
encoded = nodes.VAEEncode().encode(vae, pixels)[0]
|
||||
print(f"[Impact Pack] vae encoded in {time.time() - start:.1f}s")
|
||||
|
||||
return vae_encode.encode(vae, pixels)[0]
|
||||
return encoded
|
||||
|
||||
|
||||
def empty_pil_tensor(w=64, h=64):
|
||||
|
||||
@@ -44,7 +44,9 @@ def read_wildcard(k, v):
|
||||
elif isinstance(v, str):
|
||||
k = wildcard_normalize(k)
|
||||
wildcard_dict[k] = [v]
|
||||
|
||||
elif isinstance(v, (int, float)):
|
||||
k = wildcard_normalize(k)
|
||||
wildcard_dict[k] = [str(v)]
|
||||
|
||||
def read_wildcard_dict(wildcard_path):
|
||||
global wildcard_dict
|
||||
@@ -135,6 +137,8 @@ def process(text, seed=None):
|
||||
b = r.group(3)
|
||||
if b is not None:
|
||||
b = b.strip()
|
||||
else:
|
||||
b = "-1"
|
||||
|
||||
if r is not None:
|
||||
if b is not None and is_numeric_string(a) and is_numeric_string(b):
|
||||
@@ -145,26 +149,32 @@ def process(text, seed=None):
|
||||
x = int(a)
|
||||
select_range = (x, x)
|
||||
|
||||
# Expand wildcard path or return the string after $$
|
||||
def expand_wildcard_or_return_string(options, pattern, wildcard_pattern):
|
||||
matches = re.findall(wildcard_pattern, pattern)
|
||||
if len(options) == 1 and matches:
|
||||
# $$<single wildcard>
|
||||
return get_wildcard_options(pattern)
|
||||
else:
|
||||
# $$opt1|opt2|...
|
||||
options[0] = pattern
|
||||
return options
|
||||
|
||||
if select_range is not None and len(multi_select_pattern) == 2:
|
||||
# PATTERN: count$$
|
||||
matches = re.findall(wildcard_pattern, multi_select_pattern[1])
|
||||
if len(options) == 1 and matches:
|
||||
# count$$<single wildcard>
|
||||
options = get_wildcard_options(multi_select_pattern[1])
|
||||
else:
|
||||
# count$$opt1|opt2|...
|
||||
options[0] = multi_select_pattern[1]
|
||||
options = expand_wildcard_or_return_string(options, multi_select_pattern[1], wildcard_pattern )
|
||||
elif select_range is not None and len(multi_select_pattern) == 3:
|
||||
# PATTERN: count$$ sep $$
|
||||
select_sep = multi_select_pattern[1]
|
||||
options[0] = multi_select_pattern[2]
|
||||
options = expand_wildcard_or_return_string(options, multi_select_pattern[2], wildcard_pattern )
|
||||
|
||||
adjusted_probabilities = []
|
||||
|
||||
total_prob = 0
|
||||
|
||||
for option in options:
|
||||
parts = option.split('::', 1)
|
||||
parts = option.split('::', 1) if isinstance(option, str) else f"{option}".split('::', 1)
|
||||
|
||||
if len(parts) == 2 and is_numeric_string(parts[0].strip()):
|
||||
config_value = float(parts[0].strip())
|
||||
else:
|
||||
@@ -178,15 +188,30 @@ def process(text, seed=None):
|
||||
if select_range is None:
|
||||
select_count = 1
|
||||
else:
|
||||
select_count = random_gen.integers(low=select_range[0], high=select_range[1]+1, size=1)
|
||||
def calculate_max(_options_length, _max_select_range):
|
||||
return min(_max_select_range + 1, _options_length + 1) if _max_select_range > 0 else _options_length + 1
|
||||
|
||||
if select_count > len(options):
|
||||
def calculate_select_count(_max_value, _min_select_range, random_gen):
|
||||
if max(_max_value, _min_select_range) <= 0:
|
||||
return 0
|
||||
# fix: low >= high
|
||||
elif _max_value == _min_select_range:
|
||||
return _max_value
|
||||
else:
|
||||
# fix: low >= high
|
||||
_low_value = min(_min_select_range, _max_value)
|
||||
_high_value = max(_min_select_range, _max_value)
|
||||
return random_gen.integers(low=_low_value, high=_high_value, size=1)
|
||||
select_count = calculate_select_count(calculate_max(len(options), select_range[1]), select_range[0], random_gen)
|
||||
|
||||
if select_count > len(options) or total_prob <= 1:
|
||||
random_gen.shuffle(options)
|
||||
selected_items = options
|
||||
else:
|
||||
selected_items = random_gen.choice(options, p=normalized_probabilities, size=select_count, replace=False)
|
||||
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', x, 1) for x in selected_items]
|
||||
# x may be numpy.int32, convert to string
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', str(x), 1) for x in selected_items]
|
||||
replacement = select_sep.join(selected_items2)
|
||||
if '::' in replacement:
|
||||
pass
|
||||
@@ -237,7 +262,23 @@ def process(text, seed=None):
|
||||
keyword = match.lower()
|
||||
keyword = wildcard_normalize(keyword)
|
||||
if keyword in local_wildcard_dict:
|
||||
replacement = random_gen.choice(local_wildcard_dict[keyword])
|
||||
# look for adjusted probability
|
||||
adjusted_probabilities = []
|
||||
total_prob = 0
|
||||
options=local_wildcard_dict[keyword]
|
||||
for option in options:
|
||||
parts = option.split('::', 1)
|
||||
if len(parts) == 2 and is_numeric_string(parts[0].strip()):
|
||||
config_value = float(parts[0].strip())
|
||||
else:
|
||||
config_value = 1 # Default value if no configuration is provided
|
||||
|
||||
adjusted_probabilities.append(config_value)
|
||||
total_prob += config_value
|
||||
|
||||
normalized_probabilities = [prob / total_prob for prob in adjusted_probabilities]
|
||||
selected_item = random_gen.choice(options, p=normalized_probabilities, replace=False)
|
||||
replacement = re.sub(r'^\s*[0-9.]+::', '', selected_item, 1)
|
||||
replacements_found = True
|
||||
string = string.replace(f"__{match}__", replacement, 1)
|
||||
elif '*' in keyword:
|
||||
|
||||
@@ -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.14.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.11"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ scikit-image
|
||||
piexif
|
||||
transformers
|
||||
opencv-python-headless
|
||||
GitPython
|
||||
scipy>=1.11.4
|
||||
numpy<2
|
||||
dill
|
||||
|
||||