@cornerstonejs/core
Version:
Cornerstone3D Core
231 lines (230 loc) • 9.29 kB
JavaScript
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;