larvitar 0.20.0 → 1.2.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 (45) hide show
  1. package/.github/workflows/build-docs.yml +1 -1
  2. package/.github/workflows/deploy.yml +2 -11
  3. package/MIGRATION.md +25 -0
  4. package/README.md +28 -27
  5. package/imaging/dataDictionary.json +21865 -21865
  6. package/imaging/{image_anonymization.js → imageAnonymization.js} +1 -1
  7. package/imaging/{image_colormaps.js → imageColormaps.js} +2 -2
  8. package/imaging/{image_contours.js → imageContours.js} +1 -2
  9. package/imaging/{image_io.js → imageIo.js} +18 -15
  10. package/imaging/{image_layers.js → imageLayers.js} +2 -2
  11. package/imaging/{image_loading.js → imageLoading.js} +9 -6
  12. package/imaging/imageParsing.js +301 -0
  13. package/imaging/{image_presets.js → imagePresets.js} +2 -2
  14. package/imaging/{image_rendering.js → imageRendering.js} +36 -32
  15. package/imaging/imageReslice.js +78 -0
  16. package/imaging/{image_store.js → imageStore.js} +24 -7
  17. package/imaging/{image_tools.js → imageTools.js} +15 -23
  18. package/imaging/{image_utils.js → imageUtils.js} +1 -1
  19. package/imaging/loaders/commonLoader.js +1 -1
  20. package/imaging/loaders/dicomLoader.js +1 -1
  21. package/imaging/loaders/fileLoader.js +2 -2
  22. package/imaging/loaders/multiframeLoader.js +6 -2
  23. package/imaging/loaders/nrrdLoader.js +11 -7
  24. package/imaging/tools/{contourTool.js → custom/contourTool.js} +25 -20
  25. package/imaging/tools/{diameterTool.js → custom/diameterTool.js} +9 -3
  26. package/imaging/tools/{editMaskTool.js → custom/editMaskTool.js} +7 -1
  27. package/imaging/tools/{polylineScissorsTool.js → custom/polylineScissorsTool.js} +12 -5
  28. package/imaging/tools/{seedTool.js → custom/seedTool.js} +3 -3
  29. package/imaging/tools/{thresholdsBrushTool.js → custom/thresholdsBrushTool.js} +7 -1
  30. package/imaging/tools/{tools.default.js → default.js} +9 -2
  31. package/imaging/tools/{tools.interaction.js → interaction.js} +13 -6
  32. package/imaging/tools/{tools.io.js → io.js} +15 -6
  33. package/imaging/tools/{tools.main.js → main.js} +14 -13
  34. package/imaging/tools/polygonSegmentationMixin.js +8 -4
  35. package/imaging/tools/{tools.segmentation.js → segmentation.js} +171 -58
  36. package/imaging/tools/segmentations.md +38 -0
  37. package/imaging/tools/setLabelMap3D.js +248 -0
  38. package/imaging/tools/{tools.state.js → state.js} +7 -1
  39. package/imaging/tools/strategies/eraseFreehand.js +8 -9
  40. package/imaging/tools/strategies/fillFreehand.js +8 -9
  41. package/index.js +41 -39
  42. package/modules/vuex/larvitar.js +2 -1
  43. package/package.json +11 -8
  44. package/imaging/image_parsing.js +0 -307
  45. package/imaging/image_reslice.js +0 -80
@@ -1,4 +1,4 @@
1
- /** @module imaging/anonymization
1
+ /** @module imaging/imageAnonymization
2
2
  * @desc This file provides anonymization functionalities on DICOM images
3
3
  * following http://dicom.nema.org/medical/dicom/current/output/html/part15.html#chapter_E
4
4
  */
@@ -1,6 +1,6 @@
1
- /** @module imaging/rendering
1
+ /** @module imaging/imageColormaps
2
2
  * @desc This file provides functionalities for
3
- * rendering images in html canvas using cornerstone
3
+ * handling colormaps
4
4
  */
5
5
 
6
6
  import cornerstone from "cornerstone-core";
