feat(perception): split vegetation evidence layers
This commit is contained in:
@@ -48,9 +48,13 @@ import { fetchM48SFixedClassDetectorResult } from "./m48sFixedClassDetector";
|
||||
import { fetchM48TRiskQualityResult } from "./m48tRiskQuality";
|
||||
import { fetchM49TgsFailClosedResult } from "./m49TgsFailClosed";
|
||||
import { fetchM49TgsFullShadowResult } from "./m49TgsFullShadow";
|
||||
import { fetchVegetationShadowResult } from "./vegetationShadow";
|
||||
import {
|
||||
fetchVegetationBenchmarkResult,
|
||||
fetchVegetationShadowResult,
|
||||
} from "./vegetationShadow";
|
||||
|
||||
export type AdvancedLaboratoryWorkId =
|
||||
| "lab-v1-vegetation-benchmark"
|
||||
| "lab-v1-vegetation-shadow"
|
||||
| "m48-object-centric-quality"
|
||||
| "m48-small-static-passage-regression"
|
||||
@@ -102,6 +106,7 @@ export interface AdvancedLaboratoryIndexItem {
|
||||
}
|
||||
|
||||
const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
"lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow",
|
||||
"m48-object-centric-quality",
|
||||
"m48-small-static-passage-regression",
|
||||
@@ -148,6 +153,7 @@ const WORK_IDS: readonly AdvancedLaboratoryWorkId[] = [
|
||||
];
|
||||
|
||||
const RESULT_PREFIX: Readonly<Record<AdvancedLaboratoryWorkId, string>> = {
|
||||
"lab-v1-vegetation-benchmark": "lab-v1-vegetation-benchmark",
|
||||
"lab-v1-vegetation-shadow": "lab-v1-vegetation-shadow",
|
||||
"m48-object-centric-quality": "m48-object-quality-(?:pack|result)",
|
||||
"m48-small-static-passage-regression": "m48-small-static-passage-regression",
|
||||
@@ -201,6 +207,7 @@ export function isAdvancedLaboratoryWorkId(
|
||||
|
||||
export function emptyAdvancedLaboratoryResults(): AdvancedLaboratoryResults {
|
||||
return {
|
||||
vegetationBenchmark: null,
|
||||
vegetationShadow: null,
|
||||
m47Graph: null,
|
||||
m48: null,
|
||||
@@ -335,7 +342,8 @@ export function advancedLaboratoryResultAvailable(
|
||||
workId: AdvancedLaboratoryWorkId,
|
||||
results: AdvancedLaboratoryResults,
|
||||
): boolean {
|
||||
return workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
|
||||
return workId === "lab-v1-vegetation-benchmark" ? results.vegetationBenchmark !== null
|
||||
: workId === "lab-v1-vegetation-shadow" ? results.vegetationShadow !== null
|
||||
: workId === "m48-object-centric-quality" ? results.m48 !== null
|
||||
: workId === "m48-small-static-passage-regression" ? results.m48SmallStatic !== null
|
||||
: workId === "m48-static-occupancy-qualification" ? results.m48StaticOccupancy !== null
|
||||
@@ -393,7 +401,10 @@ export async function fetchAdvancedLaboratoryResult(
|
||||
} = {},
|
||||
): Promise<AdvancedLaboratoryResults> {
|
||||
const results = emptyAdvancedLaboratoryResults();
|
||||
if (workId === "lab-v1-vegetation-shadow") {
|
||||
if (workId === "lab-v1-vegetation-benchmark") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation benchmark identity не выбрана.");
|
||||
results.vegetationBenchmark = await fetchVegetationBenchmarkResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "lab-v1-vegetation-shadow") {
|
||||
if (!resultId) throw new AdvancedLaboratoryContractError("Vegetation LAB identity не выбрана.");
|
||||
results.vegetationShadow = await fetchVegetationShadowResult(resultId, { fetcher, signal });
|
||||
} else if (workId === "m48-object-centric-quality") {
|
||||
|
||||
@@ -45,6 +45,7 @@ import type { M49TgsFullShadowResult } from "./m49TgsFullShadow";
|
||||
import type { VegetationShadowResult } from "./vegetationShadow";
|
||||
|
||||
export interface AdvancedLaboratoryResults {
|
||||
vegetationBenchmark: VegetationShadowResult | null;
|
||||
vegetationShadow: VegetationShadowResult | null;
|
||||
m47Graph: M47ReferenceGraphLabResult | null;
|
||||
m48: M48AdvancedResult | null;
|
||||
|
||||
@@ -967,6 +967,7 @@ export async function fetchAdvancedLaboratoryResults({
|
||||
const e39 = settledCatalogValue(settled[7]);
|
||||
const e40 = settledCatalogValue(settled[8]);
|
||||
return {
|
||||
vegetationBenchmark: null,
|
||||
vegetationShadow: null,
|
||||
m47Graph: null, m48: null, m48SmallStatic: null, m48StaticOccupancy: null,
|
||||
m48r3StaticOccupancy: null,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { LaboratoryFetch } from "./advancedResults";
|
||||
|
||||
const RESULT_ID = /^lab-v1-vegetation-shadow-[a-f0-9]{64}$/;
|
||||
const BENCHMARK_RESULT_ID = /^lab-v1-vegetation-benchmark-[a-f0-9]{64}$/;
|
||||
const SHA256 = /^[a-f0-9]{64}$/;
|
||||
const CANDIDATES = ["ddrnet", "ppliteseg"] as const;
|
||||
const ROUTE_MODES = ["source", "ddrnet", "ppliteseg", "urban", "rural", "offroad"] as const;
|
||||
@@ -75,6 +76,7 @@ export interface VegetationRouteVideo {
|
||||
aggregatePredictionPixels: readonly number[];
|
||||
policyPresets: Readonly<Record<string, Readonly<Record<string, string>>>> | null;
|
||||
fusionMode: "synchronised-multilayer-review" | null;
|
||||
validFovMaskSha256: string | null;
|
||||
}
|
||||
|
||||
export interface VegetationShadowResult {
|
||||
@@ -192,6 +194,7 @@ function visualCaseValue(
|
||||
value: unknown,
|
||||
resultId: string,
|
||||
expectedKind: "goose" | "ravnoves",
|
||||
endpointRoot: string,
|
||||
): VegetationVisualCase {
|
||||
const row = objectValue(value, `vegetation.${expectedKind}.case`);
|
||||
exact(row.source_kind, expectedKind, "vegetation.case.source_kind");
|
||||
@@ -210,7 +213,7 @@ function visualCaseValue(
|
||||
if (!SHA256.test(sha256) || !path.startsWith(`visual/${expectedKind}/${caseId}/`)) {
|
||||
throw new VegetationShadowContractError(`vegetation.case.assets.${key}: proof invalid.`);
|
||||
}
|
||||
projected[key] = `/api/v1/laboratory/vegetation-shadow/${encodeURIComponent(resultId)}/assets/${path
|
||||
projected[key] = `${endpointRoot}/${encodeURIComponent(resultId)}/assets/${path
|
||||
.split("/")
|
||||
.map(encodeURIComponent)
|
||||
.join("/")}`;
|
||||
@@ -342,10 +345,16 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
evidenceState,
|
||||
};
|
||||
});
|
||||
const expectedClassCount = viewKind === "coarse-material-policy-review" ? 9 : 64;
|
||||
const expectedClassCount = viewKind === "coarse-material-policy-review" ? 10 : 64;
|
||||
if (classes.length !== expectedClassCount) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: taxonomy size changed.");
|
||||
}
|
||||
if (
|
||||
viewKind === "coarse-material-policy-review"
|
||||
&& (classes[9]?.disposition !== "undefined" || classes[9]?.evidenceState !== "UNOBSERVED")
|
||||
) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: valid-FOV class changed.");
|
||||
}
|
||||
const aggregatePredictionPixels = arrayValue(
|
||||
row.aggregate_prediction_pixels,
|
||||
"vegetation.route_video.aggregate_prediction_pixels",
|
||||
@@ -364,7 +373,18 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
integerValue(maskArchive.byte_length, "vegetation.route_video.mask_archive.byte_length");
|
||||
let policyPresets: VegetationRouteVideo["policyPresets"] = null;
|
||||
let fusionMode: VegetationRouteVideo["fusionMode"] = null;
|
||||
let validFovMaskSha256: string | null = null;
|
||||
if (viewKind === "coarse-material-policy-review") {
|
||||
const validFov = objectValue(row.valid_fov, "vegetation.route_video.valid_fov");
|
||||
exact(validFov.mask_path, "video/valid-fov-mask.png", "vegetation.route_video.valid_fov.path");
|
||||
validFovMaskSha256 = textValue(
|
||||
validFov.mask_sha256,
|
||||
"vegetation.route_video.valid_fov.sha256",
|
||||
);
|
||||
if (!SHA256.test(validFovMaskSha256)) {
|
||||
throw new VegetationShadowContractError("vegetation.route_video: valid-FOV digest invalid.");
|
||||
}
|
||||
exact(validFov.outside_valid_fov_class_id, 9, "vegetation.route_video.valid_fov.class_id");
|
||||
const policy = objectValue(row.policy, "vegetation.route_video.policy");
|
||||
const presets = objectValue(policy.presets, "vegetation.route_video.policy.presets");
|
||||
policyPresets = Object.fromEntries(Object.entries(presets).map(([presetId, rawRules]) => {
|
||||
@@ -399,10 +419,15 @@ function routeVideoValue(value: unknown): VegetationRouteVideo | null {
|
||||
aggregatePredictionPixels,
|
||||
policyPresets,
|
||||
fusionMode,
|
||||
validFovMaskSha256,
|
||||
};
|
||||
}
|
||||
|
||||
function parseResult(value: unknown, resultId: string): VegetationShadowResult {
|
||||
function parseResult(
|
||||
value: unknown,
|
||||
resultId: string,
|
||||
endpointRoot: string,
|
||||
): VegetationShadowResult {
|
||||
const payload = objectValue(value, "Vegetation LAB");
|
||||
exact(payload.schema_version, "missioncore.lab-v1-vegetation-shadow/v1", "vegetation.schema");
|
||||
exact(payload.result_id, resultId, "vegetation.result_id");
|
||||
@@ -438,9 +463,9 @@ function parseResult(value: unknown, resultId: string): VegetationShadowResult {
|
||||
"vegetation.authority.camera_semantics_can_clear_rigid_geometry",
|
||||
);
|
||||
const routeCases = arrayValue(catalogs.ravnoves, "vegetation.catalogs.ravnoves")
|
||||
.map((item) => visualCaseValue(item, resultId, "ravnoves"));
|
||||
.map((item) => visualCaseValue(item, resultId, "ravnoves", endpointRoot));
|
||||
const validationCases = arrayValue(catalogs.goose, "vegetation.catalogs.goose")
|
||||
.map((item) => visualCaseValue(item, resultId, "goose"));
|
||||
.map((item) => visualCaseValue(item, resultId, "goose", endpointRoot));
|
||||
if (routeCases.length !== 0 || validationCases.length !== 12) {
|
||||
throw new VegetationShadowContractError("vegetation.catalogs: ожидалось 12 truth-backed GOOSE случаев без route viewer.");
|
||||
}
|
||||
@@ -490,5 +515,36 @@ export async function fetchVegetationShadowResult(
|
||||
if (!response.ok) {
|
||||
throw new VegetationShadowContractError(`Vegetation LAB недоступна: HTTP ${response.status}.`);
|
||||
}
|
||||
return parseResult(await response.json(), resultId);
|
||||
return parseResult(
|
||||
await response.json(),
|
||||
resultId,
|
||||
"/api/v1/laboratory/vegetation-shadow",
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetchVegetationBenchmarkResult(
|
||||
resultId: string,
|
||||
{
|
||||
fetcher = fetch,
|
||||
signal,
|
||||
}: { fetcher?: LaboratoryFetch; signal?: AbortSignal } = {},
|
||||
): Promise<VegetationShadowResult> {
|
||||
if (!BENCHMARK_RESULT_ID.test(resultId)) {
|
||||
throw new VegetationShadowContractError("Vegetation benchmark identity недопустима.");
|
||||
}
|
||||
const endpointRoot = "/api/v1/laboratory/vegetation-benchmark";
|
||||
const response = await fetcher(
|
||||
`${endpointRoot}/${encodeURIComponent(resultId)}`,
|
||||
{ method: "GET", headers: { Accept: "application/json" }, signal },
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new VegetationShadowContractError(
|
||||
`Vegetation benchmark недоступен: HTTP ${response.status}.`,
|
||||
);
|
||||
}
|
||||
const result = parseResult(await response.json(), resultId, endpointRoot);
|
||||
if (result.routeVideo) {
|
||||
throw new VegetationShadowContractError("Vegetation benchmark содержит route video.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user