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
|
@@ -8,6 +8,7 @@ import cornerstone from "cornerstone-core";
|
|
|
8
8
|
import { default as cornerstoneDICOMImageLoader } from "cornerstone-wado-image-loader";
|
|
9
9
|
import { each, clone, range, findKey, filter, pickBy } from "lodash";
|
|
10
10
|
import { v4 as uuidv4 } from "uuid";
|
|
11
|
+
import { ImageLoader } from "cornerstone-core";
|
|
11
12
|
|
|
12
13
|
// internal libraries
|
|
13
14
|
import {
|
|
@@ -21,6 +22,16 @@ import {
|
|
|
21
22
|
getLarvitarImageTracker,
|
|
22
23
|
getLarvitarManager
|
|
23
24
|
} from "./commonLoader";
|
|
25
|
+
import type {
|
|
26
|
+
Image,
|
|
27
|
+
Instance,
|
|
28
|
+
Volume,
|
|
29
|
+
LarvitarManager,
|
|
30
|
+
ImageFrame,
|
|
31
|
+
ImageTracker,
|
|
32
|
+
MetaData
|
|
33
|
+
} from "../types";
|
|
34
|
+
import { DataSet } from "dicom-parser";
|
|
24
35
|
|
|
25
36
|
// global module variables
|
|
26
37
|
let customImageLoaderCounter = 0;
|
|
@@ -35,6 +46,80 @@ let customImageLoaderCounter = 0;
|
|
|
35
46
|
* getNrrdSerieDimensions()
|
|
36
47
|
*/
|
|
37
48
|
|
|
49
|
+
type NrrdInputVolume = {
|
|
50
|
+
header: {
|
|
51
|
+
sizes: number[];
|
|
52
|
+
"space directions": number[][]; // a property with a space in the name ?? Seriously ??
|
|
53
|
+
"space origin": [number, number];
|
|
54
|
+
kinds: string[];
|
|
55
|
+
type: string;
|
|
56
|
+
};
|
|
57
|
+
data: Uint16Array; // TODO-ts: other typed arrays ?
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type NrrdSeries = {
|
|
61
|
+
currentImageIdIndex: number;
|
|
62
|
+
imageIds: string[];
|
|
63
|
+
instances: { [key: string]: Instance };
|
|
64
|
+
instanceUIDs: { [key: string]: string };
|
|
65
|
+
numberOfImages: number;
|
|
66
|
+
seriesDescription: string;
|
|
67
|
+
seriesUID: string;
|
|
68
|
+
customLoader: string;
|
|
69
|
+
nrrdHeader: NrrdHeader;
|
|
70
|
+
bytes: number;
|
|
71
|
+
dataSet?: DataSet;
|
|
72
|
+
metadata?: MetaData;
|
|
73
|
+
ecgData?: number[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type NrrdHeader = {
|
|
77
|
+
volume: Volume;
|
|
78
|
+
intercept: number;
|
|
79
|
+
slope: number;
|
|
80
|
+
repr: string;
|
|
81
|
+
phase: string;
|
|
82
|
+
study_description: string;
|
|
83
|
+
series_description: string;
|
|
84
|
+
acquisition_date: string;
|
|
85
|
+
[imageId: string]: string | number | Volume | NrrdInstance; // TODO-ts: fix this: we need just NrrdInstance
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type NrrdInstance = {
|
|
89
|
+
instanceUID: string;
|
|
90
|
+
seriesDescription: string;
|
|
91
|
+
seriesModality: string;
|
|
92
|
+
patientName: string;
|
|
93
|
+
bitsAllocated: number;
|
|
94
|
+
pixelRepresentation: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// TODO-ts: why it's different from cornerstone type ?
|
|
98
|
+
// type Image = {
|
|
99
|
+
// imageId: string;
|
|
100
|
+
// rows: number;
|
|
101
|
+
// columns: number;
|
|
102
|
+
// minPixelValue: number;
|
|
103
|
+
// maxPixelValue: number;
|
|
104
|
+
// slope: number;
|
|
105
|
+
// intercept: number;
|
|
106
|
+
// windowCenter: number;
|
|
107
|
+
// windowWidth: number;
|
|
108
|
+
// render?: Function;
|
|
109
|
+
// getPixelData?: Function;
|
|
110
|
+
// getCanvas?: Function;
|
|
111
|
+
// color: boolean;
|
|
112
|
+
// columnPixelSpacing: number;
|
|
113
|
+
// rowPixelSpacing: number;
|
|
114
|
+
// invert: boolean;
|
|
115
|
+
// sizeInBytes: number;
|
|
116
|
+
// height: number;
|
|
117
|
+
// width: number;
|
|
118
|
+
// decodeTimeInMS?: number;
|
|
119
|
+
// webWorkerTimeInMS?: number;
|
|
120
|
+
// metadata: {[key: string]: MetadataValue};
|
|
121
|
+
// }
|
|
122
|
+
|
|
38
123
|
/**
|
|
39
124
|
* Build the data structure for the provided image orientation
|
|
40
125
|
* @instance
|
|
@@ -44,12 +129,17 @@ let customImageLoaderCounter = 0;
|
|
|
44
129
|
* @param {Object} custom_header A custom header object
|
|
45
130
|
* @return {Object} volume data
|
|
46
131
|
*/
|
|
47
|
-
export const buildNrrdImage = function (
|
|
132
|
+
export const buildNrrdImage = function (
|
|
133
|
+
volume: NrrdInputVolume,
|
|
134
|
+
seriesId: string,
|
|
135
|
+
custom_header: NrrdHeader
|
|
136
|
+
) {
|
|
137
|
+
//TODO-ts: better definition
|
|
48
138
|
let t0 = performance.now();
|
|
49
139
|
// standard image structure
|
|
50
|
-
let image = {};
|
|
51
|
-
let manager = getLarvitarManager();
|
|
52
|
-
let imageTracker = getLarvitarImageTracker();
|
|
140
|
+
let image: Partial<NrrdSeries> = {};
|
|
141
|
+
let manager = getLarvitarManager() as LarvitarManager;
|
|
142
|
+
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
53
143
|
image.currentImageIdIndex = 0;
|
|
54
144
|
image.imageIds = [];
|
|
55
145
|
image.instances = {};
|
|
@@ -57,8 +147,8 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
57
147
|
image.seriesDescription = "";
|
|
58
148
|
image.seriesUID = seriesId;
|
|
59
149
|
|
|
60
|
-
let header = {};
|
|
61
|
-
header
|
|
150
|
+
let header: Partial<NrrdHeader> = {};
|
|
151
|
+
header.volume = {} as Volume;
|
|
62
152
|
// need to extract header from nrrd file format
|
|
63
153
|
// sizes, spaceDirections and spaceOrigin
|
|
64
154
|
|
|
@@ -96,9 +186,9 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
96
186
|
header.volume.sliceThickness = spacing_z;
|
|
97
187
|
header.volume.repr =
|
|
98
188
|
volume.header.type[0].toUpperCase() + volume.header.type.slice(1);
|
|
99
|
-
header.volume.intercept = custom_header ? custom_header.intercept :
|
|
100
|
-
header.volume.slope = custom_header ? custom_header.slope :
|
|
101
|
-
header.volume.phase = custom_header ? custom_header.phase :
|
|
189
|
+
header.volume.intercept = custom_header ? custom_header.intercept : 0;
|
|
190
|
+
header.volume.slope = custom_header ? custom_header.slope : 1;
|
|
191
|
+
header.volume.phase = custom_header ? custom_header.phase : "";
|
|
102
192
|
header.volume.study_description = custom_header
|
|
103
193
|
? custom_header.study_description
|
|
104
194
|
: "";
|
|
@@ -112,9 +202,13 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
112
202
|
let rows = volume.header.sizes[index + 1];
|
|
113
203
|
let cols = volume.header.sizes[index + 0];
|
|
114
204
|
let frames = volume.header.sizes[index + 2];
|
|
115
|
-
let
|
|
205
|
+
let iopArr = volume.header["space directions"][index + 0].concat(
|
|
116
206
|
volume.header["space directions"][index + 1]
|
|
117
207
|
);
|
|
208
|
+
if (iopArr.length !== 6) {
|
|
209
|
+
throw new Error("Invalid Image Orientation");
|
|
210
|
+
}
|
|
211
|
+
let iop = iopArr as [number, number, number, number, number, number];
|
|
118
212
|
let firstIpp = header.volume.imagePosition;
|
|
119
213
|
let w = getNormalOrientation(iop);
|
|
120
214
|
let ps = header.volume.pixelSpacing;
|
|
@@ -122,7 +216,7 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
122
216
|
let intercept = header.volume.intercept;
|
|
123
217
|
let slope = header.volume.slope;
|
|
124
218
|
|
|
125
|
-
let metadata = {
|
|
219
|
+
let metadata: Partial<Instance["metadata"]> = {
|
|
126
220
|
x00280010: rows, // Rows
|
|
127
221
|
x00280011: cols, // Columns
|
|
128
222
|
x00200037: iop, // ImageOrientationPatient
|
|
@@ -131,30 +225,34 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
131
225
|
x00281052: intercept ? [intercept] : [0],
|
|
132
226
|
x00281053: slope ? [slope] : [1],
|
|
133
227
|
x00200052: header.volume.imageIds
|
|
134
|
-
? header[header.volume.imageIds[0]].instanceUID
|
|
228
|
+
? (header[header.volume.imageIds[0]] as NrrdInstance).instanceUID
|
|
135
229
|
: null,
|
|
136
230
|
x0008103e: header.volume.imageIds
|
|
137
|
-
? header[header.volume.imageIds[0]].seriesDescription
|
|
231
|
+
? (header[header.volume.imageIds[0]] as NrrdInstance).seriesDescription
|
|
138
232
|
: null,
|
|
139
233
|
x00080060: header.volume.imageIds
|
|
140
|
-
? header[header.volume.imageIds[0]].seriesModality
|
|
234
|
+
? (header[header.volume.imageIds[0]] as NrrdInstance).seriesModality
|
|
141
235
|
: null,
|
|
142
236
|
x00100010: header.volume.imageIds
|
|
143
|
-
? header[header.volume.imageIds[0]].patientName
|
|
237
|
+
? (header[header.volume.imageIds[0]] as NrrdInstance).patientName
|
|
144
238
|
: null,
|
|
145
239
|
x00280100: header.volume.imageIds
|
|
146
|
-
? header[header.volume.imageIds[0]].bitsAllocated
|
|
240
|
+
? (header[header.volume.imageIds[0]] as NrrdInstance).bitsAllocated
|
|
147
241
|
: null,
|
|
148
242
|
x00280103: header.volume.imageIds
|
|
149
|
-
? header[header.volume.imageIds[0]].pixelRepresentation
|
|
243
|
+
? (header[header.volume.imageIds[0]] as NrrdInstance).pixelRepresentation
|
|
150
244
|
: null,
|
|
151
245
|
repr: header.volume.repr || null
|
|
152
246
|
};
|
|
153
247
|
|
|
154
248
|
// compute default ww/wl values here to use them also for resliced images
|
|
155
249
|
let minMax = cornerstoneDICOMImageLoader.getMinMax(volume.data);
|
|
156
|
-
let maxVoi =
|
|
157
|
-
|
|
250
|
+
let maxVoi =
|
|
251
|
+
minMax.max * (metadata.x00281053 as number[])[0] +
|
|
252
|
+
(metadata.x00281052 as number[])[0];
|
|
253
|
+
let minVoi =
|
|
254
|
+
minMax.min * (metadata.x00281053 as number[])[0] +
|
|
255
|
+
(metadata.x00281052 as number[])[0];
|
|
158
256
|
let ww = maxVoi - minVoi;
|
|
159
257
|
let wl = (maxVoi + minVoi) / 2;
|
|
160
258
|
|
|
@@ -162,35 +260,49 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
162
260
|
metadata.x00280107 = minMax.max;
|
|
163
261
|
|
|
164
262
|
// extract the pixelData of each frame, store the data into the image object
|
|
165
|
-
each(range(frames), function (sliceIndex) {
|
|
263
|
+
each(range(frames), function (sliceIndex: number) {
|
|
166
264
|
let sliceSize = rows * cols;
|
|
167
265
|
let sliceBuffer = volume.data.subarray(
|
|
168
266
|
sliceSize * sliceIndex,
|
|
169
267
|
sliceSize * (sliceIndex + 1)
|
|
170
268
|
);
|
|
269
|
+
|
|
270
|
+
if (!metadata) {
|
|
271
|
+
throw new Error("Metadata not found");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// @ts-ignore: TODO this is concepptually wrong, we already know the Pixel Representation
|
|
275
|
+
// (see above, line 241), this function just returns the same value again
|
|
171
276
|
let r = getPixelRepresentation(metadata);
|
|
172
277
|
let typedArray = getTypedArrayFromDataType(r);
|
|
278
|
+
|
|
279
|
+
if (!typedArray) {
|
|
280
|
+
throw new Error("Typed array not found");
|
|
281
|
+
}
|
|
282
|
+
|
|
173
283
|
let pixelData = new typedArray(sliceBuffer);
|
|
174
284
|
// assign these values to the metadata of all images
|
|
175
285
|
metadata.x00281050 = wl;
|
|
176
286
|
metadata.x00281051 = ww;
|
|
177
287
|
|
|
178
288
|
let imageId = getNrrdImageId("nrrdLoader");
|
|
289
|
+
if (!imageTracker) {
|
|
290
|
+
throw new Error("Image tracker not initialized");
|
|
291
|
+
}
|
|
179
292
|
imageTracker[imageId] = seriesId;
|
|
180
293
|
|
|
181
294
|
// store file references
|
|
182
|
-
image.imageIds
|
|
183
|
-
|
|
184
|
-
instanceId: uuidv4(),
|
|
185
|
-
frame: sliceIndex
|
|
186
|
-
};
|
|
187
|
-
let frameMetadata = clone(metadata);
|
|
188
|
-
|
|
295
|
+
image.imageIds!.push(imageId);
|
|
296
|
+
let frameMetadata: MetaData = clone(metadata);
|
|
189
297
|
frameMetadata.x00200032 = firstIpp.map(function (val, i) {
|
|
190
298
|
return val + thickness * sliceIndex * w[i];
|
|
191
299
|
});
|
|
192
|
-
image.instances[imageId]
|
|
193
|
-
|
|
300
|
+
image.instances![imageId] = {
|
|
301
|
+
instanceId: uuidv4(),
|
|
302
|
+
frame: sliceIndex,
|
|
303
|
+
metadata: frameMetadata,
|
|
304
|
+
pixelData: pixelData
|
|
305
|
+
};
|
|
194
306
|
});
|
|
195
307
|
|
|
196
308
|
let middleSlice = Math.floor(image.imageIds.length / 2);
|
|
@@ -199,9 +311,13 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
199
311
|
// specify custom loader type and attach original header
|
|
200
312
|
image.customLoader = "nrrdLoader";
|
|
201
313
|
header.volume.imageIds = image.imageIds;
|
|
202
|
-
image.nrrdHeader = header;
|
|
314
|
+
image.nrrdHeader = header as NrrdHeader;
|
|
315
|
+
|
|
316
|
+
if (!manager) {
|
|
317
|
+
throw new Error("Larvitar manager not initialized");
|
|
318
|
+
}
|
|
203
319
|
|
|
204
|
-
manager[seriesId] = image;
|
|
320
|
+
manager[seriesId] = image as NrrdSeries;
|
|
205
321
|
|
|
206
322
|
let t1 = performance.now();
|
|
207
323
|
console.log(`Call to buildNrrdImage took ${t1 - t0} milliseconds.`);
|
|
@@ -215,7 +331,7 @@ export const buildNrrdImage = function (volume, seriesId, custom_header) {
|
|
|
215
331
|
* @param {String} customLoaderName The custom loader name
|
|
216
332
|
* @return {String} the custom image id
|
|
217
333
|
*/
|
|
218
|
-
export const getNrrdImageId = function (customLoaderName) {
|
|
334
|
+
export const getNrrdImageId = function (customLoaderName: string) {
|
|
219
335
|
let imageId = customLoaderName + "://" + customImageLoaderCounter;
|
|
220
336
|
customImageLoaderCounter++;
|
|
221
337
|
return imageId;
|
|
@@ -228,11 +344,15 @@ export const getNrrdImageId = function (customLoaderName) {
|
|
|
228
344
|
* @param {String} imageId The image id
|
|
229
345
|
* @return {Object} custom image object
|
|
230
346
|
*/
|
|
231
|
-
export const loadNrrdImage = function (imageId) {
|
|
232
|
-
let manager = getLarvitarManager();
|
|
233
|
-
let imageTracker = getLarvitarImageTracker();
|
|
347
|
+
export const loadNrrdImage: ImageLoader = function (imageId: string) {
|
|
348
|
+
let manager = getLarvitarManager() as LarvitarManager;
|
|
349
|
+
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
350
|
+
if (!manager || !imageTracker) {
|
|
351
|
+
throw new Error("Larvitar manager or image tracker not initialized");
|
|
352
|
+
}
|
|
234
353
|
let seriesId = imageTracker[imageId];
|
|
235
354
|
let instance = manager[seriesId].instances[imageId];
|
|
355
|
+
//@ts-ignore TODO-ts: fix this why is different typed array?
|
|
236
356
|
return createCustomImage(imageId, instance.metadata, instance.pixelData);
|
|
237
357
|
};
|
|
238
358
|
|
|
@@ -246,13 +366,13 @@ export const loadNrrdImage = function (imageId) {
|
|
|
246
366
|
* @return {String} image id
|
|
247
367
|
*/
|
|
248
368
|
export const getImageIdFromSlice = function (
|
|
249
|
-
sliceNumber,
|
|
250
|
-
orientation,
|
|
251
|
-
seriesId
|
|
369
|
+
sliceNumber: number,
|
|
370
|
+
orientation: string,
|
|
371
|
+
seriesId: string
|
|
252
372
|
) {
|
|
253
373
|
var prefix = "nrrdLoader://";
|
|
254
374
|
var serieImageTracker;
|
|
255
|
-
let imageTracker = getLarvitarImageTracker();
|
|
375
|
+
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
256
376
|
|
|
257
377
|
if (seriesId) {
|
|
258
378
|
serieImageTracker = pickBy(imageTracker, image => {
|
|
@@ -262,12 +382,18 @@ export const getImageIdFromSlice = function (
|
|
|
262
382
|
serieImageTracker = imageTracker;
|
|
263
383
|
}
|
|
264
384
|
|
|
265
|
-
var
|
|
385
|
+
var firstImageIdStr = findKey(serieImageTracker, entry => {
|
|
266
386
|
return entry[1] == orientation;
|
|
267
387
|
});
|
|
268
388
|
|
|
269
|
-
|
|
270
|
-
|
|
389
|
+
let firstImageId = firstImageIdStr?.split("//").pop();
|
|
390
|
+
|
|
391
|
+
if (firstImageId == undefined) {
|
|
392
|
+
console.error("cannot find imageId for orientation: " + orientation);
|
|
393
|
+
return "";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
var imageIndex = parseInt(firstImageId) + parseInt(sliceNumber.toString());
|
|
271
397
|
|
|
272
398
|
var imageId = prefix.concat(imageIndex.toString());
|
|
273
399
|
|
|
@@ -283,16 +409,29 @@ export const getImageIdFromSlice = function (
|
|
|
283
409
|
* @param {String} seriesId The series id
|
|
284
410
|
* @return {Integer} The image slice number
|
|
285
411
|
*/
|
|
286
|
-
export const getSliceNumberFromImageId = function (
|
|
287
|
-
|
|
288
|
-
|
|
412
|
+
export const getSliceNumberFromImageId = function (
|
|
413
|
+
imageId: string,
|
|
414
|
+
orientation: string
|
|
415
|
+
) {
|
|
416
|
+
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
417
|
+
var firstImageIdStr = findKey(imageTracker, entry => {
|
|
289
418
|
return entry[1] == orientation;
|
|
290
419
|
});
|
|
291
420
|
|
|
421
|
+
if (firstImageIdStr == undefined) {
|
|
422
|
+
console.error("cannot find imageId for orientation: " + orientation);
|
|
423
|
+
return 0;
|
|
424
|
+
}
|
|
425
|
+
|
|
292
426
|
var imageNumber = imageId.split("//").pop() || imageId;
|
|
427
|
+
let firstImageId = firstImageIdStr.split("//").pop();
|
|
293
428
|
|
|
294
|
-
|
|
295
|
-
|
|
429
|
+
if (firstImageId == undefined) {
|
|
430
|
+
console.error("cannot find imageId for orientation: " + orientation);
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
var imageIndex = parseInt(imageNumber) - parseInt(firstImageId);
|
|
296
435
|
|
|
297
436
|
return imageIndex;
|
|
298
437
|
};
|
|
@@ -304,7 +443,7 @@ export const getSliceNumberFromImageId = function (imageId, orientation) {
|
|
|
304
443
|
* @return {Object} Series dimension for each view
|
|
305
444
|
*/
|
|
306
445
|
export const getNrrdSerieDimensions = function () {
|
|
307
|
-
let imageTracker = getLarvitarImageTracker();
|
|
446
|
+
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
308
447
|
var dim_axial = filter(imageTracker, img => {
|
|
309
448
|
return img[1] == "axial";
|
|
310
449
|
});
|
|
@@ -334,11 +473,17 @@ export const getNrrdSerieDimensions = function () {
|
|
|
334
473
|
* @param {Object} dataSet The dataset
|
|
335
474
|
* @return {String} The image id
|
|
336
475
|
*/
|
|
337
|
-
let createCustomImage = function (
|
|
476
|
+
let createCustomImage = function (
|
|
477
|
+
imageId: string,
|
|
478
|
+
metadata: MetaData,
|
|
479
|
+
pixelData: Uint8ClampedArray,
|
|
480
|
+
dataSet?: any
|
|
481
|
+
) {
|
|
482
|
+
//TODO-ts check this
|
|
338
483
|
let canvas = window.document.createElement("canvas");
|
|
339
484
|
let lastImageIdDrawn = "";
|
|
340
485
|
|
|
341
|
-
let imageFrame = getImageFrame(metadata, dataSet);
|
|
486
|
+
let imageFrame = getImageFrame(metadata, dataSet) as ImageFrame;
|
|
342
487
|
|
|
343
488
|
// This function uses the pixelData received as argument without manipulating
|
|
344
489
|
// them: if the image is compressed, the decompress function should be called
|
|
@@ -361,7 +506,7 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
361
506
|
);
|
|
362
507
|
}
|
|
363
508
|
|
|
364
|
-
let image = {
|
|
509
|
+
let image: Partial<Image> = {
|
|
365
510
|
imageId: imageId,
|
|
366
511
|
color: cornerstoneDICOMImageLoader.isColorImage(
|
|
367
512
|
imageFrame.photometricInterpretation
|
|
@@ -369,7 +514,7 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
369
514
|
columnPixelSpacing: pixelSpacing ? pixelSpacing[1] : undefined,
|
|
370
515
|
columns: imageFrame.columns,
|
|
371
516
|
height: imageFrame.rows,
|
|
372
|
-
intercept: rescaleIntercept ? rescaleIntercept[0] : 0,
|
|
517
|
+
intercept: rescaleIntercept ? (rescaleIntercept as number[])[0] : 0,
|
|
373
518
|
invert: imageFrame.photometricInterpretation === "MONOCHROME1",
|
|
374
519
|
minPixelValue: imageFrame.smallestPixelValue,
|
|
375
520
|
maxPixelValue: imageFrame.largestPixelValue,
|
|
@@ -377,17 +522,21 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
377
522
|
rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : undefined,
|
|
378
523
|
rows: imageFrame.rows,
|
|
379
524
|
sizeInBytes: getSizeInBytes(),
|
|
380
|
-
slope: rescaleSlope ? rescaleSlope[0] : 1,
|
|
525
|
+
slope: rescaleSlope ? (rescaleSlope as number[])[0] : 1,
|
|
381
526
|
width: imageFrame.columns,
|
|
382
|
-
windowCenter: windowCenter ? windowCenter[0] : undefined,
|
|
383
|
-
windowWidth: windowWidth ? windowWidth[0] : undefined,
|
|
527
|
+
windowCenter: windowCenter ? (windowCenter as number[])[0] : undefined,
|
|
528
|
+
windowWidth: windowWidth ? (windowWidth as number[])[0] : undefined,
|
|
384
529
|
decodeTimeInMS: undefined,
|
|
385
530
|
webWorkerTimeInMS: undefined
|
|
386
531
|
};
|
|
387
532
|
|
|
388
533
|
// add function to return pixel data
|
|
389
534
|
image.getPixelData = function () {
|
|
390
|
-
|
|
535
|
+
if (!imageFrame.pixelData) {
|
|
536
|
+
console.warn('no pixel data for imageId "' + imageId);
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
return Array.from(imageFrame.pixelData);
|
|
391
540
|
};
|
|
392
541
|
|
|
393
542
|
// convert color space
|
|
@@ -397,6 +546,11 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
397
546
|
canvas.width = imageFrame.columns;
|
|
398
547
|
|
|
399
548
|
let context = canvas.getContext("2d");
|
|
549
|
+
|
|
550
|
+
if (!context) {
|
|
551
|
+
throw new Error("Unable to get canvas context");
|
|
552
|
+
}
|
|
553
|
+
|
|
400
554
|
let imageData = context.createImageData(
|
|
401
555
|
imageFrame.columns,
|
|
402
556
|
imageFrame.rows
|
|
@@ -415,10 +569,13 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
415
569
|
return canvas;
|
|
416
570
|
}
|
|
417
571
|
|
|
418
|
-
canvas.height = image.rows;
|
|
419
|
-
canvas.width = image.columns;
|
|
572
|
+
canvas.height = image.rows || 0;
|
|
573
|
+
canvas.width = image.columns || 0;
|
|
420
574
|
let context = canvas.getContext("2d");
|
|
421
|
-
context
|
|
575
|
+
if (!context) {
|
|
576
|
+
throw new Error("Unable to get canvas context");
|
|
577
|
+
}
|
|
578
|
+
context.putImageData(imageFrame.imageData!, 0, 0);
|
|
422
579
|
lastImageIdDrawn = imageId;
|
|
423
580
|
return canvas;
|
|
424
581
|
};
|
|
@@ -436,13 +593,23 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
436
593
|
// set the ww/wc to cover the dynamic range of the image if no values are supplied
|
|
437
594
|
if (image.windowCenter === undefined || image.windowWidth === undefined) {
|
|
438
595
|
if (image.color) {
|
|
439
|
-
image.windowWidth = 255;
|
|
440
|
-
image.windowCenter =
|
|
441
|
-
} else
|
|
596
|
+
image.windowWidth = 255.0;
|
|
597
|
+
image.windowCenter = 127.5;
|
|
598
|
+
} else if (
|
|
599
|
+
image.maxPixelValue != null &&
|
|
600
|
+
image.minPixelValue != null &&
|
|
601
|
+
image.slope != null &&
|
|
602
|
+
image.intercept != null
|
|
603
|
+
) {
|
|
442
604
|
let maxVoi = image.maxPixelValue * image.slope + image.intercept;
|
|
443
605
|
let minVoi = image.minPixelValue * image.slope + image.intercept;
|
|
444
606
|
image.windowWidth = maxVoi - minVoi;
|
|
445
607
|
image.windowCenter = (maxVoi + minVoi) / 2;
|
|
608
|
+
} else {
|
|
609
|
+
console.error(
|
|
610
|
+
"Unable to calculate default window width/center for imageId: " +
|
|
611
|
+
imageId
|
|
612
|
+
);
|
|
446
613
|
}
|
|
447
614
|
}
|
|
448
615
|
|
|
@@ -451,8 +618,8 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
451
618
|
// function to store custom image pixelData and metadata.
|
|
452
619
|
image.metadata = metadata;
|
|
453
620
|
|
|
454
|
-
let promise = new Promise(function (resolve) {
|
|
455
|
-
resolve(image);
|
|
621
|
+
let promise: Promise<Image> = new Promise(function (resolve) {
|
|
622
|
+
resolve(image as Image);
|
|
456
623
|
});
|
|
457
624
|
|
|
458
625
|
// Return an object containing the Promise to cornerstone so it can setup callbacks to be
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getLarvitarImageTracker,
|
|
14
14
|
getLarvitarManager
|
|
15
15
|
} from "./commonLoader";
|
|
16
|
+
import type { Image, ImageFrame, MetaData } from "../types";
|
|
16
17
|
|
|
17
18
|
/*
|
|
18
19
|
* This module provides the following functions to be exported:
|
|
@@ -20,18 +21,19 @@ import {
|
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
* Custom Loader for
|
|
24
|
+
* Custom Loader for DICOMImageLoader
|
|
24
25
|
* @instance
|
|
25
26
|
* @function loadReslicedImage
|
|
26
27
|
* @param {String} imageId The Id of the image
|
|
27
28
|
* @returns {Object} custom image object
|
|
28
29
|
*/
|
|
29
|
-
export const loadReslicedImage = function (imageId) {
|
|
30
|
+
export const loadReslicedImage = function (imageId: string) {
|
|
30
31
|
let manager = getLarvitarManager();
|
|
31
32
|
let imageTracker = getLarvitarImageTracker();
|
|
32
33
|
let seriesId = imageTracker[imageId];
|
|
33
34
|
let instance = manager[seriesId].instances[imageId];
|
|
34
35
|
var reslicedPixeldata = instance.pixelData;
|
|
36
|
+
//@ts-ignore deprecated
|
|
35
37
|
return createCustomImage(imageId, instance.metadata, reslicedPixeldata);
|
|
36
38
|
};
|
|
37
39
|
|
|
@@ -47,11 +49,16 @@ export const loadReslicedImage = function (imageId) {
|
|
|
47
49
|
* @param {Object} dataSet dataset object
|
|
48
50
|
* @returns {Object} custom image object
|
|
49
51
|
*/
|
|
50
|
-
let createCustomImage = function (
|
|
52
|
+
let createCustomImage = function (
|
|
53
|
+
imageId: string,
|
|
54
|
+
metadata: MetaData,
|
|
55
|
+
pixelData: Uint8ClampedArray,
|
|
56
|
+
dataSet?: any // deprecated
|
|
57
|
+
) {
|
|
51
58
|
let canvas = window.document.createElement("canvas");
|
|
52
59
|
let lastImageIdDrawn = "";
|
|
53
60
|
|
|
54
|
-
let imageFrame = getImageFrame(metadata, dataSet);
|
|
61
|
+
let imageFrame = getImageFrame(metadata, dataSet) as ImageFrame;
|
|
55
62
|
|
|
56
63
|
// This function uses the pixelData received as argument without manipulating
|
|
57
64
|
// them: if the image is compressed, the decompress function should be called
|
|
@@ -74,33 +81,39 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
74
81
|
);
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
let image = {
|
|
84
|
+
let image: Partial<Image> = {
|
|
78
85
|
imageId: imageId,
|
|
79
86
|
color: cornerstoneDICOMImageLoader.isColorImage(
|
|
80
87
|
imageFrame.photometricInterpretation
|
|
81
88
|
),
|
|
82
|
-
columnPixelSpacing: pixelSpacing
|
|
89
|
+
columnPixelSpacing: pixelSpacing
|
|
90
|
+
? (pixelSpacing as number[])[1]
|
|
91
|
+
: undefined,
|
|
83
92
|
columns: imageFrame.columns,
|
|
84
93
|
height: imageFrame.rows,
|
|
85
|
-
intercept: rescaleIntercept ? rescaleIntercept : 0,
|
|
94
|
+
intercept: rescaleIntercept ? (rescaleIntercept as number) : 0,
|
|
86
95
|
invert: imageFrame.photometricInterpretation === "MONOCHROME1",
|
|
87
96
|
minPixelValue: imageFrame.smallestPixelValue,
|
|
88
97
|
maxPixelValue: imageFrame.largestPixelValue,
|
|
89
98
|
render: undefined, // set below
|
|
90
|
-
rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : undefined,
|
|
99
|
+
rowPixelSpacing: pixelSpacing ? (pixelSpacing as number[])[0] : undefined,
|
|
91
100
|
rows: imageFrame.rows,
|
|
92
101
|
sizeInBytes: getSizeInBytes(),
|
|
93
|
-
slope: rescaleSlope ? rescaleSlope : 1,
|
|
102
|
+
slope: rescaleSlope ? (rescaleSlope as number) : 1,
|
|
94
103
|
width: imageFrame.columns,
|
|
95
|
-
windowCenter: windowCenter ? windowCenter : undefined,
|
|
96
|
-
windowWidth: windowWidth ? windowWidth : undefined,
|
|
104
|
+
windowCenter: windowCenter ? (windowCenter as number) : undefined,
|
|
105
|
+
windowWidth: windowWidth ? (windowWidth as number) : undefined,
|
|
97
106
|
decodeTimeInMS: undefined,
|
|
98
107
|
webWorkerTimeInMS: undefined
|
|
99
108
|
};
|
|
100
109
|
|
|
101
110
|
// add function to return pixel data
|
|
102
111
|
image.getPixelData = function () {
|
|
103
|
-
|
|
112
|
+
if (!imageFrame.pixelData) {
|
|
113
|
+
console.warn('no pixel data for imageId "' + imageId);
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
return Array.from(imageFrame.pixelData);
|
|
104
117
|
};
|
|
105
118
|
|
|
106
119
|
// convert color space
|
|
@@ -110,6 +123,11 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
110
123
|
canvas.width = imageFrame.columns;
|
|
111
124
|
|
|
112
125
|
let context = canvas.getContext("2d");
|
|
126
|
+
|
|
127
|
+
if (!context) {
|
|
128
|
+
throw new Error("Unable to get canvas context");
|
|
129
|
+
}
|
|
130
|
+
|
|
113
131
|
let imageData = context.createImageData(
|
|
114
132
|
imageFrame.columns,
|
|
115
133
|
imageFrame.rows
|
|
@@ -128,10 +146,13 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
128
146
|
return canvas;
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
canvas.height = image.rows;
|
|
132
|
-
canvas.width = image.columns;
|
|
149
|
+
canvas.height = image.rows || 0;
|
|
150
|
+
canvas.width = image.columns || 0;
|
|
133
151
|
let context = canvas.getContext("2d");
|
|
134
|
-
context
|
|
152
|
+
if (!context) {
|
|
153
|
+
throw new Error("Unable to get canvas context");
|
|
154
|
+
}
|
|
155
|
+
context.putImageData(imageFrame.imageData!, 0, 0);
|
|
135
156
|
lastImageIdDrawn = imageId;
|
|
136
157
|
return canvas;
|
|
137
158
|
};
|
|
@@ -147,13 +168,23 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
147
168
|
// set the ww/wc to cover the dynamic range of the image if no values are supplied
|
|
148
169
|
if (image.windowCenter === undefined || image.windowWidth === undefined) {
|
|
149
170
|
if (image.color) {
|
|
150
|
-
image.windowWidth = 255;
|
|
151
|
-
image.windowCenter =
|
|
152
|
-
} else
|
|
171
|
+
image.windowWidth = 255.0;
|
|
172
|
+
image.windowCenter = 127.5;
|
|
173
|
+
} else if (
|
|
174
|
+
image.maxPixelValue &&
|
|
175
|
+
image.minPixelValue &&
|
|
176
|
+
image.slope &&
|
|
177
|
+
image.intercept
|
|
178
|
+
) {
|
|
153
179
|
let maxVoi = image.maxPixelValue * image.slope + image.intercept;
|
|
154
180
|
let minVoi = image.minPixelValue * image.slope + image.intercept;
|
|
155
181
|
image.windowWidth = maxVoi - minVoi;
|
|
156
182
|
image.windowCenter = (maxVoi + minVoi) / 2;
|
|
183
|
+
} else {
|
|
184
|
+
console.error(
|
|
185
|
+
"Unable to calculate default window width/center for imageId: " +
|
|
186
|
+
imageId
|
|
187
|
+
);
|
|
157
188
|
}
|
|
158
189
|
}
|
|
159
190
|
|
|
@@ -162,8 +193,8 @@ let createCustomImage = function (imageId, metadata, pixelData, dataSet) {
|
|
|
162
193
|
// function to store custom image pixelData and metadata.
|
|
163
194
|
image.metadata = metadata;
|
|
164
195
|
|
|
165
|
-
let promise = new Promise(function (resolve) {
|
|
166
|
-
resolve(image);
|
|
196
|
+
let promise: Promise<Image> = new Promise(function (resolve) {
|
|
197
|
+
resolve(image as Image);
|
|
167
198
|
});
|
|
168
199
|
|
|
169
200
|
// Return an object containing the Promise to cornerstone so it can setup callbacks to be
|