@@ -1,4 +1,4 @@
1
- /** @module imaging/contours
1
+ /** @module imaging/imageContours
2
2
  * @desc This file provides functionalities to render a set of points on a canvas.
3
3
  * Use this in order to render image contours (e.g. from binary masks).
4
4
  */
@@ -21,7 +21,6 @@ import { each, range } from "lodash";
21
21
  * @param {Array} viewports - Viewport array ids
22
22
  * @returns {Number} Number of array elements consumed
23
23
  */
24
-
25
24
  export const parseContours = function (
26
25
  contoursData,
27
26
  pointBatchSize,
@@ -1,4 +1,4 @@
1
- /** @module imaging/io
1
+ /** @module imaging/imageIo
2
2
  * @desc This file provides I/O functionalities on NRRD files and DICOM images
3
3
  */
4
4
 
@@ -11,8 +11,8 @@ import {
11
11
  getMeanValue,
12
12
  getDistanceBetweenSlices,
13
13
  getTypedArrayFromDataType
14
- } from "./image_utils.js";
15
- import { larvitar_store } from "./image_store";
14
+ } from "./imageUtils.js";
15
+ import { larvitar_store } from "./imageStore";
16
16
  import { parse } from "./parsers/nrrd";
17
17
  import { checkMemoryAllocation } from "./monitors/memory";
18
18
 
