update upscaler workflow

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-08-22 20:10:00 +02:00
parent 9b5cf7a73b
commit e17efc19f7
12 changed files with 57 additions and 53 deletions
+1
View File
@@ -116,6 +116,7 @@ Plus quite a lot more, see full [changelog](https://github.com/vladmandic/automa
- api: auth via remote-ip
- krea2: fallback to base pipeline/transformer for nunchaku-lite
- torchsde: handle obsolete dependency
- upscaler: avoid unnecessary multi-pass
## Update for 2026-08-07
+3 -3
View File
@@ -20,13 +20,13 @@
"RealESRGAN": [
["4x General V3", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth"],
["4x General WDN V3", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-wdn-x4v3.pth"],
["AnimeVideo V3", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-animevideov3.pth"],
["4x AnimeVideo V3", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-animevideov3.pth"],
["4x+ Anime6B", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth"],
["4x+", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth"],
["2x+", "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth"]
],
"SCUNet": [
["GAN", "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_gan.pth"],
["PSNR", "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_psnr.pth"]
["GAN 1x", "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_gan.pth"],
["PSNR 1x", "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_psnr.pth"]
]
}
+1 -1
View File
@@ -145,7 +145,7 @@ class UpscalerESRGAN(Upscaler):
log.debug(f"Upscaler cached: type={self.name} model={info.local_data_path}")
return self.models[info.local_data_path]
state_dict = torch.load(info.local_data_path, map_location='cpu' if devices.device.type in {'mps', 'cpu'} else None)
log.info(f"Upscaler loaded: type={self.name} model={info.local_data_path}")
log.info(f'Upscaler loaded: type="{self.name}" model="{info.local_data_path}"')
if "params_ema" in state_dict:
state_dict = state_dict["params_ema"]
+21 -20
View File
@@ -29,7 +29,7 @@ class UpscalerRealESRGAN(Upscaler):
scaler.model = lambda: SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
elif scaler.name == 'RealESRGAN 4x General WDN V3':
scaler.model = lambda: SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
elif scaler.name == 'RealESRGAN AnimeVideo V3':
elif scaler.name == 'RealESRGAN 4x AnimeVideo V3':
scaler.model=lambda: SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=16, upscale=4, act_type='prelu')
elif scaler.name == 'RealESRGAN 4x+':
scaler.model = lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
@@ -47,27 +47,28 @@ class UpscalerRealESRGAN(Upscaler):
except Exception:
log.error("Error importing Real-ESRGAN:")
return img
info = self.find_model(selected_model)
if info is None or not os.path.exists(info.local_data_path):
model = self.find_model(selected_model)
if model is None or not os.path.exists(model.local_data_path):
return img
if self.models.get(info.local_data_path, None) is not None:
log.debug(f"Upscaler cached: type={self.name} model={info.local_data_path}")
upsampler=self.models[info.local_data_path]
if self.models.get(model.local_data_path, None) is not None:
log.debug(f"Upscaler cached: type={self.name} model={model.local_data_path}")
upsampler=self.models[model.local_data_path]
else:
upsampler = RealESRGANer(
name=info.name,
scale=info.scale,
model_path=info.local_data_path,
model=info.model(),
half=not opts.no_half and not opts.upcast_sampling,
tile=opts.upscaler_tile_size,
tile_pad=opts.upscaler_tile_overlap,
device=device,
)
self.models[info.local_data_path] = upsampler
upsampled = upsampler.enhance(np.array(img), outscale=info.scale)[0]
if opts.upscaler_unload and info.local_data_path in self.models:
del self.models[info.local_data_path]
kwargs = {
'name': model.name,
'scale': model.scale,
'model_path': model.local_data_path,
'model': model.model(),
'half': not opts.no_half and not opts.upcast_sampling,
'tile': opts.upscaler_tile_size,
'tile_pad': opts.upscaler_tile_overlap,
'device': device,
}
upsampler = RealESRGANer(**kwargs)
self.models[model.local_data_path] = upsampler
upsampled = upsampler.enhance(np.array(img), outscale=model.scale)[0]
if opts.upscaler_unload and model.local_data_path in self.models:
del self.models[model.local_data_path]
log.debug(f"Upscaler unloaded: type={self.name} model={selected_model}")
devices.torch_gc(force=True)
+1 -1
View File
@@ -67,7 +67,7 @@ class RealESRGANer:
from modules.modelloader import load_file_from_url
model_path = load_file_from_url(url=model_path, model_dir=os.path.join(ROOT_DIR, 'weights'), progress=True, file_name=None)
loadnet = torch.load(model_path, map_location=torch.device('cpu'))
log.info(f"Upscaler loaded: type={self.name} model={model_path}")
log.info(f'Upscaler loaded: type="{self.name}" model="{model_path}"')
# prefer to use params_ema
if 'params_ema' in loadnet:
+6 -3
View File
@@ -27,7 +27,7 @@ class UpscalerSCUNet(Upscaler):
model = net(in_nc=3, config=[4, 4, 4, 4, 4, 4, 4], dim=64)
model.load_state_dict(torch.load(info.local_data_path), strict=True)
model.eval()
log.info(f"Upscaler loaded: type={self.name} model={info.local_data_path}")
log.info(f'Upscaler loaded: type="{self.name}" model="{info.local_data_path}"')
for _, v in model.named_parameters():
v.requires_grad = False
model = model.to(devices.device)
@@ -41,9 +41,11 @@ class UpscalerSCUNet(Upscaler):
# test the image tile by tile
h, w = img.shape[2:]
tile = opts.upscaler_tile_size
tile = 0
tile_overlap = opts.upscaler_tile_overlap
if tile == 0:
return model(img)
output = model(img)
return output
assert tile % 8 == 0, "tile size should be a multiple of window_size"
sf = 1
stride = tile - tile_overlap
@@ -73,6 +75,7 @@ class UpscalerSCUNet(Upscaler):
model = self.load_model(selected_file)
if model is None:
return img
sf = 1
tile = opts.upscaler_tile_size
h, w = img.height, img.width
np_img = np.array(img)
@@ -84,7 +87,7 @@ class UpscalerSCUNet(Upscaler):
_img[:, :, :h, :w] = torch_img # pad image
torch_img = _img
torch_output = self.tiled_inference(torch_img, model).squeeze(0)
torch_output = torch_output[:, :h * 1, :w * 1] # remove padding, if any
torch_output = torch_output[:, :h * sf, :w * sf] # remove padding, if any
np_output: np.ndarray = torch_output.float().cpu().clamp_(0, 1).numpy()
del torch_img, torch_output
devices.torch_gc()
+2 -2
View File
@@ -58,12 +58,12 @@ class UpscalerSwinIR(Upscaler):
model.load_state_dict(pretrained_model[param], strict=True)
else:
model.load_state_dict(pretrained_model, strict=True)
log.info(f"Upscaler loaded: type={self.name} model={info.local_data_path} param={param}")
log.info(f'Upscaler loaded: type="{self.name}" model="{info.local_data_path}" param="{param}"')
model = compile_upscaler(model)
self.models[info.local_data_path] = model
return model
except Exception as e:
log.error(f'Upscaler invalid parameters: type={self.name} model={info.local_data_path} {e}')
log.error(f'Upscaler invalid parameters: type="{self.name}" model="{info.local_data_path}" error="{e}"')
return model
def do_upscale(self, img, selected_model): # pylint: disable=arguments-differ
+13 -14
View File
@@ -38,7 +38,7 @@ class Upscaler:
self.device = shared.device
self.img = None
self.output = None
self.scale = 1
self.scale = 4
self.half = not shared.cmd_opts.no_half
self.pre_pad = 0
self.mod_scale = None
@@ -116,11 +116,11 @@ class Upscaler:
for _ in range(3):
shape = (img.width, img.height)
img = self.do_upscale(img, selected_model)
if shape == (img.width, img.height):
if shape == (img.width, img.height): # no change, no point of running another iteration
break
if img.width >= (dest_w - 8) and img.height >= (dest_h - 8):
if (abs(img.width - dest_w) <= 12 or img.width >= dest_w) or (abs(img.height - dest_h) <= 12 or img.height >= dest_h): # close enough, do not run one more iteration
break
if img.width != dest_w or img.height != dest_h:
if abs(img.width - dest_w) > 8 or abs(img.height - dest_h) > 8:
from modules.image import sharpfin
img = sharpfin.resize(img, (int(dest_w), int(dest_h)))
shared.state.end(jobid)
@@ -158,30 +158,29 @@ class UpscalerData:
custom: bool = False
name = None
data_path = None
scale: int = 1
scale: int = 2
scaler: Upscaler | None = None
model: None
def __init__(self, name: str, path: str | None = None, upscaler: Upscaler | None = None, scale: int = 1, model=None):
def __init__(self, name: str, path: str | None = None, upscaler: Upscaler | None = None, scale: int = 0, model=None):
self.name = name
self.data_path = path
self.local_data_path = path
self.scaler = upscaler
if scale > 0:
self.scale = scale
elif '2x' in name.lower():
elif '1x' in name.lower() or 'x1' in name.lower():
self.scale = 1
elif '2x' in name.lower() or 'x2' in name.lower():
self.scale = 2
elif '3x' in name.lower():
elif '3x' in name.lower() or 'x3' in name.lower():
self.scale = 3
elif '4x' in name.lower():
elif '4x' in name.lower() or 'x4' in name.lower():
self.scale = 4
elif '4x' in name.lower():
self.scale = 4
elif '8x' in name.lower():
elif '8x' in name.lower() or 'x8' in name.lower():
self.scale = 8
else:
self.scale = 1
self.scale = scale
self.scale = 2 # default scale to 2 if not specified
self.model = model
def __str__(self):
+4 -4
View File
@@ -13,7 +13,7 @@ MODELS = {
"Spandrel 2x RealPLKSR AnimeSharpV2": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/2x-AnimeSharpV2_RPLKSR_Sharp.pth",
"Spandrel 2x RealESRGAN Compact": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/RealESRGAN-2x-Compact.pth",
"Spandrel 2x RealESRGAN UltraCompact": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/RealESRGAN-2x-UltraCompact.pth",
"Spandrel 2x RealSAFMN++": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/Real-SAFMN++.pth",
"Spandrel 4x RealSAFMN++": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/Real-SAFMN++.pth",
"Spandrel 2x RealSAFMN": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/Real-SAFMN-x2.pth",
"Spandrel 4x RealSAFMN": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/Real-SAFMN-x4-v2.pth",
"Spandrel 2x SAFMN PureScale": "https://huggingface.co/vladmandic/sdnext-upscalers/resolve/main/2x_SAFMN_PureScale.pth",
@@ -37,9 +37,9 @@ class UpscalerSpandrel(Upscaler):
scaler = UpscalerData(name=k, path=v, upscaler=self)
self.scalers.append(scaler)
else:
for s in self.scalers: # update name of existing scaler if it was found
if os.path.basename(s.data_path) == fn:
s.name = k
for i, _s in enumerate(self.scalers): # update name of existing scaler if it was found
if os.path.basename(self.scalers[i].data_path) == fn:
self.scalers[i].name = k
break
def process(self, img: Image.Image, output_type='pil', quiet=False):
+3 -3
View File
@@ -11,8 +11,8 @@ class UpscalerAsymmetricVAE(Upscaler):
self.vae = None
self.selected = None
self.scalers = [
UpscalerData("Asymmetric VAE v1", None, self),
UpscalerData("Asymmetric VAE v2", None, self),
UpscalerData("Asymmetric VAE 2x v1", None, self),
UpscalerData("Asymmetric VAE 2x v2", None, self),
]
def do_upscale(self, img: Image.Image, selected_model=None): # pylint: disable=arguments-differ
@@ -52,7 +52,7 @@ class UpscalerWanUpscale(Upscaler):
self.vae_decode = None
self.selected = None
self.scalers = [
UpscalerData("WAN Asymmetric Upscale", None, self),
UpscalerData("WAN Asymmetric Upscale 2x", None, self),
]
def do_upscale(self, img: Image.Image, selected_model=None): # pylint: disable=arguments-differ