Compare commits

...
30 Commits
Author SHA1 Message Date
Dr.Lt.Data 5a2adda580 fixed: impact switch - compatibility patch with new front
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/964
2025-04-22 02:06:08 +09:00
Dr.Lt.Data d05882a9e0 refactor: remove useless legacy code 2025-04-22 01:33:58 +09:00
Dr.Lt.Data d900939861 feat: Impact Sampler - support OSS scheduler 2025-04-13 10:24:35 +09:00
Jaquan cd2696f6fd fix #946 InversedSwitch(Any) was not append outputs after first connect (#956)
InversedSwitch(Any) not increasing number of outputs after connecting first output  (in )
# 953  #946
In the latest ComfyUI_frontend version, output is not appended as expected
2025-04-12 19:01:00 +09:00
Dr.Lt.Data 0b1ac0f1c5 bump version to v8.10 2025-03-24 00:06:42 +09:00
chutchatut 216d7fd60c Added sort=None to SEGS order filter (#944)
* added pick first N SEGs node

* remove take first N segs node and update segs order filter to add order=None

* add comments

* update for readibility

* format code

* remove match statement
2025-03-24 00:06:06 +09:00
chutchatut 28cd2f70b2 Update README.md (#943) 2025-03-21 19:06:24 +09:00
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
Dr.Lt.Data a89e9e01a6 fixed: sam editor - cannot load sam model properly when sams directory is specified manually.
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/895
2025-01-28 06:35:51 +09:00
Dr.Lt.Data e70b4df9b5 fixed: reproduce mode is reflected to the saved metadata but it wans't 2025-01-27 11:05:12 +09:00
Dr.Lt.Data af1ef7e441 feat: wildcards nodes - reproduce mode is added.
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/882

fixed: js crash
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/886#issuecomment-2613922083

removed: combo bool migration script
2025-01-27 10:55:47 +09:00
Dr.Lt.Data d8738eee2f hotfix: front - robustness fix
https://github.com/ltdrdata/ComfyUI-Impact-Pack/issues/886#issuecomment-2600716281
2025-01-19 18:08:59 +09:00
Dr.Lt.Data c397c68ca3 version marker 2025-01-19 03:17:25 +09:00
Alex Butler 0e0722ec08 Add tiled vae encoding/decoding toggle to detailer nodes (#883)
* detailers: support optional tiled vae encoding & decoding

* Remove start encoding/decoding logging
2025-01-19 03:16:56 +09:00
35 changed files with 10301 additions and 303 deletions
+6 -2
View File
@@ -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 }}
+28 -1
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.
## 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.
@@ -107,6 +132,8 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
* `SEGS Filter (label)` - This node filters SEGS based on the label of the detected areas.
* `SEGS Filter (ordered)` - This node sorts SEGS based on size and position and retrieves SEGs within a certain range.
* `SEGS Filter (range)` - This node retrieves only SEGs from SEGS that have a size and position within a certain range.
* `SEGS Filter (non max suppression)` - This node filters SEGS by removing those with high overlap based on the Intersection over Union (IoU) threshold, keeping only the most confident detections.
* `SEGS Filter (intersection)` - This node filters segs1, keeping only the SEGS that do not significantly overlap with any SEGS in segs2, based on the Intersection over Area (IoA) threshold.
* `SEGS Assign (label)` - Assign labels sequentially to SEGS. This node is useful when used with `[LAB]` of FaceDetailer.
* `SEGSConcat` - Concatenate segs1 and segs2. If source shape of segs1 and segs2 are different from segs2 will be ignored.
* `SEGS Merge` - SEGS contains multiple SEGs. SEGS Merge integrates several SEGs into a single merged SEG. The label is changed to `merged` and the confidence becomes the minimum confidence. The applied controlnet and cropped_image are removed.
@@ -224,7 +251,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
### Impact KSampler
* These samplers support basic_pipe and AYS scheduler
* These samplers support basic_pipe and AYS/OSS/GITS scheduler
* `KSampler (pipe)` - pipe version of KSampler
* `KSampler (advanced/pipe)` - pipe version of KSamplerAdvacned
* When converting the scheduler widget to input, refer to the `Impact Scheduler Adapter` node to resolve compatibility issues.
+4 -1
View File
@@ -18,7 +18,6 @@ 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})")
# Core
@@ -248,6 +247,8 @@ NODE_CLASS_MAPPINGS = {
"ImpactSEGSLabelFilter": SEGSLabelFilter,
"ImpactSEGSRangeFilter": SEGSRangeFilter,
"ImpactSEGSOrderedFilter": SEGSOrderedFilter,
"ImpactSEGSIntersectionFilter": SEGSIntersectionFilter,
"ImpactSEGSNMSFilter": SEGSNMSFilter,
"ImpactCompare": ImpactCompare,
"ImpactConditionalBranch": ImpactConditionalBranch,
@@ -363,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",
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
-35
View File
@@ -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; }
});
}
}
}
}
});
+42
View File
@@ -3,6 +3,48 @@ import { app } from "../../scripts/app.js";
let original_show = app.ui.dialog.show;
export function customAlert(message) {
try {
app.extensionManager.toast.addAlert(message);
}
catch {
alert(message);
}
}
export function isBeforeFrontendVersion(compareVersion) {
try {
const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__'];
if (typeof frontendVersion !== 'string') {
return false;
}
function parseVersion(versionString) {
const parts = versionString.split('.').map(Number);
return parts.length === 3 && parts.every(part => !isNaN(part)) ? parts : null;
}
const currentVersion = parseVersion(frontendVersion);
const comparisonVersion = parseVersion(compareVersion);
if (!currentVersion || !comparisonVersion) {
return false;
}
for (let i = 0; i < 3; i++) {
if (currentVersion[i] > comparisonVersion[i]) {
return false;
} else if (currentVersion[i] < comparisonVersion[i]) {
return true;
}
}
return false;
} catch {
return true;
}
}
function dialog_show_wrapper(html) {
if (typeof html === "string") {
if(html.includes("IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE")) {
+83 -61
View File
@@ -1,6 +1,13 @@
import { ComfyApp, app } from "../../scripts/app.js";
import { ComfyDialog, $el } from "../../scripts/ui.js";
import { api } from "../../scripts/api.js";
import { customAlert, isBeforeFrontendVersion } from "./common.js";
const is_legacy_front = () => isBeforeFrontendVersion('1.16.9');
if(is_legacy_front()) {
customAlert("An outdated version(<1.16.9) of the `comfyui-frontend-package` is installed. It is not compatible with the current version of the Impact Pack.");
}
let wildcards_list = [];
async function load_wildcards() {
@@ -93,7 +100,7 @@ const input_dirty = {};
const output_tracking = {};
function progressExecuteHandler(event) {
if(event.detail.output.aux){
if(event.detail?.output?.aux){
const id = event.detail.node;
if(input_tracking.hasOwnProperty(id)) {
if(input_tracking.hasOwnProperty(id) && input_tracking[id][0] != event.detail.output.aux[0]) {
@@ -223,29 +230,29 @@ 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
});
}
}
],
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']
}
],
menuCommands: [
{
path: ['Edit'],
commands: ['refresh-impact-wildcard']
}
],
loadedGraphNode(node, app) {
if (node.comfyClass == "MaskPainter") {
@@ -273,7 +280,7 @@ app.registerExtension({
}
else {
const node = app.graph.getNodeById(link_info.origin_id);
slot_type = node.outputs[link_info.origin_slot].type;
slot_type = node.outputs[link_info.origin_slot]?.type;
}
this.inputs[0].type = slot_type;
@@ -365,7 +372,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);
@@ -399,6 +410,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++;
}
@@ -467,6 +481,11 @@ app.registerExtension({
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
const stackTrace = new Error().stack;
if(stackTrace.includes('LGraph.configure')) {
return;
}
if(!link_info)
return;
@@ -509,7 +528,7 @@ app.registerExtension({
if(this.inputs[0].type == '*'){
const node = app.graph.getNodeById(link_info.origin_id);
let origin_type = node.outputs[link_info.origin_slot]?.type;
if(link_info.target_slot == 0 && this.inputs.length > 1) {
if(link_info.target_slot == 0 && this.inputs.length > 3) { // NOTE: widgets are regarded as input since new front
origin_type = this.inputs[1].type;
node.connect(link_info.origin_slot, node.id, 'input1');
}
@@ -534,13 +553,7 @@ app.registerExtension({
let select_slot = this.inputs.find(x => x.name == "select");
let mode_slot = this.inputs.find(x => x.name == "sel_mode");
let converted_count = 0;
converted_count += select_slot?1:0;
converted_count += mode_slot?1:0;
if (!connected && (this.inputs.length > 1+converted_count)) {
const stackTrace = new Error().stack;
if (!connected && (this.inputs.length > 3)) {
if(
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
!stackTrace.includes('LGraphNode.connect') && // for mouse device
@@ -550,6 +563,7 @@ app.registerExtension({
}
}
let slot_i = 1;
for (let i = 0; i < this.inputs.length; i++) {
let input_i = this.inputs[i];
@@ -560,11 +574,7 @@ app.registerExtension({
}
let last_slot = this.inputs[this.inputs.length - 1];
if (
(last_slot.name == 'select' && last_slot.name != 'sel_mode' && this.inputs[this.inputs.length - 2].link != undefined)
|| (last_slot.name != 'select' && last_slot.name != 'sel_mode' && last_slot.link != undefined)) {
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
}
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
if(this.widgets?.length) {
this.widgets[0].options.max = select_slot?this.inputs.length-1:this.inputs.length;
@@ -613,12 +623,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", {
@@ -692,18 +704,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"; }
});
@@ -716,14 +730,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;
}
}
}
@@ -755,14 +771,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';
}
});
}
File diff suppressed because it is too large Load Diff
+4
View File
@@ -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):
@@ -166,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 -1
View File
@@ -1,7 +1,7 @@
import configparser
import os
version_code = [8, 3, 1]
version_code = [8, 12]
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
dependency_version = 24
+16 -9
View File
@@ -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)
+67 -23
View File
@@ -96,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."}),
@@ -218,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"}),
}
}
@@ -226,6 +232,8 @@ class DetailerForEach:
CATEGORY = "ImpactPack/Detailer"
DESCRIPTION = "It enhances details by inpainting each region within the detected area bundle (SEGS) after enlarging them based on the guide size."
@staticmethod
def get_core_module():
return core
@@ -234,7 +242,7 @@ class DetailerForEach:
def do_detail(image, segs, model, clip, vae, guide_size, guide_size_for_bbox, max_size, seed, steps, cfg, sampler_name, scheduler,
positive, negative, denoise, feather, noise_mask, force_inpaint, wildcard_opt=None, detailer_hook=None,
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None,
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None):
cycle=1, inpaint_model=False, noise_mask_feather=0, scheduler_func_opt=None, tiled_encode=False, tiled_decode=False):
if len(image) > 1:
raise Exception('[Impact Pack] ERROR: DetailerForEach does not allow image batches.\nPlease refer to https://github.com/ltdrdata/ComfyUI-extension-tutorials/blob/Main/ComfyUI-Impact-Pack/tutorial/batching-detailer.md for more information.')
@@ -338,7 +346,8 @@ class DetailerForEach:
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)
scheduler_func=scheduler_func_opt, vae_tiled_encode=tiled_encode,
vae_tiled_decode=tiled_decode)
else:
enhanced_image = cropped_image
cnet_pils = None
@@ -384,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, )
@@ -425,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"}),
}
}
@@ -435,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.')
@@ -456,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:
@@ -513,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")
@@ -522,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,
@@ -530,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')
@@ -562,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 = []
@@ -588,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
@@ -606,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
@@ -1381,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"}),
}
}
@@ -1391,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
@@ -1418,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
@@ -1484,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,
@@ -1552,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.')
@@ -1561,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:
@@ -1588,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.')
@@ -1609,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:
@@ -2322,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"],),
},
@@ -2331,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
@@ -2353,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."}),
@@ -2364,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")
@@ -2391,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"
+20 -56
View File
@@ -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)
+25 -6
View File
@@ -78,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)
logging.info(f"[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"])
@@ -93,7 +97,7 @@ 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()
logging.info("[Impact Pack] SAM model loaded. ")
@@ -478,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]]
@@ -499,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):
+4 -22
View File
@@ -8,7 +8,7 @@ from impact.utils import any_typ
import impact.core as core
import re
import nodes
import traceback
class ImpactCompare:
@classmethod
@@ -574,27 +574,6 @@ class ImpactSleep:
return (signal,)
error_skip_flag = False
try:
import cm_global
def filter_message(str):
global error_skip_flag
if "IMPACT-PACK-SIGNAL: STOP CONTROL BRIDGE" in str:
return True
elif error_skip_flag and "ERROR:root:!!! Exception during processing !!!\n" == str:
error_skip_flag = False
return True
else:
return False
cm_global.try_call(api='cm.register_message_collapse', f=filter_message)
except Exception as e:
print(f"[WARN] ComfyUI-Impact-Pack: `ComfyUI` or `ComfyUI-Manager` is an outdated version.")
pass
def workflow_to_map(workflow):
nodes = {}
links = {}
@@ -701,6 +680,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'])
-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
+141 -43
View File
@@ -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
@@ -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,
@@ -174,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):
@@ -500,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}),
@@ -513,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:
@@ -625,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):
@@ -1486,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:
@@ -1542,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 = []
+10 -3
View File
@@ -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):
+55 -14
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-impact-pack"
description = "This node pack offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
version = "8.3.1"
version = "8.12"
license = { file = "LICENSE.txt" }
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