UNPKG

@cornerstonejs/core

Version:
358 lines (357 loc) • 14.3 kB
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;