Source: scripts/report_rgbd_fresh_qualification.py · Plain text

scripts/report_rgbd_fresh_qualification.py

#!/usr/bin/env python3
"""Report the frozen FRESH-V1 qualification of the V5 RGB-D carriers (saved results only)."""
# ruff: noqa: E501 -- Chinese scientific report prose

import argparse
import json
import shutil
from collections import defaultdict
from pathlib import Path

import numpy as np


def read(root, name, default=None):
    path = root / (name + ".json")
    if not path.exists() and default is not None:
        return default
    return json.loads(path.read_text())


def stat(value):
    if value is None or value.get("mean") is None:
        return "UNDEFINED"
    lo, hi = value["ci95"]
    return f"{value['mean']:+.4f},95% CI [{lo:+.4f}, {hi:+.4f}]"


def per_scene(root):
    rows = read(root, "raw/fresh_readout_rows", [])
    refs = read(root, "raw/fresh_reference_rows", [])
    table = defaultdict(dict)
    for variant in ("C0", "C1", "C2"):
        values = defaultdict(list)
        for r in rows:
            if r["variant"] == variant and r["method"] == "direct" and r["readout"] == "RAW":
                values[r["scene_id"]].append(r["depth_absrel"])
        for scene, v in values.items():
            table[scene][variant] = float(np.mean(v))
    constant = defaultdict(list)
    for r in refs:
        if r["reference"] == "REF_TRAIN_ABSREL_OPTIMAL" and r["readout"] == "RAW":
            constant[r["scene_id"]].append(r["depth_absrel"])
    for scene, v in constant.items():
        table[scene]["constant"] = float(np.mean(v))
    return dict(table)


