feat: add physically explicit narrowband MUSIC
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
findLocalPeaks,
|
||||
measureBeamSpread,
|
||||
measureMainLobe,
|
||||
normalizedMusicPseudospectrum,
|
||||
selectResultMetrics,
|
||||
} from "/packages/acoustics-core/evaluation.js";
|
||||
|
||||
@@ -426,3 +427,102 @@ test("evaluation이 non-finite, negative, shape 오류를 입력 경계에서
|
||||
"display-only map scale rejected",
|
||||
);
|
||||
});
|
||||
|
||||
test("MUSIC noise projection is minimized without rewriting raw q values", () => {
|
||||
const scan = {
|
||||
xMin: 0,
|
||||
xMax: 4,
|
||||
yMin: 0,
|
||||
yMax: 4,
|
||||
z: 1,
|
||||
columns: 5,
|
||||
rows: 5,
|
||||
};
|
||||
const values = new Float64Array(25);
|
||||
values.fill(1);
|
||||
values[6] = 0.1;
|
||||
values[18] = 0.2;
|
||||
values[3] = 0.6;
|
||||
const evaluation = evaluateLocalizationBand({
|
||||
rawMap: {
|
||||
rows: 5,
|
||||
columns: 5,
|
||||
scale: "noise-projection",
|
||||
objectiveDirection: "minimum",
|
||||
values,
|
||||
},
|
||||
scan,
|
||||
sources: [
|
||||
{ id: "left", position: [1, 3, 1] },
|
||||
{ id: "right", position: [3, 1, 1] },
|
||||
],
|
||||
detectionThresholdRatio: 0.5,
|
||||
minimumPeakDistanceM: 0,
|
||||
matchToleranceM: 0,
|
||||
});
|
||||
|
||||
assertEqual(evaluation.globalPeak.index, 6, "smallest q is the global optimum");
|
||||
assertNear(evaluation.globalPeak.value, 0.1, 1e-12, "global peak retains raw q");
|
||||
assertEqual(evaluation.detections.length, 2, "two separated minima are detected");
|
||||
assertNear(evaluation.detections[1].value, 0.2, 1e-12, "second detection retains raw q");
|
||||
assert(!("beamSpread" in evaluation.detections[0]), "power beam spread is omitted");
|
||||
assertEqual(
|
||||
Object.keys(evaluation.metrics).join(","),
|
||||
"precision,recall",
|
||||
"only objective-independent metrics are reported",
|
||||
);
|
||||
assertEqual(
|
||||
evaluation.diagnostics.detectionTransform,
|
||||
"normalized-reciprocal-noise-projection",
|
||||
"the standard normalized MUSIC pseudospectrum is disclosed",
|
||||
);
|
||||
const exported = exportBandEvaluation(evaluation);
|
||||
assertEqual(exported.evaluation.objectiveDirection, "minimum", "minimum objective is exported");
|
||||
assertEqual(
|
||||
exported.evaluation.detectionThresholdDomain,
|
||||
"normalized-music-pseudospectrum",
|
||||
"threshold domain is explicit",
|
||||
);
|
||||
assertNear(exported.peaks[0].value, 0.1, 1e-12, "exported peak remains raw q");
|
||||
|
||||
const withBadOutlier = Float64Array.from(values);
|
||||
withBadOutlier[0] = 100;
|
||||
const outlierEvaluation = evaluateLocalizationBand({
|
||||
rawMap: {
|
||||
rows: 5,
|
||||
columns: 5,
|
||||
scale: "noise-projection",
|
||||
objectiveDirection: "minimum",
|
||||
values: withBadOutlier,
|
||||
},
|
||||
scan,
|
||||
sources: [],
|
||||
detectionThresholdRatio: 0.5,
|
||||
minimumPeakDistanceM: 0,
|
||||
});
|
||||
assertEqual(
|
||||
outlierEvaluation.detections.map(({ index }) => index).join(","),
|
||||
evaluation.detections.map(({ index }) => index).join(","),
|
||||
"a worse scan outlier does not move the reciprocal threshold",
|
||||
);
|
||||
|
||||
const flat = evaluateLocalizationBand({
|
||||
rawMap: {
|
||||
rows: 5,
|
||||
columns: 5,
|
||||
scale: "noise-projection",
|
||||
objectiveDirection: "minimum",
|
||||
values: new Float64Array(25).fill(0.5),
|
||||
},
|
||||
scan,
|
||||
sources: [],
|
||||
});
|
||||
const flatDisplay = normalizedMusicPseudospectrum(
|
||||
new Float64Array(25).fill(0.5),
|
||||
);
|
||||
assert(
|
||||
flatDisplay.values.every((value) => value === 1),
|
||||
"flat q preserves the standard normalized reciprocal value",
|
||||
);
|
||||
assertEqual(flat.detections.length, 0, "flat q does not invent a location");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user