larvitar 1.5.14 → 2.0.2

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 (107) hide show
  1. package/.vscode/settings.json +4 -0
  2. package/README.md +78 -48
  3. package/bundler/webpack.common.js +27 -0
  4. package/bundler/webpack.dev.js +23 -0
  5. package/bundler/webpack.prod.js +19 -0
  6. package/decs.d.ts +12 -0
  7. package/dist/imaging/MetaDataReadable.d.ts +40 -0
  8. package/dist/imaging/MetaDataTypes.d.ts +3489 -0
  9. package/dist/imaging/imageAnonymization.d.ts +12 -0
  10. package/dist/imaging/imageColormaps.d.ts +47 -0
  11. package/dist/imaging/imageContours.d.ts +18 -0
  12. package/dist/imaging/imageIo.d.ts +42 -0
  13. package/dist/imaging/imageLayers.d.ts +56 -0
  14. package/dist/imaging/imageLoading.d.ts +65 -0
  15. package/dist/imaging/imageParsing.d.ts +46 -0
  16. package/dist/imaging/imagePresets.d.ts +43 -0
  17. package/dist/imaging/imageRendering.d.ts +238 -0
  18. package/dist/imaging/imageReslice.d.ts +14 -0
  19. package/dist/imaging/imageStore.d.ts +121 -0
  20. package/dist/imaging/imageTags.d.ts +22 -0
  21. package/dist/imaging/imageTools.d.ts +20 -0
  22. package/dist/imaging/imageUtils.d.ts +165 -0
  23. package/dist/imaging/loaders/commonLoader.d.ts +103 -0
  24. package/dist/imaging/loaders/dicomLoader.d.ts +29 -0
  25. package/dist/imaging/loaders/fileLoader.d.ts +33 -0
  26. package/dist/imaging/loaders/multiframeLoader.d.ts +37 -0
  27. package/dist/imaging/loaders/nrrdLoader.d.ts +113 -0
  28. package/dist/imaging/loaders/resliceLoader.d.ts +15 -0
  29. package/dist/imaging/monitors/memory.d.ts +41 -0
  30. package/dist/imaging/monitors/performance.d.ts +23 -0
  31. package/dist/imaging/parsers/ecg.d.ts +15 -0
  32. package/dist/imaging/parsers/nrrd.d.ts +3 -0
  33. package/dist/imaging/tools/custom/4dSliceScrollTool.d.ts +12 -0
  34. package/dist/imaging/tools/custom/BorderMagnifyTool.d.ts +18 -0
  35. package/dist/imaging/tools/custom/contourTool.d.ts +409 -0
  36. package/dist/imaging/tools/custom/diameterTool.d.ts +18 -0
  37. package/dist/imaging/tools/custom/editMaskTool.d.ts +22 -0
  38. package/dist/imaging/tools/custom/ellipticalRoiOverlayTool.d.ts +45 -0
  39. package/dist/imaging/tools/custom/polygonSegmentationMixin.d.ts +54 -0
  40. package/dist/imaging/tools/custom/polylineScissorsTool.d.ts +11 -0
  41. package/dist/imaging/tools/custom/rectangleRoiOverlayTool.d.ts +45 -0
  42. package/dist/imaging/tools/custom/seedTool.d.ts +0 -0
  43. package/dist/imaging/tools/custom/setLabelMap3D.d.ts +39 -0
  44. package/dist/imaging/tools/custom/thresholdsBrushTool.d.ts +19 -0
  45. package/dist/imaging/tools/default.d.ts +53 -0
  46. package/dist/imaging/tools/interaction.d.ts +30 -0
  47. package/dist/imaging/tools/io.d.ts +38 -0
  48. package/dist/imaging/tools/main.d.ts +81 -0
  49. package/dist/imaging/tools/segmentation.d.ts +125 -0
  50. package/dist/imaging/tools/state.d.ts +17 -0
  51. package/dist/imaging/tools/strategies/eraseFreehand.d.ts +16 -0
  52. package/dist/imaging/tools/strategies/fillFreehand.d.ts +16 -0
  53. package/dist/imaging/tools/strategies/index.d.ts +2 -0
  54. package/dist/imaging/waveforms/ecg.d.ts +39 -0
  55. package/dist/index.d.ts +35 -0
  56. package/dist/larvitar.js +90104 -0
  57. package/dist/larvitar.js.map +1 -0
  58. package/imaging/MetaDataReadable.ts +41 -0
  59. package/imaging/MetaDataTypes.ts +3491 -0
  60. package/imaging/dataDictionary.json +5328 -5328
  61. package/imaging/{imageAnonymization.js → imageAnonymization.ts} +41 -13
  62. package/imaging/{imageColormaps.js → imageColormaps.ts} +48 -30
  63. package/imaging/{imageContours.js → imageContours.ts} +24 -22
  64. package/imaging/{imageIo.js → imageIo.ts} +89 -52
  65. package/imaging/{imageLayers.js → imageLayers.ts} +31 -14
  66. package/imaging/{imageLoading.js → imageLoading.ts} +107 -43
  67. package/imaging/{imageParsing.js → imageParsing.ts} +160 -80
  68. package/imaging/{imagePresets.js → imagePresets.ts} +44 -11
  69. package/imaging/imageRendering.ts +1091 -0
  70. package/imaging/{imageReslice.js → imageReslice.ts} +18 -9
  71. package/imaging/imageStore.ts +487 -0
  72. package/imaging/imageTags.ts +609 -0
  73. package/imaging/imageTools.js +2 -1
  74. package/imaging/{imageUtils.js → imageUtils.ts} +211 -701
  75. package/imaging/loaders/{commonLoader.js → commonLoader.ts} +73 -24
  76. package/imaging/loaders/{dicomLoader.js → dicomLoader.ts} +25 -5
  77. package/imaging/loaders/{fileLoader.js → fileLoader.ts} +5 -5
  78. package/imaging/loaders/{multiframeLoader.js → multiframeLoader.ts} +145 -90
  79. package/imaging/loaders/{nrrdLoader.js → nrrdLoader.ts} +231 -64
  80. package/imaging/loaders/{resliceLoader.js → resliceLoader.ts} +51 -20
  81. package/imaging/monitors/{memory.js → memory.ts} +54 -8
  82. package/imaging/monitors/performance.ts +34 -0
  83. package/imaging/parsers/ecg.ts +54 -0
  84. package/imaging/tools/README.md +27 -0
  85. package/imaging/tools/custom/4dSliceScrollTool.js +47 -46
  86. package/imaging/tools/custom/BorderMagnifyTool.js +99 -0
  87. package/imaging/tools/custom/ellipticalRoiOverlayTool.js +534 -0
  88. package/imaging/tools/custom/polylineScissorsTool.js +1 -1
  89. package/imaging/tools/custom/rectangleRoiOverlayTool.js +564 -0
  90. package/imaging/tools/{setLabelMap3D.js → custom/setLabelMap3D.ts} +19 -25
  91. package/imaging/tools/{default.js → default.ts} +119 -33
  92. package/imaging/tools/{interaction.js → interaction.ts} +42 -23
  93. package/imaging/tools/{io.js → io.ts} +47 -31
  94. package/imaging/tools/{main.js → main.ts} +105 -40
  95. package/imaging/tools/{segmentation.js → segmentation.ts} +95 -68
  96. package/imaging/tools/{state.js → state.ts} +7 -12
  97. package/imaging/tools/types.d.ts +243 -0
  98. package/imaging/types.d.ts +200 -0
  99. package/imaging/waveforms/ecg.ts +191 -0
  100. package/{index.js → index.ts} +53 -14
  101. package/jsdoc.json +1 -1
  102. package/package.json +35 -14
  103. package/tsconfig.json +102 -0
  104. package/imaging/imageRendering.js +0 -860
  105. package/imaging/imageStore.js +0 -322
  106. package/modules/vuex/larvitar.js +0 -187
  107. /package/imaging/tools/{polygonSegmentationMixin.js → custom/polygonSegmentationMixin.js} +0 -0
