Adding StudyPrefetcher
This commit is contained in:
parent
3f575358f0
commit
1c509a9870
@ -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'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
309
Packages/ohif-viewerbase/client/lib/classes/StudyPrefetcher.js
Normal file
309
Packages/ohif-viewerbase/client/lib/classes/StudyPrefetcher.js
Normal 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();
|
||||
}
|
||||
|
||||
};
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user