Merging toolManager, enablePrefetchOnElement and imageViewerViewport...

This commit is contained in:
Emanuel F. Oliveira 2017-01-16 16:24:22 -02:00
parent 068192e035
commit 00656ef943
4 changed files with 407 additions and 187 deletions

View File

@ -1,3 +1,7 @@
import { TypeSafeCollection } from './lib/classes/TypeSafeCollection';
Studies = new TypeSafeCollection();
ViewerStudies = new Meteor.Collection(null); ViewerStudies = new Meteor.Collection(null);
ViewerStudies._debugName = 'ViewerStudies'; ViewerStudies._debugName = 'ViewerStudies';

View File

@ -1,6 +1,23 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { Random } from 'meteor/random';
import { $ } from 'meteor/jquery';
// OHIF Modules
import { OHIF } from 'meteor/ohif:core'; import { OHIF } from 'meteor/ohif:core';
// Local Modules
import { StackManager } from '../../../lib/StackManager';
import { setActiveViewport } from '../../../lib/setActiveViewport';
import { imageViewerViewportData } from '../../../lib/imageViewerViewportData';
import { updateCrosshairsSynchronizer } from '../../../lib/updateCrosshairsSynchronizer';
import { toolManager } from '../../../lib/toolManager';
import { updateOrientationMarkers } from '../../../lib/updateOrientationMarkers';
import { getInstanceClassDefaultViewport } from '../../../lib/instanceClassSpecificViewport';
import { updateMetaData } from '../../../lib/metaDataProvider';
import { OHIFError } from '../../../lib/classes/OHIFError';
var allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' + const allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' + 'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' +
'CornerstoneToolsMouseWheel CornerstoneToolsTap CornerstoneToolsTouchPress ' + 'CornerstoneToolsMouseWheel CornerstoneToolsTap CornerstoneToolsTouchPress ' +
'CornerstoneToolsTouchStart CornerstoneToolsTouchStartActive ' + 'CornerstoneToolsTouchStart CornerstoneToolsTouchStartActive ' +
@ -11,7 +28,7 @@ var allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownA
* *
* @param data {object} Object containing the study, series, and viewport element to be used * @param data {object} Object containing the study, series, and viewport element to be used
*/ */
function loadDisplaySetIntoViewport(data, templateData) { const loadDisplaySetIntoViewport = (data, templateData) => {
OHIF.log.info('imageViewerViewport loadDisplaySetIntoViewport'); OHIF.log.info('imageViewerViewport loadDisplaySetIntoViewport');
// Make sure we have all the data required to render the series // Make sure we have all the data required to render the series
@ -25,7 +42,7 @@ function loadDisplaySetIntoViewport(data, templateData) {
const element = data.element; const element = data.element;
const viewportIndex = $('.imageViewerViewport').index(element); const viewportIndex = $('.imageViewerViewport').index(element);
const layoutManager = window.layoutManager; const layoutManager = OHIF.viewerbase.layoutManager;
layoutManager.viewportData = layoutManager.viewportData || {}; layoutManager.viewportData = layoutManager.viewportData || {};
layoutManager.viewportData[viewportIndex] = layoutManager.viewportData[viewportIndex] || {}; layoutManager.viewportData[viewportIndex] = layoutManager.viewportData[viewportIndex] || {};
layoutManager.viewportData[viewportIndex].viewportIndex = viewportIndex; layoutManager.viewportData[viewportIndex].viewportIndex = viewportIndex;
@ -45,46 +62,34 @@ function loadDisplaySetIntoViewport(data, templateData) {
ViewerData[contentId].loadedSeriesData[viewportIndex] = {}; ViewerData[contentId].loadedSeriesData[viewportIndex] = {};
} }
// Create an empty array to populate with image IDs
const imageIds = [];
// Loop through the current series and add metadata to the
// Cornerstone meta data provider. This will be used to fill information
// into the viewport overlays, and to calculate reference lines and orientation markers
const displaySet = data.displaySet; const displaySet = data.displaySet;
const numImages = displaySet.images.length;
let imageId;
displaySet.images.forEach((image, imageIndex) => { // Get stack from Stack Manager
const metaData = { const displaySetStack = StackManager.findStack(displaySet.displaySetInstanceUid);
instance: image,
series: displaySet, // TODO: Check this
study: data.study,
numImages: numImages,
imageIndex: imageIndex + 1
};
const numFrames = image.numFrames; // If is a clip, updates the global FPS for cine dialog
if (numFrames > 1) { if (displaySetStack && displaySetStack.isClip === true && displaySetStack.frameRate) {
OHIF.log.info('Multiframe image detected'); // Sets the global variable
for (var i = 0; i < numFrames; i++) { OHIF.viewer.cine.framesPerSecond = parseFloat(displaySetStack.frameRate);
metaData.frame = i; // Update the cine dialog FPS
imageId = getImageId(image, i); Session.set('UpdateCINE', Random.id());
imageIds.push(imageId);
addMetaData(imageId, metaData);
} }
// Array with image IDs
let imageIds = [];
// Make sure if the stack is already loaded in the stack manager, otherwise create it
if(!displaySetStack || !displaySetStack.imageIds) {
imageIds = StackManager.makeAndAddStack(data.study, displaySet);
} }
else { else {
imageId = getImageId(image); imageIds = displaySetStack.imageIds;
imageIds.push(imageId);
addMetaData(imageId, metaData);
} }
});
// Define the current image stack using the newly created image IDs // Define the current image stack using the newly created image IDs
const stack = { const stack = {
currentImageIdIndex: data.currentImageIdIndex || 0, currentImageIdIndex: data.currentImageIdIndex || 0,
imageIds: imageIds imageIds: imageIds,
displaySetInstanceUid: data.displaySetInstanceUid
}; };
// If is a clip, updates the global FPS for cine dialog // If is a clip, updates the global FPS for cine dialog
@ -96,7 +101,7 @@ function loadDisplaySetIntoViewport(data, templateData) {
} }
// Get the current image ID for the stack that will be rendered // Get the current image ID for the stack that will be rendered
imageId = imageIds[stack.currentImageIdIndex]; const imageId = imageIds[stack.currentImageIdIndex];
// Save the current image ID inside the template data so it can be // Save the current image ID inside the template data so it can be
// retrieved from the template helpers // retrieved from the template helpers
@ -140,8 +145,33 @@ function loadDisplaySetIntoViewport(data, templateData) {
viewportIndex viewportIndex
}; };
// Handle the case where the imageId isn't loaded correctly and the
// imagePromise returns undefined
// To test, uncomment the next line
// data.imageId = 'AfileThatDoesntWork'; // For testing only!
let imagePromise;
try {
imagePromise = cornerstone.loadAndCacheImage(imageId);
} catch (error) {
console.log(error);
if (!imagePromise) {
errorLoadingHandler(element, imageId, error);
return;
}
}
// loadAndCacheImage configurable callbacks
const callbacks = imageViewerViewportData.callbacks;
// Check if it has before loadAndCacheImage callback
if(typeof callbacks.before === 'function') {
OHIF.log.info('imageViewerViewport before loadAndCacheImage callback');
callbacks.before(imagePromise, templateData);
}
// Start loading the image. // Start loading the image.
cornerstone.loadAndCacheImage(imageId).then(image => { imagePromise.then(image => {
let enabledElement; let enabledElement;
try { try {
enabledElement = cornerstone.getEnabledElement(element); enabledElement = cornerstone.getEnabledElement(element);
@ -181,8 +211,7 @@ function loadDisplaySetIntoViewport(data, templateData) {
// Resize the canvas to fit the current viewport element size. // Resize the canvas to fit the current viewport element size.
cornerstone.resize(element, false); cornerstone.resize(element, false);
} } else if (data.viewport) {
else if (data.viewport) {
// If there is a saved object containing Cornerstone viewport data // If there is a saved object containing Cornerstone viewport data
// (e.g. scale, invert, window settings) in the input data, apply it now. // (e.g. scale, invert, window settings) in the input data, apply it now.
cornerstone.displayImage(element, image, data.viewport); cornerstone.displayImage(element, image, data.viewport);
@ -190,8 +219,7 @@ function loadDisplaySetIntoViewport(data, templateData) {
// Resize the canvas to fit the current viewport element size. Fit the displayed // Resize the canvas to fit the current viewport element size. Fit the displayed
// image to the canvas dimensions. // image to the canvas dimensions.
cornerstone.resize(element, true); cornerstone.resize(element, true);
} } else {
else {
// If no saved viewport settings or modality-specific settings exists, // If no saved viewport settings or modality-specific settings exists,
// display the loaded image in the viewport element with no loaded viewport // display the loaded image in the viewport element with no loaded viewport
// settings. // settings.
@ -264,11 +292,11 @@ function loadDisplaySetIntoViewport(data, templateData) {
// Define a function to run whenever the Cornerstone viewport is rendered // Define a function to run whenever the Cornerstone viewport is rendered
// (e.g. following a change of window or zoom) // (e.g. following a change of window or zoom)
const onImageRendered = () => { const onImageRendered = (event, eventData) => {
OHIF.log.info('imageViewerViewport onImageRendered'); OHIF.log.info('imageViewerViewport onImageRendered');
if (!layoutManager.viewportData[viewportIndex]) { if (!layoutManager.viewportData[viewportIndex]) {
OHIF.log.warn("onImageRendered: LayoutManager has no viewport data for this viewport index?: " + viewportIndex); OHIF.log.warn(`onImageRendered: LayoutManager has no viewport data for this viewport index?: ${viewportIndex}`);
} }
// Use Session to trigger reactive updates in the viewportOverlay helper functions // Use Session to trigger reactive updates in the viewportOverlay helper functions
@ -281,6 +309,12 @@ function loadDisplaySetIntoViewport(data, templateData) {
layoutManager.viewportData[viewportIndex].viewport = viewport; layoutManager.viewportData[viewportIndex].viewport = viewport;
ViewerData[contentId].loadedSeriesData[viewportIndex].viewport = viewport; ViewerData[contentId].loadedSeriesData[viewportIndex].viewport = viewport;
Session.set('ViewerData', ViewerData); Session.set('ViewerData', ViewerData);
// Check if it has onImageRendered loadAndCacheImage callback
if(typeof callbacks.onImageRendered === 'function') {
OHIF.log.info('imageViewerViewport onImageRendered callback');
callbacks.onImageRendered(event, eventData, viewportIndex, templateData);
}
}; };
// Attach the onImageRendered callback to the CornerstoneImageRendered event // Attach the onImageRendered callback to the CornerstoneImageRendered event
@ -292,10 +326,10 @@ function loadDisplaySetIntoViewport(data, templateData) {
// Define a function to run whenever the Cornerstone viewport changes images // Define a function to run whenever the Cornerstone viewport changes images
// (e.g. during scrolling) // (e.g. during scrolling)
const onNewImage = (e, eventData) => { const onNewImage = (event, eventData) => {
OHIF.log.info('imageViewerViewport onNewImage'); OHIF.log.info('imageViewerViewport onNewImage');
// Update the metaData for missing fields of the images in stack // Update the metaData for missing fields
updateMetaData(eventData.enabledElement.image); updateMetaData(eventData.enabledElement.image);
// Update the templateData with the new imageId // Update the templateData with the new imageId
@ -305,7 +339,7 @@ function loadDisplaySetIntoViewport(data, templateData) {
layoutManager.viewportData[viewportIndex].imageId = eventData.enabledElement.image.imageId; layoutManager.viewportData[viewportIndex].imageId = eventData.enabledElement.image.imageId;
// Get the element and stack data // Get the element and stack data
const element = e.target; const element = event.target;
const toolData = cornerstoneTools.getToolState(element, 'stack'); const toolData = cornerstoneTools.getToolState(element, 'stack');
if (!toolData || !toolData.data || !toolData.data.length) { if (!toolData || !toolData.data || !toolData.data.length) {
return; return;
@ -324,6 +358,12 @@ function loadDisplaySetIntoViewport(data, templateData) {
ViewerData[contentId].loadedSeriesData[viewportIndex].currentImageIdIndex = imageIdIndex; ViewerData[contentId].loadedSeriesData[viewportIndex].currentImageIdIndex = imageIdIndex;
Session.set('ViewerData', ViewerData); Session.set('ViewerData', ViewerData);
} }
// Check if it has onNewImage loadAndCacheImage callback
if(typeof callbacks.onNewImage === 'function') {
OHIF.log.info('imageViewerViewport onNewImage callback');
callbacks.onNewImage(event, eventData, viewportIndex, templateData);
}
}; };
// Attach the onNewImage callback to the CornerstoneNewImage event // Attach the onNewImage callback to the CornerstoneNewImage event
@ -333,10 +373,12 @@ function loadDisplaySetIntoViewport(data, templateData) {
// Set a random value for the Session variable in order to trigger an overlay update // Set a random value for the Session variable in order to trigger an overlay update
Session.set('CornerstoneNewImage' + viewportIndex, Random.id()); Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
function onStackScroll(e, eventData) { const onStackScroll = (e, eventData) => {
OHIF.log.info('imageViewerViewport onStackScroll');
// Update the imageSlider value // Update the imageSlider value
Session.set('CornerstoneNewImage' + viewportIndex, Random.id()); Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
} };
$element.off('CornerstoneStackScroll', onStackScroll); $element.off('CornerstoneStackScroll', onStackScroll);
if (stack.imageIds.length > 1) { if (stack.imageIds.length > 1) {
@ -346,7 +388,7 @@ function loadDisplaySetIntoViewport(data, templateData) {
// Define a function to trigger an event whenever a new viewport is being used // Define a function to trigger an event whenever a new viewport is being used
// This is used to update the value of the "active viewport", when the user interacts // This is used to update the value of the "active viewport", when the user interacts
// with a new viewport element // with a new viewport element
const sendActivationTrigger = (e, eventData) => { const sendActivationTrigger = (event, eventData) => {
// Check if the current active viewport in the Meteor Session // Check if the current active viewport in the Meteor Session
// Is the same as the viewport in which the activation event was fired. // Is the same as the viewport in which the activation event was fired.
// If it was, no changes are necessary, so stop here. // If it was, no changes are necessary, so stop here.
@ -363,14 +405,14 @@ function loadDisplaySetIntoViewport(data, templateData) {
OHIF.log.info('imageViewerViewport sendActivationTrigger'); OHIF.log.info('imageViewerViewport sendActivationTrigger');
// Otherwise, trigger an 'ActivateViewport' event to be handled by the Template event // Otherwise, trigger an 'OHIFActivateViewport' event to be handled by the Template event
// handler // handler
eventData.viewportIndex = viewportIndex; eventData.viewportIndex = viewportIndex;
const customEvent = $.Event('ActivateViewport', eventData); const customEvent = $.Event('OHIFActivateViewport', eventData);
// Need to overwrite the type set in the original event // Need to overwrite the type set in the original event
customEvent.type = 'ActivateViewport'; customEvent.type = 'OHIFActivateViewport';
$(e.target).trigger(customEvent, eventData); $(event.target).trigger(customEvent, eventData);
}; };
// Attach the sendActivationTrigger function to all of the Cornerstone interaction events // Attach the sendActivationTrigger function to all of the Cornerstone interaction events
@ -379,15 +421,27 @@ function loadDisplaySetIntoViewport(data, templateData) {
ViewerData[contentId].loadedSeriesData = layoutManager.viewportData; ViewerData[contentId].loadedSeriesData = layoutManager.viewportData;
// Check if image plane (orientation / loction) data is present for the current image // Check if image plane (orientation / location) data is present for the current image
const imagePlane = cornerstoneTools.metaData.get('imagePlane', image.imageId); const imagePlane = cornerstoneTools.metaData.get('imagePlane', image.imageId);
if (imagePlane && imagePlane.frameOfReferenceUID) {
// If it is, and reference lines are enabled, add this element to the global synchronizer // If it is, add this element to the global synchronizer...
// that is used for updating reference lines, and enable reference lines for this viewport.
if (OHIF.viewer.refLinesEnabled && imagePlane && imagePlane.frameOfReferenceUID) {
OHIF.viewer.updateImageSynchronizer.add(element); OHIF.viewer.updateImageSynchronizer.add(element);
if (OHIF.viewer.refLinesEnabled) {
// ... and if reference lines are globally enabled, let cornerstoneTools know.
cornerstoneTools.referenceLines.tool.enable(element, OHIF.viewer.updateImageSynchronizer);
} }
// If the crosshairs tool is active, update the synchronizer
// that is used for its synchronized viewport updating.
// This is necessary if this new image shares a frame of reference
// with currently displayed images
if (activeTool === 'crosshairs') {
updateCrosshairsSynchronizer(imagePlane.frameOfReferenceUID);
}
}
// Set the active viewport based on the Session variable // Set the active viewport based on the Session variable
// This is done to ensure that the active element has the current // This is done to ensure that the active element has the current
// focus, so that keyboard events are triggered. // focus, so that keyboard events are triggered.
@ -402,11 +456,17 @@ function loadDisplaySetIntoViewport(data, templateData) {
// Update the LayoutManagerUpdated session key // Update the LayoutManagerUpdated session key
layoutManager.updateSession(); layoutManager.updateSession();
}, function(error) {
// Check if it has after loadAndCacheImage callback
if(typeof callbacks.after === 'function') {
OHIF.log.info('imageViewerViewport after callback');
callbacks.after(image, templateData, element);
}
}, error => {
// If something goes wrong while loading the image, fire the error handler. // If something goes wrong while loading the image, fire the error handler.
errorLoadingHandler(element, imageId, error); errorLoadingHandler(element, imageId, error);
}); });
} };
/** /**
* This function sets the display set for the study and calls LoadDisplaySetIntoViewport function * This function sets the display set for the study and calls LoadDisplaySetIntoViewport function
@ -416,13 +476,13 @@ function loadDisplaySetIntoViewport(data, templateData) {
* @param templateData currentData of Template * @param templateData currentData of Template
* *
*/ */
function setDisplaySet(data, displaySetInstanceUid, templateData) { const setDisplaySet = (data, displaySetInstanceUid, templateData) => {
var study = data.study; const study = data.study;
if (!study || !study.displaySets) { if (!study || !study.series) {
throw 'Study does not exist or has no display sets'; throw new OHIFError('Study does not exist or has no display sets');
} }
study.displaySets.every(displaySet => { study.series.every(displaySet => {
if (displaySet.displaySetInstanceUid === displaySetInstanceUid) { if (displaySet.displaySetInstanceUid === displaySetInstanceUid) {
data.displaySet = displaySet; data.displaySet = displaySet;
return false; return false;
@ -433,12 +493,12 @@ function setDisplaySet(data, displaySetInstanceUid, templateData) {
// If we didn't find anything, stop here // If we didn't find anything, stop here
if (!data.displaySet) { if (!data.displaySet) {
throw 'Display set not found in specified study!'; throw new OHIFError('Display set not found in specified study!');
} }
// Otherwise, load pass the data object into loadSeriesIntoViewport // Otherwise, load pass the data object into loadSeriesIntoViewport
loadDisplaySetIntoViewport(data, templateData); loadDisplaySetIntoViewport(data, templateData);
} };
/** /**
* This function searches an object to return the keys that contain a specific value * This function searches an object to return the keys that contain a specific value
@ -448,10 +508,10 @@ function setDisplaySet(data, displaySetInstanceUid, templateData) {
* *
* @returns {array} The keys for which the object has the specified value * @returns {array} The keys for which the object has the specified value
*/ */
function getKeysByValue(object, value) { const getKeysByValue = (object, value) => {
// http://stackoverflow.com/questions/9907419/javascript-object-get-key-by-value // http://stackoverflow.com/questions/9907419/javascript-object-get-key-by-value
return Object.keys(object).filter(key => object[key] === value); return Object.keys(object).filter(key => object[key] === value);
} };
Meteor.startup(function() { Meteor.startup(function() {
// On Meteor startup, define the global objects used to store loading imageIds // On Meteor startup, define the global objects used to store loading imageIds
@ -480,11 +540,11 @@ Meteor.startup(function() {
}); });
Template.imageViewerViewport.onRendered(function() { Template.imageViewerViewport.onRendered(function() {
var templateData = Template.currentData(); const templateData = Template.currentData();
OHIF.log.info('imageViewerViewport onRendered'); OHIF.log.info('imageViewerViewport onRendered');
// When the imageViewerViewport template is rendered // When the imageViewerViewport template is rendered
var element = this.find('.imageViewerViewport'); const element = this.find('.imageViewerViewport');
this.element = element; this.element = element;
// Display the loading indicator for this element // Display the loading indicator for this element
@ -492,32 +552,45 @@ Template.imageViewerViewport.onRendered(function() {
// Get the current active viewport index, if this viewport has the same index, // Get the current active viewport index, if this viewport has the same index,
// add the CSS 'active' class to highlight this viewport. // add the CSS 'active' class to highlight this viewport.
var activeViewport = Session.get('activeViewport'); const activeViewport = Session.get('activeViewport');
let { currentImageIdIndex } = templateData;
const { viewport, studyInstanceUid, seriesInstanceUid, renderedCallback, displaySetInstanceUid } = templateData;
if(!currentImageIdIndex) {
currentImageIdIndex = 0;
}
// Calls extendData function to provide flexibility between systems
imageViewerViewportData.extendData(templateData);
// Create a data object to pass to the series loading function (loadSeriesIntoViewport) // Create a data object to pass to the series loading function (loadSeriesIntoViewport)
var data = { const data = {
element: element, element,
viewport: this.data.viewport, viewport,
currentImageIdIndex: this.data.currentImageIdIndex, currentImageIdIndex,
displaySetInstanceUid: this.data.displaySetInstanceUid, displaySetInstanceUid,
studyInstanceUid: this.data.studyInstanceUid, studyInstanceUid,
seriesInstanceUid: this.data.seriesInstanceUid, seriesInstanceUid,
renderedCallback: this.data.renderedCallback, renderedCallback,
activeViewport: activeViewport activeViewport
}; };
// If no displaySetInstanceUid was supplied, display the drag/drop // If no displaySetInstanceUid was supplied, display the drag/drop
// instructions and then stop here since we don't know what to display in the viewport. // instructions and then stop here since we don't know what to display in the viewport.
if (!this.data.displaySetInstanceUid) { if (!displaySetInstanceUid) {
element.classList.add('empty'); element.classList.add('empty');
$(element).siblings('.imageViewerLoadingIndicator').css('display', 'none'); $(element).siblings('.imageViewerLoadingIndicator').css('display', 'none');
$(element).siblings('.viewportInstructions').show(); $(element).siblings('.viewportInstructions').show();
return; return;
} }
// @TODO: Reconcile ViewerStudies and Studies Collection.
// const study = Studies.findBy({ studyInstanceUid });
// Look through the ViewerStudies collection for a // Look through the ViewerStudies collection for a
// study with this studyInstanceUid // study with this studyInstanceUid
var study = ViewerStudies.findOne({ const study = ViewerStudies.findOne({
studyInstanceUid: this.data.studyInstanceUid studyInstanceUid: this.data.studyInstanceUid
}); });
var displaySetInstanceUid = this.data.displaySetInstanceUid; var displaySetInstanceUid = this.data.displaySetInstanceUid;
@ -531,30 +604,46 @@ Template.imageViewerViewport.onDestroyed(function() {
// When a viewport element is being destroyed // When a viewport element is being destroyed
var element = this.find('.imageViewerViewport'); var element = this.find('.imageViewerViewport');
if (!element || !$(element).find('canvas').length) { if (!element || $(element).hasClass('empty') || !$(element).find('canvas').length) {
return; return;
} }
// Disable mouse functions
cornerstoneTools.mouseInput.disable(element);
cornerstoneTools.touchInput.disable(element);
cornerstoneTools.mouseWheelInput.disable(element);
OHIF.viewer.updateImageSynchronizer.remove(element);
// Clear the stack prefetch data
let stackPrefetchData = cornerstoneTools.getToolState(element, 'stackPrefetch');
stackPrefetchData = [];
cornerstoneTools.stackPrefetch.disable(element);
// Try to stop any currently playing clips // Try to stop any currently playing clips
// Otherwise the interval will continuously throw errors // Otherwise the interval will continuously throw errors
try { try {
var enabledElement = cornerstone.getEnabledElement(this.element); const enabledElement = cornerstone.getEnabledElement(element);
if (enabledElement) { if (enabledElement) {
cornerstoneTools.stopClip(this.element); cornerstoneTools.stopClip(element);
} }
} catch (error) { } catch (error) {
OHIF.log.warn(error); OHIF.log.warn(error);
} }
// Trigger custom Destroy Viewport event
// for compatibility with other systems
$(element).trigger('OHIFDestroyedViewport');
// Disable the viewport element with Cornerstone // Disable the viewport element with Cornerstone
// This also triggers the removal of the element from all available // This also triggers the removal of the element from all available
// synchronizers, such as the one used for reference lines. // synchronizers, such as the one used for reference lines.
cornerstone.disable(this.element); cornerstone.disable(element);
}); });
Template.imageViewerViewport.events({ Template.imageViewerViewport.events({
'ActivateViewport .imageViewerViewport'(event) { 'OHIFActivateViewport .imageViewerViewport'(event) {
OHIF.log.info('imageViewerViewport ActivateViewport'); OHIF.log.info('imageViewerViewport OHIFActivateViewport');
setActiveViewport(event.currentTarget); setActiveViewport(event.currentTarget);
}, },
@ -567,18 +656,21 @@ Template.imageViewerViewport.events({
const viewportIndex = $('.imageViewerViewport').index(event.currentTarget); const viewportIndex = $('.imageViewerViewport').index(event.currentTarget);
// Enlarge the double clicked viewport // Enlarge the double clicked viewport
const layoutManager = OHIF.viewerbase.layoutManager;
layoutManager.toggleEnlargement(viewportIndex); layoutManager.toggleEnlargement(viewportIndex);
// Wait for DOM re-rendering and update the active viewport // Wait for DOM re-rendering and update the active viewport
Tracker.afterFlush(() => { Tracker.afterFlush(() => {
let viewportIndexToZoom;
// Check if the viewer is zoomed // Check if the viewer is zoomed
if (layoutManager.isZoomed) { if (layoutManager.isZoomed) {
// Set the active viewport as the only one visible // Set the active viewport as the only one visible
setActiveViewport($('.imageViewerViewport')[0]); viewportIndexToZoom = 0;
} else { } else {
// Set the active viewport as the previous zoomed viewport // Set the active viewport as the previous zoomed viewport
setActiveViewport($('.imageViewerViewport').eq(window.layoutManager.zoomedViewportIndex)); viewportIndexToZoom = layoutManager.zoomedViewportIndex || 0;
} }
setActiveViewport($('.imageViewerViewport').get(viewportIndexToZoom));
}); });
} }
}); });

View File

@ -1,25 +1,42 @@
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core'; 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) * This function enables stack prefetching for a specified element (viewport)
* It first disables any prefetching currently occurring on any other viewports. * It first disables any prefetching currently occurring on any other viewports.
* *
* @param element {node} DOM Node representing the viewport element * @param element
*/ */
enablePrefetchOnElement = function(element) { export function enablePrefetchOnElement(element) {
OHIF.log.info("imageViewerViewport enablePrefetchOnElement"); OHIF.log.info('enablePrefetchOnElement');
// Loop through all viewports and disable stackPrefetch // Loop through all of the viewports and disable stackPrefetch
$('.imageViewerViewport').each(function() { $('.viewportContainer .imageViewerViewport').each((index, viewportElement) => {
if (!$(this).find('canvas').length) { if ($(viewportElement).find('canvas').length) {
return; cornerstoneTools.stackPrefetch.disable(viewportElement);
} }
cornerstoneTools.stackPrefetch.disable(this);
}); });
// Make sure there is a stack to fetch if ($(element).find('canvas').length) {
const stack = cornerstoneTools.getToolState(element, 'stack'); // If the stack in the active viewport has more than one image,
if (stack && stack.data.length && stack.data[0].imageIds.length > 1) { // 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); cornerstoneTools.stackPrefetch.enable(element);
} }
}; }
}

View File

@ -1,13 +1,33 @@
import { Session } from 'meteor/session';
import { $ } from 'meteor/jquery';
import { OHIF } from 'meteor/ohif:core'; import { OHIF } from 'meteor/ohif:core';
import { getFrameOfReferenceUID } from './getFrameOfReferenceUID';
import { updateCrosshairsSynchronizer } from './updateCrosshairsSynchronizer';
import { crosshairsSynchronizers } from './crosshairsSynchronizers';
let activeTool = 'wwwc'; let activeTool = 'wwwc';
let defaultTool = 'wwwc'; let defaultTool = 'wwwc';
let tools = {}; let tools = {};
let gestures = {
zoomTouchPinch: {
enabled: true
},
panMultiTouch: {
enabled: true
},
stackScrollMultiTouch: {
enabled: true
},
doubleTapZoom: {
enabled: true
}
};
let toolDefaultStates = { let toolDefaultStates = {
activate: [], activate: [],
deactivate: ['length', 'angle', 'annotate', 'ellipticalRoi', 'rectangleRoi'], deactivate: ['length', 'angle', 'annotate', 'ellipticalRoi', 'rectangleRoi', 'textMarker'],
enable: [], enable: [],
disable: [], disable: [],
disabledToolButtons: [], disabledToolButtons: [],
@ -21,8 +41,83 @@ let toolDefaultStates = {
let initialized = false; let initialized = false;
const configureTools = () => { /**
* Exported "toolManager" Singleton
*/
export const toolManager = {
init() {
toolManager.addTool('wwwc', {
mouse: cornerstoneTools.wwwc,
touch: cornerstoneTools.wwwcTouchDrag
});
toolManager.addTool('zoom', {
mouse: cornerstoneTools.zoom,
touch: cornerstoneTools.zoomTouchDrag
});
toolManager.addTool('wwwcRegion', {
mouse: cornerstoneTools.wwwcRegion,
touch: cornerstoneTools.wwwcRegionTouch
});
toolManager.addTool('dragProbe', {
mouse: cornerstoneTools.dragProbe,
touch: cornerstoneTools.dragProbeTouch
});
toolManager.addTool('pan', {
mouse: cornerstoneTools.pan,
touch: cornerstoneTools.panTouchDrag
});
toolManager.addTool('stackScroll', {
mouse: cornerstoneTools.stackScroll,
touch: cornerstoneTools.stackScrollTouchDrag
});
toolManager.addTool('length', {
mouse: cornerstoneTools.length,
touch: cornerstoneTools.lengthTouch
});
toolManager.addTool('angle', {
mouse: cornerstoneTools.simpleAngle,
touch: cornerstoneTools.simpleAngleTouch
});
toolManager.addTool('magnify', {
mouse: cornerstoneTools.magnify,
touch: cornerstoneTools.magnifyTouchDrag
});
toolManager.addTool('ellipticalRoi', {
mouse: cornerstoneTools.ellipticalRoi,
touch: cornerstoneTools.ellipticalRoiTouch
});
toolManager.addTool('rectangleRoi', {
mouse: cornerstoneTools.rectangleRoi,
touch: cornerstoneTools.rectangleRoiTouch
});
toolManager.addTool('annotate', {
mouse: cornerstoneTools.arrowAnnotate,
touch: cornerstoneTools.arrowAnnotateTouch
});
toolManager.addTool('rotate', {
mouse: cornerstoneTools.rotate,
touch: cornerstoneTools.rotateTouchDrag
});
toolManager.addTool('spine', {
mouse: cornerstoneTools.textMarker,
touch: cornerstoneTools.textMarkerTouch
});
toolManager.addTool('crosshairs', {
mouse: cornerstoneTools.crosshairs,
touch: cornerstoneTools.crosshairsTouch
});
if (OHIF.viewer.defaultTool) {
activeTool = OHIF.viewer.defaultTool;
}
this.configureTools();
initialized = true;
},
configureTools() {
// Get Cornerstone Tools // Get Cornerstone Tools
const { panMultiTouch, textStyle, toolStyle, toolColors, const { panMultiTouch, textStyle, toolStyle, toolColors,
length, arrowAnnotate, zoom, ellipticalRoi } = cornerstoneTools; length, arrowAnnotate, zoom, ellipticalRoi } = cornerstoneTools;
@ -86,70 +181,12 @@ const configureTools = () => {
maxScale: 10 maxScale: 10
}; };
zoom.setConfiguration(zoomConfig); zoom.setConfiguration(zoomConfig);
} },
setGestures(newGestures) {
toolManager = { gestures = newGestures;
init() { },
toolManager.addTool('wwwc', { getGestures() {
mouse: cornerstoneTools.wwwc, return gestures;
touch: cornerstoneTools.wwwcTouchDrag
});
toolManager.addTool('zoom', {
mouse: cornerstoneTools.zoom,
touch: cornerstoneTools.zoomTouchDrag
});
toolManager.addTool('wwwcRegion', {
mouse: cornerstoneTools.wwwcRegion,
touch: cornerstoneTools.wwwcRegionTouch
});
toolManager.addTool('dragProbe', {
mouse: cornerstoneTools.dragProbe,
touch: cornerstoneTools.dragProbeTouch
});
toolManager.addTool('pan', {
mouse: cornerstoneTools.pan,
touch: cornerstoneTools.panTouchDrag
});
toolManager.addTool('stackScroll', {
mouse: cornerstoneTools.stackScroll,
touch: cornerstoneTools.stackScrollTouchDrag
});
toolManager.addTool('length', {
mouse: cornerstoneTools.length,
touch: cornerstoneTools.lengthTouch
});
toolManager.addTool('angle', {
mouse: cornerstoneTools.simpleAngle,
touch: cornerstoneTools.simpleAngleTouch
});
toolManager.addTool('magnify', {
mouse: cornerstoneTools.magnify,
touch: cornerstoneTools.magnifyTouchDrag
});
toolManager.addTool('ellipticalRoi', {
mouse: cornerstoneTools.ellipticalRoi,
touch: cornerstoneTools.ellipticalRoiTouch
});
toolManager.addTool('rectangleRoi', {
mouse: cornerstoneTools.rectangleRoi,
touch: cornerstoneTools.rectangleRoiTouch
});
toolManager.addTool('annotate', {
mouse: cornerstoneTools.arrowAnnotate,
touch: cornerstoneTools.arrowAnnotateTouch
});
toolManager.addTool('rotate', {
mouse: cornerstoneTools.rotate,
touch: cornerstoneTools.rotateTouchDrag
});
if (OHIF.viewer.defaultTool) {
activeTool = OHIF.viewer.defaultTool;
}
configureTools();
initialized = true;
}, },
addTool(name, base) { addTool(name, base) {
tools[name] = base; tools[name] = base;
@ -164,7 +201,7 @@ toolManager = {
return toolDefaultStates; return toolDefaultStates;
}, },
setActiveToolForElement(tool, element) { setActiveToolForElement(tool, element) {
var canvases = $(element).find('canvas'); const canvases = $(element).find('canvas');
if (element.classList.contains('empty') || !canvases.length) { if (element.classList.contains('empty') || !canvases.length) {
return; return;
} }
@ -177,12 +214,12 @@ toolManager = {
} }
// Enable tools based on their default states // Enable tools based on their default states
Object.keys(toolDefaultStates).forEach(function(action) { Object.keys(toolDefaultStates).forEach( action => {
var relevantTools = toolDefaultStates[action]; const relevantTools = toolDefaultStates[action];
if (!relevantTools || !relevantTools.length || action === 'disabledToolButtons') { if (!relevantTools || !relevantTools.length || action === 'disabledToolButtons') {
return; return;
} }
relevantTools.forEach(function(toolType) { relevantTools.forEach( toolType => {
// the currently active tool has already been deactivated and can be skipped // the currently active tool has already been deactivated and can be skipped
if (action === 'deactivate' && toolType === activeTool) { if (action === 'deactivate' && toolType === activeTool) {
return; return;
@ -196,40 +233,75 @@ toolManager = {
}); });
// Get the stack toolData // Get the stack toolData
var toolData = cornerstoneTools.getToolState(element, 'stack'); const toolData = cornerstoneTools.getToolState(element, 'stack');
if (!toolData || !toolData.data || !toolData.data.length) { if (!toolData || !toolData.data || !toolData.data.length) {
return; return;
} }
// Get the imageIds for this element // Get the imageIds for this element
var imageIds = toolData.data[0].imageIds; const imageIds = toolData.data[0].imageIds;
// Deactivate all the middle mouse, right click, and scroll wheel tools // Deactivate all the middle mouse, right click, and scroll wheel tools
cornerstoneTools.pan.deactivate(element); cornerstoneTools.pan.deactivate(element);
cornerstoneTools.zoom.deactivate(element); cornerstoneTools.zoom.deactivate(element);
cornerstoneTools.zoomWheel.deactivate(element); cornerstoneTools.zoomWheel.deactivate(element);
cornerstoneTools.stackScrollWheel.deactivate(element); cornerstoneTools.stackScrollWheel.deactivate(element);
cornerstoneTools.panMultiTouch.disable(element);
cornerstoneTools.zoomTouchPinch.disable(element);
cornerstoneTools.stackScrollMultiTouch.disable(element);
cornerstoneTools.doubleTapZoom.disable(element);
// Reactivate the middle mouse and right click tools
cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button
// Reactivate the relevant scrollwheel tool for this element // Reactivate the relevant scrollwheel tool for this element
let multiTouchPanConfig;
if (imageIds.length > 1) { if (imageIds.length > 1) {
// scroll is the default tool for middle mouse wheel for stacks // scroll is the default tool for middle mouse wheel for stacks
cornerstoneTools.stackScrollWheel.activate(element); cornerstoneTools.stackScrollWheel.activate(element);
if (gestures['stackScrollMultiTouch'].enabled === true) {
cornerstoneTools.stackScrollMultiTouch.activate(element); // Three finger scroll
}
multiTouchPanConfig = {
testPointers(eventData) {
return (eventData.numPointers === 2);
}
};
cornerstoneTools.panMultiTouch.setConfiguration(multiTouchPanConfig);
} else { } else {
// zoom is the default tool for middle mouse wheel for single images (non stacks) // zoom is the default tool for middle mouse wheel for single images (non stacks)
cornerstoneTools.zoomWheel.activate(element); cornerstoneTools.zoomWheel.activate(element);
multiTouchPanConfig = {
testPointers(eventData) {
return (eventData.numPointers >= 2);
}
};
cornerstoneTools.panMultiTouch.setConfiguration(multiTouchPanConfig);
} }
// This block ensures that the middle mouse and scroll tools keep working // This block ensures that the middle mouse and scroll tools keep working
if (tool === 'pan') { if (tool === 'pan') {
cornerstoneTools.pan.activate(element, 3); // 3 means left mouse button and middle mouse button cornerstoneTools.pan.activate(element, 3); // 3 means left mouse button and middle mouse button
cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button } else if (tool === 'crosshairs') {
cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button
const currentFrameOfReferenceUID = getFrameOfReferenceUID(element);
if (currentFrameOfReferenceUID) {
updateCrosshairsSynchronizer(currentFrameOfReferenceUID);
const synchronizer = crosshairsSynchronizers.synchronizers[currentFrameOfReferenceUID];
// Activate the chosen tool
tools[tool].mouse.activate(element, 1, synchronizer);
}
} else if (tool === 'zoom') { } else if (tool === 'zoom') {
cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button
cornerstoneTools.zoom.activate(element, 5); // 5 means left mouse button and right mouse button cornerstoneTools.zoom.activate(element, 5); // 5 means left mouse button and right mouse button
} else { } else {
// Reactivate the middle mouse and right click tools // Reactivate the middle mouse and right click tools
cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button
cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button
// Activate the chosen tool // Activate the chosen tool
tools[tool].mouse.activate(element, 1); tools[tool].mouse.activate(element, 1);
@ -239,14 +311,45 @@ toolManager = {
tools[tool].touch.activate(element); tools[tool].touch.activate(element);
} }
cornerstoneTools.zoomTouchPinch.activate(element); if (gestures['zoomTouchPinch'].enabled === true) {
cornerstoneTools.panMultiTouch.activate(element); cornerstoneTools.zoomTouchPinch.activate(element); // Two finger pinch
}
if (gestures['panMultiTouch'].enabled === true) {
cornerstoneTools.panMultiTouch.activate(element); // Two or >= Two finger pan
}
if (gestures['doubleTapZoom'].enabled === true) {
cornerstoneTools.doubleTapZoom.activate(element);
}
}, },
setActiveTool(tool, elements) { setActiveTool(tool, elements) {
if (!initialized) { if (!initialized) {
toolManager.init(); toolManager.init();
} }
/**
* TODO: Add textMarkerDialogs template to OHIF's
*/
const dialog = document.getElementById('textMarkerOptionsDialog');
if(dialog) {
if (tool === 'spine' && activeTool !== 'spine' && dialog.getAttribute('open') !== 'open') {
dialog.show();
} else if(activeTool !== 'spine' && dialog.getAttribute("open") === "open") {
dialog.close();
}
}
/**
* TODO: Use Session variables to activate a button and use Helpers like in toolbarSectionButton.js from OHIFs.
*/
// Set the div to active for the tool
$('.imageViewerButton').removeClass('active');
const toolButton = document.getElementById(tool);
if (toolButton) {
toolButton.classList.add('active');
}
if (!tool) { if (!tool) {
tool = defaultTool; tool = defaultTool;
} }
@ -277,6 +380,11 @@ toolManager = {
getDefaultTool() { getDefaultTool() {
return defaultTool; return defaultTool;
}, },
setConfigureTools(configureTools) {
if(typeof configureTools === 'function') {
this.configureTools = configureTools;
}
},
activateCommandButton(button) { activateCommandButton(button) {
const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || []; const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || [];
@ -294,5 +402,4 @@ toolManager = {
Session.set('ToolManagerActiveCommandButtons', activeCommandButtons); Session.set('ToolManagerActiveCommandButtons', activeCommandButtons);
} }
} }
}; };