fix: expose physical aliasing and delayed arrivals

This commit is contained in:
2026-08-29 10:21:33 +09:00
parent e048ccbadc
commit de0053eae3
38 changed files with 3086 additions and 214 deletions
+100 -2
View File
@@ -1,8 +1,10 @@
import { getFrameCount, binsForBand } from "/packages/acoustics-core/stft.js";
import { observeFrequencyBand } from "/packages/acoustics-core/sampling.js";
import {
DEFAULT_LOCALIZATION_ALGORITHM,
localizationAlgorithmDefinition,
} from "/packages/acoustics-core/localization-algorithms.js";
import { RESULT_SCHEMA_VERSION } from "/packages/contracts/validation.js";
const RESULT_METRIC_KEYS = [
"peakToSidelobeDb",
@@ -254,6 +256,70 @@ function environmentForResult({ arrayDefinition, experiment, run }) {
return environment;
}
function samplingForResult(sampling) {
if (!sampling || typeof sampling !== "object") return null;
return {
model: sampling.model,
sampleRate: sampling.sampleRate,
nyquistHz: sampling.nyquistHz,
toneObservations: (sampling.toneObservations ?? []).map((observation) => ({
scope: observation.scope,
id: observation.id,
analogFrequencyHz: observation.analogFrequencyHz,
adcFrequencyHz: observation.adcFrequencyHz,
signedAdcFrequencyHz: observation.signedAdcFrequencyHz,
aliased: observation.aliased,
spectralInversion: observation.spectralInversion,
dcEdge: observation.dcEdge,
nyquistEdge: observation.nyquistEdge,
phaseDegenerate: observation.phaseDegenerate,
})),
};
}
function frequencyObservationForResult(scene, experiment, band, algorithm) {
if (
!Number.isFinite(scene?.sampleRate) ||
!Number.isInteger(experiment?.stft?.fftSize) ||
!Number.isFinite(band?.minHz) ||
!Number.isFinite(band?.maxHz)
) {
return null;
}
const observation = observeFrequencyBand(
band,
scene.sampleRate,
experiment.stft.fftSize,
);
return {
model: observation.model,
sampleRate: observation.sampleRate,
nyquistHz: observation.nyquistHz,
fftSize: observation.fftSize,
binWidthHz: observation.binWidthHz,
requestedMinHz: observation.requestedMinHz,
requestedMaxHz: observation.requestedMaxHz,
aliased: observation.aliased,
crossesFirstNyquist: observation.crossesFirstNyquist,
localizationInputModel: observation.localizationInputModel,
selectionModel: observation.selectionModel,
localizationModel:
algorithm === "gcc-phat"
? "band-limited-gcc-phat-geometric-lag"
: "spherical-steering-at-positive-adc-bin-frequency",
originalCarrierUsed: observation.originalCarrierUsed,
aliasOrderResolution: observation.aliased ? "not-attempted" : "not-applicable",
foldBoundaryCount: observation.foldBoundaryCount,
continuousManyToOne: observation.continuousManyToOne,
gridManyToOne: observation.gridManyToOne,
foldSegments: observation.foldSegments.map((segment) => ({ ...segment })),
adcBins: [...observation.bins],
adcBinCenterRanges: observation.adcBinCenterRanges.map((range) => ({ ...range })),
adcBinSetKey: observation.adcBinSetKey,
equivalentBandIds: [],
};
}
export function createResultDocument(options) {
const {
arrayDefinition,
@@ -266,8 +332,31 @@ export function createResultDocument(options) {
} = options;
const algorithm = run.algorithm ?? experiment.beamforming?.algorithm ?? DEFAULT_LOCALIZATION_ALGORITHM;
const rawScale = localizationAlgorithmDefinition(algorithm)?.rawScale ?? "power";
const sampling = samplingForResult(run.signalStats.sampling);
const frequencyObservations = new Map();
for (const definition of experiment.frequencyBands) {
const observation = frequencyObservationForResult(
scene,
experiment,
definition,
algorithm,
);
if (observation) frequencyObservations.set(definition.id, observation);
}
const equivalentGroups = new Map();
for (const [bandId, observation] of frequencyObservations) {
const group = equivalentGroups.get(observation.adcBinSetKey) ?? [];
group.push(bandId);
equivalentGroups.set(observation.adcBinSetKey, group);
}
for (const [bandId, observation] of frequencyObservations) {
observation.equivalentBandIds = equivalentGroups
.get(observation.adcBinSetKey)
.filter((candidateId) => candidateId !== bandId)
.sort();
}
return {
schemaVersion: "1.0.0",
schemaVersion: RESULT_SCHEMA_VERSION,
kind: "micarrsim.result",
experimentId: experiment.id,
arrayId: arrayDefinition.id,
@@ -289,6 +378,7 @@ export function createResultDocument(options) {
...(run.signalStats.acousticPathCounts
? { acousticPathCounts: { ...run.signalStats.acousticPathCounts } }
: {}),
...(sampling ? { sampling } : {}),
},
timingMs: {
total: run.timingMs.total,
@@ -317,7 +407,15 @@ export function createResultDocument(options) {
peaks: evaluation.peaks.map(peakForResult),
metrics: finiteMetricSubset(evaluation.metrics),
};
if (evaluation.evaluation) bandResult.evaluation = { ...evaluation.evaluation };
const frequencyObservation = frequencyObservations.get(band.id);
if (frequencyObservation) bandResult.frequencyObservation = frequencyObservation;
if (evaluation.evaluation) {
bandResult.evaluation = {
...evaluation.evaluation,
truthSelection:
"tone-analog-interval-or-nearest-adc-bin-and-all-nontone-sources",
};
}
return bandResult;
}),
};