CoolFace
Datasetpublic

AntonioJun/workspace

Spatial Code VSI-Bench Workspace This workspace evaluates VSI-Bench question answering with several input regimes: raw video frames, perceived spatial codes from SAM3 + Depth Anything 3 caches, ground-truth spatial codes from dataset annotations, and a deterministic symbolic solver. The code is organized so important outputs are reproducible from fixed inputs, fixed packages, fixed model checkpoints, and fixed SAM3/DA3 caches. The repository intentionally separates three… See the full description on the dataset page: https://huggingface.co/datasets/AntonioJun/workspace.

sourceHugging Faceupdated 2mo agoView on Hugging Face
0likes264downloads
Select Representative Instances Using Track Persistence.py1590 linesDownload Raw Back to hypotheses
1"""Geometric assembler (runs in the pipeline venv). Imported by encoder/render.py.2 3Pure math/assembly -- builds the per-instance spatial code (object positions/sizes, pairwise4distances, closeness ranks, room outline, camera trajectory, appearance order) FROM5already-computed depth/pose/masks. Does NOT run DA3 or SAM3, and does not call cache_or_load()6-- that's run.py's job entirely (the only file that calls the actual model-inference functions).7Callers may provide raw depth/intr/c2w/conf/ftimes/per inputs or canonical world-space8geometry. Both paths emit the same compact spatial-code schema.9 10Formerly this file called into perceptual.py (as a dynamically-loaded `pl` module) for its own11geometry helpers -- build_instances, room_gravity, compute_floor_area, answer_closest_distance,12to_spatial_code, and everything else in this file below _room_outline(). Those functions are13now merged in directly, verbatim, since they were never DA3/SAM3 calls -- they're geometric14computations over already-extracted depth/masks, which is exactly this file's job.15perceptual.py's OTHER half (the actual model-calling functions) moved to run.py instead;16perceptual.py itself no longer exists.17 18The spatial code is the sole spatial representation the downstream VLM sees; no answer engine19is computed here (the harness runs the model).20"""21 22import os23import json24import numpy as np25import cv226 27# ==========================================================================================28# CONSTANTS -- geometry-cleanup knobs, merged in from perceptual.py. Env-tunable levers that29# affect only the math below (build_instances/backproject_frame/etc.), never model inference.30# ==========================================================================================31 32SCHEMA = [33    "x",34    "y",35    "z",36    "e1",37    "e2",38    "e3",39    "px",40    "pz",41    "size_median",42    "size_IQR",43    "time",44    "n",45]  # legacy row schema, see to_labeled()46KD_WORKERS = max(1, int(os.environ.get("VSI_KD_WORKERS", "4")))47# Batch workers set VSI_KD_WORKERS from their per-process CPU budget. Keeping a48# bounded default avoids pathological all-core thread spawning for small queries.49MIN_INSTANCE_PTS = (50    1  # bare geometry floor only: need >=1 valid depth pixel to place a 3D point.51)52# NO quality filtering / dedup -- count = exactly SAM3's tracked masklets (honest)53FLOOR_BAND = (54    0.15  # m above the floor to count as floor-level (LEGACY -- currently unreferenced;55)56# compute_floor_area now uses the RANSAC gravity plane directly instead)57# ---- geometry-cleanup levers (improve abs_distance etc.; env-tunable) ----58# DEPTH_COHERENCE (Tukey-fence bleed removal): SAFE + helpful everywhere -> default ON.59#   ablation: ARKit abs_distance 0.757->0.729 (no harm), ScanNet++ d755 0.443->0.486 (helps).60# CONF_PCT (per-frame confidence percentile): noisy-dataset ONLY -> default OFF.61#   helps ScanNet++ distance more (~0.56) but DESTROYS clean ARKit (0.757->0.429). Opt in via VSI_CONF_PCT=55.62CONF_PCT = float(os.environ.get("VSI_CONF_PCT", "0"))63DEPTH_COHERENCE = os.environ.get("VSI_DEPTH_COHERENCE", "1") == "1"64# cut mask-bleed at per-frame depth edges. Default OFF: it's a NO-OP on the dominant failure (same-depth bleed65# -- adjacent objects at similar range have no depth edge), and only helps depth-SEPARATED bleed. Enable per-need.66DEPTH_EDGE_REFINE = os.environ.get("VSI_DEPTH_EDGE_REFINE", "0") == "1"67# MASK_REFINE (appearance-guided boundary snap): uses the RGB color edge to clip same-depth mask bleed that68#   depth can't see. Principled + cheap (CPU, no model). Default OFF until validated; enable via VSI_MASK_REFINE=1.69MASK_REFINE = os.environ.get("VSI_MASK_REFINE", "0") == "1"70 71 72# ==========================================================================================73# ROOM/OBJECT GEOMETRY -- gravity, floor basis, per-instance spatial-code records. Merged in74# from perceptual.py, verbatim.75# ==========================================================================================76 77 78def to_labeled(objects, floor_area):79    """LEGACY per-class summary (kept for --format array-compat). Superseded by to_spatial_code."""80    out = {"objects": {}}81    for cls, row in objects.items():82        d = dict(zip(SCHEMA, row))83        out["objects"][cls] = {84            "count": int(d["n"]),85            "centroid_meters": {"x": d["x"], "y": d["y"], "z": d["z"]},86            "longest_dimension_meters": {87                "median": d["size_median"],88                "iqr": d["size_IQR"],89            },90            "centroid_spread": {91                "eigenvalues": [d["e1"], d["e2"], d["e3"]],92                "principal_axis_xz": [d["px"], d["pz"]],93            },94            "first_seen_seconds": d["time"],95        }96    out["room"] = {"floor_area_square_meters": floor_area}97    return out98 99 100def room_up_axis(instances, c2w):101    """up axis = smallest-extent axis of all object points; sign from gravity (floor->camera).102    Floor = densest horizontal slab; cameras are always above it, which fixes the sign.103    Returns (axis_index, signed_unit_vector)."""104    points = np.concatenate([i["pts"] for v in instances.values() for i in v], 0)105    ext = np.percentile(points, 98, 0) - np.percentile(points, 2, 0)106    up = int(np.argmin(ext))107    h, edges = np.histogram(points[:, up], bins=80)108    floor = 0.5 * (edges[h.argmax()] + edges[h.argmax() + 1])  # densest slab = floor109    cam_up = c2w[:, :3, 3][:, up].mean()110    e = np.zeros(3, np.float32)111    e[up] = 1.0 if cam_up > floor else -1.0112    return up, e113 114 115def room_gravity(116    depth, intr, c2w, conf, conf_pct=40, stride=12, fstride=15, iters=300, thr=0.05117):118    """Robust UP vector = normal of the RANSAC floor plane (a real physical plane), oriented toward119    the cameras. Works when the reconstruction is tilted/drifted (ScanNet++) where argmin-extent fails.120    Floor = large planar support with most non-inlier mass on ONE side. Returns (gravity_unit_vec, axis).121    """122    points = []123    for f in range(0, len(depth), fstride):124        height, width = depth[f].shape125        ys, xs = np.mgrid[0:height:stride, 0:width:stride]126        ys = ys.ravel()127        xs = xs.ravel()128        z = depth[f][ys, xs]129        ok = (z > 0) & np.isfinite(z)130        if conf is not None and conf_pct > 0:131            ok &= conf[f][ys, xs] >= np.percentile(conf[f], conf_pct)132        ys, xs, z = ys[ok], xs[ok], z[ok]133        if not len(z):134            continue135        intrinsics = intr[f]136        camera_points = np.stack(137            [138                (xs - intrinsics[0, 2]) * z / intrinsics[0, 0],139                (ys - intrinsics[1, 2]) * z / intrinsics[1, 1],140                z,141            ],142            1,143        )144        points.append((c2w[f][:3, :3] @ camera_points.T).T + c2w[f][:3, 3])145    points = np.concatenate(points).astype(np.float64) if points else np.zeros((0, 3))146    cam = c2w[:, :3, 3].mean(0)147    if len(points) < 100:  # fallback to axis-extent148        ext = (149            np.percentile(points, 98, 0) - np.percentile(points, 2, 0)150            if len(points)151            else np.ones(3)152        )153        ax = int(np.argmin(ext))154        g = np.zeros(3)155        g[ax] = 1.0156        return g, ax157    rng = np.random.RandomState(0)158    best = None159    best_score = -1160    for _ in range(iters):161        a, b, c = points[rng.choice(len(points), 3, False)]162        nrm = np.cross(b - a, c - a)163        ln = np.linalg.norm(nrm)164        if ln < 1e-6:165            continue166        nrm /= ln167        d = -nrm @ a168        side = points @ nrm + d169        ninl = int((np.abs(side) < thr).sum())170        if ninl < 50:171            continue172        score = ninl * max(173            np.mean(side > thr), np.mean(side < -thr)174        )  # big + one-sided = floor175        if score > best_score:176            best_score = score177            best = (nrm, d)178    if best is None:179        ext = np.percentile(points, 98, 0) - np.percentile(points, 2, 0)180        ax = int(np.argmin(ext))181        g = np.zeros(3)182        g[ax] = 1.0183        return g, ax184    nrm, d = best185    if (cam @ nrm + d) < 0:186        nrm = -nrm  # orient toward cameras (up)187    return nrm.astype(np.float32), int(np.argmax(np.abs(nrm)))188 189 190def pos3(rec):191    """Read either legacy numeric or current unit-string position formatting."""192    p = rec.get("position") or {}193 194    def meters(current, legacy):195        value = p.get(current, p.get(legacy, 0.0))196        if isinstance(value, str):197            if value.endswith(" meters"):198                value = value[: -len(" meters")]199            value = value.strip()200        return float(value)201 202    return [203        meters("x coordinate", "floor_x_meters"),204        meters("y coordinate", "floor_y_meters"),205        meters("height above floor", "height_above_floor_meters"),206    ]207 208 209def _floor_basis(up_vec):210    """Orthonormal floor basis (u, v horizontal; g = up) from the gravity vector. u is the OLD211    floor_x world axis projected onto the gravity plane (v the old floor_y axis), so this frame212    differs from the old axis-drop frame ONLY by the tilt correction -- NO arbitrary in-plane213    rotation (aligned scenes stay put; only tilt gets corrected). Objects / camera / room_outline214    all share this one gravity-plane frame; floor_area is rotation-invariant so it matches too.215    """216    g = np.asarray(up_vec, np.float64)217    g = g / (np.linalg.norm(g) + 1e-12)218    up_ax = int(np.argmax(np.abs(g)))219    floor_axes = [220        a for a in range(3) if a != up_ax221    ]  # the two world axes the old frame used222    e0 = np.zeros(3)223    e0[floor_axes[0]] = 1.0  # old floor_x world axis224    u = e0 - (e0 @ g) * g225    u = u / (np.linalg.norm(u) + 1e-12)  # project it into the gravity plane226    v = np.cross(g, u)227    if v[floor_axes[1]] < 0:228        v = -v  # keep floor_y sign aligned with the old axis229    return u, v, g230 231 232def _floor_level(points, gravity, v2=None):233    """Reference floor estimator shared by every model representation.234 235    The supplied geometry uses the densest gravity-height slab for its reproducible v1236    behavior and the robust second percentile for v2.  Keep that switch here, after model237    adapters have produced world points, so it cannot become model-specific.238    """239    heights = np.asarray(points, np.float64) @ np.asarray(gravity, np.float64)240    heights = heights[np.isfinite(heights)]241    if not len(heights):242        return 0.0243    if v2 is None:244        v2 = os.environ.get("VSI_CODE_V2") == "1"245    if v2:246        return float(np.percentile(heights, 2))247    counts, edges = np.histogram(heights, bins=80)248    index = int(counts.argmax())249    return float(0.5 * (edges[index] + edges[index + 1]))250 251 252def _object_records(insts, count, u, v, g, floor_level):253    """Up to `count` instances, strongest-evidence first (most observed points = best-segmented,254    closest, most geometry). `count` (peak co-visibility) decides HOW MANY; total observed points255    decide WHICH -- no threshold. Positions are projected onto the shared gravity floor basis256    (u, v horizontal; g up; height 0 = floor_level) and emitted directly in THE final spatial257    code shape: unit-strings ("1.4 meters"), spaced keys ("x coordinate"), and exactly two258    fields per instance (position + longest dimension) -- there is no separate raw form.259 260    Deliberately does NOT report a per-instance first_seen_seconds: the reported instances are261    chosen by STRONGEST evidence (most points/frames), but the class's true first appearance can262    come from a weaker, earlier masklet that never makes this cut (confirmed empirically -- e.g. a263    brief early detection with few points, superseded here by a longer later observation of264    presumably the same object). A per-instance timestamp here would silently describe a DIFFERENT265    detection than the class-level "first seen" a reader would assume it means. appearance_order266    (built below in build_spatial_code() from min(first_time) over ALL detected masklets, not just267    the reported ones) is the sole reliable source for first-appearance timing."""268    ranked = sorted(insts, key=lambda i: (i["n"], i.get("nframes", 0)), reverse=True)[269        : max(count, 1)270    ]271    recs = []272    for it in ranked:273        c = np.asarray(it["centroid"], np.float64)274        recs.append(275            {276                "position": {277                    "x coordinate": f"{round(float(c @ u), 2)} meters",278                    "y coordinate": f"{round(float(c @ v), 2)} meters",279                    "height above floor": f"{round(float(c @ g - floor_level), 2)} meters",280                },281                "longest dimension": f"{round(float(it['size']), 2)} meters",282            }283        )284    return recs285 286 287def to_spatial_code(instances, stats, floor_area, up_axis, up_vec, floor_level):288    """Per-instance spatial code, emitted directly in THE final shape: class -> {count (peak289    co-visibility), instances:[{position, longest dimension}]} plus room -> {"floor area"}.290    Positions are projected onto the GRAVITY floor plane (u, v horizontal via _floor_basis -- the291    SAME frame as compute_floor_area); "height above floor" is along gravity with 0 = on the292    floor. floor_level is the gravity-height of the floor. (up_axis is retained for signature293    compatibility; the frame now derives from up_vec.)"""294    u, v, g = _floor_basis(up_vec)295    out = {"objects": {}}296    for cls, insts in instances.items():297        cnt = int(stats[cls]["peak"])298        out["objects"][cls] = {299            "count": cnt,300            "instances": _object_records(insts, cnt, u, v, g, floor_level),301        }302    out["room"] = {"floor area": f"{floor_area} square meters"}303    return out304 305 306# ==========================================================================================307# DETERMINISTIC ANSWER LAYER (parameter-free; validated on VSI GT). Reads the in-memory308# instances (pos for direction/route, point clouds for distance). Merged in from309# perceptual.py, verbatim.310# ==========================================================================================311 312 313def _find_cls(name, classes):314    name = name.strip().lower()315    for c in classes:316        if c == name or c.replace(" ", "") == name.replace(" ", ""):317            return c318    for c in classes:319        if name in c or c in name:320            return c321    return None322 323 324def _rep(insts):325    """Prefer a persistent track, using point support as the tie-breaker."""326    return max(insts, key=lambda i: (i.get("nframes", 0), i["n"]))327 328 329def answer_rel_direction(point_a, point_b, point_c, up_vec, up_ax, mode="hard"):330    """Standing at A facing B, where is C? front/back=dot(C-A,fwd); left/right=dot(C-A, up x fwd).331    Right-handed world (OpenCV cam frame + det+1 c2w) makes up x fwd = left a fixed identity.332    Projection uses the gravity VECTOR (v-(v.g)g), so a tilted floor (ScanNet++) is handled; for an333    axis-aligned up this reduces to zeroing that axis (ARKit unchanged)."""334    g = np.asarray(up_vec, np.float64)335    g = g / (np.linalg.norm(g) + 1e-12)336 337    def fl(v):338        w = v.astype(np.float64)339        return w - (w @ g) * g340 341    fwd = fl(point_b - point_a)342    n = np.linalg.norm(fwd)343    if n < 1e-6:344        return None345    fwd /= n346    left = np.cross(g, fwd)347    left /= np.linalg.norm(left) + 1e-9348    d = fl(point_c - point_a)349    f = float(d @ fwd)350    lateral = float(d @ left)351    if mode == "medium":352        if abs(np.degrees(np.arctan2(lateral, f))) >= 135:353            return "back"354        return "left" if lateral > 0 else "right"355    return f"{'front' if f > 0 else 'back'}-{'left' if lateral > 0 else 'right'}"356 357 358def _classify_turn(h_in, h_out, up_vec, up_ax):359    """rotation h_in->h_out in floor plane -> turn left/right/back (135deg = VSI's own 'back' cutoff)."""360    g = np.asarray(up_vec, np.float64)361    g = g / (np.linalg.norm(g) + 1e-12)362 363    def fl(v):364        w = v.astype(np.float64)365        return w - (w @ g) * g366 367    a, b = fl(h_in), fl(h_out)368    na, nb = np.linalg.norm(a), np.linalg.norm(b)369    if na < 1e-6 or nb < 1e-6:370        return None371    a /= na372    b /= nb373    ang = np.degrees(np.arctan2(float(up_vec @ np.cross(a, b)), float(a @ b)))374    if abs(ang) >= 135:375        return "turn back"376    return "turn left" if ang > 0 else "turn right"377 378 379def answer_route(ql, cents, up_vec, up_ax):380    """Chain the turn primitive over the waypoint sequence -> ordered ['turn left/right/back', ...]."""381    import re as _re382 383    m = _re.search(r"beginning at the (.+?) (?:and )?facing the (.+?)\.", ql)384    if not m:385        return None386 387    def position(name):388        class_name = _find_cls(name, cents)389        return cents[class_name] if class_name else None390 391    steps_txt = ql.split(":", 1)[1] if ":" in ql else ql392    steps = _re.findall(393        r"\d+\.\s*(\[please fill in\]|Go forward until the [^0-9\[]+?)(?=\s*\d+\.|$)",394        steps_txt,395    )396    cur_pos = position(m.group(1).strip())397    if cur_pos is None:398        return None399    fac = position(m.group(2).strip())400    cur_head = (fac - cur_pos) if fac is not None else None401    turns = []402    i = 0403    while i < len(steps):404        s = steps[i].strip()405        if s.startswith("Go forward"):406            tp = position(_re.sub(r"^Go forward until the ", "", s).strip().rstrip("."))407            if tp is not None:408                cur_head = tp - cur_pos409                cur_pos = tp410        else:411            nxt = next(412                (413                    _re.sub(r"^Go forward until the ", "", steps[j].strip())414                    .strip()415                    .rstrip(".")416                    for j in range(i + 1, len(steps))417                    if steps[j].strip().startswith("Go forward")418                ),419                None,420            )421            tp = position(nxt) if nxt else None422            if tp is None or cur_head is None:423                turns.append(None)424            else:425                turns.append(_classify_turn(cur_head, tp - cur_pos, up_vec, up_ax))426                cur_head = tp - cur_pos427        i += 1428    return turns429 430 431# ==========================================================================================432# POINT-CLOUD CLEANING + DISTANCE ANSWERS -- outlier removal, closest-distance queries. Merged433# in from perceptual.py, verbatim.434# ==========================================================================================435 436 437def _sor(pts, k=16, std=2.0, cap=4000):438    """Statistical outlier removal: drop points whose mean distance to their k nearest neighbors exceeds439    mean + std*sigma. Removes mask-bleed / depth-speckle points that corrupt a literal closest-point min.440    k=16 and std=2 are universal robust-statistics defaults -- NOT tuned to VSI (no benchmark-fit knob).441    """442    from scipy.spatial import cKDTree443 444    if len(pts) < k + 2:445        return pts446    rs = np.random.RandomState(0)447    points = pts if len(pts) <= cap else pts[rs.choice(len(pts), cap, False)]448    d, _ = cKDTree(points).query(points, k=k + 1, workers=KD_WORKERS)449    md = d[:, 1:].mean(1)450    return points[md <= md.mean() + std * md.std()]451 452 453def _main_cluster(pts):454    """Keep the object's dominant spatial cluster. Coherent mask-bleed onto a SPATIALLY-SEPARATED adjacent455    object forms a disconnected component (a gap separates two objects); the true object is the largest one.456    Linkage scale self-calibrates from the cloud's own nearest-neighbor spacing -- no fixed distance.457    """458    from scipy.spatial import cKDTree459 460    if len(pts) < 30:461        return pts462    tree = cKDTree(pts)463    nn, _ = tree.query(pts, k=2)464    eps = 3.0 * float(np.median(nn[:, 1]))  # 3x median NN gap (data-derived)465    pairs = tree.query_pairs(eps, output_type="ndarray")466    if len(pairs) == 0:467        return pts468    parent = np.arange(len(pts))469 470    def find(x):471        r = x472        while parent[r] != r:473            r = parent[r]474        while parent[x] != r:475            parent[x], x = r, parent[x]476        return r477 478    for a, b in pairs:479        ra, rb = find(int(a)), find(int(b))480        if ra != rb:481            parent[ra] = rb482    roots = np.array([find(i) for i in range(len(pts))])483    v, cnt = np.unique(roots, return_counts=True)484    return pts[roots == v[cnt.argmax()]]485 486 487def _clean(inst, cap=4000):488    """Outlier removal self-calibrated from the pipeline's OWN per-object signals -- no universal constant,489    no benchmark knob. Cached on the instance so the O(classes^2) distance table cleans each object once.490      (1) per-point DA3 confidence: boundary mask-bleed = depth discontinuity = LOW conf -> drop below the491          object's OWN median confidence (data-derived cut);492      (2) statistical density outlier removal on the survivors;493      (3) dominant spatial cluster: coherent bleed onto a separated adjacent object is a disconnected cluster.494    """495    if inst.get("_cleanpts") is not None:496        return inst["_cleanpts"]497    pts = inst["pts"]498    conf = inst.get("conf")499    rs = np.random.RandomState(0)500    if len(pts) > cap:501        idx = rs.choice(len(pts), cap, False)502        pts = pts[idx]503        conf = conf[idx] if conf is not None else None504    if conf is not None and len(conf) > 20:  # self-calibrating: object's OWN median505        keep = conf >= np.median(conf)506        if keep.sum() >= 10:507            pts = pts[keep]508    pts = _sor(pts, cap=cap)  # density outlier removal on the survivors509    # NOTE: spatial de-bleeding (_main_cluster) was tried and REVERTED -- "largest cluster = the object" is510    # not guaranteed; when an object's near edge splits off it drops the true closest part (overshoot). You511    # cannot post-hoc recover the true object from a bleeding mask in 3D -- that needs better SOURCE masks.512    inst["_cleanpts"] = pts513    return pts514 515 516def answer_closest_distance(instances_a, instances_b, k=4000):517    """Closest distance between the two objects' point clouds ('closest point of each object'). Points are518    cleaned by _clean (self-calibrated per-object confidence + density) then exact nearest-neighbor min via519    KD-tree, so mask-bleed/speckle can't collapse the answer. NOTE: VSI's GT is box-to-box on CLEAN annotation520    boxes; the residual gap is object-segmentation quality (bleed), not this formula -- see _clean docstring.521    """522    points_a = _clean(_rep(instances_a), cap=k)523    points_b = _clean(_rep(instances_b), cap=k)524    if len(points_a) == 0 or len(points_b) == 0:525        return float("inf")526    from scipy.spatial import cKDTree527 528    if len(points_a) <= len(points_b):529        d, _ = cKDTree(points_a).query(points_b, k=1, workers=KD_WORKERS)530    else:531        d, _ = cKDTree(points_b).query(points_a, k=1, workers=KD_WORKERS)532    return float(d.min())533 534 535def answer_rel_distance(anchor_insts, option_insts):536    """'which option is closest to the anchor?' -> index of the option with min closest-point distance."""537    dists = [538        answer_closest_distance(anchor_insts, oi) if oi is not None else float("inf")539        for oi in option_insts540    ]541    return int(np.argmin(dists)), dists542 543 544# ==========================================================================================545# MASK/DEPTH CLEANUP + BACK-PROJECTION -- per-frame refinement before points enter an546# instance's point cloud. Merged in from perceptual.py, verbatim.547# ==========================================================================================548 549 550def depth_edges(depth_f, valid_f):551    """Per-frame depth-DISCONTINUITY map: gradient magnitude above median + 3*MAD over the valid pixels.552    The threshold is data-derived (robust, universal statistical cut -- NOT benchmark-tuned). These edges are553    where mask-bleed crosses onto an adjacent object at a different depth."""554    if valid_f.sum() < 100:555        return np.zeros_like(depth_f, bool)556    d = np.where(valid_f, depth_f, np.median(depth_f[valid_f]))557    gy, gx = np.gradient(d)558    grad = np.hypot(gx, gy)559    g = grad[valid_f]560    med = np.median(g)561    mad = np.median(np.abs(g - med)) + 1e-9562    return (grad > med + 3.0 * 1.4826 * mad) & valid_f563 564 565try:566    _GUIDED = (567        cv2.ximgproc.guidedFilter568    )  # opencv-contrib; appearance-guided boundary snap569except AttributeError:570    _GUIDED = None571 572 573def refine_mask(mask, rgb):574    """Snap a coarse SAM3 mask boundary to the RGB color edge (appearance-guided). This clips the same-depth575    bleed that depth CANNOT see: where the halo crosses onto a differently-colored neighbor, the color edge576    cuts it. Params are DERIVED, not tuned -- radius from frame scale, smoothness from the image's own color577    variance (same MAD-style principle as depth_edges). No model, no GPU. Falls back to grabCut, then erode.578    """579    if mask.shape[:2] != rgb.shape[:2]:580        mask = cv2.resize(581            mask.astype(np.uint8),582            (rgb.shape[1], rgb.shape[0]),583            interpolation=cv2.INTER_NEAREST,584        ).astype(bool)585    a = int(mask.sum())586    if a < 60:  # too small to refine meaningfully -> 1px erode as before587        me = cv2.erode(mask.astype(np.uint8), np.ones((3, 3), np.uint8), 1).astype(bool)588        return me if me.any() else mask589    if _GUIDED is not None:590        r = max(591            4, int(round(0.02 * float(np.hypot(*mask.shape[:2]))))592        )  # radius ~2% of frame diagonal593        eps = (594            float(np.var(rgb.astype(np.float32) / 255.0)) * 0.01 + 1e-6595        )  # smoothness ~ image color variance596        soft = _GUIDED(rgb, mask.astype(np.float32), r, eps)597        out = soft > 0.5598        return out if out.sum() >= 0.4 * a else mask  # majority guard: don't over-carve599    # fallback (base opencv, no ximgproc): grabCut seeded FG=mask, PR_FG=dilated ring, BG=far exterior600    try:601        gc = np.full(mask.shape[:2], cv2.GC_PR_BGD, np.uint8)602        dil = cv2.dilate(mask.astype(np.uint8), np.ones((15, 15), np.uint8), 1).astype(603            bool604        )605        er = cv2.erode(mask.astype(np.uint8), np.ones((5, 5), np.uint8), 1).astype(bool)606        gc[dil] = cv2.GC_PR_FGD607        gc[er] = cv2.GC_FGD608        bgm = np.zeros((1, 65), np.float64)609        fgm = np.zeros((1, 65), np.float64)610        cv2.grabCut(rgb, gc, None, bgm, fgm, 3, cv2.GC_INIT_WITH_MASK)611        out = (gc == cv2.GC_FGD) | (gc == cv2.GC_PR_FGD)612        return out if 0.4 * a <= out.sum() <= 1.5 * a else mask613    except Exception:614        me = cv2.erode(mask.astype(np.uint8), np.ones((3, 3), np.uint8), 1).astype(bool)615        return me if me.any() else mask616 617 618def backproject_frame(619    depth_f,620    intrinsics,621    c2w_f,622    mask_f,623    conf_f=None,624    conf_thr=0.0,625    valid_f=None,626    return_conf=False,627    edges_f=None,628):629    """Return (M,3) world points for the masked pixels of one frame (c2w_f = cam->world 4x4).630    valid_f: optional precomputed (isfinite & >0) depth mask, reused across all masks of a frame.631    return_conf: also return the (M,) DA3 confidence of each kept point (for per-point noise-aware cleaning).632    edges_f: optional per-frame depth-edge map; if given, keep the mask's largest depth-coherent component633             (cuts mask-bleed onto adjacent objects at the depth boundary, per frame, before back-projection).634    """635    height, width = depth_f.shape636    empty = (637        (np.empty((0, 3), np.float32), np.empty((0,), np.float32))638        if return_conf639        else np.empty((0, 3), np.float32)640    )641    if mask_f.shape != (height, width):642        mask_f = cv2.resize(643            mask_f.astype(np.uint8), (width, height), interpolation=cv2.INTER_NEAREST644        ).astype(bool)645    if valid_f is None:646        valid_f = np.isfinite(depth_f) & (depth_f > 0)647    m = mask_f & valid_f648    if conf_f is not None and conf_thr > 0:649        m &= conf_f >= conf_thr650    if (651        edges_f is not None and m.sum() >= 30652    ):  # keep the largest depth-coherent piece of the mask653        from scipy import ndimage654 655        lab, nlab = ndimage.label(m & ~edges_f)656        if nlab >= 1:657            sizes = np.bincount(lab.ravel())658            sizes[0] = 0659            big = int(sizes.argmax())660            if (661                sizes[big] >= 0.5 * m.sum()662            ):  # object is the MAJORITY piece (bleed is a minority)663                m = lab == big664    if not m.any():665        return empty666    ys, xs = np.nonzero(m)667    z = depth_f[ys, xs]668    if DEPTH_COHERENCE and len(z) >= 8:669        # an object is a coherent depth surface; floor/background BLEED pixels are depth outliers.670        # Drop them via the standard Tukey fence (1.5*IQR) on the masked region's depths -- parameter-free.671        q1, q3 = np.percentile(z, [25, 75])672        iqr = q3 - q1673        keep = (z >= q1 - 1.5 * iqr) & (z <= q3 + 1.5 * iqr)674        if keep.sum() >= 1:675            ys, xs, z = ys[keep], xs[keep], z[keep]676    fx, fy, cx, cy = (677        intrinsics[0, 0],678        intrinsics[1, 1],679        intrinsics[0, 2],680        intrinsics[1, 2],681    )682    camera_points = np.stack(683        [(xs - cx) * z / fx, (ys - cy) * z / fy, z], axis=1684    )  # camera coords685    world_points = (c2w_f[:3, :3] @ camera_points.T).T + c2w_f[:3, 3]  # -> world686    if return_conf:687        cw = (688            conf_f[ys, xs].astype(np.float32)689            if conf_f is not None690            else np.ones(len(ys), np.float32)691        )692        return world_points.astype(np.float32), cw693    return world_points.astype(np.float32)694 695 696# ==========================================================================================697# INSTANCE BUILDING -- oriented extent, 3D box-overlap re-identification, and the main698# build_instances() driver that turns per-frame masks into per-class 3D instances. Merged in699# from perceptual.py, verbatim.700# ==========================================================================================701 702 703def robust_centroid_extent(pts, up_axis=None):704    """median centroid + ORIENTED robust extent.705    If up_axis is given: YAW-ONLY oriented extent -- rotation is found by 2D PCA on the706    floor-projected points only, with the up axis left untouched. This matches VSI-Bench's707    own annotation convention for indoor scans (ScanNet/ARKitScenes OrientedBoundingBox708    objects follow a Manhattan-world assumption: rotated only around the vertical axis,709    never tilted). Unconstrained 3D PCA (the previous behavior) can chase noise on the710    vertical axis for flat/elongated objects and drift away from the true yaw.711    If up_axis is None (unknown at the call site): falls back to unconstrained 3D PCA.712    Either way: parameter-free, rotation-invariant in-plane, p2..p98 robust extent."""713    c = np.median(pts, axis=0)714    centered = pts - c715    if len(centered) > 5000:  # PCA on a sample (deterministic)716        centered = centered[np.random.RandomState(0).choice(len(centered), 5000, False)]717    if up_axis is not None:718        floor_axes = [i for i in range(3) if i != up_axis]719        floor_points = centered[:, floor_axes]720        try:721            _, _, floor_rotation = np.linalg.svd(722                floor_points - floor_points.mean(0), full_matrices=False723            )724            proj_floor = (725                floor_points @ floor_rotation.T726            )  # (N,2) along the object's own floor-plane axes727        except np.linalg.LinAlgError:728            proj_floor = floor_points729        up_col = centered[:, up_axis : up_axis + 1]  # up axis untouched (yaw-only)730        proj = np.concatenate([proj_floor, up_col], axis=1)731    else:732        try:733            _, _, rotation = np.linalg.svd(734                centered - centered.mean(0), full_matrices=False735            )736            proj = centered @ rotation.T  # coordinates along principal axes737        except np.linalg.LinAlgError:738            proj = centered739    lo = np.percentile(proj, 2, axis=0)740    hi = np.percentile(proj, 98, axis=0)741    ext = np.maximum(hi - lo, 0.0)742    dims = np.sort(ext)[::-1]  # the object's 3 oriented side lengths, longest first743    return c.astype(np.float32), float(dims[0]), dims744 745 746def _aabb(pts):747    """robust (p2..p98) axis-aligned 3D box of an instance's world points."""748    return np.percentile(pts, 2, axis=0), np.percentile(pts, 98, axis=0)749 750 751def merge_by_box_overlap(insts, up_axis=None):752    """Parameter-free 3D re-identification, with a temporal-exclusion gate.753 754    SAM3 (a 2D tracker) emits a NEW masklet each time the camera revisits an object, so one755    physical object -> several masklets at the same 3D location. We fuse two same-class masklets756    iff BOTH:757      (a) their measured 3D boxes overlap (or one centroid is inside the other) -- same volume, and758      (b) they NEVER appear in the same frame -- temporal exclusion.759    (b) is the key parameter-free invariant: two masklets co-visible in one frame were tracked by760    SAM3 as distinct objects in that frame, so they ARE distinct (e.g. two chairs around a table);761    we must never merge them, even if depth noise makes their boxes overlap. A revisit-duplicate,762    by contrast, lives in DISJOINT frames. Both tests are exact/measured -- NO tuned threshold.763    """764    n = len(insts)765    if n <= 1:766        return insts767    # Precompute the FULL pairwise box-match as one vectorized boolean matrix. With the per-frame768    # batched detector a class can have 100+ raw instances; the old O(n^3) loop called np.all-based769    # box_match millions of times. Here every pairwise test is one broadcast -> O(1) lookups below.770    lo = np.stack([_aabb(i["pts"])[0] for i in insts]).astype(np.float32)  # (n,3)771    hi = np.stack([_aabb(i["pts"])[1] for i in insts]).astype(np.float32)  # (n,3)772    cents = np.stack([i["centroid"] for i in insts]).astype(np.float32)  # (n,3)773    overlap = (hi[:, None, :] >= lo[None, :, :]).all(-1) & (774        hi[None, :, :] >= lo[:, None, :]775    ).all(-1)776    ins = (cents[:, None, :] >= lo[None, :, :]).all(-1) & (777        cents[:, None, :] <= hi[None, :, :]778    ).all(-1)779    match = (780        overlap | ins | ins.T781    )  # box_match[i,j]: same 3D volume (identical semantics to old code)782    # group-level agglomeration: merge two groups only if their COMBINED frame sets are disjoint783    # (so no two co-visible masklets ever land in one object) AND some cross-pair shares a 3D volume.784    groups = [785        {"members": [i], "frames": set(insts[i].get("frames", set()))} for i in range(n)786    ]787    changed = True788    while changed:789        changed = False790        for a in range(len(groups)):791            for b in range(a + 1, len(groups)):792                if (793                    groups[a]["frames"] & groups[b]["frames"]794                ):  # co-visible -> distinct objects795                    continue796                if match[np.ix_(groups[a]["members"], groups[b]["members"])].any():797                    groups[a]["members"] += groups[b]["members"]798                    groups[a]["frames"] |= groups[b]["frames"]799                    groups.pop(b)800                    changed = True801                    break802            if changed:803                break804    merged = []805    for g in groups:806        idxs = g["members"]807        pts = np.concatenate([insts[k]["pts"] for k in idxs], 0)808        cpts = np.concatenate(809            [810                insts[k].get("conf", np.ones(len(insts[k]["pts"]), np.float32))811                for k in idxs812            ],813            0,814        )815        best = max(816            (insts[k]["best_pts"] for k in idxs), key=len817        )  # largest single obs in the group818        c, longest, dims = robust_centroid_extent(819            best, up_axis820        )  # size+pos+3 oriented dims from best view (#2/#4)821        merged.append(822            {823                "centroid": c,824                "size": longest,825                "dims": dims,826                "first_time": min(insts[k]["first_time"] for k in idxs),827                "pts": pts,828                "conf": cpts,829                "best_pts": best,830                "n": sum(insts[k]["n"] for k in idxs),831                "nframes": len(g["frames"]),832            }833        )  # track persistence (evidence strength)834    return merged835 836 837def build_instances(838    per_class, depth, intr, c2w, conf, frame_times, frame_paths=None, up_axis=None839):840    """-> {class: [ {centroid(3), size(longest dim), first_time, npts} ]}841    up_axis: if known (from room_gravity, computed BEFORE this call), threads through to842    robust_centroid_extent for yaw-only oriented sizing. If None, size falls back to843    unconstrained 3D PCA."""844    out = {}845    stats = {}846    nframes = len(depth)847    # precompute each frame's valid-depth mask ONCE (was recomputed per mask -> per class).848    valid = {}849    edges = {}850    rgb = {}851    used_frames = {fi for frames in per_class.values() for fi in frames if fi < nframes}852    for fi in used_frames:853        valid[fi] = np.isfinite(depth[fi]) & (depth[fi] > 0)854        edges[fi] = depth_edges(depth[fi], valid[fi]) if DEPTH_EDGE_REFINE else None855        if MASK_REFINE and frame_paths and fi < len(frame_paths):856            im = cv2.imread(frame_paths[fi])  # BGR; guidedFilter/grabCut want 3ch uint8857            rgb[fi] = (858                cv2.resize(im, (depth[fi].shape[1], depth[fi].shape[0]))859                if im is not None860                else None861            )862    for cls, frames in per_class.items():863        # peak co-visibility: max distinct masklets SAM3 tracks SIMULTANEOUSLY in any one frame.864        # geometry-free, parameter-free, immune to revisit over-count; provable lower bound on count.865        peak = max((len(objs) for objs in frames.values()), default=0)866        # gather world points + first-seen time + frame set per obj_id (SAM3 track id = masklet)867        pts_by_id, conf_by_id, first_t, frames_by_id = {}, {}, {}, {}868        # px_by_id: (fidx, frame_time, mask_pixel_count) per obj_id, for appearance-order timing.869        # VSI-Bench's own GT defines "first appearance" as the timestamp where an object's pixel870        # count crosses a threshold (paper appendix B.1) -- NOT the first frame with any pixel at871        # all. A single stray mask-bleed/false-positive pixel would otherwise register as "first872        # seen" far too early. The threshold is self-calibrated per instance (that instance's OWN873        # median observed pixel count across its frames), same convention as _clean's median cut.874        px_by_id = {}875        for fidx, objs in frames.items():876            if fidx >= nframes:  # guard: SAM3 frame idx vs DA3 frames877                continue878            for oid, mask in objs.items():879                if MASK_REFINE and rgb.get(fidx) is not None:880                    me = (881                        mask882                        if os.environ.get("VSI_NO_REFINE") == "1"883                        else refine_mask(mask, rgb[fidx])884                    )  # appearance-guided boundary snap (clips same-depth bleed)885                else:886                    # erode 1px to drop mask-edge / background depth bleed887                    me = cv2.erode(888                        mask.astype(np.uint8), np.ones((3, 3), np.uint8), 1889                    ).astype(bool)890                    if not me.any():891                        me = mask892                conf_f = conf[fidx] if conf is not None else None893                conf_thr = (894                    np.percentile(conf_f, CONF_PCT)895                    if (conf_f is not None and CONF_PCT > 0)896                    else 0.0897                )898                world_points, cw = backproject_frame(899                    depth[fidx],900                    intr[fidx],901                    c2w[fidx],902                    me,903                    conf_f,904                    conf_thr=conf_thr,905                    valid_f=valid.get(fidx),906                    return_conf=True,907                    edges_f=edges.get(fidx),908                )909                if len(world_points):910                    pts_by_id.setdefault(oid, []).append(world_points)911                    conf_by_id.setdefault(oid, []).append(912                        cw913                    )  # per-point DA3 confidence (for _clean)914                    frames_by_id.setdefault(oid, set()).add(915                        fidx916                    )  # for co-occurrence gate917                    t = frame_times[fidx]918                    px_by_id.setdefault(oid, []).append((fidx, t, int(me.sum())))919        # appearance_order timing: per instance, first frame at/above its OWN median pixel count920        for oid, obs in px_by_id.items():921            counts = [c for _, _, c in obs]922            thresh = float(np.median(counts))923            crossing = [t for _, t, c in obs if c >= thresh]924            first_t[oid] = min(crossing) if crossing else min(t for _, t, c in obs)925        insts = []926        for oid, plist in pts_by_id.items():927            pts = np.concatenate(plist, 0)928            cpts = np.concatenate(conf_by_id[oid], 0)929            if len(pts) < MIN_INSTANCE_PTS:930                continue931            best = max(932                plist, key=len933            )  # #2/#4: largest single-frame observation (closest/most pixels)934            bc, bsize, bdims = robust_centroid_extent(935                best, up_axis936            )  # size + position + 3 oriented dims from best view937            insts.append(938                {939                    "centroid": bc,940                    "size": bsize,941                    "dims": bdims,942                    "first_time": first_t[oid],943                    "pts": pts,944                    "conf": cpts,945                    "best_pts": best,946                    "n": len(pts),947                    "frames": frames_by_id[oid],948                }949            )950        raw = len(insts)951        # 3D re-ID: fuse same-class masklets that occupy the same measured 3D volume (parameter-free).952        insts = merge_by_box_overlap(insts, up_axis)953        if insts:954            out[cls] = insts955            stats[cls] = {"raw": raw, "merged": len(insts), "peak": peak}956    return out, stats957 958 959# ==========================================================================================960# PER-CLASS SUMMARY + FLOOR AREA -- legacy array-schema row builder, and the room-scale floor961# area calculation. Merged in from perceptual.py, verbatim.962# ==========================================================================================963 964 965# ---- Per-class spatial code (legacy array-schema row) -------------------------------------966def class_spatial_code(insts, peak=0):967    cents = np.stack([i["centroid"] for i in insts], 0)  # (n,3)968    sizes = np.array([i["size"] for i in insts], np.float32)969    n = len(insts)  # merged centroids (for spatial stats)970    count = peak if peak else n  # reported count = peak co-visibility971    x, y, z = cents.mean(0)972    if n == 1:973        e1 = e2 = e3 = px = pz = 0.0974        size_iqr = 0.0975    else:976        cov = np.cov(cents.T)  # 3x3977        vals, vecs = np.linalg.eigh(cov)  # ascending978        order = np.argsort(vals)[::-1]979        vals = np.clip(vals[order], 0, None)980        vecs = vecs[:, order]981        e1, e2, e3 = vals.tolist()982        pv = vecs[:, 0]  # principal eigenvector983        px, pz = float(pv[0]), float(pv[2])984        q1, q3 = np.percentile(sizes, [25, 75])985        size_iqr = float(q3 - q1)986    size_median = float(np.median(sizes))987    first_time = float(min(i["first_time"] for i in insts))988    row = [x, y, z, e1, e2, e3, px, pz, size_median, size_iqr, first_time, count]989    row = [990        (lambda r: 0.0 if r == 0 else r)(round(float(v), 1)) for v in row991    ]  # kill -0.0992    row[-1] = int(count)993    return row994 995 996# ---- floor_area (full-scene min-Y points -> XZ convex hull) -------------------------------997def compute_floor_area(depth, intr, c2w, conf, sky, stride=8, up_vec=None):998    pts = []999    for f in range(depth.shape[0]):1000        height, width = depth[f].shape1001        ys, xs = np.mgrid[0:height:stride, 0:width:stride]1002        ys = ys.ravel()1003        xs = xs.ravel()1004        z = depth[f][ys, xs]1005        ok = np.isfinite(z) & (z > 0)1006        if sky is not None:1007            ok &= ~sky[f][ys, xs].astype(bool)1008        if conf is not None:1009            ok &= conf[f][ys, xs] >= np.percentile(conf[f], 40)1010        ys, xs, z = ys[ok], xs[ok], z[ok]1011        if not len(z):1012            continue1013        intrinsics = intr[f]1014        fx, fy, cx, cy = (1015            intrinsics[0, 0],1016            intrinsics[1, 1],1017            intrinsics[0, 2],1018            intrinsics[1, 2],1019        )1020        camera_points = np.stack([(xs - cx) * z / fx, (ys - cy) * z / fy, z], 1)1021        world_points = (c2w[f][:3, :3] @ camera_points.T).T + c2w[f][:3, 3]1022        pts.append(world_points.astype(np.float32))1023    if not pts:1024        return 0.01025    points = np.concatenate(pts, 0)1026    if up_vec is not None:1027        # VSI-faithful: area in the plane orthogonal to GRAVITY (RANSAC floor normal), like the1028        # benchmark's gravity-aligned GT meshes. Build an orthonormal in-plane basis (u, v).1029        g = np.asarray(up_vec, np.float64)1030        g /= np.linalg.norm(g) + 1e-121031        a = np.array([1.0, 0.0, 0.0]) if abs(g[0]) < 0.9 else np.array([0.0, 1.0, 0.0])1032        u = np.cross(g, a)1033        u /= np.linalg.norm(u)1034        v = np.cross(g, u)1035        all_floor_points = np.stack([points @ u, points @ v], 1)1036    else:1037        up = int(1038            np.argmin(points.max(0) - points.min(0))1039        )  # legacy: vertical = smallest-extent axis1040        floor_axes = [i for i in range(3) if i != up]1041        all_floor_points = points[:, floor_axes]1042    # VSI-Bench room-size definition = alpha-shape of the floor-plane point cloud (confirmed in their1043    # paper appendix). VSI does not publish the alpha value they use for their own GT mesh, so alpha=21044    # here is NOT a matched/verified constant -- it was chosen empirically for this pipeline's own1045    # (sparser) reconstructed point density. This is the one disclosed benchmark-adjacent tuned constant1046    # in the whole file; everything else is exact/derived or a generic, non-tuned statistical convention.1047    # (Falls back to enclosed-fill below if the alphashape package isn't available.)1048    floor_points = all_floor_points1049    lo = np.percentile(floor_points, 0.5, 0)1050    hi = np.percentile(floor_points, 99.5, 0)  # gentle clip (preserve room extent)1051    floor_points = floor_points[1052        (floor_points[:, 0] >= lo[0])1053        & (floor_points[:, 0] <= hi[0])1054        & (floor_points[:, 1] >= lo[1])1055        & (floor_points[:, 1] <= hi[1])1056    ]1057    if len(floor_points) < 10:1058        return 0.01059    try:1060        import alphashape1061 1062        idx = np.random.RandomState(0).choice(1063            len(floor_points), min(10000, len(floor_points))1064        )1065        return round(1066            float(alphashape.alphashape(floor_points[idx], alpha=2).area), 11067        )  # alpha=2 tuned for recon density1068    except Exception:1069        from scipy import ndimage1070 1071        res = 0.101072        ai = ((floor_points[:, 0] - floor_points[:, 0].min()) / res).astype(int)1073        bi = ((floor_points[:, 1] - floor_points[:, 1].min()) / res).astype(int)1074        grid = np.zeros((ai.max() + 3, bi.max() + 3), np.uint8)1075        grid[ai + 1, bi + 1] = 11076        grid = cv2.morphologyEx(grid, cv2.MORPH_CLOSE, np.ones((7, 7), np.uint8))1077        grid = ndimage.binary_fill_holes(grid).astype(np.uint8)1078        return round(float(grid.sum()) * res * res, 1)1079 1080 1081# ---------------------------------------------------------------------------1082 1083# ==========================================================================================1084# SPATIAL CODE ASSEMBLY -- the top-level entry point this whole file exists for:1085# build_spatial_code() calls everything above to turn already-computed depth/pose/masks into1086# the final spatial code dict. Room outline + JSON writer. This section was already in1087# geometric.py before the perceptual.py merge; build_spatial_code() below is updated to call1088# the geometry functions above DIRECTLY (no `pl.` prefix -- they're plain local functions now1089# that everything is in one file), same logic, unchanged otherwise.1090# ==========================================================================================1091 1092 1093def _room_outline(depth, intr, c2w, conf, bu, bv):1094    """(Currently unemitted -- the one spatial code shape has no room outline field; this1095    math is kept intact for reuse.) Room floor-boundary polygon from the SAME grid as1096    compute_floor_area: floor points1097    projected onto the shared gravity plane (bu, bv), 10cm grid, close 7x7, fill holes,1098    largest contour, 0.2m polygon simplification. Same (bu, bv) as objects/camera, so the1099    outline, object positions, and floor_area all live in one consistent frame."""1100    from scipy import ndimage1101 1102    pts, stride = [], 81103    for f in range(0, depth.shape[0], 3):1104        height, width = depth[f].shape1105        ys, xs = np.mgrid[0:height:stride, 0:width:stride]1106        ys = ys.ravel()1107        xs = xs.ravel()1108        z = depth[f][ys, xs]1109        ok = np.isfinite(z) & (z > 0)1110        if conf is not None:1111            ok &= conf[f][ys, xs] >= np.percentile(conf[f], 40)1112        ys, xs, z = ys[ok], xs[ok], z[ok]1113        if not len(z):1114            continue1115        intrinsics = intr[f]1116        camera_points = np.stack(1117            [1118                (xs - intrinsics[0, 2]) * z / intrinsics[0, 0],1119                (ys - intrinsics[1, 2]) * z / intrinsics[1, 1],1120                z,1121            ],1122            1,1123        )1124        pts.append(1125            ((c2w[f][:3, :3] @ camera_points.T).T + c2w[f][:3, 3]).astype(np.float32)1126        )1127    if not pts:1128        return []1129    world_points = np.concatenate(pts, 0)1130    points = np.stack(1131        [world_points @ bu, world_points @ bv], 11132    )  # gravity-plane projection (same bu,bv as objects/area)1133    lo = np.percentile(points, 0.5, 0)1134    hi = np.percentile(points, 99.5, 0)1135    points = points[1136        (points[:, 0] >= lo[0])1137        & (points[:, 0] <= hi[0])1138        & (points[:, 1] >= lo[1])1139        & (points[:, 1] <= hi[1])1140    ]1141    if len(points) < 10:1142        return []1143    res = 0.101144    x0, y0 = points[:, 0].min(), points[:, 1].min()1145    ai = ((points[:, 0] - x0) / res).astype(int)1146    bi = ((points[:, 1] - y0) / res).astype(int)1147    grid = np.zeros((ai.max() + 3, bi.max() + 3), np.uint8)1148    grid[ai + 1, bi + 1] = 11149    grid = cv2.morphologyEx(grid, cv2.MORPH_CLOSE, np.ones((7, 7), np.uint8))1150    grid = ndimage.binary_fill_holes(grid).astype(np.uint8)1151    cs, _ = cv2.findContours(grid, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)1152    if not cs:1153        return []1154    poly = cv2.approxPolyDP(max(cs, key=cv2.contourArea), 0.2 / res, True)[:, 0, :]1155    # cv2 contour points are (col=bi, row=ai) -> (floor_y, floor_x)1156    return [1157        {1158            "floor_x_meters": round(float((r - 1) * res + x0), 1),1159            "floor_y_meters": round(float((c - 1) * res + y0), 1),1160        }1161        for c, r in poly1162    ]1163 1164 1165def build_spatial_code_raw(depth, intr, c2w, conf, ftimes, per):1166    """Builds THE spatial code -- the one and only shape a spatial code has, everywhere1167    (on disk, in prompts, in this pipeline): unit-strings ("1.4 meters"), spaced keys1168    ("x coordinate"), per-instance position + longest dimension only, room "floor area",1169    "closest classes distance meters from" (rooted per class, distance + closeness rank),1170    and a flat earliest-first "appearance order" list of class names. There is no separate1171    raw/rendered split and no schema flag -- the old v1/v2 branching (VSI_CODE_V2) and the1172    raw intermediate form (floor_x_meters keys, bounding_box, dimensions_meters,1173    seen_in_video_frames, camera_trajectory, room.outline) are gone; every underlying VALUE1174    that survives is computed by exactly the same math as before, only the emitted fields1175    and their formatting changed."""1176    # emission-time class rename: VSI's questions say 'coat rack' while their annotations1177    # (and hence the SAM3 prompt + caches) say 'coat hanger' -- same object, their naming1178    # seam. The model sees questions, so emitted codes follow the question vocabulary.1179    class_aliases = {"coat hanger": "coat rack"}1180    per = {class_aliases.get(k, k): v for k, v in per.items()}1181    inst, stats = build_instances(per, depth, intr, c2w, conf, ftimes)1182    up_vec, up_ax = room_gravity(1183        depth, intr, c2w, conf1184    )  # gravity = RANSAC floor normal (VSI-faithful)1185    bu, bv, bg = _floor_basis(1186        up_vec1187    )  # shared gravity floor frame (bu,bv horizontal, bg up)1188    points = np.concatenate([i["pts"] for cl in inst.values() for i in cl], 0)1189    floor_level = _floor_level(points, bg)1190    fa = compute_floor_area(depth, intr, c2w, conf, None, up_vec=up_vec)1191    code = to_spatial_code(inst, stats, fa, up_ax, up_vec, floor_level)1192    cls = list(inst.keys())1193    class_first = {c: min(i["first_time"] for i in v) for c, v in inst.items()}1194 1195    # Keyed dict + integer ranks (not a sorted list): each question option becomes ONE1196    # direct key access, and "which is closest" = min over small integers -- the filtered1197    # list-scan and decimal comparison were the observed failure modes even on GT data.1198    # 2-decimal distances: 0.1m rounding costs up to ~17% relative error on sub-meter1199    # answers, which fails the strictest MRA thresholds even with perfect values.1200    ccf = {}

Showing the first 1,200 of 1590 lines. Download the file for the rest.