@@ -8,6 +8,7 @@ import cornerstone from "cornerstone-core";
8
8
  import { default as cornerstoneDICOMImageLoader } from "cornerstone-wado-image-loader";
9
9
  import { each, clone, range, findKey, filter, pickBy } from "lodash";
10
10
  import { v4 as uuidv4 } from "uuid";
11
+ import { ImageLoader } from "cornerstone-core";
11
12
 
12
13
  // internal libraries
13
14
  import {
@@ -21,6 +22,16 @@ import {
21
22
  getLarvitarImageTracker,
22
23
  getLarvitarManager
23
24
  } from "./commonLoader";
25
+ import type {
26
+ Image,
27
+ Instance,
28
+ Volume,
29
+ LarvitarManager,
30
+ ImageFrame,
31
+ ImageTracker,
32
+ MetaData
33
+ } from "../types";
34
+ import { DataSet } from "dicom-parser";
24
35
 
25
36
  // global module variables
26
37
  let customImageLoaderCounter = 0;
@@ -35,6 +46,80 @@ let customImageLoaderCounter = 0;
35
46
  * getNrrdSerieDimensions()
36
47
  */
37
48
 
49
+ type NrrdInputVolume = {
50
+ header: {
51
+ sizes: number[];
52
+ "space directions": number[][]; // a property with a space in the name ?? Seriously ??
53
+ "space origin": [number, number];
54
+ kinds: string[];
55
+ type: string;
56
+ };
57
+ data: Uint16Array; // TODO-ts: other typed arrays ?
58
+ };
59
+
60
+ export type NrrdSeries = {
61
+ currentImageIdIndex: number;
62
+ imageIds: string[];
63
+ instances: { [key: string]: Instance };
64
+ instanceUIDs: { [key: string]: string };
65
+ numberOfImages: number;
66
+ seriesDescription: string;
67
+ seriesUID: string;
68
+ customLoader: string;
69
+ nrrdHeader: NrrdHeader;
70
+ bytes: number;
71
+ dataSet?: DataSet;
72
+ metadata?: MetaData;
73
+ ecgData?: number[];
74
+ };
75
+
76
+ type NrrdHeader = {
77
+ volume: Volume;
78
+ intercept: number;
79
+ slope: number;
80
+ repr: string;
81
+ phase: string;
82
+ study_description: string;
83
+ series_description: string;
84
+ acquisition_date: string;
85
+ [imageId: string]: string | number | Volume | NrrdInstance; // TODO-ts: fix this: we need just NrrdInstance
86
+ };
87
+
88
+ type NrrdInstance = {
89
+ instanceUID: string;
90
+ seriesDescription: string;
91
+ seriesModality: string;
92
+ patientName: string;
93
+ bitsAllocated: number;
94
+ pixelRepresentation: string;
95
+ };
96
+
97
+ // TODO-ts: why it's different from cornerstone type ?
98
+ // type Image = {
99
+ // imageId: string;
100
+ // rows: number;
101
+ // columns: number;
102
+ // minPixelValue: number;
103
+ // maxPixelValue: number;
104
+ // slope: number;
105
+ // intercept: number;
106
+ // windowCenter: number;
107
+ // windowWidth: number;
108
+ // render?: Function;
109
+ // getPixelData?: Function;
110
+ // getCanvas?: Function;
111
+ // color: boolean;
112
+ // columnPixelSpacing: number;
113
+ // rowPixelSpacing: number;
114
+ // invert: boolean;
115
+ // sizeInBytes: number;
116
+ // height: number;
117
+ // width: number;
118
+ // decodeTimeInMS?: number;
119
+ // webWorkerTimeInMS?: number;
120
+ // metadata: {[key: string]: MetadataValue};
121
+ // }
122
+
38
123
  /**
39
124
  * Build the data structure for the provided image orientation
40
125
  * @instance
@@ -44,12 +129,17 @@ let customImageLoaderCounter = 0;
44
129
  * @param {Object} custom_header A custom header object
45
130
  * @return {Object} volume data
46
131
  */
47
- export const buildNrrdImage = function (volume, seriesId, custom_header) {
132
+ export const buildNrrdImage = function (
133
+ volume: NrrdInputVolume,
134
+ seriesId: string,
135
+ custom_header: NrrdHeader
136
+ ) {
137
+ //TODO-ts: better definition
48
138
  let t0 = performance.now();
49
139
  // standard image structure
50
- let image = {};
51
- let manager = getLarvitarManager();
52
- let imageTracker = getLarvitarImageTracker();
140
+ let image: Partial<NrrdSeries> = {};
141
+ let manager = getLarvitarManager() as LarvitarManager;
142
+ let imageTracker = getLarvitarImageTracker() as ImageTracker;
53
143
  image.currentImageIdIndex = 0;
54
144
  image.imageIds = [];
55
145
  image.instances = {};
@@ -57,8 +147,8 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
57
147
  image.seriesDescription = "";
58
148
  image.seriesUID = seriesId;
59
149
 
60
- let header = {};
61
- header["volume"] = {};
150
+ let header: Partial<NrrdHeader> = {};
151
+ header.volume = {} as Volume;
62
152
  // need to extract header from nrrd file format
63
153
  // sizes, spaceDirections and spaceOrigin
64
154
 
@@ -96,9 +186,9 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
96
186
  header.volume.sliceThickness = spacing_z;
97
187
  header.volume.repr =
98
188
  volume.header.type[0].toUpperCase() + volume.header.type.slice(1);
99
- header.volume.intercept = custom_header ? custom_header.intercept : null;
100
- header.volume.slope = custom_header ? custom_header.slope : null;
101
- header.volume.phase = custom_header ? custom_header.phase : null;
189
+ header.volume.intercept = custom_header ? custom_header.intercept : 0;
190
+ header.volume.slope = custom_header ? custom_header.slope : 1;
191
+ header.volume.phase = custom_header ? custom_header.phase : "";
102
192
  header.volume.study_description = custom_header
103
193
  ? custom_header.study_description
104
194
  : "";
@@ -112,9 +202,13 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
112
202
  let rows = volume.header.sizes[index + 1];
113
203
  let cols = volume.header.sizes[index + 0];
114
204
  let frames = volume.header.sizes[index + 2];
115
- let iop = volume.header["space directions"][index + 0].concat(
205
+ let iopArr = volume.header["space directions"][index + 0].concat(
116
206
  volume.header["space directions"][index + 1]
117
207
  );
208
+ if (iopArr.length !== 6) {
209
+ throw new Error("Invalid Image Orientation");
210
+ }
211
+ let iop = iopArr as [number, number, number, number, number, number];
118
212
  let firstIpp = header.volume.imagePosition;
119
213
  let w = getNormalOrientation(iop);
120
214
  let ps = header.volume.pixelSpacing;
@@ -122,7 +216,7 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
122
216
  let intercept = header.volume.intercept;
123
217
  let slope = header.volume.slope;
124
218
 
125
- let metadata = {
219
+ let metadata: Partial<Instance["metadata"]> = {
126
220
  x00280010: rows, // Rows
127
221
  x00280011: cols, // Columns
128
222
  x00200037: iop, // ImageOrientationPatient
@@ -131,30 +225,34 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
131
225
  x00281052: intercept ? [intercept] : [0],
132
226
  x00281053: slope ? [slope] : [1],
133
227
  x00200052: header.volume.imageIds
134
- ? header[header.volume.imageIds[0]].instanceUID
228
+ ? (header[header.volume.imageIds[0]] as NrrdInstance).instanceUID
135
229
  : null,
136
230
  x0008103e: header.volume.imageIds
137
- ? header[header.volume.imageIds[0]].seriesDescription
231
+ ? (header[header.volume.imageIds[0]] as NrrdInstance).seriesDescription
138
232
  : null,
139
233
  x00080060: header.volume.imageIds
140
- ? header[header.volume.imageIds[0]].seriesModality
234
+ ? (header[header.volume.imageIds[0]] as NrrdInstance).seriesModality
141
235
  : null,
142
236
  x00100010: header.volume.imageIds
143
- ? header[header.volume.imageIds[0]].patientName
237
+ ? (header[header.volume.imageIds[0]] as NrrdInstance).patientName
144
238
  : null,
145
239
  x00280100: header.volume.imageIds
146
- ? header[header.volume.imageIds[0]].bitsAllocated
240
+ ? (header[header.volume.imageIds[0]] as NrrdInstance).bitsAllocated
147
241
  : null,
148
242
  x00280103: header.volume.imageIds
149
- ? header[header.volume.imageIds[0]].pixelRepresentation
243
+ ? (header[header.volume.imageIds[0]] as NrrdInstance).pixelRepresentation
150
244
  : null,
151
245
  repr: header.volume.repr || null
152
246
  };
153
247
 
154
248
  // compute default ww/wl values here to use them also for resliced images
155
249
  let minMax = cornerstoneDICOMImageLoader.getMinMax(volume.data);
156
- let maxVoi = minMax.max * metadata.x00281053[0] + metadata.x00281052[0];
157
- let minVoi = minMax.min * metadata.x00281053[0] + metadata.x00281052[0];
250
+ let maxVoi =
251
+ minMax.max * (metadata.x00281053 as number[])[0] +
252
+ (metadata.x00281052 as number[])[0];
253
+ let minVoi =
254
+ minMax.min * (metadata.x00281053 as number[])[0] +
255
+ (metadata.x00281052 as number[])[0];
158
256
  let ww = maxVoi - minVoi;
159
257
  let wl = (maxVoi + minVoi) / 2;
160
258
 
@@ -162,35 +260,49 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
162
260
  metadata.x00280107 = minMax.max;
163
261
 
164
262
  // extract the pixelData of each frame, store the data into the image object
165
- each(range(frames), function (sliceIndex) {
263
+ each(range(frames), function (sliceIndex: number) {
166
264
  let sliceSize = rows * cols;
167
265
  let sliceBuffer = volume.data.subarray(
168
266
  sliceSize * sliceIndex,
169
267
  sliceSize * (sliceIndex + 1)
170
268
  );
269
+
270
+ if (!metadata) {
271
+ throw new Error("Metadata not found");
272
+ }
273
+
274
+ // @ts-ignore: TODO this is concepptually wrong, we already know the Pixel Representation
275
+ // (see above, line 241), this function just returns the same value again
171
276
  let r = getPixelRepresentation(metadata);
172
277
  let typedArray = getTypedArrayFromDataType(r);
278
+
279
+ if (!typedArray) {
280
+ throw new Error("Typed array not found");
281
+ }
282
+
173
283
  let pixelData = new typedArray(sliceBuffer);
174
284
  // assign these values to the metadata of all images
175
285
  metadata.x00281050 = wl;
176
286
  metadata.x00281051 = ww;
177
287
 
178
288
  let imageId = getNrrdImageId("nrrdLoader");
289
+ if (!imageTracker) {
290
+ throw new Error("Image tracker not initialized");
291
+ }
179
292
  imageTracker[imageId] = seriesId;
180
293
 
181
294
  // store file references
182
- image.imageIds.push(imageId);
183
- image.instances[imageId] = {
184
- instanceId: uuidv4(),
185
- frame: sliceIndex
186
- };
187
- let frameMetadata = clone(metadata);
188
-
295
+ image.imageIds!.push(imageId);
296
+ let frameMetadata: MetaData = clone(metadata);
189
297
  frameMetadata.x00200032 = firstIpp.map(function (val, i) {
190
298
  return val + thickness * sliceIndex * w[i];
191
299
  });
192
- image.instances[imageId].metadata = frameMetadata;
193
- image.instances[imageId].pixelData = pixelData;
300
+ image.instances![imageId] = {
301
+ instanceId: uuidv4(),
302
+ frame: sliceIndex,
303
+ metadata: frameMetadata,
304
+ pixelData: pixelData
305
+ };
194
306
  });
195
307
 
196
308
  let middleSlice = Math.floor(image.imageIds.length / 2);
@@ -199,9 +311,13 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
199
311
  // specify custom loader type and attach original header
200
312
  image.customLoader = "nrrdLoader";
201
313
  header.volume.imageIds = image.imageIds;
202
- image.nrrdHeader = header;
314
+ image.nrrdHeader = header as NrrdHeader;
315
+
316
+ if (!manager) {
317
+ throw new Error("Larvitar manager not initialized");
318
+ }
203
319
 
204
- manager[seriesId] = image;
320
+ manager[seriesId] = image as NrrdSeries;
205
321
 
206
322
  let t1 = performance.now();
207
323
  console.log(`Call to buildNrrdImage took ${t1 - t0} milliseconds.`);
@@ -215,7 +331,7 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
215
331
  * @param {String} customLoaderName The custom loader name
216
332
  * @return {String} the custom image id
217
333
  */
218
- export const getNrrdImageId = function (customLoaderName) {
334
+ export const getNrrdImageId = function (customLoaderName: string) {
219
335
  let imageId = customLoaderName + "://" + customImageLoaderCounter;
220
336
  customImageLoaderCounter++;
221
337
  return imageId;
@@ -228,11 +344,15 @@ export const getNrrdImageId = function (customLoaderName) {
228
344
  * @param {String} imageId The image id
229
345
  * @return {Object} custom image object
230
346
  */
231
- export const loadNrrdImage = function (imageId) {
232
- let manager = getLarvitarManager();
233
- let imageTracker = getLarvitarImageTracker();
347
+ export const loadNrrdImage: ImageLoader = function (imageId: string) {
348
+ let manager = getLarvitarManager() as LarvitarManager;
349
+ let imageTracker = getLarvitarImageTracker() as ImageTracker;
350
+ if (!manager || !imageTracker) {
351
+ throw new Error("Larvitar manager or image tracker not initialized");
352
+ }
234
353
  let seriesId = imageTracker[imageId];
235
354
  let instance = manager[seriesId].instances[imageId];
355
+ //@ts-ignore TODO-ts: fix this why is different typed array?
236
356
  return createCustomImage(imageId, instance.metadata, instance.pixelData);
237
357
  };
238
358
 
@@ -246,13 +366,13 @@ export const loadNrrdImage = function (imageId) {
246
366
  * @return {String} image id
247
367
  */
248
368
  export const getImageIdFromSlice = function (
249
- sliceNumber,
250
- orientation,
251
- seriesId
369
+ sliceNumber: number,
370
+ orientation: string,
371
+ seriesId: string
252
372
  ) {
253
373
  var prefix = "nrrdLoader://";
254
374
  var serieImageTracker;
255
- let imageTracker = getLarvitarImageTracker();
375
+ let imageTracker = getLarvitarImageTracker() as ImageTracker;
256
376
 
257
377
  if (seriesId) {
258
378
  serieImageTracker = pickBy(imageTracker, image => {
@@ -262,12 +382,18 @@ export const getImageIdFromSlice = function (
262
382
  serieImageTracker = imageTracker;
263
383
  }
264
384
 
265
- var firstImageId = findKey(serieImageTracker, entry => {
385
+ var firstImageIdStr = findKey(serieImageTracker, entry => {
266
386
  return entry[1] == orientation;
267
387
  });
268
388
 
269
- var imageIndex =
270
- parseInt(firstImageId.split("//").pop()) + parseInt(sliceNumber);
389
+ let firstImageId = firstImageIdStr?.split("//").pop();
390
+
391
+ if (firstImageId == undefined) {
392
+ console.error("cannot find imageId for orientation: " + orientation);
393
+ return "";
394
+ }
395
+
396
+ var imageIndex = parseInt(firstImageId) + parseInt(sliceNumber.toString());
271
397
 
272
398
  var imageId = prefix.concat(imageIndex.toString());
273
399
 
@@ -283,16 +409,29 @@ export const getImageIdFromSlice = function (
283
409
  * @param {String} seriesId The series id
284
410
  * @return {Integer} The image slice number
285
411
  */
286
- export const getSliceNumberFromImageId = function (imageId, orientation) {
287
- let imageTracker = getLarvitarImageTracker();
288
- var firstImageId = findKey(imageTracker, entry => {
412
+ export const getSliceNumberFromImageId = function (
413
+ imageId: string,
414
+ orientation: string
415
+ ) {
416
+ let imageTracker = getLarvitarImageTracker() as ImageTracker;
417
+ var firstImageIdStr = findKey(imageTracker, entry => {
289
418
  return entry[1] == orientation;
290
419
  });
291
420
 
421
+ if (firstImageIdStr == undefined) {
422
+ console.error("cannot find imageId for orientation: " + orientation);
423
+ return 0;
424
+ }
425
+
292
426
  var imageNumber = imageId.split("//").pop() || imageId;
427
+ let firstImageId = firstImageIdStr.split("//").pop();
293
428
 
294
- var imageIndex =
295
- parseInt(imageNumber) - parseInt(firstImageId.split("//").pop());
429
+ if (firstImageId == undefined) {
430
+ console.error("cannot find imageId for orientation: " + orientation);
431
+ return 0;
432
+ }
433
+
434
+ var imageIndex = parseInt(imageNumber) - parseInt(firstImageId);
296
435
 
297
436
  return imageIndex;
298
437
  };
@@ -304,7 +443,7 @@ export const getSliceNumberFromImageId = function (imageId, orientation) {
304
443
  * @return {Object} Series dimension for each view
305
444
  */
306
445
  export const getNrrdSerieDimensions = function () {
307
- let imageTracker = getLarvitarImageTracker();
446
+ let imageTracker = getLarvitarImageTracker() as ImageTracker;
308
447
  var dim_axial = filter(imageTracker, img => {
309
448
  return img[1] == "axial";
310
449
  });
@@ -334,11 +473,17 @@ export const getNrrdSerieDimensions = function () {
334
473
  * @param {Object} dataSet The dataset
335
474
  * @return {String} The image id
336
475
  */
337
- let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
476
+ let createCustomImage = function (
477
+ imageId: string,
478
+ metadata: MetaData,
479
+ pixelData: Uint8ClampedArray,
480
+ dataSet?: any
481
+ ) {
482
+ //TODO-ts check this
338
483
  let canvas = window.document.createElement("canvas");
339
484
  let lastImageIdDrawn = "";
340
485
 
341
- let imageFrame = getImageFrame(metadata, dataSet);
486
+ let imageFrame = getImageFrame(metadata, dataSet) as ImageFrame;
342
487
 
343
488
  // This function uses the pixelData received as argument without manipulating
344
489
  // them: if the image is compressed, the decompress function should be called
@@ -361,7 +506,7 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
361
506
  );
362
507
  }
363
508
 
364
- let image = {
509
+ let image: Partial<Image> = {
365
510
  imageId: imageId,
366
511
  color: cornerstoneDICOMImageLoader.isColorImage(
367
512
  imageFrame.photometricInterpretation
@@ -369,7 +514,7 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
369
514
  columnPixelSpacing: pixelSpacing ? pixelSpacing[1] : undefined,
370
515
  columns: imageFrame.columns,
371
516
  height: imageFrame.rows,
372
- intercept: rescaleIntercept ? rescaleIntercept[0] : 0,
517
+ intercept: rescaleIntercept ? (rescaleIntercept as number[])[0] : 0,
373
518
  invert: imageFrame.photometricInterpretation === "MONOCHROME1",
374
519
  minPixelValue: imageFrame.smallestPixelValue,
375
520
  maxPixelValue: imageFrame.largestPixelValue,
@@ -377,17 +522,21 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
377
522
  rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : undefined,
378
523
  rows: imageFrame.rows,
379
524
  sizeInBytes: getSizeInBytes(),
380
- slope: rescaleSlope ? rescaleSlope[0] : 1,
525
+ slope: rescaleSlope ? (rescaleSlope as number[])[0] : 1,
381
526
  width: imageFrame.columns,
382
- windowCenter: windowCenter ? windowCenter[0] : undefined,
383
- windowWidth: windowWidth ? windowWidth[0] : undefined,
527
+ windowCenter: windowCenter ? (windowCenter as number[])[0] : undefined,
528
+ windowWidth: windowWidth ? (windowWidth as number[])[0] : undefined,
384
529
  decodeTimeInMS: undefined,
385
530
  webWorkerTimeInMS: undefined
386
531
  };
387
532
 
388
533
  // add function to return pixel data
389
534
  image.getPixelData = function () {
390
- return imageFrame.pixelData;
535
+ if (!imageFrame.pixelData) {
536
+ console.warn('no pixel data for imageId "' + imageId);
537
+ return [];
538
+ }
539
+ return Array.from(imageFrame.pixelData);
391
540
  };
392
541
 
393
542
  // convert color space
@@ -397,6 +546,11 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
397
546
  canvas.width = imageFrame.columns;
398
547
 
399
548
  let context = canvas.getContext("2d");
549
+
550
+ if (!context) {
551
+ throw new Error("Unable to get canvas context");
552
+ }
553
+
400
554
  let imageData = context.createImageData(
401
555
  imageFrame.columns,
402
556
  imageFrame.rows
@@ -415,10 +569,13 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
415
569
  return canvas;
416
570
  }
417
571
 
418
- canvas.height = image.rows;
419
- canvas.width = image.columns;
572
+ canvas.height = image.rows || 0;
573
+ canvas.width = image.columns || 0;
420
574
  let context = canvas.getContext("2d");
421
- context.putImageData(imageFrame.imageData, 0, 0);
575
+ if (!context) {
576
+ throw new Error("Unable to get canvas context");
577
+ }
578
+ context.putImageData(imageFrame.imageData!, 0, 0);
422
579
  lastImageIdDrawn = imageId;
423
580
  return canvas;
424
581
  };
@@ -436,13 +593,23 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
436
593
  // set the ww/wc to cover the dynamic range of the image if no values are supplied
437
594
  if (image.windowCenter === undefined || image.windowWidth === undefined) {
438
595
  if (image.color) {
439
- image.windowWidth = 255;
440
- image.windowCenter = 128;
441
- } else {
596
+ image.windowWidth = 255.0;
597
+ image.windowCenter = 127.5;
598
+ } else if (
599
+ image.maxPixelValue != null &&
600
+ image.minPixelValue != null &&
601
+ image.slope != null &&
602
+ image.intercept != null
603
+ ) {
442
604
  let maxVoi = image.maxPixelValue * image.slope + image.intercept;
443
605
  let minVoi = image.minPixelValue * image.slope + image.intercept;
444
606
  image.windowWidth = maxVoi - minVoi;
445
607
  image.windowCenter = (maxVoi + minVoi) / 2;
608
+ } else {
609
+ console.error(
610
+ "Unable to calculate default window width/center for imageId: " +
611
+ imageId
612
+ );
446
613
  }
447
614
  }
448
615
 
@@ -451,8 +618,8 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
451
618
  // function to store custom image pixelData and metadata.
452
619
  image.metadata = metadata;
453
620
 
454
- let promise = new Promise(function (resolve) {
455
- resolve(image);
621
+ let promise: Promise<Image> = new Promise(function (resolve) {
622
+ resolve(image as Image);
456
623
  });
457
624
 
458
625
  // Return an object containing the Promise to cornerstone so it can setup callbacks to be
@@ -13,6 +13,7 @@ import {
13
13
  getLarvitarImageTracker,
14
14
  getLarvitarManager
15
15
  } from "./commonLoader";
16
+ import type { Image, ImageFrame, MetaData } from "../types";
16
17
 
17
18
  /*
18
19
  * This module provides the following functions to be exported:
@@ -20,18 +21,19 @@ import {
20
21
  */
21
22
 
22
23
  /**
23
- * Custom Loader for WadoImageLoader
24
+ * Custom Loader for DICOMImageLoader
24
25
  * @instance
25
26
  * @function loadReslicedImage
26
27
  * @param {String} imageId The Id of the image
27
28
  * @returns {Object} custom image object
28
29
  */
29
- export const loadReslicedImage = function (imageId) {
30
+ export const loadReslicedImage = function (imageId: string) {
30
31
  let manager = getLarvitarManager();
31
32
  let imageTracker = getLarvitarImageTracker();
32
33
  let seriesId = imageTracker[imageId];
33
34
  let instance = manager[seriesId].instances[imageId];
34
35
  var reslicedPixeldata = instance.pixelData;
36
+ //@ts-ignore deprecated
35
37
  return createCustomImage(imageId, instance.metadata, reslicedPixeldata);
36
38
  };
37
39
 
@@ -47,11 +49,16 @@ export const loadReslicedImage = function (imageId) {
47
49
  * @param {Object} dataSet dataset object
48
50
  * @returns {Object} custom image object
49
51
  */
50
- let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
52
+ let createCustomImage = function (
53
+ imageId: string,
54
+ metadata: MetaData,
55
+ pixelData: Uint8ClampedArray,
56
+ dataSet?: any // deprecated
57
+ ) {
51
58
  let canvas = window.document.createElement("canvas");
52
59
  let lastImageIdDrawn = "";
53
60
 
54
- let imageFrame = getImageFrame(metadata, dataSet);
61
+ let imageFrame = getImageFrame(metadata, dataSet) as ImageFrame;
55
62
 
56
63
  // This function uses the pixelData received as argument without manipulating
57
64
  // them: if the image is compressed, the decompress function should be called
@@ -74,33 +81,39 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
74
81
  );
75
82
  }
76
83
 
77
- let image = {
84
+ let image: Partial<Image> = {
78
85
  imageId: imageId,
79
86
  color: cornerstoneDICOMImageLoader.isColorImage(
80
87
  imageFrame.photometricInterpretation
81
88
  ),
82
- columnPixelSpacing: pixelSpacing ? pixelSpacing[1] : undefined,
89
+ columnPixelSpacing: pixelSpacing
90
+ ? (pixelSpacing as number[])[1]
91
+ : undefined,
83
92
  columns: imageFrame.columns,
84
93
  height: imageFrame.rows,
85
- intercept: rescaleIntercept ? rescaleIntercept : 0,
94
+ intercept: rescaleIntercept ? (rescaleIntercept as number) : 0,
86
95
  invert: imageFrame.photometricInterpretation === "MONOCHROME1",
87
96
  minPixelValue: imageFrame.smallestPixelValue,
88
97
  maxPixelValue: imageFrame.largestPixelValue,
89
98
  render: undefined, // set below
90
- rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : undefined,
99
+ rowPixelSpacing: pixelSpacing ? (pixelSpacing as number[])[0] : undefined,
91
100
  rows: imageFrame.rows,
92
101
  sizeInBytes: getSizeInBytes(),
93
- slope: rescaleSlope ? rescaleSlope : 1,
102
+ slope: rescaleSlope ? (rescaleSlope as number) : 1,
94
103
  width: imageFrame.columns,
95
- windowCenter: windowCenter ? windowCenter : undefined,
96
- windowWidth: windowWidth ? windowWidth : undefined,
104
+ windowCenter: windowCenter ? (windowCenter as number) : undefined,
105
+ windowWidth: windowWidth ? (windowWidth as number) : undefined,
97
106
  decodeTimeInMS: undefined,
98
107
  webWorkerTimeInMS: undefined
99
108
  };
100
109
 
101
110
  // add function to return pixel data
102
111
  image.getPixelData = function () {
103
- return imageFrame.pixelData;
112
+ if (!imageFrame.pixelData) {
113
+ console.warn('no pixel data for imageId "' + imageId);
114
+ return [];
115
+ }
116
+ return Array.from(imageFrame.pixelData);
104
117
  };
105
118
 
106
119
  // convert color space
@@ -110,6 +123,11 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
110
123
  canvas.width = imageFrame.columns;
111
124
 
112
125
  let context = canvas.getContext("2d");
126
+
127
+ if (!context) {
128
+ throw new Error("Unable to get canvas context");
129
+ }
130
+
113
131
  let imageData = context.createImageData(
114
132
  imageFrame.columns,
115
133
  imageFrame.rows
@@ -128,10 +146,13 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
128
146
  return canvas;
129
147
  }
130
148
 
131
- canvas.height = image.rows;
132
- canvas.width = image.columns;
149
+ canvas.height = image.rows || 0;
150
+ canvas.width = image.columns || 0;
133
151
  let context = canvas.getContext("2d");
134
- context.putImageData(imageFrame.imageData, 0, 0);
152
+ if (!context) {
153
+ throw new Error("Unable to get canvas context");
154
+ }
155
+ context.putImageData(imageFrame.imageData!, 0, 0);
135
156
  lastImageIdDrawn = imageId;
136
157
  return canvas;
137
158
  };
@@ -147,13 +168,23 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
147
168
  // set the ww/wc to cover the dynamic range of the image if no values are supplied
148
169
  if (image.windowCenter === undefined || image.windowWidth === undefined) {
149
170
  if (image.color) {
150
- image.windowWidth = 255;
151
- image.windowCenter = 128;
152
- } else {
171
+ image.windowWidth = 255.0;
172
+ image.windowCenter = 127.5;
173
+ } else if (
174
+ image.maxPixelValue &&
175
+ image.minPixelValue &&
176
+ image.slope &&
177
+ image.intercept
178
+ ) {
153
179
  let maxVoi = image.maxPixelValue * image.slope + image.intercept;
154
180
  let minVoi = image.minPixelValue * image.slope + image.intercept;
155
181
  image.windowWidth = maxVoi - minVoi;
156
182
  image.windowCenter = (maxVoi + minVoi) / 2;
183
+ } else {
184
+ console.error(
185
+ "Unable to calculate default window width/center for imageId: " +
186
+ imageId
187
+ );
157
188
  }
158
189
  }
159
190
 
@@ -162,8 +193,8 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
162
193
  // function to store custom image pixelData and metadata.
163
194
  image.metadata = metadata;
164
195
 
165
- let promise = new Promise(function (resolve) {
166
- resolve(image);
196
+ let promise: Promise<Image> = new Promise(function (resolve) {
197
+ resolve(image as Image);
167
198
  });
168
199
 
169
200
  // Return an object containing the Promise to cornerstone so it can setup callbacks to be