pbc3: restore PIL resampling and fix benchmarks
Browse files- .gitignore +3 -1
- PBC3.py +20 -7
- PBC3_animation.py +3 -2
- pbc3_benchmark.py +2 -1
- pbc3_heads.py +3 -1
- pbc3_ops.py +8 -50
- pbc3_quick_rd.py +8 -5
- server.py +2 -2
.gitignore
CHANGED
|
@@ -13,4 +13,6 @@ hpt_data_val/
|
|
| 13 |
*.pt
|
| 14 |
training_space/results/
|
| 15 |
training_space/runs/*
|
| 16 |
-
!training_space/runs/RL_checkpoint.pt
|
|
|
|
|
|
|
|
|
| 13 |
*.pt
|
| 14 |
training_space/results/
|
| 15 |
training_space/runs/*
|
| 16 |
+
!training_space/runs/RL_checkpoint.pt
|
| 17 |
+
pbc3_resampling_benchmark.*
|
| 18 |
+
pbc3_resampling_top_variants_20.*
|
PBC3.py
CHANGED
|
@@ -162,7 +162,10 @@ class PBC3:
|
|
| 162 |
return canvas
|
| 163 |
out = np.empty((new_h, new_w, ch), dtype=np.int32)
|
| 164 |
for c in range(ch):
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
| 166 |
return out
|
| 167 |
|
| 168 |
@classmethod
|
|
@@ -477,12 +480,22 @@ class PBC3:
|
|
| 477 |
return image
|
| 478 |
|
| 479 |
|
| 480 |
-
def preload_numba() -> None:
|
| 481 |
-
"""##
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
|
| 487 |
|
| 488 |
if __name__ == "__main__":
|
|
|
|
| 162 |
return canvas
|
| 163 |
out = np.empty((new_h, new_w, ch), dtype=np.int32)
|
| 164 |
for c in range(ch):
|
| 165 |
+
layer_arr = np.ascontiguousarray(canvas[:, :, c], dtype=np.float32)
|
| 166 |
+
layer = Image.frombuffer("F", (w, h), layer_arr, "raw", "F", 0, 1)
|
| 167 |
+
layer = layer.resize((new_w, new_h), cls.RESAMPLE_FILTER)
|
| 168 |
+
out[:, :, c] = np.rint(np.asarray(layer, dtype=np.float32)).astype(np.int32)
|
| 169 |
return out
|
| 170 |
|
| 171 |
@classmethod
|
|
|
|
| 480 |
return image
|
| 481 |
|
| 482 |
|
| 483 |
+
def preload_numba(model_path: str = "patch_policy.npz") -> None:
|
| 484 |
+
"""## Warms the production learned RGB/RGBA encode paths."""
|
| 485 |
+
h, w = 512, 768
|
| 486 |
+
base = np.arange(h * w * 4, dtype=np.uint32).reshape(h, w, 4)
|
| 487 |
+
config = PBC3Config.quality(
|
| 488 |
+
patch_count=50,
|
| 489 |
+
learned_filler_enabled=True,
|
| 490 |
+
learned_filler_model_path=model_path,
|
| 491 |
+
auto_downsample_max_pixels=250_000,
|
| 492 |
+
use_lzma=True,
|
| 493 |
+
compute_final_mse=True,
|
| 494 |
+
)
|
| 495 |
+
for channels in (3, 4):
|
| 496 |
+
arr = ((base[:, :, :channels] * 37 + channels * 19) % 256).astype(np.uint8)
|
| 497 |
+
PBC3.compress(Image.fromarray(arr), config=config)
|
| 498 |
+
print("[preload] production PBC3 paths warmed")
|
| 499 |
|
| 500 |
|
| 501 |
if __name__ == "__main__":
|
PBC3_animation.py
CHANGED
|
@@ -2,6 +2,7 @@ import os
|
|
| 2 |
import numpy as np
|
| 3 |
from PIL import Image, ImageDraw, ImageFont
|
| 4 |
from PBC3 import PBC3, BitReader
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
def _font(size):
|
|
@@ -345,7 +346,7 @@ def animate_pbc3(
|
|
| 345 |
|
| 346 |
tc = target.shape[2] if target is not None else 0
|
| 347 |
prev_mse = float(np.mean((canvas[:, :, :tc] - target) ** 2)) if target is not None else None
|
| 348 |
-
|
| 349 |
now_mse = float(np.mean((canvas[:, :, :tc] - target) ** 2)) if target is not None else None
|
| 350 |
parts = [f"Patch {i}/{patch_count}", f"Stream Size: {current_kb:.2f} KB", f"(+{delta_kb:.2f} KB)"]
|
| 351 |
if now_mse is not None:
|
|
@@ -364,4 +365,4 @@ def animate_pbc3(
|
|
| 364 |
output_size,
|
| 365 |
))
|
| 366 |
|
| 367 |
-
return _write_frames(frames, output_path, fps, fallback_to_gif=fallback_to_gif)
|
|
|
|
| 2 |
import numpy as np
|
| 3 |
from PIL import Image, ImageDraw, ImageFont
|
| 4 |
from PBC3 import PBC3, BitReader
|
| 5 |
+
import pbc3_ops as ops
|
| 6 |
|
| 7 |
|
| 8 |
def _font(size):
|
|
|
|
| 346 |
|
| 347 |
tc = target.shape[2] if target is not None else 0
|
| 348 |
prev_mse = float(np.mean((canvas[:, :, :tc] - target) ** 2)) if target is not None else None
|
| 349 |
+
ops.apply_grid(canvas[:, :, channel], x, y, pw, ph, cell_size, values)
|
| 350 |
now_mse = float(np.mean((canvas[:, :, :tc] - target) ** 2)) if target is not None else None
|
| 351 |
parts = [f"Patch {i}/{patch_count}", f"Stream Size: {current_kb:.2f} KB", f"(+{delta_kb:.2f} KB)"]
|
| 352 |
if now_mse is not None:
|
|
|
|
| 365 |
output_size,
|
| 366 |
))
|
| 367 |
|
| 368 |
+
return _write_frames(frames, output_path, fps, fallback_to_gif=fallback_to_gif)
|
pbc3_benchmark.py
CHANGED
|
@@ -9,7 +9,7 @@ import time
|
|
| 9 |
import numpy as np
|
| 10 |
from PIL import Image, ImageOps
|
| 11 |
|
| 12 |
-
from PBC3 import PBC3
|
| 13 |
from pbc3_types import PBC3Config
|
| 14 |
|
| 15 |
try:
|
|
@@ -216,6 +216,7 @@ def _run(codecs, n_trials):
|
|
| 216 |
images = dataset()
|
| 217 |
if not images:
|
| 218 |
raise RuntimeError("No images found in hpt_data/")
|
|
|
|
| 219 |
jobs = _jobs(codecs)
|
| 220 |
total = n_trials * len(jobs) * len(images)
|
| 221 |
run_id = time.strftime("%Y%m%d_%H%M%S")
|
|
|
|
| 9 |
import numpy as np
|
| 10 |
from PIL import Image, ImageOps
|
| 11 |
|
| 12 |
+
from PBC3 import PBC3, preload_numba
|
| 13 |
from pbc3_types import PBC3Config
|
| 14 |
|
| 15 |
try:
|
|
|
|
| 216 |
images = dataset()
|
| 217 |
if not images:
|
| 218 |
raise RuntimeError("No images found in hpt_data/")
|
| 219 |
+
preload_numba(os.path.join(PROJECT_DIR, "patch_policy.npz"))
|
| 220 |
jobs = _jobs(codecs)
|
| 221 |
total = n_trials * len(jobs) * len(images)
|
| 222 |
run_id = time.strftime("%Y%m%d_%H%M%S")
|
pbc3_heads.py
CHANGED
|
@@ -153,7 +153,9 @@ class FillerHead:
|
|
| 153 |
hidden_residual = target[y:y + bh, x:x + bw, c] - canvas[y:y + bh, x:x + bw, c]
|
| 154 |
before = target[y:y + bh, x:x + bw, c] - np.clip(canvas[y:y + bh, x:x + bw, c], 0, 255)
|
| 155 |
before_sse = float(np.sum(before.astype(np.int64) ** 2))
|
| 156 |
-
patch, values = ops.make_patch(
|
|
|
|
|
|
|
| 157 |
delta = ops.signed_resample(values, bh, bw).astype(np.int32)
|
| 158 |
after = target[y:y + bh, x:x + bw, c] - np.clip(canvas[y:y + bh, x:x + bw, c] + delta, 0, 255)
|
| 159 |
reduction = before_sse - float(np.sum(after.astype(np.int64) ** 2))
|
|
|
|
| 153 |
hidden_residual = target[y:y + bh, x:x + bw, c] - canvas[y:y + bh, x:x + bw, c]
|
| 154 |
before = target[y:y + bh, x:x + bw, c] - np.clip(canvas[y:y + bh, x:x + bw, c], 0, 255)
|
| 155 |
before_sse = float(np.sum(before.astype(np.int64) ** 2))
|
| 156 |
+
patch, values = ops.make_patch(
|
| 157 |
+
c, x, y, bw, bh, cell_size, hidden_residual, config, bitcount,
|
| 158 |
+
)
|
| 159 |
delta = ops.signed_resample(values, bh, bw).astype(np.int32)
|
| 160 |
after = target[y:y + bh, x:x + bw, c] - np.clip(canvas[y:y + bh, x:x + bw, c] + delta, 0, 255)
|
| 161 |
reduction = before_sse - float(np.sum(after.astype(np.int64) ** 2))
|
pbc3_ops.py
CHANGED
|
@@ -12,7 +12,6 @@ PALETTE_GENERATED = 0
|
|
| 12 |
|
| 13 |
_UINT64_MASK = (1 << 64) - 1
|
| 14 |
_UINT32_SCALE = 1.0 / 4294967296.0
|
| 15 |
-
_RESAMPLE_COEFF_CACHE = {}
|
| 16 |
|
| 17 |
|
| 18 |
@njit(inline="always", cache=True)
|
|
@@ -49,58 +48,17 @@ class PBC3Rng:
|
|
| 49 |
return low + self.next_uint32() % (high - low)
|
| 50 |
|
| 51 |
|
| 52 |
-
def _bicubic_kernel(x):
|
| 53 |
-
x = np.abs(np.asarray(x, dtype=np.float64))
|
| 54 |
-
out = np.zeros_like(x)
|
| 55 |
-
m1 = x < 1.0
|
| 56 |
-
m2 = (x >= 1.0) & (x < 2.0)
|
| 57 |
-
x1 = x[m1]
|
| 58 |
-
x2 = x[m2]
|
| 59 |
-
a = -0.5
|
| 60 |
-
out[m1] = ((a + 2.0) * x1 - (a + 3.0)) * x1 * x1 + 1.0
|
| 61 |
-
out[m2] = (((x2 - 5.0) * x2 + 8.0) * x2 - 4.0) * a
|
| 62 |
-
return out
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
def _resample_coeffs(in_size: int, out_size: int) -> np.ndarray:
|
| 66 |
-
in_size, out_size = int(in_size), int(out_size)
|
| 67 |
-
key = (in_size, out_size)
|
| 68 |
-
coeffs = _RESAMPLE_COEFF_CACHE.get(key)
|
| 69 |
-
if coeffs is not None:
|
| 70 |
-
return coeffs
|
| 71 |
-
|
| 72 |
-
scale = in_size / out_size
|
| 73 |
-
filterscale = max(scale, 1.0)
|
| 74 |
-
support = 2.0 * filterscale
|
| 75 |
-
inv_filterscale = 1.0 / filterscale
|
| 76 |
-
coeffs = np.zeros((out_size, in_size), dtype=np.float64)
|
| 77 |
-
|
| 78 |
-
for xx in range(out_size):
|
| 79 |
-
center = (xx + 0.5) * scale
|
| 80 |
-
xmin = max(0, int(center - support + 0.5))
|
| 81 |
-
xmax = min(in_size, int(center + support + 0.5))
|
| 82 |
-
xs = np.arange(xmin, xmax, dtype=np.float64)
|
| 83 |
-
weights = _bicubic_kernel((xs - center + 0.5) * inv_filterscale)
|
| 84 |
-
total = weights.sum()
|
| 85 |
-
if total != 0.0:
|
| 86 |
-
weights /= total
|
| 87 |
-
coeffs[xx, xmin:xmax] = weights
|
| 88 |
-
|
| 89 |
-
_RESAMPLE_COEFF_CACHE[key] = coeffs
|
| 90 |
-
return coeffs
|
| 91 |
-
|
| 92 |
-
|
| 93 |
def bicubic_resample_2d(values, out_h: int, out_w: int) -> np.ndarray:
|
| 94 |
-
|
|
|
|
| 95 |
out_h, out_w = int(out_h), int(out_w)
|
| 96 |
if arr.shape == (out_h, out_w):
|
| 97 |
-
return arr.
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
return out
|
| 104 |
|
| 105 |
|
| 106 |
def ceil_div(a: int, b: int) -> int:
|
|
|
|
| 12 |
|
| 13 |
_UINT64_MASK = (1 << 64) - 1
|
| 14 |
_UINT32_SCALE = 1.0 / 4294967296.0
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
@njit(inline="always", cache=True)
|
|
|
|
| 48 |
return low + self.next_uint32() % (high - low)
|
| 49 |
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
def bicubic_resample_2d(values, out_h: int, out_w: int) -> np.ndarray:
|
| 52 |
+
"""Resamples a signed grid with the production PIL bicubic filter."""
|
| 53 |
+
arr = np.ascontiguousarray(values, dtype=np.float32)
|
| 54 |
out_h, out_w = int(out_h), int(out_w)
|
| 55 |
if arr.shape == (out_h, out_w):
|
| 56 |
+
return arr.astype(np.float64, copy=True)
|
| 57 |
+
return np.asarray(
|
| 58 |
+
Image.frombuffer("F", (arr.shape[1], arr.shape[0]), arr, "raw", "F", 0, 1)
|
| 59 |
+
.resize((out_w, out_h), Image.Resampling.BICUBIC),
|
| 60 |
+
dtype=np.float64,
|
| 61 |
+
)
|
|
|
|
| 62 |
|
| 63 |
|
| 64 |
def ceil_div(a: int, b: int) -> int:
|
pbc3_quick_rd.py
CHANGED
|
@@ -7,10 +7,10 @@ import time
|
|
| 7 |
|
| 8 |
import numpy as np
|
| 9 |
import optuna
|
| 10 |
-
from PIL import Image
|
| 11 |
|
| 12 |
import pbc3_sweep
|
| 13 |
-
from PBC3 import PBC3
|
| 14 |
from pbc3_types import PBC3Config
|
| 15 |
|
| 16 |
RESULTS_PATH = os.path.join(pbc3_sweep.PROJECT_DIR, "pbc3_quick_rd.json")
|
|
@@ -110,19 +110,21 @@ def _eval_old_preset_subprocess(name, images, trial):
|
|
| 110 |
code = r'''
|
| 111 |
import json, os, sys, time
|
| 112 |
import numpy as np
|
| 113 |
-
from PIL import Image
|
| 114 |
sys.path.insert(0, sys.argv[1])
|
| 115 |
from PBC3 import PBC3
|
| 116 |
try:
|
| 117 |
from pbc3_types import PBC3Config
|
| 118 |
except Exception:
|
| 119 |
from pbc_types import PBC3Config
|
|
|
|
| 120 |
preset = sys.argv[2]
|
| 121 |
trial = int(sys.argv[3])
|
| 122 |
images = json.loads(sys.stdin.read())
|
|
|
|
| 123 |
out = []
|
| 124 |
for im in images:
|
| 125 |
-
img = Image.open(im["path"]).convert("RGB")
|
| 126 |
arr = np.asarray(img)
|
| 127 |
pixels = arr.shape[0] * arr.shape[1]
|
| 128 |
cfg = getattr(PBC3Config, preset)()
|
|
@@ -154,6 +156,7 @@ def _run(n_trials, run_old):
|
|
| 154 |
images = pbc3_sweep.dataset()
|
| 155 |
if not images:
|
| 156 |
raise RuntimeError("No images found in hpt_data/")
|
|
|
|
| 157 |
_log(f"loaded {len(images)} images 路 {n_trials} trial(s)" + (" 路 live old enabled" if run_old else ""))
|
| 158 |
out = {"created": time.time(), "n_trials": n_trials, "presets": []}
|
| 159 |
for trial in range(1, n_trials + 1):
|
|
@@ -260,4 +263,4 @@ def results(mp_min=None, mp_max=None):
|
|
| 260 |
"new_exists": os.path.exists(RESULTS_PATH),
|
| 261 |
"old_pbc_exists": os.path.exists(os.path.join(OLD_PBC_DIR, "PBC3.py")),
|
| 262 |
"rows": old["rows"] + live,
|
| 263 |
-
}
|
|
|
|
| 7 |
|
| 8 |
import numpy as np
|
| 9 |
import optuna
|
| 10 |
+
from PIL import Image, ImageOps
|
| 11 |
|
| 12 |
import pbc3_sweep
|
| 13 |
+
from PBC3 import PBC3, preload_numba
|
| 14 |
from pbc3_types import PBC3Config
|
| 15 |
|
| 16 |
RESULTS_PATH = os.path.join(pbc3_sweep.PROJECT_DIR, "pbc3_quick_rd.json")
|
|
|
|
| 110 |
code = r'''
|
| 111 |
import json, os, sys, time
|
| 112 |
import numpy as np
|
| 113 |
+
from PIL import Image, ImageOps
|
| 114 |
sys.path.insert(0, sys.argv[1])
|
| 115 |
from PBC3 import PBC3
|
| 116 |
try:
|
| 117 |
from pbc3_types import PBC3Config
|
| 118 |
except Exception:
|
| 119 |
from pbc_types import PBC3Config
|
| 120 |
+
from PBC3 import preload_numba
|
| 121 |
preset = sys.argv[2]
|
| 122 |
trial = int(sys.argv[3])
|
| 123 |
images = json.loads(sys.stdin.read())
|
| 124 |
+
preload_numba()
|
| 125 |
out = []
|
| 126 |
for im in images:
|
| 127 |
+
img = ImageOps.exif_transpose(Image.open(im["path"])).convert("RGB")
|
| 128 |
arr = np.asarray(img)
|
| 129 |
pixels = arr.shape[0] * arr.shape[1]
|
| 130 |
cfg = getattr(PBC3Config, preset)()
|
|
|
|
| 156 |
images = pbc3_sweep.dataset()
|
| 157 |
if not images:
|
| 158 |
raise RuntimeError("No images found in hpt_data/")
|
| 159 |
+
preload_numba(os.path.join(pbc3_sweep.PROJECT_DIR, "patch_policy.npz"))
|
| 160 |
_log(f"loaded {len(images)} images 路 {n_trials} trial(s)" + (" 路 live old enabled" if run_old else ""))
|
| 161 |
out = {"created": time.time(), "n_trials": n_trials, "presets": []}
|
| 162 |
for trial in range(1, n_trials + 1):
|
|
|
|
| 263 |
"new_exists": os.path.exists(RESULTS_PATH),
|
| 264 |
"old_pbc_exists": os.path.exists(os.path.join(OLD_PBC_DIR, "PBC3.py")),
|
| 265 |
"rows": old["rows"] + live,
|
| 266 |
+
}
|
server.py
CHANGED
|
@@ -929,8 +929,8 @@ BOOT_ID = f"{time.time():.3f}"
|
|
| 929 |
|
| 930 |
@app.on_event("startup")
|
| 931 |
def _startup():
|
| 932 |
-
print("[startup]
|
| 933 |
-
preload_numba()
|
| 934 |
print("[startup] done.", flush=True)
|
| 935 |
print(f"[startup] BOOT_ID={BOOT_ID}", flush=True)
|
| 936 |
|
|
|
|
| 929 |
|
| 930 |
@app.on_event("startup")
|
| 931 |
def _startup():
|
| 932 |
+
print("[startup] warming production PBC3 paths...", flush=True)
|
| 933 |
+
preload_numba(os.path.join(_HERE, "patch_policy.npz"))
|
| 934 |
print("[startup] done.", flush=True)
|
| 935 |
print(f"[startup] BOOT_ID={BOOT_ID}", flush=True)
|
| 936 |
|