larvitar 2.0.4 → 2.0.6
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/README.md +2 -2
- package/dist/larvitar.js +5 -3
- package/dist/larvitar.js.map +1 -1
- package/package.json +6 -2
- package/.github/workflows/build-docs.yml +0 -59
- package/.github/workflows/codeql-analysis.yml +0 -71
- package/.github/workflows/deploy.yml +0 -37
- package/.vscode/settings.json +0 -4
- package/CODE_OF_CONDUCT.md +0 -76
- package/MIGRATION.md +0 -25
- package/bundler/webpack.common.js +0 -27
- package/bundler/webpack.dev.js +0 -23
- package/bundler/webpack.prod.js +0 -19
- package/decs.d.ts +0 -12
- package/imaging/MetaDataReadable.ts +0 -42
- package/imaging/MetaDataTypes.ts +0 -3491
- package/imaging/dataDictionary.json +0 -21866
- package/imaging/imageAnonymization.ts +0 -135
- package/imaging/imageColormaps.ts +0 -217
- package/imaging/imageContours.ts +0 -196
- package/imaging/imageIo.ts +0 -251
- package/imaging/imageLayers.ts +0 -121
- package/imaging/imageLoading.ts +0 -299
- package/imaging/imageParsing.ts +0 -444
- package/imaging/imagePresets.ts +0 -156
- package/imaging/imageRendering.ts +0 -1091
- package/imaging/imageReslice.ts +0 -87
- package/imaging/imageStore.ts +0 -487
- package/imaging/imageTags.ts +0 -609
- package/imaging/imageTools.js +0 -708
- package/imaging/imageUtils.ts +0 -1079
- package/imaging/loaders/commonLoader.ts +0 -275
- package/imaging/loaders/dicomLoader.ts +0 -66
- package/imaging/loaders/fileLoader.ts +0 -71
- package/imaging/loaders/multiframeLoader.ts +0 -435
- package/imaging/loaders/nrrdLoader.ts +0 -630
- package/imaging/loaders/resliceLoader.ts +0 -205
- package/imaging/monitors/memory.ts +0 -151
- package/imaging/monitors/performance.ts +0 -34
- package/imaging/parsers/ecg.ts +0 -54
- package/imaging/parsers/nrrd.js +0 -485
- package/imaging/tools/README.md +0 -27
- package/imaging/tools/custom/4dSliceScrollTool.js +0 -146
- package/imaging/tools/custom/BorderMagnifyTool.js +0 -99
- package/imaging/tools/custom/contourTool.js +0 -1884
- package/imaging/tools/custom/diameterTool.js +0 -141
- package/imaging/tools/custom/editMaskTool.js +0 -141
- package/imaging/tools/custom/ellipticalRoiOverlayTool.js +0 -534
- package/imaging/tools/custom/polygonSegmentationMixin.js +0 -245
- package/imaging/tools/custom/polylineScissorsTool.js +0 -59
- package/imaging/tools/custom/rectangleRoiOverlayTool.js +0 -564
- package/imaging/tools/custom/seedTool.js +0 -342
- package/imaging/tools/custom/setLabelMap3D.ts +0 -242
- package/imaging/tools/custom/thresholdsBrushTool.js +0 -161
- package/imaging/tools/default.ts +0 -594
- package/imaging/tools/interaction.ts +0 -266
- package/imaging/tools/io.ts +0 -229
- package/imaging/tools/main.ts +0 -424
- package/imaging/tools/segmentation.ts +0 -532
- package/imaging/tools/segmentations.md +0 -38
- package/imaging/tools/state.ts +0 -74
- package/imaging/tools/strategies/eraseFreehand.js +0 -76
- package/imaging/tools/strategies/fillFreehand.js +0 -79
- package/imaging/tools/strategies/index.js +0 -2
- package/imaging/tools/types.d.ts +0 -243
- package/imaging/types.d.ts +0 -200
- package/imaging/waveforms/ecg.ts +0 -191
- package/index.ts +0 -431
- package/jsdoc.json +0 -52
- package/rollup.config.js +0 -51
- package/template/.gitkeep +0 -0
- package/tsconfig.json +0 -102
package/imaging/imageIo.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
/** @module imaging/imageIo
|
|
2
|
-
* @desc This file provides I/O functionalities on NRRD files and DICOM images
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// external libraries
|
|
6
|
-
import cornerstone from "cornerstone-core";
|
|
7
|
-
import { forEach, find } from "lodash";
|
|
8
|
-
|
|
9
|
-
// internal libraries
|
|
10
|
-
import {
|
|
11
|
-
getMeanValue,
|
|
12
|
-
getDistanceBetweenSlices,
|
|
13
|
-
getTypedArrayFromDataType
|
|
14
|
-
} from "./imageUtils";
|
|
15
|
-
import store from "./imageStore";
|
|
16
|
-
import { parse } from "./parsers/nrrd";
|
|
17
|
-
import { checkMemoryAllocation } from "./monitors/memory";
|
|
18
|
-
import { Series, Header, Volume, TypedArray } from "./types";
|
|
19
|
-
|
|
20
|
-
/*
|
|
21
|
-
* This module provides the following functions to be exported:
|
|
22
|
-
* buildHeader(series)
|
|
23
|
-
* getCachedPixelData(imageId)
|
|
24
|
-
* buildData(series)
|
|
25
|
-
* importNRRDImage(bufferArray)
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Build the image header from slices' metadata
|
|
30
|
-
* @function buildHeader
|
|
31
|
-
* @param {Object} series - Cornerstone series object
|
|
32
|
-
* @returns {Object} header: image metadata
|
|
33
|
-
*/
|
|
34
|
-
export const buildHeader = function (series: Series) {
|
|
35
|
-
let header: Partial<Header> = {};
|
|
36
|
-
|
|
37
|
-
forEach(series.imageIds, function (imageId: string) {
|
|
38
|
-
header[imageId] = series.instances[imageId].metadata;
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
let volume: Partial<Volume> = {};
|
|
42
|
-
|
|
43
|
-
volume.imageIds = series.imageIds;
|
|
44
|
-
volume.seriesId = series.instances[series.imageIds[0]].metadata.seriesUID;
|
|
45
|
-
volume.rows =
|
|
46
|
-
series.instances[series.imageIds[0]].metadata.rows ||
|
|
47
|
-
series.instances[series.imageIds[0]].metadata.x00280010;
|
|
48
|
-
volume.cols =
|
|
49
|
-
series.instances[series.imageIds[0]].metadata.cols ||
|
|
50
|
-
series.instances[series.imageIds[0]].metadata.x00280011;
|
|
51
|
-
volume.slope = series.instances[series.imageIds[0]].metadata.slope as number;
|
|
52
|
-
volume.repr = series.instances[series.imageIds[0]].metadata.repr as string;
|
|
53
|
-
volume.intercept = series.instances[series.imageIds[0]].metadata
|
|
54
|
-
.intercept as number;
|
|
55
|
-
volume.imagePosition = series.instances[series.imageIds[0]].metadata
|
|
56
|
-
.imagePosition as [number, number];
|
|
57
|
-
volume.numberOfSlices = series.imageIds.length;
|
|
58
|
-
|
|
59
|
-
// @ts-ignore
|
|
60
|
-
volume.imageOrientation = getMeanValue(
|
|
61
|
-
series,
|
|
62
|
-
"imageOrientation",
|
|
63
|
-
true
|
|
64
|
-
) as number[];
|
|
65
|
-
|
|
66
|
-
// @ts-ignore
|
|
67
|
-
volume.pixelSpacing = getMeanValue(series, "pixelSpacing", true) as [
|
|
68
|
-
number,
|
|
69
|
-
number
|
|
70
|
-
];
|
|
71
|
-
// volume.maxPixelValue = getMeanValue(series, "maxPixelValue", false);
|
|
72
|
-
// volume.minPixelValue = getMeanValue(series, "minPixelValue", false);
|
|
73
|
-
volume.sliceThickness = getDistanceBetweenSlices(series, 0, 1);
|
|
74
|
-
|
|
75
|
-
header.volume = volume as Volume;
|
|
76
|
-
|
|
77
|
-
return header as Header;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get cached pixel data
|
|
82
|
-
* @function getCachedPixelData
|
|
83
|
-
* @param {String} imageId - ImageId of the cached image
|
|
84
|
-
* @returns {Promise} A promise which will resolve to a pixel data array or fail if an error occurs
|
|
85
|
-
*/
|
|
86
|
-
|
|
87
|
-
export const getCachedPixelData = function (imageId: string) {
|
|
88
|
-
let cachedImage = find(cornerstone.imageCache.cachedImages, [
|
|
89
|
-
"imageId",
|
|
90
|
-
imageId
|
|
91
|
-
]);
|
|
92
|
-
let promise = new Promise<number[]>((resolve, reject) => {
|
|
93
|
-
if (cachedImage && cachedImage.image) {
|
|
94
|
-
resolve(cachedImage.image.getPixelData());
|
|
95
|
-
} else {
|
|
96
|
-
cornerstone
|
|
97
|
-
.loadImage(imageId)
|
|
98
|
-
.then(image => resolve(image.getPixelData()))
|
|
99
|
-
.catch(err => reject(err));
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
return promise;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Build the contiguous typed array from slices
|
|
107
|
-
* @function buildData
|
|
108
|
-
* @param {Object} series - Cornerstone series object
|
|
109
|
-
* @param {Bool} useSeriesData - Flag to force using "series" data instead of cached ones
|
|
110
|
-
* @returns {Array} Contiguous pixel array
|
|
111
|
-
*/
|
|
112
|
-
export const buildData = function (series: Series, useSeriesData: boolean) {
|
|
113
|
-
if (checkMemoryAllocation(series.bytes)) {
|
|
114
|
-
let t0 = performance.now();
|
|
115
|
-
let repr = series.instances[series.imageIds[0]].metadata.repr;
|
|
116
|
-
let rows =
|
|
117
|
-
(series.instances[series.imageIds[0]].metadata.rows as number) ||
|
|
118
|
-
(series.instances[series.imageIds[0]].metadata.x00280010 as number);
|
|
119
|
-
let cols =
|
|
120
|
-
(series.instances[series.imageIds[0]].metadata.cols as number) ||
|
|
121
|
-
(series.instances[series.imageIds[0]].metadata.x00280011 as number);
|
|
122
|
-
let len = rows * cols * series.imageIds.length;
|
|
123
|
-
|
|
124
|
-
if (!repr) {
|
|
125
|
-
throw new Error("Image representation metadata not found");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
let typedArray = getTypedArrayFromDataType(repr as string);
|
|
129
|
-
|
|
130
|
-
if (!typedArray) {
|
|
131
|
-
throw new Error("Image representation not supported");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let data = new typedArray(len);
|
|
135
|
-
let offsetData = 0;
|
|
136
|
-
|
|
137
|
-
// use input data or cached data
|
|
138
|
-
if (useSeriesData) {
|
|
139
|
-
forEach(series.imageIds, function (imageId: string) {
|
|
140
|
-
const sliceData = series.instances[imageId].pixelData;
|
|
141
|
-
if (sliceData) {
|
|
142
|
-
data.set(sliceData, offsetData);
|
|
143
|
-
offsetData += sliceData.length;
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
let t1 = performance.now();
|
|
147
|
-
console.log(`Call to buildData took ${t1 - t0} milliseconds.`);
|
|
148
|
-
return data;
|
|
149
|
-
} else {
|
|
150
|
-
store.addSeriesId(series.seriesUID, series.imageIds);
|
|
151
|
-
let image_counter = 0;
|
|
152
|
-
forEach(series.imageIds, function (imageId: string) {
|
|
153
|
-
getCachedPixelData(imageId).then((sliceData: number[]) => {
|
|
154
|
-
data.set(sliceData, offsetData);
|
|
155
|
-
offsetData += sliceData.length;
|
|
156
|
-
image_counter += 1;
|
|
157
|
-
if (image_counter == series.imageIds.length) {
|
|
158
|
-
let t1 = performance.now();
|
|
159
|
-
console.log(`Call to buildData took ${t1 - t0} milliseconds.`);
|
|
160
|
-
return data;
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
throw new Error("Data has not been builded: not enough memory");
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Build the contiguous typed array from slices (async version)
|
|
172
|
-
* @function buildDataAsync
|
|
173
|
-
* @param {Object} series - Cornerstone series object
|
|
174
|
-
* @param {Number} time - Time(s) to wait for garbage collector
|
|
175
|
-
* @param {Function} resolve - Promise resolve function
|
|
176
|
-
* @param {Function} reject - Promise reject function
|
|
177
|
-
*/
|
|
178
|
-
export const buildDataAsync = function (
|
|
179
|
-
series: Series,
|
|
180
|
-
time: number,
|
|
181
|
-
resolve: (response: TypedArray) => void,
|
|
182
|
-
reject: (response: string) => void
|
|
183
|
-
) {
|
|
184
|
-
const memoryAllocation = checkMemoryAllocation(series.bytes);
|
|
185
|
-
|
|
186
|
-
if (memoryAllocation) {
|
|
187
|
-
let t0 = performance.now();
|
|
188
|
-
let repr = series.instances[series.imageIds[0]].metadata.repr;
|
|
189
|
-
let rows =
|
|
190
|
-
(series.instances[series.imageIds[0]].metadata.rows as number) ||
|
|
191
|
-
(series.instances[series.imageIds[0]].metadata.x00280010 as number);
|
|
192
|
-
let cols =
|
|
193
|
-
(series.instances[series.imageIds[0]].metadata.cols as number) ||
|
|
194
|
-
(series.instances[series.imageIds[0]].metadata.x00280011 as number);
|
|
195
|
-
let len = rows * cols * series.imageIds.length;
|
|
196
|
-
|
|
197
|
-
if (!repr) {
|
|
198
|
-
throw new Error("Image representation metadata not found");
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
let typedArray = getTypedArrayFromDataType(repr as string);
|
|
202
|
-
|
|
203
|
-
if (!typedArray) {
|
|
204
|
-
throw new Error("Image representation not supported");
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
let data = new typedArray(len);
|
|
208
|
-
let offsetData = 0;
|
|
209
|
-
|
|
210
|
-
let imageIds = series.imageIds.slice();
|
|
211
|
-
store.addSeriesId(series.seriesUID, series.imageIds);
|
|
212
|
-
|
|
213
|
-
function runFillPixelData(data: TypedArray) {
|
|
214
|
-
let imageId = imageIds.shift();
|
|
215
|
-
if (imageId) {
|
|
216
|
-
getCachedPixelData(imageId).then(sliceData => {
|
|
217
|
-
data.set(sliceData, offsetData);
|
|
218
|
-
offsetData += sliceData.length;
|
|
219
|
-
// this does the trick: delay next computation to next tick
|
|
220
|
-
setTimeout(() => {
|
|
221
|
-
runFillPixelData(data);
|
|
222
|
-
}, 0);
|
|
223
|
-
});
|
|
224
|
-
} else {
|
|
225
|
-
let t1 = performance.now();
|
|
226
|
-
console.log(`Call to buildDataAsync took ${t1 - t0} milliseconds.`);
|
|
227
|
-
resolve(data);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
runFillPixelData(data);
|
|
231
|
-
} else if (time > 0) {
|
|
232
|
-
setTimeout(function () {
|
|
233
|
-
time = time - 5;
|
|
234
|
-
buildDataAsync(series, time, resolve, reject);
|
|
235
|
-
}, 5000);
|
|
236
|
-
} else {
|
|
237
|
-
reject("Data has not been builded: not enough memory");
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Import NRRD image from bufferArray
|
|
243
|
-
* @function importNRRDImage
|
|
244
|
-
* @param {ArrayBuffer} bufferArray - buffer array from nrrd file
|
|
245
|
-
* @returns {Array} Parsed pixel data array
|
|
246
|
-
*/
|
|
247
|
-
export const importNRRDImage = function (bufferArray: ArrayBuffer) {
|
|
248
|
-
// get the data
|
|
249
|
-
let volume = parse(bufferArray, { headerOnly: false });
|
|
250
|
-
return volume;
|
|
251
|
-
};
|
package/imaging/imageLayers.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/** @module imaging/imageLayers
|
|
2
|
-
* @desc This file provides functionalities for
|
|
3
|
-
* rendering image layers using cornerstone stack
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// external libraries
|
|
7
|
-
import cornerstone from "cornerstone-core";
|
|
8
|
-
|
|
9
|
-
// internal libraries
|
|
10
|
-
import { isElement } from "./imageUtils";
|
|
11
|
-
import { Series } from "./types";
|
|
12
|
-
|
|
13
|
-
/*
|
|
14
|
-
* This module provides the following functions to be exported:
|
|
15
|
-
* buildLayer(series, tag, options)
|
|
16
|
-
* updateLayer(elementId, layerId, options)
|
|
17
|
-
* getActiveLayer(elementId)
|
|
18
|
-
* setActiveLayer(elementId, layerId)
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Build the image layers object
|
|
23
|
-
* @function buildLayers
|
|
24
|
-
* @param {Object} series - Cornerstone series object
|
|
25
|
-
* @param {String} tag - Tag for the layer
|
|
26
|
-
* @param {Object} options - layer options {opacity:float, colormap: str}
|
|
27
|
-
* @returns {Object} Cornerstone layer object
|
|
28
|
-
*/
|
|
29
|
-
export const buildLayer = function (
|
|
30
|
-
series: Series,
|
|
31
|
-
tag: string,
|
|
32
|
-
options: { opacity: number; colormap: string }
|
|
33
|
-
) {
|
|
34
|
-
let layer = {
|
|
35
|
-
imageIds: series.imageIds,
|
|
36
|
-
currentImageIdIndex: Math.floor(series.imageIds.length / 2),
|
|
37
|
-
options: {
|
|
38
|
-
name: tag,
|
|
39
|
-
opacity: options?.opacity ? options?.opacity : 1.0,
|
|
40
|
-
visible: true,
|
|
41
|
-
viewport: {
|
|
42
|
-
colormap: options?.colormap ? options?.colormap : "gray"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
return layer;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Change the options of a layer
|
|
51
|
-
* @instance
|
|
52
|
-
* @function updateLayer
|
|
53
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
54
|
-
* @param {string} layer - The layer id
|
|
55
|
-
* @param {Object} options - The new layer's options
|
|
56
|
-
*/
|
|
57
|
-
export const updateLayer = function (
|
|
58
|
-
elementId: string | HTMLElement,
|
|
59
|
-
layerId: string,
|
|
60
|
-
options: { opacity: number; colormap: string }
|
|
61
|
-
) {
|
|
62
|
-
let element = isElement(elementId)
|
|
63
|
-
? (elementId as HTMLElement)
|
|
64
|
-
: document.getElementById(elementId as string);
|
|
65
|
-
if (!element) {
|
|
66
|
-
console.log("not element");
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
let layer = cornerstone.getLayer(element, layerId);
|
|
70
|
-
if (!layer.options) {
|
|
71
|
-
layer["options"] = {};
|
|
72
|
-
}
|
|
73
|
-
if (!layer.viewport) {
|
|
74
|
-
layer["viewport"] = {};
|
|
75
|
-
}
|
|
76
|
-
layer.options.opacity =
|
|
77
|
-
options.opacity != null ? options.opacity : layer.options.opacity;
|
|
78
|
-
layer.viewport.colormap = options.colormap
|
|
79
|
-
? options.colormap
|
|
80
|
-
: layer.viewport.colormap;
|
|
81
|
-
cornerstone.updateImage(element);
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Get the active layer
|
|
86
|
-
* @instance
|
|
87
|
-
* @function getActiveLayer
|
|
88
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
89
|
-
* @returns {Object} layer - The active layer object
|
|
90
|
-
*/
|
|
91
|
-
export const getActiveLayer = function (elementId: string | HTMLElement) {
|
|
92
|
-
let element = isElement(elementId)
|
|
93
|
-
? (elementId as HTMLElement)
|
|
94
|
-
: document.getElementById(elementId as string);
|
|
95
|
-
if (!element) {
|
|
96
|
-
console.log("not element");
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
return cornerstone.getActiveLayer(element);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Set the active layer
|
|
104
|
-
* @instance
|
|
105
|
-
* @function setActiveLayer
|
|
106
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
107
|
-
* @param {String} layerId - The id of the layer
|
|
108
|
-
*/
|
|
109
|
-
export const setActiveLayer = function (
|
|
110
|
-
elementId: string | HTMLElement,
|
|
111
|
-
layerId: string
|
|
112
|
-
) {
|
|
113
|
-
let element = isElement(elementId)
|
|
114
|
-
? (elementId as HTMLElement)
|
|
115
|
-
: document.getElementById(elementId as string);
|
|
116
|
-
if (!element) {
|
|
117
|
-
console.log("not element");
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
cornerstone.setActiveLayer(element, layerId);
|
|
121
|
-
};
|
package/imaging/imageLoading.ts
DELETED
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
/** @module imaging/imageLoading
|
|
2
|
-
* @desc This file provides functionalities for
|
|
3
|
-
* initialize, configure and update DICOMImageLoader
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// external libraries
|
|
7
|
-
import cornerstone from "cornerstone-core";
|
|
8
|
-
import dicomParser from "dicom-parser";
|
|
9
|
-
// import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader/dist/cornerstoneDICOMImageLoader.bundle.min.js";
|
|
10
|
-
import { default as cornerstoneDICOMImageLoader } from "cornerstone-wado-image-loader";
|
|
11
|
-
import cornerstoneWebImageLoader from "cornerstone-web-image-loader";
|
|
12
|
-
import cornerstoneFileImageLoader from "cornerstone-file-image-loader";
|
|
13
|
-
import { forEach } from "lodash";
|
|
14
|
-
|
|
15
|
-
// internal libraries
|
|
16
|
-
import store from "./imageStore";
|
|
17
|
-
import { getSortedStack, getSortedUIDs } from "./imageUtils";
|
|
18
|
-
import { loadNrrdImage } from "./loaders/nrrdLoader";
|
|
19
|
-
import { loadReslicedImage } from "./loaders/resliceLoader";
|
|
20
|
-
import { loadMultiFrameImage } from "./loaders/multiframeLoader";
|
|
21
|
-
import { ImageObject, Instance, Series, StagedProtocol } from "./types";
|
|
22
|
-
import { getLarvitarManager } from "./loaders/commonLoader";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Global standard configuration
|
|
26
|
-
* @inner
|
|
27
|
-
* @var {Object} globalConfig
|
|
28
|
-
* @property {Number} maxWebWorkers - number of maximum web workers
|
|
29
|
-
* @property {String} webWorkerPath - path to default DICOM web worker
|
|
30
|
-
* @property {} - see https://github.com/cornerstonejs/cornerstoneDICOMImageLoader/blob/master/docs/WebWorkers.md
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
const MAX_CONCURRENCY = 6;
|
|
34
|
-
const globalConfig = {
|
|
35
|
-
maxWebWorkers: Math.max(
|
|
36
|
-
Math.min(navigator.hardwareConcurrency - 1, MAX_CONCURRENCY),
|
|
37
|
-
1
|
|
38
|
-
),
|
|
39
|
-
startWebWorkersOnDemand: true,
|
|
40
|
-
taskConfiguration: {
|
|
41
|
-
decodeTask: {
|
|
42
|
-
loadCodecsOnStartup: true,
|
|
43
|
-
initializeCodecsOnStartup: false,
|
|
44
|
-
strict: true
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/*
|
|
50
|
-
* This module provides the following functions to be exported:
|
|
51
|
-
* initializeImageLoader(config)
|
|
52
|
-
* initializeWebImageLoader()
|
|
53
|
-
* initializeFileImageLoader()
|
|
54
|
-
* registerNRRDImageLoader()
|
|
55
|
-
* registerResliceLoader()
|
|
56
|
-
* updateLoadedStack(seriesData, allSeriesStack)
|
|
57
|
-
*/
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Configure DICOMImageLoader
|
|
61
|
-
* @instance
|
|
62
|
-
* @function initializeImageLoader
|
|
63
|
-
* @param {Object} config - Custom config @default globalConfig
|
|
64
|
-
*/
|
|
65
|
-
export const initializeImageLoader = function (config?: typeof globalConfig) {
|
|
66
|
-
let imageLoaderConfig = config ? config : globalConfig;
|
|
67
|
-
cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
|
|
68
|
-
cornerstoneDICOMImageLoader.external.dicomParser = dicomParser;
|
|
69
|
-
cornerstoneDICOMImageLoader.webWorkerManager.initialize(imageLoaderConfig);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Configure cornerstoneWebImageLoader
|
|
74
|
-
* @instance
|
|
75
|
-
* @function initializeWebImageLoader
|
|
76
|
-
*/
|
|
77
|
-
export const initializeWebImageLoader = function () {
|
|
78
|
-
cornerstoneWebImageLoader.external.cornerstone = cornerstone;
|
|
79
|
-
cornerstoneWebImageLoader.configure({
|
|
80
|
-
beforeSend: function () {
|
|
81
|
-
// add xhr as function arg
|
|
82
|
-
// Add custom headers here (e.g. auth tokens)
|
|
83
|
-
// xhr.setRequestHeader('x-auth-token', 'my auth token');
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Configure cornerstoneFileImageLoader
|
|
90
|
-
* @instance
|
|
91
|
-
* @function initializeFileImageLoader
|
|
92
|
-
*/
|
|
93
|
-
export const initializeFileImageLoader = function () {
|
|
94
|
-
cornerstoneFileImageLoader.external.cornerstone = cornerstone;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Register custom NRRD ImageLoader
|
|
99
|
-
* @instance
|
|
100
|
-
* @function registerNRRDImageLoader
|
|
101
|
-
*/
|
|
102
|
-
export const registerNRRDImageLoader = function () {
|
|
103
|
-
cornerstone.registerImageLoader("nrrdLoader", loadNrrdImage);
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Register custom Reslice ImageLoader
|
|
108
|
-
* @instance
|
|
109
|
-
* @function registerResliceLoader
|
|
110
|
-
*/
|
|
111
|
-
export const registerResliceLoader = function () {
|
|
112
|
-
cornerstone.registerImageLoader("resliceLoader", loadReslicedImage);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Register custom MultiFrame ImageLoader
|
|
117
|
-
* @instance
|
|
118
|
-
* @function registerMultiFrameImageLoader
|
|
119
|
-
*/
|
|
120
|
-
export const registerMultiFrameImageLoader = function () {
|
|
121
|
-
cornerstone.registerImageLoader("multiFrameLoader", loadMultiFrameImage);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Update the allSeriesStack object using DICOMImageLoader fileManager
|
|
126
|
-
* @instance
|
|
127
|
-
* @function updateLoadedStack
|
|
128
|
-
* @param {Object} seriesData - Cornerstone series object
|
|
129
|
-
* @param {Object} allSeriesStack - Dict containing all series objects
|
|
130
|
-
* @param {String} customId - Optional custom id to overwrite seriesUID as default one
|
|
131
|
-
* @param {number} sliceIndex - Optional custom index to overwrite slice index as default one
|
|
132
|
-
*/
|
|
133
|
-
export const updateLoadedStack = function (
|
|
134
|
-
seriesData: ImageObject,
|
|
135
|
-
allSeriesStack: ReturnType<typeof getLarvitarManager>,
|
|
136
|
-
customId?: string,
|
|
137
|
-
sliceIndex?: number
|
|
138
|
-
) {
|
|
139
|
-
let lid = seriesData.metadata.larvitarSeriesInstanceUID;
|
|
140
|
-
let sid = seriesData.metadata.seriesUID;
|
|
141
|
-
let ssid = seriesData.metadata.studyUID;
|
|
142
|
-
let iid = seriesData.metadata.instanceUID as string;
|
|
143
|
-
let seriesDescription = seriesData.metadata.seriesDescription;
|
|
144
|
-
let numberOfSlices = seriesData.metadata["x00540081"]
|
|
145
|
-
? seriesData.metadata["x00540081"]
|
|
146
|
-
: seriesData.metadata["x00201002"];
|
|
147
|
-
let numberOfFrames = seriesData.metadata["x00280008"];
|
|
148
|
-
let modality = seriesData.metadata["x00080060"];
|
|
149
|
-
let isMultiframe =
|
|
150
|
-
numberOfFrames && (numberOfFrames as number) > 1 ? true : false;
|
|
151
|
-
let numberOfTemporalPositions = seriesData.metadata["x00200105"];
|
|
152
|
-
let acquisitionNumberAttribute = seriesData.metadata["x00200012"];
|
|
153
|
-
let is4D = seriesData.metadata.is4D;
|
|
154
|
-
let waveform = seriesData.metadata.waveform;
|
|
155
|
-
let SOPUID = seriesData.metadata["x00080016"];
|
|
156
|
-
let isPDF = SOPUID == "1.2.840.10008.5.1.4.1.1.104.1" ? true : false;
|
|
157
|
-
let anonymized = seriesData.metadata.anonymized;
|
|
158
|
-
|
|
159
|
-
let color = cornerstoneDICOMImageLoader.isColorImage(
|
|
160
|
-
seriesData.metadata["x00280004"]
|
|
161
|
-
) as boolean;
|
|
162
|
-
let id = customId || lid?.toString();
|
|
163
|
-
|
|
164
|
-
if (!id) {
|
|
165
|
-
throw new Error("Unique UID is not defined");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Staged Protocol
|
|
169
|
-
// https://dicom.nema.org/dicom/2013/output/chtml/part17/sect_K.5.html
|
|
170
|
-
const numberOfStages = seriesData.metadata["x00082124"]; // Number of stages
|
|
171
|
-
const numberOfViews = seriesData.metadata["x0008212a"]; // Number of views in stage
|
|
172
|
-
const isStagedProtocol = numberOfStages ? true : false;
|
|
173
|
-
|
|
174
|
-
// initialize series stack
|
|
175
|
-
if (!allSeriesStack[id]) {
|
|
176
|
-
let series: Partial<Series> = {
|
|
177
|
-
currentImageIdIndex: 0,
|
|
178
|
-
imageIds: [], // (ordered)
|
|
179
|
-
instanceUIDs: {}, // instanceUID: imageId (ordered)
|
|
180
|
-
instances: {},
|
|
181
|
-
seriesDescription: seriesDescription as string,
|
|
182
|
-
larvitarSeriesInstanceUID: lid as string,
|
|
183
|
-
seriesUID: sid as string,
|
|
184
|
-
studyUID: ssid as string,
|
|
185
|
-
numberOfImages: is4D ? (acquisitionNumberAttribute as number) : 0,
|
|
186
|
-
numberOfSlices: numberOfSlices as number,
|
|
187
|
-
numberOfFrames: numberOfFrames as number,
|
|
188
|
-
numberOfTemporalPositions: numberOfTemporalPositions as number,
|
|
189
|
-
isMultiframe: isMultiframe,
|
|
190
|
-
waveform: waveform as boolean,
|
|
191
|
-
is4D: is4D as boolean,
|
|
192
|
-
isPDF: isPDF as boolean,
|
|
193
|
-
anonymized: anonymized as boolean,
|
|
194
|
-
modality: modality as string,
|
|
195
|
-
color: color,
|
|
196
|
-
bytes: 0
|
|
197
|
-
};
|
|
198
|
-
if (isStagedProtocol) {
|
|
199
|
-
const stageName = seriesData.metadata["x00082120"];
|
|
200
|
-
const stageNumber = seriesData.metadata["x00082122"];
|
|
201
|
-
const viewName = seriesData.metadata["x00082127"];
|
|
202
|
-
const viewNumber = seriesData.metadata["x00082128"];
|
|
203
|
-
const stagedProtocol: StagedProtocol = {
|
|
204
|
-
numberOfStages: numberOfStages as number,
|
|
205
|
-
numberOfViews: numberOfViews as number,
|
|
206
|
-
stageName: stageName ? (stageName as string).trim() : undefined,
|
|
207
|
-
stageNumber: stageNumber as number,
|
|
208
|
-
viewName: viewName ? (viewName as string).trim() : undefined,
|
|
209
|
-
viewNumber: viewNumber as number
|
|
210
|
-
};
|
|
211
|
-
series.stagedProtocol = stagedProtocol;
|
|
212
|
-
}
|
|
213
|
-
allSeriesStack[id] = series as Series;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// get instance number from metadata
|
|
217
|
-
const instanceNumber = seriesData.metadata["x00200013"];
|
|
218
|
-
const defaultMethod = instanceNumber ? "instanceNumber" : "imagePosition";
|
|
219
|
-
const sortMethods: Array<"imagePosition" | "contentTime" | "instanceNumber"> =
|
|
220
|
-
is4D ? [defaultMethod, "contentTime"] : [defaultMethod];
|
|
221
|
-
|
|
222
|
-
// if the parsed file is a new series instance, keep it
|
|
223
|
-
|
|
224
|
-
if (isMultiframe) {
|
|
225
|
-
allSeriesStack[id].bytes += seriesData.file.size;
|
|
226
|
-
allSeriesStack[id].dataSet = seriesData.dataSet;
|
|
227
|
-
allSeriesStack[id].metadata = seriesData.metadata;
|
|
228
|
-
} else if (isNewInstance(allSeriesStack[id].instances, iid!)) {
|
|
229
|
-
// generate an imageId for the file and store it
|
|
230
|
-
// in allSeriesStack imageIds array, used by
|
|
231
|
-
// DICOMImageLoader to display the stack of images
|
|
232
|
-
let imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(
|
|
233
|
-
seriesData.file
|
|
234
|
-
) as string;
|
|
235
|
-
|
|
236
|
-
if (sliceIndex !== undefined) {
|
|
237
|
-
allSeriesStack[id].imageIds[sliceIndex] = imageId;
|
|
238
|
-
} else {
|
|
239
|
-
allSeriesStack[id].imageIds.push(imageId);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (is4D === false) {
|
|
243
|
-
allSeriesStack[id].numberOfImages =
|
|
244
|
-
(allSeriesStack[id].numberOfImages || 0) + 1;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
allSeriesStack[id].bytes += seriesData.file.size;
|
|
248
|
-
// store needed instance tags
|
|
249
|
-
allSeriesStack[id].instances[imageId] = {
|
|
250
|
-
metadata: seriesData.metadata,
|
|
251
|
-
file: seriesData.file,
|
|
252
|
-
dataSet: seriesData.dataSet
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
if (isPDF === false) {
|
|
256
|
-
if (sliceIndex === undefined) {
|
|
257
|
-
// order images in stack
|
|
258
|
-
allSeriesStack[id].imageIds = getSortedStack(
|
|
259
|
-
allSeriesStack[id] as Series,
|
|
260
|
-
sortMethods,
|
|
261
|
-
true
|
|
262
|
-
);
|
|
263
|
-
// populate the ordered dictionary of instanceUIDs
|
|
264
|
-
allSeriesStack[id].instanceUIDs = getSortedUIDs(
|
|
265
|
-
allSeriesStack[id] as Series
|
|
266
|
-
);
|
|
267
|
-
} else {
|
|
268
|
-
allSeriesStack[id].instanceUIDs[iid] = imageId;
|
|
269
|
-
}
|
|
270
|
-
store.addSeriesId(id, allSeriesStack[id].imageIds);
|
|
271
|
-
} else {
|
|
272
|
-
allSeriesStack[id].instanceUIDs[iid] = imageId;
|
|
273
|
-
store.addSeriesId(id, allSeriesStack[id].imageIds);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
/* Internal module functions */
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Check if the instance is new or not
|
|
282
|
-
* @inner
|
|
283
|
-
* @function isNewInstance
|
|
284
|
-
* @param {Object} instances - instances already loaded
|
|
285
|
-
* @param {String} iid - instance uid to check
|
|
286
|
-
* @return {Bool} True if is new instance, false if already present
|
|
287
|
-
*/
|
|
288
|
-
let isNewInstance = function (
|
|
289
|
-
instances: { [key: string]: Instance },
|
|
290
|
-
iid: string
|
|
291
|
-
) {
|
|
292
|
-
let isNewInstance = true;
|
|
293
|
-
forEach(instances, function (instance: Instance) {
|
|
294
|
-
if (instance.metadata.instanceUID === iid) {
|
|
295
|
-
isNewInstance = false;
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
return isNewInstance;
|
|
299
|
-
};
|