upscale-nc / parity.py
arraypress's picture
MangaJPEGLQ as a 1× Core AI asset from the original weights
57e8f83 verified
Raw History Blame Contribute Delete
2.21 kB
# /// script
# requires-python = ">=3.11"
# dependencies = ["coreai-core==1.0.0b3", "coreai-torch==0.4.3", "spandrel==0.4.1", "numpy", "pillow"]
# [tool.uv]
# index-url = "https://pypi.org/simple"
# prerelease = "allow"
# index-strategy = "unsafe-best-match"
# ///
"""Core AI export vs the original PyTorch weights (float32), tile by tile, on stock photos."""
import asyncio, sys, time
import numpy as np, torch
from PIL import Image
from spandrel import ModelLoader
import coreai.runtime as rt
ckpt, path, tile = sys.argv[1], sys.argv[2], int(sys.argv[3])
images = sys.argv[4:]
desc = ModelLoader().load_from_file(ckpt)
net = desc.model.eval().float()
luma = torch.tensor([0.299, 0.587, 0.114]).view(1, 3, 1, 1)
def reference(x):
if desc.input_channels == 1: x = (x * luma).sum(dim=1, keepdim=True)
y = net(x)
if desc.output_channels == 1: y = y.repeat(1, 3, 1, 1)
return y.clamp(0, 1)
def psnr(a, b):
mse = float(np.mean((a.astype(np.float64) - b.astype(np.float64)) ** 2))
return 99.0 if mse == 0 else 10 * np.log10(1.0 / mse)
async def main():
rng = np.random.default_rng(0); tiles = []
for p in images:
im = np.asarray(Image.open(p).convert("RGB")).astype(np.float32) / 255
h, w, _ = im.shape
for _ in range(3):
y = int(rng.integers(0, max(1, h - tile))); x = int(rng.integers(0, max(1, w - tile)))
c = im[y:y+tile, x:x+tile]
if c.shape[:2] == (tile, tile): tiles.append(c)
m = await rt.AIModel.load(path)
fn = m.load_function("main")
scores, times = [], []
for c in tiles:
x = torch.from_numpy(c).permute(2, 0, 1)[None].contiguous()
with torch.no_grad(): ref = reference(x)[0].permute(1, 2, 0).numpy()
t = time.time()
out = await fn({"x": rt.NDArray(data=x.half().contiguous())})
times.append(time.time() - t)
got = out["output"].numpy().astype(np.float32)[0].transpose(1, 2, 0)
scores.append(psnr(ref, got))
print(f"{path.split('/')[-1]:34} scale x{desc.scale} PSNR vs PyTorch fp32 over {len(scores)} tiles: "
f"min {min(scores):.1f} dB, mean {np.mean(scores):.1f} dB; {np.median(times)*1000:.0f} ms/tile")
asyncio.run(main())