UNPKG

@cornerstonejs/core

Version:
383 lines (382 loc) • 14.7 kB
import cache from '../cache/cache.js'; import Events from '../enums/Events.js'; import MetadataModules from '../enums/MetadataModules.js'; import { ImageQualityStatus } from '../enums/index.js'; import eventTarget from '../eventTarget.js'; import genericMetadataProvider from '../utilities/genericMetadataProvider.js'; import { getBufferConfiguration } from '../utilities/getBufferConfiguration.js'; import triggerEvent from '../utilities/triggerEvent.js'; import uuidv4 from '../utilities/uuidv4.js'; import VoxelManager from '../utilities/VoxelManager.js'; import imageLoadPoolManager from '../requestPool/imageLoadPoolManager.js'; import * as metaData from '../metaData.js'; import VoxelManagerEnum from '../enums/VoxelManagerEnum.js'; const imageLoaders = {}; let unknownImageLoader; function getRequestedImageQualityStatus(options) { return (options.retrieveOptions?.imageQualityStatus ?? ImageQualityStatus.FULL_RESOLUTION); } function loadImageFromImageLoader(imageId, options) { const cachedImageLoadObject = !options.ignoreCache && cache.getImageLoadObject(imageId); if (cachedImageLoadObject) { handleImageLoadPromise(cachedImageLoadObject.promise, imageId); return cachedImageLoadObject; } const cachedImage = !options.ignoreCache && cache.getImage(imageId, getRequestedImageQualityStatus(options)); if (cachedImage) { const imageLoadObject = { promise: Promise.resolve(cachedImage), }; handleImageLoadPromise(imageLoadObject.promise, imageId); return imageLoadObject; } const scheme = imageId.split(':')[0]; const loader = imageLoaders[scheme] || unknownImageLoader; if (!loader) { throw new Error(`loadImageFromImageLoader: No image loader found for scheme '${scheme}'`); } const imageLoadObject = loader(imageId, options); handleImageLoadPromise(imageLoadObject.promise, imageId); return imageLoadObject; } function handleImageLoadPromise(imagePromise, imageId) { Promise.resolve(imagePromise) .then((image) => { ensureVoxelManager(image); triggerEvent(eventTarget, Events.IMAGE_LOADED, { image }); }) .catch((error) => { const errorDetails = { imageId, error, }; triggerEvent(eventTarget, Events.IMAGE_LOAD_FAILED, errorDetails); }); } function ensureVoxelManager(image) { if (!image.voxelManager) { const { width, height, numberOfComponents } = image; const voxelManager = VoxelManager.createImageVoxelManager({ scalarData: image.getPixelData(), width, height, numberOfComponents, }); image.voxelManager = voxelManager; image.getPixelData = () => voxelManager.getScalarData(); delete image.imageFrame.pixelData; } } export function loadImage(imageId, options = { priority: 0, requestType: 'prefetch' }) { if (imageId === undefined) { throw new Error('loadImage: parameter imageId must not be undefined'); } return loadImageFromImageLoader(imageId, options).promise; } export function loadAndCacheImage(imageId, options = { priority: 0, requestType: 'prefetch' }) { if (imageId === undefined) { throw new Error('loadAndCacheImage: parameter imageId must not be undefined'); } const imageLoadObject = loadImageFromImageLoader(imageId, options); if (!cache.getImageLoadObject(imageId)) { cache.putImageLoadObject(imageId, imageLoadObject); } return imageLoadObject.promise; } export function loadAndCacheImages(imageIds, options = { priority: 0, requestType: 'prefetch' }) { if (!imageIds || imageIds.length === 0) { throw new Error('loadAndCacheImages: parameter imageIds must be list of image Ids'); } const allPromises = imageIds.map((imageId) => { return loadAndCacheImage(imageId, options); }); return allPromises; } export function createAndCacheDerivedImage(referencedImageId, options = {}) { if (referencedImageId === undefined) { throw new Error('createAndCacheDerivedImage: parameter imageId must not be undefined'); } if (options.imageId === undefined) { options.imageId = `derived:${uuidv4()}`; } const { imageId, skipCreateBuffer, onCacheAdd, voxelRepresentation } = options; const imagePlaneModule = metaData.get(MetadataModules.IMAGE_PLANE, referencedImageId); const length = imagePlaneModule.rows * imagePlaneModule.columns; const { TypedArrayConstructor } = getBufferConfiguration(options.targetBuffer?.type, length); const imageScalarData = new TypedArrayConstructor(skipCreateBuffer ? 1 : length); const derivedImageId = imageId; const referencedImagePlaneMetadata = metaData.get(MetadataModules.IMAGE_PLANE, referencedImageId); genericMetadataProvider.add(derivedImageId, { type: MetadataModules.IMAGE_PLANE, metadata: referencedImagePlaneMetadata, }); const referencedImageGeneralSeriesMetadata = metaData.get(MetadataModules.GENERAL_SERIES, referencedImageId); genericMetadataProvider.add(derivedImageId, { type: MetadataModules.GENERAL_SERIES, metadata: referencedImageGeneralSeriesMetadata, }); genericMetadataProvider.add(derivedImageId, { type: MetadataModules.GENERAL_IMAGE, metadata: { instanceNumber: options.instanceNumber, }, }); const imagePixelModule = metaData.get(MetadataModules.IMAGE_PIXEL, referencedImageId); genericMetadataProvider.add(derivedImageId, { type: MetadataModules.IMAGE_PIXEL, metadata: { ...imagePixelModule, bitsAllocated: 8, bitsStored: 8, highBit: 7, samplesPerPixel: 1, pixelRepresentation: 0, }, }); const localImage = createAndCacheLocalImage(imageId, { scalarData: imageScalarData, onCacheAdd, skipCreateBuffer, targetBuffer: { type: imageScalarData.constructor.name, }, voxelRepresentation, dimensions: [imagePlaneModule.columns, imagePlaneModule.rows], spacing: [ imagePlaneModule.columnPixelSpacing, imagePlaneModule.rowPixelSpacing, ], origin: imagePlaneModule.imagePositionPatient, direction: imagePlaneModule.imageOrientationPatient, frameOfReferenceUID: imagePlaneModule.frameOfReferenceUID, referencedImageId: referencedImageId, }); localImage.referencedImageId = referencedImageId; if (!cache.getImageLoadObject(imageId)) { cache.putImageSync(imageId, localImage); } return localImage; } export function createAndCacheDerivedImages(referencedImageIds, options = {}) { if (referencedImageIds.length === 0) { throw new Error('createAndCacheDerivedImages: parameter imageIds must be list of image Ids'); } const derivedImageIds = []; const images = referencedImageIds.map((referencedImageId, index) => { const newOptions = { imageId: options?.getDerivedImageId?.(referencedImageId) || `derived:${uuidv4()}`, ...options, }; derivedImageIds.push(newOptions.imageId); return createAndCacheDerivedImage(referencedImageId, { ...newOptions, instanceNumber: index + 1, }); }); return images; } export function createAndCacheLocalImage(imageId, options) { const { scalarData, origin, direction, targetBuffer, skipCreateBuffer, onCacheAdd, frameOfReferenceUID, voxelRepresentation, referencedImageId, } = options; const dimensions = options.dimensions; const spacing = options.spacing; if (!dimensions || !spacing) { throw new Error('createAndCacheLocalImage: dimensions and spacing are required'); } const width = dimensions[0]; const height = dimensions[1]; const columnPixelSpacing = spacing[0]; const rowPixelSpacing = spacing[1]; const imagePlaneModule = { frameOfReferenceUID, rows: height, columns: width, imageOrientationPatient: direction ?? [1, 0, 0, 0, 1, 0], rowCosines: direction ? direction.slice(0, 3) : [1, 0, 0], columnCosines: direction ? direction.slice(3, 6) : [0, 1, 0], imagePositionPatient: origin ?? [0, 0, 0], pixelSpacing: [rowPixelSpacing, columnPixelSpacing], rowPixelSpacing: rowPixelSpacing, columnPixelSpacing: columnPixelSpacing, }; const length = width * height; const numberOfComponents = scalarData.length / length; let scalarDataToUse; if (scalarData) { if (!(scalarData instanceof Uint8Array || scalarData instanceof Float32Array || scalarData instanceof Uint16Array || scalarData instanceof Int16Array)) { throw new Error('createAndCacheLocalImage: scalarData must be of type Uint8Array, Uint16Array, Int16Array or Float32Array'); } scalarDataToUse = scalarData; } else if (!skipCreateBuffer) { const { TypedArrayConstructor } = getBufferConfiguration(targetBuffer?.type, length); const imageScalarData = new TypedArrayConstructor(length); scalarDataToUse = imageScalarData; } let bitsAllocated, bitsStored, highBit; if (scalarDataToUse instanceof Uint8Array) { bitsAllocated = 8; bitsStored = 8; highBit = 7; } else if (scalarDataToUse instanceof Uint16Array) { bitsAllocated = 16; bitsStored = 16; highBit = 15; } else if (scalarDataToUse instanceof Int16Array) { bitsAllocated = 16; bitsStored = 16; highBit = 15; } else if (scalarDataToUse instanceof Float32Array) { bitsAllocated = 32; bitsStored = 32; highBit = 31; } else { throw new Error('Unsupported scalarData type'); } const imagePixelModule = { samplesPerPixel: 1, photometricInterpretation: scalarDataToUse.length > dimensions[0] * dimensions[1] ? 'RGB' : 'MONOCHROME2', rows: height, columns: width, bitsAllocated, bitsStored, highBit, }; const metadata = { imagePlaneModule, imagePixelModule, }; [MetadataModules.IMAGE_PLANE, MetadataModules.IMAGE_PIXEL].forEach((type) => { genericMetadataProvider.add(imageId, { type, metadata: metadata[type] || {}, }); }); const id = imageId; const voxelManager = (voxelRepresentation === VoxelManagerEnum.RLE && VoxelManager.createRLEImageVoxelManager({ dimensions, id })) || VoxelManager.createImageVoxelManager({ height, width, numberOfComponents, scalarData: scalarDataToUse, id, }); let minPixelValue = scalarDataToUse[0]; let maxPixelValue = scalarDataToUse[0]; for (let i = 1; i < scalarDataToUse.length; i++) { if (scalarDataToUse[i] < minPixelValue) { minPixelValue = scalarDataToUse[i]; } if (scalarDataToUse[i] > maxPixelValue) { maxPixelValue = scalarDataToUse[i]; } } const image = { imageId: imageId, intercept: 0, windowCenter: 0, windowWidth: 0, color: imagePixelModule.photometricInterpretation === 'RGB', numberOfComponents: imagePixelModule.samplesPerPixel, dataType: targetBuffer?.type, slope: 1, minPixelValue, maxPixelValue, rows: imagePixelModule.rows, columns: imagePixelModule.columns, getCanvas: undefined, height: imagePixelModule.rows, width: imagePixelModule.columns, rgba: undefined, columnPixelSpacing: imagePlaneModule.columnPixelSpacing, rowPixelSpacing: imagePlaneModule.rowPixelSpacing, FrameOfReferenceUID: imagePlaneModule.frameOfReferenceUID, invert: false, getPixelData: () => voxelManager.getScalarData(), voxelManager, sizeInBytes: scalarData.byteLength, referencedImageId, }; onCacheAdd?.(image); cache.putImageSync(image.imageId, image); return image; } export function cancelLoadImage(imageId) { const filterFunction = ({ additionalDetails }) => { if (additionalDetails.imageId) { return additionalDetails.imageId !== imageId; } return true; }; imageLoadPoolManager.filterRequests(filterFunction); const imageLoadObject = cache.getImageLoadObject(imageId); if (imageLoadObject) { imageLoadObject.cancelFn(); } } export function cancelLoadImages(imageIds) { imageIds.forEach((imageId) => { cancelLoadImage(imageId); }); } export function cancelLoadAll() { const requestPool = imageLoadPoolManager.getRequestPool(); Object.keys(requestPool).forEach((type) => { const requests = requestPool[type]; Object.keys(requests).forEach((priority) => { const requestDetails = requests[priority].pop(); if (!requestDetails) { return; } const additionalDetails = requestDetails.additionalDetails; const { imageId, volumeId } = additionalDetails; let loadObject; if (imageId) { loadObject = cache.getImageLoadObject(imageId); } else if (volumeId) { loadObject = cache.getVolumeLoadObject(volumeId); } if (loadObject) { loadObject.cancel(); } }); imageLoadPoolManager.clearRequestStack(type); }); } export function registerImageLoader(scheme, imageLoader) { imageLoaders[scheme] = imageLoader; } export function registerUnknownImageLoader(imageLoader) { const oldImageLoader = unknownImageLoader; unknownImageLoader = imageLoader; return oldImageLoader; } export function unregisterAllImageLoaders() { Object.keys(imageLoaders).forEach((imageLoader) => delete imageLoaders[imageLoader]); unknownImageLoader = undefined; } export function createAndCacheDerivedLabelmapImages(referencedImageIds, options = {}) { return createAndCacheDerivedImages(referencedImageIds, { ...options, targetBuffer: { type: 'Uint8Array' }, }); } export function createAndCacheDerivedLabelmapImage(referencedImageId, options = {}) { return createAndCacheDerivedImage(referencedImageId, { ...options, targetBuffer: { type: 'Uint8Array' }, }); }