def run(root, docs=None):
    root = Path(root)
    results = read(root, "static_results")
    qualification = read(root, "qualification_results")
    controls = read(root, "wrong_scene_results")["contrasts"]
    obs = read(root, "observability_analysis")
    reference = read(root, "reference_results", {}) or None
    contract = read(root, "qualification_contract")
    v5 = Path(contract["v5_root"])
    v5_summary = (v5 / "terminal_summary.txt").read_text().splitlines()
    v5_fields = dict(line.split("=", 1) for line in v5_summary if "=" in line)
    if results["primary"] != "C1_SURFACE16":
        raise PermissionError("Frozen V2 estimator primary label expected (C1 = V5 primary)")
    groups = results["groups"]
    depth = results["surface_gain"]
    wrong, shuffle = controls["C1|wrong_scene"], controls["C1|spatial_shuffle"]
    depth_status = qualification["SURFACE_TRAINING_STATUS"]
    specificity = (
        "SUPPORTED" if wrong["ci95"][0] > 0 and shuffle["ci95"][0] > 0 else "NOT_ESTABLISHED"
    )
    static = qualification["STATIC_DEV_STATUS"]
    verdict = (
        "QUALIFIED"
        if depth_status == "SUPPORTED" and specificity == "SUPPORTED" and static == "SUPPORTED"
        else "NOT_QUALIFIED"
    )
    reference_status = (
        reference["summary"]["CARRIER_REFERENCE_STATUS"] if reference else "NOT_RECORDED"
    )
    c1_reference = reference["carriers"]["V5_C1"]["RAW"]["depth_absrel"] if reference else None
    sensitivity = reference.get("sensitivity_excluding_no_hit_scenes", {}) if reference else {}
    table = per_scene(root)
    fields = {
        "FRESH_SCENES": len(results["scene_ids"]),
        "FRESH_SCENE_IDS": results["scene_ids"],
        "DENSE_QUERY_ABSREL_FRESH": round(
            groups["C0|direct"]["query_metrics"]["depth_absrel"]["mean"], 6
        ),
        "DEPTH_QUERY_ABSREL_FRESH": round(
            groups["C1|direct"]["query_metrics"]["depth_absrel"]["mean"], 6
        ),
        "DEPTH_GAIN_FRESH": round(depth["mean"], 6),
        "DEPTH_GAIN_FRESH_CI": depth["ci95"],
        "DEPTH_GAIN_DEV_V5": v5_fields.get("DEPTH_GAIN"),
        "C1_WRONG_SCENE_DAMAGE_FRESH": round(wrong["mean"], 6),
        "C1_WRONG_SCENE_DAMAGE_FRESH_CI": wrong["ci95"],
        "C1_SHUFFLE_DAMAGE_FRESH": round(shuffle["mean"], 6),
        "C1_SHUFFLE_DAMAGE_FRESH_CI": shuffle["ci95"],
        "C1_FULL_CONTEXT_GAIN_FRESH": round(results["full_context_gain"]["C1"]["mean"], 6),
        "C1_FULL_CONTEXT_GAIN_FRESH_CI": results["full_context_gain"]["C1"]["ci95"],
        "DEPTH_STATUS_FRESH": depth_status,
        "SCENE_SPECIFICITY_STATUS_FRESH": specificity,
        "STATIC_STATUS_FRESH": static,
        "SEED_ROBUSTNESS_FRESH": qualification["SEED_ROBUSTNESS"],
        "CARRIER_REFERENCE_STATUS_FRESH": reference_status,
        "C1_REFERENCE_LABEL_FRESH": c1_reference["reference_label"] if c1_reference else None,
        "C1_REFERENCE_LABEL_FRESH_HIT_SCENES": sensitivity.get("carriers", {})
        .get("V5_C1", {})
        .get("RAW", {})
        .get("depth_absrel", {})
        .get("reference_label", "NO_NO_HIT_SCENE"),
        "FRESH_QUALIFICATION_STATUS": verdict,
        "CORE_A_STATIC_CARRIER_RGBD_TRACK": "QUALIFIED"
        if verdict == "QUALIFIED"
        else "NOT_QUALIFIED",
        "DYNAMIC_TTT_NEXT_STAGE_ALLOWED": verdict == "QUALIFIED",
        "FINAL_HOLDOUT_OPENED": False,
        "TRAINING_RUN": False,
        "DEVICE": "cpu",
    }
    lines = [
        "# EXP-3D-RGBD-FRESH-QUALIFICATION-V1",
        "",
        f"`FRESH_QUALIFICATION_STATUS={verdict}`。",
        "",
        "对 V5 已封存的 RGB-D carrier(每个 seed 各自的 DEV 选择,不重新训练)在独立队列 FRESH-V1 上做预注册资格验证。"
        f"FRESH-V1 共 {fields['FRESH_SCENES']} 个场景,均来自官方 val split、从未被观察、与 V2–V5 的 TRAIN/DEV volume 不重叠,每个 volume 一个场景。"
        "所有 fresh 状态封存之后才读取 query GT;统计沿用冻结的 V2 估计器与 V5 的 gate;受保护的官方 final holdout 未打开。",
        "",
        f"样本量说明:{fields['FRESH_SCENES']} 个场景低于数据锁的最低 6 个(修订 1 没有找到替代场景)。QUALIFIED 仍要求每一个冻结 gate;NOT_QUALIFIED 可能反映统计功效不足。",
        "",
        "| 项目 | FRESH-V1 | V5 DEV(参照) |",
        "|---|---|---|",
        f"| C0 只用 RGB,query AbsRel | {fields['DENSE_QUERY_ABSREL_FRESH']:.4f} | {v5_fields.get('DENSE_QUERY_ABSREL')} |",
        f"| C1 测得深度,query AbsRel | {fields['DEPTH_QUERY_ABSREL_FRESH']:.4f} | {v5_fields.get('DEPTH_QUERY_ABSREL')} |",
        f"| DEPTH_GAIN | {stat(depth)} | {v5_fields.get('DEPTH_GAIN')} |",
        f"| C1 wrong-scene 损伤 | {stat(wrong)} | {v5_fields.get('DEPTH_WRONG_SCENE_DAMAGE')} |",
        f"| C1 shuffle 损伤 | {stat(shuffle)} | {v5_fields.get('DEPTH_SHUFFLE_DAMAGE')} |",
        f"| 3 视角相对 anchor | {stat(results['full_context_gain']['C1'])} | {v5_fields.get('DEPTH_FULL_CONTEXT_GAIN')} |",
        f"| DEPTH_STATUS | {depth_status} | {v5_fields.get('DEPTH_STATUS')} |",
        f"| SCENE_SPECIFICITY_STATUS | {specificity} | {v5_fields.get('SCENE_SPECIFICITY_STATUS')} |",
        f"| STATIC_STATUS | {static} | {v5_fields.get('STATIC_DEV_STATUS')} |",
        "",
        "## 逐场景(RAW AbsRel,seed 与角色平均)",
        "",
        "| 场景 | 常数(TRAIN 拟合) | C0 | C1 | C2 |",
        "|---|---:|---:|---:|---:|",
    ]
    for scene in sorted(table):
        t = table[scene]
        lines.append(
            f"| {scene} | {t.get('constant', float('nan')):.3f} | {t.get('C0', float('nan')):.3f} | "
            f"{t.get('C1', float('nan')):.3f} | {t.get('C2', float('nan')):.3f} |"
        )
    lines += [
        "",
        "## 与无几何常数相比(描述性)",
        "",
        f"CARRIER_REFERENCE_STATUS={reference_status};C1 RAW AbsRel 相对 AbsRel 最优常数:"
        + (
            stat(c1_reference["vs_REF_TRAIN_ABSREL_OPTIMAL"])
            + f"({c1_reference['reference_label']})"
            if c1_reference
            else "未记录"
        )
        + f";排除 NO_HIT 场景后标签:{fields['C1_REFERENCE_LABEL_FRESH_HIT_SCENES']}。",
        "",
        "## 区域诊断(描述性)",
        "",
        "C0−C1 条件 AbsRel:"
        + ";".join(f"{r}={stat(obs['gains'][r])}" for r in ("OBS0", "OBS1", "OBS2PLUS"))
        + "。",
        "",
        "## 结论",
        "",
        (
            "三个冻结 gate 在独立队列上全部复现:Core A 的静态 carrier 在 RGB-D 轨道上通过资格验证。按项目规则,Core B(动态 TTT 写入)的下一阶段可以开启;官方 final holdout 仍保持关闭。"
            if verdict == "QUALIFIED"
            else "冻结 gate 未在独立队列上全部复现:Core A 在 RGB-D 轨道上尚未通过资格验证,Core B 保持关闭。未通过的 gate 见上表;由于只有 "
            f"{fields['FRESH_SCENES']} 个场景,需区分“效应不存在”与“功效不足”。"
        ),
    ]
    summary = (
        "RGBD FRESH QUALIFICATION V1\n"
        + "\n".join(
            f"{k}={str(v).lower() if isinstance(v, bool) else v}" for k, v in fields.items()
        )
        + "\n"
    )
    (root / "README.md").write_text("\n".join(lines) + "\n")
    (root / "STATUS.md").write_text("```text\n" + summary + "```\n")
    (root / "terminal_summary.txt").write_text(summary)
    (root / "fresh_qualification_results.json").write_text(
        json.dumps({**fields, "per_scene": table}, indent=2, ensure_ascii=False) + "\n"
    )
    if docs:
        docs = Path(docs)
        docs.mkdir(parents=True, exist_ok=True)
        for p in root.iterdir():
            if p.is_file() and p.suffix in (".json", ".md", ".txt"):
                shutil.copyfile(p, docs / p.name)
    print(summary)
    return fields


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--root", type=Path, required=True)
    parser.add_argument("--docs", type=Path)
    args = parser.parse_args()
    run(args.root, args.docs)