room-visualizer / verify_r1_metric.py
GitHub Actions
Deploy from GitHub commit ca72656c17476e5aa37a4735af6e47ff9f94fa1a
b20c82e
Raw
History Blame Contribute Delete
4.77 kB
"""R1-1 β€” metric depth certification (local harness; needs torch+transformers
and reference room photos β€” not part of `make verify`, which uses precomputed
bundles).
Runs the configured depth checkpoint on reference room photos and validates
that the output is genuinely METRIC:
1. floor depth range plausible for an interior (p5/p95 within 0.3-20 m)
2. ground-plane consistency: on a floor plane, inverse depth is linear in
image row (1/Z = (y - y_horizon) / (h_cam * f)); the fit must hold
(R^2 >= 0.9 over floor rows)
3. absolute scale: the camera height recovered from that fit's slope
(h = 1 / (slope * f), f ~ image width) must land in 0.7-2.5 m β€” the
handheld-phone band. This is the automated equivalent of the backlog's
"door height ~2.0 m +/-15%" check: both test absolute metric scale, but
this one needs no manual annotation.
Usage:
python verify_r1_metric.py <room-photo.jpg> [more photos...]
"""
import sys
import cv2
import numpy as np
import torch
from PIL import Image
from transformers import AutoImageProcessor, AutoModelForDepthEstimation
# single source of truth: read the configured model + metric predicate from app.py
src = open("app.py").read()
ns = {}
start = src.index("def depth_model_is_metric")
end = src.index("\nENABLE_DEPTH", start)
exec(compile(src[start:end], "app.py", "exec"), ns)
import re
MODEL = re.search(r'depth_model_name",\s*\n(?:\s*#.*\n)*\s*"([^"]+)"', src).group(1)
depth_model_is_metric = ns["depth_model_is_metric"]
FLOOR_FRAC = 0.45 # treat the bottom 45% of the frame as floor-dominated
def run_depth(img):
processor = run_depth.processor
model = run_depth.model
inputs = processor(images=img, return_tensors="pt")
with torch.no_grad():
out = model(**inputs)
depth = torch.nn.functional.interpolate(
out.predicted_depth.unsqueeze(1),
size=(img.height, img.width),
mode="bicubic",
align_corners=False,
).squeeze().numpy()
return cv2.GaussianBlur(depth.astype(np.float32), (0, 0), sigmaX=3)
def main():
photos = sys.argv[1:]
if not photos:
print(__doc__)
return 2
print(f"model: {MODEL}")
if not depth_model_is_metric(MODEL):
print("!! configured model is not metric β€” R1-1 not in effect")
return 1
print("loading checkpoint...")
run_depth.processor = AutoImageProcessor.from_pretrained(MODEL)
run_depth.model = AutoModelForDepthEstimation.from_pretrained(MODEL).eval()
ok = True
for path in photos:
img = Image.open(path).convert("RGB")
if max(img.size) > 1280:
s = 1280 / max(img.size)
img = img.resize((round(img.width * s), round(img.height * s)), Image.LANCZOS)
w, h = img.size
depth = run_depth(img)
floor = depth[int(h * (1 - FLOOR_FRAC)):, :]
p5, p95 = np.percentile(floor, 5), np.percentile(floor, 95)
range_ok = 0.3 <= p5 and p95 <= 20.0
# row-median inverse depth over the floor band; fit 1/Z = a*y + b
ys = np.arange(int(h * (1 - FLOOR_FRAC)), h)
inv = np.array([np.median(1.0 / np.maximum(depth[y], 0.05)) for y in ys])
a, b = np.polyfit(ys, inv, 1)
pred = a * ys + b
ss_res = float(np.sum((inv - pred) ** 2))
ss_tot = float(np.sum((inv - inv.mean()) ** 2)) + 1e-12
r2 = 1 - ss_res / ss_tot
focal = float(w) # P0 convention: f ~ image width
horizon_y = -b / a if abs(a) > 1e-12 else float("nan")
# exact ground-plane relation for a pitched camera:
# 1/Z = (sin(t)*f - cos(t)*y') / (h*f) -> h = cos(t) / (a*f)
# with pitch t recovered from the fitted horizon row.
pitch = np.arctan2(h / 2 - horizon_y, focal)
cam_h = float(np.cos(pitch) / (a * focal)) if a > 1e-9 else float("inf")
plane_ok = r2 >= 0.90 and a > 0
height_ok = 0.7 <= cam_h <= 2.5
passed = range_ok and plane_ok and height_ok
ok &= passed
print(
f" [{'PASS' if passed else 'FAIL'}] {path.split('/')[-1]}: "
f"floor p5-p95 = {p5:.2f}-{p95:.2f} m | invZ-fit R2={r2:.3f} | "
f"camera height = {cam_h:.2f} m | horizon y = {horizon_y:.0f}/{h}"
)
if not range_ok:
print(" !! floor depth outside 0.3-20 m")
if not plane_ok:
print(" !! inverse depth not linear in row β€” not plane-consistent")
if not height_ok:
print(" !! camera height outside handheld band 0.7-2.5 m")
print("\n" + ("ALL R1-1 METRIC CHECKS PASSED" if ok else "R1-1 METRIC CHECKS FAILED"))
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main())