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
@@ -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,67 @@ 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;
334
+ let waveform = metadata["x50003000"] ? true : false;
267
335
 
268
336
  if (dataSet.warnings.length > 0) {
269
337
  // warnings
@@ -274,24 +342,28 @@ let parseFile = function (file) {
274
342
  if (pixelDataElement) {
275
343
  // done, pixelDataElement found
276
344
  let instanceUID = metadata["x00080018"] || randomId();
277
- let imageObject = {
345
+ let imageObject: Partial<ImageObject> = {
278
346
  // data needed for rendering
279
347
  file: file,
280
348
  dataSet: dataSet
281
349
  };
282
- imageObject.metadata = metadata;
350
+ imageObject.metadata = metadata as MetaData;
283
351
  imageObject.metadata.anonymized = false;
352
+ imageObject.metadata.larvitarSeriesInstanceUID = uniqueId;
284
353
  imageObject.metadata.seriesUID = seriesInstanceUID;
285
354
  imageObject.metadata.instanceUID = instanceUID;
286
355
  imageObject.metadata.studyUID = metadata["x0020000d"];
287
356
  imageObject.metadata.accessionNumber = metadata["x00080050"];
288
357
  imageObject.metadata.studyDescription = metadata["x00081030"];
289
- imageObject.metadata.patientName = metadata["x00100010"];
358
+ imageObject.metadata.patientName = metadata["x00100010"] as string;
290
359
  imageObject.metadata.patientBirthdate = metadata["x00100030"];
291
- imageObject.metadata.seriesDescription = metadata["x0008103e"];
360
+ imageObject.metadata.seriesDescription = metadata[
361
+ "x0008103e"
362
+ ] as string;
292
363
  imageObject.metadata.seriesDate = metadata["x00080021"];
293
- imageObject.metadata.seriesModality =
294
- metadata["x00080060"].toLowerCase();
364
+ imageObject.metadata.seriesModality = metadata["x00080060"]
365
+ ?.toString()
366
+ .toLowerCase();
295
367
  imageObject.metadata.intercept = metadata["x00281052"];
296
368
  imageObject.metadata.slope = metadata["x00281053"];
297
369
  imageObject.metadata.pixelSpacing = pixelSpacing;
@@ -307,6 +379,9 @@ let parseFile = function (file) {
307
379
  if (isMultiframe) {
308
380
  imageObject.metadata.frameTime = metadata["x00181063"];
309
381
  imageObject.metadata.frameDelay = metadata["x00181066"];
382
+ if (metadata["x00186060"]) {
383
+ imageObject.metadata.rWaveTimeVector = metadata["x00186060"];
384
+ }
310
385
  }
311
386
  imageObject.metadata.isMultiframe = isMultiframe;
312
387
  if (is4D) {
@@ -317,44 +392,49 @@ let parseFile = function (file) {
317
392
  imageObject.metadata.contentTime = metadata["x00080033"];
318
393
  }
319
394
  imageObject.metadata.is4D = is4D;
395
+ imageObject.metadata.waveform = waveform;
320
396
  imageObject.metadata.windowCenter = metadata["x00281050"];
321
397
  imageObject.metadata.windowWidth = metadata["x00281051"];
322
398
  imageObject.metadata.minPixelValue = metadata["x00280106"];
323
399
  imageObject.metadata.maxPixelValue = metadata["x00280107"];
324
400
  imageObject.metadata.length = pixelDataElement.length;
325
401
  imageObject.metadata.repr = getPixelRepresentation(dataSet);
326
- resolve(imageObject);
402
+ resolve(imageObject as ImageObject);
327
403
  } else if (SOPUID == "1.2.840.10008.5.1.4.1.1.104.1") {
328
- let pdfObject = {
404
+ let pdfObject: Partial<ImageObject> = {
329
405
  // data needed for rendering
330
406
  file: file,
331
407
  dataSet: dataSet
332
408
  };
333
409
  pdfObject.metadata = metadata;
410
+ pdfObject.metadata.larvitarSeriesInstanceUID = uniqueId;
334
411
  pdfObject.metadata.seriesUID = seriesInstanceUID;
335
- pdfObject.instanceUID = metadata["x00080018"] || randomId();
412
+ pdfObject.instanceUID =
413
+ metadata["x00080018"]?.toString() || randomId();
336
414
  pdfObject.metadata.studyUID = metadata["x0020000d"];
337
415
  pdfObject.metadata.accessionNumber = metadata["x00080050"];
338
416
  pdfObject.metadata.studyDescription = metadata["x00081030"];
339
- pdfObject.metadata.patientName = metadata["x00100010"];
417
+ pdfObject.metadata.patientName = metadata["x00100010"] as string;
340
418
  pdfObject.metadata.patientBirthdate = metadata["x00100030"];
341
419
  pdfObject.metadata.seriesDate = metadata["x00080021"];
342
- pdfObject.metadata.seriesModality =
343
- metadata["x00080060"].toLowerCase();
420
+ pdfObject.metadata.seriesModality = metadata["x00080060"]
421
+ ?.toString()
422
+ .toLowerCase();
344
423
  pdfObject.metadata.mimeType = metadata["x00420012"];
345
424
  pdfObject.metadata.is4D = false;
346
425
  pdfObject.metadata.numberOfFrames = 0;
347
426
  pdfObject.metadata.numberOfSlices = 0;
348
427
  pdfObject.metadata.numberOfTemporalPositions = 0;
349
- resolve(pdfObject);
428
+ resolve(pdfObject as ImageObject);
350
429
  } else {
351
430
  // done, no pixelData
352
431
  reject("no pixelData");
353
432
  }
354
433
  }
355
434
  } catch (err) {
356
- console.warn(err);
357
- reject("can not read this file");
435
+ reject(
436
+ `Larvitar: can not read file "${file.name}" \nParsing error: ${err}`
437
+ );
358
438
  }
359
439
  };
360
440
  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