Upload encoder/code/k3_sheet.py with huggingface_hub
Browse files- encoder/code/k3_sheet.py +197 -0
encoder/code/k3_sheet.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""K3 sheet - four renders per photo, so quality is looked at instead of argued about.
|
| 2 |
+
|
| 3 |
+
The acceptance test we never ran: put the teacher's 20-step result next to what our
|
| 4 |
+
chain produces for the same photo, and look. Numbers like "lat cos 0.84" are a proxy
|
| 5 |
+
for this image, and they were made on cosplay studio photos, not on the photos people
|
| 6 |
+
actually drop in.
|
| 7 |
+
|
| 8 |
+
Four PLYs per input, all through the teacher's own decoder so the only differences are
|
| 9 |
+
the ones we care about:
|
| 10 |
+
|
| 11 |
+
t20 teacher conditions, 20 steps, its own sampler (guidance 3, shift 3) <- truth
|
| 12 |
+
t2 teacher conditions, 2 steps, guidance 3 <- cost of fewer steps
|
| 13 |
+
s2 student conditions, 2 steps, guidance 3 <- cost of our encoder
|
| 14 |
+
s2g student conditions, 2 steps, guidance 1, shift 1 <- what the browser runs
|
| 15 |
+
|
| 16 |
+
Also written per image: the canvas the student encoder saw (1024^2, black), the alpha
|
| 17 |
+
the student matte produced, and the teacher's own prepared image. When a sheet cell is
|
| 18 |
+
fog, one of those three says which half broke.
|
| 19 |
+
|
| 20 |
+
Run it from the k3 driver (stage "sheet"), or directly:
|
| 21 |
+
FLIP_WORK=/kaggle/working python3 k3_sheet.py /kaggle/working/eval
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
import importlib.util
|
| 25 |
+
import json
|
| 26 |
+
import os
|
| 27 |
+
import pathlib
|
| 28 |
+
import sys
|
| 29 |
+
import tarfile
|
| 30 |
+
import time
|
| 31 |
+
|
| 32 |
+
WORK = pathlib.Path(os.environ.get("FLIP_WORK", "/kaggle/working"))
|
| 33 |
+
K3 = WORK / "k3_encoder_distill.py"
|
| 34 |
+
IMGDIR = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else str(WORK / "eval"))
|
| 35 |
+
OUT = WORK / "k3" / "sheet"
|
| 36 |
+
OUT.mkdir(parents=True, exist_ok=True)
|
| 37 |
+
N_GAUSS = 65536
|
| 38 |
+
|
| 39 |
+
os.environ.setdefault("FLIP_WORK", str(WORK))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def p(*a):
|
| 43 |
+
print(f"[{time.strftime('%H:%M:%S')}]", *a, flush=True)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def load_k3():
|
| 47 |
+
spec = importlib.util.spec_from_file_location("k3", str(K3))
|
| 48 |
+
mod = importlib.util.module_from_spec(spec)
|
| 49 |
+
spec.loader.exec_module(mod)
|
| 50 |
+
return mod
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def pad2(f):
|
| 54 |
+
import torch # noqa: PLC0415
|
| 55 |
+
z = torch.zeros(f.shape[0], 5, f.shape[2], dtype=f.dtype, device=f.device)
|
| 56 |
+
return torch.cat([z, f], dim=1)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def stats(g):
|
| 60 |
+
return {
|
| 61 |
+
"n": int(g.get_xyz.shape[0]),
|
| 62 |
+
"opacity": round(float(g.get_opacity.detach().float().mean()), 4),
|
| 63 |
+
"scale": round(float(g.get_scaling.detach().float().mean()), 5),
|
| 64 |
+
"x": [round(float(g.get_xyz[:, 0].min()), 3),
|
| 65 |
+
round(float(g.get_xyz[:, 0].max()), 3)],
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main():
|
| 70 |
+
import numpy as np # noqa: PLC0415
|
| 71 |
+
import torch # noqa: PLC0415
|
| 72 |
+
import torch.nn.functional as F # noqa: PLC0415
|
| 73 |
+
from PIL import Image # noqa: PLC0415
|
| 74 |
+
from torchvision import transforms # noqa: PLC0415
|
| 75 |
+
|
| 76 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 77 |
+
p("device", dev, "torch", torch.__version__)
|
| 78 |
+
k3 = load_k3()
|
| 79 |
+
sys.path.insert(0, str(k3.REPO))
|
| 80 |
+
import triposplat as T # noqa: PLC0415
|
| 81 |
+
|
| 82 |
+
imgs = sorted(q for q in IMGDIR.rglob("*")
|
| 83 |
+
if q.suffix.lower() in (".png", ".jpg", ".jpeg", ".webp"))
|
| 84 |
+
limit = int(os.environ.get("FLIP_SHEET_N", "0"))
|
| 85 |
+
if limit:
|
| 86 |
+
imgs = imgs[:limit]
|
| 87 |
+
if not imgs:
|
| 88 |
+
raise SystemExit(f"no images under {IMGDIR}")
|
| 89 |
+
p("images", len(imgs), [q.name for q in imgs])
|
| 90 |
+
|
| 91 |
+
t0 = time.time()
|
| 92 |
+
pipe = k3.build_teacher_pipeline(T, dev)
|
| 93 |
+
p("teacher pipeline", round(time.time() - t0, 1), "s")
|
| 94 |
+
dino_s = k3.load_weights(k3.build_dino(), "dino", dev)
|
| 95 |
+
vae_s = k3.load_weights(k3.build_vae(), "vae", dev)
|
| 96 |
+
matte_s = k3.load_weights(k3.build_matte(), "matte", dev)
|
| 97 |
+
p("student encoders loaded")
|
| 98 |
+
|
| 99 |
+
report = []
|
| 100 |
+
for i, path in enumerate(imgs):
|
| 101 |
+
stem = path.stem
|
| 102 |
+
row = {"image": path.name, "stem": stem, "runs": {}}
|
| 103 |
+
try:
|
| 104 |
+
t_img = time.time()
|
| 105 |
+
image = Image.open(path).convert("RGB")
|
| 106 |
+
row["size"] = list(image.size)
|
| 107 |
+
|
| 108 |
+
# --- teacher path: its own preprocess, its own encoder
|
| 109 |
+
prepared = pipe.preprocess_image(image)
|
| 110 |
+
prepared.save(OUT / f"{stem}_prepared.png")
|
| 111 |
+
row["prepared"] = list(prepared.size)
|
| 112 |
+
gen = torch.Generator(device=dev).manual_seed(42)
|
| 113 |
+
cond_t = pipe.encode_image(prepared, generator=gen)
|
| 114 |
+
|
| 115 |
+
# --- student path: mirror of site/src/pipeline.js loadCondition
|
| 116 |
+
rgb = k3.resize_min_side(image)
|
| 117 |
+
w, h = rgb.size
|
| 118 |
+
xin = transforms.ToTensor()(rgb).unsqueeze(0).to(dev)
|
| 119 |
+
with torch.no_grad():
|
| 120 |
+
a = matte_s(xin)[0, 0].float().cpu().numpy()
|
| 121 |
+
alpha = (np.clip(a, 0, 1) * 255).astype(np.uint8)
|
| 122 |
+
Image.fromarray(alpha, "L").save(OUT / f"{stem}_mask.png")
|
| 123 |
+
er = np.array(k3.erode_alpha(alpha, 1), dtype=np.uint8)
|
| 124 |
+
box = k3.square_crop_box(er)
|
| 125 |
+
rgba = rgb.convert("RGBA")
|
| 126 |
+
rgba.putalpha(Image.fromarray(alpha, "L"))
|
| 127 |
+
cropped = rgba.crop(box).resize((k3.CANVAS, k3.CANVAS), Image.LANCZOS)
|
| 128 |
+
canvas = Image.new("RGB", (k3.CANVAS, k3.CANVAS), (0, 0, 0))
|
| 129 |
+
canvas.paste(cropped, mask=cropped.split()[3])
|
| 130 |
+
canvas.save(OUT / f"{stem}_canvas.png")
|
| 131 |
+
fx = alpha.astype(np.float32) / 255.0
|
| 132 |
+
row["mask"] = {
|
| 133 |
+
"resized": [w, h],
|
| 134 |
+
"box": [int(v) for v in box],
|
| 135 |
+
"fill": round(float(fx.mean()), 4),
|
| 136 |
+
"coverage": round(float((fx > 0.5).mean()), 4),
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
x = transforms.ToTensor()(canvas).unsqueeze(0).to(dev)
|
| 140 |
+
dino_dev = next(pipe.dinov3.parameters()).device
|
| 141 |
+
dino_dt = next(pipe.dinov3.parameters()).dtype
|
| 142 |
+
vae_dev = next(pipe.vae_encoder.parameters()).device
|
| 143 |
+
vae_dt = next(pipe.vae_encoder.parameters()).dtype
|
| 144 |
+
with torch.no_grad():
|
| 145 |
+
sf1 = dino_s(x)
|
| 146 |
+
sf2 = vae_s(x * 2 - 1)
|
| 147 |
+
# teacher features on the SAME canvas: isolates encoder error from crop
|
| 148 |
+
tf1 = F.layer_norm(pipe.dinov3(
|
| 149 |
+
pixel_values=T._DINOV3_NORMALIZE(x).to(dino_dev, dino_dt)
|
| 150 |
+
).float(), (1280,))
|
| 151 |
+
tf2 = pipe.vae_encoder.encode(
|
| 152 |
+
(x * 2 - 1).to(vae_dev, vae_dt), deterministic=True)
|
| 153 |
+
row["cos1"] = round(float(F.cosine_similarity(sf1, tf1, dim=-1).mean()), 4)
|
| 154 |
+
row["cos2"] = round(float(F.cosine_similarity(sf2, tf2, dim=-1).mean()), 4)
|
| 155 |
+
p(stem, "cos", row["cos1"], row["cos2"], "mask",
|
| 156 |
+
row["mask"]["coverage"], "box", row["mask"]["box"])
|
| 157 |
+
|
| 158 |
+
cond_s = {"feature1": sf1, "feature2": pad2(sf2)}
|
| 159 |
+
variants = [
|
| 160 |
+
("t20", cond_t, 20, 3.0, 3.0),
|
| 161 |
+
("t2", cond_t, 2, 3.0, 3.0),
|
| 162 |
+
("s2", cond_s, 2, 3.0, 3.0),
|
| 163 |
+
("s2g", cond_s, 2, 1.0, 1.0),
|
| 164 |
+
]
|
| 165 |
+
for tag, cond, steps, gs, shift in variants:
|
| 166 |
+
t1 = time.time()
|
| 167 |
+
gen = torch.Generator(device=dev).manual_seed(42)
|
| 168 |
+
lat = pipe.sample_latent(cond, steps=steps, guidance_scale=gs,
|
| 169 |
+
shift=shift, generator=gen)
|
| 170 |
+
g = pipe.decode_latent(lat["latent"], num_gaussians=N_GAUSS)
|
| 171 |
+
ply = OUT / f"{stem}_{tag}.ply"
|
| 172 |
+
g.save_ply(str(ply))
|
| 173 |
+
row["runs"][tag] = {
|
| 174 |
+
**stats(g), "ply": ply.name,
|
| 175 |
+
"mb": round(ply.stat().st_size / 1e6, 2),
|
| 176 |
+
"secs": round(time.time() - t1, 1),
|
| 177 |
+
}
|
| 178 |
+
p(f" {tag:4s} {row['runs'][tag]['secs']:6.1f}s "
|
| 179 |
+
f"op {row['runs'][tag]['opacity']} sc {row['runs'][tag]['scale']}")
|
| 180 |
+
row["secs"] = round(time.time() - t_img, 1)
|
| 181 |
+
except Exception as exc: # noqa: BLE001
|
| 182 |
+
import traceback # noqa: PLC0415
|
| 183 |
+
row["error"] = traceback.format_exc()[-2000:]
|
| 184 |
+
p(stem, "FAILED", type(exc).__name__, str(exc)[:200])
|
| 185 |
+
report.append(row)
|
| 186 |
+
(OUT / "report.json").write_text(json.dumps(report, indent=2))
|
| 187 |
+
|
| 188 |
+
tgz = OUT / "sheet.tgz"
|
| 189 |
+
with tarfile.open(tgz, "w:gz") as tf:
|
| 190 |
+
for f in sorted(OUT.iterdir()):
|
| 191 |
+
if f.name != tgz.name:
|
| 192 |
+
tf.add(f, arcname=f.name)
|
| 193 |
+
p("wrote", tgz, round(tgz.stat().st_size / 1e6, 1), "MB")
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
if __name__ == "__main__":
|
| 197 |
+
main()
|