score-back/scripts/scared_overview.py
Artur Mukhamadiev 14ae0a901f
All checks were successful
Verification / Is-Buildable (push) Successful in 2m43s
feat(benchmark): summarize SCARED reconstruction depth
- report valid coverage and depth percentiles even when XYZ ground truth is absent

- accept depth bounds and optional color-mapped PNG output for visual inspection

- retain accuracy metrics when point_cloud.obj is available

- add a batch helper that renders Markdown tables and optional JSON and PNG artifacts

- document benchmark inputs, dataset limits, and visualization tradeoffs
2026-09-14 10:43:29 +03:00

84 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run scared_dataset_benchmark over many keyframes and print a Markdown table.
Usage:
scripts/scared_overview.py [--bench PATH] [--disparities N] [--png-dir DIR]
[--json-dir DIR] [--depth-range MIN_M MAX_M]
KEYFRAME_DIR...
Each KEYFRAME_DIR must hold Left_Image.png, Right_Image.png and
endoscope_calibration.yaml. Accuracy columns are filled in only for keyframes
that also contain point_cloud.obj.
"""
import argparse
import json
import os
import subprocess
import sys
def run(bench, kf, disparities, png_dir, json_dir, depth_range):
label = "/".join(kf.rstrip("/").split("/")[-2:])
cmd = [bench, kf, str(disparities)]
if png_dir:
os.makedirs(png_dir, exist_ok=True)
cmd.append(os.path.join(png_dir, label.replace("/", "_") + ".png"))
else:
cmd.append("-")
if depth_range:
cmd += [str(depth_range[0]), str(depth_range[1])]
proc = subprocess.run(cmd, capture_output=True, text=True)
if proc.returncode != 0:
print(f"{label}: benchmark failed\n{proc.stderr}", file=sys.stderr)
return label, None
result = json.loads(proc.stdout)
if json_dir:
os.makedirs(json_dir, exist_ok=True)
with open(os.path.join(json_dir, label.replace("/", "_") + ".json"), "w") as f:
json.dump(result, f, indent=2)
return label, result
def fmt_mm(v):
return "" if v is None else f"{v * 1000:.2f}"
def fmt_pct(v):
return "" if v is None else f"{v * 100:.1f}"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--bench", default="build-opencv/src/cloud_point/scared_dataset_benchmark")
ap.add_argument("--disparities", type=int, default=160)
ap.add_argument("--png-dir")
ap.add_argument("--json-dir")
ap.add_argument("--depth-range", nargs=2, type=float, metavar=("MIN_M", "MAX_M"),
help="depth filter in metres (default: builder defaults 0.01..10)")
ap.add_argument("keyframes", nargs="+")
args = ap.parse_args()
rows = [run(args.bench, kf, args.disparities, args.png_dir, args.json_dir,
args.depth_range)
for kf in args.keyframes]
print("| keyframe | valid % | z p5 / median / p95 (mm) | match ms | GT | coverage % | MAE3D mm | RMSE3D mm | median mm | <1 mm % | <2 mm % | <5 mm % |")
print("|---|---|---|---|---|---|---|---|---|---|---|---|")
for label, r in rows:
if r is None:
print(f"| {label} | failed | | | | | | | | | | |")
continue
z = f"{r['z_p05_m']*1000:.0f} / {r['z_median_m']*1000:.0f} / {r['z_p95_m']*1000:.0f}"
gt = r.get("has_ground_truth", False)
print("| {} | {} | {} | {:.0f} | {} | {} | {} | {} | {} | {} | {} | {} |".format(
label, fmt_pct(r["valid_fraction"]), z, r["matching_ms"],
"yes" if gt else "no",
fmt_pct(r.get("coverage")), fmt_mm(r.get("mae_3d_m")),
fmt_mm(r.get("rmse_3d_m")), fmt_mm(r.get("median_3d_m")),
fmt_pct(r.get("within_1mm")), fmt_pct(r.get("within_2mm")),
fmt_pct(r.get("within_5mm"))))
if __name__ == "__main__":
main()