So the unified PaGeR checkpoint emits
both the SI depth map and the metric depth map in one shot: the dense head fixes geometry, the scale head fixes absolute scale. If you only need metric depth and don't want to manage the indoor/outdoor scale routing, the depth-only
prs-eth/PaGeR-metric-depth checkpoint predicts metric depth directly in a single head.
Indoor and outdoor scenes are served by twin scale heads, so a single checkpoint covers both regimes. The active head can be selected manually or routed automatically — see
Model Details below for how that routing is done at inference time.
You can also browse the rest of our
PaGeR HF collection or try the
interactive demo.
A minimal Python snippet that runs the unified model on a single panorama and produces metric depth, surface normals, and a sky mask in one forward pass. The snippet assumes you have
cloned the repository and
pip install -e . ed it, so that
src.pager is importable; checkpoint weights and config are streamed from the Hub on first use.
1import matplotlib.pyplot as plt
2import numpy as np
3import torch
4from huggingface_hub import hf_hub_download
5from omegaconf import OmegaConf
6from PIL import Image
7
8from src.pager import Pager
9from src.utils.geometry_utils import erp_to_cubemap
10from src.utils.utils import prepare_depth_for_logging, prepare_normals_for_logging
11
12checkpoint = "prs-eth/PaGeR" # or a local directory
13device = torch.device("cuda")
14
15# 1. Load the model config from the Hub and instantiate Pager.
16config_path = hf_hub_download(repo_id=checkpoint, filename="config.yaml")
17cfg = OmegaConf.load(config_path)
18
19pager = Pager(checkpoint, cfg=cfg, device=device)
20pager.get_intrinsics_extrinsics(image_size=cfg.face_size, fov=getattr(cfg, "cube_fov", 90.0))
21pager.model.to(device).eval()
22
23# 2. Load a panorama and project it to the 6-face cubemap PaGeR consumes.
24panorama = np.array(Image.open("assets/examples/apartment_synth.jpg").convert("RGB")) / 255.0
25panorama = torch.from_numpy(panorama).permute(2, 0, 1).float() * 2 - 1
26rgb_cubemap = erp_to_cubemap(panorama, face_w=cfg.face_size,
27 fov=getattr(cfg, "cube_fov", 90.0)).unsqueeze(0).to(device)
28
29# 3. Run one forward pass. The unified checkpoint carries both indoor and
30# outdoor scale heads; pass ``skip_heads`` to keep exactly one of them
31# (here: force the outdoor head by skipping ``scale_indoor``). The full
32# CLI in ``inference.py`` instead routes each panorama automatically via
33# a small CLIP ViT-B/32 classifier on the cubemap faces.
34with torch.inference_mode():
35 pred = pager(rgb_cubemap, dtype=torch.float16, skip_heads={"scale_indoor"})
36
37# 4. Convert raw head outputs into ERP-resolution arrays:
38# - depth: SI depth × exp(log_scale) → metric depth (metres), with the
39# predicted sky region filled to ``MAX_DEPTH`` via a soft alpha blend.
40# - normals: unit vectors in the panorama's world frame, sky-filled.
41cmap = plt.get_cmap("Spectral")
42H, W = panorama.shape[-2:]
43depth_metric, depth_viz = prepare_depth_for_logging(
44 pager, pred["depth"][0], pred["sky"][0], (H, W), cmap,
45 log_scale=pred["scale"],
46)
47normals, normals_viz = prepare_normals_for_logging(
48 pager, pred["normals"][0], pred["sky"][0], (H, W),
49)
depth_metric is a
(1, H, W) float32 array of metric depth (metres);
normals is a
(3, H, W) unit-normal field. Both already have the predicted sky region filled in. The
*_viz companions are uint8 RGB previews (Spectral-coloured for depth, per-sample rescaled for normals). See the
GitHub repository for the full CLI (
inference.py), evaluation scripts, the Gradio demo (
app.py), and the point-cloud exporter.
1@article{bozic2026pager,
2 title = {Unified Panoramic Geometry Estimation via Multi-View Foundation Models},
3 author = {Bozic, Vukasin and Slavkovic, Isidora and Narnhofer, Dominik and
4 Metzger, Nando and Rozumny, Denis and Schindler, Konrad and
5 Kalischek, Nikolai},
6 journal = {arXiv preprint arXiv:2605.26368},
7 year = {2026}
8}