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.
Files changed (72) hide show
  1. package/README.md +2 -2
  2. package/dist/larvitar.js +5 -3
  3. package/dist/larvitar.js.map +1 -1
  4. package/package.json +6 -2
  5. package/.github/workflows/build-docs.yml +0 -59
  6. package/.github/workflows/codeql-analysis.yml +0 -71
  7. package/.github/workflows/deploy.yml +0 -37
  8. package/.vscode/settings.json +0 -4
  9. package/CODE_OF_CONDUCT.md +0 -76
  10. package/MIGRATION.md +0 -25
  11. package/bundler/webpack.common.js +0 -27
  12. package/bundler/webpack.dev.js +0 -23
  13. package/bundler/webpack.prod.js +0 -19
  14. package/decs.d.ts +0 -12
  15. package/imaging/MetaDataReadable.ts +0 -42
  16. package/imaging/MetaDataTypes.ts +0 -3491
  17. package/imaging/dataDictionary.json +0 -21866
  18. package/imaging/imageAnonymization.ts +0 -135
  19. package/imaging/imageColormaps.ts +0 -217
  20. package/imaging/imageContours.ts +0 -196
  21. package/imaging/imageIo.ts +0 -251
  22. package/imaging/imageLayers.ts +0 -121
  23. package/imaging/imageLoading.ts +0 -299
  24. package/imaging/imageParsing.ts +0 -444
  25. package/imaging/imagePresets.ts +0 -156
  26. package/imaging/imageRendering.ts +0 -1091
  27. package/imaging/imageReslice.ts +0 -87
  28. package/imaging/imageStore.ts +0 -487
  29. package/imaging/imageTags.ts +0 -609
  30. package/imaging/imageTools.js +0 -708
  31. package/imaging/imageUtils.ts +0 -1079
  32. package/imaging/loaders/commonLoader.ts +0 -275
  33. package/imaging/loaders/dicomLoader.ts +0 -66
  34. package/imaging/loaders/fileLoader.ts +0 -71
  35. package/imaging/loaders/multiframeLoader.ts +0 -435
  36. package/imaging/loaders/nrrdLoader.ts +0 -630
  37. package/imaging/loaders/resliceLoader.ts +0 -205
  38. package/imaging/monitors/memory.ts +0 -151
  39. package/imaging/monitors/performance.ts +0 -34
  40. package/imaging/parsers/ecg.ts +0 -54
  41. package/imaging/parsers/nrrd.js +0 -485
  42. package/imaging/tools/README.md +0 -27
  43. package/imaging/tools/custom/4dSliceScrollTool.js +0 -146
  44. package/imaging/tools/custom/BorderMagnifyTool.js +0 -99
  45. package/imaging/tools/custom/contourTool.js +0 -1884
  46. package/imaging/tools/custom/diameterTool.js +0 -141
  47. package/imaging/tools/custom/editMaskTool.js +0 -141
  48. package/imaging/tools/custom/ellipticalRoiOverlayTool.js +0 -534
  49. package/imaging/tools/custom/polygonSegmentationMixin.js +0 -245
  50. package/imaging/tools/custom/polylineScissorsTool.js +0 -59
  51. package/imaging/tools/custom/rectangleRoiOverlayTool.js +0 -564
  52. package/imaging/tools/custom/seedTool.js +0 -342
  53. package/imaging/tools/custom/setLabelMap3D.ts +0 -242
  54. package/imaging/tools/custom/thresholdsBrushTool.js +0 -161
  55. package/imaging/tools/default.ts +0 -594
  56. package/imaging/tools/interaction.ts +0 -266
  57. package/imaging/tools/io.ts +0 -229
  58. package/imaging/tools/main.ts +0 -424
  59. package/imaging/tools/segmentation.ts +0 -532
  60. package/imaging/tools/segmentations.md +0 -38
  61. package/imaging/tools/state.ts +0 -74
  62. package/imaging/tools/strategies/eraseFreehand.js +0 -76
  63. package/imaging/tools/strategies/fillFreehand.js +0 -79
  64. package/imaging/tools/strategies/index.js +0 -2
  65. package/imaging/tools/types.d.ts +0 -243
  66. package/imaging/types.d.ts +0 -200
  67. package/imaging/waveforms/ecg.ts +0 -191
  68. package/index.ts +0 -431
  69. package/jsdoc.json +0 -52
  70. package/rollup.config.js +0 -51
  71. package/template/.gitkeep +0 -0
  72. package/tsconfig.json +0 -102
@@ -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
- };