UNPKG

@cornerstonejs/core

Version:
231 lines (230 loc) • 9.29 kB
import singleRetrieveStages from './configuration/singleRetrieve.js'; import sequentialRetrieveStages from './configuration/sequentialRetrieve.js'; import interleavedRetrieveStages from './configuration/interleavedRetrieve.js'; import { loadAndCacheImage } from './imageLoader.js'; import triggerEvent from '../utilities/triggerEvent.js'; import ProgressiveIterator from '../utilities/ProgressiveIterator.js'; import decimate from '../utilities/decimate.js'; import imageLoadPoolManager from '../requestPool/imageLoadPoolManager.js'; import { ImageQualityStatus, RequestType, Events } from '../enums/index.js'; import cache from '../cache/cache.js'; import eventTarget from '../eventTarget.js'; import { fillNearbyFrames } from './fillNearbyFrames.js'; export { sequentialRetrieveStages, interleavedRetrieveStages, singleRetrieveStages, }; export class ProgressiveRetrieveImages { constructor(imageRetrieveConfiguration) { this.stages = imageRetrieveConfiguration.stages || singleRetrieveStages; this.retrieveOptions = imageRetrieveConfiguration.retrieveOptions || {}; } loadImages(imageIds, listener) { const instance = new ProgressiveRetrieveImagesInstance(this, imageIds, listener); return instance.loadImages(); } } ProgressiveRetrieveImages.createProgressive = createProgressive; ProgressiveRetrieveImages.interleavedRetrieveStages = { stages: interleavedRetrieveStages, }; ProgressiveRetrieveImages.singleRetrieveStages = { stages: singleRetrieveStages, }; ProgressiveRetrieveImages.sequentialRetrieveStages = { stages: sequentialRetrieveStages, }; class ProgressiveRetrieveImagesInstance { constructor(configuration, imageIds, listener) { this.outstandingRequests = 0; this.stageStatusMap = new Map(); this.displayedIterator = new ProgressiveIterator('displayed'); this.stages = configuration.stages; this.retrieveOptions = configuration.retrieveOptions; this.imageIds = imageIds; this.listener = listener; } async loadImages() { const interleaved = this.createStageRequests(); this.outstandingRequests = interleaved.length; for (const request of interleaved) { this.addRequest(request); } if (this.outstandingRequests === 0) { return Promise.resolve(null); } return this.displayedIterator.getDonePromise(); } sendRequest(request, options) { const { imageId, next } = request; const errorCallback = (reason, done) => { this.listener.errorCallback(imageId, complete || !next, reason); if (done) { this.updateStageStatus(request.stage, reason); } }; const loadedPromise = (options.loader || loadAndCacheImage)(imageId, options); const uncompressedIterator = ProgressiveIterator.as(loadedPromise); let complete = false; uncompressedIterator .forEach(async (image, done) => { const oldStatus = cache.getImageQuality(imageId); if (!image) { console.warn('No image retrieved', imageId); return; } const { imageQualityStatus } = image; complete ||= imageQualityStatus === ImageQualityStatus.FULL_RESOLUTION; if (oldStatus !== undefined && oldStatus > imageQualityStatus) { this.updateStageStatus(request.stage, null, true); return; } this.listener.successCallback(imageId, image); this.displayedIterator.add(image); if (done) { this.updateStageStatus(request.stage); } fillNearbyFrames(this.listener, request, image); }, errorCallback) .finally(() => { if (!complete && next) { cache.setPartialImage(imageId, undefined, true); this.addRequest(next, options.streamingData); } else { if (!complete) { this.listener.errorCallback(imageId, true, "Couldn't decode"); } this.outstandingRequests--; for (let skip = next; skip; skip = skip.next) { this.updateStageStatus(skip.stage, null, true); } } if (this.outstandingRequests <= 0) { this.displayedIterator.resolve(); } }); const doneLoad = uncompressedIterator.getDonePromise(); return doneLoad.catch((e) => null); } addRequest(request, streamingData = {}) { const { imageId, stage } = request; const baseOptions = this.listener.getLoaderImageOptions(imageId); if (!baseOptions) { return; } const { retrieveType = 'default' } = stage; const { retrieveOptions: keyedRetrieveOptions } = this; const retrieveOptions = keyedRetrieveOptions[retrieveType] || keyedRetrieveOptions.default; const options = { ...baseOptions, retrieveType, retrieveOptions, streamingData, }; const priority = stage.priority ?? -5; const requestType = stage.requestType || RequestType.Interaction; const additionalDetails = { imageId }; imageLoadPoolManager.addRequest(this.sendRequest.bind(this, request, options), requestType, additionalDetails, priority); } updateStageStatus(stage, failure, skipped = false) { const { id } = stage; const stageStatus = this.stageStatusMap.get(id); if (!stageStatus) { return; } stageStatus.imageLoadPendingCount--; if (failure) { stageStatus.imageLoadFailedCount++; } else if (!skipped) { stageStatus.totalImageCount++; } if (!skipped && !stageStatus.stageStartTime) { stageStatus.stageStartTime = Date.now(); } if (!stageStatus.imageLoadPendingCount) { const { imageLoadFailedCount: numberOfFailures, totalImageCount: numberOfImages, stageStartTime = Date.now(), startTime, } = stageStatus; const detail = { stageId: id, numberOfFailures, numberOfImages, stageDurationInMS: stageStartTime ? Date.now() - stageStartTime : null, startDurationInMS: Date.now() - startTime, }; triggerEvent(eventTarget, Events.IMAGE_RETRIEVAL_STAGE, detail); this.stageStatusMap.delete(id); } } createStageRequests() { const interleaved = new Array(); const imageRequests = new Map(); const addStageInstance = (stage, position) => { const index = position < 0 ? this.imageIds.length + position : position < 1 ? Math.floor((this.imageIds.length - 1) * position) : position; const imageId = this.imageIds[index]; if (!imageId) { throw new Error(`No value found to add to requests at ${position}`); } const request = { imageId, stage, index, nearbyRequests: this.findNearbyRequests(index, stage), }; this.addStageStatus(stage); const existingRequest = imageRequests.get(imageId); if (existingRequest) { existingRequest.next = request; } else { interleaved.push(request); } imageRequests.set(imageId, request); }; for (const stage of this.stages) { const indices = stage.positions || decimate(this.imageIds, stage.decimate || 1, stage.offset ?? 0); indices.forEach((index) => { addStageInstance(stage, index); }); } return interleaved; } findNearbyRequests(index, stage) { const nearby = new Array(); if (!stage.nearbyFrames) { return nearby; } for (const nearbyItem of stage.nearbyFrames) { const nearbyIndex = index + nearbyItem.offset; if (nearbyIndex < 0 || nearbyIndex >= this.imageIds.length) { continue; } nearby.push({ itemId: this.imageIds[nearbyIndex], imageQualityStatus: nearbyItem.imageQualityStatus, index: nearbyIndex, }); } return nearby; } addStageStatus(stage) { const { id } = stage; const stageStatus = this.stageStatusMap.get(id) || { stageId: id, startTime: Date.now(), stageStartTime: null, totalImageCount: 0, imageLoadFailedCount: 0, imageLoadPendingCount: 0, }; stageStatus.imageLoadPendingCount++; this.stageStatusMap.set(id, stageStatus); return stageStatus; } } export function createProgressive(configuration) { return new ProgressiveRetrieveImages(configuration); } export default ProgressiveRetrieveImages;