feat(perception): qualify E31 source binding

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 11:33:07 +03:00
parent 001d597a89
commit 091d560ac8
8 changed files with 1819 additions and 14 deletions
+33 -11
View File
@@ -123,23 +123,44 @@ real authority.
confidence `0.8730`. Frames 1213, 162 and 1823 are now explicit automatic confidence `0.8730`. Frames 1213, 162 and 1823 are now explicit automatic
engineering outcomes; only geometry frames 2622 and 4147 remain in the engineering outcomes; only geometry frames 2622 and 4147 remain in the
human queue. human queue.
- [ ] Resolve only the resulting ambiguous/high-impact human-exception queue - [x] Resolve the resulting ambiguous/high-impact human-exception queue and
and freeze the minimum correction set and cause distribution. freeze the minimum correction set and cause distribution. The immutable
human generation is
`e30-review-generation-7982a882558d0be690b4c7092e328c080bfcbf52478a220452be7e887a588250`;
coverage is 2/2. Frame 2622 is `background-or-noise` and frame 4147 is
`object-present`.
- [x] Implement the E31 fail-closed source qualification contract over the
immutable E10→E30 chain. It accounts for all 4,489 source frames, audits
recorded host-arrival timing, sweeps nine offset hypotheses, verifies the
exact factory calibration identity and produces a source-scoped self-mask
profile without inferring firing time, sensor height or physical body
dimensions.
- [x] Accept the immutable diagnostic profile
`e31-source-qualification-b2460a5eb143688c7eea6821b2277e13aea79868abe81d83f7e78548c119159a`
for E32 binding. Zero offset retains support for 87/87 evidenced
correspondences. The semantic self-mask is admitted from eight source cases
with zero bbox-centre collateral; the generic geometry point mask is
rejected because it would erase accepted object evidence. Navigation,
safety and command authority remain false.
The current A3 AI-assisted engineering generation covers all 486 items and The frozen A3 engineering and human generations cover all 486 items and
retains the exact A2 materialization/review-pack and 42-sheet evidence retains the exact A2 materialization/review-pack and 42-sheet evidence
identities. It explicitly claims neither human ground truth nor navigation or identities. It explicitly claims neither human ground truth nor navigation or
safety acceptance and retains `lab_published=false`. The dominant conflict safety acceptance and retains `lab_published=false`. The dominant conflict
cause is detector placement on striped road/construction barriers rather than cause is detector placement on striped road/construction barriers rather than
camera↔LiDAR registration; the `unknown` stratum is dominated by held camera↔LiDAR registration; the `unknown` stratum is dominated by held
world-track time freshness; geometry-only contains both expected static scene world-track time freshness; geometry-only contains both expected static scene
geometry and 21 visible missed class-bearing objects. geometry and 21 visible missed class-bearing objects. Earlier generations and
drafts remain historical and are not rewritten or presented as the current
product workflow.
A3 is not complete until the two routed human exceptions are resolved and the A3 and A4 are complete. E31 accepts only the recorded RAVNOVES00 diagnostic
minimum correction set is frozen. Earlier generations and drafts remain binding and does not claim that host arrival is hardware firing time. The
historical and are not rewritten or presented as the current product workflow. factory KB4 identity is exact, but a measured calibration-target residual and
A4 remains blocked. No perception threshold changes are allowed before the physical body/mount dimensions are unavailable. The accepted profile is
exception decision is frozen. therefore source-session scoped and cannot transfer to another mount. A5
`TrackGeometry v1` is the next critical-path implementation; E32 publication
must bind both that contract and the accepted E31 profile.
### A3 residual and human-exception policy ### A3 residual and human-exception policy
@@ -153,5 +174,6 @@ exception decision is frozen.
accuracy, navigation or safety acceptance threshold. accuracy, navigation or safety acceptance threshold.
- A repeated cause cluster or a high-impact case blocks the gate regardless of - A repeated cause cluster or a high-impact case blocks the gate regardless of
percentage. The 1% budget cannot hide a systematic defect. percentage. The 1% budget cannot hide a systematic defect.
- The current queue is 2 / 486 (`0.41%`) and therefore bounded, but A3 still - The routed queue was 2 / 486 (`0.41%`) and is now fully resolved in the
requires those two recorded decisions before its correction set is frozen. immutable human generation. No unresolved `insufficient-evidence` item
remains.
@@ -0,0 +1,144 @@
# LAB E31 — source-time, calibration and self-mask qualification
Date: 2026-07-27
Status: accepted for diagnostic E32 binding; navigation, safety and command
authority are false
Immutable result:
`e31-source-qualification-b2460a5eb143688c7eea6821b2277e13aea79868abe81d83f7e78548c119159a`
## Decision under test
E31 tests whether the exact RAVNOVES00 source binding used by E29/E30 is
specific and stable enough to enter E32 full replay. It does not tune E29,
infer a raw LiDAR firing timestamp, invent a sensor height or promote the
result to navigation.
The gate is fail closed. It requires:
1. the immutable A3 engineering generation and complete human-exception
generation;
2. complete accounting of source-time bindings;
3. the exact factory KB4 calibration identity;
4. a predeclared offset sweep over evidenced camera↔LiDAR correspondences;
5. a source-evidenced self-mask decision that cannot silently erase accepted
object evidence.
## Immutable input chain
- Source pack:
`e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b`.
- Local-surface model:
`k1-local-surface-23762244c8bdb97de26fb721ac957d7a00bc9a63571ac4cfa4be19c4effc7d55`.
- E30 materialization:
`e30-materialization-841af926d8d28ab93538c46d8f31278a2234c4d1c12c7dc4dc296b249d59735a`.
- A3 engineering generation:
`e30-engineering-generation-62a4fea10dea9b77f69ceac1af5bf0e4928d9c7716083c22258a03670fe5bd4f`.
- A3 human generation:
`e30-review-generation-7982a882558d0be690b4c7092e328c080bfcbf52478a220452be7e887a588250`.
- Factory calibration content identity:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`.
- Camera binding: `sensor.camera.right`, `camera_1`, KB4, `800×600`.
Every input result and artifact digest is verified before E31 runs. Existing
results are re-opened only when the complete content identity matches.
## Source-time audit
The recording has 4,489 source frames. LiDAR/pose bindings exist for 3,928
frames (`87.50%`); 561 unavailable frames remain explicitly unavailable.
| timing relation | absolute p50 | absolute p95 | maximum | declared gate |
| --- | ---: | ---: | ---: | ---: |
| LiDAR ↔ camera | 25.303 ms | 70.789 ms | 99.574 ms | 100 ms |
| pose ↔ point cloud | 6.784 ms | 21.854 ms | 73.798 ms | 100 ms |
These are recorded host-monotonic arrival relations. They are not hardware
firing timestamps.
## Offset sensitivity
The correspondence set contains 87 A3 items where the projection was
`aligned`, point ownership was `object` and the E29 item was semantic. For each
hypothesis E31 selects the nearest available recorded cloud, projects it with
the camera-frame pose and evaluates occupied support inside the exact E29 bbox
inset (`0.03`).
| host-arrival hypothesis | evaluable | supported | supported fraction |
| ---: | ---: | ---: | ---: |
| -200 ms | 79 | 63 | 79.75% |
| -150 ms | 83 | 66 | 79.52% |
| -100 ms | 81 | 70 | 86.42% |
| -50 ms | 87 | 78 | 89.66% |
| 0 ms | 87 | 87 | 100.00% |
| +50 ms | 87 | 80 | 91.95% |
| +100 ms | 83 | 69 | 83.13% |
| +150 ms | 84 | 71 | 84.52% |
| +200 ms | 83 | 69 | 83.13% |
Zero offset is retained. Its support deficit relative to the best hypothesis is
zero. The p95 bbox support-centroid residual is `0.3947` bbox diagonals. This is
a diagnostic correspondence residual, not a measured calibration-target
residual.
## Calibration and self-mask result
The loaded factory calibration exactly matches the source-pack identity. The
camera-from-LiDAR rotation determinant is `1.00000037`, the maximum
orthonormal residual is `5.30e-7`, and translation norm is `0.1005 m`.
Eight A3 `self_points` person cases support a source-session semantic exclusion
rectangle:
```text
normalized xyxy = [0.191196, 0.730990, 0.625038, 0.992935]
application = person bbox centre inside rectangle
observed collateral = 0
```
This mask is bound to the source session and detector rule. It is not a
transferable physical vehicle mask.
The two geometry `self_points` cases cannot support a generic projected point
mask. Their union would erase 69 selected points from one accepted object and
22 from another. E31 therefore rejects that mask and retains the two exact A3
correction item identities instead.
Physical body dimensions and a body-frame mount transform are unavailable.
E31 records both as absent; it does not substitute a guessed height.
## Decision
All ten predeclared requirements pass. The result status is
`accepted-diagnostic-source-profile` and `eligible_for_e32=true`.
Acceptance means only:
- E32 may bind this exact source session, factory calibration, zero
host-arrival offset and source-scoped semantic mask;
- unavailable source evidence remains unavailable;
- the unsafe generic geometry mask remains disabled;
- the result has no command, navigation or safety authority.
It does not establish transfer to another physical mount. That remains an E36
question with a second eligible real source.
## Reproduction
```bash
PYTHONPATH=src .venv/bin/python \
experiments/perception/run_e31_source_qualification.py \
--source-pack .runtime/compute-experiments/e10/lidar-packs/e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b \
--local-surface .runtime/compute-experiments/k1-local-surface-v1/models/k1-local-surface-23762244c8bdb97de26fb721ac957d7a00bc9a63571ac4cfa4be19c4effc7d55 \
--calibration .runtime/mission-core/evidence/sessions/private/device-calibration/20260720T105432Z_k1_factory_calibration_2e0e51ae5485 \
--materialization .runtime/compute-experiments/e30/materializations/e30-materialization-841af926d8d28ab93538c46d8f31278a2234c4d1c12c7dc4dc296b249d59735a \
--engineering-generation .runtime/compute-experiments/e30/engineering-generations/e30-engineering-generation-62a4fea10dea9b77f69ceac1af5bf0e4928d9c7716083c22258a03670fe5bd4f \
--human-generation .runtime/compute-experiments/e30/human-review-generations/e30-review-generation-7982a882558d0be690b4c7092e328c080bfcbf52478a220452be7e887a588250
```
The immutable result contains:
- `qualification-report.json`;
- `binding-profile.json`;
- `offset-sweep.json`;
- 87 per-item correspondence score rows;
- a digest-bound manifest.
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Build the immutable LAB E31 source qualification profile."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from k1link.compute.e31_source_qualification import (
build_e31_source_qualification,
)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--source-pack", type=Path, required=True)
parser.add_argument("--local-surface", type=Path, required=True)
parser.add_argument("--calibration", type=Path, required=True)
parser.add_argument("--materialization", type=Path, required=True)
parser.add_argument("--engineering-generation", type=Path, required=True)
parser.add_argument("--human-generation", type=Path, required=True)
parser.add_argument(
"--output-root",
type=Path,
default=Path(".runtime/compute-experiments/e31/source-qualifications"),
)
args = parser.parse_args()
result = build_e31_source_qualification(
source_pack_root=args.source_pack,
local_surface_root=args.local_surface,
calibration_snapshot_root=args.calibration,
materialization_root=args.materialization,
engineering_generation_root=args.engineering_generation,
human_generation_root=args.human_generation,
output_root=args.output_root,
)
print(
json.dumps(
{
"result_id": result.result_id,
"result_root": str(result.result_root),
"status": result.report["status"],
"eligible_for_e32": result.report["eligible_for_e32"],
"requirements": result.report["requirements"],
"authority": result.report["authority"],
},
ensure_ascii=False,
indent=2,
)
)
if __name__ == "__main__":
main()
+18
View File
@@ -7,6 +7,16 @@ from .annotation_workspace import (
prepare_annotation_workspace, prepare_annotation_workspace,
validate_annotation_workspace, validate_annotation_workspace,
) )
from .e31_source_qualification import (
DEFAULT_E31_SOURCE_QUALIFICATION_PROFILE,
E31_BINDING_PROFILE_SCHEMA,
E31_SOURCE_QUALIFICATION_REPORT_SCHEMA,
E31_SOURCE_QUALIFICATION_SCHEMA,
E31SourceQualification,
E31SourceQualificationError,
E31SourceQualificationProfile,
build_e31_source_qualification,
)
from .evaluation_pack import ( from .evaluation_pack import (
ANNOTATION_CONTRACT_SCHEMA, ANNOTATION_CONTRACT_SCHEMA,
EVALUATION_PACK_SCHEMA, EVALUATION_PACK_SCHEMA,
@@ -210,6 +220,13 @@ __all__ = [
"COMPUTE_JOB_SCHEMA", "COMPUTE_JOB_SCHEMA",
"DEFAULT_QUALIFICATION_FRAME_COUNT", "DEFAULT_QUALIFICATION_FRAME_COUNT",
"DEFAULT_K1_LOCAL_SURFACE_PROFILE", "DEFAULT_K1_LOCAL_SURFACE_PROFILE",
"DEFAULT_E31_SOURCE_QUALIFICATION_PROFILE",
"E31_BINDING_PROFILE_SCHEMA",
"E31_SOURCE_QUALIFICATION_REPORT_SCHEMA",
"E31_SOURCE_QUALIFICATION_SCHEMA",
"E31SourceQualification",
"E31SourceQualificationError",
"E31SourceQualificationProfile",
"EVALUATION_PACK_SCHEMA", "EVALUATION_PACK_SCHEMA",
"EvaluationFrameRequest", "EvaluationFrameRequest",
"EvaluationPackFrame", "EvaluationPackFrame",
@@ -320,6 +337,7 @@ __all__ = [
"prepare_recorded_qualification_slice", "prepare_recorded_qualification_slice",
"assess_lidar_profile", "assess_lidar_profile",
"build_lidar_replay_pack_v2", "build_lidar_replay_pack_v2",
"build_e31_source_qualification",
"build_lidar_ground_annotation_template", "build_lidar_ground_annotation_template",
"build_lidar_ground_benchmark", "build_lidar_ground_benchmark",
"build_k1_local_surface", "build_k1_local_surface",
+2 -2
View File
@@ -28,13 +28,13 @@ from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
project_map_points_kb4, project_map_points_kb4,
) )
from .lidar_field_review import E10LidarFieldSource
from .lidar_local_surface import K1LocalSurfaceV1
from .e30_camera_evidence import ( from .e30_camera_evidence import (
E30CameraEvidenceSource, E30CameraEvidenceSource,
materialize_e30_camera_frames, materialize_e30_camera_frames,
open_e30_camera_evidence_source, open_e30_camera_evidence_source,
) )
from .lidar_field_review import E10LidarFieldSource
from .lidar_local_surface import K1LocalSurfaceV1
from .semantic_geometry_fusion import ( from .semantic_geometry_fusion import (
CAMERA_GEOMETRY_FUSION_SCHEMA, CAMERA_GEOMETRY_FUSION_SCHEMA,
CameraGeometryFusionProfile, CameraGeometryFusionProfile,
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,12 +9,12 @@ from fastapi import Path as ApiPath
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
from k1link.compute.e30_human_review import ( from k1link.compute.e30_human_review import (
E30ExceptionDisposition,
E30HumanReviewConflictError, E30HumanReviewConflictError,
E30HumanReviewIntegrityError, E30HumanReviewIntegrityError,
E30HumanReviewNotFoundError, E30HumanReviewNotFoundError,
E30HumanReviewStore, E30HumanReviewStore,
E30HumanReviewValidationError, E30HumanReviewValidationError,
E30ExceptionDisposition,
E30ReviewSubject, E30ReviewSubject,
E30ReviewSubstrate, E30ReviewSubstrate,
) )
+238
View File
@@ -0,0 +1,238 @@
from __future__ import annotations
import hashlib
from pathlib import Path
from typing import Any
import numpy as np
import pytest
from k1link.compute.e31_source_qualification import (
E31SourceQualificationError,
E31SourceQualificationProfile,
_E30Chain,
_offset_sweep,
_self_mask_report,
)
from k1link.compute.lidar_local_surface import POINT_OCCUPIED
class _SweepSource:
def __init__(self) -> None:
self.arrays = {
"session_seconds": np.asarray([0.0, 0.1, 0.2], dtype=np.float64),
"sample_available": np.ones(3, dtype=np.bool_),
"cloud_offsets": np.asarray([0, 2, 4, 6], dtype=np.int64),
"cloud_points_map": np.asarray(
[
[1.0, 0.0, 2.0],
[1.1, 0.0, 2.0],
[0.0, 0.0, 2.0],
[0.1, 0.0, 2.0],
[-1.0, 0.0, 2.0],
[-1.1, 0.0, 2.0],
],
dtype=np.float32,
),
"pose_positions_map": np.zeros((3, 3), dtype=np.float64),
"pose_quaternions_map_from_lidar": np.asarray(
[[0.0, 0.0, 0.0, 1.0]] * 3,
dtype=np.float64,
),
"intrinsic_fx_fy_cx_cy": np.asarray(
[100.0, 100.0, 50.0, 50.0],
dtype=np.float64,
),
"distortion_kb4": np.zeros(4, dtype=np.float64),
"t_camera_from_lidar": np.eye(4, dtype=np.float64),
}
self.identity: dict[str, Any] = {
"source_id": "sensor.camera.right",
"camera_slot": "camera_1",
"projection": {"width": 100, "height": 100},
}
class _SweepSurface:
def __init__(self) -> None:
self.arrays = {
"point_class": np.full(6, POINT_OCCUPIED, dtype=np.uint8),
}
def _artifact(path: Path) -> dict[str, object]:
return {
"path": path.name,
"byte_length": path.stat().st_size,
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
}
def _projection_artifact(
root: Path,
name: str,
pixels: list[list[float]],
) -> dict[str, object]:
path = root / f"{name}.npz"
np.savez(
path,
projected_pixels_xy=np.asarray(pixels, dtype=np.float32),
projected_selected_mask=np.ones(len(pixels), dtype=np.uint8),
)
return _artifact(path)
def test_profile_rejects_ambiguous_offset_hypotheses() -> None:
with pytest.raises(E31SourceQualificationError):
E31SourceQualificationProfile(offset_hypotheses_ms=(0, -50, 50))
with pytest.raises(E31SourceQualificationError):
E31SourceQualificationProfile(offset_hypotheses_ms=(-50, 50))
def test_offset_sweep_keeps_evidenced_zero_binding() -> None:
source = _SweepSource()
surface = _SweepSurface()
profile = E31SourceQualificationProfile(
offset_hypotheses_ms=(-100, 0, 100),
minimum_correspondence_items=1,
)
item = {
"item_id": "item-1",
"review_key": "semantic:1:1",
"evidence_binding": {"frame_index": 1},
"e29_snapshot": {"bbox_xyxy": [40.0, 40.0, 60.0, 60.0]},
}
sweep, rows = _offset_sweep(
source=source, # type: ignore[arg-type]
surface=surface, # type: ignore[arg-type]
items=(item,),
profile=profile,
bbox_inset_fraction=0.03,
)
assert sweep["selected_offset_ms"] == 0
assert sweep["baseline_supported_fraction"] == 1.0
assert sweep["best_supported_fraction"] == 1.0
assert sweep["baseline_support_deficit_fraction"] == 0.0
assert [row["supported_count"] for row in sweep["hypotheses"]] == [0, 1, 0]
assert [score["candidate_frame_index"] for score in rows[0]["scores"]] == [
0,
1,
2,
]
def test_self_mask_admits_only_non_colliding_semantic_rule(
tmp_path: Path,
) -> None:
items: list[dict[str, Any]] = []
decisions: list[dict[str, Any]] = []
for index in range(4):
item = {
"item_id": f"semantic-self-{index}",
"e29_snapshot": {
"label": "person",
"bbox_xyxy": [
20.0 + index,
75.0,
40.0 + index,
99.0,
],
},
}
items.append(item)
decisions.append(
{
"item_id": item["item_id"],
"cause_code": "self_points",
"point_ownership": "self",
}
)
geometry_self = {
"item_id": "geometry-self",
"e29_snapshot": {},
"artifact": _projection_artifact(
tmp_path,
"geometry-self",
[[20.0, 20.0], [30.0, 30.0]],
),
}
accepted_object = {
"item_id": "accepted-object",
"e29_snapshot": {},
"artifact": _projection_artifact(
tmp_path,
"accepted-object",
[[25.0, 25.0], [80.0, 80.0]],
),
}
accepted_person = {
"item_id": "accepted-person",
"e29_snapshot": {
"label": "person",
"bbox_xyxy": [70.0, 50.0, 90.0, 90.0],
},
"artifact": _projection_artifact(
tmp_path,
"accepted-person",
[[80.0, 80.0]],
),
}
items.extend([geometry_self, accepted_object, accepted_person])
decisions.extend(
[
{
"item_id": geometry_self["item_id"],
"cause_code": "self_points",
"point_ownership": "self",
},
{
"item_id": accepted_object["item_id"],
"cause_code": "none",
"point_ownership": "object",
},
{
"item_id": accepted_person["item_id"],
"cause_code": "none",
"point_ownership": "object",
},
]
)
chain = _E30Chain(
materialization_manifest={
"identity": {
"projection": {
"width": 100,
"height": 100,
}
}
},
items=tuple(items),
engineering_manifest={},
decisions=tuple(decisions),
exceptions=(),
human_manifest={},
human_decisions=(),
)
report = _self_mask_report(
materialization_root=tmp_path,
chain=chain,
profile=E31SourceQualificationProfile(
minimum_semantic_self_samples=4,
),
)
assert report["semantic_mask"]["status"] == "admitted"
assert report["semantic_mask"]["application_rule"] == "bbox-center-inside-rectangle"
assert report["semantic_mask"]["collateral_item_count"] == 0
assert report["geometry_point_mask"]["status"] == "rejected"
assert report["geometry_point_mask"]["collateral"] == [
{
"item_id": "accepted-object",
"masked_selected_point_count": 1,
}
]
assert report["exact_correction_item_ids"] == ["geometry-self"]