@@ -71,22 +71,25 @@ export const buildHeader = function (series) {
71
71
  * Get cached pixel data
72
72
  * @function getCachedPixelData
73
73
  * @param {String} imageId - ImageId of the cached image
74
- * @returns {Array} Pixel data array or null if not cached
74
+ * @returns {Promise} A promise which will resolve to a pixel data array or fail if an error occurs
75
75
  */
76
76
 
77
- export const getCachedPixelData = function (imageId, callback) {
77
+ export const getCachedPixelData = function (imageId) {
78
78
  let cachedImage = find(cornerstone.imageCache.cachedImages, [
79
79
  "imageId",
80
80
  imageId
81
81
  ]);
82
- if (cachedImage && cachedImage.image) {
83
- callback(cachedImage.image.getPixelData());
84
- } else {
85
- cornerstone.loadAndCacheImage(imageId).then(function (image) {
86
- callback(image.getPixelData());
87
- cornerstone.imageCache.removeImageLoadObject(imageId);
88
- });
89
- }
82
+ let promise = new Promise((resolve, reject) => {
83
+ if (cachedImage && cachedImage.image) {
84
+ resolve(cachedImage.image.getPixelData());
85
+ } else {
86
+ cornerstone
87
+ .loadImage(imageId)
88
+ .then(image => resolve(image.getPixelData()))
89
+ .catch(err => reject(err));
90
+ }
91
+ });
92
+ return promise;
90
93
  };
91
94
 
92
95
  /**
@@ -122,7 +125,7 @@ export const buildData = function (series, useSeriesData) {
122
125
  } else {
123
126
  larvitar_store.addSeriesIds(series.seriesUID, series.imageIds);
124
127
  forEach(series.imageIds, function (imageId) {
125
- getCachedPixelData(imageId, function (sliceData) {
128
+ getCachedPixelData(imageId).then(sliceData => {
126
129
  data.set(sliceData, offsetData);
127
130
  offsetData += sliceData.length;
128
131
  });
@@ -166,7 +169,7 @@ export const buildDataAsync = function (series, time, resolve, reject) {
166
169
  function runFillPixelData(data) {
167
170
  let imageId = imageIds.shift();
168
171
  if (imageId) {
169
- getCachedPixelData(imageId, function (sliceData) {
172
+ getCachedPixelData(imageId).then(sliceData => {
170
173
  data.set(sliceData, offsetData);
171
174
  offsetData += sliceData.length;
172
175
  // this does the trick: delay next computation to next tick
@@ -1,4 +1,4 @@
1
- /** @module imaging/layers
1
+ /** @module imaging/imageLayers
2
2
  * @desc This file provides functionalities for
3
3
  * rendering image layers using cornerstone stack
4
4
  */
@@ -7,7 +7,7 @@
7
7
  import cornerstone from "cornerstone-core";
8
8
 
9
9
  // internal libraries
10
- import { isElement } from "./image_utils";
10
+ import { isElement } from "./imageUtils";
11
11
 
12
12
  /*
13
13
  * This module provides the following functions to be exported:
@@ -1,4 +1,4 @@
1
- /** @module imaging/loading
1
+ /** @module imaging/imageLoading
2
2
  * @desc This file provides functionalities for
3
3
  * initialize, configure and update WadoImageLoader
4
4
  */
@@ -12,8 +12,8 @@ import cornerstoneFileImageLoader from "cornerstone-file-image-loader";
12
12
  import { forEach } from "lodash";
13
13
 
14
14
  // internal libraries
15
- import { larvitar_store } from "./image_store";
16
- import { getSortedStack, getSortedUIDs } from "./image_utils";
15
+ import { larvitar_store } from "./imageStore";
16
+ import { getSortedStack, getSortedUIDs } from "./imageUtils";
17
17
  import { loadNrrdImage } from "./loaders/nrrdLoader";
18
18
  import { loadReslicedImage } from "./loaders/resliceLoader";
19
19
  import { loadMultiFrameImage } from "./loaders/multiframeLoader";
@@ -26,16 +26,19 @@ import { loadMultiFrameImage } from "./loaders/multiframeLoader";
26
26
  * @property {String} webWorkerPath - path to default WADO web worker
27
27
  * @property {} - see https://github.com/cornerstonejs/cornerstoneWADOImageLoader/blob/master/docs/WebWorkers.md
28
28
  */
29
+
29
30
  const globalConfig = {
30
31
  maxWebWorkers: navigator.hardwareConcurrency || 1,
31
- webWorkerPath: "/cornerstoneWADOImageLoaderWebWorker.js",
32
32
  startWebWorkersOnDemand: true,
33
+ webWorkerTaskPaths: [
34
+ "https://unpkg.com/cornerstone-wado-image-loader@4.1.0/dist/610.bundle.min.worker.js",
35
+ "https://unpkg.com/cornerstone-wado-image-loader@4.1.0/dist/888.bundle.min.worker.js"
36
+ ],
33
37
  taskConfiguration: {
34
38
  decodeTask: {
35
39
  loadCodecsOnStartup: true,
36
40
  initializeCodecsOnStartup: false,
37
- codecsPath: "/cornerstoneWADOImageLoaderCodecs.js",
38
- usePDFJS: false
41
+ strict: true
39
42
  }
40
43
  }
41
44
  };
@@ -0,0 +1,301 @@
1
+ /** @module imaging/imageParsing
2
+ * @desc This file provides functionalities for parsing DICOM image files
3
+ */
4
+
5
+ // external libraries
6
+ import { parseDicom } from "dicom-parser";
7
+ import { forEach, each, has, pick } from "lodash";
8
+
9
+ // internal libraries
10
+ import { getPixelRepresentation, randomId, parseTag } from "./imageUtils.js";
11
+ import { updateLoadedStack } from "./imageLoading.js";
12
+ import { checkMemoryAllocation } from "./monitors/memory.js";
13
+
14
+ // global module variables
15
+ var t0 = null; // t0 variable for timing debugging purpose
16
+
17
+ /*
18
+ * This module provides the following functions to be exported:
19
+ * readFiles(fileList)
20
+ * readFile(file)
21
+ * parseDataSet(dataSet, metadata, customFilter)
22
+ * clearImageParsing(seriesStack)
23
+ */
24
+
25
+ /**
26
+ * Reset series stack object and its internal data
27
+ * @instance
28
+ * @function clearImageParsing
29
+ * @param {Object} seriesStack - Parsed series stack object
30
+ */
31
+ export const clearImageParsing = function (seriesStack) {
32
+ each(seriesStack, function (stack) {
33
+ each(stack.instances, function (instance) {
34
+ if (instance.dataSet) {
35
+ instance.dataSet.byteArray = null;
36
+ }
37
+ instance.dataSet = null;
38
+ instance.file = null;
39
+ instance.metadata = null;
40
+ });
41
+ });
42
+ seriesStack = null;
43
+ };
44
+
45
+ /**
46
+ * Read dicom files and return allSeriesStack object
47
+ * @instance
48
+ * @function readFiles
49
+ * @param {Array} entries - List of file objects
50
+ * @returns {Promise} - Return a promise which will resolve to a image object list or fail if an error occurs
51
+ */
52
+ export const readFiles = function (entries) {
53
+ let promise = new Promise((resolve, reject) => {
54
+ parseFiles(entries).then(resolve).catch(reject);
55
+ });
56
+ return promise;
57
+ };
58
+
59
+ /**
60
+ * Read a single dicom file and return parsed object
61
+ * @instance
62
+ * @function readFile
63
+ * @param {File} entry - File object
64
+ * @returns {Promise} - Return a promise which will resolve to a image object or fail if an error occurs
65
+ */
66
+ export const readFile = function (entry) {
67
+ let promise = new Promise((resolve, reject) => {
68
+ parseFile(entry).then(resolve).catch(reject);
69
+ });
70
+ return promise;
71
+ };
72
+
73
+ /* Internal module functions */
74
+
75
+ /**
76
+ * Parse metadata from dicom parser dataSet object
77
+ * @instance
78
+ * @function parseDataSet
79
+ * @param {Object} dataSet - dicom parser dataSet object
80
+ * @param {Array} metadata - Initialized metadata object
81
+ * @param {Array} customFilter - Optional filter: {tags:[], frameId: 0}
82
+ */
83
+ // This function iterates through dataSet recursively and adds new HTML strings
84
+ // to the output array passed into it
85
+ export const parseDataSet = function (dataSet, metadata, customFilter) {
86
+ // customFilter= {tags:[], frameId:xxx}
87
+ // the dataSet.elements object contains properties for each element parsed. The name of the property
88
+ // is based on the elements tag and looks like 'xGGGGEEEE' where GGGG is the group number and EEEE is the
89
+ // element number both with lowercase hexadecimal letters. For example, the Series Description DICOM element 0008,103E would
90
+ // be named 'x0008103e'. Here we iterate over each property (element) so we can build a string describing its
91
+ // contents to add to the output array
92
+ try {
93
+ let elements =
94
+ customFilter && has(customFilter, "tags")
95
+ ? pick(dataSet.elements, customFilter.tags)
96
+ : dataSet.elements;
97
+ 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
100
+ // element object for elements that don't have SQ VR type. Note that implicit little endian
101
+ // sequences will are currently not parsed.
102
+ 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);
115
+ metadata[propertyName] = tagValue;
116
+ }
117
+ }
118
+ } catch (err) {
119
+ console.log(err);
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Manage the parsing process waiting for the parsed object before proceeding with the next parse request
125
+ * @inner
126
+ * @function parseNextFile
127
+ * @param {Array} parsingQueue - Array of queued files to be parsed
128
+ * @param {Object} allSeriesStack - Series stack object to be populated
129
+ * @param {Function} resolve - Promise resolve function
130
+ * @param {Function} reject - Promise reject function
131
+ */
132
+ let parseNextFile = function (parsingQueue, allSeriesStack, resolve, reject) {
133
+ // initialize t0 on first file of the queue
134
+ if (
135
+ Object.keys(allSeriesStack).length === 0 &&
136
+ allSeriesStack.constructor === Object
137
+ ) {
138
+ t0 = performance.now();
139
+ }
140
+
141
+ if (parsingQueue.length === 0) {
142
+ let t1 = performance.now();
143
+ console.log(`Call to readFiles took ${t1 - t0} milliseconds.`);
144
+ resolve(allSeriesStack);
145
+ return;
146
+ }
147
+
148
+ // remove and return first item from queue
149
+ let file = parsingQueue.shift();
150
+
151
+ // Check if there is enough memory to parse the file
152
+ if (checkMemoryAllocation(file.size) === false) {
153
+ // do not parse the file and stop parsing
154
+ clearImageParsing(allSeriesStack);
155
+ let t1 = performance.now();
156
+ console.log(`Call to readFiles took ${t1 - t0} milliseconds.`);
157
+ file = null;
158
+ reject(allSeriesStack, "Available memory is not enough");
159
+ return;
160
+ } else {
161
+ // parse the file and wait for results
162
+ parseFile(file)
163
+ .then(seriesData => {
164
+ // add file to cornerstoneWADOImageLoader file manager
165
+ updateLoadedStack(seriesData, allSeriesStack);
166
+ // proceed with the next file to parse
167
+ parseNextFile(parsingQueue, allSeriesStack, resolve, reject);
168
+ seriesData = null;
169
+ file = null;
170
+ })
171
+ .catch(err => {
172
+ console.warn(err);
173
+ parseNextFile(parsingQueue, allSeriesStack, resolve, reject);
174
+ file = null;
175
+ });
176
+ }
177
+ };
178
+
179
+ /**
180
+ * Push files in queue and start parsing next file
181
+ * @inner
182
+ * @function parseFiles
183
+ * @param {Array} fileList - Array of file objects
184
+ * @returns {Promise} - Return a promise which will resolve to a image object list or fail if an error occurs
185
+ */
186
+ let parseFiles = function (fileList) {
187
+ let allSeriesStack = {};
188
+ let parsingQueue = [];
189
+
190
+ forEach(fileList, function (file) {
191
+ if (!file.name.startsWith(".") && !file.name.startsWith("DICOMDIR")) {
192
+ parsingQueue.push(file);
193
+ }
194
+ });
195
+ return new Promise((resolve, reject) => {
196
+ parseNextFile(parsingQueue, allSeriesStack, resolve, reject);
197
+ });
198
+ };
199
+
200
+ /**
201
+ * Parse a single DICOM File (metaData and pixelData)
202
+ * @inner
203
+ * @function parseFile
204
+ * @param {File} file - File object to be parsed
205
+ * @returns {Promise} - Return a promise which will resolve to a image object or fail if an error occurs
206
+ */
207
+ let parseFile = function (file) {
208
+ let parsePromise = new Promise((resolve, reject) => {
209
+ let reader = new FileReader();
210
+ reader.onload = function () {
211
+ let arrayBuffer = reader.result;
212
+ // Here we have the file data as an ArrayBuffer.
213
+ // dicomParser requires as input a Uint8Array so we create that here.
214
+ let byteArray = new Uint8Array(arrayBuffer);
215
+ let dataSet;
216
+
217
+ try {
218
+ dataSet = parseDicom(byteArray);
219
+ byteArray = null;
220
+ let metadata = {};
221
+ parseDataSet(dataSet, metadata);
222
+
223
+ let numberOfFrames = metadata["x00280008"];
224
+ let isMultiframe = numberOfFrames > 1 ? true : false;
225
+ // Overwrite SOPInstanceUID to manage multiframes.
226
+ // Usually different SeriesInstanceUID means different series and that value
227
+ // is used into the application to group different instances into the same series,
228
+ // but if a DICOM file contains a multiframe series, then the SeriesInstanceUID
229
+ // can be shared by other files of the same study.
230
+ // In multiframe cases, the SOPInstanceUID (unique) is used as SeriesInstanceUID.
231
+ let seriesInstanceUID = isMultiframe
232
+ ? metadata["x00080018"]
233
+ : metadata["x0020000e"];
234
+ let pixelSpacing = metadata["x00280030"];
235
+ let imageOrientation = metadata["x00200037"];
236
+ let imagePosition = metadata["x00200032"];
237
+ let sliceThickness = metadata["x00180050"];
238
+
239
+ if (dataSet.warnings.length > 0) {
240
+ // warnings
241
+ reject(dataSet.warnings);
242
+ } else {
243
+ let pixelDataElement = dataSet.elements.x7fe00010;
244
+
245
+ if (pixelDataElement) {
246
+ // done, pixelDataElement found
247
+ let instanceUID = metadata["x00080018"] || randomId();
248
+ let imageObject = {
249
+ // data needed for rendering
250
+ file: file,
251
+ dataSet: dataSet
252
+ };
253
+ imageObject.metadata = metadata;
254
+ imageObject.metadata.seriesUID = seriesInstanceUID;
255
+ imageObject.metadata.instanceUID = instanceUID;
256
+ imageObject.metadata.studyUID = metadata["x0020000d"];
257
+ imageObject.metadata.accessionNumber = metadata["x00080050"];
258
+ imageObject.metadata.studyDescription = metadata["x00081030"];
259
+ imageObject.metadata.patientName = metadata["x00100010"];
260
+ imageObject.metadata.patientBirthdate = metadata["x00100030"];
261
+ imageObject.metadata.seriesDescription = metadata["x0008103e"];
262
+ imageObject.metadata.seriesDate = metadata["x00080021"];
263
+ imageObject.metadata.seriesModality =
264
+ metadata["x00080060"].toLowerCase();
265
+ imageObject.metadata.intercept = metadata["x00281052"];
266
+ imageObject.metadata.slope = metadata["x00281053"];
267
+ imageObject.metadata.pixelSpacing = pixelSpacing;
268
+ imageObject.metadata.sliceThickness = sliceThickness;
269
+ imageObject.metadata.imageOrientation = imageOrientation;
270
+ imageObject.metadata.imagePosition = imagePosition;
271
+ imageObject.metadata.rows = metadata["x00280010"];
272
+ imageObject.metadata.cols = metadata["x00280011"];
273
+ imageObject.metadata.numberOfSlices = metadata["x00540081"]
274
+ ? metadata["x00540081"] // number of slices
275
+ : metadata["x00201002"]; // number of instances
276
+ imageObject.metadata.numberOfFrames = numberOfFrames;
277
+ if (isMultiframe) {
278
+ imageObject.metadata.frameTime = metadata["x00181063"];
279
+ imageObject.metadata.frameDelay = metadata["x00181066"];
280
+ }
281
+ imageObject.metadata.windowCenter = metadata["x00281050"];
282
+ imageObject.metadata.windowWidth = metadata["x00281051"];
283
+ imageObject.metadata.minPixelValue = metadata["x00280106"];
284
+ imageObject.metadata.maxPixelValue = metadata["x00280107"];
285
+ imageObject.metadata.length = pixelDataElement.length;
286
+ imageObject.metadata.repr = getPixelRepresentation(dataSet);
287
+ resolve(imageObject);
288
+ } else {
289
+ // done, no pixelData
290
+ reject("no pixelData");
291
+ }
292
+ }
293
+ } catch (err) {
294
+ console.error(err);
295
+ reject("can not read this file");
296
+ }
297
+ };
298
+ reader.readAsArrayBuffer(file);
299
+ });
300
+ return parsePromise;
301
+ };
@@ -1,4 +1,4 @@
1
- /** @module imaging/presets
1
+ /** @module imaging/imagePresets
2
2
  * @desc This file provides functionalities for
3
3
  * image presets for ww and wc
4
4
  */
@@ -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 "./image_store";
11
+ import { larvitar_store } from "./imageStore";
12
12
 
13
13
  /**
14
14
  * Object used to list image presets
@@ -1,4 +1,4 @@
1
- /** @module imaging/rendering
1
+ /** @module imaging/imageRendering
2
2
  * @desc This file provides functionalities for
3
3
  * rendering images in html canvas using cornerstone
4
4
  */
@@ -10,16 +10,16 @@ import { each, has } from "lodash";
10
10
 
11
11
  // internal libraries
12
12
  import { getFileImageId } from "./loaders/fileLoader";
13
- import { csToolsCreateStack } from "./tools/tools.main";
14
- import { toggleMouseToolsListeners } from "./tools/tools.interaction";
15
- import { larvitar_store } from "./image_store";
16
- import { applyColorMap } from "./image_colormaps";
17
- import { isElement } from "./image_utils";
13
+ import { csToolsCreateStack } from "./tools/main";
14
+ import { toggleMouseToolsListeners } from "./tools/interaction";
15
+ import { larvitar_store } from "./imageStore";
16
+ import { applyColorMap } from "./imageColormaps";
17
+ import { isElement } from "./imageUtils";
18
18
 
19
19
  /*
20
20
  * This module provides the following functions to be exported:
21
21
  * clearImageCache(seriesId)
22
- * loadAndCacheImages(seriesData, callback)
22
+ * loadAndCacheImages(seriesData)
23
23
  * renderFileImage(file, elementId)
24
24
  * renderWebImage(url, elementId)
25
25
  * disableViewport(elementId)
@@ -121,9 +121,9 @@ export function loadAndCacheImages(series, callback) {
121
121
  * @function renderWebImage
122
122
  * @param {Object} file - The image File object
123
123
  * @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
124
- * @param {Function} callback - Optional callback function with image object
124
+ * @returns {Promise} - Return a promise which will resolve when image is displayed
125
125
  */
126
- export const renderFileImage = function (file, elementId, callback) {
126
+ export const renderFileImage = function (file, elementId) {
127
127
  let element = isElement(elementId)
128
128
  ? elementId
129
129
  : document.getElementById(elementId);
@@ -136,23 +136,23 @@ export const renderFileImage = function (file, elementId, callback) {
136
136
  cornerstone.enable(element);
137
137
  }
138
138
 
139
- // check if imageId is already stored in fileManager
140
- const imageId = getFileImageId(file);
141
- if (imageId) {
142
- cornerstone.loadImage(imageId).then(function (image) {
143
- cornerstone.displayImage(element, image);
144
- let viewport = cornerstone.getViewport(element);
145
- viewport.displayedArea.brhc.x = image.width;
146
- viewport.displayedArea.brhc.y = image.height;
147
- cornerstone.setViewport(element, viewport);
148
- cornerstone.fitToWindow(element);
149
-
150
- csToolsCreateStack(element);
151
- if (callback) {
152
- callback(image);
153
- }
154
- });
155
- }
139
+ let renderPromise = new Promise(resolve => {
140
+ // check if imageId is already stored in fileManager
141
+ const imageId = getFileImageId(file);
142
+ if (imageId) {
143
+ cornerstone.loadImage(imageId).then(function (image) {
144
+ cornerstone.displayImage(element, image);
145
+ let viewport = cornerstone.getViewport(element);
146
+ viewport.displayedArea.brhc.x = image.width;
147
+ viewport.displayedArea.brhc.y = image.height;
148
+ cornerstone.setViewport(element, viewport);
149
+ cornerstone.fitToWindow(element);
150
+ csToolsCreateStack(element);
151
+ resolve(image);
152
+ });
153
+ }
154
+ });
155
+ return renderPromise;
156
156
  };
157
157
 
158
158
  /**
@@ -161,6 +161,7 @@ export const renderFileImage = function (file, elementId, callback) {
161
161
  * @function renderWebImage
162
162
  * @param {String} url - The image data url
163
163
  * @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
164
+ * @returns {Promise} - Return a promise which will resolve when image is displayed
164
165
  */
165
166
  export const renderWebImage = function (url, elementId) {
166
167
  let element = isElement(elementId)
@@ -170,12 +171,15 @@ export const renderWebImage = function (url, elementId) {
170
171
  console.error("invalid html element: " + elementId);
171
172
  return;
172
173
  }
173
-
174
- cornerstone.enable(element);
175
- cornerstone.loadImage(url).then(function (image) {
176
- cornerstone.displayImage(element, image);
177
- csToolsCreateStack(element);
174
+ let renderPromise = new Promise(resolve => {
175
+ cornerstone.enable(element);
176
+ cornerstone.loadImage(url).then(function (image) {
177
+ cornerstone.displayImage(element, image);
178
+ csToolsCreateStack(element);
179
+ resolve(image);
180
+ });
178
181
  });
182
+ return renderPromise;
179
183
  };
180
184
 
181
185
  /**
@@ -245,7 +249,7 @@ export const resizeViewport = function (elementId) {
245
249
  * @param {Object} seriesStack - The original series data object
246
250
  * @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
247
251
  * @param {Object} defaultProps - Optional default props
248
- * @return {Promise} Return a promise when the image has been rendered succesfully
252
+ * @return {Promise} Return a promise which will resolve when image is displayed
249
253
  */
250
254
  export const renderImage = function (seriesStack, elementId, defaultProps) {
251
255
  let t0 = performance.now();
@@ -0,0 +1,78 @@
1
+ /** @module imaging/imageReslice
2
+ * @desc This file provides functionalities for
3
+ * image reslice in orthogonal directions
4
+ */
5
+
6
+ // external libraries
7
+ import { v4 as uuidv4 } from "uuid";
8
+ import { each } from "lodash";
9
+
10
+ // internal libraries
11
+ import { getReslicedMetadata, getReslicedPixeldata } from "./imageUtils";
12
+ import {
13
+ getLarvitarImageTracker,
14
+ getLarvitarManager
15
+ } from "./loaders/commonLoader";
16
+ import { larvitar_store } from "./imageStore";
17
+
18
+ /*
19
+ * This module provides the following functions to be exported:
20
+ * resliceSeries(seriesId, seriesData, orientation)
21
+ */
22
+
23
+ /**
24
+ * Reslice a serie from native orientation to coronal or sagittal orientation
25
+ * @instance
26
+ * @function resliceSeries
27
+ * @param {Object} seriesData the original series data
28
+ * @param {String} orientation the reslice orientation [coronal or sagittal]
29
+ * @param {String} seriesId the series id
30
+ * @returns {Promise} - Return a promise which will resolve when data is available
31
+ */
32
+ export function resliceSeries(seriesData, orientation) {
33
+ let reslicePromise = new Promise(resolve => {
34
+ let reslicedSeries = {};
35
+ let reslicedSeriesId = uuidv4();
36
+ let reslicedMetaData = getReslicedMetadata(
37
+ reslicedSeriesId,
38
+ "axial",
39
+ orientation,
40
+ seriesData,
41
+ "resliceLoader"
42
+ );
43
+
44
+ reslicedSeries.imageIds = reslicedMetaData.imageIds;
45
+ reslicedSeries.instances = reslicedMetaData.instances;
46
+
47
+ reslicedSeries.currentImageIdIndex = Math.floor(
48
+ reslicedSeries.imageIds.length / 2
49
+ );
50
+
51
+ function computeReslice(seriesData, reslicedSeriesId, reslicedSeries) {
52
+ let t0 = performance.now();
53
+ let imageTracker = getLarvitarImageTracker();
54
+ let manager = getLarvitarManager();
55
+ each(reslicedSeries.imageIds, function (imageId) {
56
+ reslicedSeries.instances[imageId].pixelData = getReslicedPixeldata(
57
+ imageId,
58
+ seriesData,
59
+ reslicedSeries
60
+ );
61
+ imageTracker[imageId] = reslicedSeriesId;
62
+ });
63
+ larvitar_store.addSeriesIds(reslicedSeriesId, reslicedSeries.imageIds);
64
+ reslicedSeries.numberOfImages = reslicedSeries.imageIds.length;
65
+ reslicedSeries.seriesUID = reslicedSeriesId;
66
+ reslicedSeries.seriesDescription = seriesData.seriesDescription;
67
+ reslicedSeries.orientation = orientation;
68
+ manager[reslicedSeriesId] = reslicedSeries;
69
+ manager[seriesData.seriesUID][orientation] = reslicedSeriesId;
70
+ let t1 = performance.now();
71
+ console.log(`Call to resliceSeries took ${t1 - t0} milliseconds.`);
72
+ resolve(reslicedSeries);
73
+ }
74
+ // reslice the data
75
+ computeReslice(seriesData, reslicedSeriesId, reslicedSeries);
76
+ });
77
+ return reslicePromise;
78
+ }