larvitar 1.5.13 → 2.0.0

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 (103) 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 +39 -0
  8. package/dist/imaging/MetaDataTypes.d.ts +3488 -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 +112 -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/contourTool.d.ts +409 -0
  35. package/dist/imaging/tools/custom/diameterTool.d.ts +18 -0
  36. package/dist/imaging/tools/custom/editMaskTool.d.ts +22 -0
  37. package/dist/imaging/tools/custom/ellipticalRoiOverlayTool.d.ts +45 -0
  38. package/dist/imaging/tools/custom/polygonSegmentationMixin.d.ts +54 -0
  39. package/dist/imaging/tools/custom/polylineScissorsTool.d.ts +11 -0
  40. package/dist/imaging/tools/custom/rectangleRoiOverlayTool.d.ts +45 -0
  41. package/dist/imaging/tools/custom/seedTool.d.ts +0 -0
  42. package/dist/imaging/tools/custom/setLabelMap3D.d.ts +39 -0
  43. package/dist/imaging/tools/custom/thresholdsBrushTool.d.ts +19 -0
  44. package/dist/imaging/tools/default.d.ts +53 -0
  45. package/dist/imaging/tools/interaction.d.ts +30 -0
  46. package/dist/imaging/tools/io.d.ts +38 -0
  47. package/dist/imaging/tools/main.d.ts +81 -0
  48. package/dist/imaging/tools/segmentation.d.ts +125 -0
  49. package/dist/imaging/tools/state.d.ts +17 -0
  50. package/dist/imaging/tools/strategies/eraseFreehand.d.ts +16 -0
  51. package/dist/imaging/tools/strategies/fillFreehand.d.ts +16 -0
  52. package/dist/imaging/tools/strategies/index.d.ts +2 -0
  53. package/dist/index.d.ts +34 -0
  54. package/dist/larvitar.js +89801 -0
  55. package/dist/larvitar.js.map +1 -0
  56. package/imaging/MetaDataReadable.ts +40 -0
  57. package/imaging/MetaDataTypes.ts +3490 -0
  58. package/imaging/dataDictionary.json +5328 -5328
  59. package/imaging/{imageAnonymization.js → imageAnonymization.ts} +41 -13
  60. package/imaging/{imageColormaps.js → imageColormaps.ts} +48 -30
  61. package/imaging/{imageContours.js → imageContours.ts} +24 -22
  62. package/imaging/{imageIo.js → imageIo.ts} +89 -52
  63. package/imaging/{imageLayers.js → imageLayers.ts} +31 -14
  64. package/imaging/{imageLoading.js → imageLoading.ts} +108 -45
  65. package/imaging/{imageParsing.js → imageParsing.ts} +158 -80
  66. package/imaging/{imagePresets.js → imagePresets.ts} +44 -11
  67. package/imaging/imageRendering.ts +1091 -0
  68. package/imaging/{imageReslice.js → imageReslice.ts} +18 -9
  69. package/imaging/imageStore.ts +487 -0
  70. package/imaging/imageTags.ts +609 -0
  71. package/imaging/imageTools.js +2 -1
  72. package/imaging/{imageUtils.js → imageUtils.ts} +211 -701
  73. package/imaging/loaders/{commonLoader.js → commonLoader.ts} +73 -24
  74. package/imaging/loaders/{dicomLoader.js → dicomLoader.ts} +25 -5
  75. package/imaging/loaders/{fileLoader.js → fileLoader.ts} +5 -5
  76. package/imaging/loaders/{multiframeLoader.js → multiframeLoader.ts} +145 -90
  77. package/imaging/loaders/{nrrdLoader.js → nrrdLoader.ts} +230 -64
  78. package/imaging/loaders/{resliceLoader.js → resliceLoader.ts} +51 -20
  79. package/imaging/monitors/{memory.js → memory.ts} +54 -8
  80. package/imaging/monitors/performance.ts +34 -0
  81. package/imaging/parsers/ecg.ts +51 -0
  82. package/imaging/tools/README.md +27 -0
  83. package/imaging/tools/custom/4dSliceScrollTool.js +47 -46
  84. package/imaging/tools/custom/ellipticalRoiOverlayTool.js +534 -0
  85. package/imaging/tools/custom/polylineScissorsTool.js +1 -1
  86. package/imaging/tools/custom/rectangleRoiOverlayTool.js +564 -0
  87. package/imaging/tools/{setLabelMap3D.js → custom/setLabelMap3D.ts} +19 -25
  88. package/imaging/tools/{default.js → default.ts} +114 -30
  89. package/imaging/tools/{interaction.js → interaction.ts} +42 -23
  90. package/imaging/tools/{io.js → io.ts} +47 -31
  91. package/imaging/tools/{main.js → main.ts} +105 -40
  92. package/imaging/tools/{segmentation.js → segmentation.ts} +95 -68
  93. package/imaging/tools/{state.js → state.ts} +7 -12
  94. package/imaging/tools/types.d.ts +243 -0
  95. package/imaging/types.d.ts +197 -0
  96. package/{index.js → index.ts} +43 -14
  97. package/jsdoc.json +1 -1
  98. package/package.json +32 -14
  99. package/tsconfig.json +102 -0
  100. package/imaging/imageRendering.js +0 -860
  101. package/imaging/imageStore.js +0 -322
  102. package/modules/vuex/larvitar.js +0 -187
  103. /package/imaging/tools/{polygonSegmentationMixin.js → custom/polygonSegmentationMixin.js} +0 -0
