feat(perception): add PointPillars visual audit

This commit is contained in:
DCCONSTRUCTIONS
2026-07-31 11:47:04 +03:00
parent 8fe6184c52
commit a2cb50d1ad
16 changed files with 2351 additions and 4 deletions
@@ -13,8 +13,10 @@ import {
import { fetchE34TemporalLayerResult } from "./e34TemporalLayer";
import { fetchE35DegradationRecoveryResult } from "./e35DegradationRecovery";
import { fetchE40ProductGateResult } from "./e40ProductGate";
import { fetchL3PointPillarsVisualAudit } from "./l3PointPillarsVisualAudit";
export type AdvancedLaboratoryWorkId =
| "l3-pointpillars-visual-audit"
| "e31-source-binding"
| "e32-track-geometry"
| "e33-worker-shadow"
@@ -32,6 +34,7 @@ export interface AdvancedLaboratoryIndexItem {
}
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
"l3-pointpillars-visual-audit",
"e31-source-binding",
"e32-track-geometry",
"e33-worker-shadow",
@@ -44,6 +47,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
];
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
"l3-pointpillars-visual-audit": "l3-pointpillars-visual-audit",
"e31-source-binding": "e31-source-qualification",
"e32-track-geometry": "e32-track-geometry",
"e33-worker-shadow": "e33-worker-shadow",
@@ -63,6 +67,7 @@ export function isAdvancedLaboratoryWorkId(
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
return {
l3: null,
e31: null,
e32: null,
e33: null,
@@ -163,7 +168,8 @@ export function advancedLaboratoryResultAvailable(
workId: AdvancedLaboratoryWorkId,
results: AdvancedLaboratoryResults,
): boolean {
return workId === "e31-source-binding" ? results.e31 !== null
return workId === "l3-pointpillars-visual-audit" ? results.l3 !== null
: workId === "e31-source-binding" ? results.e31 !== null
: workId === "e32-track-geometry" ? results.e32 !== null
: workId === "e33-worker-shadow" ? results.e33 !== null
: workId === "e34-temporal-layer" ? results.e34 !== null
@@ -185,7 +191,9 @@ export async function fetchAdvancedLaboratoryResult(
} = {},
): Promise<AdvancedLaboratoryResults> {
const results = emptyAdvancedLaboratoryResults();
if (workId === "e31-source-binding") {
if (workId === "l3-pointpillars-visual-audit") {
results.l3 = await fetchL3PointPillarsVisualAudit({ fetcher, signal });
} else if (workId === "e31-source-binding") {
results.e31 = await fetchOne(
"/api/v1/laboratory/e31/results?limit=1",
parseE31,
@@ -11,6 +11,7 @@ import {
type E40PerceptionProductGateResult,
} from "./e40ProductGate";
import { settledCatalogValue } from "./catalogTransport";
import type { L3PointPillarsVisualAuditResult } from "./l3PointPillarsVisualAudit";
export interface E31LaboratoryResult {
resultId: string;
@@ -238,6 +239,7 @@ export interface E39PerceptionRefinementResult {
}
export interface AdvancedLaboratoryResults {
l3: L3PointPillarsVisualAuditResult | null;
e31: E31LaboratoryResult | null;
e32: E32LaboratoryResult | null;
e33: E33LaboratoryResult | null;
@@ -988,5 +990,5 @@ export async function fetchAdvancedLaboratoryResults({
const e38 = settledCatalogValue(settled[6]);
const e39 = settledCatalogValue(settled[7]);
const e40 = settledCatalogValue(settled[8]);
return { e31, e32, e33, e34, e35, e37, e38, e39, e40 };
return { l3: null, e31, e32, e33, e34, e35, e37, e38, e39, e40 };
}
@@ -0,0 +1,384 @@
import {
AdvancedLaboratoryContractError,
type LaboratoryFetch,
} from "./advancedResults";
export interface L3VisualFrameSummary {
frameId: string;
inferenceMs: number;
predictionCount: number;
evaluatedPredictionCount: number;
outsideSharedRangeCount: number;
truthCount: number;
truePositiveCount: number;
falsePositiveCount: number;
falseNegativeCount: number;
truthClasses: readonly string[];
}
export interface L3VisualBox {
benchmarkClass: string;
centerXyzM: readonly [number, number, number];
sizeLwhM: readonly [number, number, number];
yawRad: number;
status: "matched" | "false-negative" | "true-positive" | "false-positive";
score: number | null;
}
export interface L3VisualFrame {
frameId: string;
summary: L3VisualFrameSummary;
sourcePointCount: number;
sharedRangePointCount: number;
sampledPointCount: number;
pointsXyzi: readonly number[];
truthBoxes: readonly L3VisualBox[];
predictionBoxes: readonly L3VisualBox[];
}
export interface L3PointPillarsVisualAuditResult {
resultId: string;
createdAtUtc: string;
status: "operator-visual-review-required";
sourceRunId: string;
sourceFrameResultsIdentitySha256: string;
datasetSourceId: string;
datasetReleaseIdentitySha256: string;
metrics: {
frameCount: number;
bevMap40: number;
threeDMap40: number;
falseOccupiedRate: number;
inferenceP95Ms: number;
modelOutputBoxCount: number;
evaluatedBoxCount: number;
outsideSharedRangeCount: number;
};
frames: readonly L3VisualFrameSummary[];
}
const RESULT_ID = /^l3-pointpillars-visual-audit-[a-f0-9]{64}$/;
const RUN_ID = /^l3-pointpillars-kitti-[a-f0-9]{64}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const FRAME_ID = /^[0-9]{6}$/;
function objectValue(
value: unknown,
label: string,
): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new AdvancedLaboratoryContractError(`${label}: ожидался объект.`);
}
return value as Record<string, unknown>;
}
function arrayValue(value: unknown, label: string): readonly unknown[] {
if (!Array.isArray(value)) {
throw new AdvancedLaboratoryContractError(`${label}: ожидался массив.`);
}
return value;
}
function exact(value: unknown, expected: string, label: string): string {
if (value !== expected) {
throw new AdvancedLaboratoryContractError(`${label}: нарушен контракт.`);
}
return expected;
}
function stringValue(value: unknown, label: string): string {
if (typeof value !== "string" || !value.trim()) {
throw new AdvancedLaboratoryContractError(`${label}: ожидалась строка.`);
}
return value;
}
function numberValue(value: unknown, label: string, minimum = 0): number {
if (
typeof value !== "number"
|| !Number.isFinite(value)
|| value < minimum
) {
throw new AdvancedLaboratoryContractError(`${label}: неверное число.`);
}
return value;
}
function integerValue(value: unknown, label: string): number {
const parsed = numberValue(value, label);
if (!Number.isInteger(parsed)) {
throw new AdvancedLaboratoryContractError(`${label}: ожидалось целое.`);
}
return parsed;
}
function tuple3(
value: unknown,
label: string,
positive = false,
): readonly [number, number, number] {
const values = arrayValue(value, label);
if (values.length !== 3) {
throw new AdvancedLaboratoryContractError(`${label}: ожидалось 3 числа.`);
}
return [
numberValue(values[0], `${label}[0]`, positive ? Number.MIN_VALUE : -Infinity),
numberValue(values[1], `${label}[1]`, positive ? Number.MIN_VALUE : -Infinity),
numberValue(values[2], `${label}[2]`, positive ? Number.MIN_VALUE : -Infinity),
];
}
function parseSummary(value: unknown): L3VisualFrameSummary {
const item = objectValue(value, "L3 visual frame");
const frameId = stringValue(item.frame_id, "L3 frame_id");
if (!FRAME_ID.test(frameId)) {
throw new AdvancedLaboratoryContractError("L3 frame_id: неверный формат.");
}
const truthClasses = arrayValue(item.truth_classes, "L3 truth_classes")
.map((entry) => stringValue(entry, "L3 truth class"));
if (
truthClasses.some(
(entry) => !["Car", "Pedestrian", "Cyclist"].includes(entry),
)
) {
throw new AdvancedLaboratoryContractError("L3 truth class: неизвестен.");
}
return {
frameId,
inferenceMs: numberValue(item.inference_ms, "L3 inference_ms", Number.MIN_VALUE),
predictionCount: integerValue(item.prediction_count, "L3 prediction_count"),
evaluatedPredictionCount: integerValue(
item.evaluated_prediction_count,
"L3 evaluated_prediction_count",
),
outsideSharedRangeCount: integerValue(
item.outside_shared_range_count,
"L3 outside_shared_range_count",
),
truthCount: integerValue(item.truth_count, "L3 truth_count"),
truePositiveCount: integerValue(
item.true_positive_count,
"L3 true_positive_count",
),
falsePositiveCount: integerValue(
item.false_positive_count,
"L3 false_positive_count",
),
falseNegativeCount: integerValue(
item.false_negative_count,
"L3 false_negative_count",
),
truthClasses,
};
}
function parseBox(value: unknown, truth: boolean): L3VisualBox {
const box = objectValue(value, "L3 visual box");
const benchmarkClass = stringValue(
box.benchmark_class,
"L3 benchmark_class",
);
if (!["Car", "Pedestrian", "Cyclist"].includes(benchmarkClass)) {
throw new AdvancedLaboratoryContractError("L3 box class: неизвестен.");
}
const status = stringValue(box.status, "L3 box status");
const allowed = truth
? ["matched", "false-negative"]
: ["true-positive", "false-positive"];
if (!allowed.includes(status)) {
throw new AdvancedLaboratoryContractError("L3 box status: неизвестен.");
}
return {
benchmarkClass,
centerXyzM: tuple3(box.center_xyz_m, "L3 center_xyz_m"),
sizeLwhM: tuple3(box.size_lwh_m, "L3 size_lwh_m", true),
yawRad: numberValue(box.yaw_rad, "L3 yaw_rad", -Infinity),
status: status as L3VisualBox["status"],
score: truth ? null : numberValue(box.score, "L3 score"),
};
}
function parseResult(value: unknown): L3PointPillarsVisualAuditResult {
const result = objectValue(value, "L3 visual result");
exact(
result.schema_version,
"missioncore.l3-pointpillars-visual-audit-result/v1",
"L3 result.schema_version",
);
exact(result.access, "read-only", "L3 result.access");
const resultId = stringValue(result.result_id, "L3 result_id");
const sourceRunId = stringValue(result.source_run_id, "L3 source_run_id");
const frameIdentity = stringValue(
result.source_frame_results_identity_sha256,
"L3 frame identity",
);
const datasetIdentity = stringValue(
result.dataset_release_identity_sha256,
"L3 dataset identity",
);
if (
!RESULT_ID.test(resultId)
|| !RUN_ID.test(sourceRunId)
|| !SHA256.test(frameIdentity)
|| !SHA256.test(datasetIdentity)
) {
throw new AdvancedLaboratoryContractError(
"L3 result: нарушена идентичность.",
);
}
const metrics = objectValue(result.metrics, "L3 metrics");
const aggregates = objectValue(metrics.aggregates, "L3 aggregates");
const volume = objectValue(
aggregates.prediction_volume,
"L3 prediction_volume",
);
const latency = objectValue(
aggregates.inference_latency_ms,
"L3 latency",
);
const frames = arrayValue(result.frames, "L3 frames").map(parseSummary);
if (
!frames.length
|| frames.length > 24
|| new Set(frames.map(({ frameId }) => frameId)).size !== frames.length
) {
throw new AdvancedLaboratoryContractError(
"L3 frames: нарушен ограниченный каталог.",
);
}
return {
resultId,
createdAtUtc: stringValue(result.created_at_utc, "L3 created_at_utc"),
status: exact(
result.status,
"operator-visual-review-required",
"L3 status",
) as "operator-visual-review-required",
sourceRunId,
sourceFrameResultsIdentitySha256: frameIdentity,
datasetSourceId: stringValue(result.dataset_source_id, "L3 dataset_source_id"),
datasetReleaseIdentitySha256: datasetIdentity,
metrics: {
frameCount: integerValue(metrics.frame_count, "L3 frame_count"),
bevMap40: numberValue(aggregates.bev_map40, "L3 bev_map40"),
threeDMap40: numberValue(aggregates["3d_map40"], "L3 3d_map40"),
falseOccupiedRate: numberValue(
aggregates.false_occupied_rate,
"L3 false_occupied_rate",
),
inferenceP95Ms: numberValue(latency.p95, "L3 latency.p95"),
modelOutputBoxCount: integerValue(
volume.model_output_box_count,
"L3 model_output_box_count",
),
evaluatedBoxCount: integerValue(
volume.evaluated_box_count,
"L3 evaluated_box_count",
),
outsideSharedRangeCount: integerValue(
volume.outside_shared_range_count,
"L3 outside_shared_range_count",
),
},
frames,
};
}
export async function fetchL3PointPillarsVisualAudit({
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {}): Promise<L3PointPillarsVisualAuditResult | null> {
const response = await fetcher(
"/api/v1/laboratory/l3/pointpillars-visual-audits/results?limit=1",
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new AdvancedLaboratoryContractError(
`L3 visual audit недоступен: HTTP ${response.status}.`,
);
}
const catalog = objectValue(await response.json(), "L3 result catalog");
exact(
catalog.schema_version,
"missioncore.l3-pointpillars-visual-audit-catalog-results/v1",
"L3 catalog.schema_version",
);
exact(catalog.access, "read-only", "L3 catalog.access");
const items = arrayValue(catalog.items, "L3 catalog.items");
if (items.length > 1) {
throw new AdvancedLaboratoryContractError("L3 catalog: лишние результаты.");
}
return items.length ? parseResult(items[0]) : null;
}
export async function fetchL3PointPillarsVisualFrame(
resultId: string,
frameId: string,
{
fetcher = fetch,
signal,
}: {
fetcher?: LaboratoryFetch;
signal?: AbortSignal;
} = {},
): Promise<L3VisualFrame> {
if (!RESULT_ID.test(resultId) || !FRAME_ID.test(frameId)) {
throw new AdvancedLaboratoryContractError(
"L3 visual frame: неверная идентичность.",
);
}
const response = await fetcher(
`/api/v1/laboratory/l3/pointpillars-visual-audits/${resultId}/frames/${frameId}`,
{ method: "GET", headers: { Accept: "application/json" }, signal },
);
if (!response.ok) {
throw new AdvancedLaboratoryContractError(
`L3 visual frame недоступен: HTTP ${response.status}.`,
);
}
const payload = objectValue(await response.json(), "L3 visual frame");
exact(
payload.schema_version,
"missioncore.l3-pointpillars-visual-frame/v1",
"L3 frame.schema_version",
);
exact(payload.access, "read-only", "L3 frame.access");
exact(payload.frame_id, frameId, "L3 frame.frame_id");
const points = objectValue(payload.points, "L3 points");
exact(points.layout, "flat-xyzi", "L3 points.layout");
const pointValues = arrayValue(points.values, "L3 points.values").map(
(value, index) => numberValue(value, `L3 points[${index}]`, -Infinity),
);
const sampledPointCount = integerValue(
points.sampled_point_count,
"L3 sampled_point_count",
);
if (sampledPointCount > 12_000 || pointValues.length !== sampledPointCount * 4) {
throw new AdvancedLaboratoryContractError(
"L3 points: нарушен ограниченный массив.",
);
}
return {
frameId,
summary: parseSummary(payload.summary),
sourcePointCount: integerValue(
points.source_point_count,
"L3 source_point_count",
),
sharedRangePointCount: integerValue(
points.shared_range_point_count,
"L3 shared_range_point_count",
),
sampledPointCount,
pointsXyzi: pointValues,
truthBoxes: arrayValue(payload.truth_boxes, "L3 truth_boxes")
.map((box) => parseBox(box, true)),
predictionBoxes: arrayValue(
payload.prediction_boxes,
"L3 prediction_boxes",
).map((box) => parseBox(box, false)),
};
}
+1
View File
@@ -4,6 +4,7 @@
@import "./styles/workspaces.css";
@import "./styles/laboratory.css";
@import "./styles/e40-case-review.css";
@import "./styles/l3-pointpillars-visual-audit.css";
@import "./styles/laboratory-reporting.css";
@import "./styles/e34-temporal-layer.css";
@import "./styles/e35-degradation-recovery.css";
@@ -0,0 +1,142 @@
.l3-visual-audit {
display: grid;
height: clamp(36rem, 68vh, 54rem);
min-height: 36rem;
}
.l3-visual-audit > .laboratory-evidence-viewer {
min-height: 0;
}
.l3-visual-audit__scene {
position: relative;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
background: var(--nodedc-canvas);
}
.l3-visual-audit__scene canvas {
display: block;
width: 100%;
height: 100%;
}
.l3-visual-audit__state {
display: flex;
width: 100%;
height: 100%;
min-height: 22rem;
align-items: center;
justify-content: center;
gap: 0.55rem;
background: var(--nodedc-canvas);
color: var(--nodedc-text-muted);
font-size: 0.62rem;
}
.l3-visual-audit__actions {
display: flex;
flex: 1;
min-width: 0;
align-items: center;
gap: 0.45rem;
}
.l3-visual-audit__pagination {
display: flex;
flex: none;
gap: 0.35rem;
}
.l3-visual-audit__actions .nodedc-select-anchor,
.l3-visual-audit__actions .nodedc-select {
width: clamp(17rem, 34vw, 31rem);
}
.l3-visual-audit
.laboratory-evidence-viewer__controls:has(.l3-visual-audit__actions) {
right: 0.6rem;
left: 0.6rem;
}
.l3-visual-audit__overlay {
position: absolute;
z-index: 3;
left: 0.6rem;
bottom: 0.6rem;
display: grid;
width: min(58rem, calc(100% - 1.2rem));
grid-template-columns: minmax(8rem, 0.55fr) minmax(13rem, 1fr) minmax(19rem, 1.2fr);
align-items: end;
gap: 0.7rem;
border-radius: var(--nodedc-radius-control-compact);
background: var(--nodedc-floating-surface);
padding: 0.55rem 0.65rem;
color: var(--nodedc-text-secondary);
backdrop-filter: blur(var(--nodedc-blur-control));
pointer-events: none;
}
.l3-visual-audit__overlay > div {
display: grid;
min-width: 0;
gap: 0.12rem;
}
.l3-visual-audit__overlay span,
.l3-visual-audit__overlay small {
color: var(--nodedc-text-muted);
font-size: 0.49rem;
line-height: 1.35;
}
.l3-visual-audit__overlay strong {
color: var(--nodedc-text-primary);
font-size: 0.6rem;
}
.l3-visual-audit__legend {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.35rem 0.6rem;
}
.l3-visual-audit__legend span {
display: flex;
align-items: center;
gap: 0.35rem;
color: var(--nodedc-text-secondary);
white-space: nowrap;
}
.l3-visual-audit__legend span::before {
width: 0.42rem;
height: 0.42rem;
flex: none;
border-radius: 50%;
background: var(--nodedc-text-primary);
content: "";
}
.l3-visual-audit__legend span[data-tone="tp"]::before {
background: rgb(var(--nodedc-success-rgb));
}
.l3-visual-audit__legend span[data-tone="fp"]::before {
background: rgb(var(--nodedc-danger-rgb));
}
.l3-visual-audit__legend span[data-tone="fn"]::before {
background: rgb(var(--nodedc-warning-rgb));
}
@media (max-width: 900px) {
.l3-visual-audit__overlay {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.l3-visual-audit__legend {
display: none;
}
}
@@ -18,6 +18,7 @@ import { E37Result } from "./E37Result";
import { E38Result } from "./E38Result";
import { E39Result } from "./E39Result";
import { E40Result } from "./E40Result";
import { L3PointPillarsResult } from "./L3PointPillarsResult";
import { RecordedReplayEvidence } from "./RecordedReplayEvidence";
export { isAdvancedLaboratoryWorkId };
@@ -32,6 +33,10 @@ export function advancedLaboratoryWorkOptions(
): readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] {
const available = new Set(index.map(({ workId }) => workId));
const options: readonly LaboratoryOption<AdvancedLaboratoryWorkId>[] = [
{
id: "l3-pointpillars-visual-audit",
label: "L3 · визуальный аудит PointPillars",
},
{ id: "e31-source-binding", label: "LAB E31 · source binding" },
{ id: "e32-track-geometry", label: "LAB E32 · TrackGeometry v1" },
{ id: "e33-worker-shadow", label: "LAB E33 · worker shadow 1×" },
@@ -82,6 +87,9 @@ export function AdvancedLaboratoryResult({
failedSessionId: string | null;
replayError: string | null;
}) {
if (workId === "l3-pointpillars-visual-audit" && results.l3) {
return <L3PointPillarsResult result={results.l3} />;
}
if (workId === "e40-perception-product-gate" && results.e40) {
return <E40Result rigLabel={rigLabel} result={results.e40} />;
}
@@ -0,0 +1,141 @@
import {
LaboratoryEvidence,
LaboratoryResultSummary,
LaboratorySummary,
LaboratoryWorkTemplate,
} from "../../components/laboratory/LaboratoryPresentation";
import type {
L3PointPillarsVisualAuditResult,
} from "../../core/laboratory/l3PointPillarsVisualAudit";
import { L3PointPillarsVisualAudit } from "./L3PointPillarsVisualAudit";
function percent(value: number, digits = 3): string {
return `${(value * 100).toLocaleString("ru-RU", {
maximumFractionDigits: digits,
})}%`;
}
export function L3PointPillarsResult({
result,
}: {
result: L3PointPillarsVisualAuditResult;
}) {
const metrics = result.metrics;
return (
<LaboratoryWorkTemplate
summary={(
<LaboratorySummary
title="L3 · визуальный аудит PointPillars"
description="Визуальная производная полного KITTI transfer-прогона: исходные LiDAR-точки, независимые truth-боксы и предсказания модели сопоставлены тем же глобальным 3D IoU-контрактом. Производная не меняет метрики и не выдает результат за K1 accuracy."
status="Требуется визуальная проверка"
statusTone="warning"
facts={[
{
label: "Источник",
value: `${result.datasetSourceId} · ${metrics.frameCount.toLocaleString("ru-RU")} кадров`,
},
{
label: "Визуальная выборка",
value: `${result.frames.length} доказательных кадров · lazy-load`,
},
{
label: "Исполнение",
value: "Worker 006 · последовательная производная",
},
{
label: "Полномочия",
value: "Read-only · без navigation/safety acceptance",
},
]}
brief={{
question: "Соответствуют ли измеренные провал переноса и почти сплошная ложная занятость фактической геометрии исходных LiDAR-кадров?",
approach: "Полный sealed run проверен по hash identity. Global score-order matching повторён с исходными порогами IoU, после чего детерминированно выбраны TP-, FP-, FN- и class-coverage кадры. В браузер поступает только выбранный кадр.",
principalResult: `Полный прогон: BEV mAP40 ${percent(metrics.bevMap40)}, 3D mAP40 ${percent(metrics.threeDMap40, 6)}, false occupied ${percent(metrics.falseOccupiedRate)}. Визуальный аудит теперь доступен в 3D и BEV.`,
limitation: "Это cross-domain KITTI probe модели, обученной на proprietary solid-state LiDAR. Он проверяет перенос и корректность измерителя, но не доказывает точность K1, camera-first детектор, навигацию или safety.",
}}
method={{
completeness: "complete",
executionClass: "ai-inference",
pipelineId: "l3-pointpillars-kitti-transfer/visual-audit-v1",
components: [
{
kind: "source",
name: result.sourceRunId,
version: "sealed 3769-frame transfer run",
role: "неизменяемые предсказания и latency",
identitySha256: result.sourceFrameResultsIdentitySha256,
},
{
kind: "source",
name: result.datasetSourceId,
version: "admitted public release",
role: "LiDAR и независимые ориентированные 3D truth-боксы",
identitySha256: result.datasetReleaseIdentitySha256,
},
{
kind: "algorithm",
name: "Global score-order oriented 3D IoU matching",
version: "Car 0.7 · Pedestrian/Cyclist 0.5",
role: "единая классификация TP, FP и FN",
identitySha256: null,
},
{
kind: "runtime",
name: "Worker 006 → Mission Core lazy evidence",
version: result.resultId,
role: "append-only visual derivative без повторного inference",
identitySha256: result.resultId.split("-").at(-1) ?? null,
},
],
}}
/>
)}
evidence={(
<LaboratoryEvidence
eyebrow="SEALED RUN → ВИЗУАЛЬНОЕ ДОКАЗАТЕЛЬСТВО"
title="LiDAR, truth и предсказания PointPillars"
kind="diagnostic-model"
resizable
>
<L3PointPillarsVisualAudit result={result} />
</LaboratoryEvidence>
)}
result={(
<LaboratoryResultSummary
title="Численный transfer gate не пройден; визуальная ревизия открыта"
status="Не допущено"
statusTone="danger"
metrics={[
{
label: "BEV mAP40",
value: percent(metrics.bevMap40),
hint: "полный denominator · public cross-domain",
},
{
label: "3D mAP40",
value: percent(metrics.threeDMap40, 6),
hint: `${metrics.evaluatedBoxCount.toLocaleString("ru-RU")} оценённых боксов`,
},
{
label: "False occupied",
value: percent(metrics.falseOccupiedRate),
hint: `${metrics.modelOutputBoxCount.toLocaleString("ru-RU")} post-NMS · ${metrics.outsideSharedRangeCount.toLocaleString("ru-RU")} вне range`,
},
{
label: "Inference p95",
value: `${metrics.inferenceP95Ms.toLocaleString("ru-RU", {
maximumFractionDigits: 2,
})} мс`,
hint: "Worker 006 · последовательное исполнение",
},
]}
conclusion={{
proved: "Полный cross-domain прогон воспроизводим, его численные артефакты связаны с исходными LiDAR-кадрами, а TP/FP/FN можно проверить в 3D и BEV без повторного inference.",
notProved: "Не доказаны пригодность этой модели для K1, точность camera-first семантики, метрическая геометрия K1 в других условиях, навигация, команды или safety.",
decision: "Не переносить этот публичный PointPillars-кандидат в operational pipeline. Использовать визуальный аудит для проверки природы провала и сохранить архитектуру camera-first semantics + LiDAR metric geometry как основной продуктовый путь.",
}}
/>
)}
/>
);
}
@@ -0,0 +1,256 @@
import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import type {
L3VisualBox,
L3VisualFrame,
} from "../../core/laboratory/l3PointPillarsVisualAudit";
export type L3VisualMode = "3d" | "bev";
function tokenColor(
host: HTMLElement,
token: string,
fallback: readonly [number, number, number],
): THREE.Color {
const value = getComputedStyle(host).getPropertyValue(token).trim();
if (value.startsWith("#")) return new THREE.Color(value);
const channels = value.match(/[\d.]+/g)?.slice(0, 3).map(Number);
const [red, green, blue] = channels?.length === 3 ? channels : fallback;
return new THREE.Color(red / 255, green / 255, blue / 255);
}
function pointPositions(values: readonly number[]): Float32Array {
const positions = new Float32Array((values.length / 4) * 3);
for (
let source = 0, target = 0;
source < values.length;
source += 4, target += 3
) {
positions[target] = values[source];
positions[target + 1] = values[source + 2];
positions[target + 2] = -values[source + 1];
}
return positions;
}
function boxSegments(box: L3VisualBox): Float32Array {
const [centerX, centerY, centerZ] = box.centerXyzM;
const [length, width, height] = box.sizeLwhM;
const cosine = Math.cos(box.yawRad);
const sine = Math.sin(box.yawRad);
const corners: THREE.Vector3[] = [];
for (const zOffset of [-height / 2, height / 2]) {
for (const [xOffset, yOffset] of [
[-length / 2, -width / 2],
[length / 2, -width / 2],
[length / 2, width / 2],
[-length / 2, width / 2],
]) {
const x = centerX + xOffset * cosine - yOffset * sine;
const y = centerY + xOffset * sine + yOffset * cosine;
corners.push(new THREE.Vector3(x, centerZ + zOffset, -y));
}
}
const edges = [
[0, 1], [1, 2], [2, 3], [3, 0],
[4, 5], [5, 6], [6, 7], [7, 4],
[0, 4], [1, 5], [2, 6], [3, 7],
];
const positions = new Float32Array(edges.length * 6);
edges.forEach(([from, to], index) => {
corners[from].toArray(positions, index * 6);
corners[to].toArray(positions, index * 6 + 3);
});
return positions;
}
function addBoxes(
scene: THREE.Scene,
boxes: readonly L3VisualBox[],
colors: Readonly<Record<L3VisualBox["status"], THREE.Color>>,
opacity: number,
): THREE.LineSegments[] {
return boxes.map((box) => {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.BufferAttribute(boxSegments(box), 3),
);
const material = new THREE.LineBasicMaterial({
color: colors[box.status],
transparent: true,
opacity,
depthTest: true,
depthWrite: false,
});
const lines = new THREE.LineSegments(geometry, material);
scene.add(lines);
return lines;
});
}
export function L3PointPillarsScene({
frame,
mode,
}: {
frame: L3VisualFrame;
mode: L3VisualMode;
}) {
const hostRef = useRef<HTMLDivElement | null>(null);
const [renderError, setRenderError] = useState<string | null>(null);
useEffect(() => {
const host = hostRef.current;
if (!host) return;
setRenderError(null);
let renderer: THREE.WebGLRenderer;
try {
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: false,
powerPreference: "high-performance",
});
} catch {
setRenderError("Браузер не смог открыть WebGL-сцену L3.");
return;
}
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.setClearColor(
tokenColor(host, "--nodedc-canvas", [5, 5, 6]),
1,
);
renderer.domElement.setAttribute("role", "img");
renderer.domElement.setAttribute(
"aria-label",
`L3 PointPillars: кадр ${frame.frameId}, режим ${mode}`,
);
host.append(renderer.domElement);
const scene = new THREE.Scene();
const positions = pointPositions(frame.pointsXyzi);
const pointsGeometry = new THREE.BufferGeometry();
pointsGeometry.setAttribute(
"position",
new THREE.BufferAttribute(positions, 3),
);
const pointsMaterial = new THREE.PointsMaterial({
color: tokenColor(host, "--nodedc-text-secondary", [187, 190, 196]),
size: mode === "bev" ? 1.4 : 1.8,
sizeAttenuation: false,
transparent: true,
opacity: 0.52,
depthWrite: false,
});
scene.add(new THREE.Points(pointsGeometry, pointsMaterial));
const colors: Readonly<Record<L3VisualBox["status"], THREE.Color>> = {
matched: tokenColor(host, "--nodedc-text-primary", [247, 248, 244]),
"false-negative": tokenColor(
host,
"--nodedc-warning-rgb",
[255, 209, 102],
),
"true-positive": tokenColor(
host,
"--nodedc-success-rgb",
[143, 255, 93],
),
"false-positive": tokenColor(
host,
"--nodedc-danger-rgb",
[255, 98, 112],
),
};
const truthLines = addBoxes(scene, frame.truthBoxes, colors, 0.9);
const predictionLines = addBoxes(
scene,
frame.predictionBoxes,
colors,
0.72,
);
const grid = new THREE.GridHelper(
80,
40,
tokenColor(host, "--nodedc-text-muted", [96, 99, 106]),
tokenColor(host, "--nodedc-glass-outline", [48, 50, 56]),
);
const gridMaterials = Array.isArray(grid.material)
? grid.material
: [grid.material];
gridMaterials.forEach((material) => {
material.transparent = true;
material.opacity = 0.22;
material.depthWrite = false;
});
scene.add(grid);
const perspective = new THREE.PerspectiveCamera(52, 1, 0.1, 500);
perspective.position.set(-12, 18, 36);
const orthographic = new THREE.OrthographicCamera(-40, 40, 40, -40, 0.1, 500);
orthographic.position.set(35, 100, 0);
orthographic.up.set(1, 0, 0);
const camera = mode === "bev" ? orthographic : perspective;
camera.lookAt(30, 0, 0);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = false;
controls.enableRotate = mode === "3d";
controls.enablePan = true;
controls.enableZoom = true;
controls.screenSpacePanning = true;
controls.target.set(30, 0, 0);
controls.update();
const render = () => renderer.render(scene, camera);
controls.addEventListener("change", render);
const resize = () => {
const width = Math.max(host.clientWidth, 1);
const height = Math.max(host.clientHeight, 1);
renderer.setSize(width, height, false);
if (camera instanceof THREE.PerspectiveCamera) {
camera.aspect = width / height;
camera.updateProjectionMatrix();
} else {
const horizontal = 42;
camera.left = -horizontal;
camera.right = horizontal;
camera.top = horizontal / (width / height);
camera.bottom = -horizontal / (width / height);
camera.updateProjectionMatrix();
}
render();
};
const observer = new ResizeObserver(resize);
observer.observe(host);
resize();
return () => {
observer.disconnect();
controls.removeEventListener("change", render);
controls.dispose();
pointsGeometry.dispose();
pointsMaterial.dispose();
[...truthLines, ...predictionLines].forEach((lines) => {
lines.geometry.dispose();
(lines.material as THREE.Material).dispose();
});
grid.geometry.dispose();
gridMaterials.forEach((material) => material.dispose());
renderer.dispose();
renderer.domElement.remove();
};
}, [frame, mode]);
return (
<div className="l3-visual-audit__scene" ref={hostRef}>
{renderError ? (
<div className="l3-visual-audit__state" role="status">
{renderError}
</div>
) : null}
</div>
);
}
@@ -0,0 +1,171 @@
import { useEffect, useState } from "react";
import {
Icon,
IconButton,
Select,
} from "@nodedc/ui-react";
import { LaboratoryEvidenceViewer } from "../../components/laboratory/LaboratoryEvidenceViewer";
import {
fetchL3PointPillarsVisualFrame,
type L3PointPillarsVisualAuditResult,
type L3VisualFrame,
} from "../../core/laboratory/l3PointPillarsVisualAudit";
import {
L3PointPillarsScene,
type L3VisualMode,
} from "./L3PointPillarsScene";
function frameLabel(
frame: L3PointPillarsVisualAuditResult["frames"][number],
): string {
return (
`Кадр ${frame.frameId} · TP ${frame.truePositiveCount}`
+ ` · FP ${frame.falsePositiveCount} · FN ${frame.falseNegativeCount}`
);
}
export function L3PointPillarsVisualAudit({
result,
}: {
result: L3PointPillarsVisualAuditResult;
}) {
const [selectedFrameId, setSelectedFrameId] = useState(
result.frames[0]?.frameId ?? "",
);
const [frame, setFrame] = useState<L3VisualFrame | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [mode, setMode] = useState<L3VisualMode>("3d");
const [expanded, setExpanded] = useState(false);
useEffect(() => {
if (!selectedFrameId) return;
const controller = new AbortController();
setFrame(null);
setLoading(true);
setError(null);
void fetchL3PointPillarsVisualFrame(
result.resultId,
selectedFrameId,
{ signal: controller.signal },
).then((next) => {
if (!controller.signal.aborted) setFrame(next);
}).catch((caught: unknown) => {
if (controller.signal.aborted) return;
setError(
caught instanceof Error
? caught.message
: "Визуальный кадр L3 недоступен.",
);
}).finally(() => {
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [result.resultId, selectedFrameId]);
const selectedIndex = result.frames.findIndex(
({ frameId }) => frameId === selectedFrameId,
);
const navigate = (offset: -1 | 1) => {
if (!result.frames.length || selectedIndex < 0) return;
const index = (
selectedIndex + offset + result.frames.length
) % result.frames.length;
setSelectedFrameId(result.frames[index].frameId);
};
const controls = (
<div className="l3-visual-audit__actions">
<div className="l3-visual-audit__pagination">
<IconButton
label="Предыдущий кадр L3"
onClick={() => navigate(-1)}
>
<Icon name="chevron-left" size={16} />
</IconButton>
<IconButton
label="Следующий кадр L3"
onClick={() => navigate(1)}
>
<Icon name="chevron-right" size={16} />
</IconButton>
</div>
<Select
label="Выбрать кадр визуального аудита L3"
value={selectedFrameId}
options={result.frames.map((item) => ({
value: item.frameId,
label: frameLabel(item),
}))}
variant="split"
menuWidth="anchor"
onChange={setSelectedFrameId}
/>
</div>
);
const overlay = frame ? (
<div className="l3-visual-audit__overlay">
<div>
<span>Кадр</span>
<strong>{frame.frameId}</strong>
<small>
{frame.sampledPointCount.toLocaleString("ru-RU")} из{" "}
{frame.sharedRangePointCount.toLocaleString("ru-RU")} точек
</small>
</div>
<div>
<span>Сопоставление 3D</span>
<strong>
TP {frame.summary.truePositiveCount}
{" · "}FP {frame.summary.falsePositiveCount}
{" · "}FN {frame.summary.falseNegativeCount}
</strong>
<small>
{frame.summary.inferenceMs.toLocaleString("ru-RU", {
maximumFractionDigits: 2,
})} мс · {frame.summary.outsideSharedRangeCount} вне общего range
</small>
</div>
<div className="l3-visual-audit__legend" aria-label="Легенда L3">
<span data-tone="truth">Truth · совпало</span>
<span data-tone="tp">TP · предсказание</span>
<span data-tone="fp">FP · ложный бокс</span>
<span data-tone="fn">FN · пропущенный truth</span>
</div>
</div>
) : undefined;
return (
<div className="l3-visual-audit">
<LaboratoryEvidenceViewer
label="визуальный аудит PointPillars"
mode={mode}
modes={[
{ value: "3d", label: "3D" },
{ value: "bev", label: "BEV" },
]}
expanded={expanded}
onModeChange={setMode}
onExpandedChange={setExpanded}
actions={controls}
overlay={overlay}
>
{loading ? (
<div className="l3-visual-audit__state" role="status">
<span className="busy-indicator" aria-hidden="true" />
<span>Проверяем и открываем выбранный кадр L3</span>
</div>
) : error || !frame ? (
<div className="l3-visual-audit__state" role="status">
<Icon name="alert" size={18} />
<span>{error ?? "Визуальный кадр L3 недоступен."}</span>
</div>
) : (
<L3PointPillarsScene frame={frame} mode={mode} />
)}
</LaboratoryEvidenceViewer>
</div>
);
}
@@ -18,6 +18,7 @@ function mergeResults(
next: AdvancedLaboratoryResults,
): AdvancedLaboratoryResults {
return {
l3: next.l3 ?? current.l3,
e31: next.e31 ?? current.e31,
e32: next.e32 ?? current.e32,
e33: next.e33 ?? current.e33,