@ohif/app 3.13.0-beta.88 → 3.13.0-beta.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{5491.bundle.4866d2ecb20dd089e071.js → 1124.bundle.3c1b947a1884362c1241.js} +376 -222
- package/dist/1429.bundle.bd1585628440399e6c3f.js +2079 -0
- package/dist/{2108.bundle.142ee8d405727d02b16a.js → 1704.bundle.38d95b81b9e1293e04de.js} +1 -643
- package/dist/{7537.bundle.3c7d71348190ed4e9565.js → 1741.bundle.abc3a09729ada4c25685.js} +105024 -55129
- package/dist/{1927.bundle.be67b3aafe238ca9f191.js → 1927.bundle.1749aa6ae7b1dd3d06b1.js} +1 -1
- package/dist/{6354.bundle.929febcf6d326e582e00.js → 2802.bundle.3d6cc53ef3c69901431d.js} +31 -324
- package/dist/{3461.bundle.9bc7ca0f93d754014c23.js → 3461.bundle.2f3e998096ef3fca602b.js} +5 -4
- package/dist/{3617.bundle.f408991512372e52c6cf.js → 3617.bundle.f7f53b332f72e9fe6374.js} +2 -2
- package/dist/{3754.bundle.477cfd7fc3ff13e9dc67.js → 3754.bundle.2a46971b209140d4cb08.js} +12 -6
- package/dist/{9039.bundle.49ab126163ca208e52b3.js → 4335.bundle.69a7cc007e85f043488e.js} +4114 -4263
- package/dist/{4579.bundle.1c315389368cc476aed3.js → 451.bundle.6634a9dae2b4f0e9ebc9.js} +83 -30
- package/dist/{5028.bundle.4960393948811673d99b.js → 5028.bundle.f483671d94ded4c8f6f3.js} +2 -2
- package/dist/{5858.bundle.466e58128de344ab53f3.js → 5858.bundle.c25c73ecbc47b1a69cea.js} +0 -4
- package/dist/{6125.bundle.2c6f2ba3bd8e1493e54b.js → 6125.bundle.4e46ecd271f647cb977f.js} +2 -2087
- package/dist/{6376.bundle.f5ef0f5465d575d0dbb0.js → 6376.bundle.c7c515ba478be810e03d.js} +6 -3
- package/dist/{9567.bundle.ff782480a4c66e306027.js → 650.bundle.5decc3ce72a22162b8f4.js} +3080 -216
- package/dist/{7166.bundle.0a84efe4f3e6d0ddcbec.js → 7166.bundle.eeeda537770d73736e46.js} +161 -55
- package/dist/{6386.bundle.dc7e3b159d6b4733647f.js → 741.bundle.e47adc12429ee838d3fb.js} +3408 -3722
- package/dist/{7431.bundle.a9b4dbf97a8c196efe51.js → 7431.bundle.c3bd9a9f1ea743c81335.js} +61 -60
- package/dist/{8305.bundle.8088b210631f2b22899d.js → 8305.bundle.71787ad9e3df5d1dd30d.js} +2 -2
- package/dist/{8558.bundle.79c567857e04df2694cc.js → 8558.bundle.00d5dbdf73ca0a44831a.js} +1 -1
- package/dist/{8583.bundle.e36ff2fc6ee67e4e4232.js → 8583.bundle.40859ef5654559a1c66b.js} +3 -3
- package/dist/{9205.bundle.c974537f15d86687f6d2.js → 9205.bundle.d43cd617e38109746e4d.js} +2257 -2133
- package/dist/{4287.bundle.4f28e48c1e39a46dfc87.js → 9400.bundle.a56bfccf2bf7a51ed297.js} +1362 -692
- package/dist/{2075.bundle.ba9ac60662f1fa739378.js → 9475.bundle.693fe4de0573042a17f3.js} +203 -156
- package/dist/{app.bundle.3b116829d23059e8d9d8.js → app.bundle.93ee83d5e283aa1496da.js} +81239 -52123
- package/dist/{compute.bundle.2b82d8a0d1f3b41df1d2.js → compute.bundle.2fc1182cc9b2af5f4a40.js} +3 -15
- package/dist/{histogram-worker.bundle.a2a50c4674d99c619ca7.js → histogram-worker.bundle.c81373ef983363586dc5.js} +4 -4
- package/dist/index.html +1 -1
- package/dist/{interpolation.bundle.35be9e5cf473c6ebd3da.js → interpolation.bundle.a31edd1d26d81ef65b9b.js} +6 -6
- package/dist/{polySeg.bundle.ad2080f28d2c471bf208.js → polySeg.bundle.294012e3a5458cd3fecc.js} +3 -15
- package/dist/sw.js +1 -1
- package/package.json +21 -21
- /package/dist/{1459.bundle.23ea55eeadd13d26d6b5.js → 1459.bundle.763540bccf93fda17fff.js} +0 -0
- /package/dist/{1933.bundle.5713711c4a8f6518803b.js → 1933.bundle.8d54b74d93a7354925c5.js} +0 -0
- /package/dist/{2018.bundle.fee6699868f9e280861c.js → 2018.bundle.7ba8f9afeb48defb425b.js} +0 -0
- /package/dist/{213.bundle.3c0a19cba715a9a87151.js → 213.bundle.1a54c6878a493c5b61c5.js} +0 -0
- /package/dist/{2424.bundle.1d615d02f985abbbc633.js → 2424.bundle.c6d74f7129108d885a1b.js} +0 -0
- /package/dist/{2516.bundle.6b52ced19754035768ae.js → 2516.bundle.28427294c53e067811ca.js} +0 -0
- /package/dist/{3138.bundle.451e4105dda240465e6d.js → 3138.bundle.3845b7e06b8ab2564951.js} +0 -0
- /package/dist/{4507.bundle.51470574cbf11549ff72.js → 4507.bundle.b658ff59d35614916a2b.js} +0 -0
- /package/dist/{4819.bundle.c5e695cd51065d2a1a88.js → 4819.bundle.e28f5f1f0d481e190bb1.js} +0 -0
- /package/dist/{5015.bundle.647c560efcc9942bb42c.js → 5015.bundle.455552a6df9b442b3c40.js} +0 -0
- /package/dist/{5457.bundle.45b43e0e56637108f8f1.js → 5457.bundle.ee1026db729958b36a2e.js} +0 -0
- /package/dist/{5485.bundle.3d2d3a7f37f948da2269.js → 5485.bundle.0ecad3940c321ae65b23.js} +0 -0
- /package/dist/{5802.bundle.da6055b6bf1a3db7b6d5.js → 5802.bundle.5e1a7df7ad850a5370f8.js} +0 -0
- /package/dist/{5830.bundle.d026ec38980fb12f2518.js → 5830.bundle.8327ccd3c8a6334ac315.js} +0 -0
- /package/dist/{6027.bundle.ea7a56c1ebde501ce02d.js → 6027.bundle.f276566b6237d9e16333.js} +0 -0
- /package/dist/{7639.bundle.2b4ae693bddf15fb9641.js → 7639.bundle.25e1a86f2248148c35d5.js} +0 -0
- /package/dist/{8499.bundle.f94b5427bb2889634aeb.js → 8499.bundle.22f25667db4ab2e59e57.js} +0 -0
- /package/dist/{85.bundle.f7cb4cf8432ca6a815c0.js → 85.bundle.2aa39c9bf12258c7ca68.js} +0 -0
- /package/dist/{9862.bundle.f92516a34b9cef88c56f.js → 9862.bundle.fb87529360fb93010555.js} +0 -0
- /package/dist/{9927.bundle.5cf09e95f0253210d431.js → 9927.bundle.f6e7785bceeeb7a1c62e.js} +0 -0
|
@@ -1,646 +1,4 @@
|
|
|
1
|
-
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[
|
|
2
|
-
|
|
3
|
-
/***/ 48405
|
|
4
|
-
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
5
|
-
|
|
6
|
-
"use strict";
|
|
7
|
-
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
8
|
-
/* harmony export */ C: () => (/* binding */ calculateSUVScalingFactors)
|
|
9
|
-
/* harmony export */ });
|
|
10
|
-
/**
|
|
11
|
-
* Javascript object that handles dates and compute the time.
|
|
12
|
-
*
|
|
13
|
-
* @export
|
|
14
|
-
* @class FullDateInterface
|
|
15
|
-
*/
|
|
16
|
-
class FullDateInterface {
|
|
17
|
-
/**
|
|
18
|
-
* Creates an instance of FullDateInterface.
|
|
19
|
-
* @param {string} date formatted as yyyy-mm-ddTHH:MM:SS.FFFFFFZ
|
|
20
|
-
* @memberof FullDateInterface
|
|
21
|
-
*/
|
|
22
|
-
constructor(date) {
|
|
23
|
-
this.fullDate = date;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* returns time since 1 january 1970
|
|
27
|
-
*
|
|
28
|
-
* @returns {number} time in sec
|
|
29
|
-
* @memberof FullDateInterface
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
getTimeInSec() {
|
|
34
|
-
// yyyy-mm-ddTHH:MM:SS.FFFFFFZ
|
|
35
|
-
const dateString = this.fullDate.substring(0, 10);
|
|
36
|
-
const timeString = this.fullDate.substring(11, 28); // yyyy-mm-dd
|
|
37
|
-
|
|
38
|
-
const yyyy = parseInt(dateString.substring(0, 4), 10);
|
|
39
|
-
const mm = dateString.length >= 7 ? parseInt(dateString.substring(5, 7), 10) : undefined;
|
|
40
|
-
const dd = dateString.length >= 10 ? parseInt(dateString.substring(8, 10), 10) : undefined;
|
|
41
|
-
|
|
42
|
-
if (isNaN(yyyy) || mm !== undefined && isNaN(mm) || dd !== undefined && isNaN(dd) || yyyy > 3000 || mm && (mm < 1 || mm > 12) || dd && (dd < 1 || dd > 31)) {
|
|
43
|
-
throw new Error(`invalid date '${dateString}'`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const dateJS = new Date(`${dateString}T00:00:00.000000Z`); // HHMMSS.FFFFFF
|
|
47
|
-
|
|
48
|
-
const HH = parseInt(timeString.substring(0, 2), 10);
|
|
49
|
-
const MM = timeString.length >= 5 ? parseInt(timeString.substring(3, 5), 10) : undefined;
|
|
50
|
-
const SS = timeString.length >= 8 ? parseInt(timeString.substring(6, 8), 10) : undefined;
|
|
51
|
-
const fractionalStr = timeString.substring(9, 15);
|
|
52
|
-
const FFFFFF = fractionalStr ? parseInt(fractionalStr, 10) * Math.pow(10, -fractionalStr.length) : undefined;
|
|
53
|
-
|
|
54
|
-
if (isNaN(HH) || MM !== undefined && isNaN(MM) || SS !== undefined && isNaN(SS) || FFFFFF !== undefined && isNaN(FFFFFF) || HH < 0 || HH > 23 || MM && (MM < 0 || MM > 59) || SS && (SS < 0 || SS > 59) || FFFFFF && (FFFFFF < 0 || FFFFFF > 999999)) {
|
|
55
|
-
throw new Error(`invalid time '${timeString}'`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let timeInSec = dateJS.getTime() / 1000;
|
|
59
|
-
timeInSec += HH * 3600;
|
|
60
|
-
|
|
61
|
-
if (MM !== undefined) {
|
|
62
|
-
timeInSec += MM * 60;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (SS !== undefined) {
|
|
66
|
-
timeInSec += SS;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (FFFFFF !== undefined) {
|
|
70
|
-
timeInSec += FFFFFF;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return timeInSec;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* returns time since 1 january 1970
|
|
77
|
-
*
|
|
78
|
-
* @returns {number} time in microsec
|
|
79
|
-
* @memberof FullDateInterface
|
|
80
|
-
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
getTimeInMicroSec() {
|
|
84
|
-
const timeInMicroSec = this.getTimeInSec() * 1e6;
|
|
85
|
-
return timeInMicroSec;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Combines two javascript objects containing the date and time information
|
|
91
|
-
*
|
|
92
|
-
* @export
|
|
93
|
-
* @param {DateInterface} date
|
|
94
|
-
* @param {TimeInterface} time
|
|
95
|
-
* @returns {FullDateInterface}
|
|
96
|
-
*/
|
|
97
|
-
|
|
98
|
-
function combineDateTime(date, time) {
|
|
99
|
-
const hours = `${time.hours || '00'}`.padStart(2, '0');
|
|
100
|
-
const minutes = `${time.minutes || '00'}`.padStart(2, '0');
|
|
101
|
-
const seconds = `${time.seconds || '00'}`.padStart(2, '0');
|
|
102
|
-
const month = `${date.month}`.padStart(2, '0');
|
|
103
|
-
const day = `${date.day}`.padStart(2, '0');
|
|
104
|
-
const fractionalSeconds = `${time.fractionalSeconds || '000000'}`.padEnd(6, '0');
|
|
105
|
-
const dateString = `${date.year}-${month}-${day}`;
|
|
106
|
-
const timeString = `T${hours}:${minutes}:${seconds}.${fractionalSeconds}Z`;
|
|
107
|
-
const fullDateString = `${dateString}${timeString}`;
|
|
108
|
-
return new FullDateInterface(fullDateString);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Check the number of days for a picked month and year
|
|
113
|
-
* algorithm based on http://stackoverflow.com/questions/1433030/validate-number-of-days-in-a-given-month
|
|
114
|
-
*
|
|
115
|
-
* @param {number} m
|
|
116
|
-
* @param {number} y
|
|
117
|
-
* @returns {number} number of days
|
|
118
|
-
*/
|
|
119
|
-
function daysInMonth(m, y) {
|
|
120
|
-
// m is 0 indexed: 0-11
|
|
121
|
-
switch (m) {
|
|
122
|
-
case 2:
|
|
123
|
-
return y % 4 === 0 && y % 100 || y % 400 === 0 ? 29 : 28;
|
|
124
|
-
|
|
125
|
-
case 9:
|
|
126
|
-
case 4:
|
|
127
|
-
case 6:
|
|
128
|
-
case 11:
|
|
129
|
-
return 30;
|
|
130
|
-
|
|
131
|
-
default:
|
|
132
|
-
return 31;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Check if the date is valid
|
|
137
|
-
*
|
|
138
|
-
* @param {number} d
|
|
139
|
-
* @param {number} m
|
|
140
|
-
* @param {number} y
|
|
141
|
-
* @returns {boolean} boolean result
|
|
142
|
-
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
function isValidDate(d, m, y) {
|
|
146
|
-
// make year is a number
|
|
147
|
-
if (isNaN(y)) {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return m > 0 && m <= 12 && d > 0 && d <= daysInMonth(m, y);
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Parses a DA formatted string into a Javascript object
|
|
155
|
-
* @param {string} date a string in the DA VR format
|
|
156
|
-
* @param {boolean} [validate] - true if an exception should be thrown if the date is invalid
|
|
157
|
-
* @returns {DateInterface} Javascript object with properties year, month and day or undefined if not present or not 8 bytes long
|
|
158
|
-
*/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
function parseDA(date) {
|
|
162
|
-
if (date === undefined || date === null || date.length !== 8 || typeof date !== 'string') {
|
|
163
|
-
throw new Error(`invalid DA '${date}'`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const yyyy = parseInt(date.substring(0, 4), 10);
|
|
167
|
-
const mm = parseInt(date.substring(4, 6), 10);
|
|
168
|
-
const dd = parseInt(date.substring(6, 8), 10);
|
|
169
|
-
|
|
170
|
-
if (isValidDate(dd, mm, yyyy) !== true) {
|
|
171
|
-
throw new Error(`invalid DA '${date}'`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
year: yyyy,
|
|
176
|
-
month: mm,
|
|
177
|
-
day: dd
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Parses a TM formatted string into a javascript object with properties for hours, minutes, seconds and fractionalSeconds
|
|
183
|
-
* @param {string} time - a string in the TM VR format
|
|
184
|
-
* @returns {string} javascript object with properties for hours, minutes, seconds and fractionalSeconds or undefined if no element or data. Missing fields are set to undefined
|
|
185
|
-
*/
|
|
186
|
-
function parseTM(time) {
|
|
187
|
-
if (time === null || time === undefined || time.length < 2 || typeof time !== 'string') {
|
|
188
|
-
// must at least have HH
|
|
189
|
-
throw new Error(`invalid TM '${time}'`);
|
|
190
|
-
} // 0123456789
|
|
191
|
-
// HHMMSS.FFFFFF
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const hh = parseInt(time.substring(0, 2), 10);
|
|
195
|
-
const mm = time.length >= 4 ? parseInt(time.substring(2, 4), 10) : undefined;
|
|
196
|
-
const ss = time.length >= 6 ? parseInt(time.substring(4, 6), 10) : undefined;
|
|
197
|
-
const fractionalStr = time.length >= 8 ? time.substring(7, 13) : undefined;
|
|
198
|
-
const ffffff = fractionalStr ? parseInt(fractionalStr, 10) * Math.pow(10, 6 - fractionalStr.length) : undefined;
|
|
199
|
-
|
|
200
|
-
if (isNaN(hh) || mm !== undefined && isNaN(mm) || ss !== undefined && isNaN(ss) || ffffff !== undefined && isNaN(ffffff) || hh < 0 || hh > 23 || mm && (mm < 0 || mm > 59) || ss && (ss < 0 || ss > 59) || ffffff && (ffffff < 0 || ffffff > 999999)) {
|
|
201
|
-
throw new Error(`invalid TM '${time}'`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
hours: hh,
|
|
206
|
-
minutes: mm,
|
|
207
|
-
seconds: ss,
|
|
208
|
-
fractionalSeconds: ffffff
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Utility to create a FullDateInterface object given a string formatted as yyyy-mm-ddTHH:MM:SS.FFFFFFZ
|
|
214
|
-
*
|
|
215
|
-
* @export
|
|
216
|
-
* @param {string} dateTime
|
|
217
|
-
* @returns {FullDateInterface}
|
|
218
|
-
*/
|
|
219
|
-
|
|
220
|
-
function dateTimeToFullDateInterface(dateTime) {
|
|
221
|
-
if (dateTime === undefined || dateTime === null) {
|
|
222
|
-
throw new Error('dateTimeToFullDateInterface : dateTime not defined.');
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const date = parseDA(dateTime.substring(0, 8));
|
|
226
|
-
const time = parseTM(dateTime.substring(8));
|
|
227
|
-
return combineDateTime(date, time);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Calculate the scan times
|
|
232
|
-
*
|
|
233
|
-
* @export
|
|
234
|
-
* @param {InstanceMetadataForScanTimes[]} instances
|
|
235
|
-
* @returns {FullDateInterface[]}
|
|
236
|
-
*/
|
|
237
|
-
|
|
238
|
-
function calculateScanTimes(instances) {
|
|
239
|
-
const {
|
|
240
|
-
SeriesDate,
|
|
241
|
-
SeriesTime,
|
|
242
|
-
GEPrivatePostInjectionDateTime
|
|
243
|
-
} = instances[0];
|
|
244
|
-
const results = new Array(instances.length);
|
|
245
|
-
const seriesDate = parseDA(SeriesDate);
|
|
246
|
-
const seriesTime = parseTM(SeriesTime);
|
|
247
|
-
const seriesDateTime = combineDateTime(seriesDate, seriesTime);
|
|
248
|
-
let earliestAcquisitionDateTime = new FullDateInterface(`3000-01-01T00:00:00.000000Z`);
|
|
249
|
-
let timeError = earliestAcquisitionDateTime.getTimeInSec();
|
|
250
|
-
instances.forEach(instance => {
|
|
251
|
-
const {
|
|
252
|
-
AcquisitionDate,
|
|
253
|
-
AcquisitionTime
|
|
254
|
-
} = instance;
|
|
255
|
-
const acquisitionDate = parseDA(AcquisitionDate);
|
|
256
|
-
const acquisitionTime = parseTM(AcquisitionTime);
|
|
257
|
-
const acquisitionDateTime = combineDateTime(acquisitionDate, acquisitionTime);
|
|
258
|
-
|
|
259
|
-
if (earliestAcquisitionDateTime.getTimeInSec() >= timeError) {
|
|
260
|
-
earliestAcquisitionDateTime = acquisitionDateTime;
|
|
261
|
-
} else {
|
|
262
|
-
earliestAcquisitionDateTime = acquisitionDateTime.getTimeInSec() < earliestAcquisitionDateTime.getTimeInSec() ? acquisitionDateTime : earliestAcquisitionDateTime;
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
if (earliestAcquisitionDateTime.getTimeInSec() >= timeError) {
|
|
267
|
-
throw new Error('Earliest acquisition time or date could not be parsed.');
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (seriesDateTime.getTimeInSec() <= earliestAcquisitionDateTime.getTimeInSec()) {
|
|
271
|
-
return results.fill(seriesDateTime);
|
|
272
|
-
} else {
|
|
273
|
-
if (GEPrivatePostInjectionDateTime) {
|
|
274
|
-
// GE Private scan
|
|
275
|
-
return results.fill(dateTimeToFullDateInterface(GEPrivatePostInjectionDateTime));
|
|
276
|
-
} else {
|
|
277
|
-
/*const hasValidFrameTimes = instances.every(instance => {
|
|
278
|
-
return (
|
|
279
|
-
instance.FrameReferenceTime &&
|
|
280
|
-
instance.FrameReferenceTime > 0 &&
|
|
281
|
-
instance.ActualFrameDuration &&
|
|
282
|
-
instance.ActualFrameDuration > 0
|
|
283
|
-
);
|
|
284
|
-
});*/
|
|
285
|
-
// TODO: Temporarily commented out the checks and logic below to
|
|
286
|
-
// investigate the BQML_AC_DT_lessThan_S_DT_SIEMENS-instances case
|
|
287
|
-
//if (!hasValidFrameTimes) {
|
|
288
|
-
return results.fill(earliestAcquisitionDateTime); //}
|
|
289
|
-
|
|
290
|
-
/* Siemens PETsyngo 3.x multi-injection logic
|
|
291
|
-
- backcompute from center (average count rate ) of time window for bed position (frame) in series (reliable in all cases)
|
|
292
|
-
- Acquisition Date (0x0008,0x0022) and Time (0x0008,0x0032) are the start of the bed position (frame)
|
|
293
|
-
- Frame Reference Time (0x0054,0x1300) is the offset (ms) from the scan Date and Time we want to the average count rate time
|
|
294
|
-
*/
|
|
295
|
-
|
|
296
|
-
/*return instances.map(instance => {
|
|
297
|
-
const {
|
|
298
|
-
FrameReferenceTime,
|
|
299
|
-
ActualFrameDuration,
|
|
300
|
-
RadionuclideHalfLife,
|
|
301
|
-
AcquisitionDate,
|
|
302
|
-
AcquisitionTime,
|
|
303
|
-
} = instance;
|
|
304
|
-
// Some of these checks are only here because the compiler is complaining
|
|
305
|
-
// We could potentially use the ! operator instead
|
|
306
|
-
if (!FrameReferenceTime || FrameReferenceTime <= 0) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
`FrameReferenceTime is invalid: ${FrameReferenceTime}`
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
if (!ActualFrameDuration || ActualFrameDuration <= 0) {
|
|
312
|
-
throw new Error(
|
|
313
|
-
`ActualFrameDuration is invalid: ${ActualFrameDuration}`
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
if (!RadionuclideHalfLife) {
|
|
317
|
-
throw new Error('RadionuclideHalfLife is required');
|
|
318
|
-
}
|
|
319
|
-
if (!AcquisitionDate) {
|
|
320
|
-
throw new Error('AcquisitionDate is required');
|
|
321
|
-
}
|
|
322
|
-
if (!AcquisitionTime) {
|
|
323
|
-
throw new Error('AcquisitionTime is required');
|
|
324
|
-
}
|
|
325
|
-
const acquisitionDate: DateInterface = parseDA(AcquisitionDate);
|
|
326
|
-
const acquisitionTime: TimeInterface = parseTM(AcquisitionTime);
|
|
327
|
-
const acquisitionDateTime: FullDateInterface = combineDateTime(
|
|
328
|
-
acquisitionDate,
|
|
329
|
-
acquisitionTime
|
|
330
|
-
);
|
|
331
|
-
const frameDurationInSec = ActualFrameDuration / 1000;
|
|
332
|
-
const decayConstant = Math.log(2) / RadionuclideHalfLife;
|
|
333
|
-
const decayDuringFrame = decayConstant * frameDurationInSec;
|
|
334
|
-
// TODO: double check this is correctly copied from QIBA pseudocode
|
|
335
|
-
const averageCountRateTimeWithinFrameInSec =
|
|
336
|
-
(1 / decayConstant) *
|
|
337
|
-
Math.log(decayDuringFrame / (1 - Math.exp(-decayConstant)));
|
|
338
|
-
const scanDateTimeAsNumber =
|
|
339
|
-
Number(acquisitionDateTime) -
|
|
340
|
-
FrameReferenceTime / 1000 +
|
|
341
|
-
averageCountRateTimeWithinFrameInSec;
|
|
342
|
-
const scanDate = new Date(scanDateTimeAsNumber);
|
|
343
|
-
console.log('SIEMENS PATH');
|
|
344
|
-
console.log(new Date(scanDateTimeAsNumber));
|
|
345
|
-
return scanDate;
|
|
346
|
-
});*/
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function calculateSUVlbmScalingFactor(inputs) {
|
|
352
|
-
const {
|
|
353
|
-
PatientSex,
|
|
354
|
-
PatientWeight,
|
|
355
|
-
PatientSize
|
|
356
|
-
} = inputs;
|
|
357
|
-
let LBM;
|
|
358
|
-
const weightSizeFactor = Math.pow(PatientWeight / (PatientSize * 100), 2); // reference: https://www.medicalconnections.co.uk/kb/calculating-suv-from-pet-images/
|
|
359
|
-
|
|
360
|
-
if (PatientSex === 'F') {
|
|
361
|
-
LBM = 1.07 * PatientWeight - 148 * weightSizeFactor;
|
|
362
|
-
} else if (PatientSex === 'M') {
|
|
363
|
-
LBM = 1.1 * PatientWeight - 120 * weightSizeFactor;
|
|
364
|
-
} else {
|
|
365
|
-
throw new Error(`PatientSex is an invalid value: ${PatientSex}`);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return LBM * 1000; // convert in gr
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* From https://link.springer.com/article/10.1007/s00259-014-2961-x
|
|
372
|
-
* and https://link.springer.com/article/10.2165/00003088-200544100-00004
|
|
373
|
-
* and
|
|
374
|
-
* @param inputs
|
|
375
|
-
* @returns
|
|
376
|
-
*/
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
function calculateSUVlbmJanmahasatianScalingFactor(inputs) {
|
|
380
|
-
const {
|
|
381
|
-
PatientSex,
|
|
382
|
-
PatientWeight,
|
|
383
|
-
PatientSize
|
|
384
|
-
} = inputs;
|
|
385
|
-
let LBM;
|
|
386
|
-
const bodyMassIndex = PatientWeight / Math.pow(PatientSize, 2);
|
|
387
|
-
|
|
388
|
-
if (PatientSex === 'F') {
|
|
389
|
-
LBM = 9270 * PatientWeight / (8780 + 244 * bodyMassIndex);
|
|
390
|
-
} else if (PatientSex === 'M') {
|
|
391
|
-
LBM = 9270 * PatientWeight / (6680 + 216 * bodyMassIndex);
|
|
392
|
-
} else {
|
|
393
|
-
throw new Error(`PatientSex is an invalid value: ${PatientSex}`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return LBM * 1000; // convert in gr
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function calculateSUVbsaScalingFactor(inputs) {
|
|
400
|
-
const {
|
|
401
|
-
PatientWeight,
|
|
402
|
-
PatientSize
|
|
403
|
-
} = inputs;
|
|
404
|
-
let BSA = Math.pow(PatientWeight, 0.425) * Math.pow(PatientSize * 100, 0.725) * 71.84;
|
|
405
|
-
return BSA;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Calculate start time
|
|
410
|
-
*
|
|
411
|
-
* @export
|
|
412
|
-
* @param {{
|
|
413
|
-
* RadiopharmaceuticalStartDateTime?: string;
|
|
414
|
-
* RadiopharmaceuticalStartTime?: string;
|
|
415
|
-
* SeriesDate?: string;
|
|
416
|
-
* }} input
|
|
417
|
-
* @returns {FullDateInterface}
|
|
418
|
-
*/
|
|
419
|
-
|
|
420
|
-
function calculateStartTime(input) {
|
|
421
|
-
const {
|
|
422
|
-
RadiopharmaceuticalStartDateTime,
|
|
423
|
-
RadiopharmaceuticalStartTime,
|
|
424
|
-
SeriesDate
|
|
425
|
-
} = input;
|
|
426
|
-
let time;
|
|
427
|
-
let date;
|
|
428
|
-
|
|
429
|
-
if (RadiopharmaceuticalStartDateTime) {
|
|
430
|
-
return dateTimeToFullDateInterface(RadiopharmaceuticalStartDateTime);
|
|
431
|
-
} else if (RadiopharmaceuticalStartTime && SeriesDate) {
|
|
432
|
-
// start Date is not explicit - assume same as Series Date;
|
|
433
|
-
// but consider spanning midnight
|
|
434
|
-
// TODO: do we need some logic to check if the scan went over midnight?
|
|
435
|
-
time = parseTM(RadiopharmaceuticalStartTime);
|
|
436
|
-
date = parseDA(SeriesDate);
|
|
437
|
-
return combineDateTime(date, time);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
throw new Error(`Invalid input: ${input}`);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* The injected dose used to calculate SUV is corrected for the
|
|
445
|
-
* decay that occurs between the time of injection and the start of the scan
|
|
446
|
-
*
|
|
447
|
-
* @param {InstanceMetadata[]} instances
|
|
448
|
-
* @returns {number[]}
|
|
449
|
-
*/
|
|
450
|
-
|
|
451
|
-
function calculateDecayCorrection(instances) {
|
|
452
|
-
const {
|
|
453
|
-
RadionuclideTotalDose,
|
|
454
|
-
RadionuclideHalfLife,
|
|
455
|
-
RadiopharmaceuticalStartDateTime,
|
|
456
|
-
RadiopharmaceuticalStartTime,
|
|
457
|
-
SeriesDate
|
|
458
|
-
} = instances[0];
|
|
459
|
-
|
|
460
|
-
if (RadionuclideTotalDose === undefined || RadionuclideTotalDose === null) {
|
|
461
|
-
throw new Error('calculateDecayCorrection : RadionuclideTotalDose value not found.');
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (RadionuclideHalfLife === undefined || RadionuclideHalfLife === null) {
|
|
465
|
-
throw new Error('calculateDecayCorrection : RadionuclideHalfLife value not found.');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const scanTimes = calculateScanTimes(instances);
|
|
469
|
-
const startTime = calculateStartTime({
|
|
470
|
-
RadiopharmaceuticalStartDateTime,
|
|
471
|
-
RadiopharmaceuticalStartTime,
|
|
472
|
-
SeriesDate
|
|
473
|
-
});
|
|
474
|
-
return instances.map((_, index) => {
|
|
475
|
-
const scanTime = scanTimes[index];
|
|
476
|
-
const decayTimeInSec = scanTime.getTimeInSec() - startTime.getTimeInSec();
|
|
477
|
-
|
|
478
|
-
if (decayTimeInSec < 0) {
|
|
479
|
-
throw new Error('Decay time cannot be less than zero');
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const decayedDose = RadionuclideTotalDose * Math.pow(2, -decayTimeInSec / RadionuclideHalfLife);
|
|
483
|
-
return 1 / decayedDose;
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
/**
|
|
487
|
-
*
|
|
488
|
-
* @param a Simple value or array of simple values
|
|
489
|
-
* @param b Simple value or array of simple values
|
|
490
|
-
* @returns boolean true if the values are equal.
|
|
491
|
-
*/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const deepEquals = (a, b) => {
|
|
495
|
-
return a === b || Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index]);
|
|
496
|
-
};
|
|
497
|
-
/**
|
|
498
|
-
* Calculate the SUV factor
|
|
499
|
-
*
|
|
500
|
-
* Note: Rescale Slope / Intercept must still be applied. These must be applied
|
|
501
|
-
* on a per-Frame basis, since some scanners may have different values per Frame.
|
|
502
|
-
*
|
|
503
|
-
* @export
|
|
504
|
-
* @param {InstanceMetadata[]} instances
|
|
505
|
-
* @returns {ScalingFactorResult[]}
|
|
506
|
-
*/
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
function calculateSUVScalingFactors(instances) {
|
|
510
|
-
const {
|
|
511
|
-
CorrectedImage,
|
|
512
|
-
Units,
|
|
513
|
-
PhilipsPETPrivateGroup,
|
|
514
|
-
PatientWeight,
|
|
515
|
-
PatientSex,
|
|
516
|
-
PatientSize
|
|
517
|
-
} = instances[0];
|
|
518
|
-
|
|
519
|
-
if (!CorrectedImage.includes('ATTN') || !CorrectedImage.includes('DECY')) {
|
|
520
|
-
throw new Error(`CorrectedImage must contain "ATTN" and "DECY": ${CorrectedImage}`);
|
|
521
|
-
} // Sanity check that every instance provided has identical
|
|
522
|
-
// values for series-level metadata. If not, the provided
|
|
523
|
-
// data is invalid.
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const isSingleSeries = instances.every(instance => {
|
|
527
|
-
return instance.Units === Units && deepEquals(instance.CorrectedImage, CorrectedImage) && instance.PatientWeight === PatientWeight && instance.PatientSex === PatientSex && instance.PatientSize === PatientSize && instance.RadionuclideHalfLife === instances[0].RadionuclideHalfLife && instance.RadionuclideTotalDose === instances[0].RadionuclideTotalDose && instance.DecayCorrection === instances[0].DecayCorrection && instance.SeriesDate === instances[0].SeriesDate && instance.SeriesTime === instances[0].SeriesTime;
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
if (!isSingleSeries) {
|
|
531
|
-
throw new Error('The set of instances does not appear to come from one Series. Every instance must have identical values for series-level metadata properties');
|
|
532
|
-
} // Treat null, undefined and zero as a missing PatientWeight.
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (!PatientWeight) {
|
|
536
|
-
throw new Error('PatientWeight value is missing. It is not possible to calculate the SUV factors');
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
let decayCorrectionArray = new Array(instances.length);
|
|
540
|
-
decayCorrectionArray = calculateDecayCorrection(instances);
|
|
541
|
-
let results = new Array(instances.length);
|
|
542
|
-
const weightInGrams = PatientWeight * 1000;
|
|
543
|
-
|
|
544
|
-
if (Units === 'BQML') {
|
|
545
|
-
results = decayCorrectionArray.map(function (value) {
|
|
546
|
-
return value * weightInGrams;
|
|
547
|
-
});
|
|
548
|
-
} else if (Units === 'CNTS') {
|
|
549
|
-
const hasValidSUVScaleFactor = instances.every(instance => {
|
|
550
|
-
var _instance$PhilipsPETP, _instance$PhilipsPETP2, _instance$PhilipsPETP3;
|
|
551
|
-
|
|
552
|
-
return instance.PhilipsPETPrivateGroup && ((_instance$PhilipsPETP = instance.PhilipsPETPrivateGroup) === null || _instance$PhilipsPETP === void 0 ? void 0 : _instance$PhilipsPETP.SUVScaleFactor) !== null && ((_instance$PhilipsPETP2 = instance.PhilipsPETPrivateGroup) === null || _instance$PhilipsPETP2 === void 0 ? void 0 : _instance$PhilipsPETP2.SUVScaleFactor) !== undefined && ((_instance$PhilipsPETP3 = instance.PhilipsPETPrivateGroup) === null || _instance$PhilipsPETP3 === void 0 ? void 0 : _instance$PhilipsPETP3.SUVScaleFactor) !== 0;
|
|
553
|
-
});
|
|
554
|
-
const hasValidActivityConcentrationScaleFactor = instances.every(instance => {
|
|
555
|
-
var _instance$PhilipsPETP4, _instance$PhilipsPETP5, _instance$PhilipsPETP6;
|
|
556
|
-
|
|
557
|
-
return instance.PhilipsPETPrivateGroup && !((_instance$PhilipsPETP4 = instance.PhilipsPETPrivateGroup) !== null && _instance$PhilipsPETP4 !== void 0 && _instance$PhilipsPETP4.SUVScaleFactor) && ((_instance$PhilipsPETP5 = instance.PhilipsPETPrivateGroup) === null || _instance$PhilipsPETP5 === void 0 ? void 0 : _instance$PhilipsPETP5.ActivityConcentrationScaleFactor) !== undefined && ((_instance$PhilipsPETP6 = instance.PhilipsPETPrivateGroup) === null || _instance$PhilipsPETP6 === void 0 ? void 0 : _instance$PhilipsPETP6.ActivityConcentrationScaleFactor) !== 0;
|
|
558
|
-
}); //console.log(`hasValidSUVScaleFactor: ${hasValidSUVScaleFactor}`);
|
|
559
|
-
//console.log(`hasValidActivityConcentrationScaleFactor: ${hasValidActivityConcentrationScaleFactor}`);
|
|
560
|
-
|
|
561
|
-
if (hasValidSUVScaleFactor) {
|
|
562
|
-
results = instances.map( // Added ! to tell Typescript that this can't be undefined, since we are testing it
|
|
563
|
-
// in the .every loop above.
|
|
564
|
-
instance => instance.PhilipsPETPrivateGroup.SUVScaleFactor);
|
|
565
|
-
} else if (hasValidActivityConcentrationScaleFactor) {
|
|
566
|
-
// if (0x7053,0x1000) not present, but (0x7053,0x1009) is present, then (0x7053,0x1009) * Rescale Slope,
|
|
567
|
-
// scales pixels to Bq/ml, and proceed as if Units are BQML
|
|
568
|
-
results = instances.map((instance, index) => {
|
|
569
|
-
// Added ! to tell Typescript that this can't be undefined, since we are testing it
|
|
570
|
-
// in the .every loop above.
|
|
571
|
-
return instance.PhilipsPETPrivateGroup.ActivityConcentrationScaleFactor * decayCorrectionArray[index] * weightInGrams;
|
|
572
|
-
});
|
|
573
|
-
} else {
|
|
574
|
-
throw new Error(`Units are in CNTS, but PhilipsPETPrivateGroup has invalid values: ${JSON.stringify(PhilipsPETPrivateGroup)}`);
|
|
575
|
-
}
|
|
576
|
-
} else if (Units === 'GML') {
|
|
577
|
-
// assumes that GML indicates SUVbw instead of SUVlbm
|
|
578
|
-
results.fill(1);
|
|
579
|
-
} else {
|
|
580
|
-
throw new Error(`Units has an invalid value: ${Units}`);
|
|
581
|
-
} // get BSA
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
let suvbsaFactor;
|
|
585
|
-
|
|
586
|
-
if (PatientSize === null || PatientSize === undefined) {
|
|
587
|
-
console.warn('PatientSize value is missing. It is not possible to calculate the SUV bsa factors');
|
|
588
|
-
} else {
|
|
589
|
-
const sulInputs = {
|
|
590
|
-
PatientWeight,
|
|
591
|
-
PatientSize
|
|
592
|
-
};
|
|
593
|
-
suvbsaFactor = calculateSUVbsaScalingFactor(sulInputs);
|
|
594
|
-
} // get LBM
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
let suvlbmFactor;
|
|
598
|
-
let suvlbmJenmaFactor;
|
|
599
|
-
|
|
600
|
-
if (PatientSize === null || PatientSize === undefined) {
|
|
601
|
-
console.warn('PatientSize value is missing. It is not possible to calculate the SUV lbm factors');
|
|
602
|
-
} else if (PatientSex === null || PatientSex === undefined) {
|
|
603
|
-
console.warn('PatientSex value is missing. It is not possible to calculate the SUV lbm factors');
|
|
604
|
-
} else {
|
|
605
|
-
const suvlbmInputs = {
|
|
606
|
-
PatientWeight,
|
|
607
|
-
PatientSex,
|
|
608
|
-
PatientSize
|
|
609
|
-
};
|
|
610
|
-
suvlbmFactor = calculateSUVlbmScalingFactor(suvlbmInputs);
|
|
611
|
-
suvlbmJenmaFactor = calculateSUVlbmJanmahasatianScalingFactor(suvlbmInputs);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
return results.map(function (result, index) {
|
|
615
|
-
const factors = {
|
|
616
|
-
suvbw: result
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
if (suvbsaFactor) {
|
|
620
|
-
// multiply for BSA
|
|
621
|
-
factors.suvbsa = decayCorrectionArray[index] * suvbsaFactor;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
if (suvlbmFactor) {
|
|
625
|
-
// multiply for LBM
|
|
626
|
-
factors.suvlbm = decayCorrectionArray[index] * suvlbmFactor;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
if (suvlbmJenmaFactor) {
|
|
630
|
-
factors.suvlbmJanma = decayCorrectionArray[index] * suvlbmJenmaFactor;
|
|
631
|
-
} // factor formulaes taken from:
|
|
632
|
-
// https://www.medicalconnections.co.uk/kb/calculating-suv-from-pet-images/
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
return factors;
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
//# sourceMappingURL=calculate-suv.esm.js.map
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
/***/ },
|
|
1
|
+
(globalThis["webpackChunk"] = globalThis["webpackChunk"] || []).push([[1704],{
|
|
644
2
|
|
|
645
3
|
/***/ 81810
|
|
646
4
|
(__unused_webpack_module, exports, __webpack_require__) {
|