diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd07f49a..b04910250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,8 +29,9 @@ allows for configurable image tiling for x/y axis separately enable in *scripts -> asymmetric tiling* *note*: traditional symmetric tiling is achieved by setting circular mode for both x and y - - persist *models -> hugginface -> token* - - persist *models -> civitai -> token* + - persist *models -> hugginface -> token* + - persist *models -> civitai -> token* + - global switch to lancosz method for all interal resize ops and bicubic for interpolation ops - **Fixes** - update torch nightly urls - docs/wiki always use relative links diff --git a/TODO.md b/TODO.md index fc5af5eb3..109536b55 100644 --- a/TODO.md +++ b/TODO.md @@ -1,34 +1,28 @@ # TODO -- Check wiki links -- Update hints wiki -- Update changelog with docs and hints - Main ToDo list can be found at [GitHub projects](https://github.com/users/vladmandic/projects) -## Pending - -- LoRA direct with caching -- Previewer issues -- Redesign postprocessing - ## Future Candidates -- Flux NF4 loader: -- IPAdapter negative: -- Control API enhance scripts compatibility -- PixelSmith: +- Redesign postprocessing +- Native FP8 compute +- Flux NF4 loader: +- IPAdapter negative: +- Control API enhance scripts compatibility ## Code TODO -- TODO install: enable ROCm for windows when available -- TODO resize image: enable full VAE mode for resize-latent -- TODO processing: remove duplicate mask params -- TODO flux: fix loader for civitai nf4 models -- TODO model loader: implement model in-memory caching -- TODO hypertile: vae breaks when using non-standard sizes -- TODO model load: force-reloading entire model as loading transformers only leads to massive memory usage -- TODO lora load: direct with bnb -- TODO lora make: support quantized flux -- TODO control: support scripts via api -- TODO modernui: monkey-patch for missing tabs.select event +- flux: loader for civitai nf4 models (fixme) +- hypertile: vae breaks when using non-standard sizes (fixme) +- install: enable ROCm for windows when available (fixme) +- lora make support quantized flux (fixme) +- lora: add other quantization types (fixme) +- model load: force-reloading entire model as loading transformers only leads to massive memory usage (fixme) +- model loader: implement model in-memory caching (fixme) +- modernui: monkey-patch for missing tabs.select event (fixme) +- processing: remove duplicate mask params (fixme) +- resize image: enable full VAE mode for resize-latent (fixme) +- sana: fails when quantized (fixme) +- support scripts via api (fixme) +- transformer from-single-file with quant (fixme) +- vlm: add additional models (fixme) diff --git a/modules/control/util.py b/modules/control/util.py index 31741c6a1..f528c7ac9 100644 --- a/modules/control/util.py +++ b/modules/control/util.py @@ -36,7 +36,7 @@ def HWC3(x): def make_noise_disk(H, W, C, F): noise = np.random.uniform(low=0, high=1, size=((H // F) + 2, (W // F) + 2, C)) - noise = cv2.resize(noise, (W + 2 * F, H + 2 * F), interpolation=cv2.INTER_CUBIC) + noise = cv2.resize(noise, (W + 2 * F, H + 2 * F), interpolation=cv2.INTER_LANCZOS4) noise = noise[F: F + H, F: F + W] noise -= np.min(noise) noise /= np.max(noise) @@ -77,7 +77,7 @@ def img2mask(img, H, W, low=10, high=90): y = img[:, :, random.randrange(0, img.shape[2])] else: y = img - y = cv2.resize(y, (W, H), interpolation=cv2.INTER_CUBIC) + y = cv2.resize(y, (W, H), interpolation=cv2.INTER_LANCZOS4) if random.uniform(0, 1) < 0.5: y = 255 - y return y < np.percentile(y, random.randrange(low, high)) @@ -92,7 +92,7 @@ def resize_image(input_image, resolution): W *= k H = int(np.round(H / 64.0)) * 64 W = int(np.round(W / 64.0)) * 64 - img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4 if k > 1 else cv2.INTER_AREA) + img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4) return img @@ -150,7 +150,7 @@ def blend(images): y = np.zeros((images[0].shape[0], images[0].shape[1], 3), dtype=np.float32) for img in images: if img.shape[0] != y.shape[0] or img.shape[1] != y.shape[1]: - img = cv2.resize(img, (y.shape[1], y.shape[0]), interpolation=cv2.INTER_CUBIC) + img = cv2.resize(img, (y.shape[1], y.shape[0]), interpolation=cv2.INTER_LANCZOS4) if len(img.shape) == 3 and img.shape[2] == 4: # rgba to rgb img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB) if len(img.shape) == 2: # grayscale to rgb diff --git a/modules/interrogate/vqa.py b/modules/interrogate/vqa.py index 0752eb50d..778814560 100644 --- a/modules/interrogate/vqa.py +++ b/modules/interrogate/vqa.py @@ -315,6 +315,8 @@ def interrogate(question, image, model_name): image.thumbnail((768, 768), Image.Resampling.HAMMING) if image.mode != 'RGB': image = image.convert('RGB') + from modules import modelloader + modelloader.hf_login() try: if model_name is None: shared.log.error(f'Interrogate: type=vlm model="{model_name}" no model selected') diff --git a/modules/lama.py b/modules/lama.py index b02522140..fcbf53581 100644 --- a/modules/lama.py +++ b/modules/lama.py @@ -38,7 +38,7 @@ def prepare_img_and_mask(image, mask, device, pad_out_to_modulo=8, scale_factor= mode="symmetric", ) - def scale_image(img, factor, interpolation=cv2.INTER_AREA): + def scale_image(img, factor, interpolation=cv2.INTER_LANCZOS4): if img.shape[0] == 1: img = img[0] else: @@ -54,7 +54,7 @@ def prepare_img_and_mask(image, mask, device, pad_out_to_modulo=8, scale_factor= out_mask = get_image(mask) if scale_factor is not None: out_image = scale_image(out_image, scale_factor) - out_mask = scale_image(out_mask, scale_factor, interpolation=cv2.INTER_NEAREST) + out_mask = scale_image(out_mask, scale_factor, interpolation=cv2.INTER_LANCZOS4) if pad_out_to_modulo is not None and pad_out_to_modulo > 1: out_image = pad_img_to_modulo(out_image, pad_out_to_modulo) out_mask = pad_img_to_modulo(out_mask, pad_out_to_modulo) diff --git a/modules/layerdiffuse/layerdiffuse_model.py b/modules/layerdiffuse/layerdiffuse_model.py index 9a2259462..6311a9d53 100644 --- a/modules/layerdiffuse/layerdiffuse_model.py +++ b/modules/layerdiffuse/layerdiffuse_model.py @@ -272,7 +272,7 @@ class TransparentVAEDecoder(AutoencoderKL): B, H, W, C = fg.shape cb = checkerboard(shape=(H // 64, W // 64)) - cb = cv2.resize(cb, (W, H), interpolation=cv2.INTER_NEAREST) + cb = cv2.resize(cb, (W, H), interpolation=cv2.INTER_LANCZOS4) cb = (0.5 + (cb - 0.5) * 0.1)[None, ..., None] cb = torch.from_numpy(cb).to(fg) diff --git a/modules/masking.py b/modules/masking.py index d98766630..c317240b1 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -238,7 +238,7 @@ def run_segment(input_image: gr.Image, input_mask: np.ndarray): overlap = 0 if input_mask_size > 0: if mask.shape != input_mask.shape: - mask = cv2.resize(mask, (input_mask.shape[1], input_mask.shape[0]), interpolation=cv2.INTER_CUBIC) + mask = cv2.resize(mask, (input_mask.shape[1], input_mask.shape[0]), interpolation=cv2.INTER_LANCZOS4) overlap = cv2.bitwise_and(mask, input_mask) overlap = np.count_nonzero(overlap) if overlap == 0: @@ -278,7 +278,7 @@ def run_rembg(input_image: Image, input_mask: np.ndarray): binary_input = cv2.threshold(input_mask, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] binary_output = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] if binary_input.shape != binary_output.shape: - binary_output = cv2.resize(binary_output, binary_input.shape[:2], interpolation=cv2.INTER_LINEAR) + binary_output = cv2.resize(binary_output, binary_input.shape[:2], interpolation=cv2.INTER_LANCZOS4) binary_overlap = cv2.bitwise_and(binary_input, binary_output) input_size = np.count_nonzero(binary_input) overlap_size = np.count_nonzero(binary_overlap) @@ -419,7 +419,7 @@ def run_mask(input_image: Image.Image, input_mask: Image.Image = None, return_ty mask = run_rembg(input_image, input_mask) else: mask = run_segment(input_image, input_mask) - mask = cv2.resize(mask, (input_image.width, input_image.height), interpolation=cv2.INTER_LINEAR) + mask = cv2.resize(mask, (input_image.width, input_image.height), interpolation=cv2.INTER_LANCZOS4) debug(f'Mask shape={mask.shape} opts={opts}') if opts.mask_erode > 0: diff --git a/modules/model_flux.py b/modules/model_flux.py index a6d1b9939..bdb42037b 100644 --- a/modules/model_flux.py +++ b/modules/model_flux.py @@ -144,24 +144,27 @@ def quant_flux_bnb(checkpoint_info, transformer, text_encoder_2): def load_quants(kwargs, repo_id, cache_dir, allow_quant): - if not allow_quant: - return kwargs - quant_args = {} - quant_args = model_quant.create_bnb_config(quant_args) - if quant_args: - model_quant.load_bnb(f'Load model: type=FLUX quant={quant_args}') - if not quant_args: - quant_args = model_quant.create_ao_config(quant_args) + try: + if not allow_quant: + return kwargs + quant_args = {} + quant_args = model_quant.create_bnb_config(quant_args) if quant_args: - model_quant.load_torchao(f'Load model: type=FLUX quant={quant_args}') - if not quant_args: - return kwargs - if 'transformer' not in kwargs and ('Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization): - kwargs['transformer'] = diffusers.FluxTransformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) - shared.log.debug(f'Quantization: module=transformer type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') - if 'text_encoder_2' not in kwargs and ('Text Encoder' in shared.opts.bnb_quantization or 'Text Encoder' in shared.opts.torchao_quantization): - kwargs['text_encoder_2'] = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder="text_encoder_2", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) - shared.log.debug(f'Quantization: module=t5 type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') + model_quant.load_bnb(f'Load model: type=FLUX quant={quant_args}') + if not quant_args: + quant_args = model_quant.create_ao_config(quant_args) + if quant_args: + model_quant.load_torchao(f'Load model: type=FLUX quant={quant_args}') + if not quant_args: + return kwargs + if 'transformer' not in kwargs and ('Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization): + kwargs['transformer'] = diffusers.FluxTransformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) + shared.log.debug(f'Quantization: module=transformer type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') + if 'text_encoder_2' not in kwargs and ('Text Encoder' in shared.opts.bnb_quantization or 'Text Encoder' in shared.opts.torchao_quantization): + kwargs['text_encoder_2'] = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder="text_encoder_2", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) + shared.log.debug(f'Quantization: module=t5 type=bnb dtype={shared.opts.bnb_quantization_type} storage={shared.opts.bnb_quantization_storage}') + except Exception as e: + shared.log.error(f'Quantization: {e}') return kwargs diff --git a/modules/omnigen/utils.py b/modules/omnigen/utils.py index 0304e1732..8ace4fab6 100644 --- a/modules/omnigen/utils.py +++ b/modules/omnigen/utils.py @@ -45,12 +45,12 @@ def center_crop_arr(pil_image, image_size): """ while min(*pil_image.size) >= 2 * image_size: pil_image = pil_image.resize( - tuple(x // 2 for x in pil_image.size), resample=Image.BOX + tuple(x // 2 for x in pil_image.size), resample=Image.Resampling.LANCZOS ) scale = image_size / min(*pil_image.size) pil_image = pil_image.resize( - tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC + tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS ) arr = np.array(pil_image) @@ -63,19 +63,19 @@ def center_crop_arr(pil_image, image_size): def crop_arr(pil_image, max_image_size): while min(*pil_image.size) >= 2 * max_image_size: pil_image = pil_image.resize( - tuple(x // 2 for x in pil_image.size), resample=Image.BOX + tuple(x // 2 for x in pil_image.size), resample=Image.Resampling.LANCZOS ) if max(*pil_image.size) > max_image_size: scale = max_image_size / max(*pil_image.size) pil_image = pil_image.resize( - tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC + tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS ) if min(*pil_image.size) < 16: scale = 16 / min(*pil_image.size) pil_image = pil_image.resize( - tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC + tuple(round(x * scale) for x in pil_image.size), resample=Image.Resampling.LANCZOS ) arr = np.array(pil_image) diff --git a/modules/postprocess/codeformer_model.py b/modules/postprocess/codeformer_model.py index d0509a120..bff09c9df 100644 --- a/modules/postprocess/codeformer_model.py +++ b/modules/postprocess/codeformer_model.py @@ -103,7 +103,7 @@ def setup_model(dirname): restored_img = self.face_helper.paste_faces_to_input_image() restored_img = restored_img[:, :, ::-1] if original_resolution != restored_img.shape[0:2]: - restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LINEAR) + restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LANCZOS4) self.face_helper.clean_all() if shared.opts.detailer_unload: self.send_model_to(devices.cpu) diff --git a/modules/postprocess/dcc.py b/modules/postprocess/dcc.py new file mode 100644 index 000000000..7b9112f75 --- /dev/null +++ b/modules/postprocess/dcc.py @@ -0,0 +1,127 @@ +import numpy as np + + +def DetectDirect(A, type, k, T): + if type == 1: + # 45 degree diagonal direction + t1 = abs(A[2,0]-A[0,2]) + t2 = abs(A[4,0]-A[2,2])+abs(A[2,2]-A[0,4]) + t3 = abs(A[6,0]-A[4,2])+abs(A[4,2]-A[2,4])+abs(A[2,4]-A[0,6]) + t4 = abs(A[6,2]-A[4,4])+abs(A[4,4]-A[2,6]) + t5 = abs(A[6,4]-A[4,6]) + d1 = t1+t2+t3+t4+t5 + + # 135 degree diagonal direction + t1 = abs(A[0,4]-A[2,6]) + t2 = abs(A[0,2]-A[2,4])+abs(A[2,4]-A[4,6]) + t3 = abs(A[0,0]-A[2,2])+abs(A[2,2]-A[4,4])+abs(A[4,4]-A[6,6]) + t4 = abs(A[2,0]-A[4,2])+abs(A[4,2]-A[6,4]) + t5 = abs(A[4,0]-A[6,2]) + d2 = t1+t2+t3+t4+t5 + else: + # horizontal direction + t1 = abs(A[0,1]-A[0,3])+abs(A[2,1]-A[2,3])+abs(A[4,1]-A[4,3]) + t2 = abs(A[1,0]-A[1,2])+abs(A[1,2]-A[1,4]) + t3 = abs(A[3,0]-A[3,2])+abs(A[3,2]-A[3,4]) + d1 = t1+t2+t3 + + # vertical direction + t1 = abs(A[1,0]-A[3,0])+abs(A[1,2]-A[3,2])+abs(A[1,4]-A[3,4]) + t2 = abs(A[0,1]-A[2,1])+abs(A[2,1]-A[4,1]) + t3 = abs(A[0,3]-A[2,3])+abs(A[2,3]-A[4,3]) + d2 = t1+t2+t3 + # Compute the weight vector + w = np.array([1/(1+d1**k), 1/(1+d2**k)]) + # Compute the directional index + n = 3 + if (1+d1)/(1+d2) > T: + n = 1 + elif (1+d2)/(1+d1) > T: + n = 2 + return w, n + +def PixelValue(A, mode, w, n, f): + if mode == 1: + v1 = np.diag(np.fliplr(A))[::2] + v2 = np.diag(A)[::2] + else: + v1 = A[3,::2] + v2 = A[::2,3] + if n == 1: + p = np.dot(v2, f) + elif n == 2: + p = np.dot(v1, f) + else: + p1 = np.dot(v1, f) + p2 = np.dot(v2, f) + p = (w[0]*p1+w[1]*p2)/(w[0]+w[1]) + return p + +def PadLeftTop(img_pad, H, W): + img = img_pad[3:-3,3:-3] + # Pad the first/last three col and row + img_pad[3:H+3,1]=img[:,0] + img_pad[H+3::2,3:W+3]=img[H-2:H-1,:] + img_pad[3:H+3,W+3::2]=img[:,W-2:W-1] + img_pad[1,3:W+3]=img[0,:] + # Pad the missing nine points + img_pad[1,1]=img[0,0] + img_pad[H+3::2,1]=img[H-2,0] + img_pad[H+3::2,W+3::2]=img[H-2,W-2] + img_pad[1,W+3::2]=img[0,W-2] + return img_pad + +def PadRightBottom(img_pad, H, W): + img = img_pad[3:-3,3:-3] + # Pad the first/last three col and row + img_pad[3:H+3,0:3:2]=img[:,1:2] + img_pad[H+4::2,3:W+3]=img[H-1:H,:] + img_pad[3:H+3,W+4::2]=img[:,W-1:W] + img_pad[0:3:2,3:W+3]=img[1,:] + # Pad the missing nine points + img_pad[0:3:2,0:3:2]=img[1,1] + img_pad[H+4,0:3:2]=img[H-1,1] + img_pad[H+4,W+4]=img[H-1,W-1] + img_pad[0:3:2,W+4]=img[0,W-1] + return img_pad + +def _DCC(I, k, T): + m, n = I.shape + nRow = 2*m + nCol = 2*n + A = np.zeros([nRow+6, nCol+6]) + A[0+3:-1-3:2, 0+3:-1-3:2] = I + A = PadLeftTop(A, nRow, nCol) + f = np.array([-1, 9, 9, -1])/16 + for i in range(4,nRow+3,2): + for j in range(4,nCol+3,2): + [w,n] = DetectDirect(A[i-3:i+4,j-3:j+4],1,k,T) + A[i,j] = PixelValue(A[i-3:i+4,j-3:j+4],1,w,n,f) + A = PadRightBottom(A, nRow, nCol) + for i in range(3,nRow+3,2): + for j in range(4,nCol+3,2): + [w,n] = DetectDirect(A[i-2:i+3,j-2:j+3],2,k,T) + A[i,j] = PixelValue(A[i-3:i+4,j-3:j+4],2,w,n,f) + for i in range(4,nRow+3,2): + for j in range(3,nCol+3,2): + [w,n] = DetectDirect(A[i-2:i+3,j-2:j+3],3,k,T) + A[i,j] = PixelValue(A[i-3:i+4,j-3:j+4],3,w,n,f) + return A[3:-3,3:-3] + + +''' +img: Shape[H,W,C], Value Range[0-1] +level: super resolution level +Return: super resolution img who shape is the same with input +''' +def DCC(img, level): + # hyper parameters + k, T = 5, 1.15 + sr_img = img + # get the high resolution image channel by channel + for channel in range(img.shape[-1]): + sr_img_simple = img[:,:,channel] + for _ in range(level): + sr_img_simple = _DCC(sr_img_simple, k, T) + sr_img[:,:,channel] = sr_img_simple + return sr_img diff --git a/modules/postprocess/realesrgan_model_arch.py b/modules/postprocess/realesrgan_model_arch.py index 7947b0c7d..bfdfffad6 100644 --- a/modules/postprocess/realesrgan_model_arch.py +++ b/modules/postprocess/realesrgan_model_arch.py @@ -253,7 +253,7 @@ class RealESRGANer(): output_alpha = cv2.cvtColor(output_alpha, cv2.COLOR_BGR2GRAY) else: # use the cv2 resize for alpha channel h, w = alpha.shape[0:2] - output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LINEAR) + output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LANCZOS4) # merge the alpha channel output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2BGRA) diff --git a/modules/postprocess/restorer.py b/modules/postprocess/restorer.py index 827a08336..da138229e 100644 --- a/modules/postprocess/restorer.py +++ b/modules/postprocess/restorer.py @@ -34,7 +34,7 @@ def restore(np_image, name, session, strength): # pylint: disable=unused-argumen detected_faces = len(face_helper.cropped_faces) for cropped_face in face_helper.cropped_faces: - cropped_face = cv2.resize(cropped_face, resolution, interpolation=cv2.INTER_LINEAR) + cropped_face = cv2.resize(cropped_face, resolution, interpolation=cv2.INTER_LANCZOS4) cropped_face = cropped_face.astype(np.float16)[:,:,::-1] / 255.0 cropped_face = cropped_face.transpose((2, 0, 1)) cropped_face = (cropped_face - 0.5) / 0.5 @@ -52,7 +52,7 @@ def restore(np_image, name, session, strength): # pylint: disable=unused-argumen restored_img = face_helper.paste_faces_to_input_image() restored_img = restored_img[:, :, ::-1] if original_resolution != restored_img.shape[0:2]: - restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LINEAR) + restored_img = cv2.resize(restored_img, (0, 0), fx=original_resolution[1]/restored_img.shape[1], fy=original_resolution[0]/restored_img.shape[0], interpolation=cv2.INTER_LANCZOS4) face_helper.clean_all() t1 = time.time() diff --git a/modules/pulid/eva_clip/transform.py b/modules/pulid/eva_clip/transform.py index 39f3e4cf6..f74b750d9 100644 --- a/modules/pulid/eva_clip/transform.py +++ b/modules/pulid/eva_clip/transform.py @@ -1,4 +1,4 @@ -from typing import Optional, Sequence, Tuple +from typing import Optional, Tuple import torch import torch.nn as nn diff --git a/modules/upscaler.py b/modules/upscaler.py index 7dc9b6c1e..dcc3cee91 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -113,7 +113,7 @@ class Upscaler: if img.width >= dest_w and img.height >= dest_h: break if img.width != dest_w or img.height != dest_h: - img = img.resize((int(dest_w), int(dest_h)), resample=Image.Resampling.BICUBIC) + img = img.resize((int(dest_w), int(dest_h)), resample=Image.Resampling.LANCZOS) shared.state.end() shared.state = orig_state return img diff --git a/modules/upscaler_simple.py b/modules/upscaler_simple.py index 95f2acac2..a28d540f5 100644 --- a/modules/upscaler_simple.py +++ b/modules/upscaler_simple.py @@ -119,3 +119,25 @@ class UpscalerAsymmetricVAE(Upscaler): upscaled = F.to_pil_image(tensor.squeeze().clamp(0.0, 1.0).float().cpu()) self.vae = self.vae.to(device=devices.cpu) return upscaled + + +class UpscalerDCC(Upscaler): + def __init__(self, dirname=None): # pylint: disable=unused-argument + super().__init__(False) + self.name = "DCC Interpolation" + self.vae = None + self.scalers = [ + UpscalerData("DCC Interpolation", None, self), + ] + + def do_upscale(self, img: Image, selected_model=None): + import math + import numpy as np + from modules.postprocess.dcc import DCC + normalized = np.array(img).astype(np.float32) / 255.0 + scale = math.ceil(self.scale) + upscaled = DCC(normalized, scale) + upscaled = (upscaled - upscaled.min()) / (upscaled.max() - upscaled.min()) + upscaled = (255.0 * upscaled).astype(np.uint8) + upscaled = Image.fromarray(upscaled) + return upscaled