ml-spectra-fitting
Version:
Fit spectra using gaussian or lorentzian
152 lines (134 loc) • 4.26 kB
text/typescript
import type { Shape1D, Shape1DInstance } from 'ml-peak-shape-generator';
import { getShape1D } from 'ml-peak-shape-generator';
import type { Peak, OptimizeOptions } from '../../index.ts';
import { assert } from '../assert.ts';
import { DefaultParameters } from './DefaultParameters.ts';
type Parameter = 'x' | 'y' | 'fwhm' | 'mu' | 'gamma';
type Property = 'init' | 'min' | 'max' | 'gradientDifference';
const properties: Property[] = ['init', 'min', 'max', 'gradientDifference'];
export interface InternalPeak {
id?: string;
shape: Shape1D;
shapeFct: Shape1DInstance;
parameters: Parameter[];
propertiesValues: Record<Property, number[]>;
fromIndex: number;
toIndex: number;
}
/**
* Return an array of internalPeaks that contains the exact init, min, max values based on the options
* @param peaks
* @param minMaxY
* @param options
* @returns
*/
export function getInternalPeaks(
peaks: Peak[],
minMaxY: { min: number; max: number; range: number },
options: OptimizeOptions = {},
) {
let index = 0;
const internalPeaks: InternalPeak[] = [];
const { baseline: shiftValue = minMaxY.min } = options;
const normalizedPeaks = peaks.map((peak) => {
return {
...peak,
y: (peak.y - shiftValue) / minMaxY.range,
};
});
for (const peak of normalizedPeaks) {
const { id, shape = options.shape ? options.shape : { kind: 'gaussian' } } =
peak;
const shapeFct: Shape1DInstance = getShape1D(shape);
const parameters: Parameter[] = ['x', 'y', ...shapeFct.getParameters()];
const propertiesValues: Record<Property, number[]> = {
min: [],
max: [],
init: [],
gradientDifference: [],
};
for (const parameter of parameters) {
for (const property of properties) {
// check if the property is specified in the peak
let propertyValue = peak?.parameters?.[parameter]?.[property];
if (propertyValue) {
propertyValue = getNormalizedValue(
propertyValue,
parameter,
property,
minMaxY,
options.baseline,
);
propertiesValues[property].push(propertyValue);
continue;
}
// check if there are some global option, it could be a number or a callback
let generalParameterValue =
options?.parameters?.[parameter]?.[property];
if (generalParameterValue) {
if (typeof generalParameterValue === 'number') {
generalParameterValue = getNormalizedValue(
generalParameterValue,
parameter,
property,
minMaxY,
options.baseline,
);
propertiesValues[property].push(generalParameterValue);
continue;
} else {
let value = generalParameterValue(peak);
value = getNormalizedValue(
value,
parameter,
property,
minMaxY,
options.baseline,
);
propertiesValues[property].push(value);
continue;
}
}
// we just need to take the default parameters
assert(
DefaultParameters[parameter],
`No default parameter for ${parameter}`,
);
const defaultParameterValues = DefaultParameters[parameter][property];
//@ts-expect-error should never happen
propertiesValues[property].push(defaultParameterValues(peak, shapeFct));
}
}
const fromIndex = index;
const toIndex = fromIndex + parameters.length - 1;
index += toIndex - fromIndex + 1;
internalPeaks.push({
id,
shape,
shapeFct,
parameters,
propertiesValues,
fromIndex,
toIndex,
});
}
return internalPeaks;
}
function getNormalizedValue(
value: number,
parameter: string,
property: string,
minMaxY: { min: number; max: number; range: number },
baseline?: number,
): number {
if (parameter === 'y') {
if (property === 'gradientDifference') {
return value;
} else {
return baseline !== undefined
? (value - baseline) / minMaxY.range
: (value - minMaxY.min) / minMaxY.range;
}
}
return value;
}