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({
|
export const PublicServerConfig = new SimpleSchema({
|
||||||
verifyEmail: {
|
verifyEmail: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@ -180,6 +195,10 @@ export const PublicServerConfig = new SimpleSchema({
|
|||||||
ui: {
|
ui: {
|
||||||
type: UISettings,
|
type: UISettings,
|
||||||
label: 'UI Settings'
|
label: 'UI Settings'
|
||||||
|
},
|
||||||
|
prefetch: {
|
||||||
|
type: PrefetchSchema,
|
||||||
|
label: 'Prefetch settings'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { unloadHandlers } from '../../../lib/unloadHandlers';
|
|||||||
import { hotkeyUtils } from '../../../lib/hotkeyUtils';
|
import { hotkeyUtils } from '../../../lib/hotkeyUtils';
|
||||||
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
|
import { ResizeViewportManager } from '../../../lib/classes/ResizeViewportManager';
|
||||||
import { LayoutManager } from '../../../lib/classes/LayoutManager';
|
import { LayoutManager } from '../../../lib/classes/LayoutManager';
|
||||||
|
import { StudyPrefetcher } from '../../../lib/classes/StudyPrefetcher';
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
|
window.ResizeViewportManager = window.ResizeViewportManager || new ResizeViewportManager();
|
||||||
@ -34,8 +35,10 @@ Template.viewerMain.onRendered(() => {
|
|||||||
const instance = Template.instance();
|
const instance = Template.instance();
|
||||||
const { studies } = instance.data;
|
const { studies } = instance.data;
|
||||||
const parentElement = instance.$('#layoutManagerTarget').get(0);
|
const parentElement = instance.$('#layoutManagerTarget').get(0);
|
||||||
|
const studyPrefetcher = StudyPrefetcher.getInstance();
|
||||||
|
|
||||||
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
|
OHIF.viewerbase.layoutManager = new LayoutManager(parentElement, studies);
|
||||||
|
studyPrefetcher.setStudies(studies);
|
||||||
|
|
||||||
// Enable hotkeys
|
// Enable hotkeys
|
||||||
hotkeyUtils.enableHotkeys();
|
hotkeyUtils.enableHotkeys();
|
||||||
|
|||||||
@ -180,6 +180,10 @@ Viewerbase.ImageSet = ImageSet;
|
|||||||
import { LayoutManager } from './lib/classes/LayoutManager';
|
import { LayoutManager } from './lib/classes/LayoutManager';
|
||||||
Viewerbase.LayoutManager = LayoutManager;
|
Viewerbase.LayoutManager = LayoutManager;
|
||||||
|
|
||||||
|
// StudyPrefetcher
|
||||||
|
import { StudyPrefetcher } from './lib/classes/StudyPrefetcher';
|
||||||
|
Viewerbase.StudyPrefetcher = StudyPrefetcher;
|
||||||
|
|
||||||
// ResizeViewportManager
|
// ResizeViewportManager
|
||||||
import { ResizeViewportManager } from './lib/classes/ResizeViewportManager';
|
import { ResizeViewportManager } from './lib/classes/ResizeViewportManager';
|
||||||
Viewerbase.ResizeViewportManager = 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 { Random } from 'meteor/random';
|
||||||
|
|
||||||
import { OHIF } from 'meteor/ohif:core';
|
import { OHIF } from 'meteor/ohif:core';
|
||||||
import { enablePrefetchOnElement } from './enablePrefetchOnElement';
|
import { StudyPrefetcher } from './classes/StudyPrefetcher';
|
||||||
import { displayReferenceLines } from './displayReferenceLines';
|
import { displayReferenceLines } from './displayReferenceLines';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,8 +51,9 @@ export function setActiveViewport(element) {
|
|||||||
// Cornerstone Tools compare DOM elements (check getEnabledElement cornerstone function)
|
// 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
|
// so we can't pass a jQuery object as an argument, otherwise it throws an excepetion
|
||||||
const domElement = jQueryElement.get(0);
|
const domElement = jQueryElement.get(0);
|
||||||
enablePrefetchOnElement(domElement);
|
|
||||||
displayReferenceLines(domElement);
|
displayReferenceLines(domElement);
|
||||||
|
StudyPrefetcher.getInstance().prefetch();
|
||||||
|
|
||||||
// @TODO Add this to OHIFAfterActivateViewport handler...
|
// @TODO Add this to OHIFAfterActivateViewport handler...
|
||||||
if (OHIF.viewer.stackImagePositionOffsetSynchronizer) {
|
if (OHIF.viewer.stackImagePositionOffsetSynchronizer) {
|
||||||
OHIF.viewer.stackImagePositionOffsetSynchronizer.update();
|
OHIF.viewer.stackImagePositionOffsetSynchronizer.update();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user