@@ -3,17 +3,22 @@
3
3
  */
4
4
 
5
5
  // external libraries
6
- import { parseDicom } from "dicom-parser";
6
+ import { DataSet, parseDicom } from "dicom-parser";
7
7
  import { forEach, each, has, pick } from "lodash";
8
8
  import { v4 as uuidv4 } from "uuid";
9
9
 
10
10
  // internal libraries
11
- import { getPixelRepresentation, randomId, parseTag } from "./imageUtils.js";
12
- import { updateLoadedStack } from "./imageLoading.js";
13
- import { checkMemoryAllocation } from "./monitors/memory.js";
11
+ import { getPixelRepresentation, randomId } from "./imageUtils";
12
+ import { parseTag } from "./imageTags";
13
+ import { updateLoadedStack } from "./imageLoading";
14
+ import { checkMemoryAllocation } from "./monitors/memory";
15
+ import { ImageObject, Instance, MetaData, Series } from "./types";
16
+ import { getLarvitarManager } from "./loaders/commonLoader";
17
+ import type { MetaDataTypes } from "./MetaDataTypes";
18
+ import { NrrdSeries } from "./loaders/nrrdLoader";
14
19
 
15
20
  // global module variables
16
- var t0 = null; // t0 variable for timing debugging purpose
21
+ var t0: number; // t0 variable for timing debugging purpose
17
22
 
18
23
  /*
19
24
  * This module provides the following functions to be exported:
@@ -29,14 +34,18 @@ var t0 = null; // t0 variable for timing debugging purpose
29
34
  * @function clearImageParsing
30
35
  * @param {Object} seriesStack - Parsed series stack object
31
36
  */
