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