-
Notifications
You must be signed in to change notification settings - Fork 8
/
broadenPeaks.ts
103 lines (87 loc) · 2.85 KB
/
broadenPeaks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import { getShape1D, Shape1D } from 'ml-peak-shape-generator';
import { GSDBroadenPeak } from '../GSDBroadenPeak';
import { GSDPeak } from '../GSDPeak';
type GSDPeakOptionalShape = GSDPeak & { shape?: Shape1D };
type GSDBroadenPeakWithID = GSDBroadenPeak & { id: string };
type GSDBroadenPeakWithShape = GSDBroadenPeak & { shape: Shape1D };
type GSDBroadenPeakWithShapeID = GSDBroadenPeakWithID & { shape: Shape1D };
export type WithOrWithout<T, ToExtends, TrueType, FalseType> =
T extends ToExtends ? TrueType : FalseType;
export type WithIDOrShape<T> = T extends { id: string }
? WithOrWithout<T, GSDPeak, GSDBroadenPeakWithShapeID, GSDBroadenPeakWithID>
: WithOrWithout<T, GSDPeak, GSDBroadenPeakWithShape, GSDBroadenPeak>;
/**
* This method will allow to enlarge peaks while preventing overlap between peaks
* A typical application in chromatography peak picking.
* We should not make the hypothesis that x is equidistant
* Because peaks may not be symmetric after we add 2 properties, from and to.
* @return {Array} peakList
*/
export function broadenPeaks<T extends GSDPeakOptionalShape>(
peakList: T[],
options: {
/**
* @default 2
*/
factor?: number;
/**
* by default we don't allow overlap
* @default false
*/
overlap?: boolean;
} = {},
) {
const { factor = 2, overlap = false } = options;
const peaks = mapPeaks(peakList, factor);
if (!overlap) {
for (let i = 0; i < peaks.length - 1; i++) {
const peak = peaks[i];
const nextPeak = peaks[i + 1];
if (peak.to.x > nextPeak.from.x) {
// we do it proportional to the width of the peaks
peak.to.x =
(peak.width / (nextPeak.width + peak.width)) * (nextPeak.x - peak.x) +
peak.x;
nextPeak.from.x = peak.to.x;
}
}
}
for (const peak of peaks) {
peak.width = peak.to.x - peak.from.x;
if (peak.shape) {
const { shape, width } = peak;
if (shape.fwhm !== undefined) {
const shapeFct = getShape1D(shape);
peak.shape.fwhm = shapeFct.widthToFWHM(width);
}
}
}
return peaks;
}
function mapPeaks<T extends GSDPeakOptionalShape>(
peaks: T[],
factor: number,
): Array<WithIDOrShape<T>> {
return peaks.map((peak) => {
const { id, shape } = peak;
const xFrom = peak.x - (peak.x - peak.inflectionPoints.from.x) * factor;
const xTo = peak.x + (peak.inflectionPoints.to.x - peak.x) * factor;
let result = {
x: peak.x,
y: peak.y,
index: peak.index,
width: xTo - xFrom,
from: { x: xFrom },
to: { x: xTo },
} as GSDBroadenPeak;
if (id) {
result = { ...result, id } as GSDBroadenPeakWithID;
}
if (shape) {
result = { ...result, shape } as T extends { id: string }
? GSDBroadenPeakWithShapeID
: GSDBroadenPeakWithShape;
}
return result as WithIDOrShape<T>;
});
}