wip on react transition 8

This commit is contained in:
Erik Ziegler 2018-12-03 18:11:39 +01:00
parent 9ae5dd25ce
commit 71b49e6766
35 changed files with 818 additions and 470 deletions

View File

@ -5,6 +5,13 @@ import { Router } from 'meteor/clinical:router';
import { ReactiveVar } from 'meteor/reactive-var'; import { ReactiveVar } from 'meteor/reactive-var';
import { OHIF } from 'meteor/ohif:core'; import { OHIF } from 'meteor/ohif:core';
import { createStore } from 'redux';
const store = createStore(OHIF.viewerbase.redux.combinedReducer);
// TODO[react] Use a provider when the whole tree is React
window.store = store;
Template.ohifViewer.onCreated(() => { Template.ohifViewer.onCreated(() => {
const instance = Template.instance(); const instance = Template.instance();
instance.headerClasses = new ReactiveVar(''); instance.headerClasses = new ReactiveVar('');

View File

@ -38,13 +38,10 @@ const initHangingProtocol = () => {
HP.setEngine(ProtocolEngine); HP.setEngine(ProtocolEngine);
Session.set('ViewerReady', true); Session.set('ViewerReady', true);
Session.set('activeViewport', 0);
}); });
}; };
Meteor.startup(() => { Meteor.startup(() => {
Session.setDefault('activeViewport', false);
Session.setDefault('leftSidebar', false); Session.setDefault('leftSidebar', false);
Session.setDefault('rightSidebar', false); Session.setDefault('rightSidebar', false);
@ -112,8 +109,6 @@ Template.viewer.onCreated(() => {
// Store the viewer data in session for further user // Store the viewer data in session for further user
Session.setPersistent('ViewerData', OHIF.viewer.data); Session.setPersistent('ViewerData', OHIF.viewer.data);
Session.set('activeViewport', OHIF.viewer.data.activeViewport || 0);
// @TypeSafeStudies // @TypeSafeStudies
// Clears OHIF.viewer.Studies collection // Clears OHIF.viewer.Studies collection
OHIF.viewer.Studies.removeAll(); OHIF.viewer.Studies.removeAll();

View File

@ -151,7 +151,7 @@ Template.protocolEditor.helpers({
return ProtocolEngine.getCurrentStageModel(); return ProtocolEngine.getCurrentStageModel();
}, },
activeViewportUndefined() { activeViewportUndefined() {
const viewportIndex = Session.get('activeViewport'); const viewportIndex = window.store.getState().viewports.activeViewport;
return (viewportIndex === undefined); return (viewportIndex === undefined);
} }
}); });

View File

@ -318,7 +318,7 @@ Template.ruleEntryDialog.events({
rule.constraint = constraint; rule.constraint = constraint;
// Instruct the Protocol Engine to update the Layout Manager with new data // Instruct the Protocol Engine to update the Layout Manager with new data
var viewportIndex = Session.get('activeViewport'); var viewportIndex = window.store.getState().viewports.activeViewport;
ProtocolEngine.updateViewports(viewportIndex); ProtocolEngine.updateViewports(viewportIndex);
// Close the dialog // Close the dialog

View File

@ -6,7 +6,7 @@ Template.ruleTable.helpers({
*/ */
rulePassed: function() { rulePassed: function() {
// Retrieve the latest match details given the active viewport index // Retrieve the latest match details given the active viewport index
var viewportIndex = Session.get('activeViewport'); var viewportIndex = window.store.getState().viewports.activeViewport;
var details = ProtocolEngine.matchDetails[viewportIndex]; var details = ProtocolEngine.matchDetails[viewportIndex];
// If no match was found, stop here // If no match was found, stop here
@ -92,7 +92,7 @@ Template.ruleTable.events({
} }
// Instruct the Protocol Engine to update the Layout Manager with new data // Instruct the Protocol Engine to update the Layout Manager with new data
var viewportIndex = Session.get('activeViewport'); var viewportIndex = window.store.getState().viewports.activeViewport;
ProtocolEngine.updateViewports(viewportIndex); ProtocolEngine.updateViewports(viewportIndex);
}, },
/** /**
@ -108,7 +108,7 @@ Template.ruleTable.events({
rule.weight = $(event.currentTarget).val(); rule.weight = $(event.currentTarget).val();
// Instruct the Protocol Engine to update the Layout Manager with new data // Instruct the Protocol Engine to update the Layout Manager with new data
var viewportIndex = Session.get('activeViewport'); var viewportIndex = window.store.getState().viewports.activeViewport;
ProtocolEngine.updateViewports(viewportIndex); ProtocolEngine.updateViewports(viewportIndex);
}, },
/** /**
@ -124,7 +124,7 @@ Template.ruleTable.events({
rule.required = $(event.currentTarget).prop('checked'); rule.required = $(event.currentTarget).prop('checked');
// Instruct the Protocol Engine to update the Layout Manager with new data // Instruct the Protocol Engine to update the Layout Manager with new data
var viewportIndex = Session.get('activeViewport'); var viewportIndex = window.store.getState().viewports.activeViewport;
ProtocolEngine.updateViewports(viewportIndex); ProtocolEngine.updateViewports(viewportIndex);
} }
}); });

View File

@ -165,7 +165,7 @@ Template.settingEntryDialog.events({
viewport.viewportSettings[viewportSetting.id] = viewportSetting.value; viewport.viewportSettings[viewportSetting.id] = viewportSetting.value;
// Instruct the Protocol Engine to update the Layout Manager with new data // Instruct the Protocol Engine to update the Layout Manager with new data
var viewportIndex = Session.get('activeViewport'); var viewportIndex = window.store.getState().viewports.activeViewport;
ProtocolEngine.updateViewports(viewportIndex); ProtocolEngine.updateViewports(viewportIndex);
// Close the dialog // Close the dialog

View File

@ -37,7 +37,7 @@ Template.settingsTable.events({
delete viewport.viewportSettings[setting.key]; delete viewport.viewportSettings[setting.key];
// Instruct the Protocol Engine to update the Layout Manager with new data // Instruct the Protocol Engine to update the Layout Manager with new data
var viewportIndex = Session.get('activeViewport'); var viewportIndex = window.store.getState().viewports.activeViewport;
ProtocolEngine.updateViewports(viewportIndex); ProtocolEngine.updateViewports(viewportIndex);
} }
}); });

View File

@ -11,7 +11,7 @@ getActiveViewportModel = function() {
var stage = ProtocolEngine.getCurrentStageModel(); var stage = ProtocolEngine.getCurrentStageModel();
// Retrieve the index of the active viewport // Retrieve the index of the active viewport
var activeViewport = Session.get('activeViewport'); var activeViewport = window.store.getState().viewports.activeViewport;
// If the active viewport index is outside the bounds of the // If the active viewport index is outside the bounds of the
// number of Viewports defined for this Stage, stop here // number of Viewports defined for this Stage, stop here

View File

@ -8,7 +8,7 @@ OHIF.lesiontracker.pixelSpacingAutorunCheck = () => {
const $oncologyTools = $('button#lesion, button#nonTarget'); const $oncologyTools = $('button#lesion, button#nonTarget');
// TODO: Set activeViewport for empty viewport element // TODO: Set activeViewport for empty viewport element
const activeViewportIndex = Session.get('activeViewport'); const activeViewportIndex = window.store.getState().viewports.activeViewport;
if (activeViewportIndex === undefined) { if (activeViewportIndex === undefined) {
return; return;
} }

View File

@ -7,7 +7,7 @@ import { OHIF } from 'meteor/ohif:core';
* @returns {Object} - Timepoint data for the active viewport * @returns {Object} - Timepoint data for the active viewport
*/ */
OHIF.measurements.getActiveTimepoint = () => { OHIF.measurements.getActiveTimepoint = () => {
const activeViewportIndex = Session.get('activeViewport'); const activeViewportIndex = window.store.getState().viewports.activeViewport;
const { studyInstanceUid } = OHIF.viewerbase.layoutManager.viewportData[activeViewportIndex]; const { studyInstanceUid } = OHIF.viewerbase.layoutManager.viewportData[activeViewportIndex];
return OHIF.viewer.timepointApi.study(studyInstanceUid)[0]; return OHIF.viewer.timepointApi.study(studyInstanceUid)[0];
}; };

View File

@ -48,7 +48,7 @@ Template.thumbnailEntry.events({
// Get the viewport index // Get the viewport index
let { viewportIndex } = instance.data; let { viewportIndex } = instance.data;
if (_.isUndefined(viewportIndex)) { if (_.isUndefined(viewportIndex)) {
viewportIndex = Session.get('activeViewport') || 0; viewportIndex = window.store.getState().viewports.activeViewport || 0;
} }
// Rerender the viewport using the clicked thumbnail data // Rerender the viewport using the clicked thumbnail data
@ -61,7 +61,7 @@ Template.thumbnailEntry.events({
// Get the active viewport index and total number of viewports... // Get the active viewport index and total number of viewports...
const viewportCount = OHIF.viewerbase.layoutManager.getNumberOfViewports(); const viewportCount = OHIF.viewerbase.layoutManager.getNumberOfViewports();
let viewportIndex = Session.get('activeViewport') || 0; let viewportIndex = window.store.getState().viewports.activeViewport || 0;
if (viewportIndex >= viewportCount) { if (viewportIndex >= viewportCount) {
viewportIndex = viewportCount > 0 ? viewportCount - 1 : 0; viewportIndex = viewportCount > 0 ? viewportCount - 1 : 0;
} }

View File

@ -1,34 +1,43 @@
import { Component } from 'react'; import {
Component,
} from 'react';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cloneDeep from 'lodash.clonedeep'; import { OHIF } from 'meteor/ohif:core';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
// import * as cornerstone from 'cornerstone-core';
// import * as cornerstoneTools from 'cornerstone-tools';
//import * as cornerstone from 'cornerstone-core'; import {
//import * as cornerstoneTools from 'cornerstone-tools'; cornerstone,
cornerstoneTools,
import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; } from 'meteor/ohif:cornerstone';
//import './lib/initCornerstone.js'; import './CornerstoneViewport.styl';
// import './lib/initCornerstone.js';
import ImageScrollbar from '../ImageScrollbar/ImageScrollbar.js'; import ImageScrollbar from '../ImageScrollbar/ImageScrollbar.js';
import ViewportOverlay from '../ViewportOverlay/ViewportOverlay.js'; import ViewportOverlay from '../ViewportOverlay/ViewportOverlay.js';
import ToolContextMenu from '../ToolContextMenu/ToolContextMenu.js'; import ToolContextMenu from '../ToolContextMenu/ToolContextMenu.js';
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator.js'; import LoadingIndicator from '../LoadingIndicator/LoadingIndicator.js';
import ViewportOrientationMarkers from '../ViewportOrientationMarkers/ViewportOrientationMarkers.js'; import ViewportOrientationMarkers from '../ViewportOrientationMarkers/ViewportOrientationMarkers.js';
import { StackManager } from "../../../lib/StackManager"; import {
StackManager,
} from '../../../lib/StackManager';
import './CornerstoneViewport.styl'; import './CornerstoneViewport.styl';
const EVENT_RESIZE = 'resize'; const EVENT_RESIZE = 'resize';
const loadIndicatorDelay = 45; const loadIndicatorDelay = 45;
const { loadHandlerManager } = cornerstoneTools; const {
loadHandlerManager,
} = cornerstoneTools;
function setToolsPassive(tools) { function setToolsPassive(tools) {
tools.forEach(tool => { tools.forEach((tool) => {
cornerstoneTools.setToolPassive(tool); cornerstoneTools.setToolPassive(tool);
}); });
} }
function initializeTools(tools) { function initializeTools(tools) {
Array.from(tools).forEach(tool => { Array.from(tools).forEach((tool) => {
const apiTool = cornerstoneTools[`${tool.name}Tool`]; const apiTool = cornerstoneTools[`${tool.name}Tool`];
if (apiTool) { if (apiTool) {
cornerstoneTools.addTool(apiTool, tool.configuration); cornerstoneTools.addTool(apiTool, tool.configuration);
@ -39,12 +48,17 @@ function initializeTools(tools) {
} }
function getCornerstoneStack(viewportData) { function getCornerstoneStack(viewportData) {
const { displaySetInstanceUid, studyInstanceUid } = viewportData; const {
displaySetInstanceUid,
studyInstanceUid,
} = viewportData;
// Create shortcut to displaySet // Create shortcut to displaySet
const study = OHIF.viewer.Studies.findBy({ studyInstanceUid }); const study = OHIF.viewer.Studies.findBy({
studyInstanceUid,
});
const displaySet = study.displaySets.find(set => { const displaySet = study.displaySets.find((set) => {
return set.displaySetInstanceUid === displaySetInstanceUid; return set.displaySetInstanceUid === displaySetInstanceUid;
}); });
@ -52,7 +66,7 @@ function getCornerstoneStack(viewportData) {
const stack = StackManager.findOrCreateStack(study, displaySet); const stack = StackManager.findOrCreateStack(study, displaySet);
stack.currentImageIdIndex = 0; stack.currentImageIdIndex = 0;
return stack return stack;
} }
@ -62,7 +76,7 @@ class CornerstoneViewport extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
const stack = getCornerstoneStack(this.props.viewportData) const stack = getCornerstoneStack(this.props.viewportData);
// TODO: Allow viewport as a prop // TODO: Allow viewport as a prop
this.state = { this.state = {
@ -70,9 +84,9 @@ class CornerstoneViewport extends Component {
displaySetInstanceUid: this.props.viewportData.displaySetInstanceUid, displaySetInstanceUid: this.props.viewportData.displaySetInstanceUid,
imageId: stack.imageIds[0], imageId: stack.imageIds[0],
viewportHeight: '100%', viewportHeight: '100%',
isLoading: false,//true, isLoading: false, // true,
imageScrollbarValue: 0, imageScrollbarValue: 0,
numImagesLoaded: 0 numImagesLoaded: 0,
}; };
this.displayScrollbar = stack.imageIds.length > 1; this.displayScrollbar = stack.imageIds.length > 1;
@ -87,17 +101,21 @@ class CornerstoneViewport extends Component {
this.doneLoadingHandler = this.doneLoadingHandler.bind(this); this.doneLoadingHandler = this.doneLoadingHandler.bind(this);
this.onMouseClick = this.onMouseClick.bind(this); this.onMouseClick = this.onMouseClick.bind(this);
this.onTouchPress = this.onTouchPress.bind(this); this.onTouchPress = this.onTouchPress.bind(this);
this.onTouchStart = this.onTouchStart.bind(this);
this.onMeasurementAddedOrRemoved = this.onMeasurementAddedOrRemoved.bind( this.onMeasurementAddedOrRemoved = this.onMeasurementAddedOrRemoved.bind(
this this,
); );
this.setViewportActive = this.setViewportActive.bind(this);
this.onCloseToolContextMenu = this.onCloseToolContextMenu.bind(this); this.onCloseToolContextMenu = this.onCloseToolContextMenu.bind(this);
this.imageSliderOnInputCallback = this.imageSliderOnInputCallback.bind( this.imageSliderOnInputCallback = this.imageSliderOnInputCallback.bind(
this this,
); );
this.loadHandlerTimeout = 25; this.loadHandlerTimeout = 25;
//loadHandlerManager.setStartLoadHandler(this.startLoadingHandler); // loadHandlerManager.setStartLoadHandler(this.startLoadingHandler);
//loadHandlerManager.setEndLoadHandler(this.doneLoadingHandler); // loadHandlerManager.setEndLoadHandler(this.doneLoadingHandler);
this.debouncedResize = debounce(() => { this.debouncedResize = debounce(() => {
try { try {
@ -110,7 +128,7 @@ class CornerstoneViewport extends Component {
cornerstone.resize(this.element, true); cornerstone.resize(this.element, true);
this.setState({ this.setState({
viewportHeight: `${this.element.clientHeight - 20}px` viewportHeight: `${this.element.clientHeight - 20}px`,
}); });
}, 300); }, 300);
@ -118,50 +136,74 @@ class CornerstoneViewport extends Component {
this.slideTimeout = null; this.slideTimeout = null;
} }
render() { static defaultProps = {
const isLoading = activeTool: 'Wwwc',
this.state.isLoading || }
this.state.numImagesLoaded / this.state.stack.imageIds.length < 0.1;
return ( render() {
<> const isLoading = this.state.isLoading || this.state.numImagesLoaded / this.state.stack.imageIds.length < 0.1;
return (<div className="CornerstoneViewport" >
<ToolContextMenu <ToolContextMenu
toolContextMenuData={this.state.toolContextMenuData} toolContextMenuData={
onClose={this.onCloseToolContextMenu} this.state.toolContextMenuData
/> }
<div onClose={
className="CornerstoneViewport viewportElement" this.onCloseToolContextMenu
onContextMenu={this.onContextMenu} }
tabIndex='0' /> <div
ref={input => { className="viewport-element"
onContextMenu={
this.onContextMenu
}
ref={
(input) => {
this.element = input; this.element = input;
}} }
}
> >
{isLoading ? <LoadingIndicator /> : ''} {
<canvas className="cornerstone-canvas" /> isLoading ? <LoadingIndicator /> : ''
} <canvas className="cornerstone-canvas" />
<ViewportOverlay <ViewportOverlay
stack={this.state.stack} stack={
viewport={this.state.viewport} this.state.stack
imageId={this.state.imageId} }
numImagesLoaded={this.state.numImagesLoaded} viewport={
this.state.viewport
}
imageId={
this.state.imageId
}
numImagesLoaded={
this.state.numImagesLoaded
}
/> <ViewportOrientationMarkers
imageId={
this.state.imageId
}
viewport={
this.state.viewport
}
/> </div > <div className="viewportInstructions" >
Please drag a stack here to view images. </div> {
this.displayScrollbar && (<ImageScrollbar
onInputCallback={
this.imageSliderOnInputCallback
}
max={
this.state.stack.imageIds.length - 1
}
value={
this.state.imageScrollbarValue
}
height={
this.state.viewportHeight
}
/> />
<ViewportOrientationMarkers )
imageId={this.state.imageId} } {
viewport={this.state.viewport} /* this.state.bidirectionalAddLabelShow && (
/>
</div>
<div className='viewportInstructions'>
Please drag a stack here to view images.
</div>
{this.displayScrollbar && (
<ImageScrollbar
onInputCallback={this.imageSliderOnInputCallback}
max={this.state.stack.imageIds.length - 1}
value={this.state.imageScrollbarValue}
height={this.state.viewportHeight}
/>
)}
{/*this.state.bidirectionalAddLabelShow && (
<Labelling <Labelling
measurementData={this.bidirectional.measurementData} measurementData={this.bidirectional.measurementData}
eventData={this.bidirectional.eventData} eventData={this.bidirectional.eventData}
@ -169,17 +211,17 @@ class CornerstoneViewport extends Component {
skipButton={this.bidirectional.skipButton} skipButton={this.bidirectional.skipButton}
editDescription={this.bidirectional.editDescription} editDescription={this.bidirectional.editDescription}
/> />
)*/} )*/
</> } </div>
); );
} }
bidirectionalToolLabellingCallback = ( bidirectionalToolLabellingCallback = (
measurementData, measurementData,
eventData, eventData,
doneCallback, doneCallback,
options = {} options = {},
) => { ) => {
const labellingDoneCallback = () => { const labellingDoneCallback = () => {
this.hideExtraButtons(); this.hideExtraButtons();
return doneCallback(); return doneCallback();
@ -190,41 +232,41 @@ class CornerstoneViewport extends Component {
eventData, eventData,
labellingDoneCallback, labellingDoneCallback,
skipButton: options.skipButton, skipButton: options.skipButton,
editDescription: options.editDescription editDescription: options.editDescription,
}; };
this.setState({ this.setState({
bidirectionalAddLabelShow: true bidirectionalAddLabelShow: true,
}); });
}; };
onContextMenu(event) { onContextMenu(event) {
// Preventing the default behaviour for right-click is essential to // Preventing the default behaviour for right-click is essential to
// allow right-click tools to work. // allow right-click tools to work.
event.preventDefault(); event.preventDefault();
} }
onWindowResize() { onWindowResize() {
this.debouncedResize(); this.debouncedResize();
} }
onImageRendered() { onImageRendered() {
const viewport = cornerstone.getViewport(this.element); const viewport = cornerstone.getViewport(this.element);
/*this.setState({ this.setState({
viewport viewport,
});*/ });
} }
onNewImage() { onNewImage() {
const image = cornerstone.getImage(this.element); const image = cornerstone.getImage(this.element);
this.setState({ this.setState({
imageId: image.imageId imageId: image.imageId,
}); });
} }
componentDidMount() { componentDidMount() {
const element = this.element; const element = this.element;
// Enable the DOM Element for use with Cornerstone // Enable the DOM Element for use with Cornerstone
@ -232,11 +274,11 @@ class CornerstoneViewport extends Component {
cornerstone.events.addEventListener( cornerstone.events.addEventListener(
cornerstone.EVENTS.IMAGE_LOADED, cornerstone.EVENTS.IMAGE_LOADED,
this.onImageLoaded this.onImageLoaded,
); );
// Load the first image in the stack // Load the first image in the stack
cornerstone.loadAndCacheImage(this.state.imageId).then(image => { cornerstone.loadAndCacheImage(this.state.imageId).then((image) => {
try { try {
cornerstone.getEnabledElement(element); cornerstone.getEnabledElement(element);
} catch (error) { } catch (error) {
@ -249,7 +291,7 @@ class CornerstoneViewport extends Component {
const viewport = cornerstone.getDefaultViewportForImage(element, image); const viewport = cornerstone.getDefaultViewportForImage(element, image);
viewport.voi = { viewport.voi = {
windowWidth: 400, windowWidth: 400,
windowCenter: 40 windowCenter: 40,
}; };
// Display the first image // Display the first image
@ -267,31 +309,44 @@ class CornerstoneViewport extends Component {
cornerstoneTools.addToolState(element, 'stack', stack); cornerstoneTools.addToolState(element, 'stack', stack);
cornerstoneTools.stackPrefetch.enable(this.element); cornerstoneTools.stackPrefetch.enable(this.element);
const tools = [ const tools = [{
{
name: 'Bidirectional', name: 'Bidirectional',
configuration: { configuration: {
getMeasurementLocationCallback: this getMeasurementLocationCallback: this
.bidirectionalToolLabellingCallback, .bidirectionalToolLabellingCallback,
shadow: true, shadow: true,
drawHandlesOnHover: true drawHandlesOnHover: true,
} },
},
{
name: 'Wwwc',
}, },
{ name: 'Wwwc' },
{ {
name: 'Zoom', name: 'Zoom',
configuration: { configuration: {
minScale: 0.3, minScale: 0.3,
maxScale: 25, maxScale: 25,
preventZoomOutsideImage: true preventZoomOutsideImage: true,
} },
},
{
name: 'Pan',
},
{
name: 'StackScroll',
},
{
name: 'PanMultiTouch',
},
{
name: 'ZoomTouchPinch',
},
{
name: 'StackScrollMouseWheel',
},
{
name: 'StackScrollMultiTouch',
}, },
{ name: 'Pan' },
{ name: 'StackScroll' },
{ name: 'PanMultiTouch' },
{ name: 'ZoomTouchPinch' },
{ name: 'StackScrollMouseWheel' },
{ name: 'StackScrollMultiTouch' }
]; ];
initializeTools(tools); initializeTools(tools);
@ -305,22 +360,22 @@ class CornerstoneViewport extends Component {
*/ */
cornerstoneTools.setToolActive('PanMultiTouch', { cornerstoneTools.setToolActive('PanMultiTouch', {
mouseButtonMask: 0, mouseButtonMask: 0,
isTouchActive: true isTouchActive: true,
}); });
cornerstoneTools.setToolActive('ZoomTouchPinch', { cornerstoneTools.setToolActive('ZoomTouchPinch', {
mouseButtonMask: 0, mouseButtonMask: 0,
isTouchActive: true isTouchActive: true,
}); });
cornerstoneTools.setToolActive('StackScrollMultiTouch', { cornerstoneTools.setToolActive('StackScrollMultiTouch', {
mouseButtonMask: 0, mouseButtonMask: 0,
isTouchActive: true isTouchActive: true,
}); });
cornerstoneTools.stackPrefetch.setConfiguration({ cornerstoneTools.stackPrefetch.setConfiguration({
maxImagesToPrefetch: Infinity, maxImagesToPrefetch: Infinity,
preserveExistingPool: false, preserveExistingPool: false,
maxSimultaneousRequests: 6 maxSimultaneousRequests: 6,
}); });
/* For mouse devices, by default we turn on: /* For mouse devices, by default we turn on:
@ -332,81 +387,91 @@ class CornerstoneViewport extends Component {
cornerstoneTools.setToolActive('StackScrollMouseWheel', { cornerstoneTools.setToolActive('StackScrollMouseWheel', {
mouseButtonMask: 0, mouseButtonMask: 0,
isTouchActive: true isTouchActive: true,
}); });
element.addEventListener( element.addEventListener(
cornerstone.EVENTS.IMAGE_RENDERED, cornerstone.EVENTS.IMAGE_RENDERED,
this.onImageRendered this.onImageRendered,
); );
element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage); element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage);
element.addEventListener( element.addEventListener(
cornerstoneTools.EVENTS.STACK_SCROLL, cornerstoneTools.EVENTS.STACK_SCROLL,
this.onStackScroll this.onStackScroll,
); );
element.addEventListener( element.addEventListener(
cornerstoneTools.EVENTS.MEASUREMENT_ADDED, cornerstoneTools.EVENTS.MEASUREMENT_ADDED,
this.onMeasurementAddedOrRemoved this.onMeasurementAddedOrRemoved,
); );
element.addEventListener( element.addEventListener(
cornerstoneTools.EVENTS.MEASUREMENT_REMOVED, cornerstoneTools.EVENTS.MEASUREMENT_REMOVED,
this.onMeasurementAddedOrRemoved this.onMeasurementAddedOrRemoved,
); );
element.addEventListener( element.addEventListener(
cornerstoneTools.EVENTS.MOUSE_CLICK, cornerstoneTools.EVENTS.MOUSE_CLICK,
this.onMouseClick this.onMouseClick,
); );
element.addEventListener( element.addEventListener(
cornerstoneTools.EVENTS.TOUCH_PRESS, cornerstoneTools.EVENTS.TOUCH_PRESS,
this.onTouchPress this.onTouchPress,
);
element.addEventListener(
cornerstoneTools.EVENTS.TOUCH_START,
this.onTouchStart,
); );
window.addEventListener(EVENT_RESIZE, this.onWindowResize); window.addEventListener(EVENT_RESIZE, this.onWindowResize);
this.setState({ this.setState({
viewportHeight: `${this.element.clientHeight - 20}px` viewportHeight: `${this.element.clientHeight - 20}px`,
}); });
}); });
} }
componentWillUnmount() { componentWillUnmount() {
const element = this.element; const element = this.element;
element.removeEventListener( element.removeEventListener(
cornerstone.EVENTS.IMAGE_RENDERED, cornerstone.EVENTS.IMAGE_RENDERED,
this.onImageRendered this.onImageRendered,
); );
element.removeEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage); element.removeEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage);
element.removeEventListener( element.removeEventListener(
cornerstoneTools.EVENTS.STACK_SCROLL, cornerstoneTools.EVENTS.STACK_SCROLL,
this.onStackScroll this.onStackScroll,
); );
element.removeEventListener( element.removeEventListener(
cornerstoneTools.EVENTS.MEASUREMENT_ADDED, cornerstoneTools.EVENTS.MEASUREMENT_ADDED,
this.onMeasurementAddedOrRemoved this.onMeasurementAddedOrRemoved,
); );
element.removeEventListener( element.removeEventListener(
cornerstoneTools.EVENTS.MEASUREMENT_REMOVED, cornerstoneTools.EVENTS.MEASUREMENT_REMOVED,
this.onMeasurementAddedOrRemoved this.onMeasurementAddedOrRemoved,
); );
element.removeEventListener( element.removeEventListener(
cornerstoneTools.EVENTS.MOUSE_CLICK, cornerstoneTools.EVENTS.MOUSE_CLICK,
this.onMouseClick this.onMouseClick,
); );
element.removeEventListener( element.removeEventListener(
cornerstoneTools.EVENTS.TOUCH_PRESS, cornerstoneTools.EVENTS.TOUCH_PRESS,
this.onTouchPress this.onTouchPress,
);
element.removeEventListener(
cornerstoneTools.EVENTS.TOUCH_START,
this.onTouchStart,
); );
window.removeEventListener(EVENT_RESIZE, this.onWindowResize); window.removeEventListener(EVENT_RESIZE, this.onWindowResize);
@ -415,21 +480,27 @@ class CornerstoneViewport extends Component {
cornerstone.events.removeEventListener( cornerstone.events.removeEventListener(
cornerstone.EVENTS.IMAGE_LOADED, cornerstone.EVENTS.IMAGE_LOADED,
this.onImageLoaded this.onImageLoaded,
); );
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
// TODO: Add a real object shallow comparison here? // TODO: Add a real object shallow comparison here?
if ( if (
this.state.displaySetInstanceUid !== this.props.viewportData.displaySetInstanceUid this.state.displaySetInstanceUid !== this.props.viewportData.displaySetInstanceUid
) { ) {
const { displaySetInstanceUid, studyInstanceUid, currentImageIdIndex } = this.props.viewportData; const {
displaySetInstanceUid,
studyInstanceUid,
currentImageIdIndex,
} = this.props.viewportData;
// Create shortcut to displaySet // Create shortcut to displaySet
const study = OHIF.viewer.Studies.findBy({ studyInstanceUid }); const study = OHIF.viewer.Studies.findBy({
studyInstanceUid,
});
const displaySet = study.displaySets.find(set => { const displaySet = study.displaySets.find((set) => {
return set.displaySetInstanceUid === displaySetInstanceUid; return set.displaySetInstanceUid === displaySetInstanceUid;
}); });
@ -441,7 +512,7 @@ class CornerstoneViewport extends Component {
if (!currentStack) { if (!currentStack) {
currentStack = { currentStack = {
currentImageIdIndex, currentImageIdIndex,
imageIds: stack.imageIds imageIds: stack.imageIds,
}; };
cornerstoneTools.addStackStateManager(this.element, ['stack']); cornerstoneTools.addStackStateManager(this.element, ['stack']);
@ -458,11 +529,11 @@ class CornerstoneViewport extends Component {
displaySetInstanceUid, displaySetInstanceUid,
studyInstanceUid, studyInstanceUid,
stack, stack,
imageId imageId,
}); });
cornerstoneTools.stackPrefetch.disable(this.element); cornerstoneTools.stackPrefetch.disable(this.element);
cornerstone.loadAndCacheImage(imageId).then(image => { cornerstone.loadAndCacheImage(imageId).then((image) => {
try { try {
cornerstone.getEnabledElement(this.element); cornerstone.getEnabledElement(this.element);
} catch (error) { } catch (error) {
@ -476,8 +547,8 @@ class CornerstoneViewport extends Component {
// Workaround for Cornerstone issue #304 // Workaround for Cornerstone issue #304
viewport.displayedArea.brhc = { viewport.displayedArea.brhc = {
x: image.width, x: image.width,
y: image.height y: image.height,
} };
cornerstone.displayImage(this.element, image, viewport); cornerstone.displayImage(this.element, image, viewport);
@ -491,15 +562,15 @@ class CornerstoneViewport extends Component {
// TODO: Why do we need to do this in v3? // TODO: Why do we need to do this in v3?
cornerstoneTools.setToolActive('StackScrollMouseWheel', { cornerstoneTools.setToolActive('StackScrollMouseWheel', {
mouseButtonMask: 0, mouseButtonMask: 0,
isTouchActive: true isTouchActive: true,
}); });
} }
// TODO: Check this, causes infinite loop // TODO: Check this, causes infinite loop
//this.debouncedResize(); // this.debouncedResize();
} }
setActiveTool = activeTool => { setActiveTool = (activeTool) => {
const leftMouseTools = ['Bidirectional', 'Wwwc', 'StackScroll']; const leftMouseTools = ['Bidirectional', 'Wwwc', 'StackScroll'];
setToolsPassive(leftMouseTools); setToolsPassive(leftMouseTools);
@ -508,7 +579,7 @@ class CornerstoneViewport extends Component {
const isPanToolActive = activeTool === 'Pan'; const isPanToolActive = activeTool === 'Pan';
const panOptions = { const panOptions = {
mouseButtonMask: isPanToolActive ? [1, 4] : [4], mouseButtonMask: isPanToolActive ? [1, 4] : [4],
isTouchActive: isPanToolActive isTouchActive: isPanToolActive,
}; };
cornerstoneTools.setToolActive('Pan', panOptions); cornerstoneTools.setToolActive('Pan', panOptions);
@ -516,17 +587,19 @@ class CornerstoneViewport extends Component {
const isZoomToolActive = activeTool === 'Zoom'; const isZoomToolActive = activeTool === 'Zoom';
const zoomOptions = { const zoomOptions = {
mouseButtonMask: isZoomToolActive ? [1, 2] : [2], mouseButtonMask: isZoomToolActive ? [1, 2] : [2],
isTouchActive: isZoomToolActive isTouchActive: isZoomToolActive,
}; };
cornerstoneTools.setToolActive('Zoom', zoomOptions); cornerstoneTools.setToolActive('Zoom', zoomOptions);
cornerstoneTools.setToolActive(activeTool, { cornerstoneTools.setToolActive(activeTool, {
mouseButtonMask: 1, mouseButtonMask: 1,
isTouchActive: true isTouchActive: true,
}); });
}; };
onStackScroll(event) {
this.setViewportActive();
onStackScroll(event) {
const element = event.currentTarget; const element = event.currentTarget;
const stackData = cornerstoneTools.getToolState(element, 'stack'); const stackData = cornerstoneTools.getToolState(element, 'stack');
const stack = stackData.data[0]; const stack = stackData.data[0];
@ -535,36 +608,36 @@ class CornerstoneViewport extends Component {
this.setState({ this.setState({
stack, stack,
imageScrollbarValue: stack.currentImageIdIndex imageScrollbarValue: stack.currentImageIdIndex,
}); });
} }
onImageLoaded(event) { onImageLoaded() {
this.setState({ this.setState({
numImagesLoaded: this.state.numImagesLoaded + 1 numImagesLoaded: this.state.numImagesLoaded + 1,
}); });
} }
startLoadingHandler() { startLoadingHandler() {
//console.log('startLoadingHandler'); // console.log('startLoadingHandler');
clearTimeout(this.loadHandlerTimeout); clearTimeout(this.loadHandlerTimeout);
this.loadHandlerTimeout = setTimeout(() => { this.loadHandlerTimeout = setTimeout(() => {
this.setState({ this.setState({
isLoading: true isLoading: true,
}); });
}, loadIndicatorDelay); }, loadIndicatorDelay);
} }
doneLoadingHandler() { doneLoadingHandler() {
clearTimeout(this.loadHandlerTimeout); clearTimeout(this.loadHandlerTimeout);
this.setState({ this.setState({
isLoading: false isLoading: false,
}); });
} }
onMeasurementAddedOrRemoved(event) { onMeasurementAddedOrRemoved() {
console.log('onMeasurementAddedOrRemoved'); console.log('onMeasurementAddedOrRemoved');
/*const { toolType, measurementData } = event.detail; /* const { toolType, measurementData } = event.detail;
// TODO: Pass in as prop? // TODO: Pass in as prop?
const toolsOfInterest = ['Bidirectional']; const toolsOfInterest = ['Bidirectional'];
@ -593,64 +666,87 @@ class CornerstoneViewport extends Component {
measurementData measurementData
); );
}*/ }*/
} }
setViewportActive() {
const {
viewportIndex,
} = this.props.viewportData;
const activeViewportIndex = window.store.getState().viewports.activeViewport;
if (viewportIndex !== activeViewportIndex) {
window.store.dispatch({
type: 'SET_VIEWPORT_ACTIVE',
viewportIndex,
});
}
}
onMouseClick(event) {
this.setViewportActive();
onMouseClick(event) {
if (event.detail.event.which === 3) { if (event.detail.event.which === 3) {
this.setState({ this.setState({
toolContextMenuData: { toolContextMenuData: {
eventData: event.detail, eventData: event.detail,
isTouchEvent: false isTouchEvent: false,
} },
}); });
} }
} }
onTouchPress(event) {
this.setViewportActive();
onTouchPress(event) {
this.setState({ this.setState({
toolContextMenuData: { toolContextMenuData: {
eventData: event.detail, eventData: event.detail,
isTouchEvent: true isTouchEvent: true,
} },
}); });
} }
onCloseToolContextMenu() { onTouchStart() {
this.setViewportActive();
}
onCloseToolContextMenu() {
this.setState({ this.setState({
toolContextMenuData: null toolContextMenuData: null,
}); });
} }
imageSliderOnInputCallback(value) {
this.setViewportActive();
imageSliderOnInputCallback(value) {
this.setState({ this.setState({
imageScrollbarValue: value imageScrollbarValue: value,
}); });
// Note that we throttle requests to prevent the // Note that we throttle requests to prevent the
// user's ultrafast scrolling from firing requests too quickly. // user's ultrafast scrolling from firing requests too quickly.
//clearTimeout(this.slideTimeout); // clearTimeout(this.slideTimeout);
//this.slideTimeout = setTimeout(() => { // this.slideTimeout = setTimeout(() => {
scrollToIndex(this.element, value); scrollToIndex(this.element, value);
//}, this.slideTimeoutTime); // }, this.slideTimeoutTime);
} }
hideExtraButtons = () => { hideExtraButtons = () => {
if (this.state.bidirectionalAddLabelShow === true) { if (this.state.bidirectionalAddLabelShow === true) {
this.setState({ this.setState({
bidirectionalAddLabelShow: false bidirectionalAddLabelShow: false,
}); });
} }
this.setState({ this.setState({
toolContextMenuData: null toolContextMenuData: null,
}); });
}; };
} }
CornerstoneViewport.propTypes = { CornerstoneViewport.propTypes = {
measurementsAddedOrRemoved: PropTypes.func, measurementsAddedOrRemoved: PropTypes.func,
measurementsChanged: PropTypes.func, measurementsChanged: PropTypes.func,
activeTool: PropTypes.string, activeTool: PropTypes.string,
viewportData: PropTypes.object.isRequired viewportData: PropTypes.object.isRequired,
}; };
export default CornerstoneViewport; export default CornerstoneViewport;

View File

@ -3,6 +3,10 @@
.CornerstoneViewport .CornerstoneViewport
width: 100% width: 100%
height: 100% height: 100%
.viewport-element
width: 100%
height: 100%
position: relative; position: relative;
background-color: black background-color: black

View File

@ -413,7 +413,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
// 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.
const activeViewportIndex = Session.get('activeViewport'); const activeViewportIndex = window.store.getState().viewports.activeViewport;
if (viewportIndex === activeViewportIndex) return; if (viewportIndex === activeViewportIndex) return;
$element.focus(); $element.focus();
@ -473,7 +473,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => {
// 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.
if (viewportIndex === Session.get('activeViewport')) { if (viewportIndex === window.store.getState().viewports.activeViewport) {
const viewportContainer = $element.parents('.viewportContainer'); const viewportContainer = $element.parents('.viewportContainer');
setActiveViewport(viewportContainer); setActiveViewport(viewportContainer);
@ -563,7 +563,7 @@ 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.
const activeViewport = Session.get('activeViewport'); const activeViewport = window.store.getState().viewports.activeViewport;
// Focus the viewport if it's the active one // Focus the viewport if it's the active one
if (templateData.viewportIndex === activeViewport) { if (templateData.viewportIndex === activeViewport) {

View File

@ -2,42 +2,129 @@ import { Component } from 'react';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './ViewportOverlay.styl'; import './ViewportOverlay.styl';
import { viewportOverlayUtils } from '../../../lib/viewportOverlayUtils';
import { helpers } from '../../../lib/helpers/index.js';
const { formatPN, formatDA, formatNumberPrecision, formatTM, isValidNumber} = helpers;
function getCompression(imageId) {
const instance = cornerstone.metaData.get('instance', imageId);
if (!instance) {
return '';
}
if (instance.lossyImageCompression === '01' &&
instance.lossyImageCompressionRatio !== '') {
const compressionMethod = instance.lossyImageCompressionMethod || 'Lossy: ';
const compressionRatio = formatNumberPrecision(instance.lossyImageCompressionRatio, 2);
return compressionMethod + compressionRatio + ' : 1';
}
return 'Lossless / Uncompressed';
}
function getImageDimensions(imageId) {
const instance = cornerstone.metaData.get('instance', imageId)
if (!instance) {
return '';
}
return `${instance.columns} x ${instance.rows}`;
}
class ViewportOverlay extends Component { class ViewportOverlay extends Component {
render() { render() {
const scale = Math.round(this.props.viewport.scale * 100) / 100; const zoom = this.props.viewport.scale * 100;
const imageId = this.props.imageId; const imageId = this.props.imageId;
/*const patientId = cornerstone.metaData.get('00100020', imageId); const patientMetadata = cornerstone.metaData.get('patient', imageId);
const studyDate = cornerstone.metaData.get('00080020', imageId); const studyMetadata = cornerstone.metaData.get('study', imageId);
const collection = cornerstone.metaData.get('00131010', imageId); const seriesMetadata = cornerstone.metaData.get('series', imageId);
const studyDescription = cornerstone.metaData.get('00081030', imageId);*/ const instanceMetadata = cornerstone.metaData.get('instance', imageId);
const windowWidth = Math.round(this.props.viewport.voi.windowWidth); const patientName = patientMetadata.name;
const windowCenter = Math.round(this.props.viewport.voi.windowCenter); const patientId = patientMetadata.id;;
const imagesLeft = const { studyDate, studyTime, studyDescription } = studyMetadata;
this.props.stack.imageIds.length - this.props.numImagesLoaded; const { seriesNumber, seriesDescription } = seriesMetadata;
const { instanceNumber, frameTime, thickness, spacingBetweenSlices, sliceLocation } = instanceMetadata;
const frameRate = formatNumberPrecision(1000 / frameTime, 1);
const compression = getCompression(imageId);
const windowWidth = this.props.viewport.voi.windowWidth || 0;
const windowCenter = this.props.viewport.voi.windowCenter || 0;
const wwwc = `W: ${windowWidth.toFixed(0)} L: ${windowCenter.toFixed(0)}`;
const imageIndex = this.props.stack.imageIds.indexOf(this.props.imageId) + 1;
const numImages = this.props.stack.imageIds.length;
const imageDimensions = getImageDimensions(imageId);
const normal = (<>
<div className="top-left overlay-element">
<div>{formatPN(patientName)}</div>
<div>{patientId}</div>
</div>
<div className="top-right overlay-element">
<div>{studyDescription}</div>
<div>{formatDA(studyDate)} {formatTM(studyTime)}</div>
</div>
<div className="bottom-right overlay-element">
<div>Zoom: {formatNumberPrecision(zoom,0)}%</div>
<div className="compressionIndicator">{compression}</div>
<div>{wwwc}</div>
</div>
<div className="bottom-left overlay-element">
<div>{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}</div>
<div>{numImages > 1 ? `Img: ${instanceNumber} ${imageIndex}/${numImages}` : ''}</div>
<div>{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''}
<div>{imageDimensions}</div>
<div>
<span>{isValidNumber(sliceLocation) ? `Loc: ${formatNumberPrecision(sliceLocation, 2)} mm` : ''}</span>
<span>{thickness ? `Thick: ${formatNumberPrecision(thickness, 2)} mm` : ''}</span>
<span>{spacingBetweenSlices ? `Spacing: ${formatNumberPrecision(spacingBetweenSlices, 2)} mm` : ''}</span>
</div>
<div>{seriesDescription}</div>
</div>
</div>
</>);
const rightOnly = (<>
<div className="top-right overlay-element">
<div>{formatPN(patientName)}</div>
<div>{patientId}</div>
<div>{studyDescription}</div>
<div>{formatDA(studyDate)} {formatTM(studyTime)}</div>
</div>
<div className="bottom-right overlay-element">
<div>{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}</div>
<div>{numImages > 1 ? `Img: ${instanceNumber} ${imageIndex}/${numImages}` : ''}</div>
<div>{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''}</div>
<div>{imageDimensions}</div>
<div>{seriesDescription}</div>
<div>Zoom: ${formatNumberPrecision(zoom, 0)}%</div>
<div className="compressionIndicator">{compression}</div>
<div>{wwwc}</div>
</div>
</>);
const leftOnly = (<>
<div className="top-left overlay-element">
<div>{formatPN(patientName)}</div>
<div>{patientId}</div>
<div>{studyDescription}</div>
<div>{formatDA(studyDate)} {formatTM(studyTime)}</div>
</div>
<div className="bottom-left overlay-element">
<div>{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}</div>
<div>{numImages > 1 ? `Img: ${instanceNumber} ${imageIndex}/${numImages}` : ''}</div>
<div>{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''}</div>
<div>{imageDimensions}</div>
<div>{seriesDescription}</div>
<div>Zoom: ${formatNumberPrecision(zoom, 0)}%</div>
<div className="compressionIndicator">{compression}</div>
<div>{wwwc}</div>
</div>
</>);
return ( return (
<div className="ViewportOverlay"> <div className='ViewportOverlay'>
<div className="top-left overlay-element"> {normal}
<span>{this.props.patientId}</span>
<span>{this.props.studyDate}</span>
<span>{this.props.studyDescription}</span>
<span>
{imagesLeft > 0 ? `${imagesLeft} images remaining...` : ''}
</span>
</div>
<div className="bottom-left overlay-element">Zoom: {scale}</div>
<div className="bottom-right overlay-element">
<span>
WW/WC: {windowWidth} / {windowCenter}
</span>
<span>
Image: {this.props.stack.currentImageIdIndex + 1} /{' '}
{this.props.stack.imageIds.length}
</span>
</div>
</div> </div>
); );
} }

View File

@ -2,10 +2,10 @@
$viewportTagPadding = 20px $viewportTagPadding = 20px
.imageViewerViewport.empty ~ .imageViewerViewportOverlay .imageViewerViewport.empty ~ .ViewportOverlay
display: none display: none
.imageViewerViewportOverlay .ViewportOverlay
theme('color', '$textSecondaryColor') theme('color', '$textSecondaryColor')
.overlay-element .overlay-element

View File

@ -88,7 +88,7 @@ Template.cineDialog.onCreated(() => {
// Run this computation every time the active viewport is changed // Run this computation every time the active viewport is changed
instance.autorun(() => { instance.autorun(() => {
Session.get('activeViewport'); window.store.getState().viewports.activeViewport;
Tracker.afterFlush(() => { Tracker.afterFlush(() => {
// Get the active viewportElement // Get the active viewportElement

View File

@ -100,7 +100,7 @@ Template.toolbarSectionButton.helpers({
}, },
disableButton() { disableButton() {
Session.get('activeViewport'); window.store.getState().viewports.activeViewport;
Session.get('LayoutManagerUpdated'); Session.get('LayoutManagerUpdated');
const instance = Template.instance(); const instance = Template.instance();
const isCommandDisabled = OHIF.commands.isDisabled(instance.data.id); const isCommandDisabled = OHIF.commands.isDisabled(instance.data.id);

View File

@ -248,3 +248,6 @@ Viewerbase.StackImagePositionOffsetSynchronizer = StackImagePositionOffsetSynchr
// StudyMetadataSource // StudyMetadataSource
import { StudyMetadataSource } from './lib/classes/StudyMetadataSource'; import { StudyMetadataSource } from './lib/classes/StudyMetadataSource';
Viewerbase.StudyMetadataSource = StudyMetadataSource; Viewerbase.StudyMetadataSource = StudyMetadataSource;
import redux from './lib/redux/';
Viewerbase.redux = redux;

View File

@ -416,7 +416,7 @@ export class LayoutManager {
const isSequenced = this.isDisplaySetsSequenced(sequenceMap); const isSequenced = this.isDisplaySetsSequenced(sequenceMap);
// Get Active Viewport Index if isMultiple is false // Get Active Viewport Index if isMultiple is false
const activeViewportIndex = !isMultiple ? Session.get('activeViewport') : null; const activeViewportIndex = !isMultiple ? window.store.getState().viewports.activeViewport : null;
// Check if is next and looping is blocked // Check if is next and looping is blocked
if (isNext && !allowLooping) { if (isNext && !allowLooping) {
@ -649,7 +649,7 @@ export class LayoutManager {
this.moveMultipleViewportDisplaySets(isNext); this.moveMultipleViewportDisplaySets(isNext);
} else { } else {
// Get the selected viewport index // Get the selected viewport index
const viewportIndex = Session.get('activeViewport'); const viewportIndex = window.store.getState().viewports.activeViewport;
// Move display sets on a single viewport // Move display sets on a single viewport
this.moveSingleViewportDisplaySets(viewportIndex, isNext); this.moveSingleViewportDisplaySets(viewportIndex, isNext);

View File

@ -86,7 +86,7 @@ export class StackImagePositionOffsetSynchronizer {
} }
getActiveViewportElement() { getActiveViewportElement() {
const viewportIndex = Session.get('activeViewport') || 0; const viewportIndex = window.store.getState().viewports.activeViewport || 0;
return $('.imageViewerViewport').get(viewportIndex); return $('.imageViewerViewport').get(viewportIndex);
} }

View File

@ -86,7 +86,7 @@ export class StudyPrefetcher {
// Make sure there is a stack to fetch // Make sure there is a stack to fetch
if (this.hasStack(element)) { if (this.hasStack(element)) {
// Check if this is a clip or not // Check if this is a clip or not
const activeViewportIndex = Session.get('activeViewport'); const activeViewportIndex = window.store.getState().viewports.activeViewport;
const displaySetInstanceUid = OHIF.viewer.data.loadedSeriesData[activeViewportIndex].displaySetInstanceUid; const displaySetInstanceUid = OHIF.viewer.data.loadedSeriesData[activeViewportIndex].displaySetInstanceUid;
const stack = StackManager.findStack(displaySetInstanceUid); const stack = StackManager.findStack(displaySetInstanceUid);

View File

@ -1,10 +1,14 @@
import { Blaze } from 'meteor/blaze'; import { Blaze } from 'meteor/blaze';
function formatNumberPrecision(number, precision) {
if (number !== null) {
return parseFloat(number).toFixed(precision);
}
}
/** /**
* A global Blaze UI helper to format a float value to a specified precision * A global Blaze UI helper to format a float value to a specified precision
*/ */
Blaze.registerHelper('formatNumberPrecision', function(context, precision) { Blaze.registerHelper('formatNumberPrecision', formatNumberPrecision);
if (context != null) {
return parseFloat(context).toFixed(precision); export default formatNumberPrecision;
}
});

View File

@ -6,6 +6,7 @@ import { isTouchDevice } from './isTouchDevice';
import { formatPN } from './formatPN'; import { formatPN } from './formatPN';
import { formatDA } from './formatDA'; import { formatDA } from './formatDA';
import { formatTM } from './formatTM'; import { formatTM } from './formatTM';
import isValidNumber from './isValidNumber';
/** /**
* Helpers with side effects only... * Helpers with side effects only...
@ -13,7 +14,7 @@ import { formatTM } from './formatTM';
import './formatJSDate'; import './formatJSDate';
import './jsDateFromNow'; import './jsDateFromNow';
import './formatNumberPrecision'; import formatNumberPrecision from './formatNumberPrecision';
import './inc'; import './inc';
import './isDisplaySetActive'; import './isDisplaySetActive';
import './getUsername'; import './getUsername';
@ -33,7 +34,9 @@ const helpers = {
isTouchDevice, isTouchDevice,
formatPN, formatPN,
formatDA, formatDA,
formatTM formatTM,
formatNumberPrecision,
isValidNumber,
}; };
export { helpers }; export { helpers };

View File

@ -0,0 +1,3 @@
export default function isValidNumber(value) {
return typeof value === 'number' && !isNaN(value);
}

View File

@ -92,7 +92,7 @@ Meteor.startup(function() {
// Create a function that returns true if the active viewport is empty // Create a function that returns true if the active viewport is empty
const isActiveViewportEmpty = () => { const isActiveViewportEmpty = () => {
const activeViewport = Session.get('activeViewport') || 0; const activeViewport = window.store.getState().viewports.activeViewport || 0;
return $('.imageViewerViewport').eq(activeViewport).hasClass('empty'); return $('.imageViewerViewport').eq(activeViewport).hasClass('empty');
}; };

View File

@ -5,7 +5,7 @@ import { setActiveViewport } from './setActiveViewport';
const loadPreviousActivePanel = () => { const loadPreviousActivePanel = () => {
OHIF.log.info('nextActivePanel'); OHIF.log.info('nextActivePanel');
let currentIndex = Session.get('activeViewport'); let currentIndex = window.store.getState().viewports.activeViewport;
currentIndex--; currentIndex--;
const $viewports = $('.viewportContainer'); const $viewports = $('.viewportContainer');
@ -24,7 +24,7 @@ const loadPreviousActivePanel = () => {
const loadNextActivePanel = () => { const loadNextActivePanel = () => {
OHIF.log.info('nextActivePanel'); OHIF.log.info('nextActivePanel');
let currentIndex = Session.get('activeViewport'); let currentIndex = window.store.getState().viewports.activeViewport;
currentIndex++; currentIndex++;
const $viewports = $('.viewportContainer'); const $viewports = $('.viewportContainer');

View File

@ -0,0 +1,9 @@
export const setToolActive = tool => ({
type: 'SET_TOOL_ACTIVE',
tool
});
export const setViewportActive = viewportIndex => ({
type: 'SET_VIEWPORT_ACTIVE',
viewportIndex
});

View File

@ -0,0 +1,8 @@
import './actions.js';
import combinedReducer from './reducers';
const redux = {
combinedReducer
};
export default redux;

View File

@ -0,0 +1,10 @@
import { combineReducers } from 'redux';
import tools from './tools.js';
import viewports from './viewports.js';
const combinedReducer = combineReducers({
tools,
viewports
});
export default combinedReducer;

View File

@ -0,0 +1,101 @@
const defaultButtons = [
{
command: 'Pan',
type: 'tool',
text: 'Pan',
svgUrl: '/icons.svg#icon-tools-pan',
active: false
},
{
command: 'Zoom',
type: 'tool',
text: 'Zoom',
svgUrl: '/icons.svg#icon-tools-zoom',
active: false
},
{
command: 'Bidirectional',
type: 'tool',
text: 'Bidirectional',
svgUrl: '/icons.svg#icon-tools-measure-target',
active: true
},
{
command: 'StackScroll',
type: 'tool',
text: 'Stack Scroll',
svgUrl: '/icons.svg#icon-tools-stack-scroll',
active: false
},
{
command: 'reset',
type: 'command',
text: 'Reset',
svgUrl: '/icons.svg#icon-tools-reset',
active: false
},
{
command: 'Wwwc',
type: 'tool',
text: 'Manual',
svgUrl: '/icons.svg#icon-tools-levels',
active: false
},
{
command: 'setWLPresetSoftTissue',
type: 'command',
text: 'Soft Tissue',
svgUrl: '/icons.svg#icon-wl-soft-tissue',
active: false
},
{
command: 'setWLPresetLung',
type: 'command',
text: 'Lung',
svgUrl: '/icons.svg#icon-wl-lung',
active: false
},
{
command: 'setWLPresetLiver',
type: 'command',
text: 'Liver',
svgUrl: '/icons.svg#icon-wl-liver',
active: false
},
{
command: 'setWLPresetBrain',
type: 'command',
text: 'Brain',
svgUrl: '/icons.svg#icon-wl-brain',
active: false
}
];
const tools = (state = { buttons: defaultButtons }, action) => {
switch (action.type) {
case 'SET_TOOL_ACTIVE':
const item = state.buttons.find(button => button.command === action.tool);
let buttons = [];
if (item.type === 'tool') {
buttons = state.buttons.map(button => {
if (button.command === action.tool) {
button.active = true;
} else if (button.type === 'tool') {
button.active = false;
}
return button;
});
}
return {
buttons
};
default:
return state;
}
};
export default tools;

View File

@ -0,0 +1,15 @@
const defaultState = {
activeViewport: 0
}
const viewports = (state = defaultState, action) => {
console.warn(action);
switch (action.type) {
case 'SET_VIEWPORT_ACTIVE':
return Object.assign({}, state, { activeViewport: action.viewportIndex });
default:
return state;
}
};
export default viewports;

View File

@ -30,9 +30,12 @@ export function setActiveViewport(element) {
$element.trigger('OHIFBeforeActivateViewport'); $element.trigger('OHIFBeforeActivateViewport');
} }
// When an OHIFActivateViewport event is fired, update the Meteor Session // When an OHIFActivateViewport event is fired, update the Redux state
// with the viewport index that it was fired from. // with the viewport index that it was fired from.
Session.set('activeViewport', viewportIndex); window.store.dispatch({
type: 'SET_VIEWPORT_ACTIVE',
viewportIndex
});
// Finally, enable stack prefetching and hide the reference lines from // Finally, enable stack prefetching and hide the reference lines from
// the newly activated viewport that has a canvas // the newly activated viewport that has a canvas

View File

@ -12,7 +12,7 @@ const setFocusToActiveViewport = () => {
const viewports = $('.imageViewerViewport'); const viewports = $('.imageViewerViewport');
// Get the current active viewport index from Session // Get the current active viewport index from Session
const activeViewportIndex = Session.get('activeViewport'); const activeViewportIndex = window.store.getState().viewports.activeViewport;
// Find the div from the list of viewports // Find the div from the list of viewports
const activeViewport = viewports.eq(activeViewportIndex); const activeViewport = viewports.eq(activeViewportIndex);

View File

@ -30,7 +30,7 @@ const getEnabledElement = element => {
* @return {DOMElement} DOMElement of the current active viewport * @return {DOMElement} DOMElement of the current active viewport
*/ */
const getActiveViewportElement = () => { const getActiveViewportElement = () => {
const viewportIndex = Session.get('activeViewport') || 0; const viewportIndex = window.store.getState().viewports.activeViewport || 0;
return $('.imageViewerViewport').get(viewportIndex); return $('.imageViewerViewport').get(viewportIndex);
}; };
@ -274,7 +274,7 @@ const isPlaying = () => {
// Check if a study has multiple frames // Check if a study has multiple frames
const hasMultipleFrames = () => { const hasMultipleFrames = () => {
// Its called everytime active viewport and/or layout change // Its called everytime active viewport and/or layout change
Session.get('activeViewport'); window.store.getState().viewports.activeViewport;
Session.get('LayoutManagerUpdated'); Session.get('LayoutManagerUpdated');
const activeViewport = getActiveViewportElement(); const activeViewport = getActiveViewportElement();
@ -326,7 +326,7 @@ const isStackScrollLinkingDisabled = () => {
let linkableViewportsCount = 0; let linkableViewportsCount = 0;
// Its called everytime active viewport and/or layout change // Its called everytime active viewport and/or layout change
Session.get('activeViewport'); window.store.getState().viewports.activeViewport;
Session.get('LayoutManagerUpdated'); Session.get('LayoutManagerUpdated');
const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer; const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;