import log from '../log.js'; import OHIFError from './OHIFError'; import cornerstone from 'cornerstone-core'; import cornerstoneTools from 'cornerstone-tools'; import getImageId from '../utils/getImageId.js'; export class StudyPrefetcher { constructor(studies) { this.studies = studies || []; this.prefetchDisplaySetsTimeout = 300; this.lastActiveViewportElement = null; cornerstone.events.addEventListener( 'cornerstoneimagecachefull.StudyPrefetcher', this.cacheFullHandler ); } destroy() { this.stopPrefetching(); cornerstone.events.removeEventListener( 'cornerstoneimagecachefull.StudyPrefetcher', this.cacheFullHandler ); } static getInstance() { if (!StudyPrefetcher.instance) { StudyPrefetcher.instance = new StudyPrefetcher([]); } return StudyPrefetcher.instance; } setStudies(studies) { this.stopPrefetching(); this.studies = studies; } prefetch() { if (!this.studies || !this.studies.length) { return; } this.stopPrefetching(); this.prefetchDisplaySets(); } stopPrefetching() { cornerstoneTools.requestPoolManager.clearRequestStack('prefetch'); } prefetchDisplaySetsAsync(timeout) { timeout = timeout || this.prefetchDisplaySetsTimeout; clearTimeout(this.prefetchDisplaySetsHandler); this.prefetchDisplaySetsHandler = setTimeout(() => { this.prefetchDisplaySets(); }, timeout); } prefetchDisplaySets() { // TODO: Allow passing in config let config = { order: 'closest', displaySetCount: 1, }; const displaySetsToPrefetch = this.getDisplaySetsToPrefetch(config); const imageIds = this.getImageIdsFromDisplaySets(displaySetsToPrefetch); this.prefetchImageIds(imageIds); } prefetchImageIds(imageIds) { const nonCachedImageIds = this.filterCachedImageIds(imageIds); const requestPoolManager = cornerstoneTools.requestPoolManager; const requestType = 'prefetch'; const preventCache = false; const noop = () => {}; nonCachedImageIds.forEach(imageId => { requestPoolManager.addRequest( {}, imageId, requestType, preventCache, noop, noop ); }); requestPoolManager.startGrabbing(); } getStudy(image) { const studyMetadata = cornerstone.metaData.get('study', image.imageId); return OHIF.viewer.Studies.find( study => study.studyInstanceUid === studyMetadata.studyInstanceUid ); } getSeries(study, image) { const seriesMetadata = cornerstone.metaData.get('series', image.imageId); const studyMetadata = OHIF.viewerbase.getStudyMetadata(study); return studyMetadata.getSeriesByUID(seriesMetadata.seriesInstanceUid); } getInstance(series, image) { const instanceMetadata = cornerstone.metaData.get( 'instance', image.imageId ); return series.getInstanceByUID(instanceMetadata.sopInstanceUid); } getActiveDisplaySet(displaySets, instance) { return displaySets.find(displaySet => { return displaySet.images.some(displaySetImage => { return displaySetImage.sopInstanceUid === instance.sopInstanceUid; }); }); } getDisplaySetsToPrefetch(config) { const image = this.getActiveViewportImage(); if (!image || !config || !config.displaySetCount) { return []; } /*const study = this.getStudy(image); const series = this.getSeries(study, image); const instance = this.getInstance(series, image);*/ const displaySets = study.displaySets; const activeDisplaySet = null; //this.getActiveDisplaySet(displaySets, instance); const prefetchMethodMap = { topdown: 'getFirstDisplaySets', downward: 'getNextDisplaySets', closest: 'getClosestDisplaySets', }; const prefetchOrder = config.order; const methodName = prefetchMethodMap[prefetchOrder]; const getDisplaySets = this[methodName]; if (!getDisplaySets) { if (prefetchOrder) { log.warn(`Invalid prefetch order configuration (${prefetchOrder})`); } return []; } return getDisplaySets.call( this, displaySets, activeDisplaySet, config.displaySetCount ); } getFirstDisplaySets(displaySets, activeDisplaySet, displaySetCount) { const length = displaySets.length; const selectedDisplaySets = []; for (let i = 0; i < length && displaySetCount; i++) { const displaySet = displaySets[i]; if (displaySet !== activeDisplaySet) { selectedDisplaySets.push(displaySet); displaySetCount--; } } return selectedDisplaySets; } getNextDisplaySets(displaySets, activeDisplaySet, displaySetCount) { const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet); const begin = activeDisplaySetIndex + 1; const end = Math.min(begin + displaySetCount, displaySets.length); return displaySets.slice(begin, end); } getClosestDisplaySets(displaySets, activeDisplaySet, displaySetCount) { const activeDisplaySetIndex = displaySets.indexOf(activeDisplaySet); const length = displaySets.length; const selectedDisplaySets = []; let left = activeDisplaySetIndex - 1; let right = activeDisplaySetIndex + 1; while ((left >= 0 || right < length) && displaySetCount) { if (left >= 0) { selectedDisplaySets.push(displaySets[left]); displaySetCount--; left--; } if (right < length && displaySetCount) { selectedDisplaySets.push(displaySets[right]); displaySetCount--; right++; } } return selectedDisplaySets; } getImageIdsFromDisplaySets(displaySets) { let imageIds = []; displaySets.forEach(displaySet => { imageIds = imageIds.concat(this.getImageIdsFromDisplaySet(displaySet)); }); return imageIds; } getImageIdsFromDisplaySet(displaySet) { const imageIds = []; // TODO: This duplicates work done by the stack manager displaySet.images.forEach(image => { const numFrames = image.numFrames; if (numFrames > 1) { for (let i = 0; i < numFrames; i++) { let imageId = getImageId(image, i); imageIds.push(imageId); } } else { let imageId = getImageId(image); imageIds.push(imageId); } }); return imageIds; } filterCachedImageIds(imageIds) { return imageIds.filter(imageId => !this.isImageCached(imageId)); } isImageCached(imageId) { const image = cornerstone.imageCache.imageCache[imageId]; return image && image.sizeInBytes; } cacheFullHandler = () => { log.warn('Cache full'); this.stopPrefetching(); }; }