feat: harden end-to-end simulation workflow
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
alignExperimentToScene,
|
||||
canonicalStringify,
|
||||
createResultDocument,
|
||||
estimateExperimentWorkload,
|
||||
@@ -108,6 +109,46 @@ test("Camera distance shift keeps the scene and scan on one z plane", () => {
|
||||
assertEqual(scene.image.plane.z, 3.25, "background image plane z shift");
|
||||
});
|
||||
|
||||
test("Camera aligns an experiment to an imported scene without changing grid resolution", () => {
|
||||
const experiment = {
|
||||
scan: { z: 2, xMin: -1, xMax: 1, yMin: -1, yMax: 1, columns: 32, rows: 24 },
|
||||
};
|
||||
const imageScene = {
|
||||
sources: [{ id: "one", position: [0.2, -0.1, 4.5] }],
|
||||
image: { plane: { z: 4.5, xMin: -2, xMax: 3, yMin: -1.5, yMax: 2.5 } },
|
||||
};
|
||||
|
||||
assertEqual(alignExperimentToScene(experiment, imageScene), "image-plane", "alignment source");
|
||||
assertEqual(
|
||||
JSON.stringify(experiment.scan),
|
||||
JSON.stringify({
|
||||
z: 4.5,
|
||||
xMin: -2,
|
||||
xMax: 3,
|
||||
yMin: -1.5,
|
||||
yMax: 2.5,
|
||||
columns: 32,
|
||||
rows: 24,
|
||||
}),
|
||||
"image plane becomes scan plane while rows and columns stay unchanged",
|
||||
);
|
||||
|
||||
const sourceScene = {
|
||||
sources: [
|
||||
{ id: "one", position: [-0.5, 0.1, 3] },
|
||||
{ id: "two", position: [0.5, 0.2, 5] },
|
||||
],
|
||||
};
|
||||
assertEqual(alignExperimentToScene(experiment, sourceScene), "source-bounds", "source fallback");
|
||||
assertEqual(experiment.scan.z, 4, "median source depth");
|
||||
assertNear(experiment.scan.xMin, -0.75, 1e-12, "source x minimum with padding");
|
||||
assertNear(experiment.scan.xMax, 0.75, 1e-12, "source x maximum with padding");
|
||||
assertNear(experiment.scan.yMin, -0.15, 1e-12, "source y minimum with padding");
|
||||
assertNear(experiment.scan.yMax, 0.45, 1e-12, "source y maximum with padding");
|
||||
assertEqual(experiment.scan.columns, 32, "columns retained after source fallback");
|
||||
assertEqual(experiment.scan.rows, 24, "rows retained after source fallback");
|
||||
});
|
||||
|
||||
test("Camera result document is schema-safe and linked to its snapshot", async () => {
|
||||
const arrayDefinition = {
|
||||
id: "test-array",
|
||||
@@ -125,7 +166,7 @@ test("Camera result document is schema-safe and linked to its snapshot", async (
|
||||
id: "test-experiment",
|
||||
arrayId: arrayDefinition.id,
|
||||
sceneId: scene.id,
|
||||
scan: { rows: 2, columns: 2 },
|
||||
scan: { rows: 2, columns: 2, xMin: -1, xMax: 1, yMin: -1, yMax: 1, z: 2 },
|
||||
beamforming: { backend: "auto" },
|
||||
frequencyBands: [{ id: "band-1", color: "#65f2bf" }],
|
||||
};
|
||||
@@ -143,7 +184,14 @@ test("Camera result document is schema-safe and linked to its snapshot", async (
|
||||
}],
|
||||
};
|
||||
const evaluations = new Map([["band-1", {
|
||||
peaks: [{ index: 3, sourceId: "source-1", position: [1, -1, 2], value: 1, localizationErrorM: 0.05 }],
|
||||
peaks: [{
|
||||
index: 3,
|
||||
sourceId: "source-1",
|
||||
position: [1, -1, 2],
|
||||
value: 1,
|
||||
localizationErrorM: 0.05,
|
||||
localizationErrorPixels: 0.1,
|
||||
}],
|
||||
metrics: {
|
||||
peakToSidelobeDb: 10,
|
||||
energyInsideTruthRatio: 0.75,
|
||||
@@ -152,6 +200,18 @@ test("Camera result document is schema-safe and linked to its snapshot", async (
|
||||
mainLobeWidthM: 0.2,
|
||||
ignoredNonFinite: Number.NaN,
|
||||
},
|
||||
evaluation: {
|
||||
version: "1.0.0",
|
||||
truthRadiusM: 0.1,
|
||||
matchToleranceM: 0.2,
|
||||
detectionThresholdRatio: 0.5,
|
||||
detectionThresholdValue: 0.25,
|
||||
minimumPeakDistanceM: 0.1,
|
||||
sidelobeExclusionRadiusM: 0.1,
|
||||
halfPowerRatio: 0.5,
|
||||
dynamicRangeDb: 30,
|
||||
maximumDetections: 128,
|
||||
},
|
||||
}]]);
|
||||
|
||||
const result = createResultDocument({
|
||||
@@ -168,9 +228,12 @@ test("Camera result document is schema-safe and linked to its snapshot", async (
|
||||
assert(!("index" in result.bands[0].peaks[0]), "internal peak index is omitted");
|
||||
assertEqual(
|
||||
Object.keys(result.bands[0].metrics).join(","),
|
||||
"peakToSidelobeDb,energyInsideTruthRatio,precision,recall",
|
||||
"peakToSidelobeDb,energyInsideTruthRatio,precision,recall,mainLobeWidthM",
|
||||
"only finite schema metrics are exported",
|
||||
);
|
||||
assertEqual(result.bands[0].peaks[0].localizationErrorPixels, 0.1, "pixel error is exported");
|
||||
assertEqual(result.bands[0].evaluation.matchToleranceM, 0.2, "evaluation settings are exported");
|
||||
assertEqual(result.environment.scanXMin, -1, "scan bounds are recorded");
|
||||
assertEqual(result.environment.fallbackCode, "webgpu-unavailable", "fallback is recorded");
|
||||
|
||||
const issues = await validateResult(result, { experiment, scene });
|
||||
|
||||
@@ -153,15 +153,18 @@ test("result exporter가 schema 허용 metric만 선택하고 내부 필드를
|
||||
assertEqual(Object.keys(exported.metrics).join(","), "precision,recall", "selected metric keys");
|
||||
assertEqual(exported.peaks[0].sourceId, "truth", "schema peak source match");
|
||||
assertEqual(exported.peaks[0].localizationErrorM, 0, "schema peak localization error");
|
||||
assertEqual(exported.peaks[0].localizationErrorPixels, 0, "schema peak pixel error");
|
||||
assert(!("index" in exported.peaks[0]), "internal peak index omitted");
|
||||
assert(!("mainLobeWidthM" in exported.metrics), "diagnostic omitted from schema metrics");
|
||||
assert(!("mainLobeWidthM" in exported.metrics), "explicit metric selection is respected");
|
||||
assertEqual(exported.evaluation.version, "1.0.0", "evaluation version exported");
|
||||
assertEqual(exported.evaluation.matchToleranceM, 0, "match tolerance exported");
|
||||
assertEqual(
|
||||
Object.keys(selectResultMetrics(evaluation)).join(","),
|
||||
RESULT_METRIC_KEYS.join(","),
|
||||
"default schema metric selection",
|
||||
);
|
||||
|
||||
const unsupported = captureError(() => selectResultMetrics(evaluation, ["mainLobeWidthM"]));
|
||||
const unsupported = captureError(() => selectResultMetrics(evaluation, ["gridResolutionM"]));
|
||||
assert(unsupported instanceof RangeError, "unsupported result metric is rejected");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user