from typing import Mapping, Any, Tuple, Callable import importlib import os from urllib.parse import urlparse import torch from torch import Tensor from torch.nn import functional as F import numpy as np from torch.hub import download_url_to_file, get_dir def get_obj_from_str(string: str, reload: bool=False) -> Any: module, cls = string.rsplit(".", 1) if reload: module_imp = importlib.import_module(module) importlib.reload(module_imp) return getattr(importlib.import_module(module, package=None), cls) def instantiate_from_config(config: Mapping[str, Any]) -> Any: if not "target" in config: raise KeyError("Expected key `target` to instantiate.") # import ipdb; ipdb.set_trace() return get_obj_from_str(config["target"])(**config.get("params", dict())) def wavelet_blur(image: Tensor, radius: int): """ Apply wavelet blur to the input tensor. """ # input shape: (1, 3, H, W) # convolution kernel kernel_vals = [ [0.0625, 0.125, 0.0625], [0.125, 0.25, 0.125], [0.0625, 0.125, 0.0625], ] kernel = torch.tensor(kernel_vals, dtype=image.dtype, device=image.device) # add channel dimensions to the kernel to make it a 4D tensor kernel = kernel[None, None] # repeat the kernel across all input channels kernel = kernel.repeat(3, 1, 1, 1) image = F.pad(image, (radius, radius, radius, radius), mode='replicate') # apply convolution output = F.conv2d(image, kernel, groups=3, dilation=radius) return output def wavelet_decomposition(image: Tensor, levels=5): """ Apply wavelet decomposition to the input tensor. This function only returns the low frequency & the high frequency. """ high_freq = torch.zeros_like(image) for i in range(levels): radius = 2 ** i low_freq = wavelet_blur(image, radius) high_freq += (image - low_freq) image = low_freq return high_freq, low_freq def wavelet_reconstruction(content_feat:Tensor, style_feat:Tensor): """ Apply wavelet decomposition, so that the content will have the same color as the style. """ # calculate the wavelet decomposition of the content feature content_high_freq, content_low_freq = wavelet_decomposition(content_feat) del content_low_freq # calculate the wavelet decomposition of the style feature style_high_freq, style_low_freq = wavelet_decomposition(style_feat) del style_high_freq # reconstruct the content feature with the style's high frequency return content_high_freq + style_low_freq # https://github.com/XPixelGroup/BasicSR/blob/master/basicsr/utils/download_util.py/ def load_file_from_url(url, model_dir=None, progress=True, file_name=None): """Load file form http url, will download models if necessary. Ref:https://github.com/1adrianb/face-alignment/blob/master/face_alignment/utils.py Args: url (str): URL to be downloaded. model_dir (str): The path to save the downloaded model. Should be a full path. If None, use pytorch hub_dir. Default: None. progress (bool): Whether to show the download progress. Default: True. file_name (str): The downloaded file name. If None, use the file name in the url. Default: None. Returns: str: The path to the downloaded file. """ if model_dir is None: # use the pytorch hub_dir hub_dir = get_dir() model_dir = os.path.join(hub_dir, 'checkpoints') os.makedirs(model_dir, exist_ok=True) parts = urlparse(url) filename = os.path.basename(parts.path) if file_name is not None: filename = file_name cached_file = os.path.abspath(os.path.join(model_dir, filename)) if not os.path.exists(cached_file): print(f'Downloading: "{url}" to {cached_file}\n') download_url_to_file(url, cached_file, hash_prefix=None, progress=progress) return cached_file def sliding_windows(h: int, w: int, tile_size: int, tile_stride: int) -> Tuple[int, int, int, int]: hi_list = list(range(0, h - tile_size + 1, tile_stride)) if (h - tile_size) % tile_stride != 0: hi_list.append(h - tile_size) wi_list = list(range(0, w - tile_size + 1, tile_stride)) if (w - tile_size) % tile_stride != 0: wi_list.append(w - tile_size) coords = [] for hi in hi_list: for wi in wi_list: coords.append((hi, hi + tile_size, wi, wi + tile_size)) return coords # https://github.com/csslc/CCSR/blob/main/model/q_sampler.py#L503 def gaussian_weights(tile_width: int, tile_height: int) -> np.ndarray: """Generates a gaussian mask of weights for tile contributions""" latent_width = tile_width latent_height = tile_height var = 0.01 midpoint = (latent_width - 1) / 2 # -1 because index goes from 0 to latent_width - 1 x_probs = [ np.exp(-(x - midpoint) * (x - midpoint) / (latent_width * latent_width) / (2 * var)) / np.sqrt(2 * np.pi * var) for x in range(latent_width)] midpoint = latent_height / 2 y_probs = [ np.exp(-(y - midpoint) * (y - midpoint) / (latent_height * latent_height) / (2 * var)) / np.sqrt(2 * np.pi * var) for y in range(latent_height)] weights = np.outer(y_probs, x_probs) return weights COUNT_VRAM = bool(os.environ.get("COUNT_VRAM", False)) def count_vram_usage(func: Callable) -> Callable: if not COUNT_VRAM: return func def wrapper(*args, **kwargs): peak_before = torch.cuda.max_memory_allocated() / (1024 ** 3) ret = func(*args, **kwargs) torch.cuda.synchronize() peak_after = torch.cuda.max_memory_allocated() / (1024 ** 3) print(f"VRAM peak before {func.__name__}: {peak_before:.5f} GB, after: {peak_after:.5f} GB") return ret return wrapper