Source: docs/experiments/EXP-3D-CAJU3D-KFUSE-V1/exploration_posthoc/hole_probe.py · Plain text

docs/experiments/EXP-3D-CAJU3D-KFUSE-V1/exploration_posthoc/hole_probe.py

"""EXP-3D-CAJU3D-KFUSE-V1 post-hoc exploration (FIT cohorts only; their truth is fitting data): can the uncovered
pixels (TTT alpha < 0.5, where Core B and every KFUSE arm keep R7) gain from the TTT ray depth depth_z / alpha / cos?

Per uncovered valid pixel: RAY = the TTT ray depth (alpha floored at 1e-3), R7, NEAR (near7), ALPHA. Bins: ALPHA in
[0, .05, .1, .2, .3, .4, .5) x NEAR x DIS (4 quantile bins of |log RAY - log R7|); per bin the weight w of
R7^(1 - w) RAY^w minimizing the place-weighted AbsRel. Reported in-sample and leave-one-place-out on FIT, added to
KF_BASE's frozen covered-pixel readout. Not a confirmation: an input to a possible later preregistration.
Run from the repository root (CUDA hidden, PYTHONPATH=src):  python hole_probe.py ROOT OUT.json
"""

import json
import sys
from pathlib import Path

import numpy as np

ROOT = Path(sys.argv[1]).resolve()
sys.path.insert(0, str(ROOT / "code"))
import kf_data as D  # noqa: E402
import kf_logic as K  # noqa: E402
from score_analyze import contrast  # noqa: E402

S = D.S
ALPHA_EDGES = np.array([0.05, 0.1, 0.2, 0.3, 0.4])
DIS_BINS = 4


def load(cohort, access):
    """kf_data.load_cohort plus the raw TTT ray depth on the same valid pixels."""
    queries = D.load_cohort(ROOT, cohort, access)
    reused = json.loads((ROOT / "reused_seals.json").read_text())[cohort]
    zs = S.reused_predictions(reused["ZS"])
    geometry = json.loads(Path(reused["geometry"]["path"]).read_text())["predictions"]
    lock = json.loads((ROOT / "frame_role_lock.json").read_text())["cohorts"][cohort]
    records = {r["scene_id"]: r for r in json.loads(Path(lock["manifest"]).read_text())["scenes"]}
    by_key = {q["key"]: q for q in queries}
    for e in lock["episodes"]:
        teacher = S.TeacherAccess(records[e["scene_id"]], e["queries"], S.IMAGE_SIZE, S.REPO, access)
        for qid in e["queries"]:
            key = f"{e['scene_id']}/{e['role']}/{qid}"
            ttt, geo = S.load_checked(zs[key]), S.load_checked(geometry[key])
            gt = teacher.query_depth(qid)
            valid = np.isfinite(gt) & (gt > 0)
            ray = ttt["TTT_m1_depth_z"] / np.maximum(ttt["TTT_m1_alpha"].astype(np.float64), 1e-3) / geo["cosine"]
            by_key[key]["ray"] = np.maximum(ray[valid], 1e-6)
    return queries


def main():
    tables = json.loads((ROOT / "fusion_tables.json").read_text())
    access, queries = [], []
    for cohort in ("FIT_SYSTEM", "FIT_SYSTEM2"):
        queries += load(cohort, access)
    pw = K.place_weights(queries)
    weights = K.WEIGHTS
    unc = [q["alpha"] < K.ALPHA_MIN for q in queries]
    dis_all = np.concatenate([np.abs(np.log(q["ray"][u]) - np.log(q["r7"][u])) for q, u in zip(queries, unc)])
    dis_edges = np.quantile(dis_all, np.linspace(0, 1, DIS_BINS + 1)[1:-1])
    n_bins = (len(ALPHA_EDGES) + 1) * 2 * DIS_BINS
    loss_by_place, base = {}, {}
    for i, (q, u) in enumerate(zip(queries, unc)):
        a = np.digitize(q["alpha"][u], ALPHA_EDGES)
        near = q["near"][u].astype(np.int64)
        d = np.digitize(np.abs(np.log(q["ray"][u]) - np.log(q["r7"][u])), dis_edges)
        idx = (a * 2 + near) * DIS_BINS + d
        lr, lf, g = np.log(q["r7"][u]), np.log(q["ray"][u]), q["gt"][u]
        err = np.abs(np.exp((1 - weights)[:, None] * lr + weights[:, None] * lf) - g) / g * pw[i]
        m = loss_by_place.setdefault(q["place"], np.zeros((n_bins, len(weights))))
        for j in range(len(weights)):
            m[:, j] += np.bincount(idx, weights=err[j], minlength=n_bins)
        q["hole_idx"] = idx
        # KF_BASE's frozen readout on the covered pixels, R7 on the uncovered ones
        base[i] = K.readout(q, "KF_BASE", tables)
    total = sum(loss_by_place.values())
    j_all = K.fit_weights(loss_by_place)

    def hole_readout(q, i, j_bin):
        out = base[i].copy()
        u = q["alpha"] < K.ALPHA_MIN
        w = weights[j_bin[q["hole_idx"]]]
        out[u] = np.exp((1 - w) * np.log(q["r7"][u]) + w * np.log(q["ray"][u]))
        return out

    rows = []
    for place in sorted(loss_by_place):
        j_loo = K.fit_weights(loss_by_place, exclude=place)
        for i, q in enumerate(queries):
            if q["place"] != place:
                continue
            rows.append({"place": q["place"], "episode": q["episode"], "query": q["key"], "core_b": {
                "KF_BASE": float(np.mean(np.abs(base[i] - q["gt"]) / q["gt"])),
                "KF_BASE_HOLE_INSAMPLE": float(np.mean(np.abs(hole_readout(q, i, j_all) - q["gt"]) / q["gt"])),
                "KF_BASE_HOLE_LOO": float(np.mean(np.abs(hole_readout(q, i, j_loo) - q["gt"]) / q["gt"]))}})
    table = S.place_table(rows)
    out = {"means": {k: S.mean(v) for k, v in table.items()},
           "KF_BASE_minus_HOLE_LOO": contrast(table, "KF_BASE", "KF_BASE_HOLE_LOO")}
    share = total.sum(axis=1)
    out["bins"] = [{"alpha_bin": int(b // (2 * DIS_BINS)), "near": int((b // DIS_BINS) % 2), "dis_bin": int(b % DIS_BINS),
                    "loss_share": float(share[b] / share.sum()), "w": float(weights[j_all[b]]),
                    "gain_vs_r7": float(total[b, 0] - total[b, j_all[b]])} for b in range(n_bins)]
    out["uncovered_error_at_r7"] = float(total[:, 0].sum())
    out["dis_edges"] = dis_edges.tolist()
    Path(sys.argv[2]).write_text(json.dumps(out, indent=1))
    c = out["KF_BASE_minus_HOLE_LOO"]
    print("means", json.dumps({k: round(v, 5) for k, v in out["means"].items()}))
    print("KF_BASE - HOLE_LOO", round(c["mean"], 5), [round(x, 5) for x in c["ci95"]], c["improved_tied_worse"], c["status"])
    print("uncovered place-weighted AbsRel at R7:", round(out["uncovered_error_at_r7"], 5))
    for b in out["bins"]:
        if b["gain_vs_r7"] > 1e-5:
            print("  bin", b)


if __name__ == "__main__":
    main()