Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
613466e53d | ||
|
|
65819fd372 | ||
|
|
f1e2c00c04 | ||
|
|
a1950e4ac5 | ||
|
|
1e7423d1d1 | ||
|
|
9f0bfc4d99 | ||
|
|
b746b5d7d4 | ||
|
|
f6e84847a1 | ||
|
|
b9969ec6c2 | ||
|
|
8b82f3c9eb | ||
|
|
1d3033cc94 | ||
|
|
4f4602a53f | ||
|
|
af680e8696 | ||
|
|
0c7a4ff3a5 | ||
|
|
f680891251 | ||
|
|
b63fb8746a | ||
|
|
e118268866 | ||
|
|
360c073bd1 | ||
|
|
4b608ba8d8 | ||
|
|
672404c008 | ||
|
|
8743d0f666 | ||
|
|
8066c4e453 | ||
|
|
e22c6e56ff | ||
|
|
b5d26fb5be | ||
|
|
0cff7ce040 |
@@ -0,0 +1,20 @@
|
|||||||
|
name: Publish to Comfy registry
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "pyproject.toml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-node:
|
||||||
|
name: Publish Custom Node to registry
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Publish Custom Node
|
||||||
|
uses: Comfy-Org/publish-node-action@main
|
||||||
|
with:
|
||||||
|
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} ## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||||
@@ -4,6 +4,19 @@
|
|||||||
<a href="https://huggingface.co/spaces/AI4Editing/MagicQuill"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)"></a>
|
<a href="https://huggingface.co/spaces/AI4Editing/MagicQuill"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-blue)"></a>
|
||||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/"><img src="https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg"></a>
|
<a href="https://creativecommons.org/licenses/by-sa/4.0/"><img src="https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg"></a>
|
||||||
|
|
||||||
|
|
||||||
|
# Fixed
|
||||||
|
- [x] SD1.5
|
||||||
|
|
||||||
|
Todo
|
||||||
|
- [ ] SDXL
|
||||||
|
- [ ] FLUX
|
||||||
|
|
||||||
|
|
||||||
|
Note:
|
||||||
|
[Brushnet](https://github.com/nullquant/ComfyUI-BrushNet) and [ComfyUI ControlNet Aux](https://github.com/Fannovel16/comfyui_controlnet_aux) are required.
|
||||||
|
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/8ee9663a-fef2-484a-a0b7-8427ab590424
|
https://github.com/user-attachments/assets/8ee9663a-fef2-484a-a0b7-8427ab590424
|
||||||
|
|
||||||
There is an HD video on [Youtube](https://www.youtube.com/watch?v=5DiKfONMnE4).
|
There is an HD video on [Youtube](https://www.youtube.com/watch?v=5DiKfONMnE4).
|
||||||
|
|||||||
-10
@@ -8,13 +8,3 @@ print(models_dir)
|
|||||||
# Download the model from the Hugging Face Hub
|
# Download the model from the Hugging Face Hub
|
||||||
repo_id = "LiuZichen/MagicQuill-models" # or any other model/dataset ID
|
repo_id = "LiuZichen/MagicQuill-models" # or any other model/dataset ID
|
||||||
snapshot_download(repo_id=repo_id, local_dir=models_dir)
|
snapshot_download(repo_id=repo_id, local_dir=models_dir)
|
||||||
|
|
||||||
repos = {
|
|
||||||
'ComfyUI_BrushNet': 'https://github.com/nullquant/ComfyUI-BrushNet',
|
|
||||||
'comfyui_controlnet_aux': 'https://github.com/Fannovel16/comfyui_controlnet_aux'
|
|
||||||
}
|
|
||||||
|
|
||||||
# for name, url in repos.items():
|
|
||||||
# target_dir = os.path.join(parent_dir, name)
|
|
||||||
# if not os.path.exists(target_dir):
|
|
||||||
# subprocess.run(['git', 'clone', url, target_dir], check=True)
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ app.registerExtension({
|
|||||||
for (const w of this.widgets) {
|
for (const w of this.widgets) {
|
||||||
if (["collapse_setting", "clear_canvas"].includes(w.name)) {
|
if (["collapse_setting", "clear_canvas"].includes(w.name)) {
|
||||||
// always show these widgets
|
// always show these widgets
|
||||||
} else if (["image", "original_image", "add_color_image", "add_edge_image", "remove_edge_image", "positive_prompt"].includes(w.name)) {
|
} else if (["image", "original_image", "add_color_image", "add_edge_image", "remove_edge_image"].includes(w.name)) {
|
||||||
w.type = "hidden";
|
w.type = "hidden";
|
||||||
w.value = null;
|
w.value = null;
|
||||||
w.computeSize = () => [0, -4];
|
w.computeSize = () => [0, -4];
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ export function MaigcQuillWidget(node, inputName, inputData, app) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
node.onDrawBackground = (ctx) => {
|
node.onDrawBackground = (ctx) => {
|
||||||
const setting_widgets = ["base_model_version", "negative_prompt", "dtype", "grow_size", "stroke_as_edge", "fine_edge", "edge_strength", "color_strength", "inpaint_strength", "seed", "steps", "cfg", "sampler_name", "scheduler"]
|
const setting_widgets = ["base_model_version","positive_prompt", "negative_prompt", "dtype", "grow_size", "stroke_as_edge", "fine_edge", "edge_strength", "color_strength", "inpaint_strength", "seed", "steps", "cfg", "sampler_name", "scheduler", "optional_original_image_name", "optional_add_color_image_name", "optional_add_edge_image_name", "optional_remove_edge_image_name"]
|
||||||
if (!this.flags.setting_collapsed) {
|
if (!this.flags.setting_collapsed) {
|
||||||
for (const w of this.widgets) {
|
for (const w of this.widgets) {
|
||||||
if (setting_widgets.includes(w.name)) {
|
if (setting_widgets.includes(w.name)) {
|
||||||
|
|||||||
+21
-6
@@ -23,15 +23,30 @@ import re
|
|||||||
class LLaVAModel:
|
class LLaVAModel:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# replace the model_path with correct path folder
|
# replace the model_path with correct path folder
|
||||||
base_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
self.base_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
models_dir = os.path.join(base_path, "models")
|
self.models_dir = os.path.join(self.base_path, "models")
|
||||||
model_path = os.path.join(models_dir, "llava-v1.5-7b-finetune-clean")
|
self.model_path = os.path.join(self.models_dir, "llava-v1.5-7b-finetune-clean")
|
||||||
|
self.tokenizer = None
|
||||||
|
self.model = None
|
||||||
|
self.image_processor = None
|
||||||
|
self.context_len = None
|
||||||
|
def load_model(self):
|
||||||
self.tokenizer, self.model, self.image_processor, self.context_len = load_pretrained_model(
|
self.tokenizer, self.model, self.image_processor, self.context_len = load_pretrained_model(
|
||||||
model_path=model_path,
|
model_path=self.model_path,
|
||||||
model_base=None,
|
model_base=None,
|
||||||
model_name=get_model_name_from_path(model_path),
|
model_name=get_model_name_from_path(self.model_path),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def unload_model(self):
|
||||||
|
"""Unload the model and clear GPU memory."""
|
||||||
|
if self.model is not None:
|
||||||
|
self.model.cpu()
|
||||||
|
del self.model
|
||||||
|
torch.cuda.empty_cache()
|
||||||
|
self.tokenizer = None
|
||||||
|
self.image_processor = None
|
||||||
|
self.context_len = None
|
||||||
|
|
||||||
def generate_description(self, images, question):
|
def generate_description(self, images, question):
|
||||||
qs = question
|
qs = question
|
||||||
|
|||||||
+126
-27
@@ -1,6 +1,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import random
|
||||||
from server import PromptServer
|
from server import PromptServer
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
import torch
|
import torch
|
||||||
@@ -89,25 +90,92 @@ async def process_background_img(request):
|
|||||||
@PromptServer.instance.routes.post("/magic_quill/guess_prompt")
|
@PromptServer.instance.routes.post("/magic_quill/guess_prompt")
|
||||||
async def guess_prompt_handler(request):
|
async def guess_prompt_handler(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
add_color_image = json_data.get("add_color_image", None)
|
add_color_image_data = json_data.get("add_color_image", None)
|
||||||
original_image = json_data.get("original_image", None)
|
original_image_data = json_data.get("original_image", None)
|
||||||
add_edge_image = json_data.get("add_edge_image", None)
|
add_edge_image_data = json_data.get("add_edge_image", None)
|
||||||
|
|
||||||
original_image_path = folder_paths.get_annotated_filepath(original_image)
|
if not original_image_data:
|
||||||
original_image_tensor = load_and_preprocess_image(original_image_path)
|
return web.json_response({"error": "Original image is required."}, status=400)
|
||||||
|
|
||||||
if add_color_image:
|
|
||||||
add_color_image_path = folder_paths.get_annotated_filepath(add_color_image)
|
|
||||||
add_color_image_tensor = load_and_preprocess_image(add_color_image_path)
|
|
||||||
else:
|
|
||||||
add_color_image_tensor = original_image_tensor
|
|
||||||
|
|
||||||
width, height = original_image_tensor.shape[1], original_image_tensor.shape[2]
|
|
||||||
add_edge_mask = create_alpha_mask(folder_paths.get_annotated_filepath(add_edge_image)) if add_edge_image else torch.zeros((1, height, width), dtype=torch.float32, device="cpu")
|
|
||||||
|
|
||||||
res = MagicQuill.guess_prompt(original_image_tensor, add_color_image_tensor, add_edge_mask)
|
temp_files_to_clean = []
|
||||||
|
input_dir = folder_paths.get_input_directory()
|
||||||
|
|
||||||
return web.json_response({"prompt": res, "error": False})
|
def is_base64(s):
|
||||||
|
return isinstance(s, str) and s.startswith("data:image/")
|
||||||
|
|
||||||
|
def handle_image_input(image_data, filename_base):
|
||||||
|
if is_base64(image_data):
|
||||||
|
try:
|
||||||
|
image = read_base64_image(image_data)
|
||||||
|
timestamp = int(time.time())
|
||||||
|
# Use a short hash to minimize collision chance but keep filename reasonable
|
||||||
|
hash_part = hashlib.sha1(str(timestamp).encode() + image_data.encode()).hexdigest()[:8]
|
||||||
|
filename = f"{filename_base}_{timestamp}_{hash_part}.png"
|
||||||
|
filepath = os.path.join(input_dir, filename)
|
||||||
|
image.save(filepath)
|
||||||
|
print(f"Saved temporary image to {filepath}")
|
||||||
|
temp_files_to_clean.append(filepath)
|
||||||
|
return filepath
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing base64 image for {filename_base}: {e}")
|
||||||
|
# Raise or return error? For now, let it raise to signal failure.
|
||||||
|
raise ValueError(f"Invalid base64 data for {filename_base}") from e
|
||||||
|
elif isinstance(image_data, str): # Assume filename
|
||||||
|
return folder_paths.get_annotated_filepath(image_data)
|
||||||
|
else: # Handle None or other invalid types
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
original_image_path = handle_image_input(original_image_data, "guess_original")
|
||||||
|
if not original_image_path or not os.path.exists(original_image_path):
|
||||||
|
return web.json_response({"error": f"Original image not found or invalid: {original_image_data}"}, status=400)
|
||||||
|
|
||||||
|
original_image_tensor = load_and_preprocess_image(original_image_path)
|
||||||
|
|
||||||
|
add_color_image_path = None
|
||||||
|
if add_color_image_data:
|
||||||
|
add_color_image_path = handle_image_input(add_color_image_data, "guess_add_color")
|
||||||
|
if not add_color_image_path or not os.path.exists(add_color_image_path):
|
||||||
|
print(f"Warning: Add color image specified but not found or invalid: {add_color_image_data}. Using original image.")
|
||||||
|
add_color_image_path = None # Fallback
|
||||||
|
|
||||||
|
if add_color_image_path:
|
||||||
|
add_color_image_tensor = load_and_preprocess_image(add_color_image_path)
|
||||||
|
else:
|
||||||
|
add_color_image_tensor = original_image_tensor # Fallback to original
|
||||||
|
|
||||||
|
add_edge_image_path = None
|
||||||
|
if add_edge_image_data:
|
||||||
|
add_edge_image_path = handle_image_input(add_edge_image_data, "guess_add_edge")
|
||||||
|
if not add_edge_image_path or not os.path.exists(add_edge_image_path):
|
||||||
|
print(f"Warning: Add edge image specified but not found or invalid: {add_edge_image_data}. Ignoring add edge.")
|
||||||
|
add_edge_image_path = None # Ignore if invalid
|
||||||
|
|
||||||
|
width, height = original_image_tensor.shape[2], original_image_tensor.shape[1] # Corrected order W, H
|
||||||
|
add_edge_mask = create_alpha_mask(add_edge_image_path) if add_edge_image_path else torch.zeros((1, height, width), dtype=torch.float32, device="cpu")
|
||||||
|
|
||||||
|
# Ensure mask dimensions match original image
|
||||||
|
if add_edge_mask.shape[1] != height or add_edge_mask.shape[2] != width:
|
||||||
|
add_edge_mask = F.interpolate(add_edge_mask.unsqueeze(0), size=(height, width), mode='nearest').squeeze(0)
|
||||||
|
|
||||||
|
res = MagicQuill.guess_prompt(original_image_tensor, add_color_image_tensor, add_edge_mask)
|
||||||
|
|
||||||
|
return web.json_response({"prompt": res, "error": False})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback_str = traceback.format_exc()
|
||||||
|
print(f"Error in guess_prompt_handler: {str(e)}\nTraceback: {traceback_str}")
|
||||||
|
return web.json_response({"error": str(e), "traceback": traceback_str}, status=500)
|
||||||
|
finally:
|
||||||
|
# Cleanup temporary files
|
||||||
|
for filepath in temp_files_to_clean:
|
||||||
|
try:
|
||||||
|
if os.path.exists(filepath):
|
||||||
|
os.remove(filepath)
|
||||||
|
print(f"Cleaned up temporary file: {filepath}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Error cleaning up temporary file {filepath}: {str(e)}")
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/magic_quill/run")
|
@PromptServer.instance.routes.post("/magic_quill/run")
|
||||||
async def run_magic_quill(request):
|
async def run_magic_quill(request):
|
||||||
@@ -194,6 +262,8 @@ async def run_magic_quill(request):
|
|||||||
clip = out[1]
|
clip = out[1]
|
||||||
vae = out[2]
|
vae = out[2]
|
||||||
|
|
||||||
|
random_seed = random.randint(0, 0xffffffffffffffff)
|
||||||
|
|
||||||
if not model or not vae or not clip:
|
if not model or not vae or not clip:
|
||||||
return web.json_response({"error": "Missing required model objects"}, status=400)
|
return web.json_response({"error": "Missing required model objects"}, status=400)
|
||||||
|
|
||||||
@@ -207,7 +277,8 @@ async def run_magic_quill(request):
|
|||||||
edge_strength = post.get("edge_strength", 0.5)
|
edge_strength = post.get("edge_strength", 0.5)
|
||||||
color_strength = post.get("color_strength", 0.5)
|
color_strength = post.get("color_strength", 0.5)
|
||||||
inpaint_strength = post.get("inpaint_strength", 1.0)
|
inpaint_strength = post.get("inpaint_strength", 1.0)
|
||||||
seed = post.get("seed", 0)
|
|
||||||
|
seed = post.get("seed", random_seed) #Random seed
|
||||||
steps = post.get("steps", 20)
|
steps = post.get("steps", 20)
|
||||||
cfg = post.get("cfg", 4.0)
|
cfg = post.get("cfg", 4.0)
|
||||||
sampler_name = post.get("sampler_name", "euler_ancestral")
|
sampler_name = post.get("sampler_name", "euler_ancestral")
|
||||||
@@ -239,7 +310,7 @@ async def run_magic_quill(request):
|
|||||||
steps=steps,
|
steps=steps,
|
||||||
cfg=cfg,
|
cfg=cfg,
|
||||||
sampler_name=sampler_name,
|
sampler_name=sampler_name,
|
||||||
scheduler=scheduler
|
scheduler=scheduler,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert the result tensors to base64
|
# Convert the result tensors to base64
|
||||||
@@ -326,9 +397,18 @@ class MagicQuill(object):
|
|||||||
"cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01, "display": "slider"}),
|
"cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01, "display": "slider"}),
|
||||||
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler_ancestral"}),
|
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler_ancestral"}),
|
||||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "exponential"}),
|
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "exponential"}),
|
||||||
|
# "optional_original_image_name": ("STRING", {"default": ""}),
|
||||||
|
# "optional_add_color_image_name": ("STRING", {"default": ""}),
|
||||||
|
# "optional_add_edge_image_name": ("STRING", {"default": ""}),
|
||||||
|
# "optional_remove_edge_image_name": ("STRING", {"default": ""}),
|
||||||
},
|
},
|
||||||
"optional": {
|
"optional": {
|
||||||
|
"optional_image": ("IMAGE",),
|
||||||
|
"optional_image_mask": ("MASK",),
|
||||||
|
"optional_original_image": ("IMAGE",),
|
||||||
|
"optional_add_color_image": ("IMAGE",),
|
||||||
|
"optional_add_edge_mask": ("MASK",),
|
||||||
|
"optional_remove_edge_mask": ("MASK",),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +421,7 @@ class MagicQuill(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def prepare_images_and_masks(cls, image, original_image, add_color_image, add_edge_image, remove_edge_image):
|
def prepare_images_and_masks(cls, image, original_image, add_color_image, add_edge_image, remove_edge_image):
|
||||||
# Handle file path inputs
|
# Handle file path inputs
|
||||||
|
print(f"image: {image} original_image: {original_image} add_color_image: {add_color_image} add_edge_image: {add_edge_image} remove_edge_image: {remove_edge_image}")
|
||||||
image_path = folder_paths.get_annotated_filepath(image)
|
image_path = folder_paths.get_annotated_filepath(image)
|
||||||
image_tensor = load_and_preprocess_image(image_path)
|
image_tensor = load_and_preprocess_image(image_path)
|
||||||
height, width = image_tensor.shape[1], image_tensor.shape[2]
|
height, width = image_tensor.shape[1], image_tensor.shape[2]
|
||||||
@@ -373,20 +454,42 @@ class MagicQuill(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def guess_prompt(cls, original_image_tensor, add_color_image_tensor, add_edge_mask):
|
def guess_prompt(cls, original_image_tensor, add_color_image_tensor, add_edge_mask):
|
||||||
|
cls.llavaModel.load_model()
|
||||||
description, ans1, ans2 = cls.llavaModel.process(original_image_tensor, add_color_image_tensor, add_edge_mask)
|
description, ans1, ans2 = cls.llavaModel.process(original_image_tensor, add_color_image_tensor, add_edge_mask)
|
||||||
ans_list = []
|
ans_list = []
|
||||||
if ans1 and ans1 != "":
|
if ans1 and ans1 != "":
|
||||||
ans_list.append(ans1)
|
ans_list.append(ans1)
|
||||||
if ans2 and ans2 != "":
|
if ans2 and ans2 != "":
|
||||||
ans_list.append(ans2)
|
ans_list.append(ans2)
|
||||||
|
cls.llavaModel.unload_model()
|
||||||
return ", ".join(ans_list)
|
return ", ".join(ans_list)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def painter_execute(cls, image, original_image, add_color_image, add_edge_image, remove_edge_image, model, vae, clip, base_model_version, positive_prompt, negative_prompt, dtype, grow_size, stroke_as_edge, fine_edge, edge_strength, color_strength, inpaint_strength, seed, steps, cfg, sampler_name, scheduler):
|
def painter_execute(cls, image, original_image, add_color_image, add_edge_image, remove_edge_image, model, vae, clip, base_model_version, positive_prompt, negative_prompt, dtype, grow_size, stroke_as_edge, fine_edge, edge_strength, color_strength, inpaint_strength, seed, steps, cfg, sampler_name, scheduler, optional_image = None, optional_image_mask = None, optional_original_image = None, optional_original_image_mask = None, optional_add_color_image = None, optional_add_color_image_mask = None, optional_add_edge_mask = None, optional_add_edge_mask_mask = None, optional_remove_edge_mask = None, optional_remove_edge_mask_mask = None):
|
||||||
print(f"model: {model} vae: {vae} clip: {clip} base_model_version: {base_model_version} positive_prompt: {positive_prompt} negative_prompt: {negative_prompt} dtype: {dtype} grow_size: {grow_size} stroke_as_edge: {stroke_as_edge} fine_edge: {fine_edge} edge_strength: {edge_strength} color_strength: {color_strength} inpaint_strength: {inpaint_strength} seed: {seed} steps: {steps} cfg: {cfg} sampler_name: {sampler_name} scheduler: {scheduler}")
|
print(f"model: {model} vae: {vae} clip: {clip} base_model_version: {base_model_version} positive_prompt: {positive_prompt} negative_prompt: {negative_prompt} dtype: {dtype} grow_size: {grow_size} stroke_as_edge: {stroke_as_edge} fine_edge: {fine_edge} edge_strength: {edge_strength} color_strength: {color_strength} inpaint_strength: {inpaint_strength} seed: {seed} steps: {steps} cfg: {cfg} sampler_name: {sampler_name} scheduler: {scheduler}")
|
||||||
print(f"original_image: {original_image} add_color_image: {add_color_image} add_edge_image: {add_edge_image} remove_edge_image: {remove_edge_image}")
|
print(f"original_image: {original_image} add_color_image: {add_color_image} add_edge_image: {add_edge_image} remove_edge_image: {remove_edge_image}")
|
||||||
add_color_image, original_image, total_mask, add_edge_mask, remove_edge_mask = cls.prepare_images_and_masks(image, original_image, add_color_image, add_edge_image, remove_edge_image)
|
print(f"optional_image: {optional_image} optional_image_mask: {optional_image_mask} optional_original_image: {optional_original_image} optional_original_image_mask: {optional_original_image_mask} optional_add_color_image: {optional_add_color_image} optional_add_color_image_mask: {optional_add_color_image_mask} optional_add_edge_mask: {optional_add_edge_mask} optional_add_edge_mask_mask: {optional_add_edge_mask_mask} optional_remove_edge_mask: {optional_remove_edge_mask} optional_remove_edge_mask_mask: {optional_remove_edge_mask_mask}")
|
||||||
|
# check if optional_original_image is tensor
|
||||||
|
if isinstance(optional_image, torch.Tensor):
|
||||||
|
image = optional_image
|
||||||
|
if isinstance(optional_original_image, torch.Tensor):
|
||||||
|
original_image = optional_original_image
|
||||||
|
if isinstance(optional_add_color_image, torch.Tensor):
|
||||||
|
add_color_image = optional_add_color_image
|
||||||
|
if isinstance(optional_add_edge_mask, torch.Tensor):
|
||||||
|
add_edge_mask = optional_add_edge_mask
|
||||||
|
if isinstance(optional_remove_edge_mask, torch.Tensor):
|
||||||
|
remove_edge_mask = optional_remove_edge_mask
|
||||||
|
|
||||||
|
if isinstance(optional_image, torch.Tensor) and isinstance(optional_image_mask, torch.Tensor):
|
||||||
|
#if if not the same size, resize the mask
|
||||||
|
if optional_image_mask.shape[1] != optional_image.shape[1] or optional_image_mask.shape[2] != optional_image.shape[2]:
|
||||||
|
print("resizing mask")
|
||||||
|
optional_image_mask = F.interpolate(optional_image_mask.unsqueeze(0), size=(optional_image.shape[1], optional_image.shape[2]), mode='nearest').squeeze(0)
|
||||||
|
total_mask = optional_image_mask
|
||||||
|
|
||||||
|
if not isinstance(image, torch.Tensor) and not isinstance(original_image, torch.Tensor) and not isinstance(add_color_image, torch.Tensor) and not isinstance(add_edge_image, torch.Tensor) and not isinstance(remove_edge_image, torch.Tensor):
|
||||||
|
add_color_image, original_image, total_mask, add_edge_mask, remove_edge_mask = cls.prepare_images_and_masks(image, original_image, add_color_image, add_edge_image, remove_edge_image)
|
||||||
|
|
||||||
if torch.sum(remove_edge_mask).item() > 0 and torch.sum(add_edge_mask).item() == 0:
|
if torch.sum(remove_edge_mask).item() > 0 and torch.sum(add_edge_mask).item() == 0:
|
||||||
if positive_prompt == "":
|
if positive_prompt == "":
|
||||||
@@ -425,9 +528,5 @@ class MagicQuill(object):
|
|||||||
return m.digest().hex()
|
return m.digest().hex()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def VALIDATE_INPUTS(self, image, original_image, add_color_image, add_edge_image, remove_edge_image, model, vae, clip, base_model_version, positive_prompt, negative_prompt, dtype, grow_size, edge_strength, color_strength, inpaint_strength, seed, steps, cfg, sampler_name, scheduler):
|
def VALIDATE_INPUTS(self, image, original_image, add_color_image, add_edge_image, remove_edge_image, model, vae, clip, base_model_version, positive_prompt, negative_prompt, dtype, grow_size, edge_strength, color_strength, inpaint_strength, seed, steps, cfg, sampler_name, scheduler, optional_image = None):
|
||||||
if not folder_paths.exists_annotated_filepath(image):
|
|
||||||
print(image)
|
|
||||||
return "Invalid image file: {}".format(image)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
+71
-29
@@ -1,35 +1,77 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "ComfyUI-MagicQuill"
|
||||||
|
version = "1.0.4"
|
||||||
|
description = "Fixed version of the original MagicQuill node."
|
||||||
|
authors = ["brantje <brantje@gmail.com>"]
|
||||||
|
license = { text = "MIT License" }
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
|
||||||
|
pynvml = "^11.4.0"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=61.0"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "llava"
|
name = "comfyui_magicquill_fixed" # Unique identifier for your node. Immutable after creation..
|
||||||
version = "1.2.2.post1"
|
description = "Fixed version of the original MagicQuill node. Required nodes: ComfyUI-Brushnet and ComfyUI Controlnet AUX"
|
||||||
description = "Towards GPT-4 like large language and visual assistant."
|
version = "1.0.4" # Custom Node version. Must be semantically versioned.
|
||||||
readme = "README.md"
|
dependencies = [
|
||||||
requires-python = ">=3.8"
|
'opencv-python',
|
||||||
classifiers = [
|
'diffusers',
|
||||||
"Programming Language :: Python :: 3",
|
'torchsde',
|
||||||
"License :: OSI Approved :: Apache Software License",
|
'protobuf',
|
||||||
]
|
'transformers',
|
||||||
dependencies = [
|
'tokenizers',
|
||||||
"transformers==4.37.2", "tokenizers==0.15.1", "sentencepiece==0.2.0", "shortuuid",
|
'sentencepiece',
|
||||||
"accelerate==0.33.0", "peft", "bitsandbytes",
|
'shortuuid',
|
||||||
"pydantic", "markdown2[all]", "numpy", "scikit-learn==1.2.2",
|
'accelerate',
|
||||||
"gradio==5.4.0", "gradio_client==1.4.2", "requests", "httpx==0.24.1", "uvicorn", "fastapi",
|
'peft',
|
||||||
"einops==0.6.1", "einops-exts==0.0.4", "timm==0.6.13",
|
'bitsandbytes',
|
||||||
]
|
'pydantic',
|
||||||
|
'markdown2',
|
||||||
[project.optional-dependencies]
|
'scikit-learn',
|
||||||
train = ["deepspeed==0.12.6", "ninja", "wandb"]
|
'requests',
|
||||||
build = ["build", "twine"]
|
'httpx',
|
||||||
|
'uvicorn',
|
||||||
|
'fastapi',
|
||||||
|
'einops',
|
||||||
|
'einops-exts',
|
||||||
|
'timm',
|
||||||
|
'webcolors',
|
||||||
|
'torch',
|
||||||
|
'importlib_metadata',
|
||||||
|
'huggingface_hub',
|
||||||
|
'scipy',
|
||||||
|
'opencv-python',
|
||||||
|
'filelock',
|
||||||
|
'numpy',
|
||||||
|
'Pillow',
|
||||||
|
'einops',
|
||||||
|
'torchvision',
|
||||||
|
'pyyaml',
|
||||||
|
'scikit-image',
|
||||||
|
'python-dateutil',
|
||||||
|
'mediapipe',
|
||||||
|
'svglib',
|
||||||
|
'fvcore',
|
||||||
|
'yapf',
|
||||||
|
'omegaconf',
|
||||||
|
'ftfy',
|
||||||
|
'addict',
|
||||||
|
'yacs',
|
||||||
|
'trimesh[easy]',
|
||||||
|
'albumentations',
|
||||||
|
'scikit-learn',
|
||||||
|
'matplotlib'
|
||||||
|
] # Filled in from requirements.txt
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
"Homepage" = "https://llava-vl.github.io"
|
Repository = "https://github.com/brantje/ComfyUI_MagicQuill"
|
||||||
"Bug Tracker" = "https://github.com/haotian-liu/LLaVA/issues"
|
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.comfy]
|
||||||
exclude = ["assets*", "benchmark*", "docs", "dist*", "playground*", "scripts*", "tests*"]
|
PublisherId = "brantje"
|
||||||
|
DisplayName = "ComfyUI MagicQuill (fixed)"
|
||||||
[tool.wheel]
|
|
||||||
exclude = ["assets*", "benchmark*", "docs", "dist*", "playground*", "scripts*", "tests*"]
|
|
||||||
|
|||||||
+2
-3
@@ -1,9 +1,8 @@
|
|||||||
webcolors==1.13
|
|
||||||
opencv-python
|
opencv-python
|
||||||
diffusers
|
diffusers
|
||||||
torchsde==0.2.6
|
torchsde==0.2.6
|
||||||
protobuf==4.25.4
|
protobuf==4.25.4
|
||||||
transformers==4.37.2
|
transformers==4.38.0
|
||||||
tokenizers==0.15.1
|
tokenizers==0.15.1
|
||||||
sentencepiece==0.2.0
|
sentencepiece==0.2.0
|
||||||
shortuuid
|
shortuuid
|
||||||
@@ -20,7 +19,7 @@ fastapi
|
|||||||
einops==0.6.1
|
einops==0.6.1
|
||||||
einops-exts==0.0.4
|
einops-exts==0.0.4
|
||||||
timm==0.6.13
|
timm==0.6.13
|
||||||
|
webcolors==24.11.1
|
||||||
torch
|
torch
|
||||||
importlib_metadata
|
importlib_metadata
|
||||||
huggingface_hub
|
huggingface_hub
|
||||||
|
|||||||
+36
-4
@@ -4,14 +4,46 @@ import torch
|
|||||||
import sys
|
import sys
|
||||||
import torch.utils._pytree as pytree
|
import torch.utils._pytree as pytree
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import subprocess
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.append(current_dir)
|
sys.path.append(current_dir)
|
||||||
sys.path.append(os.path.abspath(os.path.join(current_dir, '..')))
|
custom_nodes_dir = os.path.abspath(os.path.join(current_dir, '..'))
|
||||||
|
sys.path.append(custom_nodes_dir)
|
||||||
sys.path.append(os.path.abspath(os.path.join(current_dir, '..', '..', 'comfy_extras')))
|
sys.path.append(os.path.abspath(os.path.join(current_dir, '..', '..', 'comfy_extras')))
|
||||||
print(sys.path)
|
|
||||||
|
|
||||||
from ComfyUI_BrushNet.brushnet_nodes import BrushNetLoader, BrushNet, BlendInpaint, get_files_with_extension
|
|
||||||
|
brushnet_hyphen_dir = os.path.join(custom_nodes_dir, 'comfyui-brushnet')
|
||||||
|
brushnet_underscore_dir = os.path.join(custom_nodes_dir, 'comfyui_brushnet')
|
||||||
|
|
||||||
|
if not os.path.exists(brushnet_underscore_dir):
|
||||||
|
print(f"Creating symlink from {brushnet_hyphen_dir} to {brushnet_underscore_dir}")
|
||||||
|
|
||||||
|
# Create the symlink - use different methods based on OS
|
||||||
|
if os.name == 'nt': # Windows
|
||||||
|
# Requires admin privileges or developer mode
|
||||||
|
subprocess.run(['mklink', '/D', brushnet_underscore_dir, brushnet_hyphen_dir], shell=True)
|
||||||
|
else: # Unix/Linux/Mac
|
||||||
|
os.symlink(brushnet_hyphen_dir, brushnet_underscore_dir)
|
||||||
|
|
||||||
|
print(f"Symlink created: {os.path.exists(brushnet_underscore_dir)}")
|
||||||
|
|
||||||
|
# Now try importing from the symlinked directory
|
||||||
|
try:
|
||||||
|
# Add to path
|
||||||
|
sys.path.append(custom_nodes_dir)
|
||||||
|
|
||||||
|
# Import from symlinked directory
|
||||||
|
from comfyui_brushnet.brushnet_nodes import BrushNetLoader, BrushNet, BlendInpaint, get_files_with_extension
|
||||||
|
print("Successfully imported from symlinked directory")
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Import from symlink failed: {e}")
|
||||||
|
try:
|
||||||
|
from ComfyUI_BrushNet.brushnet_nodes import BrushNetLoader, BrushNet, BlendInpaint, get_files_with_extension
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Import from ComfyUI_BrushNet failed: {e}")
|
||||||
|
raise ImportError("Failed to import even with ComfyUI_BrushNet. Please check file permissions and structure.")
|
||||||
|
|
||||||
|
|
||||||
from comfyui_controlnet_aux.node_wrappers.lineart import LineArt_Preprocessor
|
from comfyui_controlnet_aux.node_wrappers.lineart import LineArt_Preprocessor
|
||||||
from comfyui_controlnet_aux.node_wrappers.pidinet import PIDINET_Preprocessor
|
from comfyui_controlnet_aux.node_wrappers.pidinet import PIDINET_Preprocessor
|
||||||
from comfyui_controlnet_aux.node_wrappers.color import Color_Preprocessor
|
from comfyui_controlnet_aux.node_wrappers.color import Color_Preprocessor
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ def get_colored_contour(img1, img2, threshold=10):
|
|||||||
|
|
||||||
def closest_colour(requested_colour):
|
def closest_colour(requested_colour):
|
||||||
min_colours = {}
|
min_colours = {}
|
||||||
for key, name in webcolors.CSS3_HEX_TO_NAMES.items():
|
for name in webcolors.names("css3"):
|
||||||
r_c, g_c, b_c = webcolors.hex_to_rgb(key)
|
r_c, g_c, b_c = webcolors.name_to_rgb(name)
|
||||||
rd = (r_c - requested_colour[0].item()) ** 2
|
rd = (r_c - requested_colour[0].item()) ** 2
|
||||||
gd = (g_c - requested_colour[1].item()) ** 2
|
gd = (g_c - requested_colour[1].item()) ** 2
|
||||||
bd = (b_c - requested_colour[2].item()) ** 2
|
bd = (b_c - requested_colour[2].item()) ** 2
|
||||||
|
|||||||
Reference in New Issue
Block a user