larvitar 1.5.13 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +4 -0
- package/README.md +78 -48
- package/bundler/webpack.common.js +27 -0
- package/bundler/webpack.dev.js +23 -0
- package/bundler/webpack.prod.js +19 -0
- package/decs.d.ts +12 -0
- package/dist/imaging/MetaDataReadable.d.ts +39 -0
- package/dist/imaging/MetaDataTypes.d.ts +3488 -0
- package/dist/imaging/imageAnonymization.d.ts +12 -0
- package/dist/imaging/imageColormaps.d.ts +47 -0
- package/dist/imaging/imageContours.d.ts +18 -0
- package/dist/imaging/imageIo.d.ts +42 -0
- package/dist/imaging/imageLayers.d.ts +56 -0
- package/dist/imaging/imageLoading.d.ts +65 -0
- package/dist/imaging/imageParsing.d.ts +46 -0
- package/dist/imaging/imagePresets.d.ts +43 -0
- package/dist/imaging/imageRendering.d.ts +238 -0
- package/dist/imaging/imageReslice.d.ts +14 -0
- package/dist/imaging/imageStore.d.ts +121 -0
- package/dist/imaging/imageTags.d.ts +22 -0
- package/dist/imaging/imageTools.d.ts +20 -0
- package/dist/imaging/imageUtils.d.ts +165 -0
- package/dist/imaging/loaders/commonLoader.d.ts +103 -0
- package/dist/imaging/loaders/dicomLoader.d.ts +29 -0
- package/dist/imaging/loaders/fileLoader.d.ts +33 -0
- package/dist/imaging/loaders/multiframeLoader.d.ts +37 -0
- package/dist/imaging/loaders/nrrdLoader.d.ts +112 -0
- package/dist/imaging/loaders/resliceLoader.d.ts +15 -0
- package/dist/imaging/monitors/memory.d.ts +41 -0
- package/dist/imaging/monitors/performance.d.ts +23 -0
- package/dist/imaging/parsers/ecg.d.ts +15 -0
- package/dist/imaging/parsers/nrrd.d.ts +3 -0
- package/dist/imaging/tools/custom/4dSliceScrollTool.d.ts +12 -0
- package/dist/imaging/tools/custom/contourTool.d.ts +409 -0
- package/dist/imaging/tools/custom/diameterTool.d.ts +18 -0
- package/dist/imaging/tools/custom/editMaskTool.d.ts +22 -0
- package/dist/imaging/tools/custom/ellipticalRoiOverlayTool.d.ts +45 -0
- package/dist/imaging/tools/custom/polygonSegmentationMixin.d.ts +54 -0
- package/dist/imaging/tools/custom/polylineScissorsTool.d.ts +11 -0
- package/dist/imaging/tools/custom/rectangleRoiOverlayTool.d.ts +45 -0
- package/dist/imaging/tools/custom/seedTool.d.ts +0 -0
- package/dist/imaging/tools/custom/setLabelMap3D.d.ts +39 -0
- package/dist/imaging/tools/custom/thresholdsBrushTool.d.ts +19 -0
- package/dist/imaging/tools/default.d.ts +53 -0
- package/dist/imaging/tools/interaction.d.ts +30 -0
- package/dist/imaging/tools/io.d.ts +38 -0
- package/dist/imaging/tools/main.d.ts +81 -0
- package/dist/imaging/tools/segmentation.d.ts +125 -0
- package/dist/imaging/tools/state.d.ts +17 -0
- package/dist/imaging/tools/strategies/eraseFreehand.d.ts +16 -0
- package/dist/imaging/tools/strategies/fillFreehand.d.ts +16 -0
- package/dist/imaging/tools/strategies/index.d.ts +2 -0
- package/dist/index.d.ts +34 -0
- package/dist/larvitar.js +89801 -0
- package/dist/larvitar.js.map +1 -0
- package/imaging/MetaDataReadable.ts +40 -0
- package/imaging/MetaDataTypes.ts +3490 -0
- package/imaging/dataDictionary.json +5328 -5328
- package/imaging/{imageAnonymization.js → imageAnonymization.ts} +41 -13
- package/imaging/{imageColormaps.js → imageColormaps.ts} +48 -30
- package/imaging/{imageContours.js → imageContours.ts} +24 -22
- package/imaging/{imageIo.js → imageIo.ts} +89 -52
- package/imaging/{imageLayers.js → imageLayers.ts} +31 -14
- package/imaging/{imageLoading.js → imageLoading.ts} +108 -45
- package/imaging/{imageParsing.js → imageParsing.ts} +158 -80
- package/imaging/{imagePresets.js → imagePresets.ts} +44 -11
- package/imaging/imageRendering.ts +1091 -0
- package/imaging/{imageReslice.js → imageReslice.ts} +18 -9
- package/imaging/imageStore.ts +487 -0
- package/imaging/imageTags.ts +609 -0
- package/imaging/imageTools.js +2 -1
- package/imaging/{imageUtils.js → imageUtils.ts} +211 -701
- package/imaging/loaders/{commonLoader.js → commonLoader.ts} +73 -24
- package/imaging/loaders/{dicomLoader.js → dicomLoader.ts} +25 -5
- package/imaging/loaders/{fileLoader.js → fileLoader.ts} +5 -5
- package/imaging/loaders/{multiframeLoader.js → multiframeLoader.ts} +145 -90
- package/imaging/loaders/{nrrdLoader.js → nrrdLoader.ts} +230 -64
- package/imaging/loaders/{resliceLoader.js → resliceLoader.ts} +51 -20
- package/imaging/monitors/{memory.js → memory.ts} +54 -8
- package/imaging/monitors/performance.ts +34 -0
- package/imaging/parsers/ecg.ts +51 -0
- package/imaging/tools/README.md +27 -0
- package/imaging/tools/custom/4dSliceScrollTool.js +47 -46
- package/imaging/tools/custom/ellipticalRoiOverlayTool.js +534 -0
- package/imaging/tools/custom/polylineScissorsTool.js +1 -1
- package/imaging/tools/custom/rectangleRoiOverlayTool.js +564 -0
- package/imaging/tools/{setLabelMap3D.js → custom/setLabelMap3D.ts} +19 -25
- package/imaging/tools/{default.js → default.ts} +114 -30
- package/imaging/tools/{interaction.js → interaction.ts} +42 -23
- package/imaging/tools/{io.js → io.ts} +47 -31
- package/imaging/tools/{main.js → main.ts} +105 -40
- package/imaging/tools/{segmentation.js → segmentation.ts} +95 -68
- package/imaging/tools/{state.js → state.ts} +7 -12
- package/imaging/tools/types.d.ts +243 -0
- package/imaging/types.d.ts +197 -0
- package/{index.js → index.ts} +43 -14
- package/jsdoc.json +1 -1
- package/package.json +32 -14
- package/tsconfig.json +102 -0
- package/imaging/imageRendering.js +0 -860
- package/imaging/imageStore.js +0 -322
- package/modules/vuex/larvitar.js +0 -187
- /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
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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
|
|
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 (
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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[
|
|
155
|
+
if (metadata[TAG] !== undefined) {
|
|
118
156
|
console.debug(
|
|
119
157
|
`Identified duplicated tag "${propertyName}", values are:`,
|
|
120
|
-
metadata[
|
|
158
|
+
metadata[TAG],
|
|
121
159
|
tagValue
|
|
122
160
|
);
|
|
123
|
-
|
|
161
|
+
let TAG_uuidv4 = (propertyName +
|
|
162
|
+
"_" +
|
|
163
|
+
uuidv4()) as keyof ExtendedMetaDataTypes;
|
|
164
|
+
metadata[TAG_uuidv4] = tagValue;
|
|
124
165
|
} else {
|
|
125
|
-
metadata[
|
|
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
|
|
229
|
+
seriesData!.metadata.seriesUID = seriesData!.metadata.seriesUID || uuid;
|
|
184
230
|
// add file to cornerstoneDICOMImageLoader file manager
|
|
185
|
-
updateLoadedStack(seriesData
|
|
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.
|
|
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
|
-
|
|
252
|
+
const parseFiles = function (fileList: File[]) {
|
|
207
253
|
let allSeriesStack = {};
|
|
208
|
-
let parsingQueue = [];
|
|
254
|
+
let parsingQueue: File[] = [];
|
|
209
255
|
|
|
210
|
-
forEach(fileList, function (file) {
|
|
256
|
+
forEach(fileList, function (file: File) {
|
|
211
257
|
if (!file.name.startsWith(".") && !file.name.startsWith("DICOMDIR")) {
|
|
212
258
|
parsingQueue.push(file);
|
|
213
259
|
}
|
|
@@ -225,45 +271,66 @@ let parseFiles = function (fileList) {
|
|
|
225
271
|
* @param {File} file - File object to be parsed
|
|
226
272
|
* @returns {Promise} - Return a promise which will resolve to a image object or fail if an error occurs
|
|
227
273
|
*/
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
(numberOfTemporalPositions > 1
|
|
300
|
+
temporalPositionIdentifier !== undefined &&
|
|
301
|
+
(numberOfTemporalPositions as number) > 1
|
|
249
302
|
? true
|
|
250
303
|
: false;
|
|
251
304
|
|
|
252
|
-
let
|
|
253
|
-
let
|
|
254
|
-
|
|
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
|
|
260
|
-
let
|
|
323
|
+
// In these cases, the SOPInstanceUID (unique) is used as SeriesInstanceUID.
|
|
324
|
+
let uniqueId = singleFrameModalities.includes(modality)
|
|
261
325
|
? metadata["x00080018"]
|
|
262
326
|
: metadata["x0020000e"];
|
|
327
|
+
let seriesInstanceUID = metadata["x0020000e"];
|
|
263
328
|
let pixelSpacing = metadata["x00280030"];
|
|
264
329
|
let imageOrientation = metadata["x00200037"];
|
|
265
330
|
let imagePosition = metadata["x00200032"];
|
|
266
331
|
let sliceThickness = metadata["x00180050"];
|
|
332
|
+
let numberOfFrames = metadata["x00280008"];
|
|
333
|
+
let isMultiframe = (numberOfFrames as number) > 1 ? true : false;
|
|
267
334
|
|
|
268
335
|
if (dataSet.warnings.length > 0) {
|
|
269
336
|
// warnings
|
|
@@ -274,24 +341,28 @@ let parseFile = function (file) {
|
|
|
274
341
|
if (pixelDataElement) {
|
|
275
342
|
// done, pixelDataElement found
|
|
276
343
|
let instanceUID = metadata["x00080018"] || randomId();
|
|
277
|
-
let imageObject = {
|
|
344
|
+
let imageObject: Partial<ImageObject> = {
|
|
278
345
|
// data needed for rendering
|
|
279
346
|
file: file,
|
|
280
347
|
dataSet: dataSet
|
|
281
348
|
};
|
|
282
|
-
imageObject.metadata = metadata;
|
|
349
|
+
imageObject.metadata = metadata as MetaData;
|
|
283
350
|
imageObject.metadata.anonymized = false;
|
|
351
|
+
imageObject.metadata.larvitarSeriesInstanceUID = uniqueId;
|
|
284
352
|
imageObject.metadata.seriesUID = seriesInstanceUID;
|
|
285
353
|
imageObject.metadata.instanceUID = instanceUID;
|
|
286
354
|
imageObject.metadata.studyUID = metadata["x0020000d"];
|
|
287
355
|
imageObject.metadata.accessionNumber = metadata["x00080050"];
|
|
288
356
|
imageObject.metadata.studyDescription = metadata["x00081030"];
|
|
289
|
-
imageObject.metadata.patientName = metadata["x00100010"];
|
|
357
|
+
imageObject.metadata.patientName = metadata["x00100010"] as string;
|
|
290
358
|
imageObject.metadata.patientBirthdate = metadata["x00100030"];
|
|
291
|
-
imageObject.metadata.seriesDescription = metadata[
|
|
359
|
+
imageObject.metadata.seriesDescription = metadata[
|
|
360
|
+
"x0008103e"
|
|
361
|
+
] as string;
|
|
292
362
|
imageObject.metadata.seriesDate = metadata["x00080021"];
|
|
293
|
-
imageObject.metadata.seriesModality =
|
|
294
|
-
|
|
363
|
+
imageObject.metadata.seriesModality = metadata["x00080060"]
|
|
364
|
+
?.toString()
|
|
365
|
+
.toLowerCase();
|
|
295
366
|
imageObject.metadata.intercept = metadata["x00281052"];
|
|
296
367
|
imageObject.metadata.slope = metadata["x00281053"];
|
|
297
368
|
imageObject.metadata.pixelSpacing = pixelSpacing;
|
|
@@ -307,6 +378,9 @@ let parseFile = function (file) {
|
|
|
307
378
|
if (isMultiframe) {
|
|
308
379
|
imageObject.metadata.frameTime = metadata["x00181063"];
|
|
309
380
|
imageObject.metadata.frameDelay = metadata["x00181066"];
|
|
381
|
+
if (metadata["x00186060"]) {
|
|
382
|
+
imageObject.metadata.rWaveTimeVector = metadata["x00186060"];
|
|
383
|
+
}
|
|
310
384
|
}
|
|
311
385
|
imageObject.metadata.isMultiframe = isMultiframe;
|
|
312
386
|
if (is4D) {
|
|
@@ -323,38 +397,42 @@ let parseFile = function (file) {
|
|
|
323
397
|
imageObject.metadata.maxPixelValue = metadata["x00280107"];
|
|
324
398
|
imageObject.metadata.length = pixelDataElement.length;
|
|
325
399
|
imageObject.metadata.repr = getPixelRepresentation(dataSet);
|
|
326
|
-
resolve(imageObject);
|
|
400
|
+
resolve(imageObject as ImageObject);
|
|
327
401
|
} else if (SOPUID == "1.2.840.10008.5.1.4.1.1.104.1") {
|
|
328
|
-
let pdfObject = {
|
|
402
|
+
let pdfObject: Partial<ImageObject> = {
|
|
329
403
|
// data needed for rendering
|
|
330
404
|
file: file,
|
|
331
405
|
dataSet: dataSet
|
|
332
406
|
};
|
|
333
407
|
pdfObject.metadata = metadata;
|
|
408
|
+
pdfObject.metadata.larvitarSeriesInstanceUID = uniqueId;
|
|
334
409
|
pdfObject.metadata.seriesUID = seriesInstanceUID;
|
|
335
|
-
pdfObject.instanceUID =
|
|
410
|
+
pdfObject.instanceUID =
|
|
411
|
+
metadata["x00080018"]?.toString() || randomId();
|
|
336
412
|
pdfObject.metadata.studyUID = metadata["x0020000d"];
|
|
337
413
|
pdfObject.metadata.accessionNumber = metadata["x00080050"];
|
|
338
414
|
pdfObject.metadata.studyDescription = metadata["x00081030"];
|
|
339
|
-
pdfObject.metadata.patientName = metadata["x00100010"];
|
|
415
|
+
pdfObject.metadata.patientName = metadata["x00100010"] as string;
|
|
340
416
|
pdfObject.metadata.patientBirthdate = metadata["x00100030"];
|
|
341
417
|
pdfObject.metadata.seriesDate = metadata["x00080021"];
|
|
342
|
-
pdfObject.metadata.seriesModality =
|
|
343
|
-
|
|
418
|
+
pdfObject.metadata.seriesModality = metadata["x00080060"]
|
|
419
|
+
?.toString()
|
|
420
|
+
.toLowerCase();
|
|
344
421
|
pdfObject.metadata.mimeType = metadata["x00420012"];
|
|
345
422
|
pdfObject.metadata.is4D = false;
|
|
346
423
|
pdfObject.metadata.numberOfFrames = 0;
|
|
347
424
|
pdfObject.metadata.numberOfSlices = 0;
|
|
348
425
|
pdfObject.metadata.numberOfTemporalPositions = 0;
|
|
349
|
-
resolve(pdfObject);
|
|
426
|
+
resolve(pdfObject as ImageObject);
|
|
350
427
|
} else {
|
|
351
428
|
// done, no pixelData
|
|
352
429
|
reject("no pixelData");
|
|
353
430
|
}
|
|
354
431
|
}
|
|
355
432
|
} catch (err) {
|
|
356
|
-
|
|
357
|
-
|
|
433
|
+
reject(
|
|
434
|
+
`Larvitar: can not read file "${file.name}" \nParsing error: ${err}`
|
|
435
|
+
);
|
|
358
436
|
}
|
|
359
437
|
};
|
|
360
438
|
reader.readAsArrayBuffer(file);
|
|
@@ -8,7 +8,7 @@ import cornerstone from "cornerstone-core";
|
|
|
8
8
|
import { each, find } from "lodash";
|
|
9
9
|
|
|
10
10
|
// internal libraries
|
|
11
|
-
import {
|
|
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}
|
|
49
|
+
* @param {String} preset - The image preset name or the preset object
|
|
50
50
|
*/
|
|
51
|
-
export const setImagePreset = function (
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
viewport
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
149
|
+
setStore([
|
|
150
|
+
"contrast",
|
|
118
151
|
viewportName,
|
|
119
152
|
viewport.voi.windowWidth,
|
|
120
153
|
viewport.voi.windowCenter
|