Spaces:
Running
Running
| """Reusable PBR inference helpers shared by the CLI and the Gradio demo.""" | |
| import json | |
| import os | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| from src.model import PBRUNet, category_to_index | |
| def default_ckpt_for_run(run_dir: str) -> str: | |
| """Prefer the EMA checkpoint; fall back to best.pt.""" | |
| ema = os.path.join(run_dir, "best_ema.pt") | |
| best = os.path.join(run_dir, "best.pt") | |
| if os.path.isfile(ema): | |
| return ema | |
| if os.path.isfile(best): | |
| return best | |
| raise FileNotFoundError(f"No best_ema.pt or best.pt in {run_dir}") | |
| def _build_from_args_json(run_dir: str, device) -> PBRUNet: | |
| with open(os.path.join(run_dir, "args.json")) as f: | |
| a = json.load(f) | |
| kwargs = dict( | |
| encoder_name=a.get("encoder", "resnet34"), | |
| encoder_weights=None, | |
| use_category=a.get("use_category", False), | |
| normal_xy_only=a.get("normal_xy", False), | |
| separate_normal_decoder=a.get("separate_normal_decoder", False), | |
| predict_height=a.get("predict_height", False), | |
| ) | |
| return PBRUNet(**kwargs).to(device) | |
| def load_pbr_model(run_dir: str, ckpt_path, device) -> PBRUNet: | |
| """Build a PBRUNet from run_dir/args.json and load its checkpoint.""" | |
| if ckpt_path is None: | |
| ckpt_path = default_ckpt_for_run(run_dir) | |
| model = _build_from_args_json(run_dir, device) | |
| state = torch.load(ckpt_path, weights_only=False, map_location=device) | |
| if isinstance(state, dict) and "model" in state: | |
| state = state["model"] | |
| model.load_state_dict(state) | |
| model.eval() | |
| return model | |
| def predict_maps(model: PBRUNet, image, category: str, device, size: int = 256) -> dict: | |
| """Run inference on a PIL image, returning CPU map tensors in [0,1].""" | |
| img = image.convert("RGB").resize((size, size), Image.Resampling.BICUBIC) | |
| arr = np.asarray(img, dtype=np.float32) / 255.0 | |
| basecolor = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0).to(device) | |
| cat = torch.tensor([category_to_index(category)], dtype=torch.long, device=device) | |
| with torch.no_grad(): | |
| preds = model(basecolor, category=cat) | |
| return { | |
| name: preds[name][0].clamp(0, 1).cpu() | |
| for name in ("normal", "roughness", "metallic") | |
| } | |
| def available_runs(models_root: str, allow=None) -> list[str]: | |
| """Sorted run names under models_root with args.json + a checkpoint.""" | |
| out = [] | |
| if not os.path.isdir(models_root): | |
| return out | |
| for name in os.listdir(models_root): | |
| run = os.path.join(models_root, name) | |
| if not os.path.isdir(run): | |
| continue | |
| if not os.path.isfile(os.path.join(run, "args.json")): | |
| continue | |
| has_ckpt = os.path.isfile(os.path.join(run, "best_ema.pt")) or os.path.isfile( | |
| os.path.join(run, "best.pt") | |
| ) | |
| if not has_ckpt: | |
| continue | |
| if allow is not None and name not in allow: | |
| continue | |
| out.append(name) | |
| return sorted(out) | |