feat: add comparable localization algorithms
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { computeDasMap, createDasAnalysisPlan } from "./das.js";
|
||||
import { findLocalPeaks } from "./evaluation.js";
|
||||
import {
|
||||
computeLocalizationMap,
|
||||
createLocalizationAnalysisPlan,
|
||||
} from "./localization.js";
|
||||
import {
|
||||
isDasAlgorithm,
|
||||
requireLocalizationAlgorithmDefinition,
|
||||
} from "./localization-algorithms.js";
|
||||
import {
|
||||
DEFAULT_MAX_COMPENSATION_DB,
|
||||
MAX_FREQUENCY_RESPONSE_HZ,
|
||||
@@ -214,6 +221,10 @@ function throwIfAborted(signal) {
|
||||
}
|
||||
|
||||
export function detectBandPeaks(rawMap, scan, options = {}) {
|
||||
const rawScale = options.rawScale ?? rawMap?.scale ?? "power";
|
||||
if (rawScale !== "power" && rawScale !== "score") {
|
||||
throw new RangeError('peak map rawScale must be "power" or "score".');
|
||||
}
|
||||
const peakDetection = copyPeakDetection(options, scan);
|
||||
const localPeaks = findLocalPeaks(rawMap, scan);
|
||||
const threshold = (localPeaks[0]?.value ?? 0) * peakDetection.thresholdRatio;
|
||||
@@ -235,7 +246,9 @@ export function detectBandPeaks(rawMap, scan, options = {}) {
|
||||
rank: detections.length + 1,
|
||||
index: peak.index,
|
||||
position: [...peak.position],
|
||||
rawPower: peak.value,
|
||||
rawValue: peak.value,
|
||||
rawScale,
|
||||
...(rawScale === "power" ? { rawPower: peak.value } : {}),
|
||||
});
|
||||
if (detections.length === peakDetection.maximumDetections) break;
|
||||
}
|
||||
@@ -244,7 +257,10 @@ export function detectBandPeaks(rawMap, scan, options = {}) {
|
||||
|
||||
function locationsFromBands(bands, scan, peakDetection) {
|
||||
return bands.flatMap((band) =>
|
||||
detectBandPeaks(band.rawMap, scan, peakDetection).map((peak) => ({
|
||||
detectBandPeaks(band.rawMap, scan, {
|
||||
...peakDetection,
|
||||
rawScale: band.rawScale,
|
||||
}).map((peak) => ({
|
||||
bandId: band.id,
|
||||
...peak,
|
||||
})),
|
||||
@@ -287,6 +303,13 @@ export function createLocalizationPlan(config) {
|
||||
config.beamforming?.maxCompensationDb ??
|
||||
DEFAULT_MAX_COMPENSATION_DB,
|
||||
);
|
||||
const algorithm = requireLocalizationAlgorithmDefinition(
|
||||
config.algorithm ?? config.beamforming?.algorithm,
|
||||
);
|
||||
const phatRelativeFloor =
|
||||
config.phatRelativeFloor ?? config.beamforming?.phatRelativeFloor;
|
||||
const phatFloorRatio =
|
||||
config.phatFloorRatio ?? config.beamforming?.phatFloorRatio;
|
||||
|
||||
const scene = Object.freeze({ speedOfSound });
|
||||
const experiment = Object.freeze({
|
||||
@@ -294,12 +317,14 @@ export function createLocalizationPlan(config) {
|
||||
scan,
|
||||
frequencyBands,
|
||||
beamforming: Object.freeze({
|
||||
algorithm: algorithm.id,
|
||||
backend: "cpu",
|
||||
normalization,
|
||||
compensateFrequencyResponse,
|
||||
maxCompensationDb,
|
||||
}),
|
||||
});
|
||||
const dasPlan = createDasAnalysisPlan({
|
||||
const analysisPlan = createLocalizationAnalysisPlan({
|
||||
sampleRate,
|
||||
arrayDefinition,
|
||||
scene,
|
||||
@@ -310,6 +335,7 @@ export function createLocalizationPlan(config) {
|
||||
|
||||
return Object.freeze({
|
||||
kind: REALTIME_LOCALIZATION_PLAN_KIND,
|
||||
algorithm: algorithm.id,
|
||||
sampleRate,
|
||||
channelCount: arrayDefinition.microphones.length,
|
||||
arrayDefinition,
|
||||
@@ -322,10 +348,15 @@ export function createLocalizationPlan(config) {
|
||||
maxCompensationDb,
|
||||
scene,
|
||||
experiment,
|
||||
dasPlan,
|
||||
analysisPlan,
|
||||
...(isDasAlgorithm(algorithm.id)
|
||||
? { dasPlan: analysisPlan }
|
||||
: { gccPhatPlan: analysisPlan }),
|
||||
computeOptions: Object.freeze({
|
||||
pointBatchSize: DEFAULT_REALTIME_POINT_BATCH_SIZE,
|
||||
stftTransformBatchSize: DEFAULT_REALTIME_STFT_TRANSFORM_BATCH_SIZE,
|
||||
...(phatRelativeFloor !== undefined ? { phatRelativeFloor } : {}),
|
||||
...(phatFloorRatio !== undefined ? { phatFloorRatio } : {}),
|
||||
...(config.computeOptions ?? {}),
|
||||
}),
|
||||
});
|
||||
@@ -350,7 +381,7 @@ export async function localizeMultichannel(options) {
|
||||
if (sampleCount === 0) throw new RangeError("channels must contain at least one sample.");
|
||||
throwIfAborted(options.signal);
|
||||
const processingStartedAt = monotonicNow();
|
||||
const das = await computeDasMap({
|
||||
const localization = await computeLocalizationMap({
|
||||
...plan.computeOptions,
|
||||
...(options.computeOptions ?? {}),
|
||||
channels: options.channels,
|
||||
@@ -358,14 +389,18 @@ export async function localizeMultichannel(options) {
|
||||
arrayDefinition: plan.arrayDefinition,
|
||||
scene: plan.scene,
|
||||
experiment: plan.experiment,
|
||||
analysisPlan: plan.dasPlan,
|
||||
analysisPlan: plan.analysisPlan,
|
||||
signal: options.signal,
|
||||
onProgress: options.onProgress,
|
||||
});
|
||||
const locations = locationsFromBands(das.bands, plan.scan, plan.peakDetection);
|
||||
const locations = locationsFromBands(
|
||||
localization.bands,
|
||||
plan.scan,
|
||||
plan.peakDetection,
|
||||
);
|
||||
const processingTimeMs = Math.max(0, monotonicNow() - processingStartedAt);
|
||||
return {
|
||||
...das,
|
||||
...localization,
|
||||
sampleRate: plan.sampleRate,
|
||||
sampleCount,
|
||||
duration: sampleCount / plan.sampleRate,
|
||||
@@ -488,6 +523,7 @@ export class StreamingDasLocalizer {
|
||||
getState() {
|
||||
return {
|
||||
sampleRate: this.plan.sampleRate,
|
||||
algorithm: this.plan.algorithm,
|
||||
channelCount: this.plan.channelCount,
|
||||
analysisWindowSamples: this.analysisWindowSamples,
|
||||
analysisHopSamples: this.analysisHopSamples,
|
||||
@@ -675,6 +711,9 @@ export class StreamingDasLocalizer {
|
||||
}
|
||||
}
|
||||
|
||||
/** Algorithm-neutral alias; the older class name remains for compatibility. */
|
||||
export { StreamingDasLocalizer as StreamingLocalizer };
|
||||
|
||||
export function createStreamingLocalizer(config) {
|
||||
return new StreamingDasLocalizer(config);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user