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
package/index.js
CHANGED
|
@@ -5,6 +5,7 @@ console.log(`LARVITAR v${VERSION}`);
|
|
|
5
5
|
import cornerstone from "cornerstone-core";
|
|
6
6
|
import cornerstoneTools from "cornerstone-tools";
|
|
7
7
|
import cornerstoneWADOImageLoader from "cornerstone-wado-image-loader";
|
|
8
|
+
const segModule = cornerstoneTools.getModule("segmentation");
|
|
8
9
|
|
|
9
10
|
import larvitarModule from "./modules/vuex/larvitar";
|
|
10
11
|
|
|
@@ -14,15 +15,15 @@ import {
|
|
|
14
15
|
getAvailableMemory
|
|
15
16
|
} from "./imaging/monitors/memory";
|
|
16
17
|
|
|
17
|
-
import { initLarvitarStore, larvitar_store } from "./imaging/
|
|
18
|
+
import { initLarvitarStore, larvitar_store } from "./imaging/imageStore";
|
|
18
19
|
|
|
19
|
-
import { parseContours } from "./imaging/
|
|
20
|
+
import { parseContours } from "./imaging/imageContours";
|
|
20
21
|
|
|
21
22
|
import {
|
|
22
23
|
getImagePresets,
|
|
23
24
|
setImagePreset,
|
|
24
25
|
setImageCustomPreset
|
|
25
|
-
} from "./imaging/
|
|
26
|
+
} from "./imaging/imagePresets";
|
|
26
27
|
|
|
27
28
|
import {
|
|
28
29
|
getNormalOrientation,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
getReslicedMetadata,
|
|
38
39
|
getReslicedPixeldata,
|
|
39
40
|
getDistanceBetweenSlices
|
|
40
|
-
} from "./imaging/
|
|
41
|
+
} from "./imaging/imageUtils";
|
|
41
42
|
|
|
42
43
|
import {
|
|
43
44
|
buildHeader,
|
|
@@ -45,16 +46,16 @@ import {
|
|
|
45
46
|
buildData,
|
|
46
47
|
buildDataAsync,
|
|
47
48
|
importNRRDImage
|
|
48
|
-
} from "./imaging/
|
|
49
|
+
} from "./imaging/imageIo";
|
|
49
50
|
|
|
50
|
-
import { anonymize, encrypt } from "./imaging/
|
|
51
|
+
import { anonymize, encrypt } from "./imaging/imageAnonymization";
|
|
51
52
|
|
|
52
53
|
import {
|
|
53
54
|
buildLayer,
|
|
54
55
|
updateLayer,
|
|
55
56
|
getActiveLayer,
|
|
56
57
|
setActiveLayer
|
|
57
|
-
} from "./imaging/
|
|
58
|
+
} from "./imaging/imageLayers";
|
|
58
59
|
|
|
59
60
|
import {
|
|
60
61
|
initializeImageLoader,
|
|
@@ -64,14 +65,14 @@ import {
|
|
|
64
65
|
registerResliceLoader,
|
|
65
66
|
registerMultiFrameImageLoader,
|
|
66
67
|
updateLoadedStack
|
|
67
|
-
} from "./imaging/
|
|
68
|
+
} from "./imaging/imageLoading";
|
|
68
69
|
|
|
69
70
|
import {
|
|
70
71
|
readFile,
|
|
71
72
|
readFiles,
|
|
72
|
-
|
|
73
|
+
parseDataSet,
|
|
73
74
|
clearImageParsing
|
|
74
|
-
} from "./imaging/
|
|
75
|
+
} from "./imaging/imageParsing";
|
|
75
76
|
|
|
76
77
|
import {
|
|
77
78
|
clearImageCache,
|
|
@@ -91,9 +92,9 @@ import {
|
|
|
91
92
|
flipImageVertical,
|
|
92
93
|
rotateImageLeft,
|
|
93
94
|
rotateImageRight
|
|
94
|
-
} from "./imaging/
|
|
95
|
+
} from "./imaging/imageRendering";
|
|
95
96
|
|
|
96
|
-
import { resliceSeries } from "./imaging/
|
|
97
|
+
import { resliceSeries } from "./imaging/imageReslice";
|
|
97
98
|
|
|
98
99
|
import {
|
|
99
100
|
addDiameterTool,
|
|
@@ -111,7 +112,7 @@ import {
|
|
|
111
112
|
syncToolStack,
|
|
112
113
|
updateStackToolState,
|
|
113
114
|
setSegmentationConfig
|
|
114
|
-
} from "./imaging/
|
|
115
|
+
} from "./imaging/imageTools";
|
|
115
116
|
|
|
116
117
|
import {
|
|
117
118
|
csToolsCreateStack,
|
|
@@ -126,13 +127,13 @@ import {
|
|
|
126
127
|
setToolEnabled,
|
|
127
128
|
setToolPassive,
|
|
128
129
|
exportAnnotations
|
|
129
|
-
} from "./imaging/tools/
|
|
130
|
+
} from "./imaging/tools/main";
|
|
130
131
|
|
|
131
132
|
import {
|
|
132
133
|
DEFAULT_TOOLS,
|
|
133
134
|
getDefaultToolsByType,
|
|
134
135
|
setDefaultToolsProps
|
|
135
|
-
} from "./imaging/tools/
|
|
136
|
+
} from "./imaging/tools/default";
|
|
136
137
|
|
|
137
138
|
import {
|
|
138
139
|
initSegmentationModule,
|
|
@@ -142,7 +143,7 @@ import {
|
|
|
142
143
|
undoLastStroke,
|
|
143
144
|
redoLastStroke,
|
|
144
145
|
setBrushProps
|
|
145
|
-
} from "./imaging/tools/
|
|
146
|
+
} from "./imaging/tools/segmentation";
|
|
146
147
|
|
|
147
148
|
import {
|
|
148
149
|
updateLarvitarManager,
|
|
@@ -189,20 +190,21 @@ import {
|
|
|
189
190
|
addColorMap,
|
|
190
191
|
fillPixelData,
|
|
191
192
|
HSVToRGB
|
|
192
|
-
} from "./imaging/
|
|
193
|
+
} from "./imaging/imageColormaps";
|
|
193
194
|
|
|
194
|
-
import { saveAnnotations, loadAnnotations } from "./imaging/tools/
|
|
195
|
+
import { saveAnnotations, loadAnnotations } from "./imaging/tools/io";
|
|
195
196
|
|
|
196
197
|
import {
|
|
197
198
|
addMouseKeyHandlers,
|
|
198
199
|
toggleMouseToolsListeners
|
|
199
|
-
} from "./imaging/tools/
|
|
200
|
+
} from "./imaging/tools/interaction";
|
|
200
201
|
|
|
201
202
|
export {
|
|
202
203
|
VERSION,
|
|
203
204
|
// global cornerstone variables
|
|
204
205
|
cornerstone,
|
|
205
206
|
cornerstoneTools,
|
|
207
|
+
segModule,
|
|
206
208
|
cornerstoneWADOImageLoader,
|
|
207
209
|
// vuex module
|
|
208
210
|
larvitarModule,
|
|
@@ -210,14 +212,14 @@ export {
|
|
|
210
212
|
checkMemoryAllocation,
|
|
211
213
|
getUsedMemory,
|
|
212
214
|
getAvailableMemory,
|
|
213
|
-
//
|
|
215
|
+
// larvitarStore
|
|
214
216
|
initLarvitarStore,
|
|
215
217
|
larvitar_store,
|
|
216
|
-
//
|
|
218
|
+
// imagePresets
|
|
217
219
|
getImagePresets,
|
|
218
220
|
setImagePreset,
|
|
219
221
|
setImageCustomPreset,
|
|
220
|
-
//
|
|
222
|
+
// imageUtils
|
|
221
223
|
getNormalOrientation,
|
|
222
224
|
getMinPixelValue,
|
|
223
225
|
getMaxPixelValue,
|
|
@@ -230,21 +232,21 @@ export {
|
|
|
230
232
|
getReslicedMetadata,
|
|
231
233
|
getReslicedPixeldata,
|
|
232
234
|
getDistanceBetweenSlices,
|
|
233
|
-
//
|
|
235
|
+
// imageIo
|
|
234
236
|
buildHeader,
|
|
235
237
|
getCachedPixelData,
|
|
236
238
|
buildData,
|
|
237
239
|
buildDataAsync,
|
|
238
240
|
importNRRDImage,
|
|
239
|
-
//
|
|
241
|
+
// imageAnonymization
|
|
240
242
|
anonymize,
|
|
241
243
|
encrypt,
|
|
242
|
-
//
|
|
244
|
+
// imageLayers
|
|
243
245
|
buildLayer,
|
|
244
246
|
updateLayer,
|
|
245
247
|
getActiveLayer,
|
|
246
248
|
setActiveLayer,
|
|
247
|
-
//
|
|
249
|
+
// imageLoading
|
|
248
250
|
initializeImageLoader,
|
|
249
251
|
initializeWebImageLoader,
|
|
250
252
|
initializeFileImageLoader,
|
|
@@ -252,12 +254,12 @@ export {
|
|
|
252
254
|
registerResliceLoader,
|
|
253
255
|
registerMultiFrameImageLoader,
|
|
254
256
|
updateLoadedStack,
|
|
255
|
-
//
|
|
257
|
+
// imageParsing
|
|
256
258
|
readFile,
|
|
257
259
|
readFiles,
|
|
258
|
-
|
|
260
|
+
parseDataSet,
|
|
259
261
|
clearImageParsing,
|
|
260
|
-
//
|
|
262
|
+
// imageRendering
|
|
261
263
|
clearImageCache,
|
|
262
264
|
loadAndCacheImages,
|
|
263
265
|
renderFileImage,
|
|
@@ -276,15 +278,15 @@ export {
|
|
|
276
278
|
flipImageVertical,
|
|
277
279
|
rotateImageLeft,
|
|
278
280
|
rotateImageRight,
|
|
279
|
-
//
|
|
281
|
+
// imageReslice
|
|
280
282
|
resliceSeries,
|
|
281
|
-
//
|
|
283
|
+
// imageColormaps
|
|
282
284
|
getColormapsList,
|
|
283
285
|
applyColorMap,
|
|
284
286
|
addColorMap,
|
|
285
287
|
fillPixelData,
|
|
286
288
|
HSVToRGB,
|
|
287
|
-
//
|
|
289
|
+
// imageContours
|
|
288
290
|
parseContours,
|
|
289
291
|
// loaders/commonLoader
|
|
290
292
|
updateLarvitarManager,
|
|
@@ -307,7 +309,7 @@ export {
|
|
|
307
309
|
// loaders/dicomLoader
|
|
308
310
|
getDicomImageId,
|
|
309
311
|
cacheImages,
|
|
310
|
-
// loaders/
|
|
312
|
+
// loaders/multiframeLoader
|
|
311
313
|
loadMultiFrameImage,
|
|
312
314
|
buildMultiFrameImage,
|
|
313
315
|
getMultiFrameImageId,
|
|
@@ -318,7 +320,7 @@ export {
|
|
|
318
320
|
resetFileManager,
|
|
319
321
|
populateFileManager,
|
|
320
322
|
getFileImageId,
|
|
321
|
-
//
|
|
323
|
+
// imageTools
|
|
322
324
|
addDiameterTool,
|
|
323
325
|
addContoursTool,
|
|
324
326
|
addMaskEditingTool,
|
|
@@ -334,7 +336,7 @@ export {
|
|
|
334
336
|
syncToolStack,
|
|
335
337
|
updateStackToolState,
|
|
336
338
|
setSegmentationConfig,
|
|
337
|
-
// tools
|
|
339
|
+
// tools/main
|
|
338
340
|
csToolsCreateStack,
|
|
339
341
|
csToolsUpdateImageIds,
|
|
340
342
|
csToolsUpdateImageIndex,
|
|
@@ -347,16 +349,16 @@ export {
|
|
|
347
349
|
setToolEnabled,
|
|
348
350
|
setToolPassive,
|
|
349
351
|
exportAnnotations,
|
|
350
|
-
// tools
|
|
352
|
+
// tools/default
|
|
351
353
|
DEFAULT_TOOLS,
|
|
352
354
|
getDefaultToolsByType,
|
|
353
355
|
setDefaultToolsProps,
|
|
354
|
-
// tools
|
|
356
|
+
// tools/io
|
|
355
357
|
saveAnnotations,
|
|
356
358
|
loadAnnotations,
|
|
357
|
-
// tools
|
|
359
|
+
// tools/interaction
|
|
358
360
|
addMouseKeyHandlers,
|
|
359
|
-
// tools
|
|
361
|
+
// tools/segmentation
|
|
360
362
|
initSegmentationModule,
|
|
361
363
|
addSegmentationMask,
|
|
362
364
|
setActiveLabelmap,
|
package/modules/vuex/larvitar.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Larvitar Vuex storage
|
|
2
2
|
|
|
3
3
|
import Vue from "vue";
|
|
4
4
|
|
|
@@ -99,6 +99,7 @@ export default {
|
|
|
99
99
|
},
|
|
100
100
|
removeSeriesIds: ({ state }, seriesId) =>
|
|
101
101
|
Vue.delete(state.series, seriesId),
|
|
102
|
+
resetSeriesIds: ({ state }) => Vue.set(state.series, {}),
|
|
102
103
|
setErrorLog: () => {}, // TODO LT pass elementId
|
|
103
104
|
|
|
104
105
|
// Series fields setters
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "larvitar",
|
|
3
3
|
"keywords": [
|
|
4
|
-
"
|
|
5
|
-
"imaging"
|
|
4
|
+
"DICOM",
|
|
5
|
+
"imaging",
|
|
6
|
+
"medical",
|
|
7
|
+
"cornerstone"
|
|
6
8
|
],
|
|
7
|
-
"version": "
|
|
8
|
-
"description": "javascript library for loading, rendering and
|
|
9
|
+
"version": "1.2.2",
|
|
10
|
+
"description": "javascript library for parsing, loading, rendering and interacting with DICOM images",
|
|
9
11
|
"repository": {
|
|
10
12
|
"url": "https://github.com/dvisionlab/Larvitar.git",
|
|
11
13
|
"type": "git"
|
|
@@ -18,15 +20,16 @@
|
|
|
18
20
|
"author": "Simone Manini <simone.manini@dvisionlab.com> (https://www.dvisionlab.com)",
|
|
19
21
|
"contributors": [
|
|
20
22
|
"Mattia Ronzoni <mattia.ronzoni@dvisionlab.com> (https://www.dvisionlab.com)",
|
|
21
|
-
"Sara Zanchi <sara.zanchi@dvisionlab.com> (https://www.dvisionlab.com)"
|
|
23
|
+
"Sara Zanchi <sara.zanchi@dvisionlab.com> (https://www.dvisionlab.com)",
|
|
24
|
+
"Ale Re <ale.re@dvisionlab.com> (https://www.dvisionlab.com)"
|
|
22
25
|
],
|
|
23
26
|
"license": "MIT",
|
|
24
27
|
"dependencies": {
|
|
25
28
|
"@rollup/plugin-commonjs": "^17.1.0",
|
|
26
|
-
"cornerstone-core": "^2.6.
|
|
29
|
+
"cornerstone-core": "^2.6.1",
|
|
27
30
|
"cornerstone-file-image-loader": "^0.3.0",
|
|
28
|
-
"cornerstone-tools": "^
|
|
29
|
-
"cornerstone-wado-image-loader": "^
|
|
31
|
+
"cornerstone-tools": "^6.0.6",
|
|
32
|
+
"cornerstone-wado-image-loader": "^4.0.1",
|
|
30
33
|
"cornerstone-web-image-loader": "^2.1.1",
|
|
31
34
|
"crypto-js": "^4.1.1",
|
|
32
35
|
"dicom-character-set": "^1.0.3",
|
package/imaging/image_parsing.js
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
1
|
-
/** @module imaging/parsing
|
|
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 "./image_utils.js";
|
|
11
|
-
import { updateLoadedStack } from "./image_loading.js";
|
|
12
|
-
import { checkMemoryAllocation } from "./monitors/memory.js";
|
|
13
|
-
|
|
14
|
-
// global module variables
|
|
15
|
-
var parsingQueueFlag = null; // flag to handle the queue
|
|
16
|
-
var t0 = null; // t0 variable for timing debugging purpose
|
|
17
|
-
|
|
18
|
-
/*
|
|
19
|
-
* This module provides the following functions to be exported:
|
|
20
|
-
* readFiles(fileList, callback)
|
|
21
|
-
* readFile(file, callback)
|
|
22
|
-
* dumpDataSet(dataSet, metadata, customFilter)
|
|
23
|
-
* clearImageParsing(seriesStack)
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Reset series stack object and its internal data
|
|
28
|
-
* @instance
|
|
29
|
-
* @function clearImageParsing
|
|
30
|
-
* @param {Object} seriesStack - Parsed series stack object
|
|
31
|
-
*/
|
|
32
|
-
export const clearImageParsing = function (seriesStack) {
|
|
33
|
-
each(seriesStack, function (stack) {
|
|
34
|
-
each(stack.instances, function (instance) {
|
|
35
|
-
if (instance.dataSet) {
|
|
36
|
-
instance.dataSet.byteArray = null;
|
|
37
|
-
}
|
|
38
|
-
instance.dataSet = null;
|
|
39
|
-
instance.file = null;
|
|
40
|
-
instance.metadata = null;
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
seriesStack = null;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Read dicom files and return allSeriesStack object
|
|
48
|
-
* @instance
|
|
49
|
-
* @function readFiles
|
|
50
|
-
* @param {Array} entries - List of file objects
|
|
51
|
-
* @param {Function} callback - Will receive (imageObject, errorString) as args
|
|
52
|
-
*/
|
|
53
|
-
export const readFiles = function (entries, callback) {
|
|
54
|
-
let allSeriesStack = {};
|
|
55
|
-
let parsingQueue = [];
|
|
56
|
-
dumpFiles(entries, parsingQueue, allSeriesStack, callback);
|
|
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
|
-
* @param {Function} callback - Will receive (imageObject, errorString) as args
|
|
65
|
-
*/
|
|
66
|
-
export const readFile = function (entry, callback) {
|
|
67
|
-
dumpFile(entry, callback);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/* Internal module functions */
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Dump metadata from dicom parser dataSet object
|
|
74
|
-
* @instance
|
|
75
|
-
* @function dumpDataSet
|
|
76
|
-
* @param {Object} dataSet - dicom parser dataSet object
|
|
77
|
-
* @param {Array} metadata - Initialized metadata object
|
|
78
|
-
* @param {Array} customFilter - Optional filter: {tags:[], frameId: 0}
|
|
79
|
-
*/
|
|
80
|
-
// This function iterates through dataSet recursively and adds new HTML strings
|
|
81
|
-
// to the output array passed into it
|
|
82
|
-
export const dumpDataSet = function (dataSet, metadata, customFilter) {
|
|
83
|
-
// customFilter= {tags:[], frameId:xxx}
|
|
84
|
-
// the dataSet.elements object contains properties for each element parsed. The name of the property
|
|
85
|
-
// is based on the elements tag and looks like 'xGGGGEEEE' where GGGG is the group number and EEEE is the
|
|
86
|
-
// element number both with lowercase hexadecimal letters. For example, the Series Description DICOM element 0008,103E would
|
|
87
|
-
// be named 'x0008103e'. Here we iterate over each property (element) so we can build a string describing its
|
|
88
|
-
// contents to add to the output array
|
|
89
|
-
try {
|
|
90
|
-
let elements =
|
|
91
|
-
customFilter && has(customFilter, "tags")
|
|
92
|
-
? pick(dataSet.elements, customFilter.tags)
|
|
93
|
-
: dataSet.elements;
|
|
94
|
-
for (let propertyName in elements) {
|
|
95
|
-
let element = elements[propertyName];
|
|
96
|
-
// Here we check for Sequence items and iterate over them if present. items will not be set in the
|
|
97
|
-
// element object for elements that don't have SQ VR type. Note that implicit little endian
|
|
98
|
-
// sequences will are currently not parsed.
|
|
99
|
-
if (element.items) {
|
|
100
|
-
// each item contains its own data set so we iterate over the items
|
|
101
|
-
// and recursively call this function
|
|
102
|
-
if (customFilter && has(customFilter, "frameId")) {
|
|
103
|
-
let item = element.items[customFilter.frameId];
|
|
104
|
-
dumpDataSet(item.dataSet, metadata);
|
|
105
|
-
} else {
|
|
106
|
-
element.items.forEach(function (item) {
|
|
107
|
-
dumpDataSet(item.dataSet, metadata);
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
} else {
|
|
111
|
-
let tagValue = parseTag(dataSet, propertyName, element);
|
|
112
|
-
metadata[propertyName] = tagValue;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
} catch (err) {
|
|
116
|
-
console.log(err);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Manage the parsing process waiting for the parsed object before proceeding with the next parse request
|
|
122
|
-
* @inner
|
|
123
|
-
* @function parseNextFile
|
|
124
|
-
* @param {Array} parsingQueue - Array of queued files to be parsed
|
|
125
|
-
* @param {Object} allSeriesStack - Series stack object to be populated
|
|
126
|
-
* @param {Function} callback - Passed through
|
|
127
|
-
*/
|
|
128
|
-
let parseNextFile = function (parsingQueue, allSeriesStack, callback) {
|
|
129
|
-
// initialize t0 on first file of the queue
|
|
130
|
-
if (
|
|
131
|
-
Object.keys(allSeriesStack).length === 0 &&
|
|
132
|
-
allSeriesStack.constructor === Object
|
|
133
|
-
) {
|
|
134
|
-
t0 = performance.now();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!parsingQueueFlag || parsingQueue.length === 0) {
|
|
138
|
-
if (parsingQueue.length === 0) {
|
|
139
|
-
let t1 = performance.now();
|
|
140
|
-
console.log(`Call to readFiles took ${t1 - t0} milliseconds.`);
|
|
141
|
-
parsingQueueFlag = null;
|
|
142
|
-
callback(allSeriesStack);
|
|
143
|
-
}
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
parsingQueueFlag = false;
|
|
148
|
-
|
|
149
|
-
// remove and return first item from queue
|
|
150
|
-
let file = parsingQueue.shift();
|
|
151
|
-
|
|
152
|
-
// Check if there is enough memory to dump the file
|
|
153
|
-
if (checkMemoryAllocation(file.size) === false) {
|
|
154
|
-
// do not parse the file and stop parsing
|
|
155
|
-
clearImageParsing(allSeriesStack);
|
|
156
|
-
let t1 = performance.now();
|
|
157
|
-
console.log(`Call to readFiles took ${t1 - t0} milliseconds.`);
|
|
158
|
-
parsingQueueFlag = null;
|
|
159
|
-
file = null;
|
|
160
|
-
callback(allSeriesStack, "Available memory is not enough");
|
|
161
|
-
} else {
|
|
162
|
-
// parse the file and wait for results
|
|
163
|
-
dumpFile(file, function (seriesData, err) {
|
|
164
|
-
if (parsingQueueFlag === null) {
|
|
165
|
-
console.log("parsingQueueFlag is null");
|
|
166
|
-
// parsing process has been stopped, but there could be a
|
|
167
|
-
// dumpFile callback still working: prevent actions
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (err) {
|
|
171
|
-
console.warn(err);
|
|
172
|
-
parsingQueueFlag = true;
|
|
173
|
-
parseNextFile(parsingQueue, allSeriesStack, callback);
|
|
174
|
-
} else {
|
|
175
|
-
// add file to cornerstoneWADOImageLoader file manager
|
|
176
|
-
updateLoadedStack(seriesData, allSeriesStack);
|
|
177
|
-
// proceed with the next file to parse
|
|
178
|
-
parsingQueueFlag = true;
|
|
179
|
-
parseNextFile(parsingQueue, allSeriesStack, callback);
|
|
180
|
-
}
|
|
181
|
-
file = null;
|
|
182
|
-
seriesData = null;
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Push files in queue and start parsing next file
|
|
189
|
-
* @inner
|
|
190
|
-
* @function dumpFiles
|
|
191
|
-
* @param {Array} fileList - Array of file objects
|
|
192
|
-
* @param {Array} parsingQueue - Array of queued files to be parsed
|
|
193
|
-
* @param {Object} allSeriesStack - Series stack object to be populated
|
|
194
|
-
* @param {Function} callback - Passed through
|
|
195
|
-
*/
|
|
196
|
-
let dumpFiles = function (fileList, parsingQueue, allSeriesStack, callback) {
|
|
197
|
-
forEach(fileList, function (file) {
|
|
198
|
-
if (!file.name.startsWith(".") && !file.name.startsWith("DICOMDIR")) {
|
|
199
|
-
parsingQueue.push(file);
|
|
200
|
-
// enable parsing on first available path
|
|
201
|
-
if (parsingQueueFlag === null) {
|
|
202
|
-
parsingQueueFlag = true;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
parseNextFile(parsingQueue, allSeriesStack, callback);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Dump a single DICOM File (metaData and pixelData)
|
|
211
|
-
* @inner
|
|
212
|
-
* @function dumpFile
|
|
213
|
-
* @param {File} file - File object to be dumped
|
|
214
|
-
* @param {Function} callback - called with (imageObject, errorString)
|
|
215
|
-
*/
|
|
216
|
-
let dumpFile = function (file, callback) {
|
|
217
|
-
let reader = new FileReader();
|
|
218
|
-
reader.onload = function () {
|
|
219
|
-
let arrayBuffer = reader.result;
|
|
220
|
-
// Here we have the file data as an ArrayBuffer.
|
|
221
|
-
// dicomParser requires as input a Uint8Array so we create that here.
|
|
222
|
-
let byteArray = new Uint8Array(arrayBuffer);
|
|
223
|
-
|
|
224
|
-
let dataSet;
|
|
225
|
-
try {
|
|
226
|
-
dataSet = parseDicom(byteArray);
|
|
227
|
-
byteArray = null;
|
|
228
|
-
let metadata = {};
|
|
229
|
-
dumpDataSet(dataSet, metadata);
|
|
230
|
-
|
|
231
|
-
let numberOfFrames = metadata["x00280008"];
|
|
232
|
-
let isMultiframe = numberOfFrames > 1 ? true : false;
|
|
233
|
-
// Overwrite SOPInstanceUID to manage multiframes.
|
|
234
|
-
// Usually different SeriesInstanceUID means different series and that value
|
|
235
|
-
// is used into the application to group different instances into the same series,
|
|
236
|
-
// but if a DICOM file contains a multiframe series, then the SeriesInstanceUID
|
|
237
|
-
// can be shared by other files of the same study.
|
|
238
|
-
// In multiframe cases, the SOPInstanceUID (unique) is used as SeriesInstanceUID.
|
|
239
|
-
let seriesInstanceUID = isMultiframe
|
|
240
|
-
? metadata["x00080018"]
|
|
241
|
-
: metadata["x0020000e"];
|
|
242
|
-
let pixelSpacing = metadata["x00280030"];
|
|
243
|
-
let imageOrientation = metadata["x00200037"];
|
|
244
|
-
let imagePosition = metadata["x00200032"];
|
|
245
|
-
let sliceThickness = metadata["x00180050"];
|
|
246
|
-
|
|
247
|
-
if (dataSet.warnings.length > 0) {
|
|
248
|
-
// warnings
|
|
249
|
-
callback(null, dataSet.warnings);
|
|
250
|
-
} else {
|
|
251
|
-
let pixelDataElement = dataSet.elements.x7fe00010;
|
|
252
|
-
|
|
253
|
-
if (pixelDataElement) {
|
|
254
|
-
// done, pixelDataElement found
|
|
255
|
-
let instanceUID = metadata["x00080018"] || randomId();
|
|
256
|
-
let imageObject = {
|
|
257
|
-
// data needed for rendering
|
|
258
|
-
file: file,
|
|
259
|
-
dataSet: dataSet
|
|
260
|
-
};
|
|
261
|
-
imageObject.metadata = metadata;
|
|
262
|
-
imageObject.metadata.seriesUID = seriesInstanceUID;
|
|
263
|
-
imageObject.metadata.instanceUID = instanceUID;
|
|
264
|
-
imageObject.metadata.studyUID = metadata["x0020000d"];
|
|
265
|
-
imageObject.metadata.accessionNumber = metadata["x00080050"];
|
|
266
|
-
imageObject.metadata.studyDescription = metadata["x00081030"];
|
|
267
|
-
imageObject.metadata.patientName = metadata["x00100010"];
|
|
268
|
-
imageObject.metadata.patientBirthdate = metadata["x00100030"];
|
|
269
|
-
imageObject.metadata.seriesDescription = metadata["x0008103e"];
|
|
270
|
-
imageObject.metadata.seriesDate = metadata["x00080021"];
|
|
271
|
-
imageObject.metadata.seriesModality =
|
|
272
|
-
metadata["x00080060"].toLowerCase();
|
|
273
|
-
imageObject.metadata.intercept = metadata["x00281052"];
|
|
274
|
-
imageObject.metadata.slope = metadata["x00281053"];
|
|
275
|
-
imageObject.metadata.pixelSpacing = pixelSpacing;
|
|
276
|
-
imageObject.metadata.sliceThickness = sliceThickness;
|
|
277
|
-
imageObject.metadata.imageOrientation = imageOrientation;
|
|
278
|
-
imageObject.metadata.imagePosition = imagePosition;
|
|
279
|
-
imageObject.metadata.rows = metadata["x00280010"];
|
|
280
|
-
imageObject.metadata.cols = metadata["x00280011"];
|
|
281
|
-
imageObject.metadata.numberOfSlices = metadata["x00540081"]
|
|
282
|
-
? metadata["x00540081"] // number of slices
|
|
283
|
-
: metadata["x00201002"]; // number of instances
|
|
284
|
-
imageObject.metadata.numberOfFrames = numberOfFrames;
|
|
285
|
-
if (isMultiframe) {
|
|
286
|
-
imageObject.metadata.frameTime = metadata["x00181063"];
|
|
287
|
-
imageObject.metadata.frameDelay = metadata["x00181066"];
|
|
288
|
-
}
|
|
289
|
-
imageObject.metadata.windowCenter = metadata["x00281050"];
|
|
290
|
-
imageObject.metadata.windowWidth = metadata["x00281051"];
|
|
291
|
-
imageObject.metadata.minPixelValue = metadata["x00280106"];
|
|
292
|
-
imageObject.metadata.maxPixelValue = metadata["x00280107"];
|
|
293
|
-
imageObject.metadata.length = pixelDataElement.length;
|
|
294
|
-
imageObject.metadata.repr = getPixelRepresentation(dataSet);
|
|
295
|
-
callback(imageObject);
|
|
296
|
-
} else {
|
|
297
|
-
// done, no pixelData
|
|
298
|
-
callback(null, "no pixelData.");
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
} catch (err) {
|
|
302
|
-
console.log(err);
|
|
303
|
-
callback(null, "can not read this file.");
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
reader.readAsArrayBuffer(file);
|
|
307
|
-
};
|