larvitar 2.0.4 → 2.0.6
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/README.md +2 -2
- package/dist/larvitar.js +5 -3
- package/dist/larvitar.js.map +1 -1
- package/package.json +6 -2
- package/.github/workflows/build-docs.yml +0 -59
- package/.github/workflows/codeql-analysis.yml +0 -71
- package/.github/workflows/deploy.yml +0 -37
- package/.vscode/settings.json +0 -4
- package/CODE_OF_CONDUCT.md +0 -76
- package/MIGRATION.md +0 -25
- package/bundler/webpack.common.js +0 -27
- package/bundler/webpack.dev.js +0 -23
- package/bundler/webpack.prod.js +0 -19
- package/decs.d.ts +0 -12
- package/imaging/MetaDataReadable.ts +0 -42
- package/imaging/MetaDataTypes.ts +0 -3491
- package/imaging/dataDictionary.json +0 -21866
- package/imaging/imageAnonymization.ts +0 -135
- package/imaging/imageColormaps.ts +0 -217
- package/imaging/imageContours.ts +0 -196
- package/imaging/imageIo.ts +0 -251
- package/imaging/imageLayers.ts +0 -121
- package/imaging/imageLoading.ts +0 -299
- package/imaging/imageParsing.ts +0 -444
- package/imaging/imagePresets.ts +0 -156
- package/imaging/imageRendering.ts +0 -1091
- package/imaging/imageReslice.ts +0 -87
- package/imaging/imageStore.ts +0 -487
- package/imaging/imageTags.ts +0 -609
- package/imaging/imageTools.js +0 -708
- package/imaging/imageUtils.ts +0 -1079
- package/imaging/loaders/commonLoader.ts +0 -275
- package/imaging/loaders/dicomLoader.ts +0 -66
- package/imaging/loaders/fileLoader.ts +0 -71
- package/imaging/loaders/multiframeLoader.ts +0 -435
- package/imaging/loaders/nrrdLoader.ts +0 -630
- package/imaging/loaders/resliceLoader.ts +0 -205
- package/imaging/monitors/memory.ts +0 -151
- package/imaging/monitors/performance.ts +0 -34
- package/imaging/parsers/ecg.ts +0 -54
- package/imaging/parsers/nrrd.js +0 -485
- package/imaging/tools/README.md +0 -27
- package/imaging/tools/custom/4dSliceScrollTool.js +0 -146
- package/imaging/tools/custom/BorderMagnifyTool.js +0 -99
- package/imaging/tools/custom/contourTool.js +0 -1884
- package/imaging/tools/custom/diameterTool.js +0 -141
- package/imaging/tools/custom/editMaskTool.js +0 -141
- package/imaging/tools/custom/ellipticalRoiOverlayTool.js +0 -534
- package/imaging/tools/custom/polygonSegmentationMixin.js +0 -245
- package/imaging/tools/custom/polylineScissorsTool.js +0 -59
- package/imaging/tools/custom/rectangleRoiOverlayTool.js +0 -564
- package/imaging/tools/custom/seedTool.js +0 -342
- package/imaging/tools/custom/setLabelMap3D.ts +0 -242
- package/imaging/tools/custom/thresholdsBrushTool.js +0 -161
- package/imaging/tools/default.ts +0 -594
- package/imaging/tools/interaction.ts +0 -266
- package/imaging/tools/io.ts +0 -229
- package/imaging/tools/main.ts +0 -424
- package/imaging/tools/segmentation.ts +0 -532
- package/imaging/tools/segmentations.md +0 -38
- package/imaging/tools/state.ts +0 -74
- package/imaging/tools/strategies/eraseFreehand.js +0 -76
- package/imaging/tools/strategies/fillFreehand.js +0 -79
- package/imaging/tools/strategies/index.js +0 -2
- package/imaging/tools/types.d.ts +0 -243
- package/imaging/types.d.ts +0 -200
- package/imaging/waveforms/ecg.ts +0 -191
- package/index.ts +0 -431
- package/jsdoc.json +0 -52
- package/rollup.config.js +0 -51
- package/template/.gitkeep +0 -0
- package/tsconfig.json +0 -102
package/imaging/imageUtils.ts
DELETED
|
@@ -1,1079 +0,0 @@
|
|
|
1
|
-
/** @module imaging/imageUtils
|
|
2
|
-
* @desc This file provides utility functions for
|
|
3
|
-
* manipulating image pixels and image metadata
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// external libraries
|
|
7
|
-
import {
|
|
8
|
-
isEmpty,
|
|
9
|
-
sortBy,
|
|
10
|
-
clone,
|
|
11
|
-
find,
|
|
12
|
-
filter,
|
|
13
|
-
keys,
|
|
14
|
-
has,
|
|
15
|
-
max,
|
|
16
|
-
map,
|
|
17
|
-
forEach,
|
|
18
|
-
extend,
|
|
19
|
-
indexOf,
|
|
20
|
-
random
|
|
21
|
-
} from "lodash";
|
|
22
|
-
import { v4 as uuidv4 } from "uuid";
|
|
23
|
-
import cornerstone from "cornerstone-core";
|
|
24
|
-
|
|
25
|
-
// internal libraries
|
|
26
|
-
import { getDicomImageId } from "./loaders/dicomLoader";
|
|
27
|
-
import TAG_DICT from "./dataDictionary.json";
|
|
28
|
-
import { getSeriesDataFromLarvitarManager } from "./loaders/commonLoader";
|
|
29
|
-
import type {
|
|
30
|
-
CustomDataSet,
|
|
31
|
-
MetaData,
|
|
32
|
-
ReslicedInstance,
|
|
33
|
-
Series
|
|
34
|
-
} from "./types";
|
|
35
|
-
import { getTagValue } from "./imageTags";
|
|
36
|
-
import { MetaDataTypes } from "./MetaDataTypes";
|
|
37
|
-
|
|
38
|
-
// global module variables
|
|
39
|
-
// variables used to manage the reslice functionality
|
|
40
|
-
const resliceTable: {
|
|
41
|
-
[key: string]: {
|
|
42
|
-
[key: string]: [number, number, number];
|
|
43
|
-
};
|
|
44
|
-
} = {
|
|
45
|
-
sagittal: { coronal: [-2, 1, 0], axial: [-2, 0, -1] },
|
|
46
|
-
coronal: { sagittal: [2, 1, -0], axial: [0, 2, -1] },
|
|
47
|
-
axial: { sagittal: [1, -2, -0], coronal: [0, -2, 1] }
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/*
|
|
51
|
-
* This module provides the following functions to be exported:
|
|
52
|
-
* getNormalOrientation(array[6])
|
|
53
|
-
* getMinPixelValue(defaultValue, pixelData)
|
|
54
|
-
* getMaxPixelValue(defaultValue, pixelData)
|
|
55
|
-
* getPixelRepresentation(dataset)
|
|
56
|
-
* getTypedArrayFromDataType(dataType)
|
|
57
|
-
* getSortedStack(seriesData, sortPriorities, returnSuccessMethod)
|
|
58
|
-
* getSortedUIDs(seriesData)
|
|
59
|
-
* randomId()
|
|
60
|
-
* getMeanValue(series, tag, isArray)
|
|
61
|
-
* getReslicedMetadata(reslicedSeriesId, fromOrientation, toOrientation, seriesData, imageLoaderName)
|
|
62
|
-
* getCmprMetadata(reslicedSeriesId, imageLoaderName, header)
|
|
63
|
-
* getReslicedPixeldata(imageId, originalData, reslicedData)
|
|
64
|
-
* getDistanceBetweenSlices(seriesData, sliceIndex1, sliceIndex2)
|
|
65
|
-
* isElement(o)
|
|
66
|
-
* getImageMetadata(dataSet, imageId)
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* @typedef {Object} CornerstoneSeries
|
|
71
|
-
* @property {Array} imageIds Array of the instances imageIds
|
|
72
|
-
* @property {Array} instances Array of instances
|
|
73
|
-
* @property {Number} currentImageIndex Currently loaded image id index in the imageIds array
|
|
74
|
-
*/
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Return computed 3D normal from two 3D vectors
|
|
78
|
-
* @instance
|
|
79
|
-
* @function getNormalOrientation
|
|
80
|
-
* @param {Array} el - The image_orientation dicom tag
|
|
81
|
-
*/
|
|
82
|
-
export const getNormalOrientation = function (
|
|
83
|
-
el: [number, number, number, number, number, number]
|
|
84
|
-
) {
|
|
85
|
-
let a = [el[0], el[1], el[2]];
|
|
86
|
-
let b = [el[3], el[4], el[5]];
|
|
87
|
-
|
|
88
|
-
let n = [
|
|
89
|
-
a[1] * b[2] - a[2] * b[1],
|
|
90
|
-
a[2] * b[0] - a[0] * b[2],
|
|
91
|
-
a[0] * b[1] - a[1] * b[0]
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
return n;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* If a value is provided, returns it, otherwise get the min pixel value from pixelData
|
|
99
|
-
* @instance
|
|
100
|
-
* @function getMinPixelValue
|
|
101
|
-
* @param {Number} value - The min value
|
|
102
|
-
* @param {Array} pixelData - Pixel data array
|
|
103
|
-
*/
|
|
104
|
-
export const getMinPixelValue = function (
|
|
105
|
-
value: number,
|
|
106
|
-
pixelData: Uint16Array
|
|
107
|
-
) {
|
|
108
|
-
if (value !== undefined) {
|
|
109
|
-
return value;
|
|
110
|
-
}
|
|
111
|
-
let min;
|
|
112
|
-
for (let i = 0; i < pixelData.length; i++) {
|
|
113
|
-
if (!min || min > pixelData[i]) {
|
|
114
|
-
min = pixelData[i];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return min;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* If a value is provided, returns it, otherwise get the max pixel value from pixelData
|
|
122
|
-
* @instance
|
|
123
|
-
* @function getMaxPixelValue
|
|
124
|
-
* @param {Number} value - The max value
|
|
125
|
-
* @param {Array} pixelData - Pixel data array
|
|
126
|
-
*/
|
|
127
|
-
export const getMaxPixelValue = function (
|
|
128
|
-
value: string,
|
|
129
|
-
pixelData: Uint16Array
|
|
130
|
-
) {
|
|
131
|
-
if (value !== undefined) {
|
|
132
|
-
return value;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let max;
|
|
136
|
-
for (let i = 0; i < pixelData.length; i++) {
|
|
137
|
-
if (!max || max < pixelData[i]) {
|
|
138
|
-
max = pixelData[i];
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return max;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Create the pixel representation string (type and length) from dicom tags
|
|
146
|
-
* @instance
|
|
147
|
-
* @function getPixelRepresentation
|
|
148
|
-
* @param {Object} dataSet - The dataset
|
|
149
|
-
* @returns {String} The pixel representation in the form Sint / Uint + bytelength
|
|
150
|
-
*/
|
|
151
|
-
export const getPixelRepresentation = function (dataSet: CustomDataSet) {
|
|
152
|
-
if (dataSet.repr) {
|
|
153
|
-
return dataSet.repr;
|
|
154
|
-
} else {
|
|
155
|
-
// Bits Allocated (0028,0100) defines how much space is allocated
|
|
156
|
-
// in the buffer for every sample in bits.
|
|
157
|
-
let bitsAllocated = getTagValue(dataSet, "x00280100");
|
|
158
|
-
// Pixel Representation (0028,0103) is either unsigned (0) or signed (1).
|
|
159
|
-
// The default is unsigned.
|
|
160
|
-
let pixelRepresentation = getTagValue(dataSet, "x00280103");
|
|
161
|
-
let representation =
|
|
162
|
-
pixelRepresentation === 1
|
|
163
|
-
? "Sint" + bitsAllocated
|
|
164
|
-
: "Uint" + bitsAllocated;
|
|
165
|
-
return representation;
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Get a typed array from a representation type
|
|
171
|
-
* @instance
|
|
172
|
-
* @function getTypedArrayFromDataType
|
|
173
|
-
* @param {Object} dataType - The data type
|
|
174
|
-
* @returns {TypedArray} The typed array
|
|
175
|
-
*/
|
|
176
|
-
export const getTypedArrayFromDataType = function (dataType: string) {
|
|
177
|
-
let repr = dataType.toLowerCase() as keyof typeof TYPES_TO_TYPEDARRAY;
|
|
178
|
-
let typedArray = has(TYPES_TO_TYPEDARRAY, repr)
|
|
179
|
-
? TYPES_TO_TYPEDARRAY[repr]
|
|
180
|
-
: null;
|
|
181
|
-
if (!typedArray) {
|
|
182
|
-
console.error("invalid data type: ", dataType);
|
|
183
|
-
}
|
|
184
|
-
return typedArray;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Sort the array of images ids of a series trying with:
|
|
189
|
-
* - content time order, if the series has cardiacNumberOfImages tag > 1
|
|
190
|
-
* - position order, if series has needed patient position tags
|
|
191
|
-
* - instance order, if series has instance numbers tags
|
|
192
|
-
* The priority of the method depends on the instanceSortPriority value
|
|
193
|
-
* @instance
|
|
194
|
-
* @function getSortedStack
|
|
195
|
-
* @param {Object} seriesData - The dataset
|
|
196
|
-
* @param {Array} sortPriorities - An array which represents the priority tasks
|
|
197
|
-
* @param {Bool} returnSuccessMethod - Boolean for returning the success method
|
|
198
|
-
* @return {Object} The sorted stack
|
|
199
|
-
*/
|
|
200
|
-
export const getSortedStack = function (
|
|
201
|
-
seriesData: Series,
|
|
202
|
-
sortPriorities: Array<"imagePosition" | "contentTime" | "instanceNumber">,
|
|
203
|
-
returnSuccessMethod: boolean
|
|
204
|
-
) {
|
|
205
|
-
let tryToSort = function (
|
|
206
|
-
data: Series,
|
|
207
|
-
methods: typeof sortPriorities
|
|
208
|
-
): string[] {
|
|
209
|
-
if (isEmpty(methods)) {
|
|
210
|
-
if (returnSuccessMethod === true) {
|
|
211
|
-
return sorted!;
|
|
212
|
-
} else {
|
|
213
|
-
return sorted!;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
let sortMethod = methods.shift();
|
|
218
|
-
var sorted = sortBy(data.imageIds, function (imageId: string) {
|
|
219
|
-
return sortStackCallback(data, imageId, sortMethod!);
|
|
220
|
-
});
|
|
221
|
-
if (returnSuccessMethod === true) {
|
|
222
|
-
return sorted;
|
|
223
|
-
} else {
|
|
224
|
-
return sorted;
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
// sortPriorities will be shifted, so clone it before calling the tryToSort fucntion
|
|
229
|
-
let clonedList = clone(sortPriorities);
|
|
230
|
-
return tryToSort(seriesData, clonedList);
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Sort the array of instanceUIDs according to imageIds sorted using sortSeriesStack
|
|
235
|
-
* @instance
|
|
236
|
-
* @function getSortedUIDs
|
|
237
|
-
* @param {Object} seriesData - The dataset
|
|
238
|
-
* @return {Object} The sorted instanceUIDs
|
|
239
|
-
*/
|
|
240
|
-
export const getSortedUIDs = function (seriesData: Series) {
|
|
241
|
-
let instanceUIDs: { [key: string]: string } = {};
|
|
242
|
-
forEach(seriesData.imageIds, function (imageId: string) {
|
|
243
|
-
let instanceUID = seriesData.instances[imageId].metadata.instanceUID!;
|
|
244
|
-
instanceUIDs[instanceUID] = imageId;
|
|
245
|
-
});
|
|
246
|
-
return instanceUIDs;
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Generate a randomUUID in the form 'uy0x2qz9jk9co642cjfus'
|
|
251
|
-
* @instance
|
|
252
|
-
* @function randomId
|
|
253
|
-
* @return {String} - Random uid
|
|
254
|
-
*/
|
|
255
|
-
export const randomId = function () {
|
|
256
|
-
return rand() + rand();
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Get the mean value of a specified dicom tag in a serie
|
|
261
|
-
* @instance
|
|
262
|
-
* @function getMeanValue
|
|
263
|
-
* @param {Object} series - The cornerstone series object
|
|
264
|
-
* @param {Object} tag - The target tag key
|
|
265
|
-
* @param {Bool} isArray - True if tag value is an array
|
|
266
|
-
* @return {Number} - Tag mean value
|
|
267
|
-
*/
|
|
268
|
-
export const getMeanValue = function (
|
|
269
|
-
series: Series,
|
|
270
|
-
tag: keyof MetaData,
|
|
271
|
-
isArray: boolean
|
|
272
|
-
) {
|
|
273
|
-
let meanValue = isArray ? ([] as number[]) : (0 as number);
|
|
274
|
-
|
|
275
|
-
forEach(series.imageIds, function (imageId: string) {
|
|
276
|
-
let tagValue = series.instances[imageId].metadata[
|
|
277
|
-
tag
|
|
278
|
-
] as MetaData[typeof tag];
|
|
279
|
-
if (Array.isArray(tagValue)) {
|
|
280
|
-
tagValue; //exclude array of metadatatypes
|
|
281
|
-
meanValue = meanValue as number[];
|
|
282
|
-
|
|
283
|
-
if (tagValue.length === 2) {
|
|
284
|
-
tagValue = tagValue as [number, number];
|
|
285
|
-
meanValue[0] = meanValue[0] ? meanValue[0] + tagValue[0] : tagValue[0];
|
|
286
|
-
meanValue[1] = meanValue[1] ? meanValue[1] + tagValue[1] : tagValue[1];
|
|
287
|
-
} else if (tagValue.length === 3) {
|
|
288
|
-
tagValue = tagValue as [number, number, number];
|
|
289
|
-
meanValue[0] = meanValue[0] ? meanValue[0] + tagValue[0] : tagValue[0];
|
|
290
|
-
meanValue[1] = meanValue[1] ? meanValue[1] + tagValue[1] : tagValue[1];
|
|
291
|
-
meanValue[2] = meanValue[2] ? meanValue[2] + tagValue[2] : tagValue[2];
|
|
292
|
-
} else if (tagValue.length === 6) {
|
|
293
|
-
tagValue = tagValue as [number, number, number, number, number, number];
|
|
294
|
-
meanValue[0] = meanValue[0] ? meanValue[0] + tagValue[0] : tagValue[0];
|
|
295
|
-
meanValue[1] = meanValue[1] ? meanValue[1] + tagValue[1] : tagValue[1];
|
|
296
|
-
meanValue[2] = meanValue[2] ? meanValue[2] + tagValue[2] : tagValue[2];
|
|
297
|
-
meanValue[3] = meanValue[3] ? meanValue[3] + tagValue[3] : tagValue[3];
|
|
298
|
-
meanValue[4] = meanValue[4] ? meanValue[4] + tagValue[4] : tagValue[4];
|
|
299
|
-
meanValue[5] = meanValue[5] ? meanValue[5] + tagValue[5] : tagValue[5];
|
|
300
|
-
}
|
|
301
|
-
} else {
|
|
302
|
-
meanValue = meanValue as number;
|
|
303
|
-
tagValue = parseFloat(tagValue as string);
|
|
304
|
-
meanValue += tagValue;
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
if (isArray) {
|
|
309
|
-
meanValue = meanValue as number[];
|
|
310
|
-
for (let i = 0; i < meanValue.length; i++) {
|
|
311
|
-
meanValue[i] /= series.imageIds.length;
|
|
312
|
-
}
|
|
313
|
-
} else {
|
|
314
|
-
meanValue = meanValue as number;
|
|
315
|
-
meanValue /= series.imageIds.length;
|
|
316
|
-
}
|
|
317
|
-
return meanValue;
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Compute resliced metadata from a cornerstone data structure
|
|
322
|
-
* @instance
|
|
323
|
-
* @function getReslicedMetadata
|
|
324
|
-
* @param {String} reslicedSeriesId - The id of the resliced serie
|
|
325
|
-
* @param {String} fromOrientation - Source orientation (eg axial, coronal or sagittal)
|
|
326
|
-
* @param {String} toOrientation - Target orientation (eg axial, coronal or sagittal)
|
|
327
|
-
* @param {Object} seriesData - The original series data
|
|
328
|
-
* @param {String} imageLoaderName - The registered loader name
|
|
329
|
-
* @return {Object} - Cornerstone series object, filled only with metadata
|
|
330
|
-
*/
|
|
331
|
-
export const getReslicedMetadata = function (
|
|
332
|
-
reslicedSeriesId: string,
|
|
333
|
-
fromOrientation: "axial" | "coronal" | "sagittal",
|
|
334
|
-
toOrientation: "axial" | "coronal" | "sagittal",
|
|
335
|
-
seriesData: Series,
|
|
336
|
-
imageLoaderName: string
|
|
337
|
-
) {
|
|
338
|
-
// get reslice metadata and apply the reslice algorithm
|
|
339
|
-
let permuteTable = resliceTable[fromOrientation][toOrientation];
|
|
340
|
-
let permuteAbsTable = permuteTable.map(function (v) {
|
|
341
|
-
return Math.abs(v);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// orthogonal reslice algorithm
|
|
345
|
-
let reslicedImageIds: string[] = [];
|
|
346
|
-
let reslicedInstances: { [key: string]: ReslicedInstance } = {};
|
|
347
|
-
|
|
348
|
-
let sampleMetadata = seriesData.instances[seriesData.imageIds[0]].metadata;
|
|
349
|
-
|
|
350
|
-
let fromSize = [
|
|
351
|
-
sampleMetadata.x00280011!,
|
|
352
|
-
sampleMetadata.x00280010!,
|
|
353
|
-
seriesData.imageIds.length
|
|
354
|
-
];
|
|
355
|
-
let toSize = permuteValues(permuteAbsTable, fromSize);
|
|
356
|
-
let fromSpacing = spacingArray(seriesData, sampleMetadata);
|
|
357
|
-
let toSpacing = permuteValues(permuteAbsTable, fromSpacing as number[]);
|
|
358
|
-
let reslicedIOP = getReslicedIOP(sampleMetadata.x00200037!, permuteTable);
|
|
359
|
-
|
|
360
|
-
for (let f = 0; f < toSize[2]; f++) {
|
|
361
|
-
let reslicedImageId = getDicomImageId(imageLoaderName);
|
|
362
|
-
reslicedImageIds.push(reslicedImageId);
|
|
363
|
-
|
|
364
|
-
let instanceId = uuidv4();
|
|
365
|
-
let reslicedIPP = getReslicedIPP(
|
|
366
|
-
sampleMetadata.x00200032 as [number, number, number],
|
|
367
|
-
sampleMetadata.x00200037!,
|
|
368
|
-
reslicedIOP,
|
|
369
|
-
permuteTable,
|
|
370
|
-
f,
|
|
371
|
-
fromSize,
|
|
372
|
-
toSize,
|
|
373
|
-
fromSpacing as number[]
|
|
374
|
-
);
|
|
375
|
-
let metadata: MetaData = extend(clone(sampleMetadata), {
|
|
376
|
-
// pixel representation
|
|
377
|
-
x00280100: sampleMetadata.x00280100,
|
|
378
|
-
x00280103: sampleMetadata.x00280103,
|
|
379
|
-
// resliced series sizes
|
|
380
|
-
x00280010: toSize[1], // rows
|
|
381
|
-
x00280011: toSize[0], // cols
|
|
382
|
-
// resliced series spacing
|
|
383
|
-
x00280030: [toSpacing[1], toSpacing[0]],
|
|
384
|
-
x00180050: [toSpacing[2]],
|
|
385
|
-
// remove min and max pixelvalue from metadata before calling the createCustomImage function:
|
|
386
|
-
// need to recalculate the min and max pixel values on the new instance pixelData
|
|
387
|
-
x00280106: undefined,
|
|
388
|
-
x00280107: undefined,
|
|
389
|
-
// resliced series data
|
|
390
|
-
x0020000d: sampleMetadata.x0020000d,
|
|
391
|
-
x0020000e: reslicedSeriesId,
|
|
392
|
-
x00200011: random(10000),
|
|
393
|
-
x00080018: instanceId,
|
|
394
|
-
x00020003: instanceId,
|
|
395
|
-
x00200013: f + 1,
|
|
396
|
-
x00201041: getReslicedSliceLocation(reslicedIOP, reslicedIPP),
|
|
397
|
-
x00100010: sampleMetadata.x00100010,
|
|
398
|
-
x00081030: sampleMetadata.x00081030,
|
|
399
|
-
x00080020: sampleMetadata.x00080020,
|
|
400
|
-
x00080030: sampleMetadata.x00080030,
|
|
401
|
-
x00080061: sampleMetadata.x00080061,
|
|
402
|
-
x0008103e: sampleMetadata.x0008103e,
|
|
403
|
-
x00080021: sampleMetadata.x00080021,
|
|
404
|
-
x00080031: sampleMetadata.x00080031,
|
|
405
|
-
x00080060: sampleMetadata.x00080060,
|
|
406
|
-
x00280008: sampleMetadata.x00280008,
|
|
407
|
-
x00101010: sampleMetadata.x00101010,
|
|
408
|
-
x00020010: sampleMetadata.x00020010,
|
|
409
|
-
x00200052: sampleMetadata.x00200052,
|
|
410
|
-
// data needed to obtain a good rendering
|
|
411
|
-
x00281050: sampleMetadata.x00281050,
|
|
412
|
-
x00281051: sampleMetadata.x00281051,
|
|
413
|
-
x00281052: sampleMetadata.x00281052,
|
|
414
|
-
x00281053: sampleMetadata.x00281053,
|
|
415
|
-
// new image orientation
|
|
416
|
-
x00200037: reslicedIOP,
|
|
417
|
-
// new image position
|
|
418
|
-
x00200032: reslicedIPP
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// set human readable metadata.
|
|
422
|
-
metadata.seriesUID = reslicedSeriesId;
|
|
423
|
-
metadata.rows = metadata.x00280010;
|
|
424
|
-
metadata.cols = metadata.x00280011;
|
|
425
|
-
metadata.imageOrientation = metadata.x00200037;
|
|
426
|
-
metadata.imagePosition = metadata.x00200032;
|
|
427
|
-
metadata.pixelSpacing = metadata.x00280030;
|
|
428
|
-
metadata.instanceUID = metadata.x00080018;
|
|
429
|
-
metadata.minPixelValue = metadata.x00280106;
|
|
430
|
-
metadata.maxPixelValue = metadata.x00280107;
|
|
431
|
-
metadata.sliceThickness = toSpacing[2];
|
|
432
|
-
|
|
433
|
-
reslicedInstances[reslicedImageId] = {
|
|
434
|
-
instanceId: instanceId,
|
|
435
|
-
metadata: metadata,
|
|
436
|
-
permuteTable: permuteTable
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
imageIds: reslicedImageIds,
|
|
442
|
-
instances: reslicedInstances,
|
|
443
|
-
currentImageIdIndex: 0
|
|
444
|
-
};
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Compute cmpr metadata from pyCmpr data (generated using Scyther {@link https://github.com/dvisionlab/Scyther})
|
|
449
|
-
* @instance
|
|
450
|
-
* @function getCmprMetadata
|
|
451
|
-
* @param {String} reslicedSeriesId - The id of the resliced serie
|
|
452
|
-
* @param {String} imageLoaderName - The registered loader name
|
|
453
|
-
* @param {Object} header - The header of the resliced serie from Scyther
|
|
454
|
-
* @return {Object} - Cornerstone series object, filled only with metadata
|
|
455
|
-
*/
|
|
456
|
-
export const getCmprMetadata = function (
|
|
457
|
-
reslicedSeriesId: string,
|
|
458
|
-
imageLoaderName: string,
|
|
459
|
-
header: any // TODO-ts : type
|
|
460
|
-
) {
|
|
461
|
-
let reslicedImageIds: string[] = [];
|
|
462
|
-
let reslicedInstances: { [key: string]: ReslicedInstance } = {};
|
|
463
|
-
|
|
464
|
-
for (let f = 0; f < header.frames_number; f++) {
|
|
465
|
-
let reslicedImageId = getDicomImageId(imageLoaderName);
|
|
466
|
-
reslicedImageIds.push(reslicedImageId);
|
|
467
|
-
|
|
468
|
-
let instanceId = uuidv4();
|
|
469
|
-
|
|
470
|
-
let metadata: MetaData = {
|
|
471
|
-
// pixel representation
|
|
472
|
-
x00280100: header.repr,
|
|
473
|
-
// Bits Allocated
|
|
474
|
-
x00280103: header.repr,
|
|
475
|
-
// resliced series sizes
|
|
476
|
-
x00280010: header.rows, // rows
|
|
477
|
-
x00280011: header.cols, // cols
|
|
478
|
-
// resliced series spacing
|
|
479
|
-
x00280030: [header.spacing[1], header.spacing[0]],
|
|
480
|
-
x00180050: [header.distance_btw_slices] as number[],
|
|
481
|
-
// remove min and max pixelvalue from metadata before calling the createCustomImage function:
|
|
482
|
-
// need to recalculate the min and max pixel values on the new instance pixelData
|
|
483
|
-
x00280106: undefined,
|
|
484
|
-
x00280107: undefined,
|
|
485
|
-
// resliced series data
|
|
486
|
-
// x0020000d: sampleMetadata.x0020000d, //Study Instance UID
|
|
487
|
-
x0020000e: reslicedSeriesId,
|
|
488
|
-
x00200011: random(10000),
|
|
489
|
-
x00080018: instanceId,
|
|
490
|
-
x00020003: instanceId,
|
|
491
|
-
x00200013: f + 1,
|
|
492
|
-
// x00201041: getReslicedSliceLocation(reslicedIOP, reslicedIPP), // Slice Location
|
|
493
|
-
// x00100010: sampleMetadata.x00100010,
|
|
494
|
-
// x00081030: sampleMetadata.x00081030,
|
|
495
|
-
// x00080020: sampleMetadata.x00080020,
|
|
496
|
-
// x00080030: sampleMetadata.x00080030,
|
|
497
|
-
// x00080061: sampleMetadata.x00080061,
|
|
498
|
-
// x0008103e: sampleMetadata.x0008103e,
|
|
499
|
-
// x00080021: sampleMetadata.x00080021,
|
|
500
|
-
// x00080031: sampleMetadata.x00080031,
|
|
501
|
-
// x00080060: sampleMetadata.x00080060,
|
|
502
|
-
// x00280008: sampleMetadata.x00280008,
|
|
503
|
-
// x00101010: sampleMetadata.x00101010,
|
|
504
|
-
// x00020010: sampleMetadata.x00020010,
|
|
505
|
-
// x00200052: sampleMetadata.x00200052,
|
|
506
|
-
// data needed to obtain a good rendering
|
|
507
|
-
x00281050: [header.wwwl[1] / 2], // [wl]
|
|
508
|
-
x00281051: [header.wwwl[0]], // [ww]
|
|
509
|
-
x00281052: header.intercept,
|
|
510
|
-
x00281053: header.slope,
|
|
511
|
-
// new image orientation (IOP)
|
|
512
|
-
x00200037: header.iop ? header.iop.slice(f * 6, (f + 1) * 6) : null,
|
|
513
|
-
// new image position (IPP)
|
|
514
|
-
x00200032: header.ipp ? header.ipp.slice(f * 3, (f + 1) * 3) : null
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
reslicedInstances[reslicedImageId] = {
|
|
518
|
-
instanceId: instanceId,
|
|
519
|
-
metadata: metadata
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return {
|
|
524
|
-
imageIds: reslicedImageIds,
|
|
525
|
-
instances: reslicedInstances
|
|
526
|
-
};
|
|
527
|
-
};
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* Get pixel data for a single resliced slice, from cornerstone data structure
|
|
531
|
-
* @instance
|
|
532
|
-
* @function getReslicedPixeldata
|
|
533
|
-
* @param {String} imageId - The id of the resulting image
|
|
534
|
-
* @param {Object} originalData - The original series data (source)
|
|
535
|
-
* @param {Object} reslicedData - The resliced series data (target)
|
|
536
|
-
* @return {Object} - A single resliced slice pixel array
|
|
537
|
-
*/
|
|
538
|
-
export const getReslicedPixeldata = function (
|
|
539
|
-
imageId: string,
|
|
540
|
-
originalData: Series,
|
|
541
|
-
reslicedData: Series
|
|
542
|
-
) {
|
|
543
|
-
// resliced metadata must be already available
|
|
544
|
-
let reslicedInstance = reslicedData.instances[imageId] as ReslicedInstance;
|
|
545
|
-
let reslicedMetadata = reslicedInstance.metadata;
|
|
546
|
-
if (!reslicedInstance.permuteTable) {
|
|
547
|
-
throw new Error("Resliced permuteTable not available");
|
|
548
|
-
}
|
|
549
|
-
let permuteAbsTable = reslicedInstance.permuteTable.map(function (v) {
|
|
550
|
-
return Math.abs(v);
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// compute resliced series pixelData, use the correct typedarray
|
|
554
|
-
let rows = reslicedMetadata.x00280010 as number;
|
|
555
|
-
let cols = reslicedMetadata.x00280011 as number;
|
|
556
|
-
let reslicedSlice = getTypedArray(reslicedMetadata as any, rows * cols); // TODO-ts : type of reslicedMetadata?
|
|
557
|
-
|
|
558
|
-
let frame = indexOf(reslicedData.imageIds, imageId);
|
|
559
|
-
let originalInstance = originalData.instances[originalData.imageIds[0]];
|
|
560
|
-
let fromCols = originalInstance.metadata.x00280011 as number;
|
|
561
|
-
|
|
562
|
-
function getPixelValue(ijf: [number, number, number]) {
|
|
563
|
-
let i = ijf[0];
|
|
564
|
-
let j = ijf[1];
|
|
565
|
-
let f = ijf[2];
|
|
566
|
-
|
|
567
|
-
let cachedImage = find(cornerstone.imageCache.cachedImages, [
|
|
568
|
-
"imageId",
|
|
569
|
-
originalData.imageIds[f]
|
|
570
|
-
]);
|
|
571
|
-
let targetPixeldata = cachedImage.image.getPixelData();
|
|
572
|
-
let index = j * fromCols + i;
|
|
573
|
-
return targetPixeldata[index];
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// flip f values
|
|
577
|
-
if (isNegativeSign(reslicedInstance.permuteTable[2])) {
|
|
578
|
-
frame = reslicedData.imageIds.length - frame;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
for (let j = 0; j < rows; j++) {
|
|
582
|
-
for (let i = 0; i < cols; i++) {
|
|
583
|
-
let ijf: [number, number, number] = [0, 0, 0];
|
|
584
|
-
ijf[permuteAbsTable[0]] = i;
|
|
585
|
-
ijf[permuteAbsTable[1]] = j;
|
|
586
|
-
ijf[permuteAbsTable[2]] = frame;
|
|
587
|
-
|
|
588
|
-
// flip j index
|
|
589
|
-
let index;
|
|
590
|
-
if (isNegativeSign(reslicedInstance.permuteTable[1])) {
|
|
591
|
-
index = rows * cols - j * cols + i;
|
|
592
|
-
} else {
|
|
593
|
-
// let i_padded = Math.floor(i * originalSampleMetadata.x00180050 / originalSampleMetadata.x00280030[0]);
|
|
594
|
-
index = j * cols + i;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
reslicedSlice[index] = getPixelValue(ijf);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
return reslicedSlice;
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
/**
|
|
604
|
-
* Get distance between two slices
|
|
605
|
-
* @instance
|
|
606
|
-
* @function getDistanceBetweenSlices
|
|
607
|
-
* @param {Object} seriesData - The series data
|
|
608
|
-
* @param {Number} sliceIndex1 - The first slice index
|
|
609
|
-
* @param {Number} sliceIndex2 - The second slice index
|
|
610
|
-
* @return {Number} - The distance value
|
|
611
|
-
*/
|
|
612
|
-
export const getDistanceBetweenSlices = function (
|
|
613
|
-
seriesData: Series,
|
|
614
|
-
sliceIndex1: number,
|
|
615
|
-
sliceIndex2: number
|
|
616
|
-
) {
|
|
617
|
-
if (seriesData.imageIds.length <= 1) {
|
|
618
|
-
return 0;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
let imageId1 = seriesData.imageIds[sliceIndex1];
|
|
622
|
-
let instance1 = seriesData.instances[imageId1];
|
|
623
|
-
let metadata1 = instance1.metadata;
|
|
624
|
-
let imageOrientation = metadata1.x00200037;
|
|
625
|
-
let imagePosition = metadata1.x00200032 as [number, number, number];
|
|
626
|
-
|
|
627
|
-
if (imageOrientation && imagePosition) {
|
|
628
|
-
let normal = getNormalOrientation(imageOrientation);
|
|
629
|
-
let d1 =
|
|
630
|
-
normal[0] * imagePosition[0] +
|
|
631
|
-
normal[1] * imagePosition[1] +
|
|
632
|
-
normal[2] * imagePosition[2];
|
|
633
|
-
|
|
634
|
-
let imageId2 = seriesData.imageIds[sliceIndex2];
|
|
635
|
-
let instance2 = seriesData.instances[imageId2];
|
|
636
|
-
let metadata2 = instance2.metadata;
|
|
637
|
-
let imagePosition2 = metadata2.x00200032!;
|
|
638
|
-
|
|
639
|
-
let d2 =
|
|
640
|
-
normal[0] * imagePosition2[0] +
|
|
641
|
-
normal[1] * imagePosition2[1] +
|
|
642
|
-
normal[2] * imagePosition2[2]!;
|
|
643
|
-
|
|
644
|
-
return Math.abs(d1 - d2);
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
/**
|
|
649
|
-
* @instance
|
|
650
|
-
* @function getImageMetadata
|
|
651
|
-
* @param {String} seriesId - The seriesUID
|
|
652
|
-
* @param {String} instanceUID - The SOPInstanceUID
|
|
653
|
-
* @return {Array} - List of metadata objects: tag, name and value
|
|
654
|
-
*/
|
|
655
|
-
export const getImageMetadata = function (
|
|
656
|
-
seriesId: string,
|
|
657
|
-
instanceUID: string
|
|
658
|
-
) {
|
|
659
|
-
const seriesData = getSeriesDataFromLarvitarManager(seriesId);
|
|
660
|
-
if (seriesData === undefined || seriesData === null) {
|
|
661
|
-
console.log(`Invalid Series ID: ${seriesId}`);
|
|
662
|
-
return [];
|
|
663
|
-
}
|
|
664
|
-
const imageId = seriesData.instanceUIDs[instanceUID];
|
|
665
|
-
if (imageId === undefined) {
|
|
666
|
-
console.log(`Invalid InstanceUID ID: ${instanceUID}`);
|
|
667
|
-
return [];
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
let metadata = seriesData.instances[imageId].metadata;
|
|
671
|
-
// get elements from metadata where the key starts with x and is length 7
|
|
672
|
-
let metadata_keys = Object.keys(metadata);
|
|
673
|
-
// loop metadata using metadata_keys and return list of key value pairs
|
|
674
|
-
let metadata_list = map(metadata_keys, function (key: string) {
|
|
675
|
-
// if value is a dictionary return empty string
|
|
676
|
-
let KEY = key as keyof MetaDataTypes;
|
|
677
|
-
const value =
|
|
678
|
-
metadata[KEY] && metadata[KEY]!.constructor == Object
|
|
679
|
-
? ""
|
|
680
|
-
: metadata[KEY];
|
|
681
|
-
// convert key removing x and adding comma at position 4
|
|
682
|
-
const tagKey = (
|
|
683
|
-
"(" +
|
|
684
|
-
key.slice(1, 5) +
|
|
685
|
-
"," +
|
|
686
|
-
key.slice(5) +
|
|
687
|
-
")"
|
|
688
|
-
).toUpperCase();
|
|
689
|
-
|
|
690
|
-
if (!Object.keys(TAG_DICT).includes(tagKey)) {
|
|
691
|
-
console.debug(`Invalid tag key: ${tagKey}`);
|
|
692
|
-
}
|
|
693
|
-
// force type to keyof typeof TAG_DICT after having checked that it is a valid key
|
|
694
|
-
const name = TAG_DICT[tagKey as keyof typeof TAG_DICT]
|
|
695
|
-
? TAG_DICT[tagKey as keyof typeof TAG_DICT].name
|
|
696
|
-
: "";
|
|
697
|
-
return {
|
|
698
|
-
tag: tagKey,
|
|
699
|
-
name: name,
|
|
700
|
-
value: value
|
|
701
|
-
};
|
|
702
|
-
});
|
|
703
|
-
return metadata_list;
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
/* Internal module functions */
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Returns the sorting value of the image id in the array of image ids
|
|
710
|
-
* of the series according with the chosen sorting method
|
|
711
|
-
* @instance
|
|
712
|
-
* @function sortStackCallback
|
|
713
|
-
* @param {Object} seriesData - The original series data
|
|
714
|
-
* @param {String} imageId - The id of the target image
|
|
715
|
-
* @param {String} method - Orientation target
|
|
716
|
-
* @return {Number} - The sorting value (float)
|
|
717
|
-
*/
|
|
718
|
-
let sortStackCallback = function (
|
|
719
|
-
seriesData: Series,
|
|
720
|
-
imageId: string,
|
|
721
|
-
method: "instanceNumber" | "contentTime" | "imagePosition"
|
|
722
|
-
) {
|
|
723
|
-
switch (method) {
|
|
724
|
-
case "instanceNumber":
|
|
725
|
-
var instanceNumber = seriesData.instances[imageId].metadata.x00200013!;
|
|
726
|
-
return instanceNumber;
|
|
727
|
-
|
|
728
|
-
case "contentTime":
|
|
729
|
-
return seriesData.instances[imageId].metadata.x00080033;
|
|
730
|
-
|
|
731
|
-
case "imagePosition":
|
|
732
|
-
let p = seriesData.instances[imageId].metadata.imagePosition!;
|
|
733
|
-
let pStr = p?.map(String);
|
|
734
|
-
let o = seriesData.instances[imageId].metadata.imageOrientation!;
|
|
735
|
-
let oStr = o?.map(String);
|
|
736
|
-
|
|
737
|
-
var v1, v2, v3: number;
|
|
738
|
-
v1 = o[0] * o[0] + o[3] * o[3];
|
|
739
|
-
v2 = o[1] * o[1] + o[4] * o[4];
|
|
740
|
-
v3 = o[2] * o[2] + o[5] * o[5];
|
|
741
|
-
|
|
742
|
-
var sortIndex = -1;
|
|
743
|
-
if (v1 <= v2 && v2 <= v3) {
|
|
744
|
-
sortIndex = 0;
|
|
745
|
-
}
|
|
746
|
-
if (v2 <= v1 && v2 <= v3) {
|
|
747
|
-
sortIndex = 1;
|
|
748
|
-
}
|
|
749
|
-
if (v3 <= v1 && v3 <= v2) {
|
|
750
|
-
sortIndex = 2;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
if (!sortIndex) {
|
|
754
|
-
throw new Error("Invalid sort index");
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
return p[sortIndex];
|
|
758
|
-
default:
|
|
759
|
-
break;
|
|
760
|
-
}
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
/**
|
|
764
|
-
* Generate a random number and convert it to base 36 (0-9a-z)
|
|
765
|
-
* @instance
|
|
766
|
-
* @function rand
|
|
767
|
-
* @return {Number} - base36 random number
|
|
768
|
-
*/
|
|
769
|
-
let rand = function () {
|
|
770
|
-
return Math.random().toString(36).substr(2);
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* Permute array values using orientation array
|
|
775
|
-
* @instance
|
|
776
|
-
* @function permuteValues
|
|
777
|
-
* @param {Array} convertArray - The orientation array
|
|
778
|
-
* @param {Array} sourceArray - The source array
|
|
779
|
-
* @return {Array} - The converted array
|
|
780
|
-
*/
|
|
781
|
-
let permuteValues = function (convertArray: number[], sourceArray: number[]) {
|
|
782
|
-
let outputArray = new Array(convertArray.length);
|
|
783
|
-
for (let i = 0; i < convertArray.length; i++) {
|
|
784
|
-
outputArray[i] = sourceArray[convertArray[i]];
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
return outputArray;
|
|
788
|
-
};
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
* Check negative sign, considering also 0+ and 0-
|
|
792
|
-
* @instance
|
|
793
|
-
* @function isNegativeSign
|
|
794
|
-
* @param {Number} x - The number to check
|
|
795
|
-
* @return {Boolean} - Is negative boolean response
|
|
796
|
-
*/
|
|
797
|
-
let isNegativeSign = function (x: number) {
|
|
798
|
-
return 1 / x !== 1 / Math.abs(x);
|
|
799
|
-
};
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Get typed array from tag and size of original array
|
|
803
|
-
* @instance
|
|
804
|
-
* @function getTypedArray
|
|
805
|
-
* @param {String} tag - The DICOM tag used for pixel representation
|
|
806
|
-
* @param {Number} size - The size of the array
|
|
807
|
-
* @return {Array} - The typed array
|
|
808
|
-
*/
|
|
809
|
-
let getTypedArray = function (tags: CustomDataSet, size: number) {
|
|
810
|
-
let r = getPixelRepresentation(tags);
|
|
811
|
-
let typedArray = getTypedArrayFromDataType(r);
|
|
812
|
-
if (!typedArray) {
|
|
813
|
-
throw new Error("Invalid typed array");
|
|
814
|
-
}
|
|
815
|
-
return new typedArray(size);
|
|
816
|
-
};
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* Get resliced image orientation tag from permuteTable
|
|
820
|
-
* @instance
|
|
821
|
-
* @function getReslicedIOP
|
|
822
|
-
* @param {Array} iop - The image orientation array
|
|
823
|
-
* @param {Array} permuteTable - The matrix transformation
|
|
824
|
-
* @return {Array} - The resliced image orientation array
|
|
825
|
-
*/
|
|
826
|
-
let getReslicedIOP = function (
|
|
827
|
-
iop: [number, number, number, number, number, number],
|
|
828
|
-
permuteTable: number[]
|
|
829
|
-
) {
|
|
830
|
-
if (!iop) {
|
|
831
|
-
return null;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// compute resliced iop
|
|
835
|
-
let u = iop.slice(0, 3);
|
|
836
|
-
let v = iop.slice(3, 6);
|
|
837
|
-
|
|
838
|
-
// abs the w array, the sign will be eventually changed during the permutation
|
|
839
|
-
let w = getNormalOrientation(iop);
|
|
840
|
-
// let absW = _.map(w, function(v) { return Math.abs(v); });
|
|
841
|
-
|
|
842
|
-
// resliced iop components
|
|
843
|
-
let shuffledIop = permuteSignedArrays(permuteTable, [u, v, w]);
|
|
844
|
-
|
|
845
|
-
// keep the firts two components of shuffledIop
|
|
846
|
-
return shuffledIop[0].concat(shuffledIop[1]);
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Get resliced image position tag from permuteTable
|
|
851
|
-
* @instance
|
|
852
|
-
* @function getReslicedIPP
|
|
853
|
-
* @param {Array} iop - The image position array
|
|
854
|
-
* @param {Array} iop - The image orientation array
|
|
855
|
-
* @param {Array} reslicedIOP - The resliced image orientation array
|
|
856
|
-
* @param {Array} permuteTable - The matrix transformation
|
|
857
|
-
* @param {Number} imageIndex - The index of the image
|
|
858
|
-
* @param {Array} fromSize - The array of source image dimension
|
|
859
|
-
* @param {Array} toSize - The array of target image dimension
|
|
860
|
-
* @param {Array} fromSpacing - The spacing array
|
|
861
|
-
* @return {Array} - The resliced image position array
|
|
862
|
-
*/
|
|
863
|
-
let getReslicedIPP = function (
|
|
864
|
-
ipp: [number, number, number],
|
|
865
|
-
iop: [number, number, number, number, number, number],
|
|
866
|
-
reslicedIOP: [number, number, number, number, number, number],
|
|
867
|
-
permuteTable: number[],
|
|
868
|
-
imageIndex: number,
|
|
869
|
-
fromSize: number[],
|
|
870
|
-
toSize: number[],
|
|
871
|
-
fromSpacing: number[]
|
|
872
|
-
) {
|
|
873
|
-
// compute resliced ipp
|
|
874
|
-
let reslicedIPP = [];
|
|
875
|
-
|
|
876
|
-
// iop data types??
|
|
877
|
-
let u = iop.slice(0, 3);
|
|
878
|
-
let v = iop.slice(3, 6);
|
|
879
|
-
let w = getNormalOrientation(iop);
|
|
880
|
-
let absW = map(w, function (v: number) {
|
|
881
|
-
return Math.abs(v);
|
|
882
|
-
});
|
|
883
|
-
let majorOriginalIndex = indexOf(absW, max(absW));
|
|
884
|
-
|
|
885
|
-
let normalReslicedIop = getNormalOrientation(reslicedIOP);
|
|
886
|
-
normalReslicedIop = map(normalReslicedIop, function (v: number) {
|
|
887
|
-
return Math.abs(v);
|
|
888
|
-
});
|
|
889
|
-
|
|
890
|
-
let majorIndex = indexOf(normalReslicedIop, max(normalReslicedIop));
|
|
891
|
-
let index = isNegativeSign(permuteTable[majorIndex])
|
|
892
|
-
? toSize[majorIndex] - imageIndex
|
|
893
|
-
: imageIndex;
|
|
894
|
-
|
|
895
|
-
// flip z value on original slice
|
|
896
|
-
if (isNegativeSign(permuteTable[1])) {
|
|
897
|
-
ipp = ipp.map(function (val, i) {
|
|
898
|
-
return val + fromSize[2] * fromSpacing[2] * w[i];
|
|
899
|
-
}) as [number, number, number];
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
let spacing: number;
|
|
903
|
-
let versor: number[];
|
|
904
|
-
// to sagittal
|
|
905
|
-
if (majorIndex == 0) {
|
|
906
|
-
// original x spacing
|
|
907
|
-
spacing = fromSpacing[0];
|
|
908
|
-
versor = u;
|
|
909
|
-
}
|
|
910
|
-
// to coronal
|
|
911
|
-
else if (majorIndex == 1) {
|
|
912
|
-
// from sagittal
|
|
913
|
-
if (majorOriginalIndex == 0) {
|
|
914
|
-
spacing = fromSpacing[0];
|
|
915
|
-
versor = u;
|
|
916
|
-
|
|
917
|
-
// overwrite index with the majorOriginalIndex position
|
|
918
|
-
// index = isNegativeSign(permuteTable[majorOriginalIndex]) ? (toSize[majorOriginalIndex] - imageIndex) : imageIndex;
|
|
919
|
-
}
|
|
920
|
-
// from axial
|
|
921
|
-
else if (majorOriginalIndex == 2) {
|
|
922
|
-
spacing = fromSpacing[1];
|
|
923
|
-
versor = v;
|
|
924
|
-
|
|
925
|
-
// overwrite index with the majorOriginalIndex position
|
|
926
|
-
index = isNegativeSign(permuteTable[majorOriginalIndex])
|
|
927
|
-
? toSize[majorOriginalIndex] - imageIndex
|
|
928
|
-
: imageIndex;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
// to axial
|
|
932
|
-
else if (majorIndex == 2) {
|
|
933
|
-
// original y spacing
|
|
934
|
-
spacing = fromSpacing[1];
|
|
935
|
-
versor = v;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
reslicedIPP = ipp.map(function (val, i) {
|
|
939
|
-
return val + index * spacing * versor[i];
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
return reslicedIPP as [number, number, number];
|
|
943
|
-
};
|
|
944
|
-
|
|
945
|
-
/**
|
|
946
|
-
* Get resliced normal orientation vector
|
|
947
|
-
* @instance
|
|
948
|
-
* @function getReslicedSliceLocation
|
|
949
|
-
* @param {Array} reslicedIOP - The resliced image orientation array
|
|
950
|
-
* @param {Array} reslicedIPP - The resliced image position array
|
|
951
|
-
* @return {Array} - The slice location as normal orientation vector
|
|
952
|
-
*/
|
|
953
|
-
let getReslicedSliceLocation = function (
|
|
954
|
-
reslicedIOP: [number, number, number, number, number, number],
|
|
955
|
-
reslicedIPP: [number, number, number]
|
|
956
|
-
) {
|
|
957
|
-
let normalReslicedIop = getNormalOrientation(reslicedIOP);
|
|
958
|
-
normalReslicedIop = map(normalReslicedIop, function (v: number) {
|
|
959
|
-
return Math.abs(v);
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
let majorIndex = indexOf(normalReslicedIop, max(normalReslicedIop));
|
|
963
|
-
return reslicedIPP[majorIndex];
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
/**
|
|
967
|
-
* Get spacing array from seriesData
|
|
968
|
-
* @instance
|
|
969
|
-
* @function spacingArray
|
|
970
|
-
* @param {Object} seriesData - The original series data
|
|
971
|
-
* @param {Object} sampleMetadata - The medatata object
|
|
972
|
-
* @return {Array} - The spacing array
|
|
973
|
-
*/
|
|
974
|
-
let spacingArray = function (
|
|
975
|
-
seriesData: Series,
|
|
976
|
-
sampleMetadata: MetaDataTypes
|
|
977
|
-
) {
|
|
978
|
-
// the spacingArray is as follows:
|
|
979
|
-
// [0]: column pixelSpacing value (x00280030[1])
|
|
980
|
-
// [1]: row pixelSpacing value (x00280030[0])
|
|
981
|
-
// [2]: distance between slices, given the series imageOrientationPatient and
|
|
982
|
-
// imagePositionPatient of the first two slices
|
|
983
|
-
|
|
984
|
-
let distanceBetweenSlices = sampleMetadata["x00180050"]
|
|
985
|
-
? sampleMetadata["x00180050"]
|
|
986
|
-
: getDistanceBetweenSlices(seriesData, 0, 1);
|
|
987
|
-
|
|
988
|
-
let spacing = sampleMetadata.x00280030!;
|
|
989
|
-
|
|
990
|
-
return [spacing[1], spacing[0], distanceBetweenSlices as number];
|
|
991
|
-
};
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Permute an array
|
|
995
|
-
* @instance
|
|
996
|
-
* @function permuteSignedArrays
|
|
997
|
-
* @param {Array} convertArray - The array used to convert source array
|
|
998
|
-
* @param {Array} sourceArray - The source array
|
|
999
|
-
* @return {Array} - The permuted array array
|
|
1000
|
-
*/
|
|
1001
|
-
let permuteSignedArrays = function (
|
|
1002
|
-
convertArray: number[],
|
|
1003
|
-
sourceArray: number[][]
|
|
1004
|
-
) {
|
|
1005
|
-
let outputArray = new Array(convertArray.length);
|
|
1006
|
-
for (let i = 0; i < convertArray.length; i++) {
|
|
1007
|
-
let sourceIndex = Math.abs(convertArray[i]);
|
|
1008
|
-
if (isNegativeSign(convertArray[i])) {
|
|
1009
|
-
outputArray[i] = sourceArray[sourceIndex].map(function (v) {
|
|
1010
|
-
return -v;
|
|
1011
|
-
});
|
|
1012
|
-
} else {
|
|
1013
|
-
outputArray[i] = sourceArray[sourceIndex];
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
return outputArray;
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
/**
|
|
1021
|
-
* Object used to convert data type to typed array
|
|
1022
|
-
* @object
|
|
1023
|
-
*/
|
|
1024
|
-
const TYPES_TO_TYPEDARRAY = {
|
|
1025
|
-
"unsigned char": Uint8Array,
|
|
1026
|
-
uchar: Uint8Array,
|
|
1027
|
-
uint8: Uint8Array,
|
|
1028
|
-
uint8_t: Uint8Array,
|
|
1029
|
-
|
|
1030
|
-
sint8: Int8Array,
|
|
1031
|
-
"signed char": Int8Array,
|
|
1032
|
-
int8: Int8Array,
|
|
1033
|
-
int8_t: Int8Array,
|
|
1034
|
-
|
|
1035
|
-
ushort: Uint16Array,
|
|
1036
|
-
"unsigned short": Uint16Array,
|
|
1037
|
-
"unsigned short int": Uint16Array,
|
|
1038
|
-
uint16: Uint16Array,
|
|
1039
|
-
uint16_t: Uint16Array,
|
|
1040
|
-
|
|
1041
|
-
sint16: Int16Array,
|
|
1042
|
-
short: Int16Array,
|
|
1043
|
-
"short int": Int16Array,
|
|
1044
|
-
"signed short": Int16Array,
|
|
1045
|
-
"signed short int": Int16Array,
|
|
1046
|
-
int16: Int16Array,
|
|
1047
|
-
int16_t: Int16Array,
|
|
1048
|
-
|
|
1049
|
-
sint32: Int32Array,
|
|
1050
|
-
int: Int32Array,
|
|
1051
|
-
"signed int": Int32Array,
|
|
1052
|
-
int32: Int32Array,
|
|
1053
|
-
int32_t: Int32Array,
|
|
1054
|
-
|
|
1055
|
-
uint: Uint32Array,
|
|
1056
|
-
"unsigned int": Uint32Array,
|
|
1057
|
-
uint32: Uint32Array,
|
|
1058
|
-
uint32_t: Uint32Array,
|
|
1059
|
-
|
|
1060
|
-
float: Float32Array,
|
|
1061
|
-
double: Float64Array
|
|
1062
|
-
};
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* Check if a div tag is a valid DOM HTMLElement
|
|
1066
|
-
* @instance
|
|
1067
|
-
* @function isElement
|
|
1068
|
-
* @param {Object} o - The div tag
|
|
1069
|
-
* @return {Boolean} - True if is an element otherwise returns False
|
|
1070
|
-
*/
|
|
1071
|
-
export const isElement = function (o: any) {
|
|
1072
|
-
return typeof HTMLElement === "object"
|
|
1073
|
-
? o instanceof HTMLElement //DOM2
|
|
1074
|
-
: o &&
|
|
1075
|
-
typeof o === "object" &&
|
|
1076
|
-
o !== null &&
|
|
1077
|
-
o.nodeType === 1 &&
|
|
1078
|
-
typeof o.nodeName === "string";
|
|
1079
|
-
};
|