feat: tune multi-source peak sensitivity
This commit is contained in:
@@ -68,6 +68,8 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
|
||||
dispatchValue(windowObject, documentObject.querySelector("#scan-columns"), 8);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#scan-rows"), 8);
|
||||
dispatchValue(windowObject, documentObject.querySelector("#backend"), "cpu");
|
||||
assertEqual(documentObject.querySelector("#detection-threshold").value, "25", "default peak sensitivity");
|
||||
dispatchValue(windowObject, documentObject.querySelector("#detection-threshold"), 10);
|
||||
await waitFor(
|
||||
() => documentObject.documentElement.dataset.cameraValid === "true" &&
|
||||
!documentObject.querySelector("#run-button").disabled,
|
||||
@@ -134,6 +136,11 @@ test("Acoustic Camera가 8×8 CPU 실행·결과 저장·stale·재실행 흐름
|
||||
assertEqual(resultDocument.bands[0].rawMap.values.length, 64, "exported map value count");
|
||||
assert(/^[a-f0-9]{64}$/.test(resultDocument.inputHash), "exported input hash");
|
||||
assertEqual(resultDocument.bands[0].evaluation.version, "1.0.0", "evaluation version");
|
||||
assertEqual(
|
||||
resultDocument.bands[0].evaluation.detectionThresholdRatio,
|
||||
0.1,
|
||||
"selected peak sensitivity is exported",
|
||||
);
|
||||
assert(Number.isFinite(resultDocument.bands[0].evaluation.matchToleranceM), "match tolerance exported");
|
||||
assert(Number.isFinite(resultDocument.bands[0].metrics.mainLobeWidthM), "main-lobe metric exported");
|
||||
assertEqual(resultDocument.environment.scanXMin, experiment.scan.xMin, "scan bounds exported");
|
||||
|
||||
@@ -61,6 +61,35 @@ test("단일 tone 장면과 실험이 관계 검증을 통과한다", async () =
|
||||
assertEqual((await validateExperiment(experiment, { arrayDefinition, scene })).length, 0, "experiment issues");
|
||||
});
|
||||
|
||||
test("Experiment 피크 검출 임계값 비율은 재현 가능하고 유효한 범위여야 한다", async () => {
|
||||
const [arrayDefinition, scene, experiment] = await Promise.all([
|
||||
loadJson("/assets/array-geometries/rectangular-16x12.array.json"),
|
||||
loadJson("/assets/sample-scenes/single-tone.scene.json"),
|
||||
loadJson("/assets/sample-scenes/single-tone.experiment.json"),
|
||||
]);
|
||||
assertEqual(experiment.evaluation.detectionThresholdRatio, 0.25, "sample threshold ratio");
|
||||
assertEqual(
|
||||
(await validateExperiment(experiment, { arrayDefinition, scene })).length,
|
||||
0,
|
||||
"0.25 threshold issues",
|
||||
);
|
||||
|
||||
experiment.evaluation.detectionThresholdRatio = 0;
|
||||
let issues = await validateExperiment(experiment, { arrayDefinition, scene });
|
||||
assert(
|
||||
issues.some(({ path, message }) =>
|
||||
path === "$.evaluation.detectionThresholdRatio" && message.includes("0보다 크고 1 이하")),
|
||||
"zero threshold is rejected with actionable guidance",
|
||||
);
|
||||
|
||||
experiment.evaluation.detectionThresholdRatio = 1.01;
|
||||
issues = await validateExperiment(experiment, { arrayDefinition, scene });
|
||||
assert(
|
||||
issues.some(({ path }) => path === "$.evaluation.detectionThresholdRatio"),
|
||||
"threshold above one is rejected",
|
||||
);
|
||||
});
|
||||
|
||||
test("이동 음원 trajectory의 keyframe 순서와 장면 시간 범위를 검증한다", async () => {
|
||||
const scene = await loadJson("/assets/sample-scenes/single-tone.scene.json");
|
||||
scene.sources[0].trajectory = [
|
||||
|
||||
@@ -86,6 +86,32 @@ test("복수 source와 threshold detection을 one-to-one matching한다", () =>
|
||||
);
|
||||
});
|
||||
|
||||
test("peak 검출 감도를 낮추면 약한 두 번째 음원을 선택한다", () => {
|
||||
const scan = { xMin: -2, xMax: 2, yMin: -2, yMax: 2, z: 1, columns: 5, rows: 5 };
|
||||
const rawMap = new Float32Array(25);
|
||||
rawMap[6] = 10;
|
||||
rawMap[18] = 2.5;
|
||||
const options = {
|
||||
rawMap,
|
||||
scan,
|
||||
sources: [
|
||||
{ id: "strong", position: [-1, 1, 1] },
|
||||
{ id: "weak", position: [1, -1, 1] },
|
||||
],
|
||||
matchToleranceM: 0.25,
|
||||
minimumPeakDistanceM: 0,
|
||||
};
|
||||
|
||||
const conservative = evaluateDasBand({ ...options, detectionThresholdRatio: 0.5 });
|
||||
const recommended = evaluateDasBand({ ...options, detectionThresholdRatio: 0.25 });
|
||||
|
||||
assertEqual(conservative.detections.length, 1, "50% threshold omits the weak peak");
|
||||
assertEqual(conservative.counts.falseNegative, 1, "one source remains undetected");
|
||||
assertEqual(recommended.detections.length, 2, "25% threshold retains the weak peak");
|
||||
assertEqual(recommended.counts.truePositive, 2, "both sources match at recommended sensitivity");
|
||||
assertEqual(recommended.parameters.detectionThresholdRatio, 0.25, "selected ratio is recorded");
|
||||
});
|
||||
|
||||
test("zero map 평가가 빈 peak와 유한한 metric을 반환한다", () => {
|
||||
const scan = { xMin: -1, xMax: 1, yMin: -1, yMax: 1, z: 1, columns: 2, rows: 2 };
|
||||
const evaluation = evaluateDasBand({
|
||||
|
||||
Reference in New Issue
Block a user