Compare commits

...
17 Commits
Author SHA1 Message Date
Dr.Lt.Data 2708eba825 bump version v8.9 2025-03-20 21:36:25 +09:00
chutchatut c19ab92172 add intersection and non max suppression SEGS filter (#940) 2025-03-20 21:21:15 +09:00
Robin Huangandsnomiao 6e3d07277b chore(publish): update workflow for node publishing with conditional execution and permissions (#935)
Co-authored-by: snomiao <snomiao+comfy-pr@gmail.com>
2025-03-17 00:19:33 +09:00
Dr.Lt.Data 782c6f439e fixed: controlbridge - better error message
- mute/bypass behavior cannot be used in api mode

https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/933
2025-03-12 18:10:42 +09:00
Dr.Lt.Data 0e3e6a193a improved: SAM Loader - don't add 'ESAM' if 'ComfyUI-YoloWorld-EfficientSAM' is not installed 2025-03-04 22:44:31 +09:00
Dr.Lt.Data 798776838e remove sample_error_enhancer 2025-03-04 12:43:03 +09:00
Dr.Lt.Data 66493d8cb4 formatting.. 2025-03-02 16:56:59 +09:00
Dr.Lt.Data b4fd0834e0 fixed: crash when loading api json
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/919
2025-03-02 16:54:16 +09:00
Dr.Lt.Data 808b0dedf0 update README.md 2025-02-23 10:03:04 +09:00
Dr.Lt.Data c6056b132d add example workflow 2025-02-15 11:04:03 +09:00
Dr.Lt.Data 1ae7cae2df version marker 2025-02-02 15:05:41 +09:00
izmp ccb6285548 Fixed several issues related to number handling and wildcard processing (#896) 2025-02-02 15:04:57 +09:00
Dr.Lt.Data 092310bc8f refactor: impact_sampling 2025-01-31 21:01:03 +09:00
Dr.Lt.Data 5c530eb32e fixed: wildcards - cannot edit populated_text on 'fixed' mode
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/898
2025-01-30 16:55:01 +09:00
Dr.Lt.Data e35bc23fd1 add locales partly.
added: more descriptions
added: locales/ko
2025-01-30 16:49:23 +09:00
Dr.Lt.Data 869ac6fd1f version marker 2025-01-28 12:21:26 +09:00
Dijkstra cb168d64ab feat: allow wildcard file to use the adjusted probabilities feature (#891) 2025-01-28 12:16:10 +09:00
30 changed files with 10092 additions and 145 deletions
+6 -2
View File
@@ -7,15 +7,19 @@ on:
paths: paths:
- "pyproject.toml" - "pyproject.toml"
permissions:
issues: write
jobs: jobs:
publish-node: publish-node:
name: Publish Custom Node to registry name: Publish Custom Node to registry
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'ltdrdata' }}
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Publish Custom Node - name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main uses: Comfy-Org/publish-node-action@v1
with: with:
## Add your own personal access token to your Github Repository secrets and reference it here. ## 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 }}
+25
View File
@@ -32,6 +32,31 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
* With the addition of wildcard support in FaceDetailer, the structure of DETAILER_PIPE-related nodes and Detailer nodes has changed. There may be malfunctions when using the existing workflow. * 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 ## Custom Nodes
### [Detector nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detectors.md) ### [Detector nodes](https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/detectors.md)
* `SAMLoader` - Loads the SAM model. * `SAMLoader` - Loads the SAM model.
+4 -1
View File
@@ -18,7 +18,6 @@ modules_path = os.path.join(os.path.dirname(__file__), "modules")
sys.path.append(modules_path) sys.path.append(modules_path)
import impact.config import impact.config
import impact.sample_error_enhancer
print(f"### Loading: ComfyUI-Impact-Pack ({impact.config.version})") print(f"### Loading: ComfyUI-Impact-Pack ({impact.config.version})")
# Core # Core
@@ -248,6 +247,8 @@ NODE_CLASS_MAPPINGS = {
"ImpactSEGSLabelFilter": SEGSLabelFilter, "ImpactSEGSLabelFilter": SEGSLabelFilter,
"ImpactSEGSRangeFilter": SEGSRangeFilter, "ImpactSEGSRangeFilter": SEGSRangeFilter,
"ImpactSEGSOrderedFilter": SEGSOrderedFilter, "ImpactSEGSOrderedFilter": SEGSOrderedFilter,
"ImpactSEGSIntersectionFilter": SEGSIntersectionFilter,
"ImpactSEGSNMSFilter": SEGSNMSFilter,
"ImpactCompare": ImpactCompare, "ImpactCompare": ImpactCompare,
"ImpactConditionalBranch": ImpactConditionalBranch, "ImpactConditionalBranch": ImpactConditionalBranch,
@@ -363,6 +364,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ImpactSEGSLabelFilter": "SEGS Filter (label)", "ImpactSEGSLabelFilter": "SEGS Filter (label)",
"ImpactSEGSRangeFilter": "SEGS Filter (range)", "ImpactSEGSRangeFilter": "SEGS Filter (range)",
"ImpactSEGSOrderedFilter": "SEGS Filter (ordered)", "ImpactSEGSOrderedFilter": "SEGS Filter (ordered)",
"ImpactSEGSIntersectionFilter": "SEGS Filter (intersection)",
"ImpactSEGSNMSFilter": "SEGS Filter (non max suppression)",
"ImpactSEGSConcat": "SEGS Concat", "ImpactSEGSConcat": "SEGS Concat",
"ImpactSEGSToMaskList": "SEGS to Mask List", "ImpactSEGSToMaskList": "SEGS to Mask List",
"ImpactSEGSToMaskBatch": "SEGS to Mask Batch", "ImpactSEGSToMaskBatch": "SEGS to Mask Batch",
Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

+596
View File
@@ -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
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

File diff suppressed because it is too large Load Diff
+49 -43
View File
@@ -223,29 +223,29 @@ api.addEventListener("executed", progressExecuteHandler);
app.registerExtension({ app.registerExtension({
name: "Comfy.Impack", name: "Comfy.Impack",
commands: [ commands: [
{ {
id: 'refresh-impact-wildcard', id: 'refresh-impact-wildcard',
label: 'Impact: Refresh Wildcard', label: 'Impact: Refresh Wildcard',
function: async () => { function: async () => {
await api.fetchApi('/impact/wildcards/refresh'); await api.fetchApi('/impact/wildcards/refresh');
await load_wildcards(); await load_wildcards();
app.extensionManager.toast.add({ app.extensionManager.toast.add({
severity: 'info', severity: 'info',
summary: 'Refreshed!', summary: 'Refreshed!',
detail: 'Impact Wildcard List is refreshed!!', detail: 'Impact Wildcard List is refreshed!!',
life: 3000 life: 3000
}); });
} }
} }
], ],
menuCommands: [ menuCommands: [
{ {
path: ['Edit'], path: ['Edit'],
commands: ['refresh-impact-wildcard'] commands: ['refresh-impact-wildcard']
} }
], ],
loadedGraphNode(node, app) { loadedGraphNode(node, app) {
if (node.comfyClass == "MaskPainter") { if (node.comfyClass == "MaskPainter") {
@@ -617,12 +617,14 @@ app.registerExtension({
if(node.comfyClass == "ImpactSEGSLabelFilter" || node.comfyClass == "SEGSLabelFilterDetailerHookProvider") { if(node.comfyClass == "ImpactSEGSLabelFilter" || node.comfyClass == "SEGSLabelFilterDetailerHookProvider") {
node.widgets[0].callback = (value, canvas, node, pos, e) => { node.widgets[0].callback = (value, canvas, node, pos, e) => {
if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(",")) if(node) {
node.widgets[1].value += ", " if(node.widgets[1].value.trim() != "" && !node.widgets[1].value.trim().endsWith(","))
node.widgets[1].value += ", "
node.widgets[1].value += value; node.widgets[1].value += value;
if(node.widgets_values) if(node.widgets_values)
node.widgets_values[1] = node.widgets[1].value; node.widgets_values[1] = node.widgets[1].value;
}
} }
Object.defineProperty(node.widgets[0], "value", { Object.defineProperty(node.widgets[0], "value", {
@@ -696,18 +698,20 @@ app.registerExtension({
break; break;
} }
node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => { node.widgets[combo_id+1].callback = (value, canvas, node, pos, e) => {
if(node.widgets[tbox_id].value != '') if(node) {
node.widgets[tbox_id].value += ', ' 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", { Object.defineProperty(node.widgets[combo_id+1], "value", {
set: (value) => { set: (value) => {
if (value !== "Select the Wildcard to add to the text") if (value !== "Select the Wildcard to add to the text")
node._wildcard_value = value; node._wildcard_value = value;
}, },
get: () => { return "Select the Wildcard to add to the text"; } get: () => { return "Select the Wildcard to add to the text"; }
}); });
@@ -720,14 +724,16 @@ app.registerExtension({
if(has_lora) { if(has_lora) {
node.widgets[combo_id].callback = (value, canvas, node, pos, e) => { node.widgets[combo_id].callback = (value, canvas, node, pos, e) => {
let lora_name = node._value; if(node) {
if(lora_name.endsWith('.safetensors')) { let lora_name = node._value;
lora_name = lora_name.slice(0, -12); if(lora_name.endsWith('.safetensors')) {
} lora_name = lora_name.slice(0, -12);
}
node.widgets[tbox_id].value += `<lora:${lora_name}>`; node.widgets[tbox_id].value += `<lora:${lora_name}>`;
if(node.widgets_values) { if(node.widgets_values) {
node.widgets_values[tbox_id] = node.widgets[tbox_id].value; node.widgets_values[tbox_id] = node.widgets[tbox_id].value;
}
} }
} }
@@ -766,7 +772,7 @@ app.registerExtension({
else else
node._mode_value = value; // combo value node._mode_value = value; // combo value
populated_text_widget.inputEl.disabled = node._mode_value != 'populate'; populated_text_widget.inputEl.disabled = node._mode_value == 'populate';
}, },
get: () => { get: () => {
if(node._mode_value != undefined) if(node._mode_value != undefined)
File diff suppressed because it is too large Load Diff
+4
View File
@@ -45,6 +45,8 @@ class SEGSDetailerForAnimateDiff:
CATEGORY = "ImpactPack/Detailer" 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 @staticmethod
def do_detail(image_frames, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, 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): denoise, basic_pipe, refiner_ratio=None, refiner_basic_pipe_opt=None, noise_mask_feather=0, scheduler_func_opt=None):
@@ -166,6 +168,8 @@ class DetailerForEachPipeForAnimateDiff:
CATEGORY = "ImpactPack/Detailer" 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 @staticmethod
def doit(image_frames, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, 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, denoise, feather, basic_pipe, refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None,
+1 -1
View File
@@ -1,7 +1,7 @@
import configparser import configparser
import os import os
version_code = [8, 5, 2] version_code = [8, 9]
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
dependency_version = 24 dependency_version = 24
+19 -2
View File
@@ -96,9 +96,13 @@ class SAMLoader:
@classmethod @classmethod
def INPUT_TYPES(cls): def INPUT_TYPES(cls):
models = [x for x in folder_paths.get_filename_list("sams") if 'hq' not in x] models = [x for x in folder_paths.get_filename_list("sams") if 'hq' not in x]
if 'ESAM_ModelLoader_Zho' in nodes.NODE_CLASS_MAPPINGS:
models.append('ESAM')
return { return {
"required": { "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" "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" "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."}), "CPU: Always loads only on the CPU."}),
@@ -228,6 +232,8 @@ class DetailerForEach:
CATEGORY = "ImpactPack/Detailer" 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 @staticmethod
def get_core_module(): def get_core_module():
return core return core
@@ -442,6 +448,8 @@ class DetailerForEachPipe:
CATEGORY = "ImpactPack/Detailer" CATEGORY = "ImpactPack/Detailer"
DESCRIPTION = DetailerForEach.DESCRIPTION
def doit(self, image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, 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, denoise, feather, noise_mask, force_inpaint, basic_pipe, wildcard,
refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None, refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None,
@@ -533,6 +541,8 @@ class FaceDetailer:
CATEGORY = "ImpactPack/Simple" 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 @staticmethod
def enhance_face(image, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seed, steps, cfg, sampler_name, scheduler, 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, positive, negative, denoise, feather, noise_mask, force_inpaint,
@@ -1407,6 +1417,8 @@ class FaceDetailerPipe:
CATEGORY = "ImpactPack/Simple" 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, 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, denoise, feather, noise_mask, force_inpaint, bbox_threshold, bbox_dilation, bbox_crop_factor,
sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion,
@@ -1502,6 +1514,8 @@ class MaskDetailerPipe:
CATEGORY = "ImpactPack/Detailer" CATEGORY = "ImpactPack/Detailer"
DESCRIPTION = ""
def doit(self, image, mask, basic_pipe, guide_size, guide_size_for, max_size, mask_mode, def doit(self, image, mask, basic_pipe, guide_size, guide_size_for, max_size, mask_mode,
seed, steps, cfg, sampler_name, scheduler, denoise, seed, steps, cfg, sampler_name, scheduler, denoise,
feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1, feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1,
@@ -1607,6 +1621,8 @@ class DetailerForEachTestPipe(DetailerForEachPipe):
CATEGORY = "ImpactPack/Detailer" CATEGORY = "ImpactPack/Detailer"
DESCRIPTION = DetailerForEach.DESCRIPTION
def doit(self, image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, 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, 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, refiner_ratio=None, detailer_hook=None, refiner_basic_pipe_opt=None, inpaint_model=False, noise_mask_feather=0,
@@ -2343,7 +2359,7 @@ class ImpactWildcardProcessor:
return {"required": { return {"required": {
"wildcard_text": ("STRING", {"multiline": True, "dynamicPrompts": False, "tooltip": "Enter a prompt using wildcard syntax."}), "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'."}), "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": (["populate", "fixed", "reproduce"], {"default": "populate", "tooltip": "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" "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" "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." "reproduce: This mode operates as 'fixed' mode only once for reproduction, and then it switches to 'populate' mode."
@@ -2359,6 +2375,7 @@ class ImpactWildcardProcessor:
"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_TYPES = ("STRING", )
RETURN_NAMES = ("processed text",)
FUNCTION = "doit" FUNCTION = "doit"
@staticmethod @staticmethod
+18 -56
View File
@@ -46,65 +46,27 @@ def get_noise_sampler(x, cpu, total_sigmas, **kwargs):
def ksampler(sampler_name, total_sigmas, extra_options={}, inpaint_options={}): def ksampler(sampler_name, total_sigmas, extra_options={}, inpaint_options={}):
if sampler_name == "dpmpp_sde": if sampler_name in ["dpmpp_sde", "dpmpp_sde_gpu", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu"]:
def sample_dpmpp_sde(model, x, sigmas, **kwargs): if sampler_name == "dpmpp_sde":
noise_sampler = get_noise_sampler(x, True, total_sigmas, **kwargs) orig_sampler_function = k_diffusion_sampling.sample_dpmpp_sde
if noise_sampler is not None: elif sampler_name == "dpmpp_sde_gpu":
kwargs['noise_sampler'] = noise_sampler 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": sampler_function = sampler_function_wrapper
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
else: else:
return comfy.samplers.sampler_object(sampler_name) return comfy.samplers.sampler_object(sampler_name)
+3
View File
@@ -701,6 +701,9 @@ class ImpactControlBridge:
return (value, ) return (value, )
else: else:
return (ExecutionBlocker(None), ) 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: else:
workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow']) workflow_nodes, links = workflow_to_map(extra_pnginfo['workflow'])
-25
View File
@@ -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
+113
View File
@@ -60,6 +60,8 @@ class SEGSDetailer:
CATEGORY = "ImpactPack/Detailer" 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 @staticmethod
def do_detail(image, segs, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, 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, denoise, noise_mask, force_inpaint, basic_pipe, refiner_ratio=None, batch_size=1, cycle=1,
@@ -174,6 +176,8 @@ class SEGSPaste:
CATEGORY = "ImpactPack/Detailer" 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 @staticmethod
def doit(image, segs, feather, alpha=255, ref_image_opt=None): def doit(image, segs, feather, alpha=255, ref_image_opt=None):
@@ -625,6 +629,111 @@ class SEGSRangeFilter:
return (segs[0], new_segs), (segs[0], remained_segs), 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: class SEGSToImageList:
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(s):
@@ -1486,6 +1595,8 @@ class SEGSPicker:
CATEGORY = "ImpactPack/Util" CATEGORY = "ImpactPack/Util"
DESCRIPTION = "This node provides a function to select only the chosen SEGS from the input SEGS."
@staticmethod @staticmethod
def doit(picks, segs, fallback_image_opt=None, unique_id=None): def doit(picks, segs, fallback_image_opt=None, unique_id=None):
if fallback_image_opt is not None: if fallback_image_opt is not None:
@@ -1542,6 +1653,8 @@ class DefaultImageForSEGS:
CATEGORY = "ImpactPack/Util" 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 @staticmethod
def doit(segs, image, override): def doit(segs, image, override):
results = [] results = []
+55 -14
View File
@@ -44,7 +44,9 @@ def read_wildcard(k, v):
elif isinstance(v, str): elif isinstance(v, str):
k = wildcard_normalize(k) k = wildcard_normalize(k)
wildcard_dict[k] = [v] wildcard_dict[k] = [v]
elif isinstance(v, (int, float)):
k = wildcard_normalize(k)
wildcard_dict[k] = [str(v)]
def read_wildcard_dict(wildcard_path): def read_wildcard_dict(wildcard_path):
global wildcard_dict global wildcard_dict
@@ -135,6 +137,8 @@ def process(text, seed=None):
b = r.group(3) b = r.group(3)
if b is not None: if b is not None:
b = b.strip() b = b.strip()
else:
b = "-1"
if r is not None: if r is not None:
if b is not None and is_numeric_string(a) and is_numeric_string(b): 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) x = int(a)
select_range = (x, x) 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: if select_range is not None and len(multi_select_pattern) == 2:
# PATTERN: count$$ # PATTERN: count$$
matches = re.findall(wildcard_pattern, multi_select_pattern[1]) options = expand_wildcard_or_return_string(options, multi_select_pattern[1], wildcard_pattern )
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]
elif select_range is not None and len(multi_select_pattern) == 3: elif select_range is not None and len(multi_select_pattern) == 3:
# PATTERN: count$$ sep $$ # PATTERN: count$$ sep $$
select_sep = multi_select_pattern[1] 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 = [] adjusted_probabilities = []
total_prob = 0 total_prob = 0
for option in options: 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()): if len(parts) == 2 and is_numeric_string(parts[0].strip()):
config_value = float(parts[0].strip()) config_value = float(parts[0].strip())
else: else:
@@ -178,15 +188,30 @@ def process(text, seed=None):
if select_range is None: if select_range is None:
select_count = 1 select_count = 1
else: 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) random_gen.shuffle(options)
selected_items = options selected_items = options
else: else:
selected_items = random_gen.choice(options, p=normalized_probabilities, size=select_count, replace=False) 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) replacement = select_sep.join(selected_items2)
if '::' in replacement: if '::' in replacement:
pass pass
@@ -237,7 +262,23 @@ def process(text, seed=None):
keyword = match.lower() keyword = match.lower()
keyword = wildcard_normalize(keyword) keyword = wildcard_normalize(keyword)
if keyword in local_wildcard_dict: 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 replacements_found = True
string = string.replace(f"__{match}__", replacement, 1) string = string.replace(f"__{match}__", replacement, 1)
elif '*' in keyword: elif '*' in keyword:
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-impact-pack" name = "comfyui-impact-pack"
description = "This node pack offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler." 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.5.2" version = "8.9"
license = { file = "LICENSE.txt" } license = { file = "LICENSE.txt" }
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"] dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long