@cornerstonejs/core
Version:
Cornerstone3D Core
358 lines (357 loc) • 14.3 kB
JavaScript
import * as metaData from '../../metaData.js';
import { Events, ImageQualityStatus, MetadataModules, RequestType, } from '../../enums/index.js';
import eventTarget from '../../eventTarget.js';
import imageLoadPoolManager from '../../requestPool/imageLoadPoolManager.js';
import ProgressiveIterator from '../../utilities/ProgressiveIterator.js';
import imageRetrieveMetadataProvider from '../../utilities/imageRetrieveMetadataProvider.js';
import { hasFloatScalingParameters } from '../../utilities/hasFloatScalingParameters.js';
import autoLoad from '../../utilities/autoLoad.js';
import triggerEvent from '../../utilities/triggerEvent.js';
import ImageVolume from './ImageVolume.js';
import ProgressiveRetrieveImages from '../../loaders/ProgressiveRetrieveImages.js';
import { canRenderFloatTextures } from '../../init.js';
import { loadAndCacheImage } from '../../loaders/imageLoader.js';
const requestTypeDefault = RequestType.Prefetch;
export class BaseStreamingImageVolume extends ImageVolume {
constructor(imageVolumeProperties, streamingProperties) {
super(imageVolumeProperties);
this.framesLoaded = 0;
this.framesProcessed = 0;
this.framesUpdated = 0;
this.autoRenderOnLoad = true;
this.cachedFrames = [];
this.reRenderTarget = 0;
this.reRenderFraction = 2;
this.imagesLoader = this;
this.cancelLoading = () => {
const { loadStatus } = this;
if (!loadStatus || !loadStatus.loading) {
return;
}
loadStatus.loading = false;
loadStatus.cancelled = true;
this.clearLoadCallbacks();
const filterFunction = ({ additionalDetails }) => {
return additionalDetails.volumeId !== this.volumeId;
};
imageLoadPoolManager.filterRequests(filterFunction);
};
this.loadStatus = streamingProperties.loadStatus;
}
invalidateVolume(immediate) {
const { vtkOpenGLTexture } = this;
const { numFrames } = this;
for (let i = 0; i < numFrames; i++) {
vtkOpenGLTexture.setUpdatedFrame(i);
}
this.modified();
if (immediate) {
autoLoad(this.volumeId);
}
}
clearLoadCallbacks() {
this.loadStatus.callbacks = [];
}
callLoadStatusCallback(evt) {
const { framesUpdated, framesProcessed, totalNumFrames } = evt;
const { volumeId, reRenderFraction, loadStatus, metadata } = this;
const { FrameOfReferenceUID } = metadata;
if (this.autoRenderOnLoad) {
if (framesUpdated > this.reRenderTarget ||
framesProcessed === totalNumFrames) {
this.reRenderTarget += reRenderFraction;
autoLoad(volumeId);
}
}
if (framesProcessed === totalNumFrames) {
loadStatus.callbacks.forEach((callback) => callback(evt));
const eventDetail = {
FrameOfReferenceUID,
volumeId: volumeId,
};
triggerEvent(eventTarget, Events.IMAGE_VOLUME_LOADING_COMPLETED, eventDetail);
}
}
updateTextureAndTriggerEvents(imageIdIndex, imageId, imageQualityStatus = ImageQualityStatus.FULL_RESOLUTION) {
const frameIndex = this.imageIdIndexToFrameIndex(imageIdIndex);
const { cachedFrames, numFrames, totalNumFrames } = this;
const { FrameOfReferenceUID } = this.metadata;
const currentStatus = cachedFrames[frameIndex];
if (currentStatus > imageQualityStatus) {
return;
}
if (cachedFrames[frameIndex] === ImageQualityStatus.FULL_RESOLUTION) {
return;
}
const complete = imageQualityStatus === ImageQualityStatus.FULL_RESOLUTION;
cachedFrames[imageIdIndex] = imageQualityStatus;
this.framesUpdated++;
if (complete) {
this.framesLoaded++;
this.framesProcessed++;
}
const eventDetail = {
FrameOfReferenceUID,
volumeId: this.volumeId,
numberOfFrames: numFrames,
framesProcessed: this.framesProcessed,
};
triggerEvent(eventTarget, Events.IMAGE_VOLUME_MODIFIED, eventDetail);
if (complete && this.framesProcessed === this.totalNumFrames) {
this.loadStatus.loaded = true;
this.loadStatus.loading = false;
}
this.callLoadStatusCallback({
success: true,
imageIdIndex,
imageId,
framesLoaded: this.framesLoaded,
framesProcessed: this.framesProcessed,
framesUpdated: this.framesUpdated,
numFrames,
totalNumFrames,
complete,
imageQualityStatus,
});
this.vtkOpenGLTexture.setUpdatedFrame(frameIndex);
if (this.loadStatus.loaded) {
this.loadStatus.callbacks = [];
}
}
successCallback(imageId, image) {
const imageIdIndex = this.getImageIdIndex(imageId);
const { imageQualityStatus } = image;
if (this.loadStatus.cancelled) {
console.warn('volume load cancelled, returning for imageIdIndex: ', imageIdIndex);
return;
}
this.updateTextureAndTriggerEvents(imageIdIndex, imageId, imageQualityStatus);
if (this.isDynamicVolume()) {
this.checkDimensionGroupCompletion(imageIdIndex);
}
}
errorCallback(imageId, permanent, error) {
if (!permanent) {
return;
}
const { totalNumFrames, numFrames } = this;
const imageIdIndex = this.getImageIdIndex(imageId);
this.framesProcessed++;
if (this.framesProcessed === totalNumFrames) {
this.loadStatus.loaded = true;
this.loadStatus.loading = false;
}
this.callLoadStatusCallback({
success: false,
imageId,
imageIdIndex,
error,
framesLoaded: this.framesLoaded,
framesProcessed: this.framesProcessed,
framesUpdated: this.framesUpdated,
numFrames,
totalNumFrames,
});
if (this.loadStatus.loaded) {
this.loadStatus.callbacks = [];
}
const eventDetail = {
error,
imageIdIndex,
imageId,
};
triggerEvent(eventTarget, Events.IMAGE_LOAD_ERROR, eventDetail);
}
load(callback) {
const { imageIds, loadStatus, numFrames } = this;
const { transferSyntaxUID } = metaData.get(MetadataModules.TRANSFER_SYNTAX, imageIds[0]) || {};
const imageRetrieveConfiguration = metaData.get(imageRetrieveMetadataProvider.IMAGE_RETRIEVE_CONFIGURATION, this.volumeId, transferSyntaxUID, 'volume');
this.imagesLoader = this.isDynamicVolume()
? this
: imageRetrieveConfiguration
? (imageRetrieveConfiguration.create ||
ProgressiveRetrieveImages.createProgressive)(imageRetrieveConfiguration)
: this;
if (loadStatus.loading === true) {
return;
}
const { loaded } = this.loadStatus;
const totalNumFrames = imageIds.length;
if (loaded) {
if (callback) {
callback({
success: true,
framesLoaded: totalNumFrames,
framesProcessed: totalNumFrames,
numFrames,
totalNumFrames,
});
}
return;
}
if (callback) {
this.loadStatus.callbacks.push(callback);
}
this._prefetchImageIds();
}
getLoaderImageOptions(imageId) {
const { transferSyntaxUID: transferSyntaxUID } = metaData.get(MetadataModules.TRANSFER_SYNTAX, imageId) || {};
const targetRows = this.dimensions[1];
const targetCols = this.dimensions[0];
const imageIdIndex = this.getImageIdIndex(imageId);
const modalityLutModule = metaData.get(MetadataModules.MODALITY_LUT, imageId) || {};
const generalSeriesModule = metaData.get(MetadataModules.GENERAL_SERIES, imageId) || {};
const scalingParameters = {
rescaleSlope: modalityLutModule.rescaleSlope,
rescaleIntercept: modalityLutModule.rescaleIntercept,
modality: generalSeriesModule.modality,
};
const modality = scalingParameters.modality;
if (modality === 'PT' || modality === 'RTDOSE') {
const scalingFactor = metaData.get(MetadataModules.SCALING, imageId);
if (scalingFactor) {
this._addScalingToVolume(scalingFactor);
Object.assign(scalingParameters, scalingFactor);
}
}
const floatAfterScale = hasFloatScalingParameters(scalingParameters);
const allowFloatRendering = canRenderFloatTextures();
this.isPreScaled = true;
if (scalingParameters &&
scalingParameters.rescaleSlope !== undefined &&
scalingParameters.rescaleIntercept !== undefined) {
const { rescaleSlope, rescaleIntercept } = scalingParameters;
this.isPreScaled =
typeof rescaleSlope === 'number' &&
typeof rescaleIntercept === 'number';
}
if (!allowFloatRendering && floatAfterScale) {
this.isPreScaled = false;
}
const targetBuffer = {
type: this.dataType,
rows: targetRows,
columns: targetCols,
};
return {
targetBuffer,
allowFloatRendering,
preScale: {
enabled: this.isPreScaled,
scalingParameters,
},
transferPixelData: true,
requestType: requestTypeDefault,
transferSyntaxUID,
additionalDetails: {
imageId,
imageIdIndex,
volumeId: this.volumeId,
},
retrieveOptions: undefined,
};
}
callLoadImage(imageId, imageIdIndex, options) {
const { cachedFrames } = this;
if (cachedFrames[imageIdIndex] === ImageQualityStatus.FULL_RESOLUTION) {
return;
}
const handleImageCacheAdded = (event) => {
const { image } = event.detail;
if (image.imageId === imageId) {
this.vtkOpenGLTexture.setUpdatedFrame(imageIdIndex);
eventTarget.removeEventListener(Events.IMAGE_CACHE_IMAGE_ADDED, handleImageCacheAdded);
}
};
eventTarget.addEventListener(Events.IMAGE_CACHE_IMAGE_ADDED, handleImageCacheAdded);
const uncompressedIterator = ProgressiveIterator.as(loadAndCacheImage(imageId, options));
return uncompressedIterator.forEach((image) => {
this.successCallback(imageId, image);
}, this.errorCallback.bind(this, imageIdIndex, imageId));
}
getImageIdsRequests(imageIds, priorityDefault) {
this.totalNumFrames = this.imageIds.length;
const autoRenderPercentage = 2;
if (this.autoRenderOnLoad) {
this.reRenderFraction =
this.totalNumFrames * (autoRenderPercentage / 100);
this.reRenderTarget = this.reRenderFraction;
}
const requests = imageIds.map((imageId) => {
const imageIdIndex = this.getImageIdIndex(imageId);
const requestType = requestTypeDefault;
const priority = priorityDefault;
const options = this.getLoaderImageOptions(imageId);
const { retrieveOptions = {} } = metaData.get(imageRetrieveMetadataProvider.IMAGE_RETRIEVE_CONFIGURATION, imageId, 'volume') || {};
options.retrieveOptions = {
...options.retrieveOptions,
...(retrieveOptions.default ||
Object.values(retrieveOptions)?.[0] ||
{}),
};
return {
callLoadImage: this.callLoadImage.bind(this),
imageId,
imageIdIndex,
options,
priority,
requestType,
additionalDetails: {
volumeId: this.volumeId,
},
};
});
return requests;
}
getImageLoadRequests(priority) {
throw new Error('Abstract method');
}
getImageIdsToLoad() {
throw new Error('Abstract method');
}
loadImages() {
this.loadStatus.loading = true;
const requests = this.getImageLoadRequests(5);
requests.reverse().forEach((request) => {
if (!request) {
return;
}
const { callLoadImage, imageId, imageIdIndex, options, priority, requestType, additionalDetails, } = request;
imageLoadPoolManager.addRequest(callLoadImage.bind(this, imageId, imageIdIndex, options), requestType, additionalDetails, priority);
});
return Promise.resolve(true);
}
_prefetchImageIds() {
this.loadStatus.loading = true;
const imageIds = [...this.getImageIdsToLoad()];
this.totalNumFrames = this.imageIds.length;
const autoRenderPercentage = 2;
if (this.autoRenderOnLoad) {
this.reRenderFraction =
this.totalNumFrames * (autoRenderPercentage / 100);
this.reRenderTarget = this.reRenderFraction;
}
return this.imagesLoader.loadImages(imageIds, this).catch((e) => {
console.debug('progressive loading failed to complete', e);
});
}
_addScalingToVolume(suvFactor) {
if (this.scaling) {
return;
}
const { suvbw, suvlbm, suvbsa } = suvFactor;
const petScaling = {};
if (suvlbm) {
petScaling.suvbwToSuvlbm = suvlbm / suvbw;
}
if (suvbsa) {
petScaling.suvbwToSuvbsa = suvbsa / suvbw;
}
if (suvbw) {
petScaling.suvbw = suvbw;
}
this.scaling = { PT: petScaling };
}
checkDimensionGroupCompletion(imageIdIndex) { }
}
export default BaseStreamingImageVolume;