feat(lidar): admit and benchmark GOOSE baseline

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 14:14:20 +03:00
parent 881e97312b
commit 951b40c870
20 changed files with 2871 additions and 241 deletions
@@ -8,6 +8,8 @@ export interface DatasetGatewayCatalog {
configured: boolean;
admitted: boolean;
status: "ready" | "blocked-storage-policy";
attestation: "worker-manifest" | "local-worker-path" | "none";
manifestValid: boolean;
requiredWindowsRoot: string;
requiredWslRoot: string;
};
@@ -21,7 +23,31 @@ export interface DatasetGatewayCatalog {
platforms: string[];
superclasses: string[];
validationArchiveGb: number;
admissionStatus: "ready-for-download" | "blocked-storage-policy";
admissionStatus:
| "blocked-storage-policy"
| "ready-for-download"
| "downloading"
| "downloaded"
| "verifying"
| "verified"
| "frame-ready";
archive: {
bytesTransferred: number;
totalBytes: number;
sizeBytes: number | null;
sha256: string | null;
integrity: string;
vendorChecksumAvailable: false;
} | null;
frame: {
frameId: string;
pointCount: number;
semanticClassCount: number;
groundTruthGroundFraction: number;
previewPointCount: number;
previewSha256: string;
previewAvailable: true;
} | null;
};
representations: Array<{
id: DatasetRepresentationId;
@@ -45,6 +71,46 @@ export interface DatasetGatewayCatalog {
nextAction: string;
}
export interface DatasetNativeScanPreview {
sourceId: "goose-3d/v2025-08-22";
frameId: string;
sourcePointCount: number;
pointCount: number;
pointsXyzM: Array<[number, number, number]>;
remission0To255: number[];
semanticLabelIds: number[];
semanticRgb0To255: number[];
groundTruthGround: number[];
classes: Array<{
labelId: number;
className: string;
hex: string;
challengeCategoryId: number;
challengeCategoryName: string;
}>;
}
export interface DatasetGroundComparison {
sourceId: "goose-3d/v2025-08-22";
frameId: string;
pointCount: number;
currentGround: number[];
groundTruthGround: number[];
evaluated: number[];
disagreement: number[];
metrics: {
precision: number;
recall: number;
f1: number;
groundIou: number;
accuracy: number;
artificialGroundRecall: number;
naturalGroundRecall: number;
obstacleNonGroundRecall: number;
};
latencyMs: number;
}
export class DatasetGatewayContractError extends Error {}
type DatasetFetch = (
@@ -105,12 +171,43 @@ function number(value: unknown, label: string): number {
return value;
}
function nullableNumber(value: unknown, label: string): number | null {
return value === null ? null : number(value, label);
}
function nullableDigest(value: unknown, label: string): string | null {
if (value === null) return null;
const digest = string(value, label);
if (!/^[a-f0-9]{64}$/.test(digest)) {
throw new DatasetGatewayContractError(`${label}: некорректный SHA-256`);
}
return digest;
}
function integers(
value: unknown,
label: string,
maximum: number,
): number[] {
return array(value, label).map((item, index) => {
if (
typeof item !== "number"
|| !Number.isInteger(item)
|| item < 0
|| item > maximum
) {
throw new DatasetGatewayContractError(`${label}[${index}]: некорректное число`);
}
return item;
});
}
export function parseDatasetGatewayCatalog(
value: unknown,
): DatasetGatewayCatalog {
const source = record(value, "Dataset Gateway");
if (
source.schema_version !== "missioncore.dataset-gateway-catalog/v1"
source.schema_version !== "missioncore.dataset-gateway-catalog/v2"
|| source.access !== "read-only"
) {
throw new DatasetGatewayContractError("Dataset Gateway contract несовместим");
@@ -123,6 +220,14 @@ export function parseDatasetGatewayCatalog(
if (storage.path_exposed !== false) {
throw new DatasetGatewayContractError("Dataset Gateway раскрыл локальный путь");
}
const storageAttestation = storage.attestation;
if (
storageAttestation !== "worker-manifest"
&& storageAttestation !== "local-worker-path"
&& storageAttestation !== "none"
) {
throw new DatasetGatewayContractError("storage.attestation: неизвестное значение");
}
const sources = array(source.sources, "sources");
if (sources.length !== 1) {
throw new DatasetGatewayContractError("Ожидался один первичный dataset source");
@@ -137,9 +242,72 @@ export function parseDatasetGatewayCatalog(
if (
admissionStatus !== "ready-for-download"
&& admissionStatus !== "blocked-storage-policy"
&& admissionStatus !== "downloading"
&& admissionStatus !== "downloaded"
&& admissionStatus !== "verifying"
&& admissionStatus !== "verified"
&& admissionStatus !== "frame-ready"
) {
throw new DatasetGatewayContractError("source admission status неизвестен");
}
const archive = admission.archive === null
? null
: record(admission.archive, "source.admission.archive");
const archiveValue = archive
? {
bytesTransferred: number(
archive.bytes_transferred,
"archive.bytes_transferred",
),
totalBytes: number(archive.total_bytes, "archive.total_bytes"),
sizeBytes: nullableNumber(archive.size_bytes, "archive.size_bytes"),
sha256: nullableDigest(archive.sha256, "archive.sha256"),
integrity: string(archive.integrity, "archive.integrity", true),
vendorChecksumAvailable: (() => {
if (archive.vendor_checksum_available !== false) {
throw new DatasetGatewayContractError(
"archive.vendor_checksum_available: ожидался false",
);
}
return false as const;
})(),
}
: null;
const admittedFrame = admission.frame === null
? null
: record(admission.frame, "source.admission.frame");
const frameValue = admittedFrame
? {
frameId: string(admittedFrame.frame_id, "frame.frame_id", true),
pointCount: number(admittedFrame.point_count, "frame.point_count"),
semanticClassCount: number(
admittedFrame.semantic_class_count,
"frame.semantic_class_count",
),
groundTruthGroundFraction: number(
admittedFrame.ground_truth_ground_fraction,
"frame.ground_truth_ground_fraction",
),
previewPointCount: number(
admittedFrame.preview_point_count,
"frame.preview_point_count",
),
previewSha256: nullableDigest(
admittedFrame.preview_sha256,
"frame.preview_sha256",
) ?? (() => {
throw new DatasetGatewayContractError("frame.preview_sha256 отсутствует");
})(),
previewAvailable: (() => {
if (admittedFrame.preview_available !== true) {
throw new DatasetGatewayContractError(
"frame.preview_available: ожидался true",
);
}
return true as const;
})(),
}
: null;
const representations = array(
source.representations,
"representations",
@@ -188,6 +356,8 @@ export function parseDatasetGatewayCatalog(
configured: boolean(storage.configured, "storage.configured"),
admitted: boolean(storage.admitted, "storage.admitted"),
status: storageStatus,
attestation: storageAttestation,
manifestValid: boolean(storage.manifest_valid, "storage.manifest_valid"),
requiredWindowsRoot: string(
storage.required_windows_root,
"storage.required_windows_root",
@@ -208,6 +378,8 @@ export function parseDatasetGatewayCatalog(
"validation_archive_gb",
),
admissionStatus,
archive: archiveValue,
frame: frameValue,
},
representations,
pipeline,
@@ -223,6 +395,183 @@ export function parseDatasetGatewayCatalog(
};
}
export function parseDatasetNativeScanPreview(
value: unknown,
): DatasetNativeScanPreview {
const source = record(value, "Dataset preview");
if (
source.schema_version !== "missioncore.dataset-native-scan-preview/v1"
|| source.source_id !== "goose-3d/v2025-08-22"
|| source.representation !== "native-scan"
|| source.sampling !== "deterministic-even-index"
) {
throw new DatasetGatewayContractError("Dataset preview contract несовместим");
}
const sourcePointCount = number(source.source_point_count, "source_point_count");
const pointCount = number(source.point_count, "point_count");
const pointsXyzM = array(source.points_xyz_m, "points_xyz_m").map(
(value, index): [number, number, number] => {
const point = array(value, `points_xyz_m[${index}]`);
if (
point.length !== 3
|| point.some((coordinate) =>
typeof coordinate !== "number" || !Number.isFinite(coordinate)
)
) {
throw new DatasetGatewayContractError(
`points_xyz_m[${index}]: некорректная точка`,
);
}
return [point[0] as number, point[1] as number, point[2] as number];
},
);
const remission0To255 = integers(
source.remission_0_to_255,
"remission_0_to_255",
255,
);
const semanticLabelIds = integers(
source.semantic_label_ids,
"semantic_label_ids",
65_535,
);
const semanticRgb0To255 = integers(
source.semantic_rgb_0_to_255,
"semantic_rgb_0_to_255",
255,
);
const groundTruthGround = integers(
source.ground_truth_ground,
"ground_truth_ground",
1,
);
if (
pointCount < 1
|| pointCount > 50_000
|| pointCount > sourcePointCount
|| pointsXyzM.length !== pointCount
|| remission0To255.length !== pointCount
|| semanticLabelIds.length !== pointCount
|| semanticRgb0To255.length !== pointCount * 3
|| groundTruthGround.length !== pointCount
) {
throw new DatasetGatewayContractError("Dataset preview arrays не выровнены");
}
const classes = array(source.classes, "classes").map((value, index) => {
const item = record(value, `classes[${index}]`);
return {
labelId: number(item.label_id, "class.label_id"),
className: string(item.class_name, "class.class_name", true),
hex: string(item.hex, "class.hex"),
challengeCategoryId: number(
item.challenge_category_id,
"class.challenge_category_id",
),
challengeCategoryName: string(
item.challenge_category_name,
"class.challenge_category_name",
true,
),
};
});
const safety = record(source.safety, "safety");
if (
safety.visualization_only !== true
|| safety.navigation_or_safety_accepted !== false
) {
throw new DatasetGatewayContractError("Dataset preview safety boundary нарушен");
}
return {
sourceId: "goose-3d/v2025-08-22",
frameId: string(source.frame_id, "frame_id", true),
sourcePointCount,
pointCount,
pointsXyzM,
remission0To255,
semanticLabelIds,
semanticRgb0To255,
groundTruthGround,
classes,
};
}
export function parseDatasetGroundComparison(
value: unknown,
): DatasetGroundComparison {
const source = record(value, "Ground comparison");
if (
source.schema_version !== "missioncore.dataset-ground-comparison-preview/v1"
|| source.source_id !== "goose-3d/v2025-08-22"
|| source.sampling !== "deterministic-even-index"
) {
throw new DatasetGatewayContractError("Ground comparison contract несовместим");
}
const pointCount = number(source.point_count, "point_count");
const currentGround = integers(source.current_ground, "current_ground", 1);
const groundTruthGround = integers(
source.ground_truth_ground,
"ground_truth_ground",
1,
);
const evaluated = integers(source.evaluated, "evaluated", 1);
const disagreement = integers(source.disagreement, "disagreement", 1);
if (
pointCount < 1
|| pointCount > 50_000
|| currentGround.length !== pointCount
|| groundTruthGround.length !== pointCount
|| evaluated.length !== pointCount
|| disagreement.length !== pointCount
) {
throw new DatasetGatewayContractError("Ground comparison arrays не выровнены");
}
const metrics = record(source.metrics, "metrics");
const fraction = (key: string): number => {
const value = number(metrics[key], `metrics.${key}`);
if (value > 1) {
throw new DatasetGatewayContractError(`metrics.${key}: ожидалась доля`);
}
return value;
};
const provider = record(source.provider, "provider");
if (
provider.provider_id !== "missioncore-local-percentile-ground/v1"
|| provider.ground_truth !== false
|| !/^[a-f0-9]{64}$/.test(
string(provider.implementation_sha256, "provider.implementation_sha256"),
)
) {
throw new DatasetGatewayContractError("Ground comparison provider несовместим");
}
const safety = record(source.safety, "safety");
if (
safety.qualification_only !== true
|| safety.navigation_or_safety_accepted !== false
) {
throw new DatasetGatewayContractError("Ground comparison safety boundary нарушен");
}
return {
sourceId: "goose-3d/v2025-08-22",
frameId: string(source.frame_id, "frame_id", true),
pointCount,
currentGround,
groundTruthGround,
evaluated,
disagreement,
metrics: {
precision: fraction("precision"),
recall: fraction("recall"),
f1: fraction("f1"),
groundIou: fraction("ground_iou"),
accuracy: fraction("accuracy"),
artificialGroundRecall: fraction("artificial_ground_recall"),
naturalGroundRecall: fraction("natural_ground_recall"),
obstacleNonGroundRecall: fraction("obstacle_non_ground_recall"),
},
latencyMs: number(source.latency_ms, "latency_ms"),
};
}
async function responseJson(response: Response): Promise<unknown> {
if (!response.ok) {
throw new Error(`Dataset Gateway HTTP ${response.status}`);
@@ -241,3 +590,30 @@ export async function fetchDatasetGatewayCatalog(
});
return parseDatasetGatewayCatalog(await responseJson(response));
}
export async function fetchDatasetNativeScanPreview(
options: { signal?: AbortSignal; fetcher?: DatasetFetch } = {},
): Promise<DatasetNativeScanPreview> {
const fetcher = options.fetcher ?? fetch;
const response = await fetcher("/api/v1/lidar/dataset-gateway/preview", {
method: "GET",
headers: { Accept: "application/json" },
signal: options.signal,
});
return parseDatasetNativeScanPreview(await responseJson(response));
}
export async function fetchDatasetGroundComparison(
options: { signal?: AbortSignal; fetcher?: DatasetFetch } = {},
): Promise<DatasetGroundComparison> {
const fetcher = options.fetcher ?? fetch;
const response = await fetcher(
"/api/v1/lidar/dataset-gateway/ground-comparison",
{
method: "GET",
headers: { Accept: "application/json" },
signal: options.signal,
},
);
return parseDatasetGroundComparison(await responseJson(response));
}