LT-398: Manual stack scroll synchronization
This commit is contained in:
parent
e3561690f1
commit
1d2c1c55fe
@ -78,15 +78,6 @@ Template.toolbarSection.helpers({
|
||||
disabled: isToolDisabled
|
||||
});
|
||||
|
||||
// Disabling this on Lesion Tracker
|
||||
// targetSubTools.push({
|
||||
// id: 'targetEX',
|
||||
// title: 'EX Target',
|
||||
// classes: 'imageViewerTool',
|
||||
// svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-measure-target-ex',
|
||||
// disabled: isToolDisabled
|
||||
// });
|
||||
|
||||
const extraTools = [];
|
||||
|
||||
extraTools.push({
|
||||
@ -145,14 +136,6 @@ Template.toolbarSection.helpers({
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-elliptical-roi'
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'linkStackScroll',
|
||||
title: 'Link Scroll',
|
||||
classes: 'imageViewerCommand nonAutoDisableState',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-link-stack-scroll',
|
||||
disableFunction: isStackScrollLinkingDisabled
|
||||
});
|
||||
|
||||
extraTools.push({
|
||||
id: 'toggleCineDialog',
|
||||
title: 'CINE',
|
||||
@ -184,15 +167,13 @@ Template.toolbarSection.helpers({
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-pan'
|
||||
});
|
||||
|
||||
/*
|
||||
TODO: design the link functionality
|
||||
Commenting this out until we build this tool
|
||||
buttonData.push({
|
||||
id: 'link',
|
||||
title: 'Link',
|
||||
classes: 'imageViewerCommand',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-link'
|
||||
});*/
|
||||
classes: 'imageViewerCommand toolbarSectionButton nonAutoDisableState',
|
||||
svgLink: '/packages/ohif_viewerbase/assets/icons.svg#icon-tools-link',
|
||||
disableFunction: isStackScrollLinkingDisabled
|
||||
});
|
||||
|
||||
buttonData.push({
|
||||
id: 'toggleTarget',
|
||||
|
||||
@ -40,7 +40,7 @@ Template.viewer.onCreated(() => {
|
||||
flipH,
|
||||
rotateL,
|
||||
rotateR,
|
||||
linkStackScroll
|
||||
link
|
||||
});
|
||||
|
||||
if (ViewerData[contentId].loadedSeriesData) {
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
<div class="topright dicomTag">
|
||||
<div>{{studyDescription}}</div>
|
||||
<div>{{formatDA studyDate}} {{formatTM studyTime}}</div>
|
||||
{{#if linked}}
|
||||
<div class="link-icon">
|
||||
<svg>
|
||||
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/packages/ohif_viewerbase/assets/icons.svg#icon-viewport-link"></use>
|
||||
</svg>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="bottomright dicomTag">
|
||||
<div>{{#if zoom}}Zoom: {{formatNumberPrecision zoom 0}}%{{/if}}</div>
|
||||
|
||||
@ -25,5 +25,10 @@ Template[defaultTemplate].helpers({
|
||||
const timepoint = timepoints[0];
|
||||
|
||||
return timepointApi.name(timepoint);
|
||||
},
|
||||
|
||||
linked: function() {
|
||||
const linkedViewports = Session.get('StackImagePositionOffsetSynchronizerLinkedViewports') || [];
|
||||
return (linkedViewports.indexOf(this.viewportIndex) !== -1);
|
||||
}
|
||||
});
|
||||
@ -262,11 +262,11 @@
|
||||
<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">
|
||||
<symbol id="icon-viewport-link" viewBox="0 0 32 32">
|
||||
<title>
|
||||
Stack Scroll Sync
|
||||
Viewport Link
|
||||
</title>
|
||||
<g id="icon-tools-link-stack-scroll-group" stroke="none">
|
||||
<g id="icon-viewport-link-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>
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
@ -13,9 +13,11 @@ Template.toolbarSectionButton.onCreated(() => {
|
||||
const currentId = instance.data.id;
|
||||
const isCurrentTool = currentId === activeToolId;
|
||||
const isSubTool = subTools && _.findWhere(subTools, { id: activeToolId });
|
||||
const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || [];
|
||||
const isActiveCommandButton = activeCommandButtons.indexOf(instance.data.id) !== -1;
|
||||
|
||||
// Check if the current tool or a sub tool is the active one
|
||||
return isCurrentTool || isSubTool;
|
||||
// Check if the current tool, a sub tool or a command button is active
|
||||
return isCurrentTool || isSubTool || isActiveCommandButton;
|
||||
};
|
||||
|
||||
instance.getActiveToolSubProperty = (propertyName, activeToolId) => {
|
||||
|
||||
@ -39,3 +39,12 @@ $viewportTagPadding = 20px
|
||||
&.controlsVisible
|
||||
.topright, .bottomright
|
||||
right: "calc(%s + 19px)" % $viewportTagPadding
|
||||
|
||||
svg
|
||||
theme('color', '$defaultColor')
|
||||
theme('fill', '$defaultColor')
|
||||
theme('stroke', '$defaultColor')
|
||||
background-color: transparent
|
||||
margin: 2px
|
||||
width: 18px
|
||||
height: 18px
|
||||
|
||||
@ -5,7 +5,7 @@ import { $ } from 'meteor/jquery';
|
||||
class StackImagePositionOffsetSynchronizer {
|
||||
constructor() {
|
||||
this.active = false;
|
||||
this.syncedViewportElements = [];
|
||||
this.syncedViewports = [];
|
||||
this.synchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.stackImagePositionOffsetSynchronizer)
|
||||
}
|
||||
|
||||
@ -22,16 +22,24 @@ class StackImagePositionOffsetSynchronizer {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewportElements = this.getLinkableViewports();
|
||||
const viewports = this.getLinkableViewports();
|
||||
const viewportIndexes = [];
|
||||
|
||||
viewportElements.forEach((viewportElement, index) => {
|
||||
this.synchronizer.add(viewportElement);
|
||||
this.syncedViewportElements.push(viewportElement);
|
||||
if(viewports.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewports.forEach((viewport, index) => {
|
||||
this.synchronizer.add(viewport.element);
|
||||
this.syncedViewports.push(viewport);
|
||||
viewportIndexes.push(viewport.index)
|
||||
|
||||
$(viewportElement).on(StackImagePositionOffsetSynchronizer.ELEMENT_DISABLED_EVENT, this.elementDisabledHandler(this));
|
||||
$(viewport.element).on(StackImagePositionOffsetSynchronizer.ELEMENT_DISABLED_EVENT, this.elementDisabledHandler(this));
|
||||
});
|
||||
|
||||
this.active = true;
|
||||
toolManager.activateCommandButton('link');
|
||||
Session.set('StackImagePositionOffsetSynchronizerLinkedViewports', viewportIndexes);
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
@ -39,12 +47,13 @@ class StackImagePositionOffsetSynchronizer {
|
||||
return;
|
||||
}
|
||||
|
||||
while(this.syncedViewportElements.length) {
|
||||
const viewportElement = this.syncedViewportElements[0];
|
||||
this.removeViewportElement(viewportElement);
|
||||
while(this.syncedViewports.length) {
|
||||
const viewport = this.syncedViewports[0];
|
||||
this.removeViewport(viewport);
|
||||
}
|
||||
|
||||
this.active = false;
|
||||
toolManager.deactivateCommandButton('link');
|
||||
}
|
||||
|
||||
update() {
|
||||
@ -62,43 +71,68 @@ class StackImagePositionOffsetSynchronizer {
|
||||
this.activate();
|
||||
}
|
||||
|
||||
isViewportSynced(viewportElement) {
|
||||
return !!this.getViewportByElement(viewportElement);
|
||||
}
|
||||
|
||||
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);
|
||||
removeViewport(viewport) {
|
||||
const index = this.syncedViewports.indexOf(viewport);
|
||||
|
||||
if(index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.syncedViewportElements.splice(index, 1);
|
||||
this.synchronizer.remove(viewportElement);
|
||||
$(viewportElement).off(StackImagePositionOffsetSynchronizer.ELEMENT_DISABLED_EVENT);
|
||||
this.syncedViewports.splice(index, 1);
|
||||
this.synchronizer.remove(viewport.element);
|
||||
this.removeLinkedViewportFromSession(viewport);
|
||||
$(viewport.element).off(StackImagePositionOffsetSynchronizer.ELEMENT_DISABLED_EVENT);
|
||||
}
|
||||
|
||||
getViewportByElement(viewportElement) {
|
||||
const length = this.syncedViewports.length;
|
||||
|
||||
for(let i = 0; i < length; i++) {
|
||||
const viewport = this.syncedViewports[i];
|
||||
|
||||
if(viewport.element === viewportElement) {
|
||||
return viewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removeViewportByElement(viewportElement) {
|
||||
let viewport = this.getViewportByElement(viewportElement);
|
||||
|
||||
if(viewport) {
|
||||
this.removeViewport(viewport);
|
||||
}
|
||||
}
|
||||
|
||||
removeLinkedViewportFromSession(viewport) {
|
||||
const linkedViewports = Session.get('StackImagePositionOffsetSynchronizerLinkedViewports');
|
||||
const index = linkedViewports.indexOf(viewport.index);
|
||||
|
||||
if(index !== -1) {
|
||||
linkedViewports.splice(index, 1);
|
||||
Session.set('StackImagePositionOffsetSynchronizerLinkedViewports', linkedViewports);
|
||||
}
|
||||
}
|
||||
|
||||
elementDisabledHandler(context) {
|
||||
return (e, eventData) => {
|
||||
context.removeViewportElement(eventData.element);
|
||||
context.removeViewportByElement(eventData.element);
|
||||
}
|
||||
}
|
||||
|
||||
getLinkableViewports() {
|
||||
const activeViewportElement = this.getActiveViewportElement();
|
||||
const activeViewportImageNormal = this.getViewportImageNormal(activeViewportElement);
|
||||
const viewportElements = [];
|
||||
const viewports = [];
|
||||
|
||||
$('.imageViewerViewport').each((index, viewportElement) => {
|
||||
const viewportImageNormal = this.getViewportImageNormal(viewportElement);
|
||||
@ -109,12 +143,15 @@ class StackImagePositionOffsetSynchronizer {
|
||||
// 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)
|
||||
viewports.push({
|
||||
index: index,
|
||||
element: viewportElement
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return viewportElements;
|
||||
return viewports;
|
||||
}
|
||||
|
||||
getViewportImageNormal(element) {
|
||||
|
||||
@ -276,6 +276,23 @@ toolManager = {
|
||||
},
|
||||
getDefaultTool() {
|
||||
return defaultTool;
|
||||
},
|
||||
activateCommandButton(button) {
|
||||
const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || [];
|
||||
|
||||
if(activeCommandButtons.indexOf(button) === -1) {
|
||||
activeCommandButtons.push('link');
|
||||
Session.set('ToolManagerActiveCommandButtons', activeCommandButtons);
|
||||
}
|
||||
},
|
||||
deactivateCommandButton(button) {
|
||||
const activeCommandButtons = Session.get('ToolManagerActiveCommandButtons') || [];
|
||||
const index = activeCommandButtons.indexOf(button);
|
||||
|
||||
if(index !== -1) {
|
||||
activeCommandButtons.splice(index, 1);
|
||||
Session.set('ToolManagerActiveCommandButtons', activeCommandButtons);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -115,7 +115,7 @@ clearTools = () => {
|
||||
cornerstone.updateImage(element);
|
||||
};
|
||||
|
||||
linkStackScroll = () => {
|
||||
link = () => {
|
||||
const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;
|
||||
|
||||
if(synchronizer.isActive()) {
|
||||
|
||||
@ -297,5 +297,5 @@ Package.onUse(function(api) {
|
||||
api.export('flipH', 'client');
|
||||
api.export('rotateR', 'client');
|
||||
api.export('rotateL', 'client');
|
||||
api.export('linkStackScroll', 'client');
|
||||
api.export('link', 'client');
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user