LT-398: Toggle manual stack scroll synchronization between viewports
This commit is contained in:
parent
2e6c8245d8
commit
c3fc0b2b3b
@ -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]);
|
||||
|
||||
@ -39,7 +39,8 @@ Template.viewer.onCreated(() => {
|
||||
flipV,
|
||||
flipH,
|
||||
rotateL,
|
||||
rotateR
|
||||
rotateR,
|
||||
linkStackScroll
|
||||
});
|
||||
|
||||
if (ViewerData[contentId].loadedSeriesData) {
|
||||
|
||||
@ -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 |
@ -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}}
|
||||
|
||||
@ -12,7 +12,8 @@ Template.toolbarSectionButton.helpers({
|
||||
}
|
||||
},
|
||||
disableButton() {
|
||||
return this.disableFunction();
|
||||
const instance = Template.instance();
|
||||
return this.disableFunction && this.disableFunction();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
@ -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()));
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user