Files
jaidaken f09734b0ee
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Includes 30 custom nodes committed directly, 7 Civitai-exclusive
loras stored via Git LFS, and a setup script that installs all
dependencies and downloads HuggingFace-hosted models on vast.ai.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:56:42 +00:00

89 lines
3.6 KiB
Python

import torch
import numpy as np
from PIL import Image, ImageColor
from typing import Union, List
import logging
# Utility functions from mtb nodes: https://github.com/melMass/comfy_mtb
def pil2tensor(image: Union[Image.Image, List[Image.Image]]) -> torch.Tensor:
if isinstance(image, list):
return torch.cat([pil2tensor(img) for img in image], dim=0)
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def np2tensor(img_np: Union[np.ndarray, List[np.ndarray]]) -> torch.Tensor:
if isinstance(img_np, list):
return torch.cat([np2tensor(img) for img in img_np], dim=0)
return torch.from_numpy(img_np.astype(np.float32) / 255.0).unsqueeze(0)
def tensor2np(tensor: torch.Tensor):
if len(tensor.shape) == 3: # Single image
return np.clip(255.0 * tensor.cpu().numpy(), 0, 255).astype(np.uint8)
else: # Batch of images
return [np.clip(255.0 * t.cpu().numpy(), 0, 255).astype(np.uint8) for t in tensor]
def tensor2pil(image: torch.Tensor) -> List[Image.Image]:
batch_count = image.size(0) if len(image.shape) > 3 else 1
if batch_count > 1:
out = []
for i in range(batch_count):
out.extend(tensor2pil(image[i]))
return out
return [
Image.fromarray(
np.clip(255.0 * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)
)
]
def string_to_color(color_string: str) -> List[int]:
color_list = [0, 0, 0] # Default fallback (black)
if ',' in color_string:
# Handle CSV format (e.g., "255, 0, 0" or "255, 0, 0, 128" or "1.0, 0.5, 0.0")
try:
values = [float(channel.strip()) for channel in color_string.split(',')]
# Convert to 0-255 range if values are in 0-1 range
if all(0 <= v <= 1 for v in values):
color_list = [int(v * 255) for v in values]
else:
color_list = [int(v) for v in values]
except ValueError:
logging.warning(f"Invalid color format: {color_string}. Using default black.")
elif color_string.lstrip('#').isalnum() and not color_string.lstrip('#').replace('.', '', 1).isdigit():
# Could be Hex format or color name
color_string_stripped = color_string.lstrip('#')
# Try hex first
if len(color_string_stripped) in [6, 8] and all(c in '0123456789ABCDEFabcdef' for c in color_string_stripped):
if len(color_string_stripped) == 6: # #RRGGBB
color_list = [int(color_string_stripped[i:i+2], 16) for i in (0, 2, 4)]
elif len(color_string_stripped) == 8: # #RRGGBBAA
color_list = [int(color_string_stripped[i:i+2], 16) for i in (0, 2, 4, 6)]
else:
# Try color name (e.g., "red", "blue", "cyan")
try:
rgb = ImageColor.getrgb(color_string)
color_list = list(rgb)
except ValueError:
logging.warning(f"Invalid color name or hex format: {color_string}. Using default black.")
else:
# Handle single value (grayscale) - can be int or float
try:
value = float(color_string.strip())
# Convert to 0-255 range if it's a float between 0-1
if 0 <= value <= 1:
value = int(value * 255)
else:
value = int(value)
color_list = [value, value, value]
except ValueError:
logging.warning(f"Invalid color format: {color_string}. Using default black.")
# Clip values to valid range
color_list = np.clip(color_list, 0, 255).tolist()
return color_list