diff --git a/.webpack/excludeNodeModulesExcept.js b/.webpack/excludeNodeModulesExcept.js index 7bfeb0bb6..f65b7fcc9 100644 --- a/.webpack/excludeNodeModulesExcept.js +++ b/.webpack/excludeNodeModulesExcept.js @@ -4,7 +4,7 @@ function excludeNodeModulesExcept(modules) { }); return function (modulePath) { - if (/cornerstoneWADOImageLoader/.test(modulePath)) { + if (/cornerstoneDICOMImageLoader/.test(modulePath)) { return true; } diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index ed6f1537e..fbda24570 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -47,8 +47,8 @@ module.exports = (env, argv, { DIST_DIR }) => { modules: [path.resolve(PROJECT_ROOT, './node_modules'), SRC_PATH], extensions: ['.ts', '.tsx', '.js', '.jsx'], alias: { - 'cornerstone-wado-image-loader': - 'cornerstone-wado-image-loader/dist/dynamic-import/cornerstoneWADOImageLoader.min.js', + '@cornerstonejs/dicom-image-loader': + '@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js', }, fallback: { fs: false, diff --git a/common/reviews/api/core.api.md b/common/reviews/api/core.api.md index 675a7eaef..55e3db8dc 100644 --- a/common/reviews/api/core.api.md +++ b/common/reviews/api/core.api.md @@ -1052,7 +1052,7 @@ interface IImage { // (undocumented) getCanvas: () => HTMLCanvasElement; // (undocumented) - getPixelData: () => Array; + getPixelData: () => PixelDataTypedArray; // (undocumented) height: number; // (undocumented) @@ -1073,8 +1073,8 @@ interface IImage { numComps: number; // (undocumented) preScale?: { - scaled: boolean; - scalingParameters: { + scaled?: boolean; + scalingParameters?: { modality?: string; rescaleSlope?: number; rescaleIntercept?: number; @@ -1671,6 +1671,8 @@ interface IViewport { // (undocumented) type: ViewportType; // (undocumented) + updateRenderingPipeline: () => void; + // (undocumented) worldToCanvas: (worldPos: Point3) => Point2; } @@ -2045,6 +2047,9 @@ type ScalingParameters = { // @public (undocumented) export function setConfiguration(c: Cornerstone3DConfig): void; +// @public (undocumented) +export function setPreferSizeOverAccuracy(status: boolean): void; + // @public (undocumented) export class Settings { constructor(base?: Settings); @@ -2196,6 +2201,8 @@ export class StackViewport extends Viewport implements IStackViewport { // (undocumented) unsetColormap: () => void; // (undocumented) + updateRenderingPipeline: () => void; + // (undocumented) static get useCustomRenderingPipeline(): boolean; // (undocumented) worldToCanvas: (worldPos: Point3) => Point2; @@ -2515,7 +2522,7 @@ export class Viewport implements IViewport { // (undocumented) sHeight: number; // (undocumented) - protected _shouldUse16BitTexture(): boolean; + protected _shouldUseNativeDataType(): boolean; // (undocumented) readonly suppressEvents: boolean; // (undocumented) @@ -2531,6 +2538,8 @@ export class Viewport implements IViewport { // (undocumented) protected updateClippingPlanesForActors(updatedCamera: ICamera): void; // (undocumented) + updateRenderingPipeline: () => void; + // (undocumented) static get useCustomRenderingPipeline(): boolean; // (undocumented) worldToCanvas: (worldPos: Point3) => Point2; diff --git a/common/reviews/api/streaming-image-volume-loader.api.md b/common/reviews/api/streaming-image-volume-loader.api.md index 5516724a1..ead1c1b41 100644 --- a/common/reviews/api/streaming-image-volume-loader.api.md +++ b/common/reviews/api/streaming-image-volume-loader.api.md @@ -745,7 +745,7 @@ interface IImage { columns: number; // (undocumented) getCanvas: () => HTMLCanvasElement; - getPixelData: () => Array; + getPixelData: () => PixelDataTypedArray; height: number; imageId: string; intercept: number; @@ -757,8 +757,8 @@ interface IImage { modalityLUT?: CPUFallbackLUT; numComps: number; preScale?: { - scaled: boolean; - scalingParameters: { + scaled?: boolean; + scalingParameters?: { modality?: string; rescaleSlope?: number; rescaleIntercept?: number; @@ -1138,6 +1138,8 @@ interface IViewport { sx: number; sy: number; type: ViewportType; + // (undocumented) + updateRenderingPipeline: () => void; worldToCanvas: (worldPos: Point3) => Point2; } diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index e04289473..590ca4c7c 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -2444,7 +2444,7 @@ interface IImage { columns: number; // (undocumented) getCanvas: () => HTMLCanvasElement; - getPixelData: () => Array; + getPixelData: () => PixelDataTypedArray; height: number; imageId: string; intercept: number; @@ -2456,8 +2456,8 @@ interface IImage { modalityLUT?: CPUFallbackLUT; numComps: number; preScale?: { - scaled: boolean; - scalingParameters: { + scaled?: boolean; + scalingParameters?: { modality?: string; rescaleSlope?: number; rescaleIntercept?: number; @@ -2993,6 +2993,8 @@ interface IViewport { sx: number; sy: number; type: ViewportType; + // (undocumented) + updateRenderingPipeline: () => void; worldToCanvas: (worldPos: Point3) => Point2; } diff --git a/packages/core/package.json b/packages/core/package.json index 1401daf9a..1d1062eef 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -27,7 +27,7 @@ "webpack:watch": "webpack --mode development --progress --watch --config ./.webpack/webpack.dev.js" }, "peerDependencies": { - "@kitware/vtk.js": "26.5.6", + "@kitware/vtk.js": "27.3.1", "gl-matrix": "^3.4.3" }, "dependencies": { @@ -35,7 +35,7 @@ "lodash.clonedeep": "4.5.0" }, "devDependencies": { - "@kitware/vtk.js": "26.5.6", + "@kitware/vtk.js": "27.3.1", "detect-gpu": "^4.0.45", "gl-matrix": "^3.4.3", "resemblejs": "^4.1.0" diff --git a/packages/core/src/RenderingEngine/BaseVolumeViewport.ts b/packages/core/src/RenderingEngine/BaseVolumeViewport.ts index 29a23ac17..23cd87316 100644 --- a/packages/core/src/RenderingEngine/BaseVolumeViewport.ts +++ b/packages/core/src/RenderingEngine/BaseVolumeViewport.ts @@ -63,7 +63,7 @@ abstract class BaseVolumeViewport extends Viewport implements IVolumeViewport { super(props); this.useCPURendering = getShouldUseCPURendering(); - this.use16BitTexture = this._shouldUse16BitTexture(); + this.use16BitTexture = this._shouldUseNativeDataType(); if (this.useCPURendering) { throw new Error( diff --git a/packages/core/src/RenderingEngine/StackViewport.ts b/packages/core/src/RenderingEngine/StackViewport.ts index a75f7cee0..2c02eaaef 100644 --- a/packages/core/src/RenderingEngine/StackViewport.ts +++ b/packages/core/src/RenderingEngine/StackViewport.ts @@ -56,7 +56,7 @@ import resize from './helpers/cpuFallback/rendering/resize'; import resetCamera from './helpers/cpuFallback/rendering/resetCamera'; import { Transform } from './helpers/cpuFallback/rendering/transform'; -import { getShouldUseCPURendering } from '../init'; +import { getConfiguration, getShouldUseCPURendering } from '../init'; import RequestType from '../enums/RequestType'; import { StackViewportNewStackEventDetail, @@ -66,7 +66,6 @@ import { import cache from '../cache'; import correctShift from './helpers/cpuFallback/rendering/correctShift'; import { ImageActor } from '../types/IActor'; -import isRgbaSourceRgbDest from './helpers/isRgbaSourceRgbDest'; import createLinearRGBTransferFunction from '../utilities/createLinearRGBTransferFunction'; const EPSILON = 1; // Slice Thickness @@ -157,7 +156,12 @@ class StackViewport extends Viewport implements IStackViewport { private _cpuFallbackEnabledElement?: CPUFallbackEnabledElement; // CPU fallback private useCPURendering: boolean; - private use16BitTexture = false; + // Since WebGL natively supports 8 bit int and Float32, we should check if + // extra configuration flags has been set to use native data type + // which would save a lot of memory and speed up rendering but it is not + // yet widely supported in all hardwares. This feature can be turned on + // by setting useNorm16Texture or preferSizeOverAccuracy in the configuration + private useNativeDataType = false; private cpuImagePixelData: number[]; private cpuRenderingInvalidated: boolean; private csImage: IImage; @@ -178,34 +182,12 @@ class StackViewport extends Viewport implements IStackViewport { this.scaling = {}; this.modality = null; this.useCPURendering = getShouldUseCPURendering(); - this.use16BitTexture = this._shouldUse16BitTexture(); + this.useNativeDataType = this._shouldUseNativeDataType(); this._configureRenderingPipeline(); - if (this.useCPURendering) { - this._cpuFallbackEnabledElement = { - canvas: this.canvas, - renderingTools: {}, - transform: new Transform(), - viewport: { rotation: 0 }, - }; - } else { - const renderer = this.getRenderer(); - const camera = vtkCamera.newInstance(); - renderer.setActiveCamera(camera); - - const viewPlaneNormal = [0, 0, -1]; - this.initialViewUp = [0, -1, 0]; - - camera.setDirectionOfProjection( - -viewPlaneNormal[0], - -viewPlaneNormal[1], - -viewPlaneNormal[2] - ); - camera.setViewUp(...this.initialViewUp); - camera.setParallelProjection(true); - camera.setThicknessFromFocalPoint(0.1); - camera.setFreezeFocalPoint(true); - } + this.useCPURendering + ? this._resetCPUFallbackElement() + : this._resetGPUViewport(); this.imageIds = []; this.currentImageIdIndex = 0; @@ -216,21 +198,60 @@ class StackViewport extends Viewport implements IStackViewport { this.initializeElementDisabledHandler(); } + public setUseCPURendering(value: boolean) { + this.useCPURendering = value; + this._configureRenderingPipeline(); + } + static get useCustomRenderingPipeline(): boolean { return getShouldUseCPURendering(); } - public setUseCPURendering(value: boolean) { - this.useCPURendering = value; + public updateRenderingPipeline = () => { this._configureRenderingPipeline(); - } + }; private _configureRenderingPipeline() { + this.useNativeDataType = this._shouldUseNativeDataType(); + this.useCPURendering = getShouldUseCPURendering(); + for (const [funcName, functions] of Object.entries( this.renderingPipelineFunctions )) { this[funcName] = this.useCPURendering ? functions.cpu : functions.gpu; } + + this.useCPURendering + ? this._resetCPUFallbackElement() + : this._resetGPUViewport(); + } + + private _resetCPUFallbackElement() { + this._cpuFallbackEnabledElement = { + canvas: this.canvas, + renderingTools: {}, + transform: new Transform(), + viewport: { rotation: 0 }, + }; + } + + private _resetGPUViewport() { + const renderer = this.getRenderer(); + const camera = vtkCamera.newInstance(); + renderer.setActiveCamera(camera); + + const viewPlaneNormal = [0, 0, -1]; + this.initialViewUp = [0, -1, 0]; + + camera.setDirectionOfProjection( + -viewPlaneNormal[0], + -viewPlaneNormal[1], + -viewPlaneNormal[2] + ); + camera.setViewUp(...this.initialViewUp); + camera.setParallelProjection(true); + camera.setThicknessFromFocalPoint(0.1); + camera.setFreezeFocalPoint(true); } /** @@ -500,6 +521,13 @@ class StackViewport extends Viewport implements IStackViewport { actor.setMapper(mapper); + const { preferSizeOverAccuracy } = getConfiguration().rendering; + + if (preferSizeOverAccuracy) { + // @ts-ignore for now until vtk is updated + mapper.setPreferSizeOverAccuracy(true); + } + if (imageData.getPointData().getNumberOfComponents() > 1) { actor.getProperty().setIndependentComponents(false); } @@ -1430,32 +1458,10 @@ class StackViewport extends Viewport implements IStackViewport { direction, dimensions, spacing, - bitsAllocated, numComps, - numVoxels, - TypedArray, + pixelArray, }): void { - let pixelArray; - switch (bitsAllocated) { - case 8: - pixelArray = new Uint8Array(numVoxels * numComps); - break; - case 16: - if (this.use16BitTexture) { - pixelArray = new TypedArray(numVoxels * numComps); - } else { - pixelArray = new Float32Array(numVoxels * numComps); - } - - break; - case 24: - pixelArray = new Uint8Array(numVoxels * 3 * numComps); - - break; - default: - console.log('bit allocation not implemented'); - } - + // Todo: I guess nothing should be done for use16bit? const scalarArray = vtkDataArray.newInstance({ name: 'Pixels', numberOfComponents: numComps, @@ -1556,7 +1562,7 @@ class StackViewport extends Viewport implements IStackViewport { yVoxels === image.rows && isEqual(imagePlaneModule.rowCosines, rowCosines) && isEqual(imagePlaneModule.columnCosines, columnCosines) && - (!this.use16BitTexture || + (!this.useNativeDataType || dataType === image.getPixelData().constructor.name) ); } @@ -1577,8 +1583,12 @@ class StackViewport extends Viewport implements IStackViewport { this._imageData.setOrigin(origin); - // 1. Update the pixel data in the vtkImageData object with the pixelData - // from the loaded Cornerstone image + // Update the pixel data in the vtkImageData object with the pixelData + // from the loaded Cornerstone image + this._updatePixelData(image); + } + + private _updatePixelData(image: IImage) { const pixelData = image.getPixelData(); const scalars = this._imageData.getPointData().getScalars(); const scalarData = scalars.getData() as @@ -1587,24 +1597,21 @@ class StackViewport extends Viewport implements IStackViewport { | Uint16Array | Int16Array; - if (image.rgba || isRgbaSourceRgbDest(pixelData, scalarData)) { - if (!image.rgba) { - console.warn('rgba not specified but data looks rgba ish', image); - } - // if image is already cached with rgba for any reason (cpu fallback), - // we need to convert it to rgb for the pixel data set - // RGB case - const numPixels = pixelData.length / 4; - - let rgbIndex = 0; - let index = 0; - - for (let i = 0; i < numPixels; i++) { - scalarData[index++] = pixelData[rgbIndex++]; // red - scalarData[index++] = pixelData[rgbIndex++]; // green - scalarData[index++] = pixelData[rgbIndex++]; // blue - rgbIndex++; // skip alpha + // if the color image is loaded with CPU previously, it loads it + // with RGBA, and here we need to remove the A channel from the + // pixel data. + if (image.color && image.rgba) { + const newPixelData = new Uint8Array(image.columns * image.rows * 3); + for (let i = 0; i < image.columns * image.rows; i++) { + newPixelData[i * 3] = pixelData[i * 4]; + newPixelData[i * 3 + 1] = pixelData[i * 4 + 1]; + newPixelData[i * 3 + 2] = pixelData[i * 4 + 2]; } + // modify the image object to have the correct pixel data for later + // use. + image.rgba = false; + image.getPixelData = () => newPixelData; + scalarData.set(newPixelData); } else { scalarData.set(pixelData); } @@ -1651,6 +1658,50 @@ class StackViewport extends Viewport implements IStackViewport { return; } + const pixelData = image.getPixelData(); + + // handle the case where the pixelData is a Float32Array + // CPU path cannot handle it, it should be converted to Uint16Array + // and via the Modality LUT we can display it properly + if (pixelData instanceof Float32Array) { + const floatMinMax = { + min: image.maxPixelValue, + max: image.minPixelValue, + }; + const floatRange = Math.abs(floatMinMax.max - floatMinMax.min); + const intRange = 65535; + const slope = floatRange / intRange; + const intercept = floatMinMax.min; + const numPixels = pixelData.length; + const intPixelData = new Uint16Array(numPixels); + + let min = 65535; + + let max = 0; + + for (let i = 0; i < numPixels; i++) { + const rescaledPixel = Math.floor( + (pixelData[i] - intercept) / slope + ); + + intPixelData[i] = rescaledPixel; + min = Math.min(min, rescaledPixel); + max = Math.max(max, rescaledPixel); + } + + // reset the properties since basically the image has changed + image.minPixelValue = min; + image.maxPixelValue = max; + image.slope = slope; + image.intercept = intercept; + image.getPixelData = () => intPixelData; + + image.preScale = { + ...image.preScale, + scaled: false, + }; + } + image.isPreScaled = image.preScale?.scaled; this.csImage = image; @@ -1673,6 +1724,12 @@ class StackViewport extends Viewport implements IStackViewport { this._cpuFallbackEnabledElement.viewport.colormap ); + const { windowCenter, windowWidth } = viewport.voi; + this.voiRange = windowLevelUtil.toLowHighRange( + windowWidth, + windowCenter + ); + this._cpuFallbackEnabledElement.image = image; this._cpuFallbackEnabledElement.metadata = { ...metadata, @@ -1747,9 +1804,6 @@ class StackViewport extends Viewport implements IStackViewport { const requestType = RequestType.Interaction; const additionalDetails = { imageId }; const options = { - targetBuffer: { - type: this.use16BitTexture ? undefined : 'Float32Array', - }, preScale: { enabled: true, }, @@ -1833,17 +1887,19 @@ class StackViewport extends Viewport implements IStackViewport { } /** - * CSWIL will automatically choose the array type when no targetBuffer - * is provided. When CSWIL is initialized, the use16bit should match - * the settings of cornerstone3D (either preferSizeOverAccuracy or norm16 - * textures need to be enabled) + * If use16bittexture is specified, the CSWIL will automatically choose the + * array type when no targetBuffer is provided. When CSWIL is initialized, + * the use16bit should match the settings of cornerstone3D (either preferSizeOverAccuracy + * or norm16 textures need to be enabled) + * + * If use16bittexture is not specified, we force the Float32Array for now */ const priority = -5; const requestType = RequestType.Interaction; const additionalDetails = { imageId }; const options = { targetBuffer: { - type: this.use16BitTexture ? undefined : 'Float32Array', + type: this.useNativeDataType ? undefined : 'Float32Array', }, preScale: { enabled: true, @@ -1952,9 +2008,7 @@ class StackViewport extends Viewport implements IStackViewport { direction, dimensions, spacing, - bitsAllocated, numComps, - numVoxels, imagePixelModule, } = this._getImageDataMetadata(image); @@ -1965,10 +2019,8 @@ class StackViewport extends Viewport implements IStackViewport { direction, dimensions, spacing, - bitsAllocated, numComps, - numVoxels, - TypedArray: image.getPixelData().constructor, + pixelArray: image.getPixelData(), }); // Set the scalar data of the vtkImageData object from the Cornerstone diff --git a/packages/core/src/RenderingEngine/Viewport.ts b/packages/core/src/RenderingEngine/Viewport.ts index 8ff790106..9352c9290 100644 --- a/packages/core/src/RenderingEngine/Viewport.ts +++ b/packages/core/src/RenderingEngine/Viewport.ts @@ -111,6 +111,7 @@ class Viewport implements IViewport { customRenderViewportToCanvas: () => unknown; resize: () => void; getProperties: () => void; + updateRenderingPipeline: () => void; static get useCustomRenderingPipeline(): boolean { return false; @@ -1222,7 +1223,7 @@ class Viewport implements IViewport { return { widthWorld: maxX - minX, heightWorld: maxY - minY }; } - protected _shouldUse16BitTexture() { + protected _shouldUseNativeDataType() { const { useNorm16Texture, preferSizeOverAccuracy } = getConfiguration().rendering; return useNorm16Texture || preferSizeOverAccuracy; diff --git a/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts b/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts index 6d27ca3a3..f8383bc04 100644 --- a/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts +++ b/packages/core/src/RenderingEngine/helpers/cpuFallback/rendering/renderColorImage.ts @@ -75,7 +75,7 @@ function getRenderCanvas( // Fast drawing if ( enabledElement.viewport.voi.windowWidth === 255 && - enabledElement.viewport.voi.windowCenter === 128 && + enabledElement.viewport.voi.windowCenter === 127 && enabledElement.viewport.invert === false && image.getCanvas && image.getCanvas() diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a04d17b37..fb9614ec4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -33,6 +33,7 @@ import { getShouldUseSharedArrayBuffer, isCornerstoneInitialized, setUseCPURendering, + setPreferSizeOverAccuracy, setUseSharedArrayBuffer, resetUseCPURendering, resetUseSharedArrayBuffer, @@ -70,7 +71,7 @@ export { // enums Enums, CONSTANTS, - Events as EVENTS, // CornerstoneWADOImageLoader uses this, Todo: remove it after fixing wado + Events as EVENTS, // CornerstoneDICOMImageLoader uses this, Todo: remove it after fixing wado // Settings, // Rendering Engine @@ -113,6 +114,7 @@ export { // CPU Rendering getShouldUseCPURendering, setUseCPURendering, + setPreferSizeOverAccuracy, resetUseCPURendering, // SharedArrayBuffer getShouldUseSharedArrayBuffer, diff --git a/packages/core/src/init.ts b/packages/core/src/init.ts index e7480e5c9..ce137a7aa 100644 --- a/packages/core/src/init.ts +++ b/packages/core/src/init.ts @@ -1,5 +1,6 @@ import { getGPUTier } from 'detect-gpu'; import { SharedArrayBufferModes } from './enums'; +import { getRenderingEngines } from './RenderingEngine/getRenderingEngine'; let csRenderInitialized = false; let useSharedArrayBuffer = true; let sharedArrayBufferMode = SharedArrayBufferModes.TRUE; @@ -10,8 +11,9 @@ import { Cornerstone3DConfig } from './types'; const defaultConfig = { detectGPU: {}, rendering: { - preferSizeOverAccuracy: false, useCPURendering: false, + // GPU rendering options + preferSizeOverAccuracy: false, useNorm16Texture: false, // _hasNorm16TextureSupport(), strictZSpacingForVolumeViewport: true, }, @@ -22,8 +24,9 @@ const defaultConfig = { let config = { detectGPU: {}, rendering: { - preferSizeOverAccuracy: false, useCPURendering: false, + // GPU rendering options + preferSizeOverAccuracy: false, useNorm16Texture: false, // _hasNorm16TextureSupport(), strictZSpacingForVolumeViewport: true, }, @@ -68,21 +71,23 @@ function hasSharedArrayBuffer() { } } -function _hasNorm16TextureSupport() { - const gl = _getGLContext(); +// Todo: commenting this out until proper support for int16 textures +// are added to browsers, current implementation is buggy +// function _hasNorm16TextureSupport() { +// const gl = _getGLContext(); - if (gl) { - const ext = (gl as WebGL2RenderingContext).getExtension( - 'EXT_texture_norm16' - ); +// if (gl) { +// const ext = (gl as WebGL2RenderingContext).getExtension( +// 'EXT_texture_norm16' +// ); - if (ext) { - return true; - } - } +// if (ext) { +// return true; +// } +// } - return false; -} +// return false; +// } /** * Initialize the cornerstone-core. If the browser has a webgl context and @@ -140,6 +145,13 @@ async function init(configuration = {}): Promise { function setUseCPURendering(status: boolean): void { config.rendering.useCPURendering = status; csRenderInitialized = true; + _updateRenderingPipelinesForAllViewports(); +} + +function setPreferSizeOverAccuracy(status: boolean): void { + config.rendering.preferSizeOverAccuracy = status; + csRenderInitialized = true; + _updateRenderingPipelinesForAllViewports(); } /** @@ -150,6 +162,7 @@ function setUseCPURendering(status: boolean): void { */ function resetUseCPURendering(): void { config.rendering.useCPURendering = !_hasActiveWebGLContext(); + _updateRenderingPipelinesForAllViewports(); } /** @@ -225,6 +238,20 @@ function getConfiguration(): Cornerstone3DConfig { function setConfiguration(c: Cornerstone3DConfig) { config = c; + _updateRenderingPipelinesForAllViewports(); +} + +/** + * Update rendering pipelines for all viewports in all rendering engines. + * @returns {void} + * @category Initialization + */ +function _updateRenderingPipelinesForAllViewports(): void { + getRenderingEngines().forEach((engine) => + engine + .getViewports() + .forEach((viewport) => viewport.updateRenderingPipeline?.()) + ); } export { @@ -234,6 +261,7 @@ export { isCornerstoneInitialized, setUseCPURendering, setUseSharedArrayBuffer, + setPreferSizeOverAccuracy, resetUseCPURendering, resetUseSharedArrayBuffer, getConfiguration, diff --git a/packages/core/src/types/IImage.ts b/packages/core/src/types/IImage.ts index eb0628dc0..200285e12 100644 --- a/packages/core/src/types/IImage.ts +++ b/packages/core/src/types/IImage.ts @@ -2,6 +2,14 @@ import CPUFallbackLUT from './CPUFallbackLUT'; import CPUFallbackColormap from './CPUFallbackColormap'; import CPUFallbackEnabledElement from './CPUFallbackEnabledElement'; +type PixelDataTypedArray = + | Float32Array + | Int16Array + | Uint16Array + | Uint8Array + | Int8Array + | Uint8ClampedArray; + /** * Cornerstone Image interface, it is used for both CPU and GPU rendering */ @@ -14,9 +22,9 @@ interface IImage { /** preScale object */ preScale?: { /** boolean flag to indicate whether the image has been scaled */ - scaled: boolean; + scaled?: boolean; /** scaling parameters */ - scalingParameters: { + scalingParameters?: { /** modality of the image */ modality?: string; /** rescale slop */ @@ -42,7 +50,7 @@ interface IImage { /** voiLUTFunction from metadata */ voiLUTFunction: string; /** function that returns the pixelData as an array */ - getPixelData: () => Array; + getPixelData: () => PixelDataTypedArray; getCanvas: () => HTMLCanvasElement; /** image number of rows */ rows: number; diff --git a/packages/core/src/types/IViewport.ts b/packages/core/src/types/IViewport.ts index c50729ba7..efdac799e 100644 --- a/packages/core/src/types/IViewport.ts +++ b/packages/core/src/types/IViewport.ts @@ -101,6 +101,7 @@ interface IViewport { /** whether the viewport has custom rendering */ customRenderViewportToCanvas: () => unknown; _getCorners(bounds: Array): Array[]; + updateRenderingPipeline: () => void; } /** diff --git a/packages/core/src/utilities/loadImageToCanvas.ts b/packages/core/src/utilities/loadImageToCanvas.ts index 65d657f57..099aa10f7 100644 --- a/packages/core/src/utilities/loadImageToCanvas.ts +++ b/packages/core/src/utilities/loadImageToCanvas.ts @@ -64,6 +64,7 @@ export default function loadImageToCanvas( targetBuffer: { type: useNorm16Texture ? undefined : 'Float32Array', }, + useRGBA: true, preScale: { enabled: true, }, diff --git a/packages/dicomImageLoader/.webpack/webpack-base.js b/packages/dicomImageLoader/.webpack/webpack-base.js index 2baf234af..aa8b86c19 100644 --- a/packages/dicomImageLoader/.webpack/webpack-base.js +++ b/packages/dicomImageLoader/.webpack/webpack-base.js @@ -25,6 +25,7 @@ module.exports = { globalObject: 'this', path: outputPath, publicPath: 'auto', + clean: true, }, devtool: 'source-map', externals: { @@ -54,6 +55,9 @@ module.exports = { use: [ { loader: 'worker-loader', + options: { + filename: '[name].[contenthash].worker.js', + }, }, // { // loader: 'babel-loader', diff --git a/packages/dicomImageLoader/.webpack/webpack-dynamic-import-debug.js b/packages/dicomImageLoader/.webpack/webpack-dynamic-import-debug.js index ef40bb8a9..4b2af9451 100644 --- a/packages/dicomImageLoader/.webpack/webpack-dynamic-import-debug.js +++ b/packages/dicomImageLoader/.webpack/webpack-dynamic-import-debug.js @@ -9,6 +9,7 @@ const prodConfig = { stats: { children: true, }, + devtool: 'eval-source-map', output: { /*library: { //name: '[name]', @@ -16,7 +17,8 @@ const prodConfig = { path: outputPath, libraryTarget: 'umd', globalObject: 'this', - filename: '[name].min.js', + filename: '[name].js', + clean: true, }, optimization: { minimize: false, diff --git a/packages/dicomImageLoader/.webpack/webpack-dynamic-import.js b/packages/dicomImageLoader/.webpack/webpack-dynamic-import.js index bf71a664c..c4aabd1a1 100644 --- a/packages/dicomImageLoader/.webpack/webpack-dynamic-import.js +++ b/packages/dicomImageLoader/.webpack/webpack-dynamic-import.js @@ -18,6 +18,7 @@ const prodConfig = { libraryTarget: 'umd', globalObject: 'this', filename: '[name].min.js', + clean: true, }, optimization: { // minimize: false, diff --git a/packages/dicomImageLoader/.webpack/webpack-esm.js b/packages/dicomImageLoader/.webpack/webpack-esm.js index de2d22325..8ab2113d5 100644 --- a/packages/dicomImageLoader/.webpack/webpack-esm.js +++ b/packages/dicomImageLoader/.webpack/webpack-esm.js @@ -22,6 +22,7 @@ const config = { type: 'module', }, module: true, + clean: true, //libraryTarget: 'module', //globalObject: 'this', //chunkFormat: 'module', diff --git a/packages/dicomImageLoader/README.md b/packages/dicomImageLoader/README.md index 37278c9b9..f7bff3037 100644 --- a/packages/dicomImageLoader/README.md +++ b/packages/dicomImageLoader/README.md @@ -1,9 +1,170 @@ # DICOM Image Loader This package provides a DICOM image loader for the Cornerstone library. -This is the successor to the [cornerstoneWADOImageLoader] which provides the following -benefits: +This is the successor to the [cornerstoneDICOMImageLoader] which provides the following +added features: - Typescript support (and type definitions) - Better developer experience (e.g. mono repo, linting, etc) -- In Future: better handling of web worker loading + + + +A [cornerstone](https://github.com/cornerstonejs/cornerstone) Image Loader for +DICOM P10 instances over HTTP (WADO-URI) or DICOMWeb (WADO-RS) and Local filedisk. This can be used +to integrate cornerstone with WADO-URI servers, DICOMWeb servers or any other +HTTP based server that returns DICOM P10 instances (e.g. +[Orthanc](http://www.orthanc-server.com/) or custom servers) + +## Key Features + +- Implements a + [cornerstone ImageLoader](https://www.cornerstonejs.org/docs/concepts/cornerstone-core/imageLoader) + for DICOM P10 Instances via a HTTP get request. + - Can be used with a WADO-URI server + - Can be used with Orthanc's file endpoint + - Can be used with any server that returns DICOM P10 instances via HTTP GET +- Implements a + [cornerstone ImageLoader](https://www.cornerstonejs.org/docs/concepts/cornerstone-core/imageLoader) + for WADO-RS (DICOMWeb) +- Supports many popular transfer syntaxes and photometric interpretations + [see full list](docs/TransferSyntaxes.md) + and [codec](docs/Codecs.md) for more information. +- Dynamicly Utilizes WebAssembly (WASM) builds of each codec which sgnificantly improves image decoding performance and enables us to load codec at runtime when needed dynamically, which reduces the build time and complexity. +- Framework to execute CPU intensive tasks in [web workers](docs/WebWorkers.md) + - Used for image decoding + - Can be used for your own CPU intensive tasks (e.g. image processing) + + +## Install + +### NPM + +```bash +yarn add @cornerstonejs/dicom-image-loader +``` + + + +## Usage + +Specify the cornerstone instance you want to register the loader with. + +```javascript +cornerstoneDICOMImageLoader.external.cornerstone = cornerstone; +``` + +Have your code configure the web worker framework: + +```javascript +var config = { + maxWebWorkers: navigator.hardwareConcurrency || 1, + startWebWorkersOnDemand: true, +}; +cornerstoneDICOMImageLoader.webWorkerManager.initialize(config); +``` + +See the [web workers](docs/WebWorkers.md) documentation for more details on +configuring. + + + +#### Dynamic Import + +To be able to use the dynamic import feature for CDIL, instead of + +```js +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; +``` + +you need to do: + +```js +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js'; +``` + +This way, codecs are loaded dynamically when needed. You have another option to +create an alias in the webpack config file: + +```js +resolve: { + alias: { + '@cornerstonejs/dicom-image-loader': + '@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js', + }, +}, +``` + +In addition WASM builds of the codec files should be made available in the build +folder. You can use `CopyWebpackPlugin` to copy the WASM files to the build folder. + + +```js + plugins: [ + new CopyWebpackPlugin([ + { + from: + '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import', + to: DIST_DIR, + }, + ]), +``` + +Note 1: You need to give the correct path in the `CopyWebpackPlugin`, the above +path is relative to the `node_modules` folder in the OHIF Viewer. + +Note 2: For other http servers like IIS, you need to configure it to serve WASM +files with the correct MIME type. + + +## Backlog + +- ESM build for the library +- Make the examples work again +- Free up DICOM P10 instance after decoding to reduce memory consumption +- Look at using EMSCRIPTEN based build of IJG for JPEG +- Add support for bulk data items to WADO-RS Loader +- WebWorker Manager + - Better handling of web worker loading + - Add events to webWorkerManager so its activity can be monitored + - Add support for issuing progress events from web worker tasks + +# FAQ + +_Why is this a separate library from cornerstone?_ + +Mainly to avoid adding a dependency to cornerstone for the DICOM parsing +library. While cornerstone is intended to be used to display medical images that +are stored in DICOM, cornerstone aims to simplify the use of medical imaging and +therefore tries to hide some of the complexity that exists within DICOM. It is +also desirable to support display of non DICOM images so a DICOM independent +image model makes sense. + +_How do I build this library myself?_ + +See the documentation [here](docs/Building.md) + +_How do I add my own custom web worker tasks?_ + +See the documentation [here](docs/WebWorkers.md) + +_How do I create imageIds that work with this image loader?_ + +See the documentation [here](docs/ImageIds.md) + +# What Transfer Syntaxes are supported? + +See [transfer syntaxes](docs/TransferSyntaxes.md) + + +[license-image]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat +[license-url]: LICENSE + +[npm-url]: https://npmjs.org/package/@cornerstonejs/dicom-image-loader +[npm-version-image]: http://img.shields.io/npm/v/@cornerstonejs/dicom-image-loader.svg?style=flat +[npm-downloads-image]: http://img.shields.io/npm/dm/@cornerstonejs/dicom-image-loader.svg?style=flat + +[travis-url]: http://travis-ci.org/cornerstonejs/cornerstoneDICOMImageLoader +[travis-image]: https://travis-ci.org/cornerstonejs/cornerstoneDICOMImageLoader.svg?branch=master + +[coverage-url]: https://coveralls.io/github/cornerstonejs/cornerstoneDICOMImageLoader?branch=master +[coverage-image]: https://coveralls.io/repos/github/cornerstonejs/cornerstoneDICOMImageLoader/badge.svg?branch=master diff --git a/packages/dicomImageLoader/examples/dicomImageLoaderWADOURI/index.ts b/packages/dicomImageLoader/examples/dicomImageLoaderWADOURI/index.ts new file mode 100644 index 000000000..990c1bab8 --- /dev/null +++ b/packages/dicomImageLoader/examples/dicomImageLoaderWADOURI/index.ts @@ -0,0 +1,263 @@ +import htmlStr from './layout'; +import { + RenderingEngine, + Types, + Enums, + setUseCPURendering, + setPreferSizeOverAccuracy, +} from '@cornerstonejs/core'; +import * as cornerstoneTools from '@cornerstonejs/tools'; +import uids from '../uids'; +const { + PanTool, + WindowLevelTool, + StackScrollMouseWheelTool, + ZoomTool, + ToolGroupManager, + Enums: csToolsEnums, +} = cornerstoneTools; + +const { MouseBindings } = csToolsEnums; + +import { + addToggleButtonToToolbar, + initDemo, + setTitleAndDescription, +} from '../../../../utils/demo/helpers'; + +// This is for debugging purposes +console.warn( + 'Click on index.ts to open source code for this example --------->' +); + +// add to the script tag +const div = document.createElement('div'); +div.innerHTML = htmlStr; +document.getElementById('content').appendChild(div); + +setTitleAndDescription( + 'Example of displaying a DICOM P10 image using Cornerstone DICOM Image Loader', + 'You can toggle different settings as using CPU or GPU rendering, using preferSizeOverAccuracy setting' +); + +addToggleButtonToToolbar({ + title: 'Toggle CPU Rendering', + defaultToggle: false, + onClick(toggle) { + toggle ? setUseCPURendering(true) : setUseCPURendering(false); + }, +}); + +addToggleButtonToToolbar({ + title: 'Toggle Prefer Size Over Accuracy', + defaultToggle: false, + onClick(toggle) { + toggle ? setPreferSizeOverAccuracy(true) : setPreferSizeOverAccuracy(false); + }, +}); + +const { ViewportType } = Enums; +const element = document.querySelector( + '#cornerstone-element' +) as HTMLDivElement; + +const toolGroupId = 'myToolGroup'; +let viewport; + +async function run() { + // Init Cornerstone and related libraries + await initDemo(); + + cornerstoneTools.addTool(PanTool); + cornerstoneTools.addTool(WindowLevelTool); + cornerstoneTools.addTool(StackScrollMouseWheelTool); + cornerstoneTools.addTool(ZoomTool); + + // Define a tool group, which defines how mouse events map to tool commands for + // Any viewport using the group + const toolGroup = ToolGroupManager.createToolGroup(toolGroupId); + + // Add tools to the tool group + toolGroup.addTool(WindowLevelTool.toolName); + toolGroup.addTool(PanTool.toolName); + toolGroup.addTool(ZoomTool.toolName); + toolGroup.addTool(StackScrollMouseWheelTool.toolName); + + // Set the initial state of the tools, here all tools are active and bound to + // Different mouse inputs + toolGroup.setToolActive(WindowLevelTool.toolName, { + bindings: [ + { + mouseButton: MouseBindings.Primary, // Left Click + }, + ], + }); + toolGroup.setToolActive(PanTool.toolName, { + bindings: [ + { + mouseButton: MouseBindings.Auxiliary, // Middle Click + }, + ], + }); + toolGroup.setToolActive(ZoomTool.toolName, { + bindings: [ + { + mouseButton: MouseBindings.Secondary, // Right Click + }, + ], + }); + // As the Stack Scroll mouse wheel is a tool using the `mouseWheelCallback` + // hook instead of mouse buttons, it does not need to assign any mouse button. + toolGroup.setToolActive(StackScrollMouseWheelTool.toolName); + + // Get Cornerstone imageIds and fetch metadata into RAM + + // Instantiate a rendering engine + const renderingEngineId = 'myRenderingEngine'; + const renderingEngine = new RenderingEngine(renderingEngineId); + + // Create a stack viewport + const viewportId = 'CT_STACK'; + const viewportInput = { + viewportId, + type: ViewportType.STACK, + element, + }; + + renderingEngine.enableElement(viewportInput); + + // Get the stack viewport that was created + viewport = renderingEngine.getViewport(viewportId); + + toolGroup.addViewport(viewportId, renderingEngineId); +} + +async function loadAndViewImage(imageId) { + // Set the stack on the viewport + const start = new Date().getTime(); + viewport.setStack([imageId]).then( + () => { + // Set the VOI of the stack + // viewport.setProperties({ voiRange: ctVoiRange }); + // Render the image + viewport.render(); + + const image = viewport.csImage; + + function getTransferSyntax() { + const value = image.data.string('x00020010'); + return value + ' [' + uids[value] + ']'; + } + + function getSopClass() { + const value = image.data.string('x00080016'); + return value + ' [' + uids[value] + ']'; + } + + function getPixelRepresentation() { + const value = image.data.uint16('x00280103'); + if (value === undefined) { + return; + } + return value + (value === 0 ? ' (unsigned)' : ' (signed)'); + } + + function getPlanarConfiguration() { + const value = image.data.uint16('x00280006'); + if (value === undefined) { + return; + } + return value + (value === 0 ? ' (pixel)' : ' (plane)'); + } + + document.getElementById('transferSyntax').textContent = + getTransferSyntax(); + document.getElementById('sopClass').textContent = getSopClass(); + document.getElementById('samplesPerPixel').textContent = + image.data.uint16('x00280002'); + document.getElementById('photometricInterpretation').textContent = + image.data.string('x00280004'); + document.getElementById('numberOfFrames').textContent = + image.data.string('x00280008'); + document.getElementById('planarConfiguration').textContent = + getPlanarConfiguration(); + document.getElementById('rows').textContent = + image.data.uint16('x00280010'); + document.getElementById('columns').textContent = + image.data.uint16('x00280011'); + document.getElementById('pixelSpacing').textContent = + image.data.string('x00280030'); + document.getElementById('rowPixelSpacing').textContent = + image.rowPixelSpacing; + document.getElementById('columnPixelSpacing').textContent = + image.columnPixelSpacing; + document.getElementById('bitsAllocated').textContent = + image.data.uint16('x00280100'); + document.getElementById('bitsStored').textContent = + image.data.uint16('x00280101'); + document.getElementById('highBit').textContent = + image.data.uint16('x00280102'); + document.getElementById('pixelRepresentation').textContent = + getPixelRepresentation(); + document.getElementById('windowCenter').textContent = + image.data.string('x00281050'); + document.getElementById('windowWidth').textContent = + image.data.string('x00281051'); + document.getElementById('rescaleIntercept').textContent = + image.data.string('x00281052'); + document.getElementById('rescaleSlope').textContent = + image.data.string('x00281053'); + document.getElementById('basicOffsetTable').textContent = image.data + .elements.x7fe00010.basicOffsetTable + ? image.data.elements.x7fe00010.basicOffsetTable.length + : ''; + document.getElementById('fragments').textContent = image.data.elements + .x7fe00010.fragments + ? image.data.elements.x7fe00010.fragments.length + : ''; + document.getElementById('minStoredPixelValue').textContent = + image.minPixelValue; + document.getElementById('maxStoredPixelValue').textContent = + image.maxPixelValue; + const end = new Date().getTime(); + const time = end - start; + document.getElementById('totalTime').textContent = time + 'ms'; + document.getElementById('loadTime').textContent = + image.loadTimeInMS + 'ms'; + document.getElementById('decodeTime').textContent = + image.decodeTimeInMS + 'ms'; + }, + function (err) { + throw err; + } + ); +} + +function downloadAndView(downloadUrl) { + let url = downloadUrl || document.getElementById('wadoURL').value; + + // prefix the url with wadouri: so cornerstone can find the image loader + url = 'wadouri:' + url; + + // image enable the dicomImage element and activate a few tools + loadAndViewImage(url); +} + +function handleImageSelection(event) { + const selectedFile = event.target.value; + console.log('Selected file:', selectedFile); + + if (selectedFile) { + downloadAndView( + 'https://raw.githubusercontent.com/cornerstonejs/cornerstone3D-beta/main/packages/dicomImageLoader/testImages/' + + selectedFile + ); + } +} + +document.addEventListener('DOMContentLoaded', function () { + const imageSelector = document.getElementById('imageSelector'); + imageSelector.addEventListener('change', handleImageSelection); +}); + +run(); diff --git a/packages/dicomImageLoader/examples/dicomImageLoaderWADOURI/layout.ts b/packages/dicomImageLoader/examples/dicomImageLoaderWADOURI/layout.ts new file mode 100644 index 000000000..ba3e1bfd1 --- /dev/null +++ b/packages/dicomImageLoader/examples/dicomImageLoaderWADOURI/layout.ts @@ -0,0 +1,89 @@ +const html = ` + + +
+ + +
+
+
+
+
+
+
+
+
+
+ Transfer Syntax:
+ SOP Class:
+ Samples Per Pixel:
+ Photometric Interpretation:
+ Number Of Frames:
+ Planar Configuration:
+ Rows:
+ Columns:
+ Pixel Spacing:
+ Row Pixel Spacing:
+ Column Pixel Spacing:
+ Bits Allocated:
+ Bits Stored:
+ High Bit:
+ Pixel Representation:
+ WindowCenter:
+ WindowWidth:
+ RescaleIntercept:
+ RescaleSlope:
+ Basic Offset Table Entries:
+ Fragments:
+ Max Stored Pixel Value:
+ Min Stored Pixel Value:
+ Total Time:
+ Load Time:
+ Decode Time:
+
+
+
+`; + +export default html; diff --git a/packages/dicomImageLoader/examples/uids.ts b/packages/dicomImageLoader/examples/uids.ts new file mode 100644 index 000000000..2fe7d1551 --- /dev/null +++ b/packages/dicomImageLoader/examples/uids.ts @@ -0,0 +1,477 @@ +// The table below was generated by running the following javascript in the console window while +// viewing the HTML version of the standard here: +// DICOM Standard here: http://dicom.nema.org/medical/dicom/current/output/html/part06.html +// see the blog post about it here: http://chafey.blogspot.com/2015/08/parsing-dicom-standard-with-javascript.html + +/* + (function () { + var elements = document.querySelectorAll('#table_A-1 ~ div tbody tr'); + var result = "";for(var i=0; i < elements.length; i++) { + result += "'" + elements[i].childNodes[1].childNodes[1].innerText.replace(/[^\x20-\x7E]+/g, '') + "':'" + + elements[i].childNodes[3].childNodes[1].innerText.replace(/[^\x20-\x7E]+/g, '') + "',\n"; + } + return result; + })(); + */ + +const UIDs = { + '1.2.840.10008.1.1': 'Verification SOP Class', + '1.2.840.10008.1.2': + 'Implicit VR Little Endian: Default Transfer Syntax for DICOM', + '1.2.840.10008.1.2.1': 'Explicit VR Little Endian', + '1.2.840.10008.1.2.1.99': 'Deflated Explicit VR Little Endian', + '1.2.840.10008.1.2.2': 'Explicit VR Big Endian (Retired)', + '1.2.840.10008.1.2.4.50': + 'JPEG Baseline (Process 1): Default Transfer Syntax for Lossy JPEG 8 Bit Image Compression', + '1.2.840.10008.1.2.4.51': + 'JPEG Extended (Process 2 & 4): Default Transfer Syntax for Lossy JPEG 12 Bit Image Compression (Process 4 only)', + '1.2.840.10008.1.2.4.52': 'JPEG Extended (Process 3 & 5) (Retired)', + '1.2.840.10008.1.2.4.53': + 'JPEG Spectral Selection, Non-Hierarchical (Process 6 & 8) (Retired)', + '1.2.840.10008.1.2.4.54': + 'JPEG Spectral Selection, Non-Hierarchical (Process 7 & 9) (Retired)', + '1.2.840.10008.1.2.4.55': + 'JPEG Full Progression, Non-Hierarchical (Process 10 & 12) (Retired)', + '1.2.840.10008.1.2.4.56': + 'JPEG Full Progression, Non-Hierarchical (Process 11 & 13) (Retired)', + '1.2.840.10008.1.2.4.57': 'JPEG Lossless, Non-Hierarchical (Process 14)', + '1.2.840.10008.1.2.4.58': + 'JPEG Lossless, Non-Hierarchical (Process 15) (Retired)', + '1.2.840.10008.1.2.4.59': + 'JPEG Extended, Hierarchical (Process 16 & 18) (Retired)', + '1.2.840.10008.1.2.4.60': + 'JPEG Extended, Hierarchical (Process 17 & 19) (Retired)', + '1.2.840.10008.1.2.4.61': + 'JPEG Spectral Selection, Hierarchical (Process 20 & 22) (Retired)', + '1.2.840.10008.1.2.4.62': + 'JPEG Spectral Selection, Hierarchical (Process 21 & 23) (Retired)', + '1.2.840.10008.1.2.4.63': + 'JPEG Full Progression, Hierarchical (Process 24 & 26) (Retired)', + '1.2.840.10008.1.2.4.64': + 'JPEG Full Progression, Hierarchical (Process 25 & 27) (Retired)', + '1.2.840.10008.1.2.4.65': + 'JPEG Lossless, Hierarchical (Process 28) (Retired)', + '1.2.840.10008.1.2.4.66': + 'JPEG Lossless, Hierarchical (Process 29) (Retired)', + '1.2.840.10008.1.2.4.70': + 'JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1]): Default Transfer Syntax for Lossless JPEG Image Compression', + '1.2.840.10008.1.2.4.80': 'JPEG-LS Lossless Image Compression', + '1.2.840.10008.1.2.4.81': 'JPEG-LS Lossy (Near-Lossless) Image Compression', + '1.2.840.10008.1.2.4.90': 'JPEG 2000 Image Compression (Lossless Only)', + '1.2.840.10008.1.2.4.91': 'JPEG 2000 Image Compression', + '1.2.840.10008.1.2.4.92': + 'JPEG 2000 Part 2 Multi-component Image Compression (Lossless Only)', + '1.2.840.10008.1.2.4.93': + 'JPEG 2000 Part 2 Multi-component Image Compression', + '1.2.840.10008.1.2.4.94': 'JPIP Referenced', + '1.2.840.10008.1.2.4.95': 'JPIP Referenced Deflate', + '1.2.840.10008.1.2.4.100': 'MPEG2 Main Profile @ Main Level', + '1.2.840.10008.1.2.4.101': 'MPEG2 Main Profile @ High Level', + '1.2.840.10008.1.2.4.102': 'MPEG-4 AVC/H.264 High Profile / Level 4.1', + '1.2.840.10008.1.2.4.103': + 'MPEG-4 AVC/H.264 BD-compatible High Profile / Level 4.1', + '1.2.840.10008.1.2.4.104': + 'MPEG-4 AVC/H.264 High Profile / Level 4.2 For 2D Video', + '1.2.840.10008.1.2.4.105': + 'MPEG-4 AVC/H.264 High Profile / Level 4.2 For 3D Video', + '1.2.840.10008.1.2.4.106': 'MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2', + '1.2.840.10008.1.2.5': 'RLE Lossless', + '1.2.840.10008.1.2.6.1': 'RFC 2557 MIME encapsulation', + '1.2.840.10008.1.2.6.2': 'XML Encoding', + '1.2.840.10008.1.3.10': 'Media Storage Directory Storage', + '1.2.840.10008.1.4.1.1': 'Talairach Brain Atlas Frame of Reference', + '1.2.840.10008.1.4.1.2': 'SPM2 T1 Frame of Reference', + '1.2.840.10008.1.4.1.3': 'SPM2 T2 Frame of Reference', + '1.2.840.10008.1.4.1.4': 'SPM2 PD Frame of Reference', + '1.2.840.10008.1.4.1.5': 'SPM2 EPI Frame of Reference', + '1.2.840.10008.1.4.1.6': 'SPM2 FIL T1 Frame of Reference', + '1.2.840.10008.1.4.1.7': 'SPM2 PET Frame of Reference', + '1.2.840.10008.1.4.1.8': 'SPM2 TRANSM Frame of Reference', + '1.2.840.10008.1.4.1.9': 'SPM2 SPECT Frame of Reference', + '1.2.840.10008.1.4.1.10': 'SPM2 GRAY Frame of Reference', + '1.2.840.10008.1.4.1.11': 'SPM2 WHITE Frame of Reference', + '1.2.840.10008.1.4.1.12': 'SPM2 CSF Frame of Reference', + '1.2.840.10008.1.4.1.13': 'SPM2 BRAINMASK Frame of Reference', + '1.2.840.10008.1.4.1.14': 'SPM2 AVG305T1 Frame of Reference', + '1.2.840.10008.1.4.1.15': 'SPM2 AVG152T1 Frame of Reference', + '1.2.840.10008.1.4.1.16': 'SPM2 AVG152T2 Frame of Reference', + '1.2.840.10008.1.4.1.17': 'SPM2 AVG152PD Frame of Reference', + '1.2.840.10008.1.4.1.18': 'SPM2 SINGLESUBJT1 Frame of Reference', + '1.2.840.10008.1.4.2.1': 'ICBM 452 T1 Frame of Reference', + '1.2.840.10008.1.4.2.2': 'ICBM Single Subject MRI Frame of Reference', + '1.2.840.10008.1.5.1': 'Hot Iron Color Palette SOP Instance', + '1.2.840.10008.1.5.2': 'PET Color Palette SOP Instance', + '1.2.840.10008.1.5.3': 'Hot Metal Blue Color Palette SOP Instance', + '1.2.840.10008.1.5.4': 'PET 20 Step Color Palette SOP Instance', + '1.2.840.10008.1.9': 'Basic Study Content Notification SOP Class (Retired)', + '1.2.840.10008.1.20.1': 'Storage Commitment Push Model SOP Class', + '1.2.840.10008.1.20.1.1': 'Storage Commitment Push Model SOP Instance', + '1.2.840.10008.1.20.2': 'Storage Commitment Pull Model SOP Class (Retired)', + '1.2.840.10008.1.20.2.1': + 'Storage Commitment Pull Model SOP Instance (Retired)', + '1.2.840.10008.1.40': 'Procedural Event Logging SOP Class', + '1.2.840.10008.1.40.1': 'Procedural Event Logging SOP Instance', + '1.2.840.10008.1.42': 'Substance Administration Logging SOP Class', + '1.2.840.10008.1.42.1': 'Substance Administration Logging SOP Instance', + '1.2.840.10008.2.6.1': 'DICOM UID Registry', + '1.2.840.10008.2.16.4': 'DICOM Controlled Terminology', + '1.2.840.10008.3.1.1.1': 'DICOM Application Context Name', + '1.2.840.10008.3.1.2.1.1': 'Detached Patient Management SOP Class (Retired)', + '1.2.840.10008.3.1.2.1.4': + 'Detached Patient Management Meta SOP Class (Retired)', + '1.2.840.10008.3.1.2.2.1': 'Detached Visit Management SOP Class (Retired)', + '1.2.840.10008.3.1.2.3.1': 'Detached Study Management SOP Class (Retired)', + '1.2.840.10008.3.1.2.3.2': 'Study Component Management SOP Class (Retired)', + '1.2.840.10008.3.1.2.3.3': 'Modality Performed Procedure Step SOP Class', + '1.2.840.10008.3.1.2.3.4': + 'Modality Performed Procedure Step Retrieve SOP Class', + '1.2.840.10008.3.1.2.3.5': + 'Modality Performed Procedure Step Notification SOP Class', + '1.2.840.10008.3.1.2.5.1': 'Detached Results Management SOP Class (Retired)', + '1.2.840.10008.3.1.2.5.4': + 'Detached Results Management Meta SOP Class (Retired)', + '1.2.840.10008.3.1.2.5.5': + 'Detached Study Management Meta SOP Class (Retired)', + '1.2.840.10008.3.1.2.6.1': + 'Detached Interpretation Management SOP Class (Retired)', + '1.2.840.10008.4.2': 'Storage Service Class', + '1.2.840.10008.5.1.1.1': 'Basic Film Session SOP Class', + '1.2.840.10008.5.1.1.2': 'Basic Film Box SOP Class', + '1.2.840.10008.5.1.1.4': 'Basic Grayscale Image Box SOP Class', + '1.2.840.10008.5.1.1.4.1': 'Basic Color Image Box SOP Class', + '1.2.840.10008.5.1.1.4.2': 'Referenced Image Box SOP Class (Retired)', + '1.2.840.10008.5.1.1.9': 'Basic Grayscale Print Management Meta SOP Class', + '1.2.840.10008.5.1.1.9.1': + 'Referenced Grayscale Print Management Meta SOP Class (Retired)', + '1.2.840.10008.5.1.1.14': 'Print Job SOP Class', + '1.2.840.10008.5.1.1.15': 'Basic Annotation Box SOP Class', + '1.2.840.10008.5.1.1.16': 'Printer SOP Class', + '1.2.840.10008.5.1.1.16.376': 'Printer Configuration Retrieval SOP Class', + '1.2.840.10008.5.1.1.17': 'Printer SOP Instance', + '1.2.840.10008.5.1.1.17.376': 'Printer Configuration Retrieval SOP Instance', + '1.2.840.10008.5.1.1.18': 'Basic Color Print Management Meta SOP Class', + '1.2.840.10008.5.1.1.18.1': + 'Referenced Color Print Management Meta SOP Class (Retired)', + '1.2.840.10008.5.1.1.22': 'VOI LUT Box SOP Class', + '1.2.840.10008.5.1.1.23': 'Presentation LUT SOP Class', + '1.2.840.10008.5.1.1.24': 'Image Overlay Box SOP Class (Retired)', + '1.2.840.10008.5.1.1.24.1': + 'Basic Print Image Overlay Box SOP Class (Retired)', + '1.2.840.10008.5.1.1.25': 'Print Queue SOP Instance (Retired)', + '1.2.840.10008.5.1.1.26': 'Print Queue Management SOP Class (Retired)', + '1.2.840.10008.5.1.1.27': 'Stored Print Storage SOP Class (Retired)', + '1.2.840.10008.5.1.1.29': + 'Hardcopy Grayscale Image Storage SOP Class (Retired)', + '1.2.840.10008.5.1.1.30': 'Hardcopy Color Image Storage SOP Class (Retired)', + '1.2.840.10008.5.1.1.31': 'Pull Print Request SOP Class (Retired)', + '1.2.840.10008.5.1.1.32': + 'Pull Stored Print Management Meta SOP Class (Retired)', + '1.2.840.10008.5.1.1.33': 'Media Creation Management SOP Class UID', + '1.2.840.10008.5.1.1.40': 'Display System SOP Class', + '1.2.840.10008.5.1.1.40.1': 'Display System SOP Instance', + '1.2.840.10008.5.1.4.1.1.1': 'Computed Radiography Image Storage', + '1.2.840.10008.5.1.4.1.1.1.1': + 'Digital X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.1.1.1': + 'Digital X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.1.2': + 'Digital Mammography X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.1.2.1': + 'Digital Mammography X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.1.3': + 'Digital Intra-Oral X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.1.3.1': + 'Digital Intra-Oral X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.2': 'CT Image Storage', + '1.2.840.10008.5.1.4.1.1.2.1': 'Enhanced CT Image Storage', + '1.2.840.10008.5.1.4.1.1.2.2': 'Legacy Converted Enhanced CT Image Storage', + '1.2.840.10008.5.1.4.1.1.3': 'Ultrasound Multi-frame Image Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.3.1': 'Ultrasound Multi-frame Image Storage', + '1.2.840.10008.5.1.4.1.1.4': 'MR Image Storage', + '1.2.840.10008.5.1.4.1.1.4.1': 'Enhanced MR Image Storage', + '1.2.840.10008.5.1.4.1.1.4.2': 'MR Spectroscopy Storage', + '1.2.840.10008.5.1.4.1.1.4.3': 'Enhanced MR Color Image Storage', + '1.2.840.10008.5.1.4.1.1.4.4': 'Legacy Converted Enhanced MR Image Storage', + '1.2.840.10008.5.1.4.1.1.5': 'Nuclear Medicine Image Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.6': 'Ultrasound Image Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.6.1': 'Ultrasound Image Storage', + '1.2.840.10008.5.1.4.1.1.6.2': 'Enhanced US Volume Storage', + '1.2.840.10008.5.1.4.1.1.7': 'Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.7.1': + 'Multi-frame Single Bit Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.7.2': + 'Multi-frame Grayscale Byte Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.7.3': + 'Multi-frame Grayscale Word Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.7.4': + 'Multi-frame True Color Secondary Capture Image Storage', + '1.2.840.10008.5.1.4.1.1.8': 'Standalone Overlay Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.9': 'Standalone Curve Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.9.1': 'Waveform Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.9.1.1': '12-lead ECG Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.1.2': 'General ECG Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.1.3': 'Ambulatory ECG Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.2.1': 'Hemodynamic Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.3.1': 'Cardiac Electrophysiology Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.4.1': 'Basic Voice Audio Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.4.2': 'General Audio Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.5.1': 'Arterial Pulse Waveform Storage', + '1.2.840.10008.5.1.4.1.1.9.6.1': 'Respiratory Waveform Storage', + '1.2.840.10008.5.1.4.1.1.10': 'Standalone Modality LUT Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.11': 'Standalone VOI LUT Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.11.1': + 'Grayscale Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.11.2': + 'Color Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.11.3': + 'Pseudo-Color Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.11.4': + 'Blending Softcopy Presentation State Storage SOP Class', + '1.2.840.10008.5.1.4.1.1.11.5': + 'XA/XRF Grayscale Softcopy Presentation State Storage', + '1.2.840.10008.5.1.4.1.1.12.1': 'X-Ray Angiographic Image Storage', + '1.2.840.10008.5.1.4.1.1.12.1.1': 'Enhanced XA Image Storage', + '1.2.840.10008.5.1.4.1.1.12.2': 'X-Ray Radiofluoroscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.12.2.1': 'Enhanced XRF Image Storage', + '1.2.840.10008.5.1.4.1.1.12.3': + 'X-Ray Angiographic Bi-Plane Image Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.13.1.1': 'X-Ray 3D Angiographic Image Storage', + '1.2.840.10008.5.1.4.1.1.13.1.2': 'X-Ray 3D Craniofacial Image Storage', + '1.2.840.10008.5.1.4.1.1.13.1.3': 'Breast Tomosynthesis Image Storage', + '1.2.840.10008.5.1.4.1.1.13.1.4': + 'Breast Projection X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.13.1.5': + 'Breast Projection X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.14.1': + 'Intravascular Optical Coherence Tomography Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.14.2': + 'Intravascular Optical Coherence Tomography Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.20': 'Nuclear Medicine Image Storage', + '1.2.840.10008.5.1.4.1.1.30': 'Parametric Map Storage', + '1.2.840.10008.5.1.4.1.1.66': 'Raw Data Storage', + '1.2.840.10008.5.1.4.1.1.66.1': 'Spatial Registration Storage', + '1.2.840.10008.5.1.4.1.1.66.2': 'Spatial Fiducials Storage', + '1.2.840.10008.5.1.4.1.1.66.3': 'Deformable Spatial Registration Storage', + '1.2.840.10008.5.1.4.1.1.66.4': 'Segmentation Storage', + '1.2.840.10008.5.1.4.1.1.66.5': 'Surface Segmentation Storage', + '1.2.840.10008.5.1.4.1.1.67': 'Real World Value Mapping Storage', + '1.2.840.10008.5.1.4.1.1.68.1': 'Surface Scan Mesh Storage', + '1.2.840.10008.5.1.4.1.1.68.2': 'Surface Scan Point Cloud Storage', + '1.2.840.10008.5.1.4.1.1.77.1': 'VL Image Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.77.2': + 'VL Multi-frame Image Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.77.1.1': 'VL Endoscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.1.1': 'Video Endoscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.2': 'VL Microscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.2.1': 'Video Microscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.3': + 'VL Slide-Coordinates Microscopic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.4': 'VL Photographic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.4.1': 'Video Photographic Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.1': + 'Ophthalmic Photography 8 Bit Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.2': + 'Ophthalmic Photography 16 Bit Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.3': 'Stereometric Relationship Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.4': 'Ophthalmic Tomography Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.5': + 'Wide Field Ophthalmic Photography Stereographic Projection Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.5.6': + 'Wide Field Ophthalmic Photography 3D Coordinates Image Storage', + '1.2.840.10008.5.1.4.1.1.77.1.6': 'VL Whole Slide Microscopy Image Storage', + '1.2.840.10008.5.1.4.1.1.78.1': 'Lensometry Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.2': 'Autorefraction Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.3': 'Keratometry Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.4': 'Subjective Refraction Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.5': 'Visual Acuity Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.6': 'Spectacle Prescription Report Storage', + '1.2.840.10008.5.1.4.1.1.78.7': 'Ophthalmic Axial Measurements Storage', + '1.2.840.10008.5.1.4.1.1.78.8': 'Intraocular Lens Calculations Storage', + '1.2.840.10008.5.1.4.1.1.79.1': + 'Macular Grid Thickness and Volume Report Storage', + '1.2.840.10008.5.1.4.1.1.80.1': + 'Ophthalmic Visual Field Static Perimetry Measurements Storage', + '1.2.840.10008.5.1.4.1.1.81.1': 'Ophthalmic Thickness Map Storage', + '1.2.840.10008.5.1.4.1.1.82.1': 'Corneal Topography Map Storage', + '1.2.840.10008.5.1.4.1.1.88.1': 'Text SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.2': 'Audio SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.3': 'Detail SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.4': 'Comprehensive SR Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.1.1.88.11': 'Basic Text SR Storage', + '1.2.840.10008.5.1.4.1.1.88.22': 'Enhanced SR Storage', + '1.2.840.10008.5.1.4.1.1.88.33': 'Comprehensive SR Storage', + '1.2.840.10008.5.1.4.1.1.88.34': 'Comprehensive 3D SR Storage', + '1.2.840.10008.5.1.4.1.1.88.35': 'Extensible SR Storage', + '1.2.840.10008.5.1.4.1.1.88.40': 'Procedure Log Storage', + '1.2.840.10008.5.1.4.1.1.88.50': 'Mammography CAD SR Storage', + '1.2.840.10008.5.1.4.1.1.88.59': 'Key Object Selection Document Storage', + '1.2.840.10008.5.1.4.1.1.88.65': 'Chest CAD SR Storage', + '1.2.840.10008.5.1.4.1.1.88.67': 'X-Ray Radiation Dose SR Storage', + '1.2.840.10008.5.1.4.1.1.88.68': + 'Radiopharmaceutical Radiation Dose SR Storage', + '1.2.840.10008.5.1.4.1.1.88.69': 'Colon CAD SR Storage', + '1.2.840.10008.5.1.4.1.1.88.70': 'Implantation Plan SR Storage', + '1.2.840.10008.5.1.4.1.1.104.1': 'Encapsulated PDF Storage', + '1.2.840.10008.5.1.4.1.1.104.2': 'Encapsulated CDA Storage', + '1.2.840.10008.5.1.4.1.1.128': 'Positron Emission Tomography Image Storage', + '1.2.840.10008.5.1.4.1.1.128.1': + 'Legacy Converted Enhanced PET Image Storage', + '1.2.840.10008.5.1.4.1.1.129': 'Standalone PET Curve Storage (Retired)', + '1.2.840.10008.5.1.4.1.1.130': 'Enhanced PET Image Storage', + '1.2.840.10008.5.1.4.1.1.131': 'Basic Structured Display Storage', + '1.2.840.10008.5.1.4.1.1.481.1': 'RT Image Storage', + '1.2.840.10008.5.1.4.1.1.481.2': 'RT Dose Storage', + '1.2.840.10008.5.1.4.1.1.481.3': 'RT Structure Set Storage', + '1.2.840.10008.5.1.4.1.1.481.4': 'RT Beams Treatment Record Storage', + '1.2.840.10008.5.1.4.1.1.481.5': 'RT Plan Storage', + '1.2.840.10008.5.1.4.1.1.481.6': 'RT Brachy Treatment Record Storage', + '1.2.840.10008.5.1.4.1.1.481.7': 'RT Treatment Summary Record Storage', + '1.2.840.10008.5.1.4.1.1.481.8': 'RT Ion Plan Storage', + '1.2.840.10008.5.1.4.1.1.481.9': 'RT Ion Beams Treatment Record Storage', + '1.2.840.10008.5.1.4.1.1.501.1': 'DICOS CT Image Storage', + '1.2.840.10008.5.1.4.1.1.501.2.1': + 'DICOS Digital X-Ray Image Storage - For Presentation', + '1.2.840.10008.5.1.4.1.1.501.2.2': + 'DICOS Digital X-Ray Image Storage - For Processing', + '1.2.840.10008.5.1.4.1.1.501.3': 'DICOS Threat Detection Report Storage', + '1.2.840.10008.5.1.4.1.1.501.4': 'DICOS 2D AIT Storage', + '1.2.840.10008.5.1.4.1.1.501.5': 'DICOS 3D AIT Storage', + '1.2.840.10008.5.1.4.1.1.501.6': 'DICOS Quadrupole Resonance (QR) Storage', + '1.2.840.10008.5.1.4.1.1.601.1': 'Eddy Current Image Storage', + '1.2.840.10008.5.1.4.1.1.601.2': 'Eddy Current Multi-frame Image Storage', + '1.2.840.10008.5.1.4.1.2.1.1': + 'Patient Root Query/Retrieve Information Model - FIND', + '1.2.840.10008.5.1.4.1.2.1.2': + 'Patient Root Query/Retrieve Information Model - MOVE', + '1.2.840.10008.5.1.4.1.2.1.3': + 'Patient Root Query/Retrieve Information Model - GET', + '1.2.840.10008.5.1.4.1.2.2.1': + 'Study Root Query/Retrieve Information Model - FIND', + '1.2.840.10008.5.1.4.1.2.2.2': + 'Study Root Query/Retrieve Information Model - MOVE', + '1.2.840.10008.5.1.4.1.2.2.3': + 'Study Root Query/Retrieve Information Model - GET', + '1.2.840.10008.5.1.4.1.2.3.1': + 'Patient/Study Only Query/Retrieve Information Model - FIND (Retired)', + '1.2.840.10008.5.1.4.1.2.3.2': + 'Patient/Study Only Query/Retrieve Information Model - MOVE (Retired)', + '1.2.840.10008.5.1.4.1.2.3.3': + 'Patient/Study Only Query/Retrieve Information Model - GET (Retired)', + '1.2.840.10008.5.1.4.1.2.4.2': 'Composite Instance Root Retrieve - MOVE', + '1.2.840.10008.5.1.4.1.2.4.3': 'Composite Instance Root Retrieve - GET', + '1.2.840.10008.5.1.4.1.2.5.3': + 'Composite Instance Retrieve Without Bulk Data - GET', + '1.2.840.10008.5.1.4.31': 'Modality Worklist Information Model - FIND', + '1.2.840.10008.5.1.4.32': + 'General Purpose Worklist Management Meta SOP Class (Retired)', + '1.2.840.10008.5.1.4.32.1': + 'General Purpose Worklist Information Model - FIND (Retired)', + '1.2.840.10008.5.1.4.32.2': + 'General Purpose Scheduled Procedure Step SOP Class (Retired)', + '1.2.840.10008.5.1.4.32.3': + 'General Purpose Performed Procedure Step SOP Class (Retired)', + '1.2.840.10008.5.1.4.33': 'Instance Availability Notification SOP Class', + '1.2.840.10008.5.1.4.34.1': + 'RT Beams Delivery Instruction Storage - Trial (Retired)', + '1.2.840.10008.5.1.4.34.2': + 'RT Conventional Machine Verification - Trial (Retired)', + '1.2.840.10008.5.1.4.34.3': 'RT Ion Machine Verification - Trial (Retired)', + '1.2.840.10008.5.1.4.34.4': + 'Unified Worklist and Procedure Step Service Class - Trial (Retired)', + '1.2.840.10008.5.1.4.34.4.1': + 'Unified Procedure Step - Push SOP Class - Trial (Retired)', + '1.2.840.10008.5.1.4.34.4.2': + 'Unified Procedure Step - Watch SOP Class - Trial (Retired)', + '1.2.840.10008.5.1.4.34.4.3': + 'Unified Procedure Step - Pull SOP Class - Trial (Retired)', + '1.2.840.10008.5.1.4.34.4.4': + 'Unified Procedure Step - Event SOP Class - Trial (Retired)', + '1.2.840.10008.5.1.4.34.5': 'UPS Global Subscription SOP Instance', + '1.2.840.10008.5.1.4.34.5.1': 'UPS Filtered Global Subscription SOP Instance', + '1.2.840.10008.5.1.4.34.6': + 'Unified Worklist and Procedure Step Service Class', + '1.2.840.10008.5.1.4.34.6.1': 'Unified Procedure Step - Push SOP Class', + '1.2.840.10008.5.1.4.34.6.2': 'Unified Procedure Step - Watch SOP Class', + '1.2.840.10008.5.1.4.34.6.3': 'Unified Procedure Step - Pull SOP Class', + '1.2.840.10008.5.1.4.34.6.4': 'Unified Procedure Step - Event SOP Class', + '1.2.840.10008.5.1.4.34.7': 'RT Beams Delivery Instruction Storage', + '1.2.840.10008.5.1.4.34.8': 'RT Conventional Machine Verification', + '1.2.840.10008.5.1.4.34.9': 'RT Ion Machine Verification', + '1.2.840.10008.5.1.4.37.1': 'General Relevant Patient Information Query', + '1.2.840.10008.5.1.4.37.2': + 'Breast Imaging Relevant Patient Information Query', + '1.2.840.10008.5.1.4.37.3': 'Cardiac Relevant Patient Information Query', + '1.2.840.10008.5.1.4.38.1': 'Hanging Protocol Storage', + '1.2.840.10008.5.1.4.38.2': 'Hanging Protocol Information Model - FIND', + '1.2.840.10008.5.1.4.38.3': 'Hanging Protocol Information Model - MOVE', + '1.2.840.10008.5.1.4.38.4': 'Hanging Protocol Information Model - GET', + '1.2.840.10008.5.1.4.39.1': 'Color Palette Storage', + '1.2.840.10008.5.1.4.39.2': 'Color Palette Information Model - FIND', + '1.2.840.10008.5.1.4.39.3': 'Color Palette Information Model - MOVE', + '1.2.840.10008.5.1.4.39.4': 'Color Palette Information Model - GET', + '1.2.840.10008.5.1.4.41': 'Product Characteristics Query SOP Class', + '1.2.840.10008.5.1.4.42': 'Substance Approval Query SOP Class', + '1.2.840.10008.5.1.4.43.1': 'Generic Implant Template Storage', + '1.2.840.10008.5.1.4.43.2': + 'Generic Implant Template Information Model - FIND', + '1.2.840.10008.5.1.4.43.3': + 'Generic Implant Template Information Model - MOVE', + '1.2.840.10008.5.1.4.43.4': + 'Generic Implant Template Information Model - GET', + '1.2.840.10008.5.1.4.44.1': 'Implant Assembly Template Storage', + '1.2.840.10008.5.1.4.44.2': + 'Implant Assembly Template Information Model - FIND', + '1.2.840.10008.5.1.4.44.3': + 'Implant Assembly Template Information Model - MOVE', + '1.2.840.10008.5.1.4.44.4': + 'Implant Assembly Template Information Model - GET', + '1.2.840.10008.5.1.4.45.1': 'Implant Template Group Storage', + '1.2.840.10008.5.1.4.45.2': 'Implant Template Group Information Model - FIND', + '1.2.840.10008.5.1.4.45.3': 'Implant Template Group Information Model - MOVE', + '1.2.840.10008.5.1.4.45.4': 'Implant Template Group Information Model - GET', + '1.2.840.10008.7.1.1': 'Native DICOM Model', + '1.2.840.10008.7.1.2': 'Abstract Multi-Dimensional Image Model', + '1.2.840.10008.8.1.1': 'DICOM Content Mapping Resource', + '1.2.840.10008.15.0.3.1': 'dicomDeviceName', + '1.2.840.10008.15.0.3.2': 'dicomDescription', + '1.2.840.10008.15.0.3.3': 'dicomManufacturer', + '1.2.840.10008.15.0.3.4': 'dicomManufacturerModelName', + '1.2.840.10008.15.0.3.5': 'dicomSoftwareVersion', + '1.2.840.10008.15.0.3.6': 'dicomVendorData', + '1.2.840.10008.15.0.3.7': 'dicomAETitle', + '1.2.840.10008.15.0.3.8': 'dicomNetworkConnectionReference', + '1.2.840.10008.15.0.3.9': 'dicomApplicationCluster', + '1.2.840.10008.15.0.3.10': 'dicomAssociationInitiator', + '1.2.840.10008.15.0.3.11': 'dicomAssociationAcceptor', + '1.2.840.10008.15.0.3.12': 'dicomHostname', + '1.2.840.10008.15.0.3.13': 'dicomPort', + '1.2.840.10008.15.0.3.14': 'dicomSOPClass', + '1.2.840.10008.15.0.3.15': 'dicomTransferRole', + '1.2.840.10008.15.0.3.16': 'dicomTransferSyntax', + '1.2.840.10008.15.0.3.17': 'dicomPrimaryDeviceType', + '1.2.840.10008.15.0.3.18': 'dicomRelatedDeviceReference', + '1.2.840.10008.15.0.3.19': 'dicomPreferredCalledAETitle', + '1.2.840.10008.15.0.3.20': 'dicomTLSCyphersuite', + '1.2.840.10008.15.0.3.21': 'dicomAuthorizedNodeCertificateReference', + '1.2.840.10008.15.0.3.22': 'dicomThisNodeCertificateReference', + '1.2.840.10008.15.0.3.23': 'dicomInstalled', + '1.2.840.10008.15.0.3.24': 'dicomStationName', + '1.2.840.10008.15.0.3.25': 'dicomDeviceSerialNumber', + '1.2.840.10008.15.0.3.26': 'dicomInstitutionName', + '1.2.840.10008.15.0.3.27': 'dicomInstitutionAddress', + '1.2.840.10008.15.0.3.28': 'dicomInstitutionDepartmentName', + '1.2.840.10008.15.0.3.29': 'dicomIssuerOfPatientID', + '1.2.840.10008.15.0.3.30': 'dicomPreferredCallingAETitle', + '1.2.840.10008.15.0.3.31': 'dicomSupportedCharacterSet', + '1.2.840.10008.15.0.4.1': 'dicomConfigurationRoot', + '1.2.840.10008.15.0.4.2': 'dicomDevicesRoot', + '1.2.840.10008.15.0.4.3': 'dicomUniqueAETitlesRegistryRoot', + '1.2.840.10008.15.0.4.4': 'dicomDevice', + '1.2.840.10008.15.0.4.5': 'dicomNetworkAE', + '1.2.840.10008.15.0.4.6': 'dicomNetworkConnection', + '1.2.840.10008.15.0.4.7': 'dicomUniqueAETitle', + '1.2.840.10008.15.0.4.8': 'dicomTransferCapability', + '1.2.840.10008.15.1.1': 'Universal Coordinated Time', +}; + +export default UIDs; diff --git a/packages/dicomImageLoader/examples/customWebWorkerTask/convolveTask.js b/packages/dicomImageLoader/examplesOld/customWebWorkerTask/convolveTask.js similarity index 100% rename from packages/dicomImageLoader/examples/customWebWorkerTask/convolveTask.js rename to packages/dicomImageLoader/examplesOld/customWebWorkerTask/convolveTask.js diff --git a/packages/dicomImageLoader/examples/customWebWorkerTask/index.html b/packages/dicomImageLoader/examplesOld/customWebWorkerTask/index.html similarity index 100% rename from packages/dicomImageLoader/examples/customWebWorkerTask/index.html rename to packages/dicomImageLoader/examplesOld/customWebWorkerTask/index.html diff --git a/packages/dicomImageLoader/examples/customWebWorkerTask/sleepTask.js b/packages/dicomImageLoader/examplesOld/customWebWorkerTask/sleepTask.js similarity index 100% rename from packages/dicomImageLoader/examples/customWebWorkerTask/sleepTask.js rename to packages/dicomImageLoader/examplesOld/customWebWorkerTask/sleepTask.js diff --git a/packages/dicomImageLoader/examples/dicomfile/index.html b/packages/dicomImageLoader/examplesOld/dicomfile/index.html similarity index 100% rename from packages/dicomImageLoader/examples/dicomfile/index.html rename to packages/dicomImageLoader/examplesOld/dicomfile/index.html diff --git a/packages/dicomImageLoader/examples/dicomfile/uids.js b/packages/dicomImageLoader/examplesOld/dicomfile/uids.js similarity index 100% rename from packages/dicomImageLoader/examples/dicomfile/uids.js rename to packages/dicomImageLoader/examplesOld/dicomfile/uids.js diff --git a/packages/dicomImageLoader/examples/index.html b/packages/dicomImageLoader/examplesOld/index.html similarity index 100% rename from packages/dicomImageLoader/examples/index.html rename to packages/dicomImageLoader/examplesOld/index.html diff --git a/packages/dicomImageLoader/examples/progressive-jph/index.html b/packages/dicomImageLoader/examplesOld/progressive-jph/index.html similarity index 100% rename from packages/dicomImageLoader/examples/progressive-jph/index.html rename to packages/dicomImageLoader/examplesOld/progressive-jph/index.html diff --git a/packages/dicomImageLoader/examples/test-images/CT0012.fragmented_no_bot_jpeg_baseline.51.dcm b/packages/dicomImageLoader/examplesOld/test-images/CT0012.fragmented_no_bot_jpeg_baseline.51.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CT0012.fragmented_no_bot_jpeg_baseline.51.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CT0012.fragmented_no_bot_jpeg_baseline.51.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CT1_J2KR b/packages/dicomImageLoader/examplesOld/test-images/CT1_J2KR similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CT1_J2KR rename to packages/dicomImageLoader/examplesOld/test-images/CT1_J2KR diff --git a/packages/dicomImageLoader/examples/test-images/CT2_J2KR b/packages/dicomImageLoader/examplesOld/test-images/CT2_J2KR similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CT2_J2KR rename to packages/dicomImageLoader/examplesOld/test-images/CT2_J2KR diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_BigEndianExplicitTransferSyntax_1.2.840.10008.1.2.2.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_BigEndianExplicitTransferSyntax_1.2.840.10008.1.2.2.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_BigEndianExplicitTransferSyntax_1.2.840.10008.1.2.2.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_BigEndianExplicitTransferSyntax_1.2.840.10008.1.2.2.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_DeflatedExplicitVRLittleEndianTransferSyntax_1.2.840.10008.1.2.1.99.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_DeflatedExplicitVRLittleEndianTransferSyntax_1.2.840.10008.1.2.1.99.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_DeflatedExplicitVRLittleEndianTransferSyntax_1.2.840.10008.1.2.1.99.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_DeflatedExplicitVRLittleEndianTransferSyntax_1.2.840.10008.1.2.1.99.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEG2000LosslessOnlyTransferSyntax_1.2.840.10008.1.2.4.90.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEG2000LosslessOnlyTransferSyntax_1.2.840.10008.1.2.4.90.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEG2000LosslessOnlyTransferSyntax_1.2.840.10008.1.2.4.90.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEG2000LosslessOnlyTransferSyntax_1.2.840.10008.1.2.4.90.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEG2000TransferSyntax_1.2.840.10008.1.2.4.91.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEG2000TransferSyntax_1.2.840.10008.1.2.4.91.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEG2000TransferSyntax_1.2.840.10008.1.2.4.91.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEG2000TransferSyntax_1.2.840.10008.1.2.4.91.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGLSLosslessTransferSyntax_1.2.840.10008.1.2.4.80.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGLSLosslessTransferSyntax_1.2.840.10008.1.2.4.80.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGLSLosslessTransferSyntax_1.2.840.10008.1.2.4.80.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGLSLosslessTransferSyntax_1.2.840.10008.1.2.4.80.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGLSLossyTransferSyntax_1.2.840.10008.1.2.4.81.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGLSLossyTransferSyntax_1.2.840.10008.1.2.4.81.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGLSLossyTransferSyntax_1.2.840.10008.1.2.4.81.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGLSLossyTransferSyntax_1.2.840.10008.1.2.4.81.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess10_12TransferSyntax_1.2.840.10008.1.2.4.55.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess10_12TransferSyntax_1.2.840.10008.1.2.4.55.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess10_12TransferSyntax_1.2.840.10008.1.2.4.55.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess10_12TransferSyntax_1.2.840.10008.1.2.4.55.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess14SV1TransferSyntax_1.2.840.10008.1.2.4.70.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess14SV1TransferSyntax_1.2.840.10008.1.2.4.70.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess14SV1TransferSyntax_1.2.840.10008.1.2.4.70.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess14SV1TransferSyntax_1.2.840.10008.1.2.4.70.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess14TransferSyntax_1.2.840.10008.1.2.4.57.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess14TransferSyntax_1.2.840.10008.1.2.4.57.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess14TransferSyntax_1.2.840.10008.1.2.4.57.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess14TransferSyntax_1.2.840.10008.1.2.4.57.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess1TransferSyntax_1.2.840.10008.1.2.4.50.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess1TransferSyntax_1.2.840.10008.1.2.4.50.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess1TransferSyntax_1.2.840.10008.1.2.4.50.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess1TransferSyntax_1.2.840.10008.1.2.4.50.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess2_4TransferSyntax_1.2.840.10008.1.2.4.51.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess2_4TransferSyntax_1.2.840.10008.1.2.4.51.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess2_4TransferSyntax_1.2.840.10008.1.2.4.51.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess2_4TransferSyntax_1.2.840.10008.1.2.4.51.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess6_8TransferSyntax_1.2.840.10008.1.2.4.53.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess6_8TransferSyntax_1.2.840.10008.1.2.4.53.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_JPEGProcess6_8TransferSyntax_1.2.840.10008.1.2.4.53.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_JPEGProcess6_8TransferSyntax_1.2.840.10008.1.2.4.53.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_LittleEndianExplicitTransferSyntax_1.2.840.10008.1.2.1.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_LittleEndianExplicitTransferSyntax_1.2.840.10008.1.2.1.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_LittleEndianExplicitTransferSyntax_1.2.840.10008.1.2.1.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_LittleEndianExplicitTransferSyntax_1.2.840.10008.1.2.1.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_LittleEndianImplicitTransferSyntax_1.2.840.10008.1.2.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_LittleEndianImplicitTransferSyntax_1.2.840.10008.1.2.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_LittleEndianImplicitTransferSyntax_1.2.840.10008.1.2.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_LittleEndianImplicitTransferSyntax_1.2.840.10008.1.2.dcm diff --git a/packages/dicomImageLoader/examples/test-images/CTImage.dcm_RLELosslessTransferSyntax_1.2.840.10008.1.2.5.dcm b/packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_RLELosslessTransferSyntax_1.2.840.10008.1.2.5.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/CTImage.dcm_RLELosslessTransferSyntax_1.2.840.10008.1.2.5.dcm rename to packages/dicomImageLoader/examplesOld/test-images/CTImage.dcm_RLELosslessTransferSyntax_1.2.840.10008.1.2.5.dcm diff --git a/packages/dicomImageLoader/examples/test-images/no-pixel-data.dcm b/packages/dicomImageLoader/examplesOld/test-images/no-pixel-data.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/no-pixel-data.dcm rename to packages/dicomImageLoader/examplesOld/test-images/no-pixel-data.dcm diff --git a/packages/dicomImageLoader/examples/test-images/no-pixel-spacing.dcm b/packages/dicomImageLoader/examplesOld/test-images/no-pixel-spacing.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/no-pixel-spacing.dcm rename to packages/dicomImageLoader/examplesOld/test-images/no-pixel-spacing.dcm diff --git a/packages/dicomImageLoader/examples/test-images/paramap-float.dcm b/packages/dicomImageLoader/examplesOld/test-images/paramap-float.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/paramap-float.dcm rename to packages/dicomImageLoader/examplesOld/test-images/paramap-float.dcm diff --git a/packages/dicomImageLoader/examples/test-images/paramap.dcm b/packages/dicomImageLoader/examplesOld/test-images/paramap.dcm similarity index 100% rename from packages/dicomImageLoader/examples/test-images/paramap.dcm rename to packages/dicomImageLoader/examplesOld/test-images/paramap.dcm diff --git a/packages/dicomImageLoader/examples/utils/customWebWorkersConfig.js b/packages/dicomImageLoader/examplesOld/utils/customWebWorkersConfig.js similarity index 100% rename from packages/dicomImageLoader/examples/utils/customWebWorkersConfig.js rename to packages/dicomImageLoader/examplesOld/utils/customWebWorkersConfig.js diff --git a/packages/dicomImageLoader/examples/utils/initializeWebWorkers.js b/packages/dicomImageLoader/examplesOld/utils/initializeWebWorkers.js similarity index 100% rename from packages/dicomImageLoader/examples/utils/initializeWebWorkers.js rename to packages/dicomImageLoader/examplesOld/utils/initializeWebWorkers.js diff --git a/packages/dicomImageLoader/examples/wadors/index.html b/packages/dicomImageLoader/examplesOld/wadors/index.html similarity index 100% rename from packages/dicomImageLoader/examples/wadors/index.html rename to packages/dicomImageLoader/examplesOld/wadors/index.html diff --git a/packages/dicomImageLoader/examples/wadouri/index.html b/packages/dicomImageLoader/examplesOld/wadouri/index.html similarity index 100% rename from packages/dicomImageLoader/examples/wadouri/index.html rename to packages/dicomImageLoader/examplesOld/wadouri/index.html diff --git a/packages/dicomImageLoader/examples/wadourimultiframe/index.html b/packages/dicomImageLoader/examplesOld/wadourimultiframe/index.html similarity index 100% rename from packages/dicomImageLoader/examples/wadourimultiframe/index.html rename to packages/dicomImageLoader/examplesOld/wadourimultiframe/index.html diff --git a/packages/dicomImageLoader/src/imageLoader/createImage.ts b/packages/dicomImageLoader/src/imageLoader/createImage.ts index 81a79eb84..1836ed07e 100644 --- a/packages/dicomImageLoader/src/imageLoader/createImage.ts +++ b/packages/dicomImageLoader/src/imageLoader/createImage.ts @@ -1,19 +1,21 @@ import { ByteArray } from 'dicom-parser'; import external from '../externalModules'; import getMinMax from '../shared/getMinMax'; +import getPixelDataTypeFromMinMax from '../shared/getPixelDataTypeFromMinMax'; import { DICOMLoaderImageOptions, MetadataImagePlaneModule, MetadataSopCommonModule, DICOMLoaderIImage, ImageFrame, + PixelDataTypedArray, } from '../types'; import convertColorSpace from './convertColorSpace'; import decodeImageFrame from './decodeImageFrame'; import getImageFrame from './getImageFrame'; import getScalingParameters from './getScalingParameters'; import { getOptions } from './internal/options'; -import isColorImageFn from './isColorImage'; +import isColorImageFn from '../shared/isColorImage'; import isJPEGBaseline8BitColor from './isJPEGBaseline8BitColor'; let lastImageIdDrawn = ''; @@ -27,58 +29,24 @@ function isModalityLUTForDisplay(sopClassUid: string): boolean { ); // XRF } -function convertToIntPixelData(floatPixelData) { - const floatMinMax = getMinMax(floatPixelData); - const floatRange = Math.abs(floatMinMax.max - floatMinMax.min); - const intRange = 65535; - const slope = floatRange / intRange; - const intercept = floatMinMax.min; - const numPixels = floatPixelData.length; - const intPixelData = new Uint16Array(numPixels); - - let min = 65535; - - let max = 0; - - for (let i = 0; i < numPixels; i++) { - const rescaledPixel = Math.floor((floatPixelData[i] - intercept) / slope); - - intPixelData[i] = rescaledPixel; - min = Math.min(min, rescaledPixel); - max = Math.max(max, rescaledPixel); - } - - return { - min, - max, - intPixelData, - slope, - intercept, - }; -} - /** - * Helper function to set pixel data to the right typed array. This is needed because web workers - * can transfer array buffers but not typed arrays - * @param imageFrame + * Helper function to set pixel d2023-03-17-16-35-04.pngata to the right typed array. + * This is needed because web workers can transfer array buffers but not typed arrays + * + * Here we are setting the pixel data to the right typed array based on the final + * min and max values */ -function setPixelDataType(imageFrame, preScale) { - const isScaled = preScale?.scaled; - const scalingParmeters = preScale?.scalingParameters; - const rescaleSlope = scalingParmeters?.rescaleSlope; - const rescaleIntercept = scalingParmeters?.rescaleIntercept; - const isNegative = rescaleSlope < 0 || rescaleIntercept < 0; - - if (imageFrame.bitsAllocated === 32) { - imageFrame.pixelData = new Float32Array(imageFrame.pixelData); - } else if (imageFrame.bitsAllocated === 16) { - if (imageFrame.pixelRepresentation === 0 && !(isScaled && isNegative)) { - imageFrame.pixelData = new Uint16Array(imageFrame.pixelData); - } else { - imageFrame.pixelData = new Int16Array(imageFrame.pixelData); - } +function setPixelDataType(imageFrame) { + const minValue = imageFrame.smallestPixelValue; + const maxValue = imageFrame.largestPixelValue; + + const TypedArray = getPixelDataTypeFromMinMax(minValue, maxValue); + + if (TypedArray) { + const typedArray = new TypedArray(imageFrame.pixelData); + imageFrame.pixelData = typedArray; } else { - imageFrame.pixelData = new Uint8Array(imageFrame.pixelData); + throw new Error('Could not apply a typed array to the pixel data'); } } @@ -87,28 +55,23 @@ function setPixelDataType(imageFrame, preScale) { * decoding happens with browser API which results in RGBA, but if useRGBA flag * is set to false, we want to return RGB * - * @param imageFrame - decoded image in RGBA + * @param pixelData - decoded image in RGBA * @param targetBuffer - target buffer to write to */ function removeAFromRGBA( - imageFrame: - | Float32Array // populated later after decoding - | Int16Array - | Uint16Array - | Uint8Array - | Uint8ClampedArray, - targetBuffer: Uint8ClampedArray + pixelData: PixelDataTypedArray, + targetBuffer: Uint8ClampedArray | Uint8Array ) { - const numPixels = imageFrame.length / 4; + const numPixels = pixelData.length / 4; let rgbIndex = 0; let bufferIndex = 0; for (let i = 0; i < numPixels; i++) { - targetBuffer[bufferIndex++] = imageFrame[rgbIndex++]; // red - targetBuffer[bufferIndex++] = imageFrame[rgbIndex++]; // green - targetBuffer[bufferIndex++] = imageFrame[rgbIndex++]; // blue + targetBuffer[bufferIndex++] = pixelData[rgbIndex++]; // red + targetBuffer[bufferIndex++] = pixelData[rgbIndex++]; // green + targetBuffer[bufferIndex++] = pixelData[rgbIndex++]; // blue rgbIndex++; // skip alpha } @@ -124,11 +87,7 @@ function createImage( // whether to use RGBA for color images, default true as cs-legacy uses RGBA // but we don't need RGBA in cs3d, and it's faster, and memory-efficient // in cs3d - let useRGBA = true; - - if (options.useRGBA !== undefined) { - useRGBA = options.useRGBA; - } + const useRGBA = options.useRGBA; // always preScale the pixel array unless it is asked not to options.preScale = { @@ -171,12 +130,12 @@ function createImage( decodeConfig ); - const { convertFloatPixelDataToInt, use16BitDataType } = decodeConfig; + const { use16BitDataType } = decodeConfig; + const isColorImage = isColorImageFn(imageFrame.photometricInterpretation); return new Promise((resolve, reject) => { // eslint-disable-next-line complexity decodePromise.then(function (imageFrame: ImageFrame) { - debugger; // if it is desired to skip creating image, return the imageFrame // after the decode. This might be useful for some applications // that only need the decoded pixel data and not the image object @@ -187,66 +146,56 @@ function createImage( // Decode task, point the image to it here. // We can't have done it within the thread incase it was a SharedArrayBuffer. let alreadyTyped = false; - - if (options.targetBuffer) { - let offset: number, length: number; - // If we have a target buffer, write to that instead. This helps reduce memory duplication. - - ({ offset, length } = options.targetBuffer); - const { arrayBuffer, type } = options.targetBuffer; - - let TypedArrayConstructor; - - if (length === null || length === undefined) { - length = imageFrame.pixelDataLength; - } - - if (offset === null || offset === undefined) { - offset = 0; - } - - switch (type) { - case 'Uint8Array': - TypedArrayConstructor = Uint8Array; - break; - case use16BitDataType && 'Uint16Array': - TypedArrayConstructor = Uint16Array; - break; - case use16BitDataType && 'Int16Array': - TypedArrayConstructor = Int16Array; - break; - case 'Float32Array': - TypedArrayConstructor = Float32Array; - break; - default: - throw new Error( - 'target array for image does not have a valid type.' - ); - } + // We can safely render color image in 8 bit, so no need to convert + if (options.targetBuffer && options.targetBuffer.type && !isColorImage) { + const { + arrayBuffer, + type, + offset: rawOffset = 0, + length: rawLength, + } = options.targetBuffer; + + const imageFrameLength = imageFrame.pixelDataLength; + + const offset = rawOffset; + const length = + rawLength !== null && rawLength !== undefined + ? rawLength + : imageFrameLength - offset; + + const typedArrayConstructors = { + Uint8Array, + Uint16Array: use16BitDataType ? Uint16Array : undefined, + Int16Array: use16BitDataType ? Int16Array : undefined, + Float32Array, + }; if (length !== imageFrame.pixelDataLength) { throw new Error( - 'target array for image does not have the same length as the decoded image length.' + `target array for image does not have the same length (${length}) as the decoded image length (${imageFrame.pixelDataLength}).` ); } + const TypedArrayConstructor = typedArrayConstructors[type]; + // TypedArray.Set is api level and ~50x faster than copying elements even for // Arrays of different types, which aren't simply memcpy ops. - let typedArray; + const typedArray = arrayBuffer + ? new TypedArrayConstructor(arrayBuffer, offset, length) + : new TypedArrayConstructor(imageFrame.pixelData); - if (arrayBuffer) { - typedArray = new TypedArrayConstructor(arrayBuffer, offset, length); - } else { - typedArray = new TypedArrayConstructor(imageFrame.pixelData); + if (length !== imageFrame.pixelDataLength) { + throw new Error( + 'target array for image does not have the same length as the decoded image length.' + ); } - // If need to scale, need to scale correct array. imageFrame.pixelData = typedArray; alreadyTyped = true; } if (!alreadyTyped) { - setPixelDataType(imageFrame, imageFrame.preScale); + setPixelDataType(imageFrame); } const imagePlaneModule: MetadataImagePlaneModule = @@ -257,33 +206,34 @@ function createImage( cornerstone.metaData.get('modalityLutModule', imageId) || {}; const sopCommonModule: MetadataSopCommonModule = cornerstone.metaData.get('sopCommonModule', imageId) || {}; - const isColorImage = isColorImageFn(imageFrame.photometricInterpretation); - if (isColorImage) { if (useRGBA) { // JPEGBaseline (8 bits) is already returning the pixel data in the right format (rgba) // because it's using a canvas to load and decode images. - if (!isJPEGBaseline8BitColor(imageFrame, transferSyntax)) { + if ( + !isJPEGBaseline8BitColor(imageFrame, transferSyntax) && + imageFrame.photometricInterpretation !== 'RGB' + ) { canvas.height = imageFrame.rows; canvas.width = imageFrame.columns; - const context = canvas.getContext('2d'); - const imageData = context.createImageData( imageFrame.columns, imageFrame.rows ); convertColorSpace(imageFrame, imageData.data, useRGBA); - imageFrame.imageData = imageData; imageFrame.pixelData = imageData.data; + imageFrame.pixelDataLength = imageData.data.length; } } else if (isJPEGBaseline8BitColor(imageFrame, transferSyntax)) { // If we don't need the RGBA but the decoding is done with RGBA (the case // for JPEG Baseline 8 bit color), AND the option specifies to use RGB (no RGBA) // we need to remove the A channel from pixel data - const colorBuffer = new Uint8ClampedArray( + // Note: rendering libraries like vtk expect Uint8Array for RGB images + // otherwise they will convert them to Float32Array which might be slow + const colorBuffer = new Uint8Array( (imageFrame.pixelData.length / 4) * 3 ); @@ -292,6 +242,8 @@ function createImage( imageFrame.pixelData, colorBuffer ); + + imageFrame.pixelDataLength = imageFrame.pixelData.length; } else if (imageFrame.photometricInterpretation === 'PALETTE COLOR') { canvas.height = imageFrame.rows; canvas.width = imageFrame.columns; @@ -311,7 +263,10 @@ function createImage( ); // remove the A from the RGBA of the imageFrame - imageFrame.pixelData = removeAFromRGBA(imageData.data, colorBuffer); + imageFrame.pixelData = new Uint8Array( + removeAFromRGBA(imageData.data, colorBuffer) + ); + imageFrame.pixelDataLength = imageFrame.pixelData.length; } /** @todo check as any */ @@ -322,7 +277,7 @@ function createImage( imageFrame.largestPixelValue = minMax.max; } - const image = { + const image: DICOMLoaderIImage = { imageId, color: isColorImage, columnPixelSpacing: imagePlaneModule.columnPixelSpacing, @@ -357,44 +312,57 @@ function createImage( floatPixelData: undefined, imageFrame, rgba: isColorImage && useRGBA, - getPixelData: undefined, + getPixelData: () => imageFrame.pixelData, getCanvas: undefined, numComps: undefined, - } as DICOMLoaderIImage; - - // If pixel data is intrinsically floating 32 array, we convert it to int for - // display in cornerstone. For other cases when pixel data is typed as - // Float32Array for scaling; this conversion is not needed. - if ( - imageFrame.pixelData instanceof Float32Array && - convertFloatPixelDataToInt - ) { - const floatPixelData = imageFrame.pixelData; - const results = convertToIntPixelData(floatPixelData); - - image.minPixelValue = results.min; - image.maxPixelValue = results.max; - image.slope = results.slope; - image.intercept = results.intercept; - image.floatPixelData = floatPixelData; - /** @todo check as any */ - image.getPixelData = () => results.intPixelData as any; - } else { - /** @todo check as any */ - image.getPixelData = () => imageFrame.pixelData as any; - } + }; + window.image = image; if (image.color) { image.getCanvas = function () { + // the getCanvas function is used in the CPU rendering path + // and it is used to use the canvas api to draw the image + // instead of looping through the pixel data and drawing each pixel + // to use the canvas api, we need to convert the pixel data to a + // Uint8ClampedArray (which is what the canvas api expects) + // and then we can use the putImageData api to draw the image + // However, if the image already was loaded without the alpha channel + // we need to add the alpha channel back in if (lastImageIdDrawn === imageId) { return canvas; } - canvas.height = image.rows; - canvas.width = image.columns; - const context = canvas.getContext('2d'); + const width = image.columns; + const height = image.rows; + + canvas.height = height; + canvas.width = width; + const ctx = canvas.getContext('2d'); + const imageData = ctx.createImageData(width, height); + + const arr = imageFrame.pixelData; + + if (arr.length === width * height * 4) { + for (let i = 0; i < arr.length; i++) { + imageData.data[i] = arr[i]; + } + } + // Set pixel data for RGB array + else if (arr.length === width * height * 3) { + let j = 0; + for (let i = 0; i < arr.length; i += 3) { + imageData.data[j++] = arr[i]; + imageData.data[j++] = arr[i + 1]; + imageData.data[j++] = arr[i + 2]; + imageData.data[j++] = 255; + } + } + + imageFrame.pixelData = imageData.data; + imageFrame.pixelDataLength = imageData.data.length; - context.putImageData(imageFrame.imageData, 0, 0); + imageFrame.imageData = imageData; + ctx.putImageData(imageFrame.imageData, 0, 0); lastImageIdDrawn = imageId; return canvas; diff --git a/packages/dicomImageLoader/src/imageLoader/decodeJPEGBaseline8BitColor.ts b/packages/dicomImageLoader/src/imageLoader/decodeJPEGBaseline8BitColor.ts index ca0ea9c69..142eca30f 100644 --- a/packages/dicomImageLoader/src/imageLoader/decodeJPEGBaseline8BitColor.ts +++ b/packages/dicomImageLoader/src/imageLoader/decodeJPEGBaseline8BitColor.ts @@ -63,7 +63,7 @@ function decodeJPEGBaseline8BitColor( const imageData = context.getImageData(0, 0, img.width, img.height); const end = new Date().getTime(); - imageFrame.pixelData = imageData.data; + imageFrame.pixelData = new Uint8Array(imageData.data.buffer); imageFrame.imageData = imageData; imageFrame.decodeTimeInMS = end - start; @@ -72,6 +72,7 @@ function decodeJPEGBaseline8BitColor( imageFrame.smallestPixelValue = minMax.min; imageFrame.largestPixelValue = minMax.max; + imageFrame.pixelDataLength = imageFrame.pixelData.length; resolve(imageFrame); }; diff --git a/packages/dicomImageLoader/src/imageLoader/index-noWorkers.ts b/packages/dicomImageLoader/src/imageLoader/index-noWorkers.ts index 7ab76a175..9aa8a4450 100644 --- a/packages/dicomImageLoader/src/imageLoader/index-noWorkers.ts +++ b/packages/dicomImageLoader/src/imageLoader/index-noWorkers.ts @@ -15,7 +15,7 @@ import { default as decodeImageFrame } from './decodeImageFrame-noWorkers'; import { default as decodeJPEGBaseline8BitColor } from './decodeJPEGBaseline8BitColor'; import { default as getImageFrame } from './getImageFrame'; import { default as getMinMax } from '../shared/getMinMax'; -import { default as isColorImage } from './isColorImage'; +import { default as isColorImage } from '../shared/isColorImage'; import { default as isJPEGBaseline8BitColor } from './isJPEGBaseline8BitColor'; import { default as webWorkerManager } from './webWorkerManager'; import { default as getPixelData } from './wadors/getPixelData'; diff --git a/packages/dicomImageLoader/src/imageLoader/index.ts b/packages/dicomImageLoader/src/imageLoader/index.ts index eb950a52c..0dc8a0f6b 100644 --- a/packages/dicomImageLoader/src/imageLoader/index.ts +++ b/packages/dicomImageLoader/src/imageLoader/index.ts @@ -15,7 +15,7 @@ import { default as decodeImageFrame } from './decodeImageFrame'; import { default as decodeJPEGBaseline8BitColor } from './decodeJPEGBaseline8BitColor'; import { default as getImageFrame } from './getImageFrame'; import { default as getMinMax } from '../shared/getMinMax'; -import { default as isColorImage } from './isColorImage'; +import { default as isColorImage } from '../shared/isColorImage'; import { default as isJPEGBaseline8BitColor } from './isJPEGBaseline8BitColor'; import { default as webWorkerManager } from './webWorkerManager'; import { default as getPixelData } from './wadors/getPixelData'; diff --git a/packages/dicomImageLoader/src/shared/calculateMinMax.ts b/packages/dicomImageLoader/src/shared/calculateMinMax.ts index 51aed31ff..7aaff5930 100644 --- a/packages/dicomImageLoader/src/shared/calculateMinMax.ts +++ b/packages/dicomImageLoader/src/shared/calculateMinMax.ts @@ -1,3 +1,4 @@ +import { ImageFrame } from '../types'; import getMinMax from './getMinMax'; /** @@ -11,8 +12,13 @@ import getMinMax from './getMinMax'; * @param {Boolean} strict If 'strict' is true, log to the console a warning if these values do not match. * Otherwise, correct them automatically.Default is true. */ -export default function calculateMinMax(imageFrame, strict = true) { - const minMax = getMinMax(imageFrame.pixelData); +export default function calculateMinMax(imageFrame: ImageFrame, strict = true) { + const minMax = + imageFrame.minAfterScale !== undefined || + imageFrame.maxAfterScale !== undefined + ? { min: imageFrame.minAfterScale, max: imageFrame.maxAfterScale } + : getMinMax(imageFrame.pixelData); + const mustAssign = !( isNumber(imageFrame.smallestPixelValue) && isNumber(imageFrame.largestPixelValue) diff --git a/packages/dicomImageLoader/src/shared/decodeImageFrame.ts b/packages/dicomImageLoader/src/shared/decodeImageFrame.ts index 2494964b2..7bb34dc9c 100644 --- a/packages/dicomImageLoader/src/shared/decodeImageFrame.ts +++ b/packages/dicomImageLoader/src/shared/decodeImageFrame.ts @@ -12,7 +12,10 @@ import decodeJPEGLS from './decoders/decodeJPEGLS'; import decodeJPEG2000 from './decoders/decodeJPEG2000'; import decodeHTJ2K from './decoders/decodeHTJ2K'; import scaleArray from './scaling/scaleArray'; -import { ImageFrame, LoaderDecodeOptions } from '../types'; +import { ImageFrame, LoaderDecodeOptions, PixelDataTypedArray } from '../types'; +import getMinMax from './getMinMax'; +import getPixelDataTypeFromMinMax from './getPixelDataTypeFromMinMax'; +import isColorImage from './isColorImage'; /** * Decodes the provided image frame. @@ -90,7 +93,7 @@ async function decodeImageFrame( case '1.2.840.10008.1.2.4.81': // JPEG-LS Lossy (Near-Lossless) Image Compression opts = { - signed: false, // imageFrame.signed, + signed: imageFrame.pixelRepresentation === 1, // imageFrame.signed, // shouldn't need... bytesPerPixel: imageFrame.bitsAllocated <= 8 ? 1 : 2, ...imageFrame, @@ -186,108 +189,186 @@ function postProcessDecodedPixels( // Cache the pixelData reference quickly incase we want to set a targetBuffer _and_ scale. let pixelDataArray = imageFrame.pixelData; - imageFrame.pixelDataLength = imageFrame.pixelData.length; + const { min: minBeforeScale, max: maxBeforeScale } = getMinMax( + imageFrame.pixelData + ); - if (options.targetBuffer) { - let offset, length; - // If we have a target buffer, write to that instead. This helps reduce memory duplication. + const typedArrayConstructors = { + Uint8Array, + Uint16Array: use16BitDataType ? Uint16Array : undefined, + Int16Array: use16BitDataType ? Int16Array : undefined, + Float32Array, + }; - ({ offset, length } = options.targetBuffer); - const { arrayBuffer, type } = options.targetBuffer; + if ( + options.targetBuffer && + options.targetBuffer.type && + !isColorImage(imageFrame.photometricInterpretation) + ) { + pixelDataArray = _handleTargetBuffer( + options, + imageFrame, + typedArrayConstructors, + pixelDataArray + ); + } else if (options.preScale.enabled) { + pixelDataArray = _handlePreScaleSetup( + options, + minBeforeScale, + maxBeforeScale, + imageFrame + ); + } else { + pixelDataArray = _getDefaultPixelDataArray( + minBeforeScale, + maxBeforeScale, + imageFrame + ); + } - let TypedArrayConstructor; + let minAfterScale = minBeforeScale; + let maxAfterScale = maxBeforeScale; - if (offset === null || offset === undefined) { - offset = 0; - } + if (options.preScale.enabled) { + const scalingParameters = options.preScale.scalingParameters; + _validateScalingParameters(scalingParameters); - if ((length === null || length === undefined) && offset !== 0) { - length = imageFrame.pixelDataLength - offset; - } else if (length === null || length === undefined) { - length = imageFrame.pixelDataLength; - } + const { rescaleSlope, rescaleIntercept, suvbw } = scalingParameters; + const isSlopeAndInterceptNumbers = + typeof rescaleSlope === 'number' && typeof rescaleIntercept === 'number'; - switch (type) { - case 'Uint8Array': - TypedArrayConstructor = Uint8Array; - break; - case use16BitDataType && 'Uint16Array': - TypedArrayConstructor = Uint16Array; - break; - case use16BitDataType && 'Int16Array': - TypedArrayConstructor = Int16Array; - break; - case 'Float32Array': - TypedArrayConstructor = Float32Array; - break; - default: - throw new Error('target array for image does not have a valid type.'); - } + if (isSlopeAndInterceptNumbers) { + scaleArray(pixelDataArray, scalingParameters); + imageFrame.preScale = { + ...options.preScale, + scaled: true, + }; - const imageFramePixelData = imageFrame.pixelData; + // calculate the min and max after scaling + const { rescaleIntercept, rescaleSlope, suvbw } = scalingParameters; + minAfterScale = rescaleSlope * minBeforeScale + rescaleIntercept; + maxAfterScale = rescaleSlope * maxBeforeScale + rescaleIntercept; - if (length !== imageFramePixelData.length) { - throw new Error( - `target array for image does not have the same length (${length}) as the decoded image length (${imageFramePixelData.length}).` - ); + if (suvbw) { + minAfterScale = minAfterScale * suvbw; + maxAfterScale = maxAfterScale * suvbw; + } } + } - // TypedArray.Set is api level and ~50x faster than copying elements even for - // Arrays of different types, which aren't simply memcpy ops. - let typedArray; + // assign the array buffer to the pixelData only if it is not a SharedArrayBuffer + // since we can't transfer ownership of a SharedArrayBuffer to another thread + // in the workers + const hasTargetBuffer = options.targetBuffer !== undefined; + const isNotSharedArrayBuffer = + hasTargetBuffer && + !(options.targetBuffer.arrayBuffer instanceof SharedArrayBuffer); - if (arrayBuffer) { - typedArray = new TypedArrayConstructor(arrayBuffer, offset, length); - } else { - typedArray = new TypedArrayConstructor(length); - } + if (!hasTargetBuffer || isNotSharedArrayBuffer) { + imageFrame.pixelData = pixelDataArray; + } - typedArray.set(imageFramePixelData, 0); + imageFrame.minAfterScale = minAfterScale; + imageFrame.maxAfterScale = maxAfterScale; - // If need to scale, need to scale correct array. - pixelDataArray = typedArray; - } + const end = new Date().getTime(); + imageFrame.decodeTimeInMS = end - start; - if (options.preScale.enabled) { - const scalingParameters = options.preScale.scalingParameters; + return imageFrame; +} - if (!scalingParameters) { - throw new Error( - 'options.preScale.scalingParameters must be defined if preScale.enabled is true, and scalingParameters cannot be derived from the metadata providers.' - ); - } +function _handleTargetBuffer( + options: any, + imageFrame: ImageFrame, + typedArrayConstructors: { + Uint8Array: Uint8ArrayConstructor; + Uint16Array: Uint16ArrayConstructor; + Int16Array: Int16ArrayConstructor; + Float32Array: Float32ArrayConstructor; + }, + pixelDataArray: PixelDataTypedArray +) { + const { + arrayBuffer, + type, + offset: rawOffset = 0, + length: rawLength, + } = options.targetBuffer; - const { rescaleSlope, rescaleIntercept } = scalingParameters; - - if ( - typeof rescaleSlope === 'number' && - typeof rescaleIntercept === 'number' - ) { - // @ts-ignore - if (scaleArray(pixelDataArray, scalingParameters)) { - imageFrame.preScale = { - ...options.preScale, - scaled: true, - }; - } - } + const imageFrameLength = imageFrame.pixelDataLength; + + const offset = rawOffset; + const length = + rawLength !== null && rawLength !== undefined + ? rawLength + : imageFrameLength - offset; + + const TypedArrayConstructor = typedArrayConstructors[type]; + + if (!TypedArrayConstructor) { + throw new Error(`target array ${type} is not supported`); } - // Handle cases where the targetBuffer is not backed by a SharedArrayBuffer - if ( - options.targetBuffer && - (!options.targetBuffer.arrayBuffer || - options.targetBuffer.arrayBuffer instanceof ArrayBuffer) - ) { - imageFrame.pixelData = pixelDataArray; + const imageFramePixelData = imageFrame.pixelData; + + if (length !== imageFramePixelData.length) { + throw new Error( + `target array for image does not have the same length (${length}) as the decoded image length (${imageFramePixelData.length}).` + ); } - const end = new Date().getTime(); + // TypedArray.Set is api level and ~50x faster than copying elements even for + // Arrays of different types, which aren't simply memcpy ops. + const typedArray = arrayBuffer + ? new TypedArrayConstructor(arrayBuffer, offset, length) + : new TypedArrayConstructor(length); - imageFrame.decodeTimeInMS = end - start; + typedArray.set(imageFramePixelData, 0); - return imageFrame; + // If need to scale, need to scale correct array. + pixelDataArray = typedArray; + return pixelDataArray; +} + +function _handlePreScaleSetup( + options, + minBeforeScale, + maxBeforeScale, + imageFrame +) { + const scalingParameters = options.preScale.scalingParameters; + _validateScalingParameters(scalingParameters); + + const { rescaleSlope, rescaleIntercept } = scalingParameters; + const areSlopeAndInterceptNumbers = + typeof rescaleSlope === 'number' && typeof rescaleIntercept === 'number'; + + let scaledMin = minBeforeScale; + let scaledMax = maxBeforeScale; + + if (areSlopeAndInterceptNumbers) { + scaledMin = rescaleSlope * minBeforeScale + rescaleIntercept; + scaledMax = rescaleSlope * maxBeforeScale + rescaleIntercept; + } + + return _getDefaultPixelDataArray(scaledMin, scaledMax, imageFrame); +} + +function _getDefaultPixelDataArray(min, max, imageFrame) { + const TypedArrayConstructor = getPixelDataTypeFromMinMax(min, max); + const typedArray = new TypedArrayConstructor(imageFrame.pixelData.length); + typedArray.set(imageFrame.pixelData, 0); + + return typedArray; +} + +function _validateScalingParameters(scalingParameters) { + if (!scalingParameters) { + throw new Error( + 'options.preScale.scalingParameters must be defined if preScale.enabled is true, and scalingParameters cannot be derived from the metadata providers.' + ); + } } export default decodeImageFrame; diff --git a/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLS.ts b/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLS.ts index 81fd75f62..0c9bf9535 100644 --- a/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLS.ts +++ b/packages/dicomImageLoader/src/shared/decoders/decodeJPEGLS.ts @@ -64,7 +64,6 @@ async function decodeAsync( imageInfo ): Promise { try { - console.debug('decodeAsync', compressedImageFrame, imageInfo); await initialize(); const decoder = local.decoder; diff --git a/packages/dicomImageLoader/src/shared/getMinMax.ts b/packages/dicomImageLoader/src/shared/getMinMax.ts index 02eb142a2..efc956f8a 100644 --- a/packages/dicomImageLoader/src/shared/getMinMax.ts +++ b/packages/dicomImageLoader/src/shared/getMinMax.ts @@ -1,4 +1,4 @@ -import { ByteArray } from 'dicom-parser'; +import { PixelDataTypedArray } from '../types'; /** * Calculate the minimum and maximum values in an Array @@ -6,7 +6,7 @@ import { ByteArray } from 'dicom-parser'; * @param {Number[]} storedPixelData * @return {{min: Number, max: Number}} */ -function getMinMax(storedPixelData: ByteArray | number[]): { +function getMinMax(storedPixelData: PixelDataTypedArray): { min: number; max: number; } { diff --git a/packages/dicomImageLoader/src/shared/getPixelDataTypeFromMinMax.ts b/packages/dicomImageLoader/src/shared/getPixelDataTypeFromMinMax.ts new file mode 100644 index 000000000..8ad218bcc --- /dev/null +++ b/packages/dicomImageLoader/src/shared/getPixelDataTypeFromMinMax.ts @@ -0,0 +1,28 @@ +import { PixelDataTypedArray } from '../types'; + +export default function getPixelDataTypeFromMinMax( + min: number, + max: number +): PixelDataTypedArray { + let pixelDataType; + + if (Number.isInteger(min) && Number.isInteger(max)) { + if (min >= 0) { + if (max <= 255) { + pixelDataType = Uint8Array; + } else if (max <= 65535) { + pixelDataType = Uint16Array; + } + } else { + if (min >= -128 && max <= 127) { + pixelDataType = Int8Array; + } else if (min >= -32768 && max <= 32767) { + pixelDataType = Int16Array; + } + } + } else { + pixelDataType = Float32Array; + } + + return pixelDataType; +} diff --git a/packages/dicomImageLoader/src/imageLoader/isColorImage.ts b/packages/dicomImageLoader/src/shared/isColorImage.ts similarity index 100% rename from packages/dicomImageLoader/src/imageLoader/isColorImage.ts rename to packages/dicomImageLoader/src/shared/isColorImage.ts diff --git a/packages/dicomImageLoader/src/shared/scaling/scaleArray.ts b/packages/dicomImageLoader/src/shared/scaling/scaleArray.ts index 9712779be..72fa0ec2f 100644 --- a/packages/dicomImageLoader/src/shared/scaling/scaleArray.ts +++ b/packages/dicomImageLoader/src/shared/scaling/scaleArray.ts @@ -1,5 +1,7 @@ +import { PixelDataTypedArray } from '../../types'; + export default function scaleArray( - array: number[], + array: PixelDataTypedArray, scalingParameters ): boolean { const arrayLength = array.length; diff --git a/packages/dicomImageLoader/src/types/ImageFrame.ts b/packages/dicomImageLoader/src/types/ImageFrame.ts index 390c74041..2fdbb815d 100644 --- a/packages/dicomImageLoader/src/types/ImageFrame.ts +++ b/packages/dicomImageLoader/src/types/ImageFrame.ts @@ -1,3 +1,5 @@ +import PixelDataTypedArray from './PixelDataTypedArray'; + interface ImageFrame { samplesPerPixel: number; photometricInterpretation: string; @@ -16,17 +18,22 @@ interface ImageFrame { greenPaletteColorLookupTableData: number[]; bluePaletteColorLookupTableData: number[]; // populated later after decoding - pixelData: - | Float32Array - | Int16Array - | Uint16Array - | Uint8Array - | Uint8ClampedArray - | undefined; + pixelData: PixelDataTypedArray; imageData?: ImageData; decodeTimeInMS?: number; pixelDataLength?: number; - preScale?: any; + preScale?: { + enabled?: boolean; + scalingParameters?: { + intercept: number; + slope: number; + modality?: string; + suvbw?: number; + }; + scaled?: boolean; + }; + minAfterScale?: number; + maxAfterScale?: number; } export default ImageFrame; diff --git a/packages/dicomImageLoader/src/types/PixelDataTypedArray.ts b/packages/dicomImageLoader/src/types/PixelDataTypedArray.ts new file mode 100644 index 000000000..d23b58040 --- /dev/null +++ b/packages/dicomImageLoader/src/types/PixelDataTypedArray.ts @@ -0,0 +1,9 @@ +type PixelDataTypedArray = + | Float32Array + | Int16Array + | Uint16Array + | Uint8Array + | Int8Array + | Uint8ClampedArray; + +export default PixelDataTypedArray; diff --git a/packages/dicomImageLoader/src/types/index.ts b/packages/dicomImageLoader/src/types/index.ts index dd5ed18d4..f31fa976b 100644 --- a/packages/dicomImageLoader/src/types/index.ts +++ b/packages/dicomImageLoader/src/types/index.ts @@ -1,4 +1,5 @@ import ImageFrame from './ImageFrame'; +import PixelDataTypedArray from './PixelDataTypedArray'; import { LoaderXhrRequestError, LoaderXhrRequestParams, @@ -74,4 +75,5 @@ export { WebWorkerDeferredObject, LoadRequestFunction, DICOMLoaderDataSetWithFetchMore, + PixelDataTypedArray, }; diff --git a/packages/docs/docusaurus.config.js b/packages/docs/docusaurus.config.js index bf00e9ef5..f8b7ded0e 100644 --- a/packages/docs/docusaurus.config.js +++ b/packages/docs/docusaurus.config.js @@ -145,7 +145,7 @@ module.exports = { }, { label: 'Slack', - href: 'https://join.slack.com/t/cornerstonejs/shared_invite/zt-1orclt43p-BqXxKHiuiHCtchuY9yY70Q', + href: 'https://join.slack.com/t/cornerstonejs/shared_invite/zt-1r8xb2zau-dOxlD6jit3TN0Uwf928w9Q', }, ], }, diff --git a/packages/docs/package.json b/packages/docs/package.json index ef0353215..3298b345e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -29,16 +29,16 @@ }, "dependencies": { "@cornerstonejs/core": "^0.43.1", - "@cornerstonejs/streaming-image-volume-loader": "^0.17.1", - "@cornerstonejs/tools": "^0.64.0", + "@cornerstonejs/streaming-image-volume-loader": "^0.17.0", + "@cornerstonejs/dicom-image-loader": "^0.3.1", + "@cornerstonejs/tools": "^0.63.0", "@docusaurus/core": "2.3.1", "@docusaurus/module-type-aliases": "2.3.1", "@docusaurus/preset-classic": "2.3.1", - "@kitware/vtk.js": "26.5.6", + "@kitware/vtk.js": "27.3.1", "@mdx-js/react": "^1.6.21", "@svgr/webpack": "^6.2.1", "clsx": "^1.1.1", - "cornerstone-wado-image-loader": "^4.10.2", "dcmjs": "^0.24.4", "detect-gpu": "^4.0.45", "dicom-parser": "^1.8.11", diff --git a/packages/docs/webpackConfigurationPlugin.js b/packages/docs/webpackConfigurationPlugin.js index e4a33b5d2..4c8d02af4 100644 --- a/packages/docs/webpackConfigurationPlugin.js +++ b/packages/docs/webpackConfigurationPlugin.js @@ -21,7 +21,7 @@ const CopyPlugin = require('copy-webpack-plugin'); // new CopyPlugin({ // patterns: [ // { -// from: '../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import/', +// from: '../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import/', // }, // ], // }), @@ -41,7 +41,7 @@ const CopyPlugin = require('copy-webpack-plugin'); // ), // // We use this alias and the CopyPlugin to support using the dynamic-import version // // of WADO Image Loader -// 'cornerstone-wado-image-loader': 'cornerstone-wado-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js', +// '@cornerstonejs/dicom-image-loader': '@cornerstonejs/dicom-image-loader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js', // }, // }, // devServer: { diff --git a/packages/streaming-image-volume-loader/package.json b/packages/streaming-image-volume-loader/package.json index 279de193f..1f3543c27 100644 --- a/packages/streaming-image-volume-loader/package.json +++ b/packages/streaming-image-volume-loader/package.json @@ -28,12 +28,8 @@ "@cornerstonejs/core": "^0.43.1", "cornerstone-wado-image-loader": "^4.10.2" }, - "peerDependencies": { - "@cornerstonejs/calculate-suv": "1.0.2" - }, "devDependencies": { - "@cornerstonejs/calculate-suv": "1.0.2", - "cornerstone-wado-image-loader": "^4.10.2" + "@cornerstonejs/calculate-suv": "1.0.2" }, "contributors": [ { diff --git a/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts b/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts index 57018f936..4e3ec7766 100644 --- a/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts +++ b/packages/streaming-image-volume-loader/src/BaseStreamingImageVolume.ts @@ -19,6 +19,7 @@ const { getMinMax } = csUtils; /** * Streaming Image Volume Class that extends ImageVolume base class. * It implements load method to load the imageIds and insert them into the volume. + * */ export default class BaseStreamingImageVolume extends ImageVolume { private framesLoaded = 0; diff --git a/packages/tools/examples/dynamicCINETool/index.ts b/packages/tools/examples/dynamicCINETool/index.ts index daf700fdc..8bca6a3dc 100644 --- a/packages/tools/examples/dynamicCINETool/index.ts +++ b/packages/tools/examples/dynamicCINETool/index.ts @@ -1,4 +1,4 @@ -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; import { RenderingEngine, Types, @@ -229,7 +229,7 @@ function initViewports(volume, elements) { } async function createVolume(numTimePoints: number): any { - const { metaDataManager } = cornerstoneWADOImageLoader.wadors; + const { metaDataManager } = cornerstoneDICOMImageLoader.wadors; if (numTimePoints < 1 || numTimePoints > MAX_NUM_TIMEPOINTS) { throw new Error('numTimePoints is out of range'); diff --git a/packages/tools/examples/dynamicPetCt/index.ts b/packages/tools/examples/dynamicPetCt/index.ts index ac9af4c00..82a24287e 100644 --- a/packages/tools/examples/dynamicPetCt/index.ts +++ b/packages/tools/examples/dynamicPetCt/index.ts @@ -1,4 +1,4 @@ -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; import { RenderingEngine, Types, @@ -522,7 +522,7 @@ function setUpSynchronizers() { } async function setUpDisplay() { - const { metaDataManager } = cornerstoneWADOImageLoader.wadors; + const { metaDataManager } = cornerstoneDICOMImageLoader.wadors; const wadoRsRoot = 'https://d28o5kq0jsoob5.cloudfront.net/dicomweb'; const StudyInstanceUID = '1.3.6.1.4.1.12842.1.1.14.3.20220915.105557.468.2963630849'; diff --git a/packages/tools/examples/generateImageFromTimeData/index.ts b/packages/tools/examples/generateImageFromTimeData/index.ts index e6f893943..72137dbb8 100644 --- a/packages/tools/examples/generateImageFromTimeData/index.ts +++ b/packages/tools/examples/generateImageFromTimeData/index.ts @@ -15,7 +15,7 @@ import { addButtonToToolbar, } from '../../../../utils/demo/helpers'; import * as cornerstoneTools from '@cornerstonejs/tools'; -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; const { SegmentationDisplayTool, @@ -23,7 +23,7 @@ const { Enums: csToolsEnums, PanTool, StackScrollMouseWheelTool, - ZoomTool + ZoomTool, } = cornerstoneTools; const { MouseBindings } = csToolsEnums; @@ -223,7 +223,7 @@ async function run() { ], }); - const { metaDataManager } = cornerstoneWADOImageLoader.wadors; + const { metaDataManager } = cornerstoneDICOMImageLoader.wadors; // Get Cornerstone imageIds and fetch metadata into RAM let imageIds = await createImageIdsAndCacheMetaData({ diff --git a/packages/tools/examples/local/index.ts b/packages/tools/examples/local/index.ts index 4ff4a8366..0aecded7d 100644 --- a/packages/tools/examples/local/index.ts +++ b/packages/tools/examples/local/index.ts @@ -1,5 +1,5 @@ import { RenderingEngine, Types, Enums, metaData } from '@cornerstonejs/core'; -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; import * as cornerstoneTools from '@cornerstonejs/tools'; import htmlSetup from './htmlSetup'; import uids from './uids'; @@ -55,7 +55,7 @@ document // Add the file to the cornerstoneFileImageLoader and get unique // number for that file const file = e.target.files[0]; - const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file); + const imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(file); loadAndViewImage(imageId); }); @@ -143,7 +143,7 @@ function handleFileSelect(evt) { // this UI is only built for a single file so just dump the first one const file = files[0]; - const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file); + const imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(file); loadAndViewImage(imageId); } diff --git a/packages/tools/examples/localCPU/index.ts b/packages/tools/examples/localCPU/index.ts index bafa800d2..e56a49b97 100644 --- a/packages/tools/examples/localCPU/index.ts +++ b/packages/tools/examples/localCPU/index.ts @@ -1,5 +1,5 @@ import { RenderingEngine, Types, Enums, metaData } from '@cornerstonejs/core'; -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; import * as cornerstoneTools from '@cornerstonejs/tools'; import htmlSetup from '../local/htmlSetup'; import uids from '../local/uids'; @@ -53,7 +53,7 @@ document // Add the file to the cornerstoneFileImageLoader and get unique // number for that file const file = e.target.files[0]; - const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file); + const imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(file); loadAndViewImage(imageId); }); @@ -145,7 +145,7 @@ function handleFileSelect(evt) { // this UI is only built for a single file so just dump the first one const file = files[0]; - const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file); + const imageId = cornerstoneDICOMImageLoader.wadouri.fileManager.add(file); loadAndViewImage(imageId); } diff --git a/packages/tools/package.json b/packages/tools/package.json index f58543b46..52324b0f8 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -31,7 +31,7 @@ "lodash.get": "^4.4.2" }, "peerDependencies": { - "@kitware/vtk.js": "26.5.6", + "@kitware/vtk.js": "27.3.1", "@types/d3-array": "^3.0.3", "@types/d3-interpolate": "^3.0.1", "d3-array": "^3.0.3", @@ -39,7 +39,7 @@ "gl-matrix": "^3.4.3" }, "devDependencies": { - "@kitware/vtk.js": "26.5.6" + "@kitware/vtk.js": "27.3.1" }, "contributors": [ { diff --git a/packages/tools/src/tools/base/BaseTool.ts b/packages/tools/src/tools/base/BaseTool.ts index 00068d187..0c72bfe86 100644 --- a/packages/tools/src/tools/base/BaseTool.ts +++ b/packages/tools/src/tools/base/BaseTool.ts @@ -1,4 +1,8 @@ -import { StackViewport, VolumeViewport, utilities } from '@cornerstonejs/core'; +import { + StackViewport, + utilities, + BaseVolumeViewport, +} from '@cornerstonejs/core'; import { Types } from '@cornerstonejs/core'; import { ToolModes } from '../../enums'; import { InteractionTypes, ToolProps, PublicToolProps } from '../../types'; @@ -199,7 +203,7 @@ abstract class BaseTool implements IBaseTool { protected getTargetId(viewport: Types.IViewport): string | undefined { if (viewport instanceof StackViewport) { return `imageId:${viewport.getCurrentImageId()}`; - } else if (viewport instanceof VolumeViewport) { + } else if (viewport instanceof BaseVolumeViewport) { return `volumeId:${this.getTargetVolumeId(viewport)}`; } else { throw new Error( diff --git a/packages/tools/src/tools/displayTools/Contour/addContourSetsToElement.ts b/packages/tools/src/tools/displayTools/Contour/addContourSetsToElement.ts index 1bc6aa92c..0ee78cf52 100644 --- a/packages/tools/src/tools/displayTools/Contour/addContourSetsToElement.ts +++ b/packages/tools/src/tools/displayTools/Contour/addContourSetsToElement.ts @@ -104,7 +104,10 @@ export function addContourSetsToElement( actor.setForceOpaque(true); - viewport.addActor({ uid: contourActorUID, actor }); + viewport.addActor({ + uid: contourActorUID, + actor: actor as unknown as Types.Actor, + }); viewport.resetCamera(); viewport.render(); } diff --git a/packages/tools/src/tools/displayTools/Contour/updateContourSets.ts b/packages/tools/src/tools/displayTools/Contour/updateContourSets.ts index 34eed0224..7ad080de3 100644 --- a/packages/tools/src/tools/displayTools/Contour/updateContourSets.ts +++ b/packages/tools/src/tools/displayTools/Contour/updateContourSets.ts @@ -34,7 +34,9 @@ export function updateContourSets( const newOutlineWithActive = newContourConfig.outlineWidthActive; if (cachedConfig?.outlineWidthActive !== newOutlineWithActive) { - (actor as vtkActor).getProperty().setLineWidth(newOutlineWithActive); + (actor as unknown as vtkActor) + .getProperty() + .setLineWidth(newOutlineWithActive); setConfigCache( segmentationRepresentationUID, @@ -44,7 +46,7 @@ export function updateContourSets( ); } - const mapper = (actor as vtkActor).getMapper(); + const mapper = (actor as unknown as vtkActor).getMapper(); const lut = mapper.getLookupTable(); const segmentsToSetToInvisible = []; diff --git a/utils/ExampleRunner/build-all-examples-cli.js b/utils/ExampleRunner/build-all-examples-cli.js index b16cef3b3..a0c8cbac9 100644 --- a/utils/ExampleRunner/build-all-examples-cli.js +++ b/utils/ExampleRunner/build-all-examples-cli.js @@ -59,6 +59,10 @@ if (options.fromRoot === true) { path: 'packages/streaming-image-volume-loader/examples', regexp: 'index.ts', }, + { + path: 'packages/dicomImageLoader/examples', + regexp: 'index.ts', + }, ], }; } else { @@ -112,6 +116,14 @@ if (configuration.examples) { return; } + // say name of running example + const currentWD = process.cwd(); + // run the build for dicom image loader + shell.cd('../../dicomImageLoader'); + shell.exec(`yarn run webpack:dynamic-import`); + + shell.cd(currentWD); + const examplePaths = Object.values(allExamplePaths); const exampleNames = Object.keys(allExamplePaths); const conf = buildConfig( @@ -140,6 +152,8 @@ if (configuration.examples) { if (options.build == true) { shell.exec(`webpack --progress --config ${webpackConfigPath}`); } else { - shell.exec(`webpack serve --progress --host 0.0.0.0 --config ${webpackConfigPath}`); + shell.exec( + `webpack serve --progress --host 0.0.0.0 --config ${webpackConfigPath}` + ); } } diff --git a/utils/ExampleRunner/example-info.json b/utils/ExampleRunner/example-info.json index 4c49e2670..8c281adbd 100644 --- a/utils/ExampleRunner/example-info.json +++ b/utils/ExampleRunner/example-info.json @@ -14,6 +14,9 @@ }, "tools-advanced": { "description": "Advanced usage of Tools library" + }, + "dicom-image-loader": { + "description": "usage of cornerstone dicom image loader" } }, "examplesByCategory": { @@ -260,6 +263,12 @@ "name": "Generate 3D Volume From 4D Data", "description": "Demostrates generating a 3D volume from 4D data using subtract, average or sum." } + }, + "dicom-image-loader": { + "dicomImageLoaderWADOURI": { + "name": "WADO-URI (DICOM P10)", + "description": "WADO-URI (DICOM P10 via HTTP GET) with different codecs" + } } } } diff --git a/utils/ExampleRunner/example-runner-cli.js b/utils/ExampleRunner/example-runner-cli.js index f47018bd0..7f134b177 100755 --- a/utils/ExampleRunner/example-runner-cli.js +++ b/utils/ExampleRunner/example-runner-cli.js @@ -73,6 +73,10 @@ const configuration = { path: 'packages/streaming-image-volume-loader/examples', regexp: 'index.ts', }, + { + path: 'packages/dicomImageLoader/examples', + regexp: 'index.ts', + }, ], }; @@ -158,6 +162,10 @@ if (configuration.examples) { // say name of running example console.log(`\n=> Running examples ${filterExamples.join(', ')}\n`); + // run the build for dicom image loader + + // shell.cd('../../dicomImageLoader'); + // shell.exec(`yarn run webpack:dynamic-import`); if (buildExample) { var exBasePath = null; @@ -178,8 +186,11 @@ if (configuration.examples) { // console.log('conf', conf); shell.ShellString(conf).to(webpackConfigPath); + shell.cd(exBasePath); - shell.exec(`webpack serve --host 0.0.0.0 --progress --config ${webpackConfigPath}`); + shell.exec( + `webpack serve --host 0.0.0.0 --progress --config ${webpackConfigPath}` + ); } else { console.log('=> To run an example:'); console.log(' $ npm run example -- PUT_YOUR_EXAMPLE_NAME_HERE\n'); diff --git a/utils/ExampleRunner/template-config.js b/utils/ExampleRunner/template-config.js index cc64d8473..7f1a99f91 100644 --- a/utils/ExampleRunner/template-config.js +++ b/utils/ExampleRunner/template-config.js @@ -5,6 +5,9 @@ const csToolsBasePath = path.resolve('packages/tools/src/index'); const csStreamingBasePath = path.resolve( 'packages/streaming-image-volume-loader/src/index' ); +const csDICOMImageLoaderDistPath = path.resolve( + 'packages/dicomImageLoader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js' +); module.exports = function buildConfig( name, @@ -43,7 +46,7 @@ module.exports = { patterns: [ { from: - '../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import', + '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import', to: '${destPath.replace(/\\/g, '/')}', }, ], @@ -71,7 +74,10 @@ module.exports = { )}', // We use this alias and the CopyPlugin to support using the dynamic-import version // of WADO Image Loader - 'cornerstone-wado-image-loader': 'cornerstone-wado-image-loader/dist/dynamic-import/cornerstoneWADOImageLoader.min.js', + '@cornerstonejs/dicom-image-loader': '${csDICOMImageLoaderDistPath.replace( + /\\/g, + '/' + )}' }, modules, extensions: ['.ts', '.tsx', '.js', '.jsx'], @@ -84,7 +90,7 @@ module.exports = { devServer: { hot: true, open: false, - port: 3000, + port: 3001, historyApiFallback: true, headers: { "Cross-Origin-Embedder-Policy": "require-corp", diff --git a/utils/ExampleRunner/template-multiexample-config.js b/utils/ExampleRunner/template-multiexample-config.js index 5a350b7f8..29a7d3c30 100644 --- a/utils/ExampleRunner/template-multiexample-config.js +++ b/utils/ExampleRunner/template-multiexample-config.js @@ -8,6 +8,9 @@ const csToolsBasePath = path.resolve('./packages/tools/src/index'); const csStreamingBasePath = path.resolve( './packages/streaming-image-volume-loader/src/index' ); +const csDICOMImageLoaderDistPath = path.resolve( + './packages/dicomImageLoader/dist/dynamic-import/cornerstoneDICOMImageLoader.min.js' +); module.exports = function buildConfig(names, exampleBasePaths, destPath, root) { let multiExampleEntryPoints = ''; @@ -74,7 +77,7 @@ module.exports = { )}" }, { from: - '../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import', + '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import', to: '${destPath.replace(/\\/g, '/')}', }, ], @@ -100,7 +103,10 @@ module.exports = { )}', // We use this alias and the CopyPlugin to support using the dynamic-import version // of WADO Image Loader - 'cornerstone-wado-image-loader': 'cornerstone-wado-image-loader/dist/dynamic-import/cornerstoneWADOImageLoader.min.js', + '@cornerstonejs/dicom-image-loader': '${csDICOMImageLoaderDistPath.replace( + /\\/g, + '/' + )}' }, modules, extensions: ['.ts', '.tsx', '.js', '.jsx'], diff --git a/utils/demo/helpers/convertMultiframeImageIds.js b/utils/demo/helpers/convertMultiframeImageIds.js index c49285f44..41b2d39d3 100644 --- a/utils/demo/helpers/convertMultiframeImageIds.js +++ b/utils/demo/helpers/convertMultiframeImageIds.js @@ -1,11 +1,11 @@ import { metaData } from '@cornerstonejs/core'; -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; /** * preloads imageIds metadata in memory **/ async function prefetchMetadataInformation(imageIdsToPrefetch) { for (let i = 0; i < imageIdsToPrefetch.length; i++) { - await cornerstoneWADOImageLoader.wadouri.loadImage(imageIdsToPrefetch[i]) + await cornerstoneDICOMImageLoader.wadouri.loadImage(imageIdsToPrefetch[i]) .promise; } } diff --git a/utils/demo/helpers/createImageIdsAndCacheMetaData.js b/utils/demo/helpers/createImageIdsAndCacheMetaData.js index 53521296d..d0f0fc3f6 100644 --- a/utils/demo/helpers/createImageIdsAndCacheMetaData.js +++ b/utils/demo/helpers/createImageIdsAndCacheMetaData.js @@ -3,7 +3,7 @@ import dcmjs from 'dcmjs'; import { calculateSUVScalingFactors } from '@cornerstonejs/calculate-suv'; import { getPTImageIdInstanceMetadata } from './getPTImageIdInstanceMetadata'; import { utilities } from '@cornerstonejs/core'; -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; import ptScalingMetaDataProvider from './ptScalingMetaDataProvider'; import getPixelSpacingInformation from './getPixelSpacingInformation'; @@ -27,7 +27,9 @@ const { calibratedPixelSpacingMetadataProvider } = utilities; export default async function createImageIdsAndCacheMetaData({ StudyInstanceUID, SeriesInstanceUID, + SOPInstanceUID, wadoRsRoot, + client = null, }) { const SOP_INSTANCE_UID = '00080018'; const SERIES_INSTANCE_UID = '0020000E'; @@ -38,12 +40,13 @@ export default async function createImageIdsAndCacheMetaData({ seriesInstanceUID: SeriesInstanceUID, }; - const client = new api.DICOMwebClient({ url: wadoRsRoot }); + client = client || new api.DICOMwebClient({ url: wadoRsRoot }); const instances = await client.retrieveSeriesMetadata(studySearchOptions); const modality = instances[0][MODALITY].Value[0]; let imageIds = instances.map((instanceMetaData) => { const SeriesInstanceUID = instanceMetaData[SERIES_INSTANCE_UID].Value[0]; - const SOPInstanceUID = instanceMetaData[SOP_INSTANCE_UID].Value[0]; + const SOPInstanceUIDToUse = + SOPInstanceUID || instanceMetaData[SOP_INSTANCE_UID].Value[0]; const prefix = 'wadors:'; @@ -55,10 +58,10 @@ export default async function createImageIdsAndCacheMetaData({ '/series/' + SeriesInstanceUID + '/instances/' + - SOPInstanceUID + + SOPInstanceUIDToUse + '/frames/1'; - cornerstoneWADOImageLoader.wadors.metaDataManager.add( + cornerstoneDICOMImageLoader.wadors.metaDataManager.add( imageId, instanceMetaData ); @@ -71,7 +74,7 @@ export default async function createImageIdsAndCacheMetaData({ imageIds.forEach((imageId) => { let instanceMetaData = - cornerstoneWADOImageLoader.wadors.metaDataManager.get(imageId); + cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId); // It was using JSON.parse(JSON.stringify(...)) before but it is 8x slower instanceMetaData = removeInvalidTags(instanceMetaData); diff --git a/utils/demo/helpers/initCornerstoneDICOMImageLoader.js b/utils/demo/helpers/initCornerstoneDICOMImageLoader.js index eb7411ba6..1e683f215 100644 --- a/utils/demo/helpers/initCornerstoneDICOMImageLoader.js +++ b/utils/demo/helpers/initCornerstoneDICOMImageLoader.js @@ -1,8 +1,7 @@ import dicomParser from 'dicom-parser'; import * as cornerstone from '@cornerstonejs/core'; import * as cornerstoneTools from '@cornerstonejs/tools'; - -import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader'; +import cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader'; window.cornerstone = cornerstone; window.cornerstoneTools = cornerstoneTools; @@ -10,9 +9,9 @@ const { preferSizeOverAccuracy, useNorm16Texture } = cornerstone.getConfiguration().rendering; export default function initCornerstoneDICOMImageLoader() { - cornerstoneWADOImageLoader.external.cornerstone = cornerstone; - cornerstoneWADOImageLoader.external.dicomParser = dicomParser; - cornerstoneWADOImageLoader.configure({ + cornerstoneDICOMImageLoader.external.cornerstone = cornerstone; + cornerstoneDICOMImageLoader.external.dicomParser = dicomParser; + cornerstoneDICOMImageLoader.configure({ useWebWorkers: true, decodeConfig: { convertFloatPixelDataToInt: false, @@ -37,5 +36,5 @@ export default function initCornerstoneDICOMImageLoader() { }, }; - cornerstoneWADOImageLoader.webWorkerManager.initialize(config); + cornerstoneDICOMImageLoader.webWorkerManager.initialize(config); } diff --git a/yarn.lock b/yarn.lock index 2bedae42e..a60bd1456 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3430,10 +3430,10 @@ merge-source-map "^1.1.0" schema-utils "^2.7.0" -"@kitware/vtk.js@26.5.6": - version "26.5.6" - resolved "https://registry.npmjs.org/@kitware/vtk.js/-/vtk.js-26.5.6.tgz#58fb474ce6929c18988c53fc993d02a463fc7ce1" - integrity sha512-1bTeo9xMa0hmeuAeRPp6JB98RuF36VCPTlq0A7I/zTmhP4XIsPQf/u468qE8Z3kXSb3RkCCNYo04GgaKlQr5VQ== +"@kitware/vtk.js@27.3.1": + version "27.3.1" + resolved "https://registry.npmjs.org/@kitware/vtk.js/-/vtk.js-27.3.1.tgz#65004b041642663da94e6a488ebb813576e7de7d" + integrity sha512-GvAXdOKtDDbkaSdO+UhKnDST4CW1C3fHgaDvA0wn1ZWTLm/6SFAMzarTjpzsVGYCPoEYIhCAAlBQ7K7aDcu3Vg== dependencies: "@babel/runtime" "7.17.9" commander "9.2.0" @@ -8896,9 +8896,9 @@ core-util-is@~1.0.0: integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== cornerstone-wado-image-loader@^4.10.2: - version "4.10.2" - resolved "https://registry.npmjs.org/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.10.2.tgz#139956654324fd2b01fe5b4900d0f4ed8c52633d" - integrity sha512-qj9dThELqYCm3jAZfg9qnUl8d76gngOl55kYJabY5lh/dFeVIxno/hYxy3ydE7RtG2c/TUGXb+EMUl0CJSqKBQ== + version "4.13.1" + resolved "https://registry.npmjs.org/cornerstone-wado-image-loader/-/cornerstone-wado-image-loader-4.13.1.tgz#99b90e15834b6e1b5f84f0495270400b18b07e8e" + integrity sha512-IKAeI2JLG2HFBFF+ANHYyHxOJmjbys+wfX15flC0bz3D9AhDU1HX34qJMRh+HMMZ/Jcqjc4EJJq1ttR0OwMdig== dependencies: "@babel/eslint-parser" "^7.19.1" "@cornerstonejs/codec-charls" "^1.2.3"