feat: add comparable localization algorithms

This commit is contained in:
2026-08-28 20:20:57 +09:00
parent 7fe846fbf9
commit d58f673402
20 changed files with 2104 additions and 68 deletions
+36 -3
View File
@@ -6,6 +6,7 @@ const MAX_SOURCES = 32;
const MAX_GRID_SIDE = 512;
const MAX_FREQUENCY_RESPONSE_POINTS = 64;
const MAX_FREQUENCY_RESPONSE_HZ = 1_000_000;
const LOCALIZATION_ALGORITHMS = ["das", "das-phat", "gcc-phat"];
/** @typedef {{ path: string, message: string }} ValidationIssue */
@@ -436,10 +437,26 @@ function validateExperimentRelationships(value, context = {}) {
if (value.stft.window !== "hann") addIssue(issues, "$.stft.window", "hann이어야 합니다.");
}
if (requireRecord(value.beamforming, "$.beamforming", issues)) {
if (value.beamforming.algorithm !== "das") addIssue(issues, "$.beamforming.algorithm", "das이어야 합니다.");
if (!LOCALIZATION_ALGORITHMS.includes(value.beamforming.algorithm)) {
addIssue(
issues,
"$.beamforming.algorithm",
"das, das-phat 또는 gcc-phat이어야 합니다.",
);
}
if (!["auto", "cpu", "webgpu"].includes(value.beamforming.backend)) {
addIssue(issues, "$.beamforming.backend", "auto, cpu 또는 webgpu여야 합니다.");
}
if (
value.beamforming.algorithm === "gcc-phat" &&
value.beamforming.backend === "webgpu"
) {
addIssue(
issues,
"$.beamforming.backend",
"gcc-phat는 현재 CPU backend만 지원합니다. auto 또는 cpu를 선택하세요.",
);
}
requireFinite(value.beamforming.minimumDistance, "$.beamforming.minimumDistance", issues, {
min: 0,
max: 10,
@@ -475,6 +492,16 @@ function validateExperimentRelationships(value, context = {}) {
}
}
if (context.arrayDefinition && context.scene) {
if (
value.beamforming?.algorithm === "gcc-phat" &&
context.arrayDefinition.microphones.length < 2
) {
addIssue(
issues,
"$.beamforming.algorithm",
"gcc-phat에는 서로 다른 두 개 이상의 마이크가 필요합니다.",
);
}
const retainedBufferCount = context.arrayDefinition.microphones.length + context.scene.sources.length + 1;
const sampleCount = Math.ceil(context.scene.duration * context.scene.sampleRate);
let maximumDelaySamples = 0;
@@ -519,6 +546,12 @@ function validateResultRelationships(value, context = {}) {
if (value.experimentId !== context.experiment.id) addIssue(issues, "$.experimentId", "불러온 실험 id와 다릅니다.");
if (value.arrayId !== context.experiment.arrayId) addIssue(issues, "$.arrayId", "실험의 배열 id와 다릅니다.");
if (value.sceneId !== context.experiment.sceneId) addIssue(issues, "$.sceneId", "실험의 장면 id와 다릅니다.");
if (
value.algorithm !== undefined &&
value.algorithm !== context.experiment.beamforming?.algorithm
) {
addIssue(issues, "$.algorithm", "실험의 위치 추정 알고리즘과 다릅니다.");
}
}
const sourceIds = new Set(context.scene?.sources?.map((source) => source.id) ?? []);
const expectedBandIds = new Set(context.experiment?.frequencyBands?.map((band) => band.id) ?? []);
@@ -541,8 +574,8 @@ function validateResultRelationships(value, context = {}) {
addIssue(issues, `${path}.maps[${mapIndex}]`, "map shape이 실험 scan grid와 다릅니다.");
}
});
if (isRecord(band.rawMap) && band.rawMap.scale !== "power") {
addIssue(issues, `${path}.rawMap.scale`, "rawMap은 power scale이어야 합니다.");
if (isRecord(band.rawMap) && !["power", "score"].includes(band.rawMap.scale)) {
addIssue(issues, `${path}.rawMap.scale`, "rawMap은 power 또는 score scale이어야 합니다.");
}
if (Array.isArray(band.peaks)) {
band.peaks.forEach((peak, peakIndex) => {