@cornerstonejs/core
Version:
Cornerstone3D Core
383 lines (382 loc) • 14.7 kB
JavaScript
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' },
});
}