LT-398: Toggle manual stack scroll synchronization between viewports

This commit is contained in:
Leonardo Campos 2016-12-29 11:37:25 -02:00
parent 2e6c8245d8
commit c3fc0b2b3b
9 changed files with 187 additions and 4 deletions

View File

@ -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]);

View File

@ -39,7 +39,8 @@ Template.viewer.onCreated(() => {
flipV,
flipH,
rotateL,
rotateR
rotateR,
linkStackScroll
});
if (ViewerData[contentId].loadedSeriesData) {

View File

@ -235,4 +235,13 @@
<rect x="26" y="26" width="18.8" height="18.8" rx="1" ry="1"/>
</g>
</symbol>
<symbol id="icon-tools-link-stack-scroll" viewBox="0 0 32 32">
<title>
Stack Scroll Sync
</title>
<g id="icon-tools-link-stack-scroll-group" stroke="none">
<path d="m5.364 28.917c1.047 1.046 2.422 1.57 3.797 1.57 1.376 0 2.751-0.523 3.798-1.572l7.552-7.554c1.016-1.014 1.574-2.361 1.574-3.797 -0.001-1.432-0.558-2.781-1.574-3.796l-2.297-2.298 -2.009 2.009 2.297 2.298c0.478 0.478 0.741 1.113 0.741 1.788 0 0.674-0.264 1.309-0.741 1.786l-7.553 7.555c-0.987 0.985-2.59 0.985-3.576 0l-2.297-2.298c-0.987-0.985-0.987-2.589 0-3.576l3.775-3.776 -2.009-2.009 -3.776 3.776c-2.094 2.096-2.092 5.502 0 7.595l2.299 2.3zM26.695 2.992"/>
<path d="m26.695 2.992c-1.014-1.016-2.362-1.575-3.797-1.575 -0.001 0-0.001 0-0.002 0 -1.435 0-2.784 0.56-3.798 1.573l-7.551 7.553c-1.017 1.016-1.576 2.363-1.576 3.799 0 1.434 0.558 2.784 1.574 3.797l2.297 2.297 2.01-2.009 -2.298-2.297c-0.477-0.477-0.741-1.113-0.741-1.788 0-0.676 0.265-1.311 0.742-1.788l7.553-7.555c0.477-0.477 1.111-0.74 1.789-0.74 0 0 0 0 0.001 0 0.674 0 1.309 0.264 1.786 0.74l2.297 2.299c0.477 0.477 0.74 1.111 0.74 1.788 0 0.674-0.264 1.311-0.74 1.788l-3.776 3.777 2.009 2.009 3.776-3.777c1.014-1.013 1.573-2.363 1.574-3.797 0-1.435-0.56-2.784-1.574-3.797l-2.295-2.296z"/>
</g>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -3,7 +3,7 @@
{{>UI.dynamic template=this.buttonTemplateName data=this}}
{{else}}
<div id="{{this.id}}"
class="toolbarSectionButton rp-x-1 {{this.classes}} {{activeClass}} {{#if this.disabled}}disabled{{/if}} {{#if this.disableFunction}}{{#if (disableButton)}}disabled{{/if}}{{/if}}"
class="toolbarSectionButton rp-x-1 {{this.classes}} {{activeClass}} {{#if this.disabled}}disabled{{/if}} {{#if (disableButton)}}disabled{{/if}}"
title="{{#if this.tooltipTitle}}{{this.tooltipTitle}}{{else}}{{this.title}}{{/if}}">
<div class="svgContainer">
{{#if this.svgLink}}

View File

@ -12,7 +12,8 @@ Template.toolbarSectionButton.helpers({
}
},
disableButton() {
return this.disableFunction();
const instance = Template.instance();
return this.disableFunction && this.disableFunction();
}
});

View File

@ -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

View File

@ -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();

View File

@ -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()));

View File

@ -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');
});