Adding StudyPrefetcher

This commit is contained in:
Leonardo Campos 2017-01-25 21:12:36 -02:00
parent 3f575358f0
commit 1c509a9870
6 changed files with 338 additions and 44 deletions

View File

@ -166,6 +166,21 @@ export const UISettings = new SimpleSchema({
}
});
export const PrefetchSchema = new SimpleSchema({
order: {
type: String,
label: 'Prefetch Order',
allowedValues: ['topdown', 'downward', 'closest'],
optional: false
},
displaySetCount: {
type: Number,
label: 'Display Set Count',
min: 1,
defaultValue: 1
}
});
export const PublicServerConfig = new SimpleSchema({
verifyEmail: {
type: Boolean,
@ -180,6 +195,10 @@ export const PublicServerConfig = new SimpleSchema({
ui: {
type: UISettings,
label: 'UI Settings'
},
prefetch: {
type: PrefetchSchema,
label: 'Prefetch settings'
}
});

View File

@ -8,6 +8,7 @@ import { unloadHandlers } from '../../../lib/unloadHandlers';
import { hotkeyUtils } from '../../../lib/hotkeyUtils';
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
import { LayoutManager } from '../../../lib/classes/LayoutManager';
import { StudyPrefetcher } from '../../../lib/classes/StudyPrefetcher';
Meteor.startup(() => {
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
@ -34,8 +35,10 @@ Template.viewerMain.onRendered(() => {
const instance = Template.instance();
const { studies } = instance.data;
const parentElement = instance.$('#layoutManagerTarget').get(0);
const studyPrefetcher = StudyPrefetcher.getInstance();
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
studyPrefetcher.setStudies(studies);
// Enable hotkeys
hotkeyUtils.enableHotkeys();

View File

@ -180,6 +180,10 @@ Viewerbase.ImageSet = ImageSet;
import { LayoutManager } from './lib/classes/LayoutManager';
Viewerbase.LayoutManager = LayoutManager;
// StudyPrefetcher
import { StudyPrefetcher } from './lib/classes/StudyPrefetcher';
Viewerbase.StudyPrefetcher = StudyPrefetcher;
// ResizeViewportManager
import { ResizeViewportManager } from './lib/classes/ResizeViewportManager';
Viewerbase.ResizeViewportManager = ResizeViewportManager;

View File

@ -0,0 +1,309 @@
import { OHIF } from 'meteor/ohif:core';
import { Session } from 'meteor/session';
import { StackManager } from '../StackManager.js';
import { OHIFError } from './OHIFError';
import { $ } from 'meteor/jquery';
import { _ } from 'meteor/underscore';
export class StudyPrefetcher {
constructor(studies) {
this.studies = [];
this.prefetchDisplaySetsTimeout = 300;
this.lastActiveViewportElement = null;
$(cornerstone).on('CornerstoneImageCacheFull.StudyPrefetcher', _.bind(this.cacheFullHandler, this));
}
destroy() {
this.stopPrefetching();
$(cornerstone).off('CornerstoneImageCacheFull.StudyPrefetcher');
}
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.prefetchActiveViewport();
this.prefetchDisplaySets();
}
stopPrefetching() {
this.disableViewportPrefetch();
cornerstoneTools.requestPoolManager.clearRequestStack('prefetch');
}
prefetchActiveViewport() {
const activeViewportElement = OHIF.viewerbase.viewportUtils.getActiveViewportElement();
this.enablePrefetchOnElement(activeViewportElement);
this.attachActiveViewportListeners(activeViewportElement);
}
disableViewportPrefetch() {
$('.imageViewerViewport').each(function() {
if (!$(this).find('canvas').length) {
return;
}
cornerstoneTools.stackPrefetch.disable(this);
});
}
hasStack(element) {
const stack = cornerstoneTools.getToolState(element, 'stack');
return stack && stack.data.length && (stack.data[0].imageIds.length > 1);
}
/**
* This function enables stack prefetching for a specified element (viewport)
* It first disables any prefetching currently occurring on any other viewports.
*
* @param element {node} DOM Node representing the viewport element
*/
enablePrefetchOnElement(element) {
if (!$(element).find('canvas').length) {
return;
}
// Make sure there is a stack to fetch
if (this.hasStack(element)) {
// Check if this is a clip or not
const activeViewportIndex = Session.get('activeViewport');
const contentId = Session.get('activeContentId');
const displaySetInstanceUid = ViewerData[contentId].loadedSeriesData[activeViewportIndex].displaySetInstanceUid;
const stack = StackManager.findStack(displaySetInstanceUid);
if (!stack) {
throw new OHIFError(`Requested stack ${displaySetInstanceUid} was not created`);
}
cornerstoneTools.stackPrefetch.enable(element);
}
}
attachActiveViewportListeners(activeViewportElement) {
const newImageHandler = () => {
// It needs to be called asynchronously because cornerstone does it at the same way.
// All instance urls to be prefetched will be removed again if we add them before
// Cornerstone callback (see stackPrefetch.onImageUpdated).
this.prefetchDisplaySetsAsync();
};
$(this.lastActiveViewportElement).off('CornerstoneNewImage.StudyPrefetcher');
$(activeViewportElement).off('CornerstoneNewImage.StudyPrefetcher');
// Cornerstone will not attach an event listener if the element doesn't have a stack
if (this.hasStack(activeViewportElement)) {
$(activeViewportElement).on('CornerstoneNewImage.StudyPrefetcher', newImageHandler);
}
this.lastActiveViewportElement = activeViewportElement;
}
prefetchDisplaySetsAsync(timeout) {
timeout = timeout || this.prefetchDisplaySetsTimeout;
clearTimeout(this.prefetchDisplaySetsHandler);
this.prefetchDisplaySetsHandler = setTimeout(() => {
this.prefetchDisplaySets();
}, timeout);
}
prefetchDisplaySets() {
const config = Meteor.settings.public.prefetch;
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();
}
getActiveViewportImage() {
const element = OHIF.viewerbase.viewportUtils.getActiveViewportElement();
if (!element) {
return;
}
const enabledElement = cornerstone.getEnabledElement(element);
const image = enabledElement.image;
return image;
}
getStudy(image) {
const studyMetadata = cornerstoneTools.metaData.get('study', image.imageId);
return _.find(this.studies, study => study.studyInstanceUid === studyMetadata.studyInstanceUid);
}
getSeries(study, image) {
const seriesMetadata = cornerstoneTools.metaData.get('series', image.imageId);
return _.find(study.seriesList, series => series.seriesInstanceUid === seriesMetadata.seriesInstanceUid);
}
getInstance(series, image) {
const instanceMetadata = cornerstoneTools.metaData.get('instance', image.imageId);
return _.find(series.instances, instance => instance.sopInstanceUid === instanceMetadata.sopInstanceUid);
}
getActiveDisplaySet(displaySets, instance) {
return _.find(displaySets, displaySet => {
return _.some(displaySet.images, 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 = 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) {
OHIF.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 = [];
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 _.filter(imageIds, imageId => {
return !this.isImageCached(imageId);
});
}
isImageCached(imageId) {
const imagePromise = cornerstone.imageCache.getImagePromise(imageId);
return imagePromise && (imagePromise.state() === 'resolved');
}
cacheFullHandler() {
OHIF.log.warn('Cache full');
this.stopPrefetching();
}
};

View File

@ -1,42 +0,0 @@
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core';
import { StackManager } from './StackManager.js';
import { OHIFError } from './classes/OHIFError';
/**
* This function enables stack prefetching for a specified element (viewport)
* It first disables any prefetching currently occurring on any other viewports.
*
* @param element
*/
export function enablePrefetchOnElement(element) {
OHIF.log.info('enablePrefetchOnElement');
// Loop through all of the viewports and disable stackPrefetch
$('.viewportContainer .imageViewerViewport').each((index, viewportElement) => {
if ($(viewportElement).find('canvas').length) {
cornerstoneTools.stackPrefetch.disable(viewportElement);
}
});
if ($(element).find('canvas').length) {
// If the stack in the active viewport has more than one image,
// enable prefetching for the element
const cornerstoneStack = cornerstoneTools.getToolState(element, 'stack');
if (cornerstoneStack && cornerstoneStack.data.length && cornerstoneStack.data[0].imageIds.length > 1) {
// Check if this is a clip or not
const activeViewportIndex = Session.get('activeViewport');
const contentId = Session.get('activeContentId');
const displaySetInstanceUid = ViewerData[contentId].loadedSeriesData[activeViewportIndex].displaySetInstanceUid;
const stack = StackManager.findStack(displaySetInstanceUid);
if (!stack) {
throw new OHIFError(`Requested stack ${displaySetInstanceUid} was not created`);
}
cornerstoneTools.stackPrefetch.enable(element);
}
}
}

View File

@ -3,7 +3,7 @@ import { $ } from 'meteor/jquery';
import { Random } from 'meteor/random';
import { OHIF } from 'meteor/ohif:core';
import { enablePrefetchOnElement } from './enablePrefetchOnElement';
import { StudyPrefetcher } from './classes/StudyPrefetcher';
import { displayReferenceLines } from './displayReferenceLines';
/**
@ -51,8 +51,9 @@ export function setActiveViewport(element) {
// Cornerstone Tools compare DOM elements (check getEnabledElement cornerstone function)
// so we can't pass a jQuery object as an argument, otherwise it throws an excepetion
const domElement = jQueryElement.get(0);
enablePrefetchOnElement(domElement);
displayReferenceLines(domElement);
StudyPrefetcher.getInstance().prefetch();
// @TODO Add this to OHIFAfterActivateViewport handler...
if (OHIF.viewer.stackImagePositionOffsetSynchronizer) {
OHIF.viewer.stackImagePositionOffsetSynchronizer.update();