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,1091 +0,0 @@
|
|
|
1
|
-
/** @module imaging/imageRendering
|
|
2
|
-
* @desc This file provides functionalities for
|
|
3
|
-
* rendering images in html canvas using cornerstone
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// external libraries
|
|
7
|
-
import cornerstone from "cornerstone-core";
|
|
8
|
-
import { default as cornerstoneDICOMImageLoader } from "cornerstone-wado-image-loader";
|
|
9
|
-
import { each, has } from "lodash";
|
|
10
|
-
|
|
11
|
-
// internal libraries
|
|
12
|
-
import { getPerformanceMonitor } from "./monitors/performance";
|
|
13
|
-
import { getFileImageId } from "./loaders/fileLoader";
|
|
14
|
-
import { csToolsCreateStack } from "./tools/main";
|
|
15
|
-
import { toggleMouseToolsListeners } from "./tools/interaction";
|
|
16
|
-
import store, { set as setStore } from "./imageStore";
|
|
17
|
-
import { applyColorMap } from "./imageColormaps";
|
|
18
|
-
import { isElement } from "./imageUtils";
|
|
19
|
-
import {
|
|
20
|
-
Image,
|
|
21
|
-
Instance,
|
|
22
|
-
Series,
|
|
23
|
-
StoreViewport,
|
|
24
|
-
StoreViewportOptions,
|
|
25
|
-
Viewport
|
|
26
|
-
} from "./types";
|
|
27
|
-
|
|
28
|
-
/*
|
|
29
|
-
* This module provides the following functions to be exported:
|
|
30
|
-
* clearImageCache(seriesId)
|
|
31
|
-
* loadAndCacheImage(imageIndex)
|
|
32
|
-
* loadAndCacheImages(seriesData)
|
|
33
|
-
* renderFileImage(file, elementId)
|
|
34
|
-
* renderWebImage(url, elementId)
|
|
35
|
-
* disableViewport(elementId)
|
|
36
|
-
* unloadViewport(elementId, seriesId)
|
|
37
|
-
* resizeViewport(elementId)
|
|
38
|
-
* renderImage(series, elementId, defaultProps)
|
|
39
|
-
* updateImage(series, elementId, imageIndex)
|
|
40
|
-
* resetViewports([elementIds])
|
|
41
|
-
* updateViewportData(elementId)
|
|
42
|
-
* toggleMouseHandlers(elementId, disableFlag)
|
|
43
|
-
* storeViewportData(params...)
|
|
44
|
-
* invertImage(elementId)
|
|
45
|
-
* flipImageHorizontal(elementId)
|
|
46
|
-
* flipImageVertical(elementId)
|
|
47
|
-
* rotateImageLeft(elementId)
|
|
48
|
-
* rotateImageRight(elementId)
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Purge the cornestone internal cache
|
|
53
|
-
* If seriesId is passed as argument only imageIds of the series are purged from internal cache
|
|
54
|
-
* @instance
|
|
55
|
-
* @function clearImageCache
|
|
56
|
-
* @param {String} seriesId - The id of the serie
|
|
57
|
-
*/
|
|
58
|
-
export const clearImageCache = function (seriesId?: string) {
|
|
59
|
-
if (seriesId) {
|
|
60
|
-
let series = store.get("series");
|
|
61
|
-
if (has(series, seriesId)) {
|
|
62
|
-
each(series[seriesId].imageIds, function (imageId: string) {
|
|
63
|
-
if (cornerstone.imageCache.cachedImages.length > 0) {
|
|
64
|
-
try {
|
|
65
|
-
cornerstone.imageCache.removeImageLoadObject(imageId);
|
|
66
|
-
} catch (e) {
|
|
67
|
-
console.warn("no cached image");
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
let uri =
|
|
71
|
-
cornerstoneDICOMImageLoader.wadouri.parseImageId(imageId).url;
|
|
72
|
-
cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.unload(uri);
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
store.removeSeriesId(seriesId);
|
|
77
|
-
console.log("Uncached images for ", seriesId);
|
|
78
|
-
}
|
|
79
|
-
} else {
|
|
80
|
-
cornerstone.imageCache.purgeCache();
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Load and cache a single image
|
|
86
|
-
* Add series's imageIds into store
|
|
87
|
-
* @instance
|
|
88
|
-
* @function loadAndCacheImage
|
|
89
|
-
* @param {Object} series the parsed series data
|
|
90
|
-
* @param {number} imageIndex the image index in the imageIds array
|
|
91
|
-
*/
|
|
92
|
-
export function loadAndCacheImage(
|
|
93
|
-
series: Series,
|
|
94
|
-
imageIndex: number
|
|
95
|
-
): Promise<true> {
|
|
96
|
-
const t0 = performance.now();
|
|
97
|
-
// add serie's imageIds into store
|
|
98
|
-
store.addSeriesId(series.seriesUID, series.imageIds);
|
|
99
|
-
const imageId: string | undefined = series.imageIds[imageIndex];
|
|
100
|
-
|
|
101
|
-
const cachePromise = new Promise<true>((resolve, reject) => {
|
|
102
|
-
if (imageId) {
|
|
103
|
-
cornerstone.loadAndCacheImage(imageId).then(function () {
|
|
104
|
-
const t1 = performance.now();
|
|
105
|
-
console.log(`Call to cacheImages took ${t1 - t0} milliseconds.`);
|
|
106
|
-
console.log(
|
|
107
|
-
`Cached image with index ${imageIndex} for ${series.seriesUID}`
|
|
108
|
-
);
|
|
109
|
-
resolve(true);
|
|
110
|
-
});
|
|
111
|
-
} else {
|
|
112
|
-
reject(`Error: wrong image index ${imageIndex}, no imageId available`);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
return cachePromise;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Load and cache all serie's images
|
|
120
|
-
* Add series's imageIds into store
|
|
121
|
-
* @instance
|
|
122
|
-
* @function loadAndCacheImages
|
|
123
|
-
* @param {Object} series the parsed series data
|
|
124
|
-
* @param {Function} callback a callback function
|
|
125
|
-
*/
|
|
126
|
-
export function loadAndCacheImages(
|
|
127
|
-
series: Series,
|
|
128
|
-
callback: (payload: {
|
|
129
|
-
seriesId: string;
|
|
130
|
-
loading: number;
|
|
131
|
-
series: Series;
|
|
132
|
-
}) => any
|
|
133
|
-
) {
|
|
134
|
-
const t0 = performance.now();
|
|
135
|
-
let cachingCounter = 0;
|
|
136
|
-
const response = {
|
|
137
|
-
seriesId: series.seriesUID,
|
|
138
|
-
loading: 0,
|
|
139
|
-
series: {} as Series
|
|
140
|
-
};
|
|
141
|
-
callback(response);
|
|
142
|
-
// add serie's imageIds into store
|
|
143
|
-
store.addSeriesId(series.seriesUID, series.imageIds);
|
|
144
|
-
// add serie's caching progress into store
|
|
145
|
-
setStore(["progress", series.seriesUID, 0]);
|
|
146
|
-
|
|
147
|
-
function updateProgress() {
|
|
148
|
-
cachingCounter += 1;
|
|
149
|
-
const cachingPercentage = Math.floor(
|
|
150
|
-
(cachingCounter / series.imageIds.length) * 100
|
|
151
|
-
);
|
|
152
|
-
response.loading = cachingPercentage;
|
|
153
|
-
setStore(["progress", series.seriesUID, cachingPercentage]);
|
|
154
|
-
if (cachingCounter == series.imageIds.length) {
|
|
155
|
-
const t1 = performance.now();
|
|
156
|
-
console.log(`Call to cacheImages took ${t1 - t0} milliseconds.`);
|
|
157
|
-
console.log(`Cached images for ${series.seriesUID}`);
|
|
158
|
-
response.series = series;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
each(series.imageIds, function (imageId: string | undefined, index: number) {
|
|
163
|
-
if (imageId) {
|
|
164
|
-
cornerstone.loadAndCacheImage(imageId).then(function () {
|
|
165
|
-
updateProgress();
|
|
166
|
-
callback(response);
|
|
167
|
-
});
|
|
168
|
-
} else {
|
|
169
|
-
updateProgress();
|
|
170
|
-
console.warn(
|
|
171
|
-
`Stack is not fully loaded, skipping cache for index ${index}`
|
|
172
|
-
);
|
|
173
|
-
callback(response);
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Render a PDF from a DICOM Encapsulated PDF
|
|
180
|
-
* @instance
|
|
181
|
-
* @function renderDICOMPDF
|
|
182
|
-
* @param {Object} seriesStack - The original series data object
|
|
183
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
184
|
-
* @returns {Promise} - Return a promise which will resolve when pdf is displayed
|
|
185
|
-
*/
|
|
186
|
-
export const renderDICOMPDF = function (
|
|
187
|
-
seriesStack: Series,
|
|
188
|
-
elementId: string | HTMLElement
|
|
189
|
-
) {
|
|
190
|
-
let t0 = performance.now();
|
|
191
|
-
let element: HTMLElement | null = isElement(elementId)
|
|
192
|
-
? (elementId as HTMLElement)
|
|
193
|
-
: document.getElementById(elementId as string);
|
|
194
|
-
|
|
195
|
-
let renderPromise = new Promise<true>((resolve, reject) => {
|
|
196
|
-
let image: Instance | null = seriesStack.instances[seriesStack.imageIds[0]];
|
|
197
|
-
const SOPUID = image.dataSet?.string("x00080016");
|
|
198
|
-
|
|
199
|
-
if (SOPUID === "1.2.840.10008.5.1.4.1.1.104.1") {
|
|
200
|
-
let fileTag = image.dataSet?.elements.x00420011;
|
|
201
|
-
|
|
202
|
-
if (!fileTag) {
|
|
203
|
-
throw new Error("No file tag found");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
let pdfByteArray = image.dataSet?.byteArray.slice(
|
|
207
|
-
fileTag.dataOffset,
|
|
208
|
-
fileTag.dataOffset + fileTag.length
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
if (!pdfByteArray) {
|
|
212
|
-
console.error("No pdf byte array found");
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (!element) {
|
|
217
|
-
console.error("invalid html element: " + elementId);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let PDF: Blob | null = new Blob([pdfByteArray], {
|
|
222
|
-
type: "application/pdf"
|
|
223
|
-
});
|
|
224
|
-
let fileURL = URL.createObjectURL(PDF);
|
|
225
|
-
element.innerHTML =
|
|
226
|
-
'<object data="' +
|
|
227
|
-
fileURL +
|
|
228
|
-
'" type="application/pdf" width="100%" height="100%"></object>';
|
|
229
|
-
const id: string = isElement(elementId)
|
|
230
|
-
? element.id
|
|
231
|
-
: (elementId as string);
|
|
232
|
-
setStore(["isPDF", id, true]);
|
|
233
|
-
let t1 = performance.now();
|
|
234
|
-
console.log(`Call to renderDICOMPDF took ${t1 - t0} milliseconds.`);
|
|
235
|
-
image = null;
|
|
236
|
-
fileTag = undefined;
|
|
237
|
-
pdfByteArray = undefined;
|
|
238
|
-
PDF = null;
|
|
239
|
-
resolve(true);
|
|
240
|
-
} else {
|
|
241
|
-
reject("This is not a DICOM with a PDF");
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
return renderPromise;
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Render an image (png or jpg) from File on a html div using cornerstone
|
|
249
|
-
* @instance
|
|
250
|
-
* @function renderFileImage
|
|
251
|
-
* @param {Object} file - The image File object
|
|
252
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
253
|
-
* @returns {Promise} - Return a promise which will resolve when image is displayed
|
|
254
|
-
*/
|
|
255
|
-
export const renderFileImage = function (
|
|
256
|
-
file: File,
|
|
257
|
-
elementId: string | HTMLElement
|
|
258
|
-
) {
|
|
259
|
-
let element = isElement(elementId)
|
|
260
|
-
? (elementId as HTMLElement)
|
|
261
|
-
: document.getElementById(elementId as string);
|
|
262
|
-
|
|
263
|
-
if (!element) {
|
|
264
|
-
console.error("invalid html element: " + elementId);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (cornerstone.getEnabledElements().length == 0) {
|
|
269
|
-
cornerstone.enable(element);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
let renderPromise = new Promise(resolve => {
|
|
273
|
-
// check if imageId is already stored in fileManager
|
|
274
|
-
const imageId = getFileImageId(file);
|
|
275
|
-
if (imageId) {
|
|
276
|
-
cornerstone.loadImage(imageId).then(function (image) {
|
|
277
|
-
if (!element) {
|
|
278
|
-
console.error("invalid html element: " + elementId);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
cornerstone.displayImage(element, image);
|
|
282
|
-
const viewport = cornerstone.getViewport(element) as Viewport;
|
|
283
|
-
|
|
284
|
-
if (!viewport) {
|
|
285
|
-
console.error("invalid viewport");
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
viewport.displayedArea.brhc.x = image.width;
|
|
290
|
-
viewport.displayedArea.brhc.y = image.height;
|
|
291
|
-
cornerstone.setViewport(element, viewport);
|
|
292
|
-
cornerstone.fitToWindow(element);
|
|
293
|
-
csToolsCreateStack(element);
|
|
294
|
-
resolve(image);
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
return renderPromise;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Render an image (png or jpg) from web url on a html div using cornerstone
|
|
303
|
-
* @instance
|
|
304
|
-
* @function renderWebImage
|
|
305
|
-
* @param {String} url - The image data url
|
|
306
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
307
|
-
* @returns {Promise} - Return a promise which will resolve when image is displayed
|
|
308
|
-
*/
|
|
309
|
-
export const renderWebImage = function (
|
|
310
|
-
url: string,
|
|
311
|
-
elementId: string | HTMLElement
|
|
312
|
-
) {
|
|
313
|
-
let element = isElement(elementId)
|
|
314
|
-
? (elementId as HTMLElement)
|
|
315
|
-
: document.getElementById(elementId as string);
|
|
316
|
-
let renderPromise = new Promise<cornerstone.Image>((resolve, reject) => {
|
|
317
|
-
if (!element) {
|
|
318
|
-
console.error("invalid html element: " + elementId);
|
|
319
|
-
reject("invalid html element: " + elementId);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
cornerstone.enable(element);
|
|
323
|
-
cornerstone.loadImage(url).then(function (image) {
|
|
324
|
-
if (!element) {
|
|
325
|
-
console.error("invalid html element: " + elementId);
|
|
326
|
-
reject("invalid html element: " + elementId);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
cornerstone.displayImage(element, image);
|
|
330
|
-
csToolsCreateStack(element);
|
|
331
|
-
resolve(image);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
return renderPromise;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Unrender an image on a html div using cornerstone
|
|
339
|
-
* @instance
|
|
340
|
-
* @function disableViewport
|
|
341
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
342
|
-
*/
|
|
343
|
-
export const disableViewport = function (elementId: string | HTMLElement) {
|
|
344
|
-
let element = isElement(elementId)
|
|
345
|
-
? (elementId as HTMLElement)
|
|
346
|
-
: document.getElementById(elementId as string);
|
|
347
|
-
if (!element) {
|
|
348
|
-
console.error("invalid html element: " + elementId);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const id: string = isElement(elementId) ? element.id : (elementId as string);
|
|
352
|
-
toggleMouseToolsListeners(id, true);
|
|
353
|
-
cornerstone.disable(element);
|
|
354
|
-
setStore(["ready", id, false]);
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Unrender an image on a html div using cornerstone
|
|
359
|
-
* Remove image from cornerstone cache and remove from store
|
|
360
|
-
* @instance
|
|
361
|
-
* @function unloadViewport
|
|
362
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
363
|
-
* @param {String} seriesId - The id of the serie
|
|
364
|
-
*/
|
|
365
|
-
export const unloadViewport = function (elementId: string, seriesId: string) {
|
|
366
|
-
disableViewport(elementId);
|
|
367
|
-
|
|
368
|
-
if (!seriesId) {
|
|
369
|
-
console.warn(
|
|
370
|
-
"seriesId not provided, use disableViewport if you do not want to uncache images"
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
// remove images from cornerstone cache
|
|
374
|
-
if (seriesId && has(store.get("series"), seriesId)) {
|
|
375
|
-
clearImageCache(seriesId);
|
|
376
|
-
}
|
|
377
|
-
store.deleteViewport(elementId);
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Resize a viewport using cornerstone resize
|
|
382
|
-
* And forcing fit to window
|
|
383
|
-
* @instance
|
|
384
|
-
* @function resizeViewport
|
|
385
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
386
|
-
*/
|
|
387
|
-
export const resizeViewport = function (elementId: string | HTMLElement) {
|
|
388
|
-
let element = isElement(elementId)
|
|
389
|
-
? (elementId as HTMLElement)
|
|
390
|
-
: document.getElementById(elementId as string);
|
|
391
|
-
if (!element) {
|
|
392
|
-
console.error("invalid html element: " + elementId);
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
cornerstone.resize(element, true); // true flag forces fitToWindow
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Cache image and render it in a html div using cornerstone
|
|
400
|
-
* @instance
|
|
401
|
-
* @function renderImage
|
|
402
|
-
* @param {Object} seriesStack - The original series data object
|
|
403
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
404
|
-
* @param {Object} defaultProps - Optional default props
|
|
405
|
-
* @return {Promise} Return a promise which will resolve when image is displayed
|
|
406
|
-
*/
|
|
407
|
-
export const renderImage = function (
|
|
408
|
-
seriesStack: Series,
|
|
409
|
-
elementId: string | HTMLElement,
|
|
410
|
-
defaultProps: StoreViewportOptions
|
|
411
|
-
): Promise<true> {
|
|
412
|
-
const t0 = performance.now();
|
|
413
|
-
// get element and enable it
|
|
414
|
-
const element = isElement(elementId)
|
|
415
|
-
? (elementId as HTMLElement)
|
|
416
|
-
: document.getElementById(elementId as string);
|
|
417
|
-
if (!element) {
|
|
418
|
-
console.error("invalid html element: " + elementId);
|
|
419
|
-
return new Promise((_, reject) =>
|
|
420
|
-
reject("invalid html element: " + elementId)
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
const id: string = isElement(elementId) ? element.id : (elementId as string);
|
|
424
|
-
cornerstone.enable(element);
|
|
425
|
-
|
|
426
|
-
setStore(["ready", id, false]);
|
|
427
|
-
|
|
428
|
-
let series = { ...seriesStack };
|
|
429
|
-
let data = getSeriesData(series, defaultProps);
|
|
430
|
-
if (!data.imageId) {
|
|
431
|
-
console.warn("error during renderImage: imageId has not been loaded yet.");
|
|
432
|
-
return new Promise((_, reject) => {
|
|
433
|
-
setStore(["pendingSliceId", id, data.imageIndex]);
|
|
434
|
-
reject("error during renderImage: imageId has not been loaded yet.");
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const renderPromise = new Promise<true>((resolve, reject) => {
|
|
439
|
-
// load and display one image (imageId)
|
|
440
|
-
cornerstone.loadImage(data.imageId as string).then(function (image) {
|
|
441
|
-
if (!element) {
|
|
442
|
-
console.error("invalid html element: " + elementId);
|
|
443
|
-
reject("invalid html element: " + elementId);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
cornerstone.displayImage(element, image);
|
|
448
|
-
|
|
449
|
-
if (series.layer) {
|
|
450
|
-
// assign the image to its layer and return its id
|
|
451
|
-
series.layer.id = cornerstone.addLayer(
|
|
452
|
-
element,
|
|
453
|
-
image,
|
|
454
|
-
series.layer.options
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const viewport = cornerstone.getViewport(element);
|
|
459
|
-
|
|
460
|
-
if (!viewport) {
|
|
461
|
-
console.error("viewport not found");
|
|
462
|
-
reject("viewport not found for element: " + elementId);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// window width and window level
|
|
467
|
-
// are stored in specific dicom tags
|
|
468
|
-
// (x00281050 and x00281051)
|
|
469
|
-
// if not present check in image object
|
|
470
|
-
if (data.viewport?.voi?.windowWidth === undefined) {
|
|
471
|
-
data.viewport.voi.windowWidth = image.windowWidth;
|
|
472
|
-
}
|
|
473
|
-
if (data.viewport?.voi?.windowCenter === undefined) {
|
|
474
|
-
data.viewport.voi.windowCenter = image.windowCenter;
|
|
475
|
-
}
|
|
476
|
-
if (data.default?.voi?.windowWidth === undefined) {
|
|
477
|
-
data.default.voi.windowWidth = data.viewport.voi.windowWidth;
|
|
478
|
-
}
|
|
479
|
-
if (data.default?.voi?.windowCenter === undefined) {
|
|
480
|
-
data.default.voi.windowCenter = data.viewport.voi.windowCenter;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
cornerstone.fitToWindow(element);
|
|
484
|
-
|
|
485
|
-
if (defaultProps && defaultProps.scale !== undefined) {
|
|
486
|
-
viewport.scale = defaultProps["scale"];
|
|
487
|
-
cornerstone.setViewport(element, viewport);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (
|
|
491
|
-
defaultProps &&
|
|
492
|
-
defaultProps.tr_x !== undefined &&
|
|
493
|
-
defaultProps.tr_y !== undefined
|
|
494
|
-
) {
|
|
495
|
-
viewport.translation.x = defaultProps.tr_x;
|
|
496
|
-
viewport.translation.y = defaultProps.tr_y;
|
|
497
|
-
cornerstone.setViewport(element, viewport);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// color maps
|
|
501
|
-
if (defaultProps && defaultProps.colormap && image.color == false) {
|
|
502
|
-
applyColorMap(defaultProps["colormap"]);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
const storedViewport = cornerstone.getViewport(element);
|
|
506
|
-
|
|
507
|
-
if (!storedViewport) {
|
|
508
|
-
console.error("storedViewport not found");
|
|
509
|
-
reject("storedViewport not found for element: " + elementId);
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
storeViewportData(image, element.id, storedViewport as Viewport, data);
|
|
514
|
-
setStore(["ready", element.id, true]);
|
|
515
|
-
const t1 = performance.now();
|
|
516
|
-
console.log(`Call to renderImage took ${t1 - t0} milliseconds.`);
|
|
517
|
-
|
|
518
|
-
const uri = cornerstoneDICOMImageLoader.wadouri.parseImageId(
|
|
519
|
-
data.imageId
|
|
520
|
-
).url;
|
|
521
|
-
cornerstoneDICOMImageLoader.wadouri.dataSetCacheManager.unload(uri);
|
|
522
|
-
//@ts-ignore
|
|
523
|
-
image = null;
|
|
524
|
-
//@ts-ignore
|
|
525
|
-
series = null;
|
|
526
|
-
//@ts-ignore
|
|
527
|
-
data = null;
|
|
528
|
-
resolve(true);
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
csToolsCreateStack(element, series.imageIds, (data.imageIndex as number) - 1);
|
|
533
|
-
toggleMouseToolsListeners(id, false);
|
|
534
|
-
|
|
535
|
-
return renderPromise;
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Update the cornerstone image with new imageIndex
|
|
540
|
-
* @instance
|
|
541
|
-
* @function updateImage
|
|
542
|
-
* @param {Object} series - The original series data object
|
|
543
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
544
|
-
* @param {Number} imageIndex - The index of the image to be rendered
|
|
545
|
-
* @param {Boolean} cacheImage - A flag to handle image cache
|
|
546
|
-
*/
|
|
547
|
-
export const updateImage = async function (
|
|
548
|
-
series: Series,
|
|
549
|
-
elementId: string | HTMLElement,
|
|
550
|
-
imageIndex: number,
|
|
551
|
-
cacheImage: boolean
|
|
552
|
-
): Promise<void> {
|
|
553
|
-
const element = isElement(elementId)
|
|
554
|
-
? (elementId as HTMLElement)
|
|
555
|
-
: document.getElementById(elementId as string);
|
|
556
|
-
if (!element) {
|
|
557
|
-
throw "not element";
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const id: string = isElement(elementId) ? element.id : (elementId as string);
|
|
561
|
-
const imageId = series.imageIds[imageIndex];
|
|
562
|
-
if (!imageId) {
|
|
563
|
-
setStore(["pendingSliceId", id, imageIndex]);
|
|
564
|
-
throw `Error: wrong image index ${imageIndex}, no imageId available`;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (series.is4D) {
|
|
568
|
-
const timestamp = series.instances[imageId].metadata.contentTime;
|
|
569
|
-
const timeId =
|
|
570
|
-
series.instances[imageId].metadata.temporalPositionIdentifier! - 1; // timeId from 0 to N
|
|
571
|
-
setStore(["timeId", id as string, timeId]);
|
|
572
|
-
setStore(["timestamp", id as string, timestamp]);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (cacheImage) {
|
|
576
|
-
let t0: number | undefined;
|
|
577
|
-
if (getPerformanceMonitor() === true) {
|
|
578
|
-
t0 = performance.now();
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const image = await cornerstone.loadAndCacheImage(imageId);
|
|
582
|
-
cornerstone.displayImage(element, image);
|
|
583
|
-
|
|
584
|
-
if (getPerformanceMonitor() === true) {
|
|
585
|
-
const t1 = performance.now();
|
|
586
|
-
if (t0 !== undefined) {
|
|
587
|
-
// check if t0 is defined before using it
|
|
588
|
-
console.log(`Call to updateImage took ${t1 - t0} milliseconds.`);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
setStore(["sliceId", id, imageIndex]);
|
|
593
|
-
const pendingSliceId = store.get(["viewports", id, "pendingSliceId"]);
|
|
594
|
-
if (imageIndex == pendingSliceId) {
|
|
595
|
-
setStore(["pendingSliceId", id, undefined]);
|
|
596
|
-
}
|
|
597
|
-
setStore(["minPixelValue", id, image.minPixelValue]);
|
|
598
|
-
setStore(["maxPixelValue", id, image.maxPixelValue]);
|
|
599
|
-
} else {
|
|
600
|
-
let t0: number | undefined;
|
|
601
|
-
if (getPerformanceMonitor() === true) {
|
|
602
|
-
t0 = performance.now();
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
const image = await cornerstone.loadImage(imageId);
|
|
606
|
-
cornerstone.displayImage(element, image);
|
|
607
|
-
|
|
608
|
-
if (getPerformanceMonitor() === true) {
|
|
609
|
-
const t1 = performance.now();
|
|
610
|
-
if (t0 !== undefined) {
|
|
611
|
-
// check if t0 is defined before using it
|
|
612
|
-
console.log(`Call to updateImage took ${t1 - t0} milliseconds.`);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
setStore(["sliceId", id, imageIndex]);
|
|
617
|
-
const pendingSliceId = store.get(["viewports", id, "pendingSliceId"]);
|
|
618
|
-
if (imageIndex == pendingSliceId) {
|
|
619
|
-
setStore(["pendingSliceId", id, undefined]);
|
|
620
|
-
}
|
|
621
|
-
setStore(["minPixelValue", id, image.minPixelValue]);
|
|
622
|
-
setStore(["maxPixelValue", id, image.maxPixelValue]);
|
|
623
|
-
}
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Reset viewport values (scale, translation and wwwc)
|
|
628
|
-
* @instance
|
|
629
|
-
* @function resetViewports
|
|
630
|
-
* @param {Array} elementIds - The array of hmtl div ids
|
|
631
|
-
* @param {Array} keys - The array of viewport sections to resets (default is all)
|
|
632
|
-
*/
|
|
633
|
-
export const resetViewports = function (
|
|
634
|
-
elementIds: string[],
|
|
635
|
-
keys?: Array<
|
|
636
|
-
"contrast" | "scaleAndTranslation" | "rotation" | "flip" | "zoom"
|
|
637
|
-
>
|
|
638
|
-
) {
|
|
639
|
-
each(elementIds, function (elementId: string) {
|
|
640
|
-
const element = document.getElementById(elementId);
|
|
641
|
-
if (!element) {
|
|
642
|
-
console.error("invalid html element: " + elementId);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
const defaultViewport = store.get(["viewports", elementId, "default"]);
|
|
647
|
-
const viewport = cornerstone.getViewport(element);
|
|
648
|
-
|
|
649
|
-
if (!viewport) {
|
|
650
|
-
throw new Error("viewport not found");
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
if (!keys || keys.find(v => v === "contrast")) {
|
|
654
|
-
viewport.voi.windowWidth = defaultViewport.voi.windowWidth;
|
|
655
|
-
viewport.voi.windowCenter = defaultViewport.voi.windowCenter;
|
|
656
|
-
viewport.invert = defaultViewport.voi.invert;
|
|
657
|
-
setStore([
|
|
658
|
-
"contrast",
|
|
659
|
-
elementId,
|
|
660
|
-
viewport.voi.windowWidth,
|
|
661
|
-
viewport.voi.windowCenter
|
|
662
|
-
]);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
if (!keys || keys.find(v => v === "scaleAndTranslation")) {
|
|
666
|
-
viewport.scale = defaultViewport.scale;
|
|
667
|
-
setStore(["scale", elementId, viewport.scale]);
|
|
668
|
-
|
|
669
|
-
viewport.translation.x = defaultViewport.translation.x;
|
|
670
|
-
viewport.translation.y = defaultViewport.translation.y;
|
|
671
|
-
setStore([
|
|
672
|
-
"translation",
|
|
673
|
-
elementId,
|
|
674
|
-
viewport.translation.x,
|
|
675
|
-
viewport.translation.y
|
|
676
|
-
]);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (!keys || keys.find(v => v === "rotation")) {
|
|
680
|
-
viewport.rotation = defaultViewport.rotation;
|
|
681
|
-
setStore(["rotation", elementId, viewport.rotation]);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
if (!keys || keys.find(v => v === "flip")) {
|
|
685
|
-
viewport.hflip = false;
|
|
686
|
-
viewport.vflip = false;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if (!keys || keys.find(v => v === "zoom")) {
|
|
690
|
-
viewport.scale = defaultViewport.scale;
|
|
691
|
-
setStore(["scale", elementId, viewport.scale]);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
cornerstone.setViewport(element, viewport);
|
|
695
|
-
|
|
696
|
-
if (!keys || keys.find(v => v === "scaleAndTranslation")) {
|
|
697
|
-
cornerstone.fitToWindow(element);
|
|
698
|
-
}
|
|
699
|
-
cornerstone.updateImage(element);
|
|
700
|
-
});
|
|
701
|
-
};
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* Update viewport data in store
|
|
705
|
-
* @instance
|
|
706
|
-
* @function updateViewportData
|
|
707
|
-
* @param {String} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
708
|
-
* @param {Object} viewportData - The new viewport data
|
|
709
|
-
*/
|
|
710
|
-
export const updateViewportData = function (
|
|
711
|
-
elementId: string,
|
|
712
|
-
viewportData: Viewport,
|
|
713
|
-
activeTool: string
|
|
714
|
-
) {
|
|
715
|
-
let element = document.getElementById(elementId as string);
|
|
716
|
-
if (!element) {
|
|
717
|
-
console.error("invalid html element: " + elementId);
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
// TODO: understand how to handle synchronized tools
|
|
721
|
-
switch (activeTool) {
|
|
722
|
-
case "Wwwc":
|
|
723
|
-
case "WwwcRegion":
|
|
724
|
-
if (viewportData.voi) {
|
|
725
|
-
setStore([
|
|
726
|
-
"contrast",
|
|
727
|
-
elementId,
|
|
728
|
-
viewportData.voi.windowWidth,
|
|
729
|
-
viewportData.voi.windowCenter
|
|
730
|
-
]);
|
|
731
|
-
}
|
|
732
|
-
break;
|
|
733
|
-
case "Pan":
|
|
734
|
-
if (viewportData.translation) {
|
|
735
|
-
setStore([
|
|
736
|
-
"translation",
|
|
737
|
-
elementId,
|
|
738
|
-
viewportData.translation.x,
|
|
739
|
-
viewportData.translation.y
|
|
740
|
-
]);
|
|
741
|
-
}
|
|
742
|
-
break;
|
|
743
|
-
case "Zoom":
|
|
744
|
-
if (viewportData.scale) {
|
|
745
|
-
setStore(["scale", elementId, viewportData.scale]);
|
|
746
|
-
}
|
|
747
|
-
break;
|
|
748
|
-
case "Rotate":
|
|
749
|
-
if (viewportData.rotation) {
|
|
750
|
-
setStore(["rotation", elementId, viewportData.rotation]);
|
|
751
|
-
}
|
|
752
|
-
break;
|
|
753
|
-
case "mouseWheel":
|
|
754
|
-
case "stackscroll":
|
|
755
|
-
const viewport = store.get(["viewports", elementId]);
|
|
756
|
-
const isTimeserie = viewport.isTimeserie;
|
|
757
|
-
if (isTimeserie) {
|
|
758
|
-
const index = viewportData.newImageIdIndex;
|
|
759
|
-
const timeId = viewport.timeIds[index];
|
|
760
|
-
const timestamp = viewport.timestamps[index];
|
|
761
|
-
setStore(["timeId", elementId, timeId]);
|
|
762
|
-
setStore(["timestamp", elementId, timestamp]);
|
|
763
|
-
}
|
|
764
|
-
break;
|
|
765
|
-
default:
|
|
766
|
-
break;
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
* Store the viewport data into internal storage
|
|
772
|
-
* @instance
|
|
773
|
-
* @function storeViewportData
|
|
774
|
-
* @param {Object} image - The cornerstone image frame
|
|
775
|
-
* @param {String} elementId - The html div id used for rendering
|
|
776
|
-
* @param {String} viewport - The viewport tag name
|
|
777
|
-
* @param {Object} data - The viewport data object
|
|
778
|
-
*/
|
|
779
|
-
export const storeViewportData = function (
|
|
780
|
-
image: cornerstone.Image,
|
|
781
|
-
elementId: string,
|
|
782
|
-
viewport: Viewport,
|
|
783
|
-
data: ReturnType<typeof getSeriesData>
|
|
784
|
-
) {
|
|
785
|
-
setStore(["dimensions", elementId, data.rows, data.cols]);
|
|
786
|
-
setStore(["spacing", elementId, data.spacing_x, data.spacing_y]);
|
|
787
|
-
setStore(["thickness", elementId, data.thickness]);
|
|
788
|
-
setStore(["minPixelValue", elementId, image.minPixelValue]);
|
|
789
|
-
setStore(["maxPixelValue", elementId, image.maxPixelValue]);
|
|
790
|
-
// slice id from 0 to n - 1
|
|
791
|
-
setStore(["minSliceId", elementId, 0]);
|
|
792
|
-
setStore(["sliceId", elementId, data.imageIndex]);
|
|
793
|
-
const pendingSliceId = store.get(["viewports", elementId, "pendingSliceId"]);
|
|
794
|
-
if (data.imageIndex == pendingSliceId) {
|
|
795
|
-
setStore(["pendingSliceId", elementId, undefined]);
|
|
796
|
-
}
|
|
797
|
-
setStore(["maxSliceId", elementId, data.numberOfSlices - 1]);
|
|
798
|
-
|
|
799
|
-
if (data.isTimeserie) {
|
|
800
|
-
setStore(["minTimeId", elementId, 0]);
|
|
801
|
-
setStore(["timeId", elementId, data.timeIndex || 0]);
|
|
802
|
-
setStore(["maxTimeId", elementId, data.numberOfTemporalPositions - 1]);
|
|
803
|
-
let maxSliceId = data.numberOfSlices * data.numberOfTemporalPositions - 1;
|
|
804
|
-
setStore(["maxSliceId", elementId, maxSliceId]);
|
|
805
|
-
|
|
806
|
-
setStore(["timestamp", elementId, data.timestamp]);
|
|
807
|
-
setStore(["timestamps", elementId, data.timestamps]);
|
|
808
|
-
setStore(["timeIds", elementId, data.timeIds]);
|
|
809
|
-
} else {
|
|
810
|
-
setStore(["minTimeId", elementId, 0]);
|
|
811
|
-
setStore(["timeId", elementId, 0]);
|
|
812
|
-
setStore(["maxTimeId", elementId, 0]);
|
|
813
|
-
setStore(["timestamp", elementId, 0]);
|
|
814
|
-
setStore(["timestamps", elementId, []]);
|
|
815
|
-
setStore(["timeIds", elementId, []]);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
setStore([
|
|
819
|
-
"defaultViewport",
|
|
820
|
-
elementId,
|
|
821
|
-
viewport.scale || 0,
|
|
822
|
-
viewport.rotation || 0,
|
|
823
|
-
viewport.translation?.x || 0,
|
|
824
|
-
viewport.translation?.y || 0,
|
|
825
|
-
data.default?.voi?.windowWidth,
|
|
826
|
-
data.default?.voi?.windowCenter,
|
|
827
|
-
viewport.invert === true
|
|
828
|
-
]);
|
|
829
|
-
setStore(["scale", elementId, viewport.scale || 0]);
|
|
830
|
-
setStore(["rotation", elementId, viewport.rotation || 0]);
|
|
831
|
-
|
|
832
|
-
setStore([
|
|
833
|
-
"translation",
|
|
834
|
-
elementId,
|
|
835
|
-
viewport.translation?.x || 0,
|
|
836
|
-
viewport.translation?.y || 0
|
|
837
|
-
]);
|
|
838
|
-
setStore([
|
|
839
|
-
"contrast",
|
|
840
|
-
elementId,
|
|
841
|
-
viewport.voi?.windowWidth || 0,
|
|
842
|
-
viewport.voi?.windowCenter || 0
|
|
843
|
-
]);
|
|
844
|
-
setStore(["isColor", elementId, data.isColor]);
|
|
845
|
-
setStore(["isMultiframe", elementId, data.isMultiframe]);
|
|
846
|
-
setStore(["isTimeserie", elementId, data.isTimeserie]);
|
|
847
|
-
setStore(["isPDF", elementId, false]);
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* Invert pixels of an image
|
|
852
|
-
* @instance
|
|
853
|
-
* @function invertImage
|
|
854
|
-
* @param {Object} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
855
|
-
*/
|
|
856
|
-
export const invertImage = function (elementId: string | HTMLElement) {
|
|
857
|
-
let element = isElement(elementId)
|
|
858
|
-
? (elementId as HTMLElement)
|
|
859
|
-
: document.getElementById(elementId as string);
|
|
860
|
-
if (!element) {
|
|
861
|
-
console.error("invalid html element: " + elementId);
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
let viewport = cornerstone.getViewport(element);
|
|
865
|
-
|
|
866
|
-
if (!viewport) {
|
|
867
|
-
throw new Error("Viewport is undefined");
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
viewport.invert = !viewport.invert;
|
|
871
|
-
cornerstone.setViewport(element, viewport);
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
/**
|
|
875
|
-
* Flip image around horizontal axis
|
|
876
|
-
* @instance
|
|
877
|
-
* @function flipImageHorizontal
|
|
878
|
-
* @param {Object} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
879
|
-
*/
|
|
880
|
-
export const flipImageHorizontal = function (elementId: string | HTMLElement) {
|
|
881
|
-
let element = isElement(elementId)
|
|
882
|
-
? (elementId as HTMLElement)
|
|
883
|
-
: document.getElementById(elementId as string);
|
|
884
|
-
if (!element) {
|
|
885
|
-
console.error("invalid html element: " + elementId);
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
let viewport = cornerstone.getViewport(element);
|
|
889
|
-
|
|
890
|
-
if (!viewport) {
|
|
891
|
-
throw new Error("Viewport is undefined");
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
viewport.hflip = !viewport.hflip;
|
|
895
|
-
cornerstone.setViewport(element, viewport);
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
/**
|
|
899
|
-
* Flip image around vertical axis
|
|
900
|
-
* @instance
|
|
901
|
-
* @function flipImageVertical
|
|
902
|
-
* @param {Object} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
903
|
-
*/
|
|
904
|
-
export const flipImageVertical = function (elementId: string | HTMLElement) {
|
|
905
|
-
let element = isElement(elementId)
|
|
906
|
-
? (elementId as HTMLElement)
|
|
907
|
-
: document.getElementById(elementId as string);
|
|
908
|
-
if (!element) {
|
|
909
|
-
console.error("invalid html element: " + elementId);
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
let viewport = cornerstone.getViewport(element);
|
|
913
|
-
|
|
914
|
-
if (!viewport) {
|
|
915
|
-
throw new Error("Viewport is undefined");
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
viewport.vflip = !viewport.vflip;
|
|
919
|
-
cornerstone.setViewport(element, viewport);
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
/**
|
|
923
|
-
* Rotate image by 90° in left direction
|
|
924
|
-
* @instance
|
|
925
|
-
* @function rotateImageLeft
|
|
926
|
-
* @param {Object} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
927
|
-
*/
|
|
928
|
-
export const rotateImageLeft = function (elementId: string | HTMLElement) {
|
|
929
|
-
let element = isElement(elementId)
|
|
930
|
-
? (elementId as HTMLElement)
|
|
931
|
-
: document.getElementById(elementId as string);
|
|
932
|
-
if (!element) {
|
|
933
|
-
console.error("invalid html element: " + elementId);
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
let viewport = cornerstone.getViewport(element);
|
|
937
|
-
|
|
938
|
-
if (!viewport) {
|
|
939
|
-
throw new Error("Viewport is undefined");
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
viewport.rotation -= 90;
|
|
943
|
-
cornerstone.setViewport(element, viewport);
|
|
944
|
-
};
|
|
945
|
-
|
|
946
|
-
/**
|
|
947
|
-
* Rotate image by 90° in right direction
|
|
948
|
-
* @instance
|
|
949
|
-
* @function rotateImageRight
|
|
950
|
-
* @param {Object} elementId - The html div id used for rendering or its DOM HTMLElement
|
|
951
|
-
*/
|
|
952
|
-
export const rotateImageRight = function (elementId: string | HTMLElement) {
|
|
953
|
-
let element = isElement(elementId)
|
|
954
|
-
? (elementId as HTMLElement)
|
|
955
|
-
: document.getElementById(elementId as string);
|
|
956
|
-
if (!element) {
|
|
957
|
-
console.error("invalid html element: " + elementId);
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
let viewport = cornerstone.getViewport(element);
|
|
961
|
-
|
|
962
|
-
if (!viewport) {
|
|
963
|
-
throw new Error("Viewport is undefined");
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
viewport.rotation += 90;
|
|
967
|
-
cornerstone.setViewport(element, viewport);
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
/* Internal module functions */
|
|
971
|
-
|
|
972
|
-
/**
|
|
973
|
-
* Get series metadata from default props and series' metadata
|
|
974
|
-
* @instance
|
|
975
|
-
* @function getSeriesData
|
|
976
|
-
* @param {Object} series - The parsed data series
|
|
977
|
-
* @param {Object} defaultProps - Optional default properties
|
|
978
|
-
* @return {Object} data - A data dictionary with parsed tags' values
|
|
979
|
-
*/
|
|
980
|
-
const getSeriesData = function (
|
|
981
|
-
series: Series,
|
|
982
|
-
defaultProps: StoreViewportOptions
|
|
983
|
-
) {
|
|
984
|
-
type RecursivePartial<T> = {
|
|
985
|
-
[P in keyof T]?: RecursivePartial<T[P]>;
|
|
986
|
-
};
|
|
987
|
-
type SeriesData = StoreViewport & {
|
|
988
|
-
imageIndex: number;
|
|
989
|
-
imageId: string;
|
|
990
|
-
numberOfSlices: number;
|
|
991
|
-
numberOfTemporalPositions: number;
|
|
992
|
-
timeIndex?: number;
|
|
993
|
-
};
|
|
994
|
-
const data: RecursivePartial<SeriesData> = {};
|
|
995
|
-
|
|
996
|
-
if (series.isMultiframe) {
|
|
997
|
-
data.isMultiframe = true;
|
|
998
|
-
data.numberOfSlices = series.imageIds.length;
|
|
999
|
-
data.imageIndex = 0;
|
|
1000
|
-
data.imageId = series.imageIds[data.imageIndex];
|
|
1001
|
-
data.isTimeserie = false;
|
|
1002
|
-
} else if (series.is4D) {
|
|
1003
|
-
data.isMultiframe = false;
|
|
1004
|
-
data.isTimeserie = true;
|
|
1005
|
-
// check with real indices
|
|
1006
|
-
data.numberOfSlices = series.numberOfImages;
|
|
1007
|
-
data.numberOfTemporalPositions = series.numberOfTemporalPositions;
|
|
1008
|
-
data.imageIndex = 0;
|
|
1009
|
-
data.timeIndex = 0;
|
|
1010
|
-
data.imageId = series.imageIds[data.imageIndex];
|
|
1011
|
-
data.timestamp = series.instances[data.imageId].metadata[
|
|
1012
|
-
"x00080033"
|
|
1013
|
-
] as number;
|
|
1014
|
-
data.timestamps = [];
|
|
1015
|
-
data.timeIds = [];
|
|
1016
|
-
each(series.imageIds, function (imageId: string) {
|
|
1017
|
-
(data.timestamps as any[]).push(
|
|
1018
|
-
series.instances[imageId].metadata.contentTime
|
|
1019
|
-
);
|
|
1020
|
-
(data.timeIds as any[]).push(
|
|
1021
|
-
series.instances[imageId].metadata.temporalPositionIdentifier! - 1 // timeId from 0 to N
|
|
1022
|
-
);
|
|
1023
|
-
});
|
|
1024
|
-
} else {
|
|
1025
|
-
data.isMultiframe = false;
|
|
1026
|
-
data.isTimeserie = false;
|
|
1027
|
-
const numberOfSlices =
|
|
1028
|
-
defaultProps && defaultProps.numberOfSlices
|
|
1029
|
-
? defaultProps.numberOfSlices
|
|
1030
|
-
: series.imageIds.length;
|
|
1031
|
-
data.numberOfSlices = numberOfSlices;
|
|
1032
|
-
data.imageIndex =
|
|
1033
|
-
defaultProps?.sliceNumber !== undefined && defaultProps?.sliceNumber >= 0 // slice number between 0 and n-1
|
|
1034
|
-
? defaultProps.sliceNumber
|
|
1035
|
-
: Math.floor(numberOfSlices / 2);
|
|
1036
|
-
|
|
1037
|
-
data.imageId = series.imageIds[data.imageIndex];
|
|
1038
|
-
}
|
|
1039
|
-
const instance: Instance | null = data.imageId
|
|
1040
|
-
? series.instances[data.imageId]
|
|
1041
|
-
: null;
|
|
1042
|
-
|
|
1043
|
-
data.isColor = series.color as boolean;
|
|
1044
|
-
data.isPDF = series.isPDF;
|
|
1045
|
-
if (instance) {
|
|
1046
|
-
data.rows = instance.metadata.x00280010!;
|
|
1047
|
-
data.cols = instance.metadata.x00280011!;
|
|
1048
|
-
data.thickness = instance.metadata.x00180050 as number;
|
|
1049
|
-
|
|
1050
|
-
let spacing = instance.metadata.x00280030!;
|
|
1051
|
-
data.spacing_x = spacing ? spacing[0] : 1;
|
|
1052
|
-
data.spacing_y = spacing ? spacing[1] : 1;
|
|
1053
|
-
// window center and window width
|
|
1054
|
-
data.viewport = {
|
|
1055
|
-
voi: {
|
|
1056
|
-
windowCenter:
|
|
1057
|
-
defaultProps && defaultProps.wc
|
|
1058
|
-
? defaultProps.wc
|
|
1059
|
-
: (instance.metadata.x00281050 as number),
|
|
1060
|
-
windowWidth:
|
|
1061
|
-
defaultProps && defaultProps.ww
|
|
1062
|
-
? defaultProps.ww
|
|
1063
|
-
: (instance.metadata.x00281051 as number)
|
|
1064
|
-
}
|
|
1065
|
-
};
|
|
1066
|
-
data.default = {
|
|
1067
|
-
voi: {
|
|
1068
|
-
windowCenter:
|
|
1069
|
-
defaultProps && has(defaultProps, "defaultWC")
|
|
1070
|
-
? defaultProps.defaultWC
|
|
1071
|
-
: data.viewport!.voi!.windowCenter,
|
|
1072
|
-
windowWidth:
|
|
1073
|
-
defaultProps && has(defaultProps, "defaultWW")
|
|
1074
|
-
? defaultProps.defaultWW
|
|
1075
|
-
: data.viewport!.voi!.windowWidth
|
|
1076
|
-
}
|
|
1077
|
-
};
|
|
1078
|
-
if (data.rows == null || data.cols == null) {
|
|
1079
|
-
console.warn("invalid image metadata (rows or cols is null)");
|
|
1080
|
-
setStore(["errorLog", "Invalid Image Metadata"]);
|
|
1081
|
-
} else {
|
|
1082
|
-
setStore(["errorLog", ""]);
|
|
1083
|
-
}
|
|
1084
|
-
} else {
|
|
1085
|
-
console.warn(
|
|
1086
|
-
`ImageId not found in imageIds with index ${data.imageIndex}.`
|
|
1087
|
-
);
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
return data as SeriesData;
|
|
1091
|
-
};
|