From c3fc0b2b3b2b340641902557af0af428bda12886 Mon Sep 17 00:00:00 2001 From: Leonardo Campos Date: Thu, 29 Dec 2016 11:37:25 -0200 Subject: [PATCH] LT-398: Toggle manual stack scroll synchronization between viewports --- .../toolbarSection/toolbarSection.js | 10 +- .../client/components/viewer/viewer.js | 3 +- Packages/ohif-viewerbase/assets/icons.svg | 9 ++ .../toolbarSectionButton.html | 2 +- .../toolbarSectionButton.js | 3 +- .../client/lib/setActiveViewport.js | 3 + .../stackImagePositionOffsetSynchronizer.js | 135 ++++++++++++++++++ .../client/lib/viewportFunctions.js | 23 +++ Packages/ohif-viewerbase/package.js | 3 + 9 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 Packages/ohif-viewerbase/client/lib/stackImagePositionOffsetSynchronizer.js diff --git a/LesionTracker/client/components/toolbarSection/toolbarSection.js b/LesionTracker/client/components/toolbarSection/toolbarSection.js index 18cb39450..7c0d80dac 100644 --- a/LesionTracker/client/components/toolbarSection/toolbarSection.js +++ b/LesionTracker/client/components/toolbarSection/toolbarSection.js @@ -170,6 +170,14 @@ Template.toolbarSection.helpers({ svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-elliptical-roi' }); + buttonData.push({ + id: 'linkStackScroll', + title: 'Link Scroll', + classes: 'imageViewerCommand toolbarSectionButton nonAutoDisableState', + svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-link-stack-scroll', + disableFunction: isStackScrollLinkingDisabled + }); + buttonData.push({ id: 'toggleCineDialog', title: 'CINE', @@ -235,7 +243,7 @@ Template.toolbarSection.onRendered(function() { // Set disabled/enabled tool buttons that are set in toolManager const states = toolManager.getToolDefaultStates(); const disabledToolButtons = states.disabledToolButtons; - const allToolbarButtons = $('.toolbarSection').find('.toolbarSectionButton'); + const allToolbarButtons = $('.toolbarSection').find('.toolbarSectionButton:not(.nonAutoDisableState)'); // Additional toolbar buttons whose classes are not toolbarSectionButton allToolbarButtons.push($('#toolbarSectionEntry')[0]); diff --git a/LesionTracker/client/components/viewer/viewer.js b/LesionTracker/client/components/viewer/viewer.js index 0a5ffbd85..c46122b95 100644 --- a/LesionTracker/client/components/viewer/viewer.js +++ b/LesionTracker/client/components/viewer/viewer.js @@ -39,7 +39,8 @@ Template.viewer.onCreated(() => { flipV, flipH, rotateL, - rotateR + rotateR, + linkStackScroll }); if (ViewerData[contentId].loadedSeriesData) { diff --git a/Packages/ohif-viewerbase/assets/icons.svg b/Packages/ohif-viewerbase/assets/icons.svg index 36b40d99a..c6885d1ad 100644 --- a/Packages/ohif-viewerbase/assets/icons.svg +++ b/Packages/ohif-viewerbase/assets/icons.svg @@ -235,4 +235,13 @@ + + + Stack Scroll Sync + + + + + + diff --git a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.html b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.html index 950fe223b..46f12aca8 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.html +++ b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.html @@ -3,7 +3,7 @@ {{>UI.dynamic template=this.buttonTemplateName data=this}} {{else}}
{{#if this.svgLink}} diff --git a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js index 5cf7283aa..786a84eda 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js +++ b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js @@ -12,7 +12,8 @@ Template.toolbarSectionButton.helpers({ } }, disableButton() { - return this.disableFunction(); + const instance = Template.instance(); + return this.disableFunction && this.disableFunction(); } }); diff --git a/Packages/ohif-viewerbase/client/lib/setActiveViewport.js b/Packages/ohif-viewerbase/client/lib/setActiveViewport.js index 0b2dfdadd..fa6ac2774 100644 --- a/Packages/ohif-viewerbase/client/lib/setActiveViewport.js +++ b/Packages/ohif-viewerbase/client/lib/setActiveViewport.js @@ -1,3 +1,5 @@ +import { OHIF } from 'meteor/ohif:core'; + setActiveViewport = element => { if (!element) { return; @@ -26,6 +28,7 @@ setActiveViewport = element => { const domElement = jQueryElement.get(0); enablePrefetchOnElement(domElement); displayReferenceLines(domElement); + OHIF.viewer.stackImagePositionOffsetSynchronizer.update(); } // Set the div to focused, so keypress events are handled diff --git a/Packages/ohif-viewerbase/client/lib/stackImagePositionOffsetSynchronizer.js b/Packages/ohif-viewerbase/client/lib/stackImagePositionOffsetSynchronizer.js new file mode 100644 index 000000000..6e046d19a --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/stackImagePositionOffsetSynchronizer.js @@ -0,0 +1,135 @@ +import { OHIF } from 'meteor/ohif:core'; +import { Session } from 'meteor/session'; +import { $ } from 'meteor/jquery'; + +class StackImagePositionOffsetSynchronizer { + constructor() { + this.active = false; + this.syncedViewportElements = []; + this.synchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.stackImagePositionOffsetSynchronizer) + } + + static get ELEMENT_DISABLED_EVENT() { + return 'CornerstoneElementDisabled.StackImagePositionOffsetSynchronizer'; + } + + isActive() { + return this.active; + } + + activate() { + if(this.isActive()) { + return; + } + + const viewportElements = this.getLinkableViewports(); + + viewportElements.forEach((viewportElement, index) => { + this.synchronizer.add(viewportElement); + this.syncedViewportElements.push(viewportElement); + + $(viewportElement).on(StackImagePositionOffsetSynchronizer.ELEMENT_DISABLED_EVENT, this.elementDisabledHandler(this)); + }); + + this.active = true; + } + + deactivate() { + if(!this.isActive()) { + return; + } + + while(this.syncedViewportElements.length) { + const viewportElement = this.syncedViewportElements[0]; + this.removeViewportElement(viewportElement); + } + + this.active = false; + } + + update() { + if(!this.isActive()) { + return; + } + + const activeViewportElement = this.getActiveViewportElement(); + + if(this.isViewportSynced(activeViewportElement)) { + return; + } + + this.deactivate(); + this.activate(); + } + + getActiveViewportElement() { + const viewportIndex = Session.get('activeViewport') || 0; + return $('.imageViewerViewport').get(viewportIndex); + } + + isViewportSynced(viewportElement) { + let isSynced = false; + + this.syncedViewportElements.forEach(syncedViewportElement => { + isSynced = isSynced || (syncedViewportElement === viewportElement); + }); + + return isSynced; + } + + removeViewportElement(viewportElement) { + const index = this.syncedViewportElements.indexOf(viewportElement); + + if(index === -1) { + return; + } + + this.syncedViewportElements.splice(index, 1); + this.synchronizer.remove(viewportElement); + $(viewportElement).off(StackImagePositionOffsetSynchronizer.ELEMENT_DISABLED_EVENT); + } + + elementDisabledHandler(context) { + return (e, eventData) => { + context.removeViewportElement(eventData.element); + } + } + + getLinkableViewports() { + const activeViewportElement = this.getActiveViewportElement(); + const activeViewportImageNormal = this.getViewportImageNormal(activeViewportElement); + const viewportElements = []; + + $('.imageViewerViewport').each((index, viewportElement) => { + const viewportImageNormal = this.getViewportImageNormal(viewportElement); + + if(activeViewportImageNormal && viewportImageNormal) { + const angleInRadians = viewportImageNormal.angleTo(activeViewportImageNormal); + + // Pi / 12 radians = 15 degrees + // If the angle between two vectors is Pi, it means they are just inverted + if (angleInRadians < Math.PI / 12 || angleInRadians === Math.PI) { + viewportElements.push(viewportElement) + } + } + }); + + return viewportElements; + } + + getViewportImageNormal(element) { + element = $(element).get(0); + + try { + const enabledElement = cornerstone.getEnabledElement(element); + const imageId = enabledElement.image.imageId; + const imagePlane = cornerstoneTools.metaData.get('imagePlane', imageId); + + return imagePlane.rowCosines.clone().cross(imagePlane.columnCosines);; + } catch(error) { + console.log(error.message); + } + } +} + +OHIF.viewer.stackImagePositionOffsetSynchronizer = new StackImagePositionOffsetSynchronizer(); \ No newline at end of file diff --git a/Packages/ohif-viewerbase/client/lib/viewportFunctions.js b/Packages/ohif-viewerbase/client/lib/viewportFunctions.js index 961f11b6b..1c9928b9b 100644 --- a/Packages/ohif-viewerbase/client/lib/viewportFunctions.js +++ b/Packages/ohif-viewerbase/client/lib/viewportFunctions.js @@ -115,6 +115,16 @@ clearTools = () => { cornerstone.updateImage(element); }; +linkStackScroll = () => { + const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer; + + if(synchronizer.isActive()) { + synchronizer.deactivate(); + } else { + synchronizer.activate(); + } +} + // Toggle the play/stop state for the cornerstone clip tool toggleCinePlay = () => { // Get the active viewport element @@ -168,6 +178,8 @@ isPlaying = () => { return false; }; + + // Check if a study has multiple frames hasMultipleFrames = () => { // Its called everytime active viewport and/or layout change @@ -239,5 +251,16 @@ hasMultipleFrames = () => { return false; }; +isStackScrollLinkingDisabled = () => { + // Its called everytime active viewport and/or layout change + Session.get('activeViewport'); + Session.get('LayoutManagerUpdated'); + + const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer; + const linkableViewports = synchronizer.getLinkableViewports(); + + return linkableViewports.length <= 1; +} + // Create an event listener to update playing state when a clip stops playing $(window).on('CornerstoneToolsClipStopped', () => Session.set('UpdateCINE', Random.id())); diff --git a/Packages/ohif-viewerbase/package.js b/Packages/ohif-viewerbase/package.js index 5c4eefdcb..1c7c5c162 100644 --- a/Packages/ohif-viewerbase/package.js +++ b/Packages/ohif-viewerbase/package.js @@ -218,6 +218,7 @@ Package.onUse(function(api) { api.addFiles('client/lib/resizeViewportElements.js', 'client'); api.addFiles('client/lib/setFocusToActiveViewport.js', 'client'); api.addFiles('client/lib/updateAllViewports.js', 'client'); + api.addFiles('client/lib/stackImagePositionOffsetSynchronizer.js', 'client'); api.addFiles('client/lib/instanceClassSpecificViewport.js', 'client'); api.addFiles('client/lib/setMammogramViewportAlignment.js', 'client'); @@ -252,6 +253,7 @@ Package.onUse(function(api) { api.export('sopClassDictionary', 'client'); api.export('addMetaData', 'client'); api.export('hasMultipleFrames', 'client'); + api.export('isStackScrollLinkingDisabled') // Viewer management objects api.export('toolManager', 'client'); @@ -295,4 +297,5 @@ Package.onUse(function(api) { api.export('flipH', 'client'); api.export('rotateR', 'client'); api.export('rotateL', 'client'); + api.export('linkStackScroll', 'client'); });