Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f57d309932 | ||
|
|
6ccf9bab68 | ||
|
|
ac3668d946 | ||
|
|
78d3793a77 | ||
|
|
2346b67766 | ||
|
|
025db4b581 | ||
|
|
f8e16df2be | ||
|
|
7df60b2107 | ||
|
|
b394c158ea | ||
|
|
3e3cf3a5b4 | ||
|
|
93fc248503 | ||
|
|
16ffa7d462 | ||
|
|
cd34cfdd63 | ||
|
|
38bb9ffdf6 | ||
|
|
f939e66e1c | ||
|
|
b22aa90cf4 | ||
|
|
d18aaecb93 | ||
|
|
a7840b4fbf | ||
|
|
efa0cb66b2 | ||
|
|
0b94005b1b | ||
|
|
bfcb8674b3 | ||
|
|
839e5a9f90 | ||
|
|
5a2adda580 | ||
|
|
d05882a9e0 |
@@ -189,6 +189,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `PreviewDetailerHook` - Connecting this hook node helps provide assistance for viewing previews whenever SEGS Detailing tasks are completed. When working with a large number of SEGS, such as Make Tile SEGS, it allows for monitoring the situation as improvements progress incrementally.
|
||||
* Since this is the hook applied when pasting onto the original image, it has no effect on nodes like `SEGSDetailer`.
|
||||
* `VariationNoiseDetailerHookProvider` - Apply variation seed to the detailer. It can be applied in multiple stages through combine.
|
||||
* `CustomSamplerDetailerHookProvider` - Apply a hook that allows you to use a custom sampler in the Detailer nodes. When using `DetailerHookCombine`, the sampler from the first hook is applied.
|
||||
|
||||
### Iterative Upscale nodes
|
||||
* `Iterative Upscale (Latent/on Pixel Space)` - The upscaler takes the input upscaler and splits the scale_factor into steps, then iteratively performs upscaling.
|
||||
@@ -268,6 +269,7 @@ NOTE: The UltralyticsDetectorProvider node is not part of the ComfyUI-Impact-Pac
|
||||
* `Masks to Mask List`, `Mask List to Masks`, `Make Mask List`, `Make Mask Batch` - It has the same functionality as the nodes above, but uses mask as input instead of image.
|
||||
* `Flatten Mask Batch` - Flattens a Mask Batch into a single Mask. Normal operation is not guaranteed for non-binary masks.
|
||||
* `Make List (Any)` - Create a list with arbitrary values.
|
||||
* `Select Nth Item (Any list)` - Selects the Nth item from a list. If the index is out of range, it returns the last item in the list.
|
||||
|
||||
### Logics (experimental)
|
||||
* These nodes are experimental nodes designed to implement the logic for loops and dynamic switching.
|
||||
|
||||
@@ -122,6 +122,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"UnsamplerHookProvider": UnsamplerHookProvider,
|
||||
"CoreMLDetailerHookProvider": CoreMLDetailerHookProvider,
|
||||
"PreviewDetailerHookProvider": PreviewDetailerHookProvider,
|
||||
"CustomSamplerDetailerHookProvider": CustomSamplerDetailerHookProvider,
|
||||
|
||||
"DetailerHookCombine": DetailerHookCombine,
|
||||
"NoiseInjectionDetailerHookProvider": NoiseInjectionDetailerHookProvider,
|
||||
@@ -234,6 +235,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImpactMakeAnyList": MakeAnyList,
|
||||
"ImpactMakeMaskList": MakeMaskList,
|
||||
"ImpactMakeMaskBatch": MakeMaskBatch,
|
||||
"ImpactSelectNthItemOfAnyList": NthItemOfAnyList,
|
||||
|
||||
"RegionalSampler": RegionalSampler,
|
||||
"RegionalSamplerAdvanced": RegionalSamplerAdvanced,
|
||||
@@ -407,6 +409,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImpactMakeMaskList": "Make Mask List",
|
||||
"ImpactMakeMaskBatch": "Make Mask Batch",
|
||||
"ImpactMakeAnyList": "Make List (Any)",
|
||||
"ImpactSelectNthItemOfAnyList": "Select Nth Item (Any list)",
|
||||
|
||||
"ImpactStringSelector": "String Selector",
|
||||
"StringListToString": "String List to String",
|
||||
|
||||
+14
-12
@@ -68,7 +68,6 @@ def process_wrap(cmd_str, cwd=None, handler=None, env=None):
|
||||
|
||||
|
||||
try:
|
||||
import platform
|
||||
from torchvision.datasets.utils import download_url
|
||||
import impact.config
|
||||
|
||||
@@ -99,7 +98,7 @@ try:
|
||||
if not os.path.exists(os.path.join(sam_path, "sam_vit_b_01ec64.pth")):
|
||||
download_url("https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth", sam_path)
|
||||
except:
|
||||
print(f"[Impact Pack] Failed to auto-download model files. Please download them manually.")
|
||||
print("[Impact Pack] Failed to auto-download model files. Please download them manually.")
|
||||
|
||||
if not os.path.exists(onnx_path):
|
||||
print(f"### ComfyUI-Impact-Pack: onnx model directory created ({onnx_path})")
|
||||
@@ -108,18 +107,21 @@ try:
|
||||
impact.config.write_config()
|
||||
|
||||
# Remove legacy subpack
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'impact_subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
try:
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'impact_subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
|
||||
subpack_path = os.path.join(os.path.dirname(__file__), 'subpack')
|
||||
if os.path.exists(subpack_path):
|
||||
shutil.rmtree(subpack_path)
|
||||
print(f"Legacy subpack is detected. '{subpack_path}' is removed.")
|
||||
except:
|
||||
print(f"ERROT: Failed to delete legacy subpack '{subpack_path}'\nPlease delete the folder after terminate ComfyUI.")
|
||||
|
||||
install()
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
print("[ERROR] ComfyUI-Impact-Pack: Dependency installation has failed. Please install manually.")
|
||||
traceback.print_exc()
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
+71
-28
@@ -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() {
|
||||
@@ -324,6 +331,32 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
if(nodeData.name == "ImpactSelectNthItemOfAnyList") {
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
|
||||
if(!link_info || this.inputs[0].type != '*')
|
||||
return;
|
||||
|
||||
if(index >= 2)
|
||||
return;
|
||||
|
||||
// assign type
|
||||
let slot_type = '*';
|
||||
|
||||
if(type == 2) {
|
||||
slot_type = link_info.type;
|
||||
}
|
||||
else {
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
slot_type = node.outputs[link_info.origin_slot].type;
|
||||
}
|
||||
|
||||
this.inputs[0].type = slot_type;
|
||||
this.outputs[0].type = slot_type;
|
||||
this.outputs[0].label = slot_type;
|
||||
}
|
||||
}
|
||||
|
||||
if(nodeData.name === 'ImpactInversedSwitch') {
|
||||
nodeData.output = ['*'];
|
||||
nodeData.output_is_list = [false];
|
||||
@@ -337,12 +370,12 @@ app.registerExtension({
|
||||
if(type == 2) {
|
||||
// connect output
|
||||
if(connected){
|
||||
if(app.graph._nodes_by_id[link_info.target_id].type == 'Reroute') {
|
||||
if(app.graph._nodes_by_id[link_info.target_id]?.type == 'Reroute') {
|
||||
app.graph._nodes_by_id[link_info.target_id].disconnectInput(link_info.target_slot);
|
||||
}
|
||||
|
||||
if(this.outputs[0].type == '*'){
|
||||
if(link_info.type == '*') {
|
||||
if(link_info.type == '*' && app.graph.getNodeById(link_info.target_id).slots[link_info.target_slot].type != '*') {
|
||||
app.graph._nodes_by_id[link_info.target_id].disconnectInput(link_info.target_slot);
|
||||
}
|
||||
else {
|
||||
@@ -359,7 +392,7 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(app.graph._nodes_by_id[link_info.origin_id].type == 'Reroute')
|
||||
if(app.graph._nodes_by_id[link_info.origin_id]?.type == 'Reroute')
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
|
||||
// connect input
|
||||
@@ -371,7 +404,7 @@ app.registerExtension({
|
||||
return; // fallback
|
||||
}
|
||||
|
||||
if(origin_type == '*') {
|
||||
if(origin_type == '*' && app.graph.getNodeById(link_info.origin_id).slots[link_info.origin_slot].type != '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
return;
|
||||
}
|
||||
@@ -395,8 +428,9 @@ app.registerExtension({
|
||||
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
|
||||
!stackTrace.includes('LGraphNode.connect') && // for mouse device
|
||||
!stackTrace.includes('loadGraphData')) {
|
||||
if(this.outputs[link_info.origin_slot].links.length == 0)
|
||||
if(this.outputs[link_info.origin_slot].links.length == 0) {
|
||||
this.removeOutput(link_info.origin_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,9 +443,12 @@ app.registerExtension({
|
||||
slot_i++;
|
||||
}
|
||||
|
||||
let last_slot = this.outputs[this.outputs.length - 1];
|
||||
if (last_slot.slot_index == link_info.origin_slot) {
|
||||
this.addOutput(`output${slot_i}`, this.outputs[0].type);
|
||||
if(connected) {
|
||||
// NOTE: node.slot_index is different with link_info.origin_slot
|
||||
let last_slot_index = this.outputs.length - 1;
|
||||
if (last_slot_index == link_info.origin_slot) {
|
||||
this.addOutput(`output${slot_i}`, this.outputs[0].type);
|
||||
}
|
||||
}
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
@@ -474,6 +511,23 @@ app.registerExtension({
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
|
||||
const stackTrace = new Error().stack;
|
||||
if(stackTrace.includes('loadGraphData')) {
|
||||
if(this.widgets?.[0]) {
|
||||
this.widgets[0].options.max = this.inputs.length-3;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(stackTrace.includes('pasteFromClipboard')) {
|
||||
if(this.widgets?.[0]) {
|
||||
this.widgets[0].options.max = this.inputs.length-3;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!link_info)
|
||||
return;
|
||||
|
||||
@@ -485,7 +539,7 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
if(this.outputs[0].type == '*'){
|
||||
if(link_info.type == '*') {
|
||||
if(link_info.type == '*' && app.graph.getNodeById(link_info.target_id).slots[link_info.target_slot].type != '*') {
|
||||
app.graph._nodes_by_id[link_info.target_id].disconnectInput(link_info.target_slot);
|
||||
}
|
||||
else {
|
||||
@@ -516,12 +570,12 @@ app.registerExtension({
|
||||
if(this.inputs[0].type == '*'){
|
||||
const node = app.graph.getNodeById(link_info.origin_id);
|
||||
let origin_type = node.outputs[link_info.origin_slot]?.type;
|
||||
if(link_info.target_slot == 0 && this.inputs.length > 1) {
|
||||
if(link_info.target_slot == 0 && this.inputs.length > 3) { // NOTE: widgets are regarded as input since new front
|
||||
origin_type = this.inputs[1].type;
|
||||
node.connect(link_info.origin_slot, node.id, 'input1');
|
||||
}
|
||||
|
||||
if(origin_type == '*') {
|
||||
if(origin_type == '*' && app.graph.getNodeById(link_info.origin_id).slots[link_info.origin_slot].type != '*') {
|
||||
this.disconnectInput(link_info.target_slot);
|
||||
return;
|
||||
}
|
||||
@@ -539,15 +593,8 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
let select_slot = this.inputs.find(x => x.name == "select");
|
||||
let mode_slot = this.inputs.find(x => x.name == "sel_mode");
|
||||
|
||||
let converted_count = 0;
|
||||
converted_count += select_slot?1:0;
|
||||
converted_count += mode_slot?1:0;
|
||||
|
||||
if (!connected && (this.inputs.length > 1+converted_count)) {
|
||||
const stackTrace = new Error().stack;
|
||||
|
||||
if (!connected && (this.inputs.length > 3)) {
|
||||
if(
|
||||
!stackTrace.includes('LGraphNode.prototype.connect') && // for touch device
|
||||
!stackTrace.includes('LGraphNode.connect') && // for mouse device
|
||||
@@ -557,6 +604,7 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let slot_i = 1;
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
let input_i = this.inputs[i];
|
||||
@@ -566,18 +614,13 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
let last_slot = this.inputs[this.inputs.length - 1];
|
||||
if (
|
||||
(last_slot.name == 'select' && last_slot.name != 'sel_mode' && this.inputs[this.inputs.length - 2].link != undefined)
|
||||
|| (last_slot.name != 'select' && last_slot.name != 'sel_mode' && last_slot.link != undefined)) {
|
||||
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
|
||||
if(connected) {
|
||||
this.addInput(`${input_name}${slot_i}`, this.outputs[0].type);
|
||||
}
|
||||
|
||||
if(this.widgets?.length) {
|
||||
this.widgets[0].options.max = select_slot?this.inputs.length-1:this.inputs.length;
|
||||
if(this.widgets?.[0]) {
|
||||
this.widgets[0].options.max = this.inputs.length-3;
|
||||
this.widgets[0].value = Math.min(this.widgets[0].value, this.widgets[0].options.max);
|
||||
if(this.widgets[0].options.max > 0 && this.widgets[0].value == 0)
|
||||
this.widgets[0].value = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import configparser
|
||||
import os
|
||||
|
||||
version_code = [8, 11]
|
||||
version_code = [8, 16, 1]
|
||||
version = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||
|
||||
dependency_version = 24
|
||||
|
||||
+12
-3
@@ -344,6 +344,10 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
|
||||
refined_latent = latent_image
|
||||
|
||||
sampler_opt=None
|
||||
if detailer_hook is not None:
|
||||
sampler_opt = detailer_hook.get_custom_sampler()
|
||||
|
||||
# ksampler
|
||||
for i in range(0, cycle):
|
||||
if detailer_hook is not None:
|
||||
@@ -364,7 +368,7 @@ def enhance_detail(image, model, clip, vae, guide_size, guide_size_for_bbox, max
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model2, seed2, steps2, cfg2, sampler_name2, scheduler2, positive2, negative2,
|
||||
refined_latent, denoise2, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative,
|
||||
noise=noise, scheduler_func=scheduler_func)
|
||||
noise=noise, scheduler_func=scheduler_func, sampler_opt=sampler_opt)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
@@ -513,11 +517,16 @@ def enhance_detail_for_animatediff(image_frames, model, clip, vae, guide_size, g
|
||||
'samples': latent_frames
|
||||
}
|
||||
|
||||
|
||||
sampler_opt=None
|
||||
if detailer_hook is not None:
|
||||
sampler_opt = detailer_hook.get_custom_sampler()
|
||||
|
||||
if detailer_hook is not None:
|
||||
latent = detailer_hook.post_encode(latent)
|
||||
|
||||
refined_latent = impact_sampling.ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative,
|
||||
latent, denoise, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative, scheduler_func=scheduler_func)
|
||||
latent, denoise, refiner_ratio, refiner_model, refiner_clip, refiner_positive, refiner_negative, scheduler_func=scheduler_func, sampler_opt=sampler_opt)
|
||||
|
||||
if detailer_hook is not None:
|
||||
refined_latent = detailer_hook.pre_decode(refined_latent)
|
||||
@@ -1077,7 +1086,7 @@ class ONNXDetector:
|
||||
def detect(self, image, threshold, dilation, crop_factor, drop_size=1, detailer_hook=None):
|
||||
drop_size = max(drop_size, 1)
|
||||
try:
|
||||
import impact.onnx as onnx
|
||||
import impact.impact_onnx as onnx
|
||||
|
||||
h = image.shape[1]
|
||||
w = image.shape[2]
|
||||
|
||||
@@ -163,7 +163,7 @@ class SegmDetectorCombined:
|
||||
mask = segm_detector.detect_combined(image, threshold, dilation)
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((image.shape[2], image.shape[1]), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
|
||||
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
@@ -183,7 +183,7 @@ class BboxDetectorCombined(SegmDetectorCombined):
|
||||
mask = bbox_detector.detect_combined(image, threshold, dilation)
|
||||
|
||||
if mask is None:
|
||||
mask = torch.zeros((image.shape[2], image.shape[1]), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
|
||||
|
||||
return (mask.unsqueeze(0),)
|
||||
|
||||
|
||||
@@ -109,6 +109,12 @@ class DetailerHookCombine(PixelKSampleHookCombine):
|
||||
noise_2nd, is_touched = self.hook2.get_custom_noise(seed, noise, is_touched)
|
||||
return noise, is_touched
|
||||
|
||||
def get_custom_sampler():
|
||||
if self.hook1.get_custom_sampler() is not None:
|
||||
return self.hook1.get_custom_sampler()
|
||||
else:
|
||||
return self.hook2.get_custom_sampler()
|
||||
|
||||
|
||||
class SimpleCfgScheduleHook(PixelKSampleHook):
|
||||
target_cfg = 0
|
||||
@@ -173,6 +179,18 @@ class DetailerHook(PixelKSampleHook):
|
||||
def get_custom_noise(self, seed, noise, is_touched):
|
||||
return noise, is_touched
|
||||
|
||||
def get_custom_sampler(self):
|
||||
return None
|
||||
|
||||
|
||||
class CustomSamplerDetailerHookProvider(DetailerHook):
|
||||
def __init__(self, sampler):
|
||||
super().__init__()
|
||||
self.sampler = sampler
|
||||
|
||||
def get_custom_sampler(self):
|
||||
return self.sampler
|
||||
|
||||
|
||||
# class CustomNoiseDetailerHookProvider(DetailerHook):
|
||||
# def __init__(self, noise):
|
||||
|
||||
@@ -811,6 +811,26 @@ class CoreMLDetailerHookProvider:
|
||||
return (hook, )
|
||||
|
||||
|
||||
class CustomSamplerDetailerHookProvider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"sampler": ("SAMPLER", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DETAILER_HOOK",)
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Detailer"
|
||||
|
||||
DESCRIPTION = "Apply a hook that allows you to use a custom sampler in the Detailer nodes. When using `DetailerHookCombine`, the sampler from the first hook is applied."
|
||||
|
||||
def doit(self, sampler):
|
||||
hook = hooks.CustomSamplerDetailerHookProvider(sampler)
|
||||
return (hook, )
|
||||
|
||||
|
||||
class CfgScheduleHookProvider:
|
||||
schedules = ["simple"]
|
||||
|
||||
@@ -1906,7 +1926,7 @@ class MaskRectArea:
|
||||
# search for node
|
||||
node_found = False
|
||||
for node in extra_pnginfo["workflow"]["nodes"]:
|
||||
if node["id"] == int(unique_id):
|
||||
if str(node["id"]) == unique_id:
|
||||
min_x = node["properties"].get("x", 0) / 100
|
||||
min_y = node["properties"].get("y", 0) / 100
|
||||
width = node["properties"].get("w", 0) / 100
|
||||
|
||||
@@ -194,7 +194,7 @@ def impact_sample(model, seed, steps, cfg, sampler_name, scheduler, positive, ne
|
||||
|
||||
|
||||
def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent_image, denoise,
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0, noise=None, scheduler_func=None):
|
||||
refiner_ratio=None, refiner_model=None, refiner_clip=None, refiner_positive=None, refiner_negative=None, sigma_factor=1.0, noise=None, scheduler_func=None, sampler_opt=None):
|
||||
|
||||
if refiner_ratio is None or refiner_model is None or refiner_clip is None or refiner_positive is None or refiner_negative is None:
|
||||
# Use separated_sample instead of KSampler for `AYS scheduler`
|
||||
@@ -206,7 +206,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
|
||||
refined_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, False,
|
||||
sigma_ratio=sigma_factor, noise=noise, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, noise=noise, scheduler_func=scheduler_func)
|
||||
else:
|
||||
advanced_steps = math.floor(steps / denoise)
|
||||
start_at_step = advanced_steps - steps
|
||||
@@ -215,7 +215,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
# print(f"pre: {start_at_step} .. {end_at_step} / {advanced_steps}")
|
||||
temp_latent = separated_sample(model, True, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
positive, negative, latent_image, start_at_step, end_at_step, True,
|
||||
sigma_ratio=sigma_factor, noise=noise, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, noise=noise, scheduler_func=scheduler_func)
|
||||
|
||||
if 'noise_mask' in latent_image:
|
||||
# noise_latent = \
|
||||
@@ -229,7 +229,7 @@ def ksampler_wrapper(model, seed, steps, cfg, sampler_name, scheduler, positive,
|
||||
# print(f"post: {end_at_step} .. {advanced_steps + 1} / {advanced_steps}")
|
||||
refined_latent = separated_sample(refiner_model, False, seed, advanced_steps, cfg, sampler_name, scheduler,
|
||||
refiner_positive, refiner_negative, temp_latent, end_at_step, advanced_steps + 1, False,
|
||||
sigma_ratio=sigma_factor, scheduler_func=scheduler_func)
|
||||
sigma_ratio=sigma_factor, sampler_opt=sampler_opt, scheduler_func=scheduler_func)
|
||||
|
||||
return refined_latent
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import numpy as np
|
||||
import nodes
|
||||
from PIL import Image
|
||||
import io
|
||||
import impact.wildcards as wildcards
|
||||
import comfy
|
||||
from io import BytesIO
|
||||
import random
|
||||
@@ -183,7 +182,7 @@ async def wildcards_list(request):
|
||||
@PromptServer.instance.routes.post("/impact/wildcards")
|
||||
async def populate_wildcards(request):
|
||||
data = await request.json()
|
||||
populated = wildcards.process(data['text'], data.get('seed', None))
|
||||
populated = impact.wildcards.process(data['text'], data.get('seed', None))
|
||||
return web.json_response({"text": populated})
|
||||
|
||||
|
||||
@@ -512,7 +511,7 @@ def onprompt_populate_wildcards(json_data):
|
||||
else:
|
||||
input_seed = int(inputs['seed'])
|
||||
|
||||
inputs['populated_text'] = wildcards.process(inputs['wildcard_text'], input_seed)
|
||||
inputs['populated_text'] = impact.wildcards.process(inputs['wildcard_text'], input_seed)
|
||||
inputs['mode'] = 'reproduce'
|
||||
|
||||
PromptServer.instance.send_sync("impact-node-feedback", {"node_id": k, "widget_name": "populated_text", "type": "STRING", "value": inputs['populated_text']})
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -446,6 +446,31 @@ class MakeMaskList:
|
||||
return (masks, )
|
||||
|
||||
|
||||
class NthItemOfAnyList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"any_list": (any_typ,),
|
||||
"index": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1, "tooltip": "The index of the item you want to select from the list."}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_typ,)
|
||||
INPUT_IS_LIST = True
|
||||
FUNCTION = "doit"
|
||||
|
||||
CATEGORY = "ImpactPack/Util"
|
||||
|
||||
DESCRIPTION = "Selects the Nth item from a list. If the index is out of range, it returns the last item in the list."
|
||||
|
||||
def doit(self, any_list, index):
|
||||
i = index[0]
|
||||
if i >= len(any_list):
|
||||
return (any_list[-1],)
|
||||
else:
|
||||
return (any_list[i],)
|
||||
|
||||
|
||||
class MakeImageList:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
+53
-13
@@ -178,31 +178,71 @@ def tensor2numpy(image):
|
||||
|
||||
|
||||
def tensor_paste(image1, image2, left_top, mask):
|
||||
"""Mask and image2 has to be the same size"""
|
||||
"""
|
||||
Pastes image2 onto image1 at position left_top using mask.
|
||||
Supports both RGB and RGBA images.
|
||||
"""
|
||||
_tensor_check_image(image1)
|
||||
_tensor_check_image(image2)
|
||||
_tensor_check_mask(mask)
|
||||
|
||||
if image2.shape[1:3] != mask.shape[1:3]:
|
||||
mask = resize_mask(mask.squeeze(dim=3), image2.shape[1:3]).unsqueeze(dim=3)
|
||||
# raise ValueError(f"Inconsistent size: Image ({image2.shape[1:3]}) != Mask ({mask.shape[1:3]})")
|
||||
|
||||
|
||||
x, y = left_top
|
||||
_, h1, w1, _ = image1.shape
|
||||
_, h2, w2, _ = image2.shape
|
||||
|
||||
# calculate image patch size
|
||||
_, h1, w1, c1 = image1.shape
|
||||
_, h2, w2, c2 = image2.shape
|
||||
|
||||
# Calculate image patch size
|
||||
w = min(w1, x + w2) - x
|
||||
h = min(h1, y + h2) - y
|
||||
|
||||
|
||||
# If the patch is out of bound, nothing to do!
|
||||
if w <= 0 or h <= 0:
|
||||
return
|
||||
|
||||
|
||||
mask = mask[:, :h, :w, :]
|
||||
image1[:, y:y+h, x:x+w, :] = (
|
||||
(1 - mask) * image1[:, y:y+h, x:x+w, :] +
|
||||
mask * image2[:, :h, :w, :]
|
||||
)
|
||||
|
||||
# Get the region to be modified
|
||||
region1 = image1[:, y:y+h, x:x+w, :]
|
||||
region2 = image2[:, :h, :w, :]
|
||||
|
||||
# Handle RGB and RGBA cases
|
||||
if c1 == 3 and c2 == 3:
|
||||
# Both RGB - simple case
|
||||
image1[:, y:y+h, x:x+w, :] = (1 - mask) * region1 + mask * region2
|
||||
|
||||
elif c1 == 4 and c2 == 4:
|
||||
# Both RGBA - need to handle alpha channel separately
|
||||
# RGB channels
|
||||
image1[:, y:y+h, x:x+w, :3] = (
|
||||
(1 - mask) * region1[:, :, :, :3] +
|
||||
mask * region2[:, :, :, :3]
|
||||
)
|
||||
|
||||
# Alpha channel - use "over" composition
|
||||
a1 = region1[:, :, :, 3:4]
|
||||
a2 = region2[:, :, :, 3:4] * mask
|
||||
new_alpha = a1 + a2 * (1 - a1)
|
||||
image1[:, y:y+h, x:x+w, 3:4] = new_alpha
|
||||
|
||||
elif c1 == 4 and c2 == 3:
|
||||
# Target is RGBA, source is RGB - assume source is fully opaque
|
||||
image1[:, y:y+h, x:x+w, :3] = (
|
||||
(1 - mask) * region1[:, :, :, :3] +
|
||||
mask * region2
|
||||
)
|
||||
# Alpha channel - reduce alpha where mask is applied
|
||||
image1[:, y:y+h, x:x+w, 3:4] = region1[:, :, :, 3:4] * (1 - mask) + mask
|
||||
|
||||
elif c1 == 3 and c2 == 4:
|
||||
# Target is RGB, source is RGBA - apply source alpha to mask
|
||||
effective_mask = mask * region2[:, :, :, 3:4]
|
||||
image1[:, y:y+h, x:x+w, :] = (
|
||||
(1 - effective_mask) * region1 +
|
||||
effective_mask * region2[:, :, :, :3]
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
+38
-22
@@ -8,6 +8,7 @@ import numpy as np
|
||||
import threading
|
||||
from impact import utils
|
||||
from impact import config
|
||||
import logging
|
||||
|
||||
|
||||
wildcards_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "wildcards"))
|
||||
@@ -65,7 +66,7 @@ def read_wildcard_dict(wildcard_path):
|
||||
with open(file_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||
lines = f.read().splitlines()
|
||||
wildcard_dict[key] = [x for x in lines if not x.strip().startswith('#')]
|
||||
elif file.endswith('.yaml'):
|
||||
elif file.endswith('.yaml') or file.endswith('.yml'):
|
||||
file_path = os.path.join(root, file)
|
||||
|
||||
try:
|
||||
@@ -211,7 +212,7 @@ def process(text, seed=None):
|
||||
selected_items = random_gen.choice(options, p=normalized_probabilities, size=select_count, replace=False)
|
||||
|
||||
# x may be numpy.int32, convert to string
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', str(x), 1) for x in selected_items]
|
||||
selected_items2 = [re.sub(r'^\s*[0-9.]+::', '', str(x), count=1) for x in selected_items]
|
||||
replacement = select_sep.join(selected_items2)
|
||||
if '::' in replacement:
|
||||
pass
|
||||
@@ -278,7 +279,7 @@ def process(text, seed=None):
|
||||
|
||||
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)
|
||||
replacement = re.sub(r'^\s*[0-9.]+::', '', selected_item, count=1)
|
||||
replacements_found = True
|
||||
string = string.replace(f"__{match}__", replacement, 1)
|
||||
elif '*' in keyword:
|
||||
@@ -358,6 +359,7 @@ def extract_lora_values(string):
|
||||
lbw = None
|
||||
lbw_a = None
|
||||
lbw_b = None
|
||||
loader = None
|
||||
|
||||
if len(item) > 0:
|
||||
lora = item[0]
|
||||
@@ -376,6 +378,8 @@ def extract_lora_values(string):
|
||||
lbw_b = safe_float(lbw_item[2:].strip())
|
||||
elif lbw_item.strip() != '':
|
||||
lbw = lbw_item
|
||||
elif sub_item.startswith("LOADER="):
|
||||
loader = sub_item[7:]
|
||||
|
||||
if a is None:
|
||||
a = 1.0
|
||||
@@ -383,7 +387,7 @@ def extract_lora_values(string):
|
||||
b = a
|
||||
|
||||
if lora is not None and lora not in added:
|
||||
result.append((lora, a, b, lbw, lbw_a, lbw_b))
|
||||
result.append((lora, a, b, lbw, lbw_a, lbw_b, loader))
|
||||
added.add(lora)
|
||||
|
||||
return result
|
||||
@@ -407,6 +411,8 @@ def resolve_lora_name(lora_name_cache, name):
|
||||
if x.endswith(name):
|
||||
return x
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None, processed=None):
|
||||
"""
|
||||
@@ -427,7 +433,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
loras = extract_lora_values(pass1)
|
||||
pass2 = remove_lora_tags(pass1)
|
||||
|
||||
for lora_name, model_weight, clip_weight, lbw, lbw_a, lbw_b in loras:
|
||||
for lora_name, model_weight, clip_weight, lbw, lbw_a, lbw_b, loader in loras:
|
||||
lora_name_ext = lora_name.split('.')
|
||||
if ('.'+lora_name_ext[-1]) not in folder_paths.supported_pt_extensions:
|
||||
lora_name = lora_name+".safetensors"
|
||||
@@ -441,26 +447,36 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
path = None
|
||||
|
||||
if path is not None:
|
||||
print(f"LOAD LORA: {lora_name}: {model_weight}, {clip_weight}, LBW={lbw}, A={lbw_a}, B={lbw_b}")
|
||||
logging.info(f"LOAD LORA: {lora_name}: {model_weight}, {clip_weight}, LBW={lbw}, A={lbw_a}, B={lbw_b}, LOADER={loader}")
|
||||
|
||||
def default_lora():
|
||||
return nodes.LoraLoader().load_lora(model, clip, lora_name, model_weight, clip_weight)
|
||||
|
||||
if lbw is not None:
|
||||
if 'LoraLoaderBlockWeight //Inspire' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node(
|
||||
'https://github.com/ltdrdata/ComfyUI-Inspire-Pack',
|
||||
"To use 'LBW=' syntax in wildcards, 'Inspire Pack' extension is required.")
|
||||
|
||||
print(f"'LBW(Lora Block Weight)' is given, but the 'Inspire Pack' is not installed. The LBW= attribute is being ignored.")
|
||||
model, clip = default_lora()
|
||||
if loader is not None:
|
||||
if loader == 'nunchaku':
|
||||
if 'NunchakuFluxLoraLoader' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
logging.warning(f"To use `LOADER=nunchaku`, 'ComfyUI-nunchaku' is required. The LOADER= attribute is being ignored.")
|
||||
cls = nodes.NODE_CLASS_MAPPINGS['NunchakuFluxLoraLoader']
|
||||
model = cls().load_lora(model, lora_name, model_weight)[0]
|
||||
else:
|
||||
cls = nodes.NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
|
||||
model, clip, _ = cls().doit(model, clip, lora_name, model_weight, clip_weight, False, 0, lbw_a, lbw_b, "", lbw)
|
||||
logging.warning(f"LORA LOADER NOT FOUND: '{loader}'")
|
||||
else:
|
||||
model, clip = default_lora()
|
||||
def default_lora():
|
||||
return nodes.LoraLoader().load_lora(model, clip, lora_name, model_weight, clip_weight)
|
||||
|
||||
if lbw is not None:
|
||||
if 'LoraLoaderBlockWeight //Inspire' not in nodes.NODE_CLASS_MAPPINGS:
|
||||
utils.try_install_custom_node(
|
||||
'https://github.com/ltdrdata/ComfyUI-Inspire-Pack',
|
||||
"To use 'LBW=' syntax in wildcards, 'Inspire Pack' extension is required.")
|
||||
|
||||
logging.warning(f"'LBW(Lora Block Weight)' is given, but the 'Inspire Pack' is not installed. The LBW= attribute is being ignored.")
|
||||
model, clip = default_lora()
|
||||
else:
|
||||
cls = nodes.NODE_CLASS_MAPPINGS['LoraLoaderBlockWeight //Inspire']
|
||||
model, clip, _ = cls().doit(model, clip, lora_name, model_weight, clip_weight, False, 0, lbw_a, lbw_b, "", lbw)
|
||||
|
||||
else:
|
||||
model, clip = default_lora()
|
||||
else:
|
||||
print(f"LORA NOT FOUND: {orig_lora_name}")
|
||||
logging.warning(f"LORA NOT FOUND: {orig_lora_name}")
|
||||
|
||||
pass3 = [x.strip() for x in pass2.split("BREAK")]
|
||||
pass3 = [x for x in pass3 if x != '']
|
||||
@@ -469,7 +485,7 @@ def process_with_loras(wildcard_opt, model, clip, clip_encoder=None, seed=None,
|
||||
pass3 = ['']
|
||||
|
||||
pass3_str = [f'[{x}]' for x in pass3]
|
||||
print(f"CLIP: {str.join(' + ', pass3_str)}")
|
||||
logging.info(f"CLIP: {str.join(' + ', pass3_str)}")
|
||||
|
||||
result = None
|
||||
|
||||
|
||||
+1
-1
@@ -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.11"
|
||||
version = "8.16.1"
|
||||
license = { file = "LICENSE.txt" }
|
||||
dependencies = ["segment-anything", "scikit-image", "piexif", "transformers", "opencv-python-headless", "GitPython", "scipy>=1.11.4"]
|
||||
|
||||
|
||||
+1
-1
@@ -4,6 +4,6 @@ piexif
|
||||
transformers
|
||||
opencv-python-headless
|
||||
scipy>=1.11.4
|
||||
numpy<2
|
||||
numpy
|
||||
dill
|
||||
matplotlib
|
||||
Reference in New Issue
Block a user