Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/x/__tests__/xNoiseSanPlot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(Object.keys(noise.percentiles.positive)).toHaveLength(100);
expect(Object.keys(noise.percentiles.negative)).toHaveLength(100);
Comment thread
jobo322 marked this conversation as resolved.
Outdated
expect(noise.percentiles.positive[99]).toBeGreaterThan(
noise.percentiles.positive[98],
);
expect(noise.percentiles.negative[99]).toBeGreaterThan(
noise.percentiles.negative[98],
);
});
80 changes: 59 additions & 21 deletions src/x/xNoiseSanPlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export interface XNoiseSanPlotResult {
negative: number;
snr: number;
sanplot: Record<string, DataXY>;
percentiles: {
positive: Record<number, number>;
negative: Record<number, number>;
};
}

/**
Expand Down Expand Up @@ -154,6 +158,10 @@ export function xNoiseSanPlot(
negative: { from: firstNegativeValueIndex, to: input.length },
},
}),
percentiles: {
positive: getPercentiles(signPositive),
negative: getPercentiles(signNegative),
},
};
}

Expand Down Expand Up @@ -194,6 +202,7 @@ function calculateNoiseLevel(
const effectiveCutOffDist =
(cutOffDist * cloneSign.length + cutOffSignalsIndex) /
(cloneSign.length + cutOffSignalsIndex);

const refinedCorrectionFactor =
-1 * simpleNormInvValue(effectiveCutOffDist / 2);
noiseLevel /= refinedCorrectionFactor;
Expand All @@ -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.
Expand Down Expand Up @@ -255,10 +260,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
) {
Comment thread
jobo322 marked this conversation as resolved.
const index = Math.round(indexMax * quantileFraction);
Expand All @@ -267,7 +273,6 @@ function determineCutOff(
sigmaEstimates.push([quantileFraction, Math.abs(sigma)]);
}

const { from, to, step } = considerList;
const halfWindow = step / 2;

let bestVariance = Number.MAX_SAFE_INTEGER;
Expand Down Expand Up @@ -375,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);
Expand All @@ -397,9 +389,55 @@ function createNegativeSign(array: Float64Array, from: number): Float64Array {
return result;
}

/**
* 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.
*/
Comment thread
jobo322 marked this conversation as resolved.
function getPercentiles(sorted: NumberArray): Record<number, number> {
if (sorted.length === 0) return {};

const nbPercentiles = 100;
const result: Record<number, number> = {};
const maxIndex = sorted.length - 1;

for (let i = 0; i < nbPercentiles; i++) {
const pLabel = 50 + (i * 50) / nbPercentiles;
const percentile = pLabel / 100;
const index = maxIndex - Math.floor(percentile * maxIndex);

result[pLabel] = sorted[index];
}

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<ArrayBuffer> {
const { scaleFactor, mask } = options;

Expand Down
Loading