feat: tune multi-source peak sensitivity

This commit is contained in:
Codex
2026-08-28 16:30:01 +09:00
parent a54aa25cd9
commit 7cd737429a
9 changed files with 119 additions and 3 deletions
+1
View File
@@ -137,6 +137,7 @@
<label class="field"><span>Hop size</span><input id="hop-size" type="number" min="1" max="4096" step="1" /></label>
<label class="field"><span>Map 정규화</span><select id="normalization"><option value="peak">Peak</option><option value="none">없음</option></select></label>
<label class="field"><span>Dynamic range (dB)</span><input id="dynamic-range" type="number" min="6" max="120" step="1" /></label>
<label class="field" title="최강 peak 대비 검출 임계값입니다. 낮추면 약한 음원을 더 찾지만 sidelobe 오검출도 늘 수 있습니다."><span>Peak 검출 임계값 (%)</span><input id="detection-threshold" type="number" min="1" max="100" step="1" /></label>
<label class="field field--wide"><span>후처리</span><select id="postprocess"><option value="none">없음 · raw DAS</option><option disabled>DAMAS · 후속 milestone</option><option disabled>CLEAN-SC · 후속 milestone</option></select></label>
</div>
</section>
+37 -3
View File
@@ -43,6 +43,7 @@ const BAND_COLORS = ["#65f2bf", "#58c7ff", "#ff7f8f", "#ffc56e", "#b294ff", "#8f
const MAX_HISTORY_ITEMS = 24;
const MAX_LIVE_ANALYSIS_FRAMES = 48;
const MAX_LIVE_SCAN_POINTS = 2500;
const DEFAULT_DETECTION_THRESHOLD_RATIO = 0.25;
const byId = (id) => document.getElementById(id);
const elements = {
@@ -102,6 +103,7 @@ const elements = {
hopSize: byId("hop-size"),
normalization: byId("normalization"),
dynamicRange: byId("dynamic-range"),
detectionThreshold: byId("detection-threshold"),
postprocess: byId("postprocess"),
addBandButton: byId("add-band-button"),
bandList: byId("band-list"),
@@ -168,6 +170,14 @@ function comparisonSignature(scene = state.scene, sourceExperiment = state.exper
return canonicalStringify({ scene, experiment });
}
function applyExperimentDefaults(experiment) {
if (!experiment.evaluation) experiment.evaluation = {};
if (experiment.evaluation.detectionThresholdRatio === undefined) {
experiment.evaluation.detectionThresholdRatio = DEFAULT_DETECTION_THRESHOLD_RATIO;
}
return experiment;
}
function setRunStatus(message) {
elements.runStatus.textContent = message;
}
@@ -359,6 +369,9 @@ function syncControls() {
elements.hopSize.value = experiment.stft.hopSize;
elements.normalization.value = experiment.beamforming.normalization;
elements.dynamicRange.value = experiment.beamforming.dynamicRangeDb;
const detectionThresholdRatio =
experiment.evaluation?.detectionThresholdRatio ?? DEFAULT_DETECTION_THRESHOLD_RATIO;
elements.detectionThreshold.value = String(Number((detectionThresholdRatio * 100).toFixed(4)));
elements.arrayPreset.value = ARRAY_PRESET_URLS.has(state.arrayDefinition.id)
? state.arrayDefinition.id
: "custom";
@@ -638,6 +651,14 @@ function renderResult() {
`${formatNumber(displayed.run.timingMs.beamforming, 0)} ms`,
`${displayed.snapshot.experiment.scan.columns}×${displayed.snapshot.experiment.scan.rows} grid`,
);
const detectionThreshold =
displayed.snapshot.experiment.evaluation?.detectionThresholdRatio ??
DEFAULT_DETECTION_THRESHOLD_RATIO;
appendMetric(
"Peak 검출 임계값",
`${formatNumber(detectionThreshold * 100, 0)}%`,
"최강 peak power 기준 · 낮을수록 민감",
);
for (const band of displayed.snapshot.experiment.frequencyBands) {
const evaluation = displayed.evaluations.get(band.id);
if (!evaluation) continue;
@@ -1001,6 +1022,9 @@ async function runSimulation() {
scan: snapshot.experiment.scan,
sources: sourcesForBand(snapshot.scene, definition),
dynamicRangeDb: snapshot.experiment.beamforming.dynamicRangeDb,
detectionThresholdRatio:
snapshot.experiment.evaluation?.detectionThresholdRatio ??
DEFAULT_DETECTION_THRESHOLD_RATIO,
});
evaluations.set(band.id, evaluation);
exportedEvaluations.set(band.id, exportBandEvaluation(evaluation));
@@ -1124,6 +1148,9 @@ function evaluateLiveFrames(snapshot, plan, frames) {
scan: snapshot.experiment.scan,
sources: sourcesForBand(frameScene, definition, frame.sampleTime),
dynamicRangeDb: snapshot.experiment.beamforming.dynamicRangeDb,
detectionThresholdRatio:
snapshot.experiment.evaluation?.detectionThresholdRatio ??
DEFAULT_DETECTION_THRESHOLD_RATIO,
}),
);
}
@@ -1312,7 +1339,7 @@ async function loadMovingExample() {
if (issues.length > 0) throw new Error(issues.slice(0, 4).map(formatIssue).join(" · "));
state.arrayDefinition = arrayDefinition;
state.scene = scene;
state.experiment = experiment;
state.experiment = applyExperimentDefaults(experiment);
state.sceneDirty = false;
state.experimentDirty = false;
state.visibleBandIds = new Set(experiment.frequencyBands.map((band) => band.id));
@@ -1403,7 +1430,7 @@ async function importDocument(kind, input) {
state.sceneDirty = false;
assetRuntime.reset();
} else {
state.experiment = value;
state.experiment = applyExperimentDefaults(value);
state.experimentDirty = false;
}
state.visibleBandIds = new Set(state.experiment.frequencyBands.map((band) => band.id));
@@ -1498,6 +1525,13 @@ function installDocumentInputListeners() {
state.experiment.beamforming.dynamicRangeDb = value;
markChanged();
});
elements.detectionThreshold.addEventListener("change", () => {
const value = readFiniteInput(elements.detectionThreshold);
if (value === null) return syncControls();
state.experiment.evaluation ??= {};
state.experiment.evaluation.detectionThresholdRatio = value / 100;
markChanged();
});
}
function installBandListeners() {
@@ -1697,7 +1731,7 @@ async function initialize() {
]);
state.arrayDefinition = arrayDefinition;
state.scene = scene;
state.experiment = experiment;
state.experiment = applyExperimentDefaults(experiment);
state.visibleBandIds = new Set(experiment.frequencyBands.map((band) => band.id));
syncControls();
renderBandEditors();