larvitar 1.5.14 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +40 -0
- package/dist/imaging/MetaDataTypes.d.ts +3489 -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 +113 -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/BorderMagnifyTool.d.ts +18 -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/imaging/waveforms/ecg.d.ts +39 -0
- package/dist/index.d.ts +35 -0
- package/dist/larvitar.js +90104 -0
- package/dist/larvitar.js.map +1 -0
- package/imaging/MetaDataReadable.ts +41 -0
- package/imaging/MetaDataTypes.ts +3491 -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} +107 -43
- package/imaging/{imageParsing.js → imageParsing.ts} +160 -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} +231 -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 +54 -0
- package/imaging/tools/README.md +27 -0
- package/imaging/tools/custom/4dSliceScrollTool.js +47 -46
- package/imaging/tools/custom/BorderMagnifyTool.js +99 -0
- 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} +119 -33
- 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 +200 -0
- package/imaging/waveforms/ecg.ts +191 -0
- package/{index.js → index.ts} +53 -14
- package/jsdoc.json +1 -1
- package/package.json +35 -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,67 @@ let parseFiles = function (fileList) {
|
|
|
225
271
|
* @param {File} file - File object to be parsed
|
|
226
272
|
* @returns {Promise} - Return a promise which will resolve to a image object or fail if an error occurs
|
|
227
273
|
*/
|
|
228
|
-
|
|
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;
|
|
334
|
+
let waveform = metadata["x50003000"] ? true : false;
|
|
267
335
|
|
|
268
336
|
if (dataSet.warnings.length > 0) {
|
|
269
337
|
// warnings
|
|
@@ -274,24 +342,28 @@ let parseFile = function (file) {
|
|
|
274
342
|
if (pixelDataElement) {
|
|
275
343
|
// done, pixelDataElement found
|
|
276
344
|
let instanceUID = metadata["x00080018"] || randomId();
|
|
277
|
-
let imageObject = {
|
|
345
|
+
let imageObject: Partial<ImageObject> = {
|
|
278
346
|
// data needed for rendering
|
|
279
347
|
file: file,
|
|
280
348
|
dataSet: dataSet
|
|
281
349
|
};
|
|
282
|
-
imageObject.metadata = metadata;
|
|
350
|
+
imageObject.metadata = metadata as MetaData;
|
|
283
351
|
imageObject.metadata.anonymized = false;
|
|
352
|
+
imageObject.metadata.larvitarSeriesInstanceUID = uniqueId;
|
|
284
353
|
imageObject.metadata.seriesUID = seriesInstanceUID;
|
|
285
354
|
imageObject.metadata.instanceUID = instanceUID;
|
|
286
355
|
imageObject.metadata.studyUID = metadata["x0020000d"];
|
|
287
356
|
imageObject.metadata.accessionNumber = metadata["x00080050"];
|
|
288
357
|
imageObject.metadata.studyDescription = metadata["x00081030"];
|
|
289
|
-
imageObject.metadata.patientName = metadata["x00100010"];
|
|
358
|
+
imageObject.metadata.patientName = metadata["x00100010"] as string;
|
|
290
359
|
imageObject.metadata.patientBirthdate = metadata["x00100030"];
|
|
291
|
-
imageObject.metadata.seriesDescription = metadata[
|
|
360
|
+
imageObject.metadata.seriesDescription = metadata[
|
|
361
|
+
"x0008103e"
|
|
362
|
+
] as string;
|
|
292
363
|
imageObject.metadata.seriesDate = metadata["x00080021"];
|
|
293
|
-
imageObject.metadata.seriesModality =
|
|
294
|
-
|
|
364
|
+
imageObject.metadata.seriesModality = metadata["x00080060"]
|
|
365
|
+
?.toString()
|
|
366
|
+
.toLowerCase();
|
|
295
367
|
imageObject.metadata.intercept = metadata["x00281052"];
|
|
296
368
|
imageObject.metadata.slope = metadata["x00281053"];
|
|
297
369
|
imageObject.metadata.pixelSpacing = pixelSpacing;
|
|
@@ -307,6 +379,9 @@ let parseFile = function (file) {
|
|
|
307
379
|
if (isMultiframe) {
|
|
308
380
|
imageObject.metadata.frameTime = metadata["x00181063"];
|
|
309
381
|
imageObject.metadata.frameDelay = metadata["x00181066"];
|
|
382
|
+
if (metadata["x00186060"]) {
|
|
383
|
+
imageObject.metadata.rWaveTimeVector = metadata["x00186060"];
|
|
384
|
+
}
|
|
310
385
|
}
|
|
311
386
|
imageObject.metadata.isMultiframe = isMultiframe;
|
|
312
387
|
if (is4D) {
|
|
@@ -317,44 +392,49 @@ let parseFile = function (file) {
|
|
|
317
392
|
imageObject.metadata.contentTime = metadata["x00080033"];
|
|
318
393
|
}
|
|
319
394
|
imageObject.metadata.is4D = is4D;
|
|
395
|
+
imageObject.metadata.waveform = waveform;
|
|
320
396
|
imageObject.metadata.windowCenter = metadata["x00281050"];
|
|
321
397
|
imageObject.metadata.windowWidth = metadata["x00281051"];
|
|
322
398
|
imageObject.metadata.minPixelValue = metadata["x00280106"];
|
|
323
399
|
imageObject.metadata.maxPixelValue = metadata["x00280107"];
|
|
324
400
|
imageObject.metadata.length = pixelDataElement.length;
|
|
325
401
|
imageObject.metadata.repr = getPixelRepresentation(dataSet);
|
|
326
|
-
resolve(imageObject);
|
|
402
|
+
resolve(imageObject as ImageObject);
|
|
327
403
|
} else if (SOPUID == "1.2.840.10008.5.1.4.1.1.104.1") {
|
|
328
|
-
let pdfObject = {
|
|
404
|
+
let pdfObject: Partial<ImageObject> = {
|
|
329
405
|
// data needed for rendering
|
|
330
406
|
file: file,
|
|
331
407
|
dataSet: dataSet
|
|
332
408
|
};
|
|
333
409
|
pdfObject.metadata = metadata;
|
|
410
|
+
pdfObject.metadata.larvitarSeriesInstanceUID = uniqueId;
|
|
334
411
|
pdfObject.metadata.seriesUID = seriesInstanceUID;
|
|
335
|
-
pdfObject.instanceUID =
|
|
412
|
+
pdfObject.instanceUID =
|
|
413
|
+
metadata["x00080018"]?.toString() || randomId();
|
|
336
414
|
pdfObject.metadata.studyUID = metadata["x0020000d"];
|
|
337
415
|
pdfObject.metadata.accessionNumber = metadata["x00080050"];
|
|
338
416
|
pdfObject.metadata.studyDescription = metadata["x00081030"];
|
|
339
|
-
pdfObject.metadata.patientName = metadata["x00100010"];
|
|
417
|
+
pdfObject.metadata.patientName = metadata["x00100010"] as string;
|
|
340
418
|
pdfObject.metadata.patientBirthdate = metadata["x00100030"];
|
|
341
419
|
pdfObject.metadata.seriesDate = metadata["x00080021"];
|
|
342
|
-
pdfObject.metadata.seriesModality =
|
|
343
|
-
|
|
420
|
+
pdfObject.metadata.seriesModality = metadata["x00080060"]
|
|
421
|
+
?.toString()
|
|
422
|
+
.toLowerCase();
|
|
344
423
|
pdfObject.metadata.mimeType = metadata["x00420012"];
|
|
345
424
|
pdfObject.metadata.is4D = false;
|
|
346
425
|
pdfObject.metadata.numberOfFrames = 0;
|
|
347
426
|
pdfObject.metadata.numberOfSlices = 0;
|
|
348
427
|
pdfObject.metadata.numberOfTemporalPositions = 0;
|
|
349
|
-
resolve(pdfObject);
|
|
428
|
+
resolve(pdfObject as ImageObject);
|
|
350
429
|
} else {
|
|
351
430
|
// done, no pixelData
|
|
352
431
|
reject("no pixelData");
|
|
353
432
|
}
|
|
354
433
|
}
|
|
355
434
|
} catch (err) {
|
|
356
|
-
|
|
357
|
-
|
|
435
|
+
reject(
|
|
436
|
+
`Larvitar: can not read file "${file.name}" \nParsing error: ${err}`
|
|
437
|
+
);
|
|
358
438
|
}
|
|
359
439
|
};
|
|
360
440
|
reader.readAsArrayBuffer(file);
|
|
@@ -8,7 +8,7 @@ import cornerstone from "cornerstone-core";
|
|
|
8
8
|
import { each, find } from "lodash";
|
|
9
9
|
|
|
10
10
|
// internal libraries
|
|
11
|
-
import {
|
|
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
|