refactor: integrate sharpfin for high-quality image resize

Vendor sharpfin library (Apache 2.0) and add centralized wrapper
module (images_sharpfin.py) replacing torchvision tensor/PIL
conversion and resize operations throughout the codebase.

- Add modules/sharpfin/ vendored library with MKS2021, Lanczos3,
  Mitchell, Catmull-Rom kernels and optional Triton sparse acceleration
- Add modules/images_sharpfin.py wrapper with to_tensor(), to_pil(),
  pil_to_tensor(), normalize(), resize(), resize_tensor()
- Add resize_quality and resize_linearize_srgb settings
- Add MKS2021 and Lanczos3 upscaler entries
- Replace torchvision.transforms.functional imports across 18 files
- to_pil() auto-detects HWC/BHWC layout, adds .round() before uint8
- Sparse Triton path falls back to dense GPU on compilation failure
- Mixed-axis resize splits into two single-axis scale() calls
- Masks and non-sRGB data always use linearize=False
This commit is contained in:
CalamitousFelicitousness
2026-02-10 00:13:35 +00:00
committed by vladmandic
parent 2c4d0751d9
commit 76aa949a26
30 changed files with 2878 additions and 78 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
import numpy as np
import torch
import torchvision.transforms.functional as vF
from modules import images_sharpfin
import PIL
@@ -13,7 +13,7 @@ def preprocess(image, processor, **kwargs):
elif isinstance(image, np.ndarray):
image = PIL.Image.fromarray(image)
elif isinstance(image, torch.Tensor):
image = vF.to_pil_image(image)
image = images_sharpfin.to_pil(image)
else:
raise TypeError(f"Image must be of type PIL.Image, np.ndarray, or torch.Tensor, got {type(image)} instead.")
+7 -7
View File
@@ -14,7 +14,6 @@ from packaging import version
import PIL.Image
import numpy as np
import torch
import torchvision
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
from diffusers.image_processor import VaeImageProcessor
from diffusers.loaders import FromSingleFileMixin, LoraLoaderMixin, TextualInversionLoaderMixin
@@ -859,7 +858,8 @@ class StableDiffusionXLDiffImg2ImgPipeline(DiffusionPipeline, FromSingleFileMixi
# 4. Preprocess image
#image = self.image_processor.preprocess(image) #ideally we would have preprocess the image with diffusers, but for this POC we won't --- it throws a deprecated warning
map = torchvision.transforms.Resize(tuple(s // self.vae_scale_factor for s in original_image.shape[2:]),antialias=None)(map)
from modules import images_sharpfin
map = images_sharpfin.resize_tensor(map, tuple(s // self.vae_scale_factor for s in original_image.shape[2:]), linearize=False)
# 5. Prepare timesteps
def denoising_value_valid(dnv):
return type(denoising_end) == float and 0 < dnv < 1
@@ -1758,7 +1758,8 @@ class StableDiffusionDiffImg2ImgPipeline(DiffusionPipeline):
# 7. Prepare extra step kwargs.
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
map = torchvision.transforms.Resize(tuple(s // self.vae_scale_factor for s in image.shape[2:]),antialias=None)(map)
from modules import images_sharpfin
map = images_sharpfin.resize_tensor(map, tuple(s // self.vae_scale_factor for s in image.shape[2:]), linearize=False)
# 8. Denoising loop
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
@@ -1833,8 +1834,7 @@ class StableDiffusionDiffImg2ImgPipeline(DiffusionPipeline):
import gradio as gr
import diffusers
from PIL import Image, ImageEnhance, ImageOps # pylint: disable=reimported
from torchvision import transforms
from modules import errors, shared, devices, scripts_manager, processing, sd_models, images
from modules import errors, shared, devices, scripts_manager, processing, sd_models, images, images_sharpfin
detector = None
@@ -1888,9 +1888,9 @@ class Script(scripts_manager.Script):
else:
return None, None, None
image_mask = image_map.copy()
image_map = transforms.ToTensor()(image_map)
image_map = images_sharpfin.to_tensor(image_map)
image_map = image_map.to(devices.device)
image_init = 2 * transforms.ToTensor()(image_init) - 1
image_init = 2 * images_sharpfin.to_tensor(image_init) - 1
image_init = image_init.unsqueeze(0)
image_init = image_init.to(devices.device)
return image_init, image_map, image_mask
+3 -3
View File
@@ -84,7 +84,7 @@ class Script(scripts_manager.Script):
from installer import install
install('lpips')
from torchvision.transforms import ToPILImage, ToTensor
from modules import images_sharpfin
from scripts.lbm import get_model, extract_object, resize_and_center_crop # pylint: disable=no-name-in-module
ori_h_bg, ori_w_bg = fg_image.size
@@ -110,7 +110,7 @@ class Script(scripts_manager.Script):
if lbm_method == 'Simple':
output_image = img_pasted
else:
img_pasted_tensor = ToTensor()(img_pasted).to(device=devices.device, dtype=devices.dtype).unsqueeze(0) * 2 - 1
img_pasted_tensor = images_sharpfin.to_tensor(img_pasted).to(device=devices.device, dtype=devices.dtype).unsqueeze(0) * 2 - 1
batch = { "source_image": img_pasted_tensor }
z_source = model.vae.encode(batch[model.source_key])
output_image = model.sample(
@@ -120,7 +120,7 @@ class Script(scripts_manager.Script):
max_samples=1,
)
output_image = (output_image[0].clamp(-1, 1).float().cpu() + 1) / 2
output_image = ToPILImage()(output_image)
output_image = images_sharpfin.to_pil(output_image)
if lbm_composite:
output_image = Image.composite(output_image, bg_image, fg_mask)
+2 -6
View File
@@ -26,17 +26,13 @@ class Script(scripts_manager.Script):
def encode(self, p: processing.StableDiffusionProcessing, image: Image.Image):
if image is None:
return None
import numpy as np
import torch
from modules import images_sharpfin
if p.width is None or p.width == 0:
p.width = int(8 * (image.width * p.scale_by // 8))
if p.height is None or p.height == 0:
p.height = int(8 * (image.height * p.scale_by // 8))
image = images.resize_image(p.resize_mode, image, p.width, p.height, upscaler_name=p.resize_name, context=p.resize_context)
tensor = np.array(image).astype(np.float16) / 255.0
tensor = tensor[None].transpose(0, 3, 1, 2)
# image = image.transpose(0, 3, 1, 2)
tensor = torch.from_numpy(tensor).to(device=devices.device, dtype=devices.dtype)
tensor = images_sharpfin.to_tensor(image).unsqueeze(0).to(device=devices.device, dtype=devices.dtype)
tensor = 2.0 * tensor - 1.0
with devices.inference_context():
latent = shared.sd_model.vae.tiled_encode(tensor)
+6 -6
View File
@@ -18,7 +18,6 @@ import cv2
import numpy as np
from PIL import Image, ImageFilter
import torch
import torchvision
from torchvision import transforms
from transformers import (
CLIPImageProcessor,
@@ -1323,7 +1322,8 @@ class StableDiffusionXLSoftFillPipeline(
image.save("noised_image.png")
image = transforms.CenterCrop((image.size[1] // 64 * 64, image.size[0] // 64 * 64))(image)
image = transforms.ToTensor()(image)
from modules import images_sharpfin
image = images_sharpfin.to_tensor(image)
image = image * 2 - 1 # Normalize to [-1, 1]
return image.unsqueeze(0)
@@ -1334,7 +1334,8 @@ class StableDiffusionXLSoftFillPipeline(
"""
map = map.convert("L")
map = transforms.CenterCrop((map.size[1] // 64 * 64, map.size[0] // 64 * 64))(map)
map = transforms.ToTensor()(map)
from modules import images_sharpfin
map = images_sharpfin.to_tensor(map)
map = (map - 0.05) / (0.95 - 0.05)
map = torch.clamp(map, 0.0, 1.0)
return 1.0 - map
@@ -1349,9 +1350,8 @@ class StableDiffusionXLSoftFillPipeline(
# Prepare mask as rescaled tensor map
map = preprocess_map(mask).to(device)
map = torchvision.transforms.Resize(
tuple(s // self.vae_scale_factor for s in original_image_tensor.shape[2:]), antialias=None
)(map)
from modules import images_sharpfin
map = images_sharpfin.resize_tensor(map, tuple(s // self.vae_scale_factor for s in original_image_tensor.shape[2:]), linearize=False)
# Generate latent tensor with noise
original_with_noise = self.prepare_latents(