larvitar 2.0.5 → 2.0.7
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/imaging/imageRendering.d.ts +1 -71
- package/dist/imaging/imageStore.d.ts +5 -0
- package/dist/imaging/loaders/commonLoader.d.ts +4 -4
- package/dist/imaging/loaders/nrrdLoader.d.ts +1 -51
- package/dist/larvitar.js +13 -1
- package/dist/larvitar.js.map +1 -1
- package/imaging/tools/types.d.ts +19 -19
- package/imaging/types.d.ts +110 -2
- package/package.json +7 -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/dist/imaging/MetaDataReadable.d.ts +0 -41
- package/dist/imaging/MetaDataTypes.d.ts +0 -3489
- 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/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 -427
- 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/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/{MetaDataReadable.ts → MetaDataReadable.d.ts} +0 -0
- /package/imaging/{MetaDataTypes.ts → MetaDataTypes.d.ts} +0 -0
|
@@ -1,630 +0,0 @@
|
|
|
1
|
-
/** @module loaders/nrrdLoader
|
|
2
|
-
* @desc This file provides functionalities for
|
|
3
|
-
* custom NRRD Loader
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// external libraries
|
|
7
|
-
import cornerstone from "cornerstone-core";
|
|
8
|
-
import { default as cornerstoneDICOMImageLoader } from "cornerstone-wado-image-loader";
|
|
9
|
-
import { each, clone, range, findKey, filter, pickBy } from "lodash";
|
|
10
|
-
import { v4 as uuidv4 } from "uuid";
|
|
11
|
-
import { ImageLoader } from "cornerstone-core";
|
|
12
|
-
|
|
13
|
-
// internal libraries
|
|
14
|
-
import {
|
|
15
|
-
getNormalOrientation,
|
|
16
|
-
getPixelRepresentation,
|
|
17
|
-
getTypedArrayFromDataType
|
|
18
|
-
} from "../imageUtils";
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
getImageFrame,
|
|
22
|
-
getLarvitarImageTracker,
|
|
23
|
-
getLarvitarManager
|
|
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";
|
|
35
|
-
|
|
36
|
-
// global module variables
|
|
37
|
-
let customImageLoaderCounter = 0;
|
|
38
|
-
|
|
39
|
-
/*
|
|
40
|
-
* This module provides the following functions to be exported:
|
|
41
|
-
* buildNrrdImage(volume, seriesId, custom_header)
|
|
42
|
-
* getNrrdImageId(customLoaderName)
|
|
43
|
-
* loadNrrdImage(imageId)
|
|
44
|
-
* getImageIdFromSlice(sliceNumber, orientation)
|
|
45
|
-
* getSliceNumberFromImageId(imageId, orientation)
|
|
46
|
-
* getNrrdSerieDimensions()
|
|
47
|
-
*/
|
|
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
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Build the data structure for the provided image orientation
|
|
125
|
-
* @instance
|
|
126
|
-
* @function buildNrrdImage
|
|
127
|
-
* @param {Object} volume The volume object
|
|
128
|
-
* @param {String} seriesId The Id of the series
|
|
129
|
-
* @param {Object} custom_header A custom header object
|
|
130
|
-
* @return {Object} volume data
|
|
131
|
-
*/
|
|
132
|
-
export const buildNrrdImage = function (
|
|
133
|
-
volume: NrrdInputVolume,
|
|
134
|
-
seriesId: string,
|
|
135
|
-
custom_header: NrrdHeader
|
|
136
|
-
) {
|
|
137
|
-
//TODO-ts: better definition
|
|
138
|
-
let t0 = performance.now();
|
|
139
|
-
// standard image structure
|
|
140
|
-
let image: Partial<NrrdSeries> = {};
|
|
141
|
-
let manager = getLarvitarManager() as LarvitarManager;
|
|
142
|
-
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
143
|
-
image.currentImageIdIndex = 0;
|
|
144
|
-
image.imageIds = [];
|
|
145
|
-
image.instances = {};
|
|
146
|
-
image.numberOfImages = 0;
|
|
147
|
-
image.seriesDescription = "";
|
|
148
|
-
image.seriesUID = seriesId;
|
|
149
|
-
|
|
150
|
-
let header: Partial<NrrdHeader> = {};
|
|
151
|
-
header.volume = {} as Volume;
|
|
152
|
-
// need to extract header from nrrd file format
|
|
153
|
-
// sizes, spaceDirections and spaceOrigin
|
|
154
|
-
|
|
155
|
-
const index = volume.header.kinds[0] == "domain" ? 0 : 1;
|
|
156
|
-
|
|
157
|
-
let spacing_x = Math.sqrt(
|
|
158
|
-
volume.header["space directions"][index + 0][0] *
|
|
159
|
-
volume.header["space directions"][index + 0][0] +
|
|
160
|
-
volume.header["space directions"][index + 0][1] *
|
|
161
|
-
volume.header["space directions"][index + 0][1] +
|
|
162
|
-
volume.header["space directions"][index + 0][2] *
|
|
163
|
-
volume.header["space directions"][index + 0][2]
|
|
164
|
-
);
|
|
165
|
-
let spacing_y = Math.sqrt(
|
|
166
|
-
volume.header["space directions"][index + 1][0] *
|
|
167
|
-
volume.header["space directions"][index + 1][0] +
|
|
168
|
-
volume.header["space directions"][index + 1][1] *
|
|
169
|
-
volume.header["space directions"][index + 1][1] +
|
|
170
|
-
volume.header["space directions"][index + 1][2] *
|
|
171
|
-
volume.header["space directions"][index + 1][2]
|
|
172
|
-
);
|
|
173
|
-
let spacing_z = Math.sqrt(
|
|
174
|
-
volume.header["space directions"][index + 2][0] *
|
|
175
|
-
volume.header["space directions"][index + 2][0] +
|
|
176
|
-
volume.header["space directions"][index + 2][1] *
|
|
177
|
-
volume.header["space directions"][index + 2][1] +
|
|
178
|
-
volume.header["space directions"][index + 2][2] *
|
|
179
|
-
volume.header["space directions"][index + 2][2]
|
|
180
|
-
);
|
|
181
|
-
header.volume.rows = volume.header.sizes[index + 1];
|
|
182
|
-
header.volume.cols = volume.header.sizes[index + 0];
|
|
183
|
-
header.volume.numberOfSlices = volume.header.sizes[index + 2];
|
|
184
|
-
header.volume.imagePosition = volume.header["space origin"];
|
|
185
|
-
header.volume.pixelSpacing = [spacing_x, spacing_y];
|
|
186
|
-
header.volume.sliceThickness = spacing_z;
|
|
187
|
-
header.volume.repr =
|
|
188
|
-
volume.header.type[0].toUpperCase() + volume.header.type.slice(1);
|
|
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 : "";
|
|
192
|
-
header.volume.study_description = custom_header
|
|
193
|
-
? custom_header.study_description
|
|
194
|
-
: "";
|
|
195
|
-
header.volume.series_description = custom_header
|
|
196
|
-
? custom_header.series_description
|
|
197
|
-
: "";
|
|
198
|
-
header.volume.acquisition_date = custom_header
|
|
199
|
-
? custom_header.acquisition_date
|
|
200
|
-
: "";
|
|
201
|
-
|
|
202
|
-
let rows = volume.header.sizes[index + 1];
|
|
203
|
-
let cols = volume.header.sizes[index + 0];
|
|
204
|
-
let frames = volume.header.sizes[index + 2];
|
|
205
|
-
let iopArr = volume.header["space directions"][index + 0].concat(
|
|
206
|
-
volume.header["space directions"][index + 1]
|
|
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];
|
|
212
|
-
let firstIpp = header.volume.imagePosition;
|
|
213
|
-
let w = getNormalOrientation(iop);
|
|
214
|
-
let ps = header.volume.pixelSpacing;
|
|
215
|
-
let thickness = header.volume.sliceThickness;
|
|
216
|
-
let intercept = header.volume.intercept;
|
|
217
|
-
let slope = header.volume.slope;
|
|
218
|
-
|
|
219
|
-
let metadata: Partial<Instance["metadata"]> = {
|
|
220
|
-
x00280010: rows, // Rows
|
|
221
|
-
x00280011: cols, // Columns
|
|
222
|
-
x00200037: iop, // ImageOrientationPatient
|
|
223
|
-
x00280030: ps, // PixelSpacing
|
|
224
|
-
x00180050: [thickness][0], // SliceThickness
|
|
225
|
-
x00281052: intercept ? [intercept] : [0],
|
|
226
|
-
x00281053: slope ? [slope] : [1],
|
|
227
|
-
x00200052: header.volume.imageIds
|
|
228
|
-
? (header[header.volume.imageIds[0]] as NrrdInstance).instanceUID
|
|
229
|
-
: null,
|
|
230
|
-
x0008103e: header.volume.imageIds
|
|
231
|
-
? (header[header.volume.imageIds[0]] as NrrdInstance).seriesDescription
|
|
232
|
-
: null,
|
|
233
|
-
x00080060: header.volume.imageIds
|
|
234
|
-
? (header[header.volume.imageIds[0]] as NrrdInstance).seriesModality
|
|
235
|
-
: null,
|
|
236
|
-
x00100010: header.volume.imageIds
|
|
237
|
-
? (header[header.volume.imageIds[0]] as NrrdInstance).patientName
|
|
238
|
-
: null,
|
|
239
|
-
x00280100: header.volume.imageIds
|
|
240
|
-
? (header[header.volume.imageIds[0]] as NrrdInstance).bitsAllocated
|
|
241
|
-
: null,
|
|
242
|
-
x00280103: header.volume.imageIds
|
|
243
|
-
? (header[header.volume.imageIds[0]] as NrrdInstance).pixelRepresentation
|
|
244
|
-
: null,
|
|
245
|
-
repr: header.volume.repr || null
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
// compute default ww/wl values here to use them also for resliced images
|
|
249
|
-
let minMax = cornerstoneDICOMImageLoader.getMinMax(volume.data);
|
|
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];
|
|
256
|
-
let ww = maxVoi - minVoi;
|
|
257
|
-
let wl = (maxVoi + minVoi) / 2;
|
|
258
|
-
|
|
259
|
-
metadata.x00280106 = minMax.min;
|
|
260
|
-
metadata.x00280107 = minMax.max;
|
|
261
|
-
|
|
262
|
-
// extract the pixelData of each frame, store the data into the image object
|
|
263
|
-
each(range(frames), function (sliceIndex: number) {
|
|
264
|
-
let sliceSize = rows * cols;
|
|
265
|
-
let sliceBuffer = volume.data.subarray(
|
|
266
|
-
sliceSize * sliceIndex,
|
|
267
|
-
sliceSize * (sliceIndex + 1)
|
|
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
|
|
276
|
-
let r = getPixelRepresentation(metadata);
|
|
277
|
-
let typedArray = getTypedArrayFromDataType(r);
|
|
278
|
-
|
|
279
|
-
if (!typedArray) {
|
|
280
|
-
throw new Error("Typed array not found");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
let pixelData = new typedArray(sliceBuffer);
|
|
284
|
-
// assign these values to the metadata of all images
|
|
285
|
-
metadata.x00281050 = wl;
|
|
286
|
-
metadata.x00281051 = ww;
|
|
287
|
-
|
|
288
|
-
let imageId = getNrrdImageId("nrrdLoader");
|
|
289
|
-
if (!imageTracker) {
|
|
290
|
-
throw new Error("Image tracker not initialized");
|
|
291
|
-
}
|
|
292
|
-
imageTracker[imageId] = seriesId;
|
|
293
|
-
|
|
294
|
-
// store file references
|
|
295
|
-
image.imageIds!.push(imageId);
|
|
296
|
-
let frameMetadata: MetaData = clone(metadata);
|
|
297
|
-
frameMetadata.x00200032 = firstIpp.map(function (val, i) {
|
|
298
|
-
return val + thickness * sliceIndex * w[i];
|
|
299
|
-
});
|
|
300
|
-
image.instances![imageId] = {
|
|
301
|
-
instanceId: uuidv4(),
|
|
302
|
-
frame: sliceIndex,
|
|
303
|
-
metadata: frameMetadata,
|
|
304
|
-
pixelData: pixelData
|
|
305
|
-
};
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
let middleSlice = Math.floor(image.imageIds.length / 2);
|
|
309
|
-
image.currentImageIdIndex = middleSlice;
|
|
310
|
-
image.numberOfImages = image.imageIds.length;
|
|
311
|
-
// specify custom loader type and attach original header
|
|
312
|
-
image.customLoader = "nrrdLoader";
|
|
313
|
-
header.volume.imageIds = image.imageIds;
|
|
314
|
-
image.nrrdHeader = header as NrrdHeader;
|
|
315
|
-
|
|
316
|
-
if (!manager) {
|
|
317
|
-
throw new Error("Larvitar manager not initialized");
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
manager[seriesId] = image as NrrdSeries;
|
|
321
|
-
|
|
322
|
-
let t1 = performance.now();
|
|
323
|
-
console.log(`Call to buildNrrdImage took ${t1 - t0} milliseconds.`);
|
|
324
|
-
return image;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Get the custom imageId from custom loader
|
|
329
|
-
* @instance
|
|
330
|
-
* @function getNrrdImageId
|
|
331
|
-
* @param {String} customLoaderName The custom loader name
|
|
332
|
-
* @return {String} the custom image id
|
|
333
|
-
*/
|
|
334
|
-
export const getNrrdImageId = function (customLoaderName: string) {
|
|
335
|
-
let imageId = customLoaderName + "://" + customImageLoaderCounter;
|
|
336
|
-
customImageLoaderCounter++;
|
|
337
|
-
return imageId;
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Custom cornerstone image loader for nrrd files
|
|
342
|
-
* @instance
|
|
343
|
-
* @function loadNrrdImage
|
|
344
|
-
* @param {String} imageId The image id
|
|
345
|
-
* @return {Object} custom image object
|
|
346
|
-
*/
|
|
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
|
-
}
|
|
353
|
-
let seriesId = imageTracker[imageId];
|
|
354
|
-
let instance = manager[seriesId].instances[imageId];
|
|
355
|
-
//@ts-ignore TODO-ts: fix this why is different typed array?
|
|
356
|
-
return createCustomImage(imageId, instance.metadata, instance.pixelData);
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Retrieve imageId for a slice in the given orientation
|
|
361
|
-
* @instance
|
|
362
|
-
* @function getImageIdFromSlice
|
|
363
|
-
* @param {Integer} sliceNumber The image slice number
|
|
364
|
-
* @param {String} orientation The orientation tag
|
|
365
|
-
* @param {String} seriesId The series id
|
|
366
|
-
* @return {String} image id
|
|
367
|
-
*/
|
|
368
|
-
export const getImageIdFromSlice = function (
|
|
369
|
-
sliceNumber: number,
|
|
370
|
-
orientation: string,
|
|
371
|
-
seriesId: string
|
|
372
|
-
) {
|
|
373
|
-
var prefix = "nrrdLoader://";
|
|
374
|
-
var serieImageTracker;
|
|
375
|
-
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
376
|
-
|
|
377
|
-
if (seriesId) {
|
|
378
|
-
serieImageTracker = pickBy(imageTracker, image => {
|
|
379
|
-
return image[0] == seriesId;
|
|
380
|
-
});
|
|
381
|
-
} else {
|
|
382
|
-
serieImageTracker = imageTracker;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
var firstImageIdStr = findKey(serieImageTracker, entry => {
|
|
386
|
-
return entry[1] == orientation;
|
|
387
|
-
});
|
|
388
|
-
|
|
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());
|
|
397
|
-
|
|
398
|
-
var imageId = prefix.concat(imageIndex.toString());
|
|
399
|
-
|
|
400
|
-
return imageId;
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Retrieve slice number for a the given orientation
|
|
405
|
-
* @instance
|
|
406
|
-
* @function getSliceNumberFromImageId
|
|
407
|
-
* @param {String} imageId The image slice id
|
|
408
|
-
* @param {String} orientation The orientation tag
|
|
409
|
-
* @param {String} seriesId The series id
|
|
410
|
-
* @return {Integer} The image slice number
|
|
411
|
-
*/
|
|
412
|
-
export const getSliceNumberFromImageId = function (
|
|
413
|
-
imageId: string,
|
|
414
|
-
orientation: string
|
|
415
|
-
) {
|
|
416
|
-
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
417
|
-
var firstImageIdStr = findKey(imageTracker, entry => {
|
|
418
|
-
return entry[1] == orientation;
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
if (firstImageIdStr == undefined) {
|
|
422
|
-
console.error("cannot find imageId for orientation: " + orientation);
|
|
423
|
-
return 0;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
var imageNumber = imageId.split("//").pop() || imageId;
|
|
427
|
-
let firstImageId = firstImageIdStr.split("//").pop();
|
|
428
|
-
|
|
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);
|
|
435
|
-
|
|
436
|
-
return imageIndex;
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Get series dimension for each view
|
|
441
|
-
* @instance
|
|
442
|
-
* @function getNrrdSerieDimensions
|
|
443
|
-
* @return {Object} Series dimension for each view
|
|
444
|
-
*/
|
|
445
|
-
export const getNrrdSerieDimensions = function () {
|
|
446
|
-
let imageTracker = getLarvitarImageTracker() as ImageTracker;
|
|
447
|
-
var dim_axial = filter(imageTracker, img => {
|
|
448
|
-
return img[1] == "axial";
|
|
449
|
-
});
|
|
450
|
-
var dim_coronal = filter(imageTracker, img => {
|
|
451
|
-
return img[1] == "coronal";
|
|
452
|
-
});
|
|
453
|
-
var dim_sagittal = filter(imageTracker, img => {
|
|
454
|
-
return img[1] == "sagittal";
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
return {
|
|
458
|
-
axial: [dim_coronal.length, dim_sagittal.length, dim_axial.length],
|
|
459
|
-
coronal: [dim_sagittal.length, dim_axial.length, dim_coronal.length],
|
|
460
|
-
sagittal: [dim_coronal.length, dim_axial.length, dim_sagittal.length]
|
|
461
|
-
};
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
/* Internal functions */
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Create the custom image object for conrnestone from nrrd file
|
|
468
|
-
* @instance
|
|
469
|
-
* @function createCustomImage
|
|
470
|
-
* @param {String} imageId The series id
|
|
471
|
-
* @param {Object} metadata The metadata object
|
|
472
|
-
* @param {Object} pixelData The pixelData object
|
|
473
|
-
* @param {Object} dataSet The dataset
|
|
474
|
-
* @return {String} The image id
|
|
475
|
-
*/
|
|
476
|
-
let createCustomImage = function (
|
|
477
|
-
imageId: string,
|
|
478
|
-
metadata: MetaData,
|
|
479
|
-
pixelData: Uint8ClampedArray,
|
|
480
|
-
dataSet?: any
|
|
481
|
-
) {
|
|
482
|
-
//TODO-ts check this
|
|
483
|
-
let canvas = window.document.createElement("canvas");
|
|
484
|
-
let lastImageIdDrawn = "";
|
|
485
|
-
|
|
486
|
-
let imageFrame = getImageFrame(metadata, dataSet) as ImageFrame;
|
|
487
|
-
|
|
488
|
-
// This function uses the pixelData received as argument without manipulating
|
|
489
|
-
// them: if the image is compressed, the decompress function should be called
|
|
490
|
-
// before creating the custom image object (like the multiframe case).
|
|
491
|
-
imageFrame.pixelData = pixelData;
|
|
492
|
-
|
|
493
|
-
let pixelSpacing = metadata.x00280030;
|
|
494
|
-
let rescaleIntercept = metadata.x00281052;
|
|
495
|
-
let rescaleSlope = metadata.x00281053;
|
|
496
|
-
let windowCenter = metadata.x00281050;
|
|
497
|
-
let windowWidth = metadata.x00281051;
|
|
498
|
-
|
|
499
|
-
function getSizeInBytes() {
|
|
500
|
-
let bytesPerPixel = Math.round(imageFrame.bitsAllocated / 8);
|
|
501
|
-
return (
|
|
502
|
-
imageFrame.rows *
|
|
503
|
-
imageFrame.columns *
|
|
504
|
-
bytesPerPixel *
|
|
505
|
-
imageFrame.samplesPerPixel
|
|
506
|
-
);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
let image: Partial<Image> = {
|
|
510
|
-
imageId: imageId,
|
|
511
|
-
color: cornerstoneDICOMImageLoader.isColorImage(
|
|
512
|
-
imageFrame.photometricInterpretation
|
|
513
|
-
),
|
|
514
|
-
columnPixelSpacing: pixelSpacing ? pixelSpacing[1] : undefined,
|
|
515
|
-
columns: imageFrame.columns,
|
|
516
|
-
height: imageFrame.rows,
|
|
517
|
-
intercept: rescaleIntercept ? (rescaleIntercept as number[])[0] : 0,
|
|
518
|
-
invert: imageFrame.photometricInterpretation === "MONOCHROME1",
|
|
519
|
-
minPixelValue: imageFrame.smallestPixelValue,
|
|
520
|
-
maxPixelValue: imageFrame.largestPixelValue,
|
|
521
|
-
render: undefined, // set below
|
|
522
|
-
rowPixelSpacing: pixelSpacing ? pixelSpacing[0] : undefined,
|
|
523
|
-
rows: imageFrame.rows,
|
|
524
|
-
sizeInBytes: getSizeInBytes(),
|
|
525
|
-
slope: rescaleSlope ? (rescaleSlope as number[])[0] : 1,
|
|
526
|
-
width: imageFrame.columns,
|
|
527
|
-
windowCenter: windowCenter ? (windowCenter as number[])[0] : undefined,
|
|
528
|
-
windowWidth: windowWidth ? (windowWidth as number[])[0] : undefined,
|
|
529
|
-
decodeTimeInMS: undefined,
|
|
530
|
-
webWorkerTimeInMS: undefined
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
// add function to return pixel data
|
|
534
|
-
image.getPixelData = function () {
|
|
535
|
-
if (!imageFrame.pixelData) {
|
|
536
|
-
console.warn('no pixel data for imageId "' + imageId);
|
|
537
|
-
return [];
|
|
538
|
-
}
|
|
539
|
-
return Array.from(imageFrame.pixelData);
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
// convert color space
|
|
543
|
-
if (image.color) {
|
|
544
|
-
// setup the canvas context
|
|
545
|
-
canvas.height = imageFrame.rows;
|
|
546
|
-
canvas.width = imageFrame.columns;
|
|
547
|
-
|
|
548
|
-
let context = canvas.getContext("2d");
|
|
549
|
-
|
|
550
|
-
if (!context) {
|
|
551
|
-
throw new Error("Unable to get canvas context");
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
let imageData = context.createImageData(
|
|
555
|
-
imageFrame.columns,
|
|
556
|
-
imageFrame.rows
|
|
557
|
-
);
|
|
558
|
-
cornerstoneDICOMImageLoader.convertColorSpace(imageFrame, imageData);
|
|
559
|
-
|
|
560
|
-
imageFrame.imageData = imageData;
|
|
561
|
-
imageFrame.pixelData = imageData.data;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Setup the renderer
|
|
565
|
-
if (image.color) {
|
|
566
|
-
image.render = cornerstone.renderColorImage;
|
|
567
|
-
image.getCanvas = function () {
|
|
568
|
-
if (lastImageIdDrawn === imageId) {
|
|
569
|
-
return canvas;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
canvas.height = image.rows || 0;
|
|
573
|
-
canvas.width = image.columns || 0;
|
|
574
|
-
let context = canvas.getContext("2d");
|
|
575
|
-
if (!context) {
|
|
576
|
-
throw new Error("Unable to get canvas context");
|
|
577
|
-
}
|
|
578
|
-
context.putImageData(imageFrame.imageData!, 0, 0);
|
|
579
|
-
lastImageIdDrawn = imageId;
|
|
580
|
-
return canvas;
|
|
581
|
-
};
|
|
582
|
-
} else {
|
|
583
|
-
image.render = undefined; // will be set at need in cornerstone render pipeline, see drawImageSync.js (line 44)
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// calculate min/max if not supplied
|
|
587
|
-
if (image.minPixelValue === undefined || image.maxPixelValue === undefined) {
|
|
588
|
-
let minMax = cornerstoneDICOMImageLoader.getMinMax(pixelData);
|
|
589
|
-
image.minPixelValue = minMax.min;
|
|
590
|
-
image.maxPixelValue = minMax.max;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// set the ww/wc to cover the dynamic range of the image if no values are supplied
|
|
594
|
-
if (image.windowCenter === undefined || image.windowWidth === undefined) {
|
|
595
|
-
if (image.color) {
|
|
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
|
-
) {
|
|
604
|
-
let maxVoi = image.maxPixelValue * image.slope + image.intercept;
|
|
605
|
-
let minVoi = image.minPixelValue * image.slope + image.intercept;
|
|
606
|
-
image.windowWidth = maxVoi - minVoi;
|
|
607
|
-
image.windowCenter = (maxVoi + minVoi) / 2;
|
|
608
|
-
} else {
|
|
609
|
-
console.error(
|
|
610
|
-
"Unable to calculate default window width/center for imageId: " +
|
|
611
|
-
imageId
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Custom images does not have the "data" attribute becaouse their dataset is
|
|
617
|
-
// not available. The "metadata" attribute is used by the storeImageData
|
|
618
|
-
// function to store custom image pixelData and metadata.
|
|
619
|
-
image.metadata = metadata;
|
|
620
|
-
|
|
621
|
-
let promise: Promise<Image> = new Promise(function (resolve) {
|
|
622
|
-
resolve(image as Image);
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
// Return an object containing the Promise to cornerstone so it can setup callbacks to be
|
|
626
|
-
// invoked asynchronously for the success/resolve and failure/reject scenarios.
|
|
627
|
-
return {
|
|
628
|
-
promise
|
|
629
|
-
};
|
|
630
|
-
};
|