32
- export const clearImageParsing = function (seriesStack) {
33
- each(seriesStack, function (stack) {
34
- each(stack.instances, function (instance) {
37
+ export const clearImageParsing = function (
38
+ seriesStack: ReturnType<typeof getLarvitarManager> | null
39
+ ) {
40
+ each(seriesStack, function (stack: Series | NrrdSeries) {
41
+ each(stack.instances, function (instance: Instance) {
35
42
  if (instance.dataSet) {
43
+ // @ts-ignore
36
44
  instance.dataSet.byteArray = null;
37
45
  }
38
46
  instance.dataSet = null;
39
47
  instance.file = null;
48
+ // @ts-ignore
40
49
  instance.metadata = null;
41
50
  });
42
51
  });
@@ -50,11 +59,8 @@ export const clearImageParsing = function (seriesStack) {
50
59
  * @param {Array} entries - List of file objects
51
60
  * @returns {Promise} - Return a promise which will resolve to a image object list or fail if an error occurs
52
61
  */
53
- export const readFiles = function (entries) {
54
- let promise = new Promise((resolve, reject) => {
55
- parseFiles(entries).then(resolve).catch(reject);
56
- });
57
- return promise;
62
+ export const readFiles = function (entries: File[]) {
63
+ return parseFiles(entries);
58
64
  };
59
65
 
60
66
  /**
@@ -64,15 +70,24 @@ export const readFiles = function (entries) {
64
70
  * @param {File} entry - File object
65
71
  * @returns {Promise} - Return a promise which will resolve to a image object or fail if an error occurs
66
72
  */
67
- export const readFile = function (entry) {
68
- let promise = new Promise((resolve, reject) => {
69
- parseFile(entry).then(resolve).catch(reject);
70
- });
71
- return promise;
73
+ export const readFile = function (entry: File) {
74
+ return parseFile(entry);
72
75
  };
73
76
 
74
77
  /* Internal module functions */
75
78
 
79
+ //This type is used to instantiate metadata and nested objects in imageParsing in order to allow dynamic setting
80
+ //of MetaDataTypes objects' properties.
81
+ //In other words, metadata : ExtendedMetaDataTypes is useful and substitutes metadata : MetaDataTypes when metadata values
82
+ //aren't called explicitly using metadata['xGGGGEEEEE']
83
+ //and are instead called using TAG as a variable (i.e. metadata[TAG]).
84
+ //It is important to highlight that even if we set metadata[TAG]=tagValue; whose type (of tagValue) is the correct one (returned by ParseTag).
85
+ //if we then extract x=metadata[TAG]; we obtain tagValue : unknown and a casting with : MetaDataType[typeof TAG] is necessary.
86
+
87
+ type ExtendedMetaDataTypes = MetaDataTypes & {
88
+ [key: string]: unknown;
89
+ };
90
+
76
91
  /**
77
92
  * Parse metadata from dicom parser dataSet object
78
93
  * @instance
@@ -82,7 +97,12 @@ export const readFile = function (entry) {
82
97
  * @param {Array} customFilter - Optional filter: {tags:[], frameId: 0}
83
98
  */
84
99
  // This function iterates through dataSet recursively and store tag values into metadata object
85
- export const parseDataSet = function (dataSet, metadata, customFilter) {
100
+
101
+ export const parseDataSet = function (
102
+ dataSet: DataSet,
103
+ metadata: ExtendedMetaDataTypes,
104
+ customFilter?: { tags: string[]; frameId: number }
105
+ ) {
86
106
  // customFilter= {tags:[], frameId:xxx}
87
107
  // the dataSet.elements object contains properties for each element parsed. The name of the property
88
108
  // is based on the elements tag and looks like 'xGGGGEEEE' where GGGG is the group number and EEEE is the
@@ -90,39 +110,60 @@ export const parseDataSet = function (dataSet, metadata, customFilter) {
90
110
  // be named 'x0008103e'. Here we iterate over each property (element) so we can build a string describing its
91
111
  // contents to add to the output array
92
112
  try {
93
- let elements =
94
- customFilter && has(customFilter, "tags")
95
- ? pick(dataSet.elements, customFilter.tags)
96
- : dataSet.elements;
113
+ let elements = dataSet.elements;
114
+
115
+ customFilter && has(customFilter, "tags")
116
+ ? pick(dataSet.elements, customFilter.tags)
117
+ : dataSet.elements;
97
118
  for (let propertyName in elements) {
98
- let element = elements[propertyName];
99
- // Here we check for Sequence items and iterate over them if present. items will not be set in the
119
+ let element = elements[propertyName]; //metadata
120
+ const TAG = propertyName as keyof ExtendedMetaDataTypes;
121
+ // Here we check for Sequence items and iterate over them if present. items will not be set in the
100
122
  // element object for elements that don't have SQ VR type. Note that implicit little endian
101
123
  // sequences will are currently not parsed.
102
124
  if (element.items) {
103
- // each item contains its own data set so we iterate over the items
104
- // and recursively call this function
105
- if (customFilter && has(customFilter, "frameId")) {
106
- let item = element.items[customFilter.frameId];
107
- parseDataSet(item.dataSet, metadata);
108
- } else {
109
- element.items.forEach(function (item) {
110
- parseDataSet(item.dataSet, metadata);
111
- });
112
- }
113
- } else {
114
- let tagValue = parseTag(dataSet, propertyName, element);
125
+ let nestedArray: MetaDataTypes[] = [];
115
126
 
127
+ // iterates over nested elements (nested metadata)
128
+ element.items.forEach(function (item) {
129
+ let nestedObject: ExtendedMetaDataTypes = {};
130
+ for (let nestedPropertyName in item.dataSet!.elements) {
131
+ let TAG_tagValue = nestedPropertyName as keyof MetaDataTypes;
132
+
133
+ let tagValue = parseTag<MetaDataTypes[typeof TAG_tagValue]>(
134
+ item.dataSet!,
135
+ nestedPropertyName,
136
+ item.dataSet!.elements[nestedPropertyName]
137
+ );
138
+
139
+ let TAG_nested = nestedPropertyName as keyof ExtendedMetaDataTypes;
140
+ nestedObject[TAG_nested] = tagValue;
141
+ //see MetaDataTypes.ts last property to understand how this dynamic value setting is possible
142
+ }
143
+ nestedArray.push(nestedObject);
144
+ });
145
+ metadata[TAG] = nestedArray;
146
+ } else {
147
+ let TAG_tagValue = propertyName as keyof MetaDataTypes;
148
+ let tagValue = parseTag<MetaDataTypes[typeof TAG_tagValue]>(
149
+ dataSet,
150
+ propertyName,
151
+ element
152
+ );
153
+ let TAG = propertyName as keyof ExtendedMetaDataTypes;
116
154
  // identify duplicated tags (keep the first occurency and store the others in another tag eg x00280010_uuid)
117
- if (metadata[propertyName] !== undefined) {
155
+ if (metadata[TAG] !== undefined) {
118
156
  console.debug(
119
157
  `Identified duplicated tag "${propertyName}", values are:`,
120
- metadata[propertyName],
158
+ metadata[TAG],
121
159
  tagValue
122
160
  );
123
- metadata[propertyName + "_" + uuidv4()] = tagValue;
161
+ let TAG_uuidv4 = (propertyName +
162
+ "_" +
163
+ uuidv4()) as keyof ExtendedMetaDataTypes;
164
+ metadata[TAG_uuidv4] = tagValue;
124
165
  } else {
125
- metadata[propertyName] = tagValue;
166
+ metadata[TAG] = tagValue;
126
167
  }
127
168
  }
128
169
  }
@@ -142,11 +183,11 @@ export const parseDataSet = function (dataSet, metadata, customFilter) {
142
183
  * @param {Function} reject - Promise reject function
143
184
  */
144
185
  let parseNextFile = function (
145
- parsingQueue,
146
- allSeriesStack,
147
- uuid,
148
- resolve,
149
- reject
186
+ parsingQueue: File[],
187
+ allSeriesStack: ReturnType<typeof getLarvitarManager>,
188
+ uuid: string,
189
+ resolve: Function,
190
+ reject: Function
150
191
  ) {
151
192
  // initialize t0 on first file of the queue
152
193
  if (
@@ -164,7 +205,12 @@ let parseNextFile = function (
164
205
  }
165
206
 
166
207
  // remove and return first item from queue
167
- let file = parsingQueue.shift();
208
+ let file = parsingQueue.shift() as File | undefined | null;
209
+
210
+ if (!file) {
211
+ console.warn("File is undefined or null");
212
+ return;
213
+ }
168
214
 
169
215
  // Check if there is enough memory to parse the file
170
216
  if (checkMemoryAllocation(file.size) === false) {
@@ -178,18 +224,18 @@ let parseNextFile = function (
178
224
  } else {
179
225
  // parse the file and wait for results
180
226
  parseFile(file)
181
- .then(seriesData => {
227
+ .then((seriesData: ImageObject | null) => {
182
228
  // use generated series uid if not found in dicom file
183
- seriesData.metadata.seriesUID = seriesData.metadata.seriesUID || uuid;
229
+ seriesData!.metadata.seriesUID = seriesData!.metadata.seriesUID || uuid;
184
230
  // add file to cornerstoneDICOMImageLoader file manager
185
- updateLoadedStack(seriesData, allSeriesStack);
231
+ updateLoadedStack(seriesData!, allSeriesStack);
186
232
  // proceed with the next file to parse
187
233
  parseNextFile(parsingQueue, allSeriesStack, uuid, resolve, reject);
188
234
  seriesData = null;
189
235
  file = null;
190
236
  })
191
237
  .catch(err => {
192
- console.warn(err);
238
+ console.error(err);
193
239
  parseNextFile(parsingQueue, allSeriesStack, uuid, resolve, reject);
194
240
  file = null;
195
241
  });
@@ -203,11 +249,11 @@ let parseNextFile = function (
203
249
  * @param {Array} fileList - Array of file objects
204
250
  * @returns {Promise} - Return a promise which will resolve to a image object list or fail if an error occurs
205
251
  */
206
- let parseFiles = function (fileList) {
252
+ const parseFiles = function (fileList: File[]) {
207
253
  let allSeriesStack = {};
208
- let parsingQueue = [];
254
+ let parsingQueue: File[] = [];
209
255
 
210
- forEach(fileList, function (file) {
256
+ forEach(fileList, function (file: File) {
211
257
  if (!file.name.startsWith(".") && !file.name.startsWith("DICOMDIR")) {
212
258
  parsingQueue.push(file);
213
259
  }
@@ -225,45 +271,66 @@ let parseFiles = function (fileList) {
225
271
  * @param {File} file - File object to be parsed
226
272
  * @returns {Promise} - Return a promise which will resolve to a image object or fail if an error occurs
227
273
  */
228
- let parseFile = function (file) {
229
- let parsePromise = new Promise((resolve, reject) => {
274
+ const parseFile = function (file: File) {
275
+ const parsePromise = new Promise<ImageObject>((resolve, reject) => {
230
276
  let reader = new FileReader();
231
277
  reader.onload = function () {
232
278
  let arrayBuffer = reader.result;
233
279
  // Here we have the file data as an ArrayBuffer.
234
280
  // dicomParser requires as input a Uint8Array so we create that here.
235
- let byteArray = new Uint8Array(arrayBuffer);
281
+
282
+ if (!arrayBuffer || typeof arrayBuffer === "string") {
283
+ reject("Error reading file");
284
+ return;
285
+ }
286
+
287
+ let byteArray: Uint8Array | null = new Uint8Array(arrayBuffer);
236
288
  let dataSet;
237
289
 
290
+ // this try-catch is used to handle non-DICOM files: log error but continue parsing the other files
238
291
  try {
239
292
  dataSet = parseDicom(byteArray);
240
293
  byteArray = null;
241
- let metadata = {};
294
+ let metadata: MetaData = {};
242
295
  parseDataSet(dataSet, metadata);
243
296
 
244
297
  let temporalPositionIdentifier = metadata["x00200100"]; // Temporal order of a dynamic or functional set of Images.
245
298
  let numberOfTemporalPositions = metadata["x00200105"]; // Total number of temporal positions prescribed.
246
299
  const is4D =
247
- (temporalPositionIdentifier !== undefined) &
248
- (numberOfTemporalPositions > 1)
300
+ temporalPositionIdentifier !== undefined &&
301
+ (numberOfTemporalPositions as number) > 1
249
302
  ? true
250
303
  : false;
251
304
 
252
- let numberOfFrames = metadata["x00280008"];
253
- let isMultiframe = numberOfFrames > 1 ? true : false;
254
- // Overwrite SOPInstanceUID to manage multiframes.
305
+ let modality = metadata["x00080060"] as string;
306
+ let singleFrameModalities = [
307
+ "CR",
308
+ "DX",
309
+ "MG",
310
+ "PX",
311
+ "RF",
312
+ "XA",
313
+ "US",
314
+ "IVUS",
315
+ "OCT"
316
+ ];
317
+ // US XA RF IVUS OCT DX CR PX MG
318
+ // Overwrite SOPInstanceUID to manage single stack images (US, XA).
255
319
  // Usually different SeriesInstanceUID means different series and that value
256
320
  // is used into the application to group different instances into the same series,
257
321
  // but if a DICOM file contains a multiframe series, then the SeriesInstanceUID
258
322
  // can be shared by other files of the same study.
259
- // In multiframe cases, the SOPInstanceUID (unique) is used as SeriesInstanceUID.
260
- let seriesInstanceUID = isMultiframe
323
+ // In these cases, the SOPInstanceUID (unique) is used as SeriesInstanceUID.
324
+ let uniqueId = singleFrameModalities.includes(modality)
261
325
  ? metadata["x00080018"]
262
326
  : metadata["x0020000e"];
327
+ let seriesInstanceUID = metadata["x0020000e"];
263
328
  let pixelSpacing = metadata["x00280030"];
264
329
  let imageOrientation = metadata["x00200037"];
265
330
  let imagePosition = metadata["x00200032"];
266
331
  let sliceThickness = metadata["x00180050"];
332
+ let numberOfFrames = metadata["x00280008"];
333
+ let isMultiframe = (numberOfFrames as number) > 1 ? true : false;
267
334
 
268
335
  if (dataSet.warnings.length > 0) {
269
336
  // warnings
@@ -274,24 +341,28 @@ let parseFile = function (file) {
274
341
  if (pixelDataElement) {
275
342
  // done, pixelDataElement found
276
343
  let instanceUID = metadata["x00080018"] || randomId();
277
- let imageObject = {
344
+ let imageObject: Partial<ImageObject> = {
278
345
  // data needed for rendering
279
346
  file: file,
280
347
  dataSet: dataSet
281
348
  };
282
- imageObject.metadata = metadata;
349
+ imageObject.metadata = metadata as MetaData;
283
350
  imageObject.metadata.anonymized = false;
351
+ imageObject.metadata.larvitarSeriesInstanceUID = uniqueId;
284
352
  imageObject.metadata.seriesUID = seriesInstanceUID;
285
353
  imageObject.metadata.instanceUID = instanceUID;
286
354
  imageObject.metadata.studyUID = metadata["x0020000d"];
287
355
  imageObject.metadata.accessionNumber = metadata["x00080050"];
288
356
  imageObject.metadata.studyDescription = metadata["x00081030"];
289
- imageObject.metadata.patientName = metadata["x00100010"];
357
+ imageObject.metadata.patientName = metadata["x00100010"] as string;
290
358
  imageObject.metadata.patientBirthdate = metadata["x00100030"];
291
- imageObject.metadata.seriesDescription = metadata["x0008103e"];
359
+ imageObject.metadata.seriesDescription = metadata[
360
+ "x0008103e"
361
+ ] as string;
292
362
  imageObject.metadata.seriesDate = metadata["x00080021"];
293
- imageObject.metadata.seriesModality =
294
- metadata["x00080060"].toLowerCase();
363
+ imageObject.metadata.seriesModality = metadata["x00080060"]
364
+ ?.toString()
365
+ .toLowerCase();
295
366
  imageObject.metadata.intercept = metadata["x00281052"];
296
367
  imageObject.metadata.slope = metadata["x00281053"];
297
368
  imageObject.metadata.pixelSpacing = pixelSpacing;
@@ -307,6 +378,9 @@ let parseFile = function (file) {
307
378
  if (isMultiframe) {
308
379
  imageObject.metadata.frameTime = metadata["x00181063"];
309
380
  imageObject.metadata.frameDelay = metadata["x00181066"];
381
+ if (metadata["x00186060"]) {
382
+ imageObject.metadata.rWaveTimeVector = metadata["x00186060"];
383
+ }
310
384
  }
311
385
  imageObject.metadata.isMultiframe = isMultiframe;
312
386
  if (is4D) {
@@ -323,38 +397,42 @@ let parseFile = function (file) {
323
397
  imageObject.metadata.maxPixelValue = metadata["x00280107"];
324
398
  imageObject.metadata.length = pixelDataElement.length;
325
399
  imageObject.metadata.repr = getPixelRepresentation(dataSet);
326
- resolve(imageObject);
400
+ resolve(imageObject as ImageObject);
327
401
  } else if (SOPUID == "1.2.840.10008.5.1.4.1.1.104.1") {
328
- let pdfObject = {
402
+ let pdfObject: Partial<ImageObject> = {
329
403
  // data needed for rendering
330
404
  file: file,
331
405
  dataSet: dataSet
332
406
  };
333
407
  pdfObject.metadata = metadata;
408
+ pdfObject.metadata.larvitarSeriesInstanceUID = uniqueId;
334
409
  pdfObject.metadata.seriesUID = seriesInstanceUID;
335
- pdfObject.instanceUID = metadata["x00080018"] || randomId();
410
+ pdfObject.instanceUID =
411
+ metadata["x00080018"]?.toString() || randomId();
336
412
  pdfObject.metadata.studyUID = metadata["x0020000d"];
337
413
  pdfObject.metadata.accessionNumber = metadata["x00080050"];
338
414
  pdfObject.metadata.studyDescription = metadata["x00081030"];
339
- pdfObject.metadata.patientName = metadata["x00100010"];
415
+ pdfObject.metadata.patientName = metadata["x00100010"] as string;
340
416
  pdfObject.metadata.patientBirthdate = metadata["x00100030"];
341
417
  pdfObject.metadata.seriesDate = metadata["x00080021"];
342
- pdfObject.metadata.seriesModality =
343
- metadata["x00080060"].toLowerCase();
418
+ pdfObject.metadata.seriesModality = metadata["x00080060"]
419
+ ?.toString()
420
+ .toLowerCase();
344
421
  pdfObject.metadata.mimeType = metadata["x00420012"];
345
422
  pdfObject.metadata.is4D = false;
346
423
  pdfObject.metadata.numberOfFrames = 0;
347
424
  pdfObject.metadata.numberOfSlices = 0;
348
425
  pdfObject.metadata.numberOfTemporalPositions = 0;
349
- resolve(pdfObject);
426
+ resolve(pdfObject as ImageObject);
350
427
  } else {
351
428
  // done, no pixelData
352
429
  reject("no pixelData");
353
430
  }
354
431
  }
355
432
  } catch (err) {
356
- console.warn(err);
357
- reject("can not read this file");
433
+ reject(
434
+ `Larvitar: can not read file "${file.name}" \nParsing error: ${err}`
435
+ );
358
436
  }
359
437
  };
360
438
  reader.readAsArrayBuffer(file);
@@ -8,7 +8,7 @@ import cornerstone from "cornerstone-core";
8
8
  import { each, find } from "lodash";
9
9
 
10
10
  // internal libraries
11
- import { larvitar_store } from "./imageStore";
11
+ import { set as setStore } from "./imageStore";
12
12
 
13
13
  /**
14
14
  * Object used to list image presets
@@ -46,24 +46,35 @@ export const getImagePresets = function () {
46
46
  * @instance
47
47
  * @function setImagePreset
48
48
  * @param {Array} viewportNames - List of viewports where to apply preset
49
- * @param {String} preset_name - The image preset name
49
+ * @param {String} preset - The image preset name or the preset object
50
50
  */
51
- export const setImagePreset = function (viewportNames, preset_name) {
51
+ export const setImagePreset = function (
52
+ viewportNames: string[],
53
+ preset: string | (typeof IMAGE_PRESETS)[0]
54
+ ) {
52
55
  if (!Array.isArray(viewportNames)) {
53
56
  console.error(
54
57
  "Invalid parameter, viewportNames has to be an array of viewport names."
55
58
  );
56
59
  return;
57
60
  }
58
- let image_preset = find(IMAGE_PRESETS, { name: preset_name });
61
+ let image_preset =
62
+ typeof preset === "string" ? find(IMAGE_PRESETS, { name: preset }) : preset;
63
+
59
64
  if (!image_preset) {
60
65
  console.error("Invalid image preset");
61
66
  return;
62
67
  }
63
- each(viewportNames, function (viewportName) {
68
+
69
+ each(viewportNames, function (viewportName: string) {
64
70
  let element = document.getElementById(viewportName);
65
71
  let enabledElement;
66
72
 
73
+ if (!element) {
74
+ console.warn("No element with id", viewportName);
75
+ return;
76
+ }
77
+
67
78
  try {
68
79
  enabledElement = cornerstone.getEnabledElement(element);
69
80
  } catch {
@@ -72,11 +83,18 @@ export const setImagePreset = function (viewportNames, preset_name) {
72
83
  }
73
84
 
74
85
  let viewport = cornerstone.getViewport(element);
75
- viewport.voi.windowWidth = image_preset.ww;
76
- viewport.voi.windowCenter = image_preset.wl;
86
+
87
+ if (!viewport) {
88
+ console.warn("No viewport with id", viewportName);
89
+ return;
90
+ }
91
+
92
+ viewport.voi.windowWidth = image_preset!.ww;
93
+ viewport.voi.windowCenter = image_preset!.wl;
77
94
  cornerstone.setViewport(element, viewport);
78
95
  // sync ww and wc values in store
79
- larvitar_store.set("contrast", [
96
+ setStore([
97
+ "contrast",
80
98
  viewportName,
81
99
  viewport.voi.windowWidth,
82
100
  viewport.voi.windowCenter
@@ -91,17 +109,25 @@ export const setImagePreset = function (viewportNames, preset_name) {
91
109
  * @param {Array} viewportNames - List of viewports where to apply preset
92
110
  * @param {Object} customValues - {wl: value, ww: value}
93
111
  */
94
- export const setImageCustomPreset = function (viewportNames, customValues) {
112
+ export const setImageCustomPreset = function (
113
+ viewportNames: string[],
114
+ customValues: { wl: number; ww: number }
115
+ ) {
95
116
  if (!Array.isArray(viewportNames)) {
96
117
  console.error(
97
118
  "Invalid parameter, viewportNames has to be an array of viewport names."
98
119
  );
99
120
  return;
100
121
  }
101
- each(viewportNames, function (viewportName) {
122
+ each(viewportNames, function (viewportName: string) {
102
123
  let element = document.getElementById(viewportName);
103
124
  let enabledElement;
104
125
 
126
+ if (!element) {
127
+ console.warn("No element with id", viewportName);
128
+ return;
129
+ }
130
+
105
131
  try {
106
132
  enabledElement = cornerstone.getEnabledElement(element);
107
133
  } catch {
@@ -110,11 +136,18 @@ export const setImageCustomPreset = function (viewportNames, customValues) {
110
136
  }
111
137
 
112
138
  let viewport = cornerstone.getViewport(element);
139
+
140
+ if (!viewport) {
141
+ console.warn("No viewport with id", viewportName);
142
+ return;
143
+ }
144
+
113
145
  viewport.voi.windowWidth = customValues.ww;
114
146
  viewport.voi.windowCenter = customValues.wl;
115
147
  cornerstone.setViewport(element, viewport);
116
148
  // sync ww and wc values in store
117
- larvitar_store.set("contrast", [
149
+ setStore([
150
+ "contrast",
118
151
  viewportName,
119
152
  viewport.voi.windowWidth,
120
153
  viewport.voi.windowCenter