Files
wildminder-ComfyUI-KEEP/modules/utils.py
T

147 lines
6.6 KiB
Python

import os
import torch
import numpy as np
import cv2
from PIL import Image
import folder_paths
from comfy.utils import ProgressBar
from urllib.request import urlopen, Request
import hashlib
from torch.hub import download_url_to_file, get_dir
from .. import logger
# directly import from the vendored basicsr
#
# P2.7: the swallow-and-stub fallback is gone. A broken vendored dependency
# must fail loudly - the dummy registry hid the missing KEEP architecture and
# the node kept loading without it.
from .deps.basicsr.utils.registry import ARCH_REGISTRY
from .deps.basicsr.utils.download_util import load_file_from_url
KEEP_MODEL_CONFIGS = {
'KEEP': {
'architecture': {
'img_size': 512, 'emb_dim': 256, 'dim_embd': 512, 'n_head': 8, 'n_layers': 9,
'codebook_size': 1024, 'cft_list': ['16', '32', '64'], 'kalman_attn_head_dim': 48,
'num_uncertainty_layers': 3, 'cfa_list': ['16', '32'], 'cfa_nhead': 4, 'cfa_dim': 256, 'cond': 1,
'nf': 64, # from test_KEEP.yml (network_g) / stage1_VQGAN.yml (network_g)
'ch_mult': [1, 2, 2, 4, 4, 8], # from stage1_VQGAN.yml (network_g)
'attn_resolutions': [16], # from stage1_VQGAN.yml (network_g)
'res_blocks': 2, # from stage1_VQGAN.yml (network_g) -> defaults in VQAutoEncoder
'quantizer_type': "nearest", # from stage1_VQGAN.yml (network_g -> quantizer) -> used by KEEP
'beta': 0.25, # VQAutoEncoder default
'temp_reg_list': ['32'], # From stage3_KEEP.yml (network_g)
},
'url': 'https://github.com/jnjaby/KEEP/releases/download/v1.0.0/KEEP-b76feb75.pth',
'dest_dir': 'keep_models/KEEP'
},
'Asian': {
'architecture': {
'img_size': 512, 'emb_dim': 256, 'dim_embd': 512, 'n_head': 8, 'n_layers': 9,
'codebook_size': 1024, 'cft_list': ['32', '64', '128', '256'], 'kalman_attn_head_dim': 48,
'num_uncertainty_layers': 3, 'cfa_list': ['16', '32'], 'cfa_nhead': 4, 'cfa_dim': 256, 'cond': 1,
'nf': 64,
'ch_mult': [1, 2, 2, 4, 4, 8], # Check if Asian model has different VQGAN base
'attn_resolutions': [16],
'res_blocks': 2,
'quantizer_type': "nearest",
'beta': 0.25,
'temp_reg_list': [], # Check if Asian model uses temp_reg_list
},
'url': 'https://github.com/jnjaby/KEEP/releases/download/v1.0.0/KEEP_Asian-4765ebe0.pth',
'dest_dir': 'keep_models/KEEP'
}
}
default_arch_params = {
'gumbel_straight_through': False, 'gumbel_kl_weight': 1e-8,
'vqgan_path': None, # Loaded separately if needed by arch, but KEEP loads its own VQGAN weights
'latent_size': 256, # Default from KEEP arch
'fix_modules': ['quantize', 'generator'], # Common setting, can be adjusted
'flownet_path': None, # Will be loaded by ComfyUI node if needed
'cfa_nlayers': 4, # Default from KEEP arch
'cross_residual': True, # Default from KEEP arch
'mask_ratio': 0. # Default from KEEP arch
}
for model_key in KEEP_MODEL_CONFIGS:
for param_key, default_val in default_arch_params.items():
if param_key not in KEEP_MODEL_CONFIGS[model_key]['architecture']:
KEEP_MODEL_CONFIGS[model_key]['architecture'][param_key] = default_val
FACELIB_MODEL_URLS = {
'detection_Resnet50_Final.pth': ('https://github.com/jnjaby/KEEP/releases/download/v1.0.0/detection_Resnet50_Final.pth', None),
'detection_mobilenet0.25_Final.pth': ('https://github.com/jnjaby/KEEP/releases/download/v1.0.0/detection_mobilenet0.25_Final.pth', None),
'yolov5n-face.pth': ('https://github.com/jnjaby/KEEP/releases/download/v1.0.0/yolov5n-face.pth', None),
'yolov5l-face.pth': ('https://github.com/jnjaby/KEEP/releases/download/v1.0.0/yolov5l-face.pth', None),
'parsing_parsenet.pth': ('https://github.com/jnjaby/KEEP/releases/download/v1.0.0/parsing_parsenet.pth', None),
}
FACELIB_DEST_DIR = 'facedetection'
def load_file_from_url_comfy(url, model_dir_name, progress=True, file_name=None, sha256=None):
"""
`model_dir_name` is a category like 'upscale_models', 'facedetection', etc.
"""
if file_name is None:
file_name = os.path.basename(url)
if '?' in file_name:
file_name = file_name.split('?')[0]
destination_dir_paths = [os.path.join(folder_paths.models_dir, model_dir_name)]
if not destination_dir_paths:
# If category not found, default to ComfyUI/models/<model_dir_name>
destination_dir = os.path.join(folder_paths.models_dir, model_dir_name)
# print(f"Warning: Model directory type '{model_dir_name}' not in folder_paths. Using default: {destination_dir}")
else:
# Use the first path
destination_dir = destination_dir_paths[0]
os.makedirs(destination_dir, exist_ok=True)
cached_file = os.path.join(destination_dir, file_name)
if os.path.exists(cached_file) and sha256:
with open(cached_file, "rb") as f:
file_hash = hashlib.sha256(f.read()).hexdigest()
if file_hash == sha256:
logger.debug(f"File {file_name} already exists and hash matches. Skipping download.")
return cached_file
else:
logger.warning(f"File {file_name} exists but hash mismatch. Redownloading.")
elif os.path.exists(cached_file) and not sha256:
logger.debug(f"File {file_name} already exists. Skipping download (no hash check).")
return cached_file
logger.info(f'Downloading: "{file_name}" from {url} to {cached_file}')
try:
download_url_to_file(url, cached_file, hash_prefix=None, progress=progress)
except Exception as e:
logger.error(f"Error downloading {url} using torch.hub.download_url_to_file: {e}")
if os.path.exists(cached_file):
os.remove(cached_file)
raise
if sha256:
with open(cached_file, "rb") as f:
file_hash = hashlib.sha256(f.read()).hexdigest()
if file_hash != sha256:
if os.path.exists(cached_file):
os.remove(cached_file)
raise ValueError(f"Downloaded file {file_name} hash mismatch. Expected {sha256}, got {file_hash}.")
return cached_file
def comfy_image_to_cv2(comfy_image: torch.Tensor) -> np.ndarray:
if comfy_image.ndim == 3: comfy_image = comfy_image.unsqueeze(0)
img_np = comfy_image.cpu().numpy()
img_np = (img_np.squeeze(0) * 255).astype(np.uint8)
img_cv2 = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
return img_cv2
def cv2_to_comfy_image(cv2_image: np.ndarray) -> torch.Tensor:
img_rgb = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB)
img_np = img_rgb.astype(np.float32) / 255.0
comfy_image = torch.from_numpy(img_np).unsqueeze(0)
return comfy_image