Files
NODEDC_MISSION_CORE/tests/test_kitti_3d_admission.py
T

231 lines
7.6 KiB
Python

from __future__ import annotations
import hashlib
import json
import zipfile
from pathlib import Path
from typing import Any
import pytest
from k1link.datasets import kitti_3d_admission as module
def _sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _calibration(*, complete: bool = True) -> str:
rows = [
"P0: " + " ".join(["1"] * 12),
"P1: " + " ".join(["1"] * 12),
"P2: " + " ".join(["1"] * 12),
"P3: " + " ".join(["1"] * 12),
"R0_rect: " + " ".join(["1"] * 9),
"Tr_velo_to_cam: " + " ".join(["1"] * 12),
]
if complete:
rows.append("Tr_imu_to_velo: " + " ".join(["1"] * 12))
return "\n".join(rows) + "\n"
def _label(class_name: str, *, valid: bool = True) -> str:
dimensions = "1.5 1.6 3.8" if valid else "0 1.6 3.8"
return (
f"{class_name} 0 0 0 0 0 10 10 {dimensions} 1 1 10 0\n"
)
def _release(
root: Path,
monkeypatch: pytest.MonkeyPatch,
*,
labels: dict[str, str] | None = None,
complete_calibration: bool = True,
invalid_velodyne_frame: bool = False,
train_rows: tuple[str, ...] = ("000000",),
validation_rows: tuple[str, ...] = ("000001", "000002"),
) -> dict[str, Path]:
archive_root = root / module.KITTI_3D_RELEASE_ROOT / "archives"
split_root = root / module.KITTI_3D_RELEASE_ROOT / "splits" / (
f"openpcdet-{module.KITTI_STANDARD_SPLIT_COMMIT}"
)
archive_root.mkdir(parents=True)
split_root.mkdir(parents=True)
paths = {
module.KITTI_VELODYNE_ARCHIVE: archive_root
/ module.KITTI_VELODYNE_ARCHIVE,
module.KITTI_LABEL_ARCHIVE: archive_root / module.KITTI_LABEL_ARCHIVE,
module.KITTI_CALIB_ARCHIVE: archive_root / module.KITTI_CALIB_ARCHIVE,
"train": split_root / "train.txt",
"validation": split_root / "val.txt",
}
with zipfile.ZipFile(paths[module.KITTI_VELODYNE_ARCHIVE], "w") as archive:
for frame_id in ("000000", "000001", "000002"):
size = 15 if invalid_velodyne_frame and frame_id == "000001" else 16
archive.writestr(f"training/velodyne/{frame_id}.bin", b"\x00" * size)
for frame_id in ("000000", "000001"):
archive.writestr(f"testing/velodyne/{frame_id}.bin", b"\x00" * 16)
label_payloads = labels or {
"000000": _label("Car"),
"000001": _label("Pedestrian") + _label("Car"),
"000002": _label("Cyclist"),
}
with zipfile.ZipFile(paths[module.KITTI_LABEL_ARCHIVE], "w") as archive:
for frame_id, payload in label_payloads.items():
archive.writestr(f"training/label_2/{frame_id}.txt", payload)
with zipfile.ZipFile(paths[module.KITTI_CALIB_ARCHIVE], "w") as archive:
for split, frames in {
"training": ("000000", "000001", "000002"),
"testing": ("000000", "000001"),
}.items():
for frame_id in frames:
archive.writestr(
f"{split}/calib/{frame_id}.txt",
_calibration(complete=complete_calibration),
)
paths["train"].write_text("\n".join(train_rows) + "\n", encoding="ascii")
paths["validation"].write_text(
"\n".join(validation_rows) + "\n",
encoding="ascii",
)
monkeypatch.setattr(module, "KITTI_TRAINING_FRAMES", 3)
monkeypatch.setattr(module, "KITTI_TEST_FRAMES", 2)
monkeypatch.setattr(
module,
"KITTI_ARCHIVE_BYTES",
{
name: paths[name].stat().st_size
for name in (
module.KITTI_VELODYNE_ARCHIVE,
module.KITTI_LABEL_ARCHIVE,
module.KITTI_CALIB_ARCHIVE,
)
},
)
monkeypatch.setattr(
module,
"KITTI_SPLIT_COUNTS",
{"train": len(train_rows), "validation": len(validation_rows)},
)
monkeypatch.setattr(
module,
"KITTI_SPLIT_SHA256",
{"train": _sha256(paths["train"]), "validation": _sha256(paths["validation"])},
)
monkeypatch.setattr(module, "_is_worker_dataset_root", lambda _root: True)
return paths
def test_admits_archive_only_box_truth_with_path_free_state(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_release(tmp_path, monkeypatch)
manifest = module.admit_kitti_3d_object_release(tmp_path)
assert manifest["status"] == "archive-ready"
assert manifest["storage"]["source_archives_extracted"] is False
assert manifest["benchmark_contract"] == {
"independent_ground_truth": True,
"annotations": ["oriented-3d-boxes"],
"point_fields": ["x", "y", "z", "intensity"],
"eligible_split": "validation",
"target_classes": ["Car", "Pedestrian", "Cyclist"],
"official_test_submission_authorized": False,
"retuning_on_validation_allowed": False,
"k1_quality_claim_authorized": False,
}
assert manifest["alignment"]["validation_target_box_counts"] == {
"Car": 1,
"Cyclist": 1,
"Pedestrian": 1,
}
serialized = json.dumps(manifest)
assert str(tmp_path) not in serialized
assert module.read_kitti_3d_admission(tmp_path) == manifest
assert module.read_kitti_standard_splits(tmp_path) == {
"train": ("000000",),
"validation": ("000001", "000002"),
}
def test_rejects_tampered_standard_split(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
paths = _release(tmp_path, monkeypatch)
paths["validation"].write_text("000002\n000001\n", encoding="ascii")
with pytest.raises(module.Kitti3DAdmissionError, match="pinned OpenPCDet"):
module.admit_kitti_3d_object_release(tmp_path)
def test_rejects_overlapping_split(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_release(
tmp_path,
monkeypatch,
train_rows=("000000", "000001"),
validation_rows=("000001", "000002"),
)
with pytest.raises(module.Kitti3DAdmissionError, match="overlapping or incomplete"):
module.admit_kitti_3d_object_release(tmp_path)
def test_rejects_non_xyzi_velodyne_frame(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_release(tmp_path, monkeypatch, invalid_velodyne_frame=True)
with pytest.raises(module.Kitti3DAdmissionError, match="not packed XYZI"):
module.admit_kitti_3d_object_release(tmp_path)
def test_rejects_invalid_target_box(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_release(
tmp_path,
monkeypatch,
labels={
"000000": _label("Car"),
"000001": _label("Pedestrian", valid=False),
"000002": _label("Cyclist"),
},
)
with pytest.raises(module.Kitti3DAdmissionError, match="invalid dimensions"):
module.admit_kitti_3d_object_release(tmp_path)
def test_rejects_missing_calibration_transform(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_release(tmp_path, monkeypatch, complete_calibration=False)
with pytest.raises(module.Kitti3DAdmissionError, match="required transforms"):
module.admit_kitti_3d_object_release(tmp_path)
def test_read_rejects_tampered_content_identity(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
_release(tmp_path, monkeypatch)
module.admit_kitti_3d_object_release(tmp_path)
state = tmp_path / "state/kitti-3d-object-v2017.json"
payload: dict[str, Any] = json.loads(state.read_text(encoding="utf-8"))
payload["identity"]["license"]["spdx"] = "unknown"
state.write_text(json.dumps(payload), encoding="utf-8")
with pytest.raises(module.Kitti3DAdmissionError, match="identity is invalid"):
module.read_kitti_3d_admission(tmp_path)