| """Prove and export the recovered p01 legacy-H5-row to MPIIGaze-frame mapping.""" |
| from __future__ import annotations |
|
|
| import csv |
| import hashlib |
| import json |
| from pathlib import Path |
| import sys |
|
|
| ROOT = Path(r"E:\Gaze_estimation") |
| sys.path.insert(0, str(ROOT / ".codex_deps")) |
|
|
| import h5py |
| import numpy as np |
|
|
|
|
| LEGACY = ROOT / "data" / "processed" / "p01_v16.h5" |
| CLEAN = ROOT / "data" / "processed_kd_clean_v1" / "cache" / "p01.full.h5" |
| OUT = ROOT / "artifacts" / "kd-teacher-trap-diagnostic" |
|
|
|
|
| def file_sha256(path: Path) -> str: |
| digest = hashlib.sha256() |
| with path.open("rb") as stream: |
| for block in iter(lambda: stream.read(1024 * 1024), b""): |
| digest.update(block) |
| return digest.hexdigest().upper() |
|
|
|
|
| def decode(values: np.ndarray) -> list[str]: |
| return [value.decode("utf-8") if isinstance(value, bytes) else str(value) for value in values] |
|
|
|
|
| def softmax(values: np.ndarray) -> np.ndarray: |
| shifted = values.astype(np.float64) - values.max(axis=1, keepdims=True) |
| exp = np.exp(shifted) |
| return exp / exp.sum(axis=1, keepdims=True) |
|
|
|
|
| def expectation(values: np.ndarray) -> np.ndarray: |
| return softmax(values) @ np.arange(90, dtype=np.float64) * 2.0 - 90.0 |
|
|
|
|
| def vectors(pitch_deg: np.ndarray, yaw_deg: np.ndarray) -> np.ndarray: |
| pitch, yaw = np.deg2rad(pitch_deg), np.deg2rad(yaw_deg) |
| return np.column_stack( |
| (-np.cos(pitch) * np.sin(yaw), -np.sin(pitch), -np.cos(pitch) * np.cos(yaw)) |
| ) |
|
|
|
|
| def angular_error(prediction: np.ndarray, target: np.ndarray) -> np.ndarray: |
| prediction /= np.linalg.norm(prediction, axis=1, keepdims=True) |
| target /= np.linalg.norm(target, axis=1, keepdims=True) |
| return np.rad2deg(np.arccos(np.clip(np.sum(prediction * target, axis=1), -1.0, 1.0))) |
|
|
|
|
| def main() -> None: |
| with h5py.File(LEGACY, "r") as legacy, h5py.File(CLEAN, "r") as clean: |
| legacy_count = int(legacy["left_gaze"].shape[0]) |
| clean_count = int(clean["left_gaze"].shape[0]) |
| left_equal = bool(np.array_equal(legacy["left_gaze"][:], clean["left_gaze"][:])) |
| right_equal = bool(np.array_equal(legacy["right_gaze"][:], clean["right_gaze"][:])) |
| landmark_delta = np.abs( |
| legacy["landmarks"][:].astype(np.float64) - clean["landmarks"][:].astype(np.float64) |
| ) |
| row_landmark_max = landmark_delta.reshape(clean_count, -1).max(axis=1) |
| paths = decode(clean["relative_frame_path"][:]) |
| ids = decode(clean["sample_id"][:]) |
| image_hashes = decode(clean["raw_image_sha256"][:]) |
| source_indices = clean["source_index"][:].astype(np.int64) |
|
|
| target_deg = np.rad2deg(clean["left_gaze"][:].astype(np.float64)) |
| target_vector = vectors(target_deg[:, 0], target_deg[:, 1]) |
| legacy_pitch = expectation(legacy["teacher_pitch_logits"][:]) |
| legacy_yaw = expectation(legacy["teacher_yaw_logits"][:]) |
| corrected_raw_pitch = expectation(clean["teacher_pitch_logits_raw"][:]) |
| corrected_raw_yaw = expectation(clean["teacher_yaw_logits_raw"][:]) |
| corrected_aligned_vector = clean["teacher_aligned_vector"][:].astype(np.float64) |
| legacy_error = angular_error(vectors(legacy_pitch, legacy_yaw), target_vector.copy()) |
| corrected_raw_error = angular_error( |
| vectors(corrected_raw_pitch, corrected_raw_yaw), target_vector.copy() |
| ) |
| corrected_aligned_error = angular_error(corrected_aligned_vector, target_vector.copy()) |
|
|
| csv_path = OUT / "p01_legacy_row_to_frame_mapping.csv" |
| with csv_path.open("w", encoding="utf-8", newline="") as stream: |
| writer = csv.DictWriter( |
| stream, |
| fieldnames=( |
| "legacy_h5_row", "source_index", "sample_id", "relative_frame_path", |
| "raw_image_sha256", "left_gaze_exact", "right_gaze_exact", |
| "landmark_row_max_abs_diff", |
| ), |
| ) |
| writer.writeheader() |
| for index in range(clean_count): |
| writer.writerow( |
| { |
| "legacy_h5_row": index, |
| "source_index": int(source_indices[index]), |
| "sample_id": ids[index], |
| "relative_frame_path": paths[index], |
| "raw_image_sha256": image_hashes[index], |
| "left_gaze_exact": bool( |
| np.array_equal(legacy["left_gaze"][index], clean["left_gaze"][index]) |
| ), |
| "right_gaze_exact": bool( |
| np.array_equal(legacy["right_gaze"][index], clean["right_gaze"][index]) |
| ), |
| "landmark_row_max_abs_diff": float(row_landmark_max[index]), |
| } |
| ) |
|
|
| mapping_pass = bool( |
| legacy_count == clean_count |
| and left_equal |
| and right_equal |
| and np.max(row_landmark_max) <= 1e-6 |
| and np.all(np.diff(source_indices) > 0) |
| and len(ids) == len(set(ids)) |
| ) |
| report = { |
| "legacy_h5": str(LEGACY), |
| "legacy_h5_sha256": file_sha256(LEGACY), |
| "clean_h5": str(CLEAN), |
| "clean_h5_sha256": file_sha256(CLEAN), |
| "legacy_rows": legacy_count, |
| "clean_rows": clean_count, |
| "left_gaze_all_rows_exact": left_equal, |
| "right_gaze_all_rows_exact": right_equal, |
| "landmarks_global_max_abs_diff": float(np.max(landmark_delta)), |
| "landmarks_global_mean_abs_diff": float(np.mean(landmark_delta)), |
| "landmark_rows_with_max_abs_diff_le_1e_6": int(np.sum(row_landmark_max <= 1e-6)), |
| "source_indices_strictly_increasing": bool(np.all(np.diff(source_indices) > 0)), |
| "unique_sample_ids": len(set(ids)), |
| "mapping_csv": str(csv_path), |
| "mapping_csv_sha256": file_sha256(csv_path), |
| "mapping_status": "RECOVERED_FOR_P01" if mapping_pass else "NOT_PROVEN", |
| "first_five": [ |
| { |
| "legacy_h5_row": index, |
| "source_index": int(source_indices[index]), |
| "sample_id": ids[index], |
| "relative_frame_path": paths[index], |
| "raw_image_sha256": image_hashes[index], |
| } |
| for index in range(min(5, clean_count)) |
| ], |
| "teacher_comparison_against_roll_corrected_left_gaze": { |
| "legacy_cached_teacher_3d_error_mean_deg": float(legacy_error.mean()), |
| "corrected_loader_raw_teacher_3d_error_mean_deg": float(corrected_raw_error.mean()), |
| "corrected_loader_roll_aligned_teacher_3d_error_mean_deg": float( |
| corrected_aligned_error.mean() |
| ), |
| "corrected_aligned_minus_legacy_error_deg": float( |
| corrected_aligned_error.mean() - legacy_error.mean() |
| ), |
| "legacy_vs_corrected_pitch_prediction_pearson": float( |
| np.corrcoef(legacy_pitch, corrected_raw_pitch)[0, 1] |
| ), |
| "legacy_vs_corrected_yaw_prediction_pearson": float( |
| np.corrcoef(legacy_yaw, corrected_raw_yaw)[0, 1] |
| ), |
| }, |
| "interpretation": ( |
| "The identical accepted-row count, exact left/right gaze arrays, and <=1e-6 " |
| "landmark agreement establish the p01 legacy row order. Patch differences do " |
| "not invalidate identity because patch-processing variants were already known." |
| ), |
| } |
| output = OUT / "p01_legacy_row_mapping_audit.json" |
| output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8", newline="\n") |
| print(json.dumps(report, indent=2)) |
| if not mapping_pass: |
| raise SystemExit(1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|