166 lines
7.6 KiB
Python
166 lines
7.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
|
|
import sys
|
|
from torch.hub import download_url_to_file, get_dir
|
|
|
|
from .. import logger
|
|
|
|
# Path to ComfyUI-KEEP/modules/
|
|
module_path = os.path.dirname(os.path.abspath(__file__))
|
|
deps_path = os.path.join(module_path, 'deps')
|
|
if deps_path not in sys.path:
|
|
sys.path.insert(0, deps_path)
|
|
|
|
# directly import from the vendored wm_basicsr
|
|
try:
|
|
from wm_basicsr.utils.registry import ARCH_REGISTRY
|
|
from wm_basicsr.utils.download_util import load_file_from_url
|
|
# print("Successfully imported ARCH_REGISTRY and load_file_from_url from vendored wm_basicsr.")
|
|
except ImportError as e:
|
|
print(f"Error importing from vendored wm_basicsr in utils.py: {e}")
|
|
# Fallback ARCH_REGISTRY if needed, though ideally the import should work
|
|
class DummyArchRegistry:
|
|
def __init__(self): self._registry = {}
|
|
def register(self, obj): self._registry[obj.__name__] = obj; return obj
|
|
def get(self, name): return self._registry.get(name)
|
|
ARCH_REGISTRY = DummyArchRegistry()
|
|
# Fallback load_file_from_url if needed
|
|
def load_file_from_url(url, model_dir, progress, file_name): # Simplified
|
|
target_path = os.path.join(model_dir, file_name if file_name else os.path.basename(url))
|
|
if not os.path.exists(target_path):
|
|
raise FileNotFoundError(f"Fallback load_file_from_url: {target_path} not found. Please download manually.")
|
|
return target_path
|
|
|
|
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 |