Files
yolov26_3d/tools/feishu_project/case_calib_recovery.py

327 lines
11 KiB
Python
Raw Normal View History

2026-06-24 09:35:46 +08:00
#!/usr/bin/env python3
"""Recover camera4.json for downloaded standard-path issue cases."""
from __future__ import annotations
import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable, Optional
TARGET_CALIB_REL = Path("test_data") / "calibs" / "camera4.json"
CALIB_ATTACHMENT_NAMES = (
"sigmastar.1/calibs/camera4.json",
"test_data/calibs/camera4.json",
"calibs/camera4.json",
)
CAMERA_CONFIG_FILE_NAME = "camera_config_folder.bin"
PREFERRED_CAMERA_IDS = (4, 0)
REQUIRED_FLAT_CALIB_KEYS = ("focal_u", "focal_v", "cu", "cv")
@dataclass(frozen=True)
class ExtractedCalibSource:
payload: dict[str, Any]
source_kind: str
source_path: Path
detail: str
@dataclass(frozen=True)
class CalibRecoveryResult:
status: str
detail: str
target_path: Path
source_path: Path | None = None
source_kind: str | None = None
def _safe_int(value: Any) -> int | None:
try:
if value is None:
return None
return int(value)
except (TypeError, ValueError):
return None
def _sorted_unique_paths(paths: Iterable[Path]) -> list[Path]:
unique = {path.resolve() if path.exists() else path for path in paths}
return sorted(unique, key=lambda path: (len(path.parts), str(path)))
def _camera_attachment_matches(name: str) -> bool:
normalized = str(name).strip().replace("\\", "/")
if normalized in CALIB_ATTACHMENT_NAMES:
return True
return normalized.endswith("/camera4.json") or normalized == "camera4.json"
def iter_concatenated_json_objects(text: str) -> list[dict[str, Any]]:
decoder = json.JSONDecoder()
pos = 0
payloads: list[dict[str, Any]] = []
text_len = len(text)
while pos < text_len:
while pos < text_len and (text[pos].isspace() or text[pos] == "\x00"):
pos += 1
if pos >= text_len:
break
obj, end = decoder.raw_decode(text, pos)
if isinstance(obj, dict):
payloads.append(obj)
pos = end
return payloads
def _coerce_float_list(value: Any, *, min_len: int = 0) -> list[float]:
if isinstance(value, dict):
ordered = [value.get("x"), value.get("y"), value.get("z")]
result = [float(item) for item in ordered if item is not None]
elif isinstance(value, (list, tuple)):
result = [float(item) for item in value]
else:
result = []
while len(result) < min_len:
result.append(0.0)
return result
def _is_valid_flat_calib_payload(payload: dict[str, Any]) -> bool:
return all(payload.get(key) is not None for key in REQUIRED_FLAT_CALIB_KEYS)
def _build_flat_camera4_payload(record: dict[str, Any], source_path: Path) -> dict[str, Any]:
payload = dict(record)
payload["focal_u"] = float(record["focal_u"])
payload["focal_v"] = float(record["focal_v"])
payload["cu"] = float(record["cu"])
payload["cv"] = float(record["cv"])
payload["roll"] = float(record.get("roll", 0.0))
payload["pitch"] = float(record.get("pitch", 0.0))
payload["yaw"] = float(record.get("yaw", 0.0))
payload["distort_coeffs"] = _coerce_float_list(record.get("distort_coeffs"))
payload["pos"] = _coerce_float_list(record.get("pos"), min_len=3)[:3]
if payload.get("image_width") is not None:
payload["image_width"] = int(payload["image_width"])
if payload.get("image_height") is not None:
payload["image_height"] = int(payload["image_height"])
payload.setdefault("camera_id", 4)
payload["recovered_from"] = CAMERA_CONFIG_FILE_NAME
payload["recovered_from_path"] = str(source_path)
return payload
def extract_calib_from_camera_config_folder(
config_path: Path,
preferred_camera_ids: tuple[int, ...] = PREFERRED_CAMERA_IDS,
) -> ExtractedCalibSource | None:
text = config_path.read_text(encoding="utf-8")
records = iter_concatenated_json_objects(text)
valid_records = [record for record in records if _is_valid_flat_calib_payload(record)]
if not valid_records:
return None
selected_records = valid_records
available_camera_ids = {
_safe_int(record.get("camera_id"))
for record in valid_records
if _safe_int(record.get("camera_id")) is not None
}
chosen_camera_id: int | None = None
for camera_id in preferred_camera_ids:
preferred_records = [
record for record in valid_records if _safe_int(record.get("camera_id")) == camera_id
]
if preferred_records:
selected_records = preferred_records
chosen_camera_id = camera_id
break
if chosen_camera_id is None:
if len(available_camera_ids) == 1:
chosen_camera_id = next(iter(available_camera_ids))
else:
counts: dict[int, int] = {}
for record in valid_records:
camera_id = _safe_int(record.get("camera_id"))
if camera_id is None:
continue
counts[camera_id] = counts.get(camera_id, 0) + 1
if counts:
chosen_camera_id = max(counts.items(), key=lambda item: item[1])[0]
selected_records = [
record for record in valid_records if _safe_int(record.get("camera_id")) == chosen_camera_id
]
best_record = max(
enumerate(selected_records),
key=lambda item: (_safe_int(item[1].get("utc_tick")) or -1, item[0]),
)[1]
payload = _build_flat_camera4_payload(best_record, config_path)
detail = (
f"camera_id={chosen_camera_id if chosen_camera_id is not None else 'unknown'} "
f"records={len(valid_records)} latest_utc_tick={best_record.get('utc_tick')}"
)
return ExtractedCalibSource(
payload=payload,
source_kind="camera_config_folder",
source_path=config_path,
detail=detail,
)
def _extract_calib_from_mcap_with_clip_reader(mcap_path: Path) -> ExtractedCalibSource | None:
try:
from pdcl_pyclip.reader import ClipReader
except ImportError:
return None
reader = ClipReader(str(mcap_path))
for attachment in reader.iter_attachments():
if _camera_attachment_matches(attachment.name):
payload = json.loads(attachment.data.decode("utf-8"))
return ExtractedCalibSource(
payload=payload,
source_kind="mcap_attachment",
source_path=mcap_path,
detail=f"attachment={attachment.name}",
)
return None
def _extract_calib_from_mcap_with_generic_reader(mcap_path: Path) -> ExtractedCalibSource | None:
try:
from mcap.reader import make_reader
except ImportError:
return None
with mcap_path.open("rb") as file:
reader = make_reader(file)
for attachment in reader.iter_attachments():
if _camera_attachment_matches(attachment.name):
payload = json.loads(attachment.data.decode("utf-8"))
return ExtractedCalibSource(
payload=payload,
source_kind="mcap_attachment",
source_path=mcap_path,
detail=f"attachment={attachment.name}",
)
return None
def extract_calib_from_mcap_attachment(mcap_path: Path) -> ExtractedCalibSource | None:
errors: list[str] = []
for extractor in (_extract_calib_from_mcap_with_clip_reader, _extract_calib_from_mcap_with_generic_reader):
try:
extracted = extractor(mcap_path)
except Exception as exc: # pragma: no cover - recovery fallback logging
errors.append(f"{extractor.__name__}: {type(exc).__name__}: {exc}")
continue
if extracted is not None:
return extracted
if errors:
raise RuntimeError("; ".join(errors))
return None
def _find_candidate_camera_config_paths(source_root: Path) -> list[Path]:
return _sorted_unique_paths(source_root.rglob(CAMERA_CONFIG_FILE_NAME))
def _find_candidate_mcap_paths(source_root: Path) -> list[Path]:
return _sorted_unique_paths(source_root.rglob("*.mcap"))
def _write_camera4_json(target_path: Path, payload: dict[str, Any]) -> None:
target_path.parent.mkdir(parents=True, exist_ok=True)
target_path.write_text(
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8",
)
def recover_camera4_json(
source_root: Path,
target_root: Path,
dry_run: bool = False,
) -> CalibRecoveryResult:
target_path = target_root / TARGET_CALIB_REL
if target_path.is_file():
return CalibRecoveryResult(
status="skipped_existing_calib",
detail=f"target already exists: {target_path}",
target_path=target_path,
)
camera_config_paths = _find_candidate_camera_config_paths(source_root)
mcap_paths = _find_candidate_mcap_paths(source_root)
errors: list[str] = []
for config_path in camera_config_paths:
try:
extracted = extract_calib_from_camera_config_folder(config_path)
except Exception as exc:
errors.append(f"{config_path}: {type(exc).__name__}: {exc}")
continue
if extracted is None:
continue
if dry_run:
return CalibRecoveryResult(
status="planned_calib_recovery_from_camera_config_folder",
detail=f"would write {target_path} from {config_path} ({extracted.detail})",
target_path=target_path,
source_path=config_path,
source_kind=extracted.source_kind,
)
_write_camera4_json(target_path, extracted.payload)
return CalibRecoveryResult(
status="recovered_calib_from_camera_config_folder",
detail=f"wrote {target_path} from {config_path} ({extracted.detail})",
target_path=target_path,
source_path=config_path,
source_kind=extracted.source_kind,
)
for mcap_path in mcap_paths:
try:
extracted = extract_calib_from_mcap_attachment(mcap_path)
except Exception as exc:
errors.append(f"{mcap_path}: {type(exc).__name__}: {exc}")
continue
if extracted is None:
continue
if dry_run:
return CalibRecoveryResult(
status="planned_calib_recovery_from_mcap_attachment",
detail=f"would write {target_path} from {mcap_path} ({extracted.detail})",
target_path=target_path,
source_path=mcap_path,
source_kind=extracted.source_kind,
)
_write_camera4_json(target_path, extracted.payload)
return CalibRecoveryResult(
status="recovered_calib_from_mcap_attachment",
detail=f"wrote {target_path} from {mcap_path} ({extracted.detail})",
target_path=target_path,
source_path=mcap_path,
source_kind=extracted.source_kind,
)
checked_sources = (
f"camera_config_folder.bin={len(camera_config_paths)} mcap={len(mcap_paths)}"
)
if errors:
checked_sources += f"; errors: {' | '.join(errors[:3])}"
return CalibRecoveryResult(
status="failed_calib_recovery",
detail=f"no recoverable calibration found under {source_root} ({checked_sources})",
target_path=target_path,
)