#!/usr/bin/env python3 """Recover missing camera4.json files for downloaded standard-path issue data.""" from __future__ import annotations import argparse import json import sys from datetime import datetime from pathlib import Path FILE = Path(__file__).resolve() ROOT = FILE.parents[2] if str(ROOT) not in sys.path: sys.path.append(str(ROOT)) from tools.feishu_project.case_calib_recovery import CalibRecoveryResult, recover_camera4_json DEFAULT_DOWNLOAD_ROOT = Path("/data1/dongying/Mono3d/G1Q3/feishu_project/downloaded_issue_data") def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--download-root", default=str(DEFAULT_DOWNLOAD_ROOT), help="Root directory containing downloaded issue data.", ) parser.add_argument( "--issue-id", action="append", dest="issue_ids", type=int, help="Only process the specified issue id. Can be repeated.", ) parser.add_argument( "--path-root", action="append", default=[], help=( "Optional explicit copied target root to recover. Can be repeated. " "Example: downloaded_issue_data/issue_x/path_01/case_dir" ), ) parser.add_argument( "--manifest-path", default="", help="Optional explicit manifest path. Defaults to /calib_recovery_manifest.json", ) parser.add_argument("--dry-run", action="store_true") return parser.parse_args() def log_progress(message: str) -> None: timestamp = datetime.now().astimezone().strftime("%Y-%m-%d %H:%M:%S") print(f"[recover_downloaded_issue_calibs {timestamp}] {message}", flush=True) def discover_target_roots(download_root: Path, issue_ids: list[int], explicit_roots: list[str]) -> list[Path]: if explicit_roots: return sorted({Path(path).resolve() for path in explicit_roots}) issue_dirs: list[Path] if issue_ids: issue_dirs = [download_root / f"issue_{issue_id}" for issue_id in sorted(set(issue_ids))] else: issue_dirs = sorted(path for path in download_root.glob("issue_*") if path.is_dir()) targets: list[Path] = [] seen: set[Path] = set() for issue_dir in issue_dirs: if not issue_dir.is_dir(): continue for path_dir in sorted(path for path in issue_dir.glob("path_*") if path.is_dir()): direct_case = path_dir / "sigmastar.1" / "camera4.bin" if direct_case.is_file() and path_dir not in seen: seen.add(path_dir) targets.append(path_dir) for child in sorted(path_dir.iterdir()): if not child.is_dir() or child.name == "test_data": continue if child in seen: continue seen.add(child) targets.append(child) return targets def result_to_dict(target_root: Path, result: CalibRecoveryResult) -> dict: return { "target_root": str(target_root), "target_path": str(result.target_path), "status": result.status, "detail": result.detail, "source_path": None if result.source_path is None else str(result.source_path), "source_kind": result.source_kind, } def build_manifest( download_root: Path, dry_run: bool, issue_ids: list[int], target_roots: list[Path], results: list[dict], ) -> dict: summary: dict[str, int] = {} for result in results: status = result["status"] summary[status] = summary.get(status, 0) + 1 return { "generated_at": datetime.now().astimezone().isoformat(timespec="seconds"), "download_root": str(download_root), "dry_run": dry_run, "issue_filter": issue_ids, "target_roots": [str(path) for path in target_roots], "summary": summary, "results": results, } def print_summary(manifest: dict) -> None: print(f"download_root: {manifest['download_root']}") print(f"dry_run: {manifest['dry_run']}") print(f"target_roots: {len(manifest['target_roots'])}") for status, count in sorted(manifest["summary"].items()): print(f"{status}: {count}") def main() -> int: args = parse_args() download_root = Path(args.download_root).resolve() manifest_path = ( Path(args.manifest_path).resolve() if args.manifest_path else download_root / "calib_recovery_manifest.json" ) target_roots = discover_target_roots(download_root, args.issue_ids or [], args.path_root) log_progress(f"targets_to_process: {len(target_roots)}") results: list[dict] = [] for index, target_root in enumerate(target_roots, start=1): log_progress(f"[{index}/{len(target_roots)}] start: {target_root}") recovery = recover_camera4_json( source_root=target_root, target_root=target_root, dry_run=args.dry_run, ) results.append(result_to_dict(target_root, recovery)) log_progress(f"[{index}/{len(target_roots)}] done: {recovery.status}") manifest = build_manifest( download_root=download_root, dry_run=args.dry_run, issue_ids=args.issue_ids or [], target_roots=target_roots, results=results, ) if not args.dry_run: manifest_path.parent.mkdir(parents=True, exist_ok=True) manifest_path.write_text( json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) print_summary(manifest) if args.dry_run: print(f"manifest (not written in dry-run): {manifest_path}") else: print(f"manifest: {manifest_path}") return 0 if __name__ == "__main__": raise SystemExit(main())