From 81a2c62473e30d161bc20025c53f03df43f49633 Mon Sep 17 00:00:00 2001 From: jobo322 Date: Mon, 20 Jul 2026 11:54:55 -0500 Subject: [PATCH 1/3] fix(xNoiseSanplot): only calculate sigma values in the user defined ranges --- src/x/xNoiseSanPlot.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/x/xNoiseSanPlot.ts b/src/x/xNoiseSanPlot.ts index b68036ec..ddd7e426 100644 --- a/src/x/xNoiseSanPlot.ts +++ b/src/x/xNoiseSanPlot.ts @@ -255,10 +255,11 @@ function determineCutOff( // For each quantile fraction, compute a local sigma estimate by inverting // the theoretical relationship between order statistics and the assumed // noise distribution (Gaussian or Rayleigh). + const { from, to, step } = considerList; const sigmaEstimates: Array<[quantileFraction: number, sigma: number]> = []; for ( - let quantileFraction = 0.01; - quantileFraction <= 0.99; + let quantileFraction = from - step / 2; + quantileFraction <= to + step / 2; quantileFraction += 0.01 ) { const index = Math.round(indexMax * quantileFraction); @@ -267,7 +268,6 @@ function determineCutOff( sigmaEstimates.push([quantileFraction, Math.abs(sigma)]); } - const { from, to, step } = considerList; const halfWindow = step / 2; let bestVariance = Number.MAX_SAFE_INTEGER; From 8a5c6373cb613c7ea2adc465a6b97daf5bc7f876 Mon Sep 17 00:00:00 2001 From: jobo322 Date: Mon, 27 Jul 2026 09:31:49 -0500 Subject: [PATCH 2/3] feat: add 50-99 percentiles per serie --- src/x/__tests__/xNoiseSanPlot.test.ts | 9 ++++++++ src/x/xNoiseSanPlot.ts | 33 +++++++++++++++++++++++---- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/x/__tests__/xNoiseSanPlot.test.ts b/src/x/__tests__/xNoiseSanPlot.test.ts index 3ee9cf9e..d199cd2d 100644 --- a/src/x/__tests__/xNoiseSanPlot.test.ts +++ b/src/x/__tests__/xNoiseSanPlot.test.ts @@ -1055,4 +1055,13 @@ test('get noise level', () => { //the SNR should be less because the biggest peak is not present. expect(noiseWithoutBigPeaks.snr).toBeLessThan(noise.snr); + + expect(noise.percentiles.positive).toHaveLength(100); + expect(noise.percentiles.negative).toHaveLength(100); + expect(noise.percentiles.positive[0]).toBeCloseTo( + noise.percentiles.positive[0], + ); + expect(noise.percentiles.positive[99]).toBe( + noise.percentiles.positive.at(-1), + ); }); diff --git a/src/x/xNoiseSanPlot.ts b/src/x/xNoiseSanPlot.ts index ddd7e426..d15712c8 100644 --- a/src/x/xNoiseSanPlot.ts +++ b/src/x/xNoiseSanPlot.ts @@ -70,6 +70,10 @@ export interface XNoiseSanPlotResult { negative: number; snr: number; sanplot: Record; + percentiles: { + positive: number[]; + negative: number[]; + }; } /** @@ -154,6 +158,10 @@ export function xNoiseSanPlot( negative: { from: firstNegativeValueIndex, to: input.length }, }, }), + percentiles: { + positive: getPercentiles(signPositive), + negative: getPercentiles(signNegative), + }, }; } @@ -194,6 +202,7 @@ function calculateNoiseLevel( const effectiveCutOffDist = (cutOffDist * cloneSign.length + cutOffSignalsIndex) / (cloneSign.length + cutOffSignalsIndex); + const refinedCorrectionFactor = -1 * simpleNormInvValue(effectiveCutOffDist / 2); noiseLevel /= refinedCorrectionFactor; @@ -220,10 +229,6 @@ function calculateNoiseLevel( * it will be biased and noisy. We therefore scan candidate quantile windows * and pick the one where the sigma estimates are most self-consistent * (lowest variance) - a proxy for "where the noise region reliably starts". - * <<<<<<< Updated upstream - * - * ======= - * >>>>>>> Stashed changes * @param signPositive - an array of positive numbers. * @param options - optional parameters to configure the cut-off determination. * @param options.magnitudeMode - if true, uses magnitude mode for normalization. Default is false. @@ -397,6 +402,26 @@ function createNegativeSign(array: Float64Array, from: number): Float64Array { return result; } +/** + * The idea is to get the great + * @param sorted + */ +function getPercentiles(sorted: NumberArray): number[] { + if (sorted.length === 0) return []; + const nbPercentiles = 100; + + const percentiles = new Array(nbPercentiles); + const maxIndex = sorted.length - 1; + + for (let i = 0; i < nbPercentiles; i++) { + const percentile = (90 + (i * 10) / nbPercentiles) / 100; + const index = maxIndex - Math.floor(percentile * maxIndex); + percentiles[i] = sorted[index]; + } + + return percentiles; +} + function prepareData( array: NumberArray, options: { scaleFactor: number; mask?: NumberArray }, From 01fcf8be91bea21a604ab8363c8de78587fd4aa7 Mon Sep 17 00:00:00 2001 From: jobo322 Date: Mon, 27 Jul 2026 10:54:31 -0500 Subject: [PATCH 3/3] refactor(xNoiseSanPlot): change percentiles to use records for better clarity and structure --- src/x/__tests__/xNoiseSanPlot.test.ts | 12 ++--- src/x/xNoiseSanPlot.ts | 63 ++++++++++++++++----------- 2 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/x/__tests__/xNoiseSanPlot.test.ts b/src/x/__tests__/xNoiseSanPlot.test.ts index d199cd2d..2a6af132 100644 --- a/src/x/__tests__/xNoiseSanPlot.test.ts +++ b/src/x/__tests__/xNoiseSanPlot.test.ts @@ -1056,12 +1056,12 @@ test('get noise level', () => { //the SNR should be less because the biggest peak is not present. expect(noiseWithoutBigPeaks.snr).toBeLessThan(noise.snr); - expect(noise.percentiles.positive).toHaveLength(100); - expect(noise.percentiles.negative).toHaveLength(100); - expect(noise.percentiles.positive[0]).toBeCloseTo( - noise.percentiles.positive[0], + expect(Object.keys(noise.percentiles.positive)).toHaveLength(100); + expect(Object.keys(noise.percentiles.negative)).toHaveLength(100); + expect(noise.percentiles.positive[99]).toBeGreaterThan( + noise.percentiles.positive[98], ); - expect(noise.percentiles.positive[99]).toBe( - noise.percentiles.positive.at(-1), + expect(noise.percentiles.negative[99]).toBeGreaterThan( + noise.percentiles.negative[98], ); }); diff --git a/src/x/xNoiseSanPlot.ts b/src/x/xNoiseSanPlot.ts index d15712c8..cf0e07b8 100644 --- a/src/x/xNoiseSanPlot.ts +++ b/src/x/xNoiseSanPlot.ts @@ -71,8 +71,8 @@ export interface XNoiseSanPlotResult { snr: number; sanplot: Record; percentiles: { - positive: number[]; - negative: number[]; + positive: Record; + negative: Record; }; } @@ -380,19 +380,6 @@ function scale( return { x: xAxis, y: array }; } -/** - * Prepares and processes the input data array based on the provided options. - * @param array - the input array of numbers to be processed. - * @param options - an object containing the following properties: - * - scaleFactor: A number by which to scale each element of the array. - * - mask: An optional array of the same length as the input array, where - * elements corresponding to `true` values will be excluded from processing. - * @param options.scaleFactor - * @param options.mask - * @param from - * @returns A new Float64Array containing the processed data, scaled by the - * scaleFactor and sorted in descending order. - */ function createNegativeSign(array: Float64Array, from: number): Float64Array { const length = array.length - from; const result = new Float64Array(length); @@ -403,28 +390,54 @@ function createNegativeSign(array: Float64Array, from: number): Float64Array { } /** - * The idea is to get the great - * @param sorted + * Calculates intensity percentiles ranging from the 50th to the 99.5th percentile + * (in 0.5 increments) from a pre-sorted array. + * @param sorted - a pre-sorted array of numbers. + * @returns An object mapping percentile labels to their corresponding values. */ -function getPercentiles(sorted: NumberArray): number[] { - if (sorted.length === 0) return []; - const nbPercentiles = 100; +function getPercentiles(sorted: NumberArray): Record { + if (sorted.length === 0) return {}; - const percentiles = new Array(nbPercentiles); + const nbPercentiles = 100; + const result: Record = {}; const maxIndex = sorted.length - 1; for (let i = 0; i < nbPercentiles; i++) { - const percentile = (90 + (i * 10) / nbPercentiles) / 100; + const pLabel = 50 + (i * 50) / nbPercentiles; + const percentile = pLabel / 100; const index = maxIndex - Math.floor(percentile * maxIndex); - percentiles[i] = sorted[index]; + + result[pLabel] = sorted[index]; } - return percentiles; + return result; } +interface PrepareDataOptions { + /** + * A multiplier by which to scale each element of the array. + * Note: Scaling is only applied if this value is strictly greater than 1. + */ + scaleFactor: number; + + /** + * An optional array of the same length as the input array. + * Elements in the input array corresponding to truthy values in this mask + * will be excluded from the final processed output. + */ + mask?: NumberArray; +} + +/** + * Prepares and processes the input data array based on the provided options. + * @param array - The input array of numbers to be processed. + * @param options - The configuration options for processing the data. + * @returns A new Float64Array containing the processed data, scaled by the + * scaleFactor and sorted in descending order. + */ function prepareData( array: NumberArray, - options: { scaleFactor: number; mask?: NumberArray }, + options: PrepareDataOptions, ): Float64Array { const { scaleFactor, mask } = options;