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.
- package/.github/workflows/build-docs.yml +1 -1
- package/.github/workflows/deploy.yml +2 -11
- package/MIGRATION.md +25 -0
- package/README.md +28 -27
- package/imaging/dataDictionary.json +21865 -21865
- package/imaging/{image_anonymization.js → imageAnonymization.js} +1 -1
- package/imaging/{image_colormaps.js → imageColormaps.js} +2 -2
- package/imaging/{image_contours.js → imageContours.js} +1 -2
- package/imaging/{image_io.js → imageIo.js} +18 -15
- package/imaging/{image_layers.js → imageLayers.js} +2 -2
- package/imaging/{image_loading.js → imageLoading.js} +9 -6
- package/imaging/imageParsing.js +301 -0
- package/imaging/{image_presets.js → imagePresets.js} +2 -2
- package/imaging/{image_rendering.js → imageRendering.js} +36 -32
- package/imaging/imageReslice.js +78 -0
- package/imaging/{image_store.js → imageStore.js} +24 -7
- package/imaging/{image_tools.js → imageTools.js} +15 -23
- package/imaging/{image_utils.js → imageUtils.js} +1 -1
- package/imaging/loaders/commonLoader.js +1 -1
- package/imaging/loaders/dicomLoader.js +1 -1
- package/imaging/loaders/fileLoader.js +2 -2
- package/imaging/loaders/multiframeLoader.js +6 -2
- package/imaging/loaders/nrrdLoader.js +11 -7
- package/imaging/tools/{contourTool.js → custom/contourTool.js} +25 -20
- package/imaging/tools/{diameterTool.js → custom/diameterTool.js} +9 -3
- package/imaging/tools/{editMaskTool.js → custom/editMaskTool.js} +7 -1
- package/imaging/tools/{polylineScissorsTool.js → custom/polylineScissorsTool.js} +12 -5
- package/imaging/tools/{seedTool.js → custom/seedTool.js} +3 -3
- package/imaging/tools/{thresholdsBrushTool.js → custom/thresholdsBrushTool.js} +7 -1
- package/imaging/tools/{tools.default.js → default.js} +9 -2
- package/imaging/tools/{tools.interaction.js → interaction.js} +13 -6
- package/imaging/tools/{tools.io.js → io.js} +15 -6
- package/imaging/tools/{tools.main.js → main.js} +14 -13
- package/imaging/tools/polygonSegmentationMixin.js +8 -4
- package/imaging/tools/{tools.segmentation.js → segmentation.js} +171 -58
- package/imaging/tools/segmentations.md +38 -0
- package/imaging/tools/setLabelMap3D.js +248 -0
- package/imaging/tools/{tools.state.js → state.js} +7 -1
- package/imaging/tools/strategies/eraseFreehand.js +8 -9
- package/imaging/tools/strategies/fillFreehand.js +8 -9
- package/index.js +41 -39
- package/modules/vuex/larvitar.js +2 -1
- package/package.json +11 -8
- package/imaging/image_parsing.js +0 -307
- package/imaging/image_reslice.js +0 -80
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @module imaging/
|
|
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/
|
|
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 "./
|
|
15
|
-
import { larvitar_store } from "./
|
|
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 {
|
|
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
|
|
77
|
+
export const getCachedPixelData = function (imageId) {
|
|
78
78
|
let cachedImage = find(cornerstone.imageCache.cachedImages, [
|
|
79
79
|
"imageId",
|
|
80
80
|
imageId
|
|
81
81
|
]);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
|
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/
|
|
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 "./
|
|
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/
|
|
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 "./
|
|
16
|
-
import { getSortedStack, getSortedUIDs } from "./
|
|
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
|
-
|
|
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/
|
|
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 "./
|
|
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/
|
|
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/
|
|
14
|
-
import { toggleMouseToolsListeners } from "./tools/
|
|
15
|
-
import { larvitar_store } from "./
|
|
16
|
-
import { applyColorMap } from "./
|
|
17
|
-
import { isElement } from "./
|
|
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
|
|
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
|
-
* @
|
|
124
|
+
* @returns {Promise} - Return a promise which will resolve when image is displayed
|
|
125
125
|
*/
|
|
126
|
-
export const renderFileImage = function (file, elementId
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
cornerstone.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
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
|
+
}
|