From 71b49e6766acc76dcd572db7f48e6b33eaec374c Mon Sep 17 00:00:00 2001 From: Erik Ziegler Date: Mon, 3 Dec 2018 18:11:39 +0100 Subject: [PATCH] wip on react transition 8 --- .../components/ohifViewer/ohifViewer.js | 7 + OHIFViewer/client/components/viewer/viewer.js | 9 +- .../protocolEditor/protocolEditor.js | 2 +- .../ruleEntryDialog/ruleEntryDialog.js | 2 +- .../client/components/ruleTable/ruleTable.js | 8 +- .../settingEntryDialog/settingEntryDialog.js | 4 +- .../components/settingsTable/settingsTable.js | 4 +- .../components/stageDetails/stageDetails.js | 2 +- .../client/lib/pixelSpacingAutorunCheck.js | 2 +- .../client/lib/getActiveTimepoint.js | 2 +- .../thumbnailEntry/thumbnailEntry.js | 4 +- .../CornerstoneViewport.js | 880 ++++++++++-------- .../CornerstoneViewport.styl | 4 + .../imageViewerViewport.js | 6 +- .../viewer/ViewportOverlay/ViewportOverlay.js | 143 ++- .../ViewportOverlay/ViewportOverlay.styl | 4 +- .../viewer/cineDialog/cineDialog.js | 2 +- .../toolbarSectionButton.js | 2 +- Packages/ohif-viewerbase/client/index.js | 3 + .../client/lib/classes/LayoutManager.js | 4 +- .../StackImagePositionOffsetSynchronizer.js | 2 +- .../client/lib/classes/StudyPrefetcher.js | 2 +- .../lib/helpers/formatNumberPrecision.js | 14 +- .../client/lib/helpers/index.js | 7 +- .../client/lib/helpers/isValidNumber.js | 3 + .../ohif-viewerbase/client/lib/hotkeyUtils.js | 2 +- .../client/lib/panelNavigation.js | 4 +- .../client/lib/redux/actions.js | 9 + .../ohif-viewerbase/client/lib/redux/index.js | 8 + .../client/lib/redux/reducers/index.js | 10 + .../client/lib/redux/reducers/tools.js | 101 ++ .../client/lib/redux/reducers/viewports.js | 15 + .../client/lib/setActiveViewport.js | 7 +- .../client/lib/setFocusToActiveViewport.js | 4 +- .../client/lib/viewportUtils.js | 6 +- 35 files changed, 818 insertions(+), 470 deletions(-) create mode 100644 Packages/ohif-viewerbase/client/lib/helpers/isValidNumber.js create mode 100644 Packages/ohif-viewerbase/client/lib/redux/actions.js create mode 100644 Packages/ohif-viewerbase/client/lib/redux/index.js create mode 100644 Packages/ohif-viewerbase/client/lib/redux/reducers/index.js create mode 100644 Packages/ohif-viewerbase/client/lib/redux/reducers/tools.js create mode 100644 Packages/ohif-viewerbase/client/lib/redux/reducers/viewports.js diff --git a/OHIFViewer/client/components/ohifViewer/ohifViewer.js b/OHIFViewer/client/components/ohifViewer/ohifViewer.js index 3e2be61ab..a43935211 100644 --- a/OHIFViewer/client/components/ohifViewer/ohifViewer.js +++ b/OHIFViewer/client/components/ohifViewer/ohifViewer.js @@ -5,6 +5,13 @@ import { Router } from 'meteor/clinical:router'; import { ReactiveVar } from 'meteor/reactive-var'; 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(() => { const instance = Template.instance(); instance.headerClasses = new ReactiveVar(''); diff --git a/OHIFViewer/client/components/viewer/viewer.js b/OHIFViewer/client/components/viewer/viewer.js index db0acf46e..9fcda639f 100644 --- a/OHIFViewer/client/components/viewer/viewer.js +++ b/OHIFViewer/client/components/viewer/viewer.js @@ -38,13 +38,10 @@ const initHangingProtocol = () => { HP.setEngine(ProtocolEngine); Session.set('ViewerReady', true); - - Session.set('activeViewport', 0); }); }; Meteor.startup(() => { - Session.setDefault('activeViewport', false); Session.setDefault('leftSidebar', false); Session.setDefault('rightSidebar', false); @@ -80,7 +77,7 @@ Meteor.startup(() => { cornerstone.metaData.addProvider(metadataProvider.provider.bind(metadataProvider)); // Instanciate viewer plugins - OHIF.viewer.measurementTable = new MeasurementTable(); + OHIF.viewer.measurementTable = new MeasurementTable(); }); Template.viewer.onCreated(() => { @@ -112,8 +109,6 @@ Template.viewer.onCreated(() => { // Store the viewer data in session for further user Session.setPersistent('ViewerData', OHIF.viewer.data); - Session.set('activeViewport', OHIF.viewer.data.activeViewport || 0); - // @TypeSafeStudies // Clears OHIF.viewer.Studies collection OHIF.viewer.Studies.removeAll(); @@ -163,7 +158,7 @@ Template.viewer.onRendered(function() { } }); - // Call Viewer plugins onRendered functions + // Call Viewer plugins onRendered functions if(typeof OHIF.viewer.measurementTable.onRendered === 'function') { OHIF.viewer.measurementTable.onRendered(instance); } diff --git a/Packages/ohif-hanging-protocols/client/components/protocolEditor/protocolEditor.js b/Packages/ohif-hanging-protocols/client/components/protocolEditor/protocolEditor.js index 5e224203e..295c5f223 100644 --- a/Packages/ohif-hanging-protocols/client/components/protocolEditor/protocolEditor.js +++ b/Packages/ohif-hanging-protocols/client/components/protocolEditor/protocolEditor.js @@ -151,7 +151,7 @@ Template.protocolEditor.helpers({ return ProtocolEngine.getCurrentStageModel(); }, activeViewportUndefined() { - const viewportIndex = Session.get('activeViewport'); + const viewportIndex = window.store.getState().viewports.activeViewport; return (viewportIndex === undefined); } }); diff --git a/Packages/ohif-hanging-protocols/client/components/ruleEntryDialog/ruleEntryDialog.js b/Packages/ohif-hanging-protocols/client/components/ruleEntryDialog/ruleEntryDialog.js index 4a29bcc25..623575461 100644 --- a/Packages/ohif-hanging-protocols/client/components/ruleEntryDialog/ruleEntryDialog.js +++ b/Packages/ohif-hanging-protocols/client/components/ruleEntryDialog/ruleEntryDialog.js @@ -318,7 +318,7 @@ Template.ruleEntryDialog.events({ rule.constraint = constraint; // 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); // Close the dialog diff --git a/Packages/ohif-hanging-protocols/client/components/ruleTable/ruleTable.js b/Packages/ohif-hanging-protocols/client/components/ruleTable/ruleTable.js index 2a76b00b7..e379d91aa 100644 --- a/Packages/ohif-hanging-protocols/client/components/ruleTable/ruleTable.js +++ b/Packages/ohif-hanging-protocols/client/components/ruleTable/ruleTable.js @@ -6,7 +6,7 @@ Template.ruleTable.helpers({ */ rulePassed: function() { // 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]; // 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 - var viewportIndex = Session.get('activeViewport'); + var viewportIndex = window.store.getState().viewports.activeViewport; ProtocolEngine.updateViewports(viewportIndex); }, /** @@ -108,7 +108,7 @@ Template.ruleTable.events({ rule.weight = $(event.currentTarget).val(); // 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); }, /** @@ -124,7 +124,7 @@ Template.ruleTable.events({ rule.required = $(event.currentTarget).prop('checked'); // 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); } }); diff --git a/Packages/ohif-hanging-protocols/client/components/settingEntryDialog/settingEntryDialog.js b/Packages/ohif-hanging-protocols/client/components/settingEntryDialog/settingEntryDialog.js index fb91b4e8b..4c0fd1250 100644 --- a/Packages/ohif-hanging-protocols/client/components/settingEntryDialog/settingEntryDialog.js +++ b/Packages/ohif-hanging-protocols/client/components/settingEntryDialog/settingEntryDialog.js @@ -165,7 +165,7 @@ Template.settingEntryDialog.events({ viewport.viewportSettings[viewportSetting.id] = viewportSetting.value; // 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); // Close the dialog @@ -176,7 +176,7 @@ Template.settingEntryDialog.events({ */ 'click #cancel': function() { var dialog = Template.settingEntryDialog.dialog; - closeHandler(dialog); + closeHandler(dialog); }, /** * Allow Esc keydown events to close the dialog diff --git a/Packages/ohif-hanging-protocols/client/components/settingsTable/settingsTable.js b/Packages/ohif-hanging-protocols/client/components/settingsTable/settingsTable.js index 160a94ad6..126b5415e 100644 --- a/Packages/ohif-hanging-protocols/client/components/settingsTable/settingsTable.js +++ b/Packages/ohif-hanging-protocols/client/components/settingsTable/settingsTable.js @@ -37,7 +37,7 @@ Template.settingsTable.events({ delete viewport.viewportSettings[setting.key]; // 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); } }); @@ -54,4 +54,4 @@ Template.settingsTable.helpers({ } } -}); \ No newline at end of file +}); diff --git a/Packages/ohif-hanging-protocols/client/components/stageDetails/stageDetails.js b/Packages/ohif-hanging-protocols/client/components/stageDetails/stageDetails.js index f81742f91..b90d23ccc 100644 --- a/Packages/ohif-hanging-protocols/client/components/stageDetails/stageDetails.js +++ b/Packages/ohif-hanging-protocols/client/components/stageDetails/stageDetails.js @@ -11,7 +11,7 @@ getActiveViewportModel = function() { var stage = ProtocolEngine.getCurrentStageModel(); // 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 // number of Viewports defined for this Stage, stop here diff --git a/Packages/ohif-lesiontracker/client/lib/pixelSpacingAutorunCheck.js b/Packages/ohif-lesiontracker/client/lib/pixelSpacingAutorunCheck.js index 552bc90fd..2a3c63c12 100644 --- a/Packages/ohif-lesiontracker/client/lib/pixelSpacingAutorunCheck.js +++ b/Packages/ohif-lesiontracker/client/lib/pixelSpacingAutorunCheck.js @@ -8,7 +8,7 @@ OHIF.lesiontracker.pixelSpacingAutorunCheck = () => { const $oncologyTools = $('button#lesion, button#nonTarget'); // TODO: Set activeViewport for empty viewport element - const activeViewportIndex = Session.get('activeViewport'); + const activeViewportIndex = window.store.getState().viewports.activeViewport; if (activeViewportIndex === undefined) { return; } diff --git a/Packages/ohif-measurements/client/lib/getActiveTimepoint.js b/Packages/ohif-measurements/client/lib/getActiveTimepoint.js index 8d37f4363..ca7cf5fba 100644 --- a/Packages/ohif-measurements/client/lib/getActiveTimepoint.js +++ b/Packages/ohif-measurements/client/lib/getActiveTimepoint.js @@ -7,7 +7,7 @@ import { OHIF } from 'meteor/ohif:core'; * @returns {Object} - Timepoint data for the active viewport */ OHIF.measurements.getActiveTimepoint = () => { - const activeViewportIndex = Session.get('activeViewport'); + const activeViewportIndex = window.store.getState().viewports.activeViewport; const { studyInstanceUid } = OHIF.viewerbase.layoutManager.viewportData[activeViewportIndex]; return OHIF.viewer.timepointApi.study(studyInstanceUid)[0]; }; diff --git a/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js b/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js index c1b65193c..ffdef25b0 100644 --- a/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js +++ b/Packages/ohif-viewerbase/client/components/studyBrowser/thumbnailEntry/thumbnailEntry.js @@ -48,7 +48,7 @@ Template.thumbnailEntry.events({ // Get the viewport index let { viewportIndex } = instance.data; if (_.isUndefined(viewportIndex)) { - viewportIndex = Session.get('activeViewport') || 0; + viewportIndex = window.store.getState().viewports.activeViewport || 0; } // 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... const viewportCount = OHIF.viewerbase.layoutManager.getNumberOfViewports(); - let viewportIndex = Session.get('activeViewport') || 0; + let viewportIndex = window.store.getState().viewports.activeViewport || 0; if (viewportIndex >= viewportCount) { viewportIndex = viewportCount > 0 ? viewportCount - 1 : 0; } diff --git a/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.js b/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.js index 084ed8d7f..dd592793f 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.js +++ b/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.js @@ -1,50 +1,64 @@ -import { Component } from 'react'; +import { + Component, +} from 'react'; import React from 'react'; import PropTypes from 'prop-types'; -import cloneDeep from 'lodash.clonedeep'; +import { OHIF } from 'meteor/ohif:core'; import debounce from 'lodash.debounce'; +// import * as cornerstone from 'cornerstone-core'; +// import * as cornerstoneTools from 'cornerstone-tools'; -//import * as cornerstone from 'cornerstone-core'; -//import * as cornerstoneTools from 'cornerstone-tools'; - -import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; -//import './lib/initCornerstone.js'; +import { + cornerstone, + cornerstoneTools, +} from 'meteor/ohif:cornerstone'; +import './CornerstoneViewport.styl'; +// import './lib/initCornerstone.js'; import ImageScrollbar from '../ImageScrollbar/ImageScrollbar.js'; import ViewportOverlay from '../ViewportOverlay/ViewportOverlay.js'; import ToolContextMenu from '../ToolContextMenu/ToolContextMenu.js'; import LoadingIndicator from '../LoadingIndicator/LoadingIndicator.js'; import ViewportOrientationMarkers from '../ViewportOrientationMarkers/ViewportOrientationMarkers.js'; -import { StackManager } from "../../../lib/StackManager"; +import { + StackManager, +} from '../../../lib/StackManager'; import './CornerstoneViewport.styl'; const EVENT_RESIZE = 'resize'; const loadIndicatorDelay = 45; -const { loadHandlerManager } = cornerstoneTools; +const { + loadHandlerManager, +} = cornerstoneTools; function setToolsPassive(tools) { - tools.forEach(tool => { - cornerstoneTools.setToolPassive(tool); - }); + tools.forEach((tool) => { + cornerstoneTools.setToolPassive(tool); + }); } function initializeTools(tools) { - Array.from(tools).forEach(tool => { - const apiTool = cornerstoneTools[`${tool.name}Tool`]; - if (apiTool) { - cornerstoneTools.addTool(apiTool, tool.configuration); - } else { - throw new Error(`Tool not found: ${tool.name}Tool`); - } - }); + Array.from(tools).forEach((tool) => { + const apiTool = cornerstoneTools[`${tool.name}Tool`]; + if (apiTool) { + cornerstoneTools.addTool(apiTool, tool.configuration); + } else { + throw new Error(`Tool not found: ${tool.name}Tool`); + } + }); } function getCornerstoneStack(viewportData) { - const { displaySetInstanceUid, studyInstanceUid } = viewportData; + const { + displaySetInstanceUid, + studyInstanceUid, + } = viewportData; // 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; }); @@ -52,361 +66,412 @@ function getCornerstoneStack(viewportData) { const stack = StackManager.findOrCreateStack(study, displaySet); stack.currentImageIdIndex = 0; - return stack + return stack; } const scrollToIndex = cornerstoneTools.import('util/scrollToIndex'); class CornerstoneViewport extends Component { - constructor(props) { - super(props); + constructor(props) { + super(props); - const stack = getCornerstoneStack(this.props.viewportData) + const stack = getCornerstoneStack(this.props.viewportData); - // TODO: Allow viewport as a prop - this.state = { - stack, - displaySetInstanceUid: this.props.viewportData.displaySetInstanceUid, - imageId: stack.imageIds[0], - viewportHeight: '100%', - isLoading: false,//true, - imageScrollbarValue: 0, - numImagesLoaded: 0 - }; + // TODO: Allow viewport as a prop + this.state = { + stack, + displaySetInstanceUid: this.props.viewportData.displaySetInstanceUid, + imageId: stack.imageIds[0], + viewportHeight: '100%', + isLoading: false, // true, + imageScrollbarValue: 0, + numImagesLoaded: 0, + }; - this.displayScrollbar = stack.imageIds.length > 1; - this.state.viewport = cornerstone.getDefaultViewport(null, undefined); + this.displayScrollbar = stack.imageIds.length > 1; + this.state.viewport = cornerstone.getDefaultViewport(null, undefined); - this.onImageRendered = this.onImageRendered.bind(this); - this.onNewImage = this.onNewImage.bind(this); - this.onWindowResize = this.onWindowResize.bind(this); - this.onImageLoaded = this.onImageLoaded.bind(this); - this.onStackScroll = this.onStackScroll.bind(this); - this.startLoadingHandler = this.startLoadingHandler.bind(this); - this.doneLoadingHandler = this.doneLoadingHandler.bind(this); - this.onMouseClick = this.onMouseClick.bind(this); - this.onTouchPress = this.onTouchPress.bind(this); - this.onMeasurementAddedOrRemoved = this.onMeasurementAddedOrRemoved.bind( - this - ); - this.onCloseToolContextMenu = this.onCloseToolContextMenu.bind(this); - this.imageSliderOnInputCallback = this.imageSliderOnInputCallback.bind( - this - ); + this.onImageRendered = this.onImageRendered.bind(this); + this.onNewImage = this.onNewImage.bind(this); + this.onWindowResize = this.onWindowResize.bind(this); + this.onImageLoaded = this.onImageLoaded.bind(this); + this.onStackScroll = this.onStackScroll.bind(this); + this.startLoadingHandler = this.startLoadingHandler.bind(this); + this.doneLoadingHandler = this.doneLoadingHandler.bind(this); + this.onMouseClick = this.onMouseClick.bind(this); + this.onTouchPress = this.onTouchPress.bind(this); + this.onTouchStart = this.onTouchStart.bind(this); + this.onMeasurementAddedOrRemoved = this.onMeasurementAddedOrRemoved.bind( + this, + ); - this.loadHandlerTimeout = 25; - //loadHandlerManager.setStartLoadHandler(this.startLoadingHandler); - //loadHandlerManager.setEndLoadHandler(this.doneLoadingHandler); + this.setViewportActive = this.setViewportActive.bind(this); - this.debouncedResize = debounce(() => { - try { - cornerstone.getEnabledElement(this.element); - } catch (error) { - console.error(error); - return; - } + this.onCloseToolContextMenu = this.onCloseToolContextMenu.bind(this); + this.imageSliderOnInputCallback = this.imageSliderOnInputCallback.bind( + this, + ); - cornerstone.resize(this.element, true); + this.loadHandlerTimeout = 25; + // loadHandlerManager.setStartLoadHandler(this.startLoadingHandler); + // loadHandlerManager.setEndLoadHandler(this.doneLoadingHandler); - this.setState({ - viewportHeight: `${this.element.clientHeight - 20}px` - }); - }, 300); + this.debouncedResize = debounce(() => { + try { + cornerstone.getEnabledElement(this.element); + } catch (error) { + console.error(error); + return; + } - this.slideTimeoutTime = 25; - this.slideTimeout = null; - } + cornerstone.resize(this.element, true); - render() { - const isLoading = - this.state.isLoading || - this.state.numImagesLoaded / this.state.stack.imageIds.length < 0.1; + this.setState({ + viewportHeight: `${this.element.clientHeight - 20}px`, + }); + }, 300); - return ( - <> - +
{ + this.element = input; + } + } + > + { + isLoading ? : '' + } +
+ Please drag a stack here to view images.
{ + this.displayScrollbar && ( + ) + } { + /* this.state.bidirectionalAddLabelShow && ( + -
{ - this.element = input; - }} - > - {isLoading ? : ''} - - - -
-
- Please drag a stack here to view images. -
- {this.displayScrollbar && ( - - )} - {/*this.state.bidirectionalAddLabelShow && ( - - )*/} - - ); - } + )*/ + } + ); + } - bidirectionalToolLabellingCallback = ( +bidirectionalToolLabellingCallback = ( measurementData, eventData, doneCallback, - options = {} - ) => { + options = {}, +) => { const labellingDoneCallback = () => { - this.hideExtraButtons(); - return doneCallback(); + this.hideExtraButtons(); + return doneCallback(); }; this.bidirectional = { - measurementData, - eventData, - labellingDoneCallback, - skipButton: options.skipButton, - editDescription: options.editDescription + measurementData, + eventData, + labellingDoneCallback, + skipButton: options.skipButton, + editDescription: options.editDescription, }; this.setState({ - bidirectionalAddLabelShow: true + bidirectionalAddLabelShow: true, }); - }; +}; - onContextMenu(event) { +onContextMenu(event) { // Preventing the default behaviour for right-click is essential to // allow right-click tools to work. event.preventDefault(); - } +} - onWindowResize() { +onWindowResize() { this.debouncedResize(); - } +} - onImageRendered() { +onImageRendered() { const viewport = cornerstone.getViewport(this.element); - /*this.setState({ - viewport - });*/ - } + this.setState({ + viewport, + }); +} - onNewImage() { +onNewImage() { const image = cornerstone.getImage(this.element); this.setState({ - imageId: image.imageId + imageId: image.imageId, }); - } +} - componentDidMount() { +componentDidMount() { const element = this.element; // Enable the DOM Element for use with Cornerstone cornerstone.enable(element); cornerstone.events.addEventListener( - cornerstone.EVENTS.IMAGE_LOADED, - this.onImageLoaded + cornerstone.EVENTS.IMAGE_LOADED, + this.onImageLoaded, ); // Load the first image in the stack - cornerstone.loadAndCacheImage(this.state.imageId).then(image => { - try { - cornerstone.getEnabledElement(element); - } catch (error) { - // Handle cases where the user ends the session before the image is displayed. - console.error(error); - return; - } + cornerstone.loadAndCacheImage(this.state.imageId).then((image) => { + try { + cornerstone.getEnabledElement(element); + } catch (error) { + // Handle cases where the user ends the session before the image is displayed. + console.error(error); + return; + } - // Set Soft Tissue preset for all images by default - const viewport = cornerstone.getDefaultViewportForImage(element, image); - viewport.voi = { - windowWidth: 400, - windowCenter: 40 - }; + // Set Soft Tissue preset for all images by default + const viewport = cornerstone.getDefaultViewportForImage(element, image); + viewport.voi = { + windowWidth: 400, + windowCenter: 40, + }; - // Display the first image - cornerstone.displayImage(element, image, viewport); + // Display the first image + cornerstone.displayImage(element, image, viewport); - // Clear any previous tool state - cornerstoneTools.clearToolState(this.element, 'stack'); + // Clear any previous tool state + cornerstoneTools.clearToolState(this.element, 'stack'); - // Disable stack prefetch in case there are still queued requests - cornerstoneTools.stackPrefetch.disable(this.element); + // Disable stack prefetch in case there are still queued requests + cornerstoneTools.stackPrefetch.disable(this.element); - // Add the stack tool state to the enabled element - const stack = this.state.stack; - cornerstoneTools.addStackStateManager(element, ['stack']); - cornerstoneTools.addToolState(element, 'stack', stack); - cornerstoneTools.stackPrefetch.enable(this.element); + // Add the stack tool state to the enabled element + const stack = this.state.stack; + cornerstoneTools.addStackStateManager(element, ['stack']); + cornerstoneTools.addToolState(element, 'stack', stack); + cornerstoneTools.stackPrefetch.enable(this.element); - const tools = [ - { - name: 'Bidirectional', - configuration: { - getMeasurementLocationCallback: this - .bidirectionalToolLabellingCallback, - shadow: true, - drawHandlesOnHover: true - } + const tools = [{ + name: 'Bidirectional', + configuration: { + getMeasurementLocationCallback: this + .bidirectionalToolLabellingCallback, + shadow: true, + drawHandlesOnHover: true, + }, }, - { name: 'Wwwc' }, { - name: 'Zoom', - configuration: { - minScale: 0.3, - maxScale: 25, - preventZoomOutsideImage: true - } + name: 'Wwwc', }, - { name: 'Pan' }, - { name: 'StackScroll' }, - { name: 'PanMultiTouch' }, - { name: 'ZoomTouchPinch' }, - { name: 'StackScrollMouseWheel' }, - { name: 'StackScrollMultiTouch' } - ]; + { + name: 'Zoom', + configuration: { + minScale: 0.3, + maxScale: 25, + preventZoomOutsideImage: true, + }, + }, + { + name: 'Pan', + }, + { + name: 'StackScroll', + }, + { + name: 'PanMultiTouch', + }, + { + name: 'ZoomTouchPinch', + }, + { + name: 'StackScrollMouseWheel', + }, + { + name: 'StackScrollMultiTouch', + }, + ]; - initializeTools(tools); + initializeTools(tools); - this.setActiveTool(this.props.activeTool); + this.setActiveTool(this.props.activeTool); - /* For touch devices, by default we activate: + /* For touch devices, by default we activate: - Pinch to zoom - Two-finger Pan - Three (or more) finger Stack Scroll */ - cornerstoneTools.setToolActive('PanMultiTouch', { - mouseButtonMask: 0, - isTouchActive: true - }); - cornerstoneTools.setToolActive('ZoomTouchPinch', { - mouseButtonMask: 0, - isTouchActive: true - }); + cornerstoneTools.setToolActive('PanMultiTouch', { + mouseButtonMask: 0, + isTouchActive: true, + }); + cornerstoneTools.setToolActive('ZoomTouchPinch', { + mouseButtonMask: 0, + isTouchActive: true, + }); - cornerstoneTools.setToolActive('StackScrollMultiTouch', { - mouseButtonMask: 0, - isTouchActive: true - }); + cornerstoneTools.setToolActive('StackScrollMultiTouch', { + mouseButtonMask: 0, + isTouchActive: true, + }); - cornerstoneTools.stackPrefetch.setConfiguration({ - maxImagesToPrefetch: Infinity, - preserveExistingPool: false, - maxSimultaneousRequests: 6 - }); + cornerstoneTools.stackPrefetch.setConfiguration({ + maxImagesToPrefetch: Infinity, + preserveExistingPool: false, + maxSimultaneousRequests: 6, + }); - /* For mouse devices, by default we turn on: + /* For mouse devices, by default we turn on: - Stack scrolling by mouse wheel - Stack scrolling by keyboard up / down arrow keys - Pan with middle click - Zoom with right click */ - cornerstoneTools.setToolActive('StackScrollMouseWheel', { - mouseButtonMask: 0, - isTouchActive: true - }); + cornerstoneTools.setToolActive('StackScrollMouseWheel', { + mouseButtonMask: 0, + isTouchActive: true, + }); - element.addEventListener( - cornerstone.EVENTS.IMAGE_RENDERED, - this.onImageRendered - ); + element.addEventListener( + cornerstone.EVENTS.IMAGE_RENDERED, + this.onImageRendered, + ); - element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage); + element.addEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage); - element.addEventListener( - cornerstoneTools.EVENTS.STACK_SCROLL, - this.onStackScroll - ); + element.addEventListener( + cornerstoneTools.EVENTS.STACK_SCROLL, + this.onStackScroll, + ); - element.addEventListener( - cornerstoneTools.EVENTS.MEASUREMENT_ADDED, - this.onMeasurementAddedOrRemoved - ); + element.addEventListener( + cornerstoneTools.EVENTS.MEASUREMENT_ADDED, + this.onMeasurementAddedOrRemoved, + ); - element.addEventListener( - cornerstoneTools.EVENTS.MEASUREMENT_REMOVED, - this.onMeasurementAddedOrRemoved - ); + element.addEventListener( + cornerstoneTools.EVENTS.MEASUREMENT_REMOVED, + this.onMeasurementAddedOrRemoved, + ); - element.addEventListener( - cornerstoneTools.EVENTS.MOUSE_CLICK, - this.onMouseClick - ); + element.addEventListener( + cornerstoneTools.EVENTS.MOUSE_CLICK, + this.onMouseClick, + ); - element.addEventListener( - cornerstoneTools.EVENTS.TOUCH_PRESS, - this.onTouchPress - ); + element.addEventListener( + cornerstoneTools.EVENTS.TOUCH_PRESS, + this.onTouchPress, + ); - window.addEventListener(EVENT_RESIZE, this.onWindowResize); + element.addEventListener( + cornerstoneTools.EVENTS.TOUCH_START, + this.onTouchStart, + ); - this.setState({ - viewportHeight: `${this.element.clientHeight - 20}px` - }); + window.addEventListener(EVENT_RESIZE, this.onWindowResize); + + this.setState({ + viewportHeight: `${this.element.clientHeight - 20}px`, + }); }); - } +} - componentWillUnmount() { +componentWillUnmount() { const element = this.element; element.removeEventListener( - cornerstone.EVENTS.IMAGE_RENDERED, - this.onImageRendered + cornerstone.EVENTS.IMAGE_RENDERED, + this.onImageRendered, ); element.removeEventListener(cornerstone.EVENTS.NEW_IMAGE, this.onNewImage); element.removeEventListener( - cornerstoneTools.EVENTS.STACK_SCROLL, - this.onStackScroll + cornerstoneTools.EVENTS.STACK_SCROLL, + this.onStackScroll, ); element.removeEventListener( - cornerstoneTools.EVENTS.MEASUREMENT_ADDED, - this.onMeasurementAddedOrRemoved + cornerstoneTools.EVENTS.MEASUREMENT_ADDED, + this.onMeasurementAddedOrRemoved, ); element.removeEventListener( - cornerstoneTools.EVENTS.MEASUREMENT_REMOVED, - this.onMeasurementAddedOrRemoved + cornerstoneTools.EVENTS.MEASUREMENT_REMOVED, + this.onMeasurementAddedOrRemoved, ); element.removeEventListener( - cornerstoneTools.EVENTS.MOUSE_CLICK, - this.onMouseClick + cornerstoneTools.EVENTS.MOUSE_CLICK, + this.onMouseClick, ); element.removeEventListener( - cornerstoneTools.EVENTS.TOUCH_PRESS, - this.onTouchPress + cornerstoneTools.EVENTS.TOUCH_PRESS, + this.onTouchPress, + ); + + element.removeEventListener( + cornerstoneTools.EVENTS.TOUCH_START, + this.onTouchStart, ); window.removeEventListener(EVENT_RESIZE, this.onWindowResize); @@ -414,92 +479,98 @@ class CornerstoneViewport extends Component { cornerstone.disable(element); cornerstone.events.removeEventListener( - cornerstone.EVENTS.IMAGE_LOADED, - this.onImageLoaded + cornerstone.EVENTS.IMAGE_LOADED, + this.onImageLoaded, ); - } +} - componentDidUpdate(prevProps) { +componentDidUpdate(prevProps) { // TODO: Add a real object shallow comparison here? 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 - const study = OHIF.viewer.Studies.findBy({ studyInstanceUid }); + // Create shortcut to displaySet + const study = OHIF.viewer.Studies.findBy({ + studyInstanceUid, + }); - const displaySet = study.displaySets.find(set => { - return set.displaySetInstanceUid === displaySetInstanceUid; - }); + const displaySet = study.displaySets.find((set) => { + return set.displaySetInstanceUid === displaySetInstanceUid; + }); - // Get stack from Stack Manager - const stack = StackManager.findOrCreateStack(study, displaySet); - const stackData = cornerstoneTools.getToolState(this.element, 'stack'); - let currentStack = stackData && stackData.data[0]; + // Get stack from Stack Manager + const stack = StackManager.findOrCreateStack(study, displaySet); + const stackData = cornerstoneTools.getToolState(this.element, 'stack'); + let currentStack = stackData && stackData.data[0]; - if (!currentStack) { - currentStack = { - currentImageIdIndex, - imageIds: stack.imageIds - }; + if (!currentStack) { + currentStack = { + currentImageIdIndex, + imageIds: stack.imageIds, + }; - cornerstoneTools.addStackStateManager(this.element, ['stack']); - cornerstoneTools.addToolState(this.element, 'stack', currentStack); - } else { - // TODO: we should make something like setToolState by an ID - currentStack.currentImageIdIndex = currentImageIdIndex; - currentStack.imageIds = stack.imageIds; - } - - const imageId = currentStack.imageIds[currentImageIdIndex]; - - this.setState({ - displaySetInstanceUid, - studyInstanceUid, - stack, - imageId - }); - - cornerstoneTools.stackPrefetch.disable(this.element); - cornerstone.loadAndCacheImage(imageId).then(image => { - try { - cornerstone.getEnabledElement(this.element); - } catch (error) { - // Handle cases where the user ends the session before the image is displayed. - console.error(error); - return; + cornerstoneTools.addStackStateManager(this.element, ['stack']); + cornerstoneTools.addToolState(this.element, 'stack', currentStack); + } else { + // TODO: we should make something like setToolState by an ID + currentStack.currentImageIdIndex = currentImageIdIndex; + currentStack.imageIds = stack.imageIds; } - const viewport = cornerstone.getDefaultViewportForImage(this.element, image); + const imageId = currentStack.imageIds[currentImageIdIndex]; - // Workaround for Cornerstone issue #304 - viewport.displayedArea.brhc = { - x: image.width, - y: image.height - } + this.setState({ + displaySetInstanceUid, + studyInstanceUid, + stack, + imageId, + }); - cornerstone.displayImage(this.element, image, viewport); + cornerstoneTools.stackPrefetch.disable(this.element); + cornerstone.loadAndCacheImage(imageId).then((image) => { + try { + cornerstone.getEnabledElement(this.element); + } catch (error) { + // Handle cases where the user ends the session before the image is displayed. + console.error(error); + return; + } - cornerstoneTools.stackPrefetch.enable(this.element); - }); + const viewport = cornerstone.getDefaultViewportForImage(this.element, image); + + // Workaround for Cornerstone issue #304 + viewport.displayedArea.brhc = { + x: image.width, + y: image.height, + }; + + cornerstone.displayImage(this.element, image, viewport); + + cornerstoneTools.stackPrefetch.enable(this.element); + }); } if (this.props.activeTool !== prevProps.activeTool) { - this.setActiveTool(this.props.activeTool); + this.setActiveTool(this.props.activeTool); - // TODO: Why do we need to do this in v3? - cornerstoneTools.setToolActive('StackScrollMouseWheel', { - mouseButtonMask: 0, - isTouchActive: true - }); + // TODO: Why do we need to do this in v3? + cornerstoneTools.setToolActive('StackScrollMouseWheel', { + mouseButtonMask: 0, + isTouchActive: true, + }); } // TODO: Check this, causes infinite loop - //this.debouncedResize(); - } + // this.debouncedResize(); +} - setActiveTool = activeTool => { +setActiveTool = (activeTool) => { const leftMouseTools = ['Bidirectional', 'Wwwc', 'StackScroll']; setToolsPassive(leftMouseTools); @@ -507,26 +578,28 @@ class CornerstoneViewport extends Component { // pan is the default tool for middle mouse button const isPanToolActive = activeTool === 'Pan'; const panOptions = { - mouseButtonMask: isPanToolActive ? [1, 4] : [4], - isTouchActive: isPanToolActive + mouseButtonMask: isPanToolActive ? [1, 4] : [4], + isTouchActive: isPanToolActive, }; cornerstoneTools.setToolActive('Pan', panOptions); // zoom is the default tool for right mouse button const isZoomToolActive = activeTool === 'Zoom'; const zoomOptions = { - mouseButtonMask: isZoomToolActive ? [1, 2] : [2], - isTouchActive: isZoomToolActive + mouseButtonMask: isZoomToolActive ? [1, 2] : [2], + isTouchActive: isZoomToolActive, }; cornerstoneTools.setToolActive('Zoom', zoomOptions); cornerstoneTools.setToolActive(activeTool, { - mouseButtonMask: 1, - isTouchActive: true + mouseButtonMask: 1, + isTouchActive: true, }); - }; +}; + +onStackScroll(event) { + this.setViewportActive(); - onStackScroll(event) { const element = event.currentTarget; const stackData = cornerstoneTools.getToolState(element, 'stack'); const stack = stackData.data[0]; @@ -534,37 +607,37 @@ class CornerstoneViewport extends Component { this.hideExtraButtons(); this.setState({ - stack, - imageScrollbarValue: stack.currentImageIdIndex + stack, + imageScrollbarValue: stack.currentImageIdIndex, }); - } +} - onImageLoaded(event) { +onImageLoaded() { this.setState({ - numImagesLoaded: this.state.numImagesLoaded + 1 + numImagesLoaded: this.state.numImagesLoaded + 1, }); - } +} - startLoadingHandler() { - //console.log('startLoadingHandler'); +startLoadingHandler() { + // console.log('startLoadingHandler'); clearTimeout(this.loadHandlerTimeout); this.loadHandlerTimeout = setTimeout(() => { - this.setState({ - isLoading: true - }); + this.setState({ + isLoading: true, + }); }, loadIndicatorDelay); - } +} - doneLoadingHandler() { +doneLoadingHandler() { clearTimeout(this.loadHandlerTimeout); this.setState({ - isLoading: false + isLoading: false, }); - } +} - onMeasurementAddedOrRemoved(event) { +onMeasurementAddedOrRemoved() { console.log('onMeasurementAddedOrRemoved'); - /*const { toolType, measurementData } = event.detail; + /* const { toolType, measurementData } = event.detail; // TODO: Pass in as prop? const toolsOfInterest = ['Bidirectional']; @@ -593,64 +666,87 @@ class CornerstoneViewport extends Component { measurementData ); }*/ - } +} - onMouseClick(event) { - if (event.detail.event.which === 3) { - this.setState({ - toolContextMenuData: { - eventData: event.detail, - isTouchEvent: false - } - }); +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(); + + if (event.detail.event.which === 3) { + this.setState({ + toolContextMenuData: { + eventData: event.detail, + isTouchEvent: false, + }, + }); + } +} + +onTouchPress(event) { + this.setViewportActive(); - onTouchPress(event) { this.setState({ - toolContextMenuData: { - eventData: event.detail, - isTouchEvent: true - } + toolContextMenuData: { + eventData: event.detail, + isTouchEvent: true, + }, }); - } +} - onCloseToolContextMenu() { +onTouchStart() { + this.setViewportActive(); +} + +onCloseToolContextMenu() { this.setState({ - toolContextMenuData: null + toolContextMenuData: null, }); - } +} + +imageSliderOnInputCallback(value) { + this.setViewportActive(); - imageSliderOnInputCallback(value) { this.setState({ - imageScrollbarValue: value + imageScrollbarValue: value, }); // Note that we throttle requests to prevent the // user's ultrafast scrolling from firing requests too quickly. - //clearTimeout(this.slideTimeout); - //this.slideTimeout = setTimeout(() => { + // clearTimeout(this.slideTimeout); + // this.slideTimeout = setTimeout(() => { scrollToIndex(this.element, value); - //}, this.slideTimeoutTime); - } + // }, this.slideTimeoutTime); +} - hideExtraButtons = () => { +hideExtraButtons = () => { if (this.state.bidirectionalAddLabelShow === true) { - this.setState({ - bidirectionalAddLabelShow: false - }); + this.setState({ + bidirectionalAddLabelShow: false, + }); } this.setState({ - toolContextMenuData: null + toolContextMenuData: null, }); - }; +}; } CornerstoneViewport.propTypes = { - measurementsAddedOrRemoved: PropTypes.func, - measurementsChanged: PropTypes.func, - activeTool: PropTypes.string, - viewportData: PropTypes.object.isRequired + measurementsAddedOrRemoved: PropTypes.func, + measurementsChanged: PropTypes.func, + activeTool: PropTypes.string, + viewportData: PropTypes.object.isRequired, }; export default CornerstoneViewport; diff --git a/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.styl b/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.styl index c1f2f461a..261365a59 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.styl +++ b/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/CornerstoneViewport.styl @@ -3,6 +3,10 @@ .CornerstoneViewport width: 100% height: 100% + +.viewport-element + width: 100% + height: 100% position: relative; background-color: black diff --git a/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/imageViewerViewport.js b/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/imageViewerViewport.js index 795033687..49933421e 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/imageViewerViewport.js +++ b/Packages/ohif-viewerbase/client/components/viewer/CornerstoneViewport/imageViewerViewport.js @@ -413,7 +413,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => { // Check if the current active viewport in the Meteor Session // Is the same as the viewport in which the activation event was fired. // 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; $element.focus(); @@ -473,7 +473,7 @@ const loadDisplaySetIntoViewport = (data, templateData) => { // Set the active viewport based on the Session variable // This is done to ensure that the active element has the current // focus, so that keyboard events are triggered. - if (viewportIndex === Session.get('activeViewport')) { + if (viewportIndex === window.store.getState().viewports.activeViewport) { const viewportContainer = $element.parents('.viewportContainer'); setActiveViewport(viewportContainer); @@ -563,7 +563,7 @@ Template.imageViewerViewport.onRendered(function() { // Get the current active viewport index, if this viewport has the same index, // 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 if (templateData.viewportIndex === activeViewport) { diff --git a/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.js b/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.js index 39f31c01c..34c7c9a68 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.js +++ b/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.js @@ -2,42 +2,129 @@ import { Component } from 'react'; import React from 'react'; import PropTypes from 'prop-types'; 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 { render() { - const scale = Math.round(this.props.viewport.scale * 100) / 100; + const zoom = this.props.viewport.scale * 100; const imageId = this.props.imageId; - /*const patientId = cornerstone.metaData.get('00100020', imageId); - const studyDate = cornerstone.metaData.get('00080020', imageId); - const collection = cornerstone.metaData.get('00131010', imageId); - const studyDescription = cornerstone.metaData.get('00081030', imageId);*/ + const patientMetadata = cornerstone.metaData.get('patient', imageId); + const studyMetadata = cornerstone.metaData.get('study', imageId); + const seriesMetadata = cornerstone.metaData.get('series', imageId); + const instanceMetadata = cornerstone.metaData.get('instance', imageId); - const windowWidth = Math.round(this.props.viewport.voi.windowWidth); - const windowCenter = Math.round(this.props.viewport.voi.windowCenter); - const imagesLeft = - this.props.stack.imageIds.length - this.props.numImagesLoaded; + const patientName = patientMetadata.name; + const patientId = patientMetadata.id;; + const { studyDate, studyTime, studyDescription } = studyMetadata; + 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 = (<> +
+
{formatPN(patientName)}
+
{patientId}
+
+
+
{studyDescription}
+
{formatDA(studyDate)} {formatTM(studyTime)}
+
+
+
Zoom: {formatNumberPrecision(zoom,0)}%
+
{compression}
+
{wwwc}
+
+
+
{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}
+
{numImages > 1 ? `Img: ${instanceNumber} ${imageIndex}/${numImages}` : ''}
+
{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''} +
{imageDimensions}
+
+ {isValidNumber(sliceLocation) ? `Loc: ${formatNumberPrecision(sliceLocation, 2)} mm` : ''} + {thickness ? `Thick: ${formatNumberPrecision(thickness, 2)} mm` : ''} + {spacingBetweenSlices ? `Spacing: ${formatNumberPrecision(spacingBetweenSlices, 2)} mm` : ''} +
+
{seriesDescription}
+
+
+ ); + + const rightOnly = (<> +
+
{formatPN(patientName)}
+
{patientId}
+
{studyDescription}
+
{formatDA(studyDate)} {formatTM(studyTime)}
+
+
+
{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}
+
{numImages > 1 ? `Img: ${instanceNumber} ${imageIndex}/${numImages}` : ''}
+
{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''}
+
{imageDimensions}
+
{seriesDescription}
+
Zoom: ${formatNumberPrecision(zoom, 0)}%
+
{compression}
+
{wwwc}
+
+ ); + + const leftOnly = (<> +
+
{formatPN(patientName)}
+
{patientId}
+
{studyDescription}
+
{formatDA(studyDate)} {formatTM(studyTime)}
+
+
+
{seriesNumber >= 0 ? `Ser: ${seriesNumber}` : ''}
+
{numImages > 1 ? `Img: ${instanceNumber} ${imageIndex}/${numImages}` : ''}
+
{frameRate >= 0 ? `${formatNumberPrecision(frameRate, 2)} FPS` : ''}
+
{imageDimensions}
+
{seriesDescription}
+
Zoom: ${formatNumberPrecision(zoom, 0)}%
+
{compression}
+
{wwwc}
+
+ ); return ( -
-
- {this.props.patientId} - {this.props.studyDate} - {this.props.studyDescription} - - {imagesLeft > 0 ? `${imagesLeft} images remaining...` : ''} - -
-
Zoom: {scale}
-
- - WW/WC: {windowWidth} / {windowCenter} - - - Image: {this.props.stack.currentImageIdIndex + 1} /{' '} - {this.props.stack.imageIds.length} - -
+
+ {normal}
); } diff --git a/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.styl b/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.styl index ed3fe101d..eb87fa6ee 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.styl +++ b/Packages/ohif-viewerbase/client/components/viewer/ViewportOverlay/ViewportOverlay.styl @@ -2,10 +2,10 @@ $viewportTagPadding = 20px -.imageViewerViewport.empty ~ .imageViewerViewportOverlay +.imageViewerViewport.empty ~ .ViewportOverlay display: none -.imageViewerViewportOverlay +.ViewportOverlay theme('color', '$textSecondaryColor') .overlay-element diff --git a/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js b/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js index 719da8371..0e0ed09d7 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js +++ b/Packages/ohif-viewerbase/client/components/viewer/cineDialog/cineDialog.js @@ -88,7 +88,7 @@ Template.cineDialog.onCreated(() => { // Run this computation every time the active viewport is changed instance.autorun(() => { - Session.get('activeViewport'); + window.store.getState().viewports.activeViewport; Tracker.afterFlush(() => { // Get the active viewportElement diff --git a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js index 6d3dcadae..c5a8f3663 100644 --- a/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js +++ b/Packages/ohif-viewerbase/client/components/viewer/toolbarSectionButton/toolbarSectionButton.js @@ -100,7 +100,7 @@ Template.toolbarSectionButton.helpers({ }, disableButton() { - Session.get('activeViewport'); + window.store.getState().viewports.activeViewport; Session.get('LayoutManagerUpdated'); const instance = Template.instance(); const isCommandDisabled = OHIF.commands.isDisabled(instance.data.id); diff --git a/Packages/ohif-viewerbase/client/index.js b/Packages/ohif-viewerbase/client/index.js index f858ec8ac..40651e043 100644 --- a/Packages/ohif-viewerbase/client/index.js +++ b/Packages/ohif-viewerbase/client/index.js @@ -248,3 +248,6 @@ Viewerbase.StackImagePositionOffsetSynchronizer = StackImagePositionOffsetSynchr // StudyMetadataSource import { StudyMetadataSource } from './lib/classes/StudyMetadataSource'; Viewerbase.StudyMetadataSource = StudyMetadataSource; + +import redux from './lib/redux/'; +Viewerbase.redux = redux; diff --git a/Packages/ohif-viewerbase/client/lib/classes/LayoutManager.js b/Packages/ohif-viewerbase/client/lib/classes/LayoutManager.js index 1f809e824..e4689fdf7 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/LayoutManager.js +++ b/Packages/ohif-viewerbase/client/lib/classes/LayoutManager.js @@ -416,7 +416,7 @@ export class LayoutManager { const isSequenced = this.isDisplaySetsSequenced(sequenceMap); // 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 if (isNext && !allowLooping) { @@ -649,7 +649,7 @@ export class LayoutManager { this.moveMultipleViewportDisplaySets(isNext); } else { // Get the selected viewport index - const viewportIndex = Session.get('activeViewport'); + const viewportIndex = window.store.getState().viewports.activeViewport; // Move display sets on a single viewport this.moveSingleViewportDisplaySets(viewportIndex, isNext); diff --git a/Packages/ohif-viewerbase/client/lib/classes/StackImagePositionOffsetSynchronizer.js b/Packages/ohif-viewerbase/client/lib/classes/StackImagePositionOffsetSynchronizer.js index e63286e9d..0137e3659 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/StackImagePositionOffsetSynchronizer.js +++ b/Packages/ohif-viewerbase/client/lib/classes/StackImagePositionOffsetSynchronizer.js @@ -86,7 +86,7 @@ export class StackImagePositionOffsetSynchronizer { } getActiveViewportElement() { - const viewportIndex = Session.get('activeViewport') || 0; + const viewportIndex = window.store.getState().viewports.activeViewport || 0; return $('.imageViewerViewport').get(viewportIndex); } diff --git a/Packages/ohif-viewerbase/client/lib/classes/StudyPrefetcher.js b/Packages/ohif-viewerbase/client/lib/classes/StudyPrefetcher.js index 98184b50b..7df8241a1 100644 --- a/Packages/ohif-viewerbase/client/lib/classes/StudyPrefetcher.js +++ b/Packages/ohif-viewerbase/client/lib/classes/StudyPrefetcher.js @@ -86,7 +86,7 @@ export class StudyPrefetcher { // Make sure there is a stack to fetch if (this.hasStack(element)) { // 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 stack = StackManager.findStack(displaySetInstanceUid); diff --git a/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js b/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js index 80e19f5eb..ba46bbe8b 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/formatNumberPrecision.js @@ -1,10 +1,14 @@ 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 */ -Blaze.registerHelper('formatNumberPrecision', function(context, precision) { - if (context != null) { - return parseFloat(context).toFixed(precision); - } -}); +Blaze.registerHelper('formatNumberPrecision', formatNumberPrecision); + +export default formatNumberPrecision; diff --git a/Packages/ohif-viewerbase/client/lib/helpers/index.js b/Packages/ohif-viewerbase/client/lib/helpers/index.js index 867832cc5..3761040cc 100644 --- a/Packages/ohif-viewerbase/client/lib/helpers/index.js +++ b/Packages/ohif-viewerbase/client/lib/helpers/index.js @@ -6,6 +6,7 @@ import { isTouchDevice } from './isTouchDevice'; import { formatPN } from './formatPN'; import { formatDA } from './formatDA'; import { formatTM } from './formatTM'; +import isValidNumber from './isValidNumber'; /** * Helpers with side effects only... @@ -13,7 +14,7 @@ import { formatTM } from './formatTM'; import './formatJSDate'; import './jsDateFromNow'; -import './formatNumberPrecision'; +import formatNumberPrecision from './formatNumberPrecision'; import './inc'; import './isDisplaySetActive'; import './getUsername'; @@ -33,7 +34,9 @@ const helpers = { isTouchDevice, formatPN, formatDA, - formatTM + formatTM, + formatNumberPrecision, + isValidNumber, }; export { helpers }; diff --git a/Packages/ohif-viewerbase/client/lib/helpers/isValidNumber.js b/Packages/ohif-viewerbase/client/lib/helpers/isValidNumber.js new file mode 100644 index 000000000..df8947fff --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/helpers/isValidNumber.js @@ -0,0 +1,3 @@ +export default function isValidNumber(value) { + return typeof value === 'number' && !isNaN(value); +} diff --git a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js index 865963191..714c80ddd 100644 --- a/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js +++ b/Packages/ohif-viewerbase/client/lib/hotkeyUtils.js @@ -92,7 +92,7 @@ Meteor.startup(function() { // Create a function that returns true if the active viewport is empty const isActiveViewportEmpty = () => { - const activeViewport = Session.get('activeViewport') || 0; + const activeViewport = window.store.getState().viewports.activeViewport || 0; return $('.imageViewerViewport').eq(activeViewport).hasClass('empty'); }; diff --git a/Packages/ohif-viewerbase/client/lib/panelNavigation.js b/Packages/ohif-viewerbase/client/lib/panelNavigation.js index dbe9feaf6..c3af73db9 100644 --- a/Packages/ohif-viewerbase/client/lib/panelNavigation.js +++ b/Packages/ohif-viewerbase/client/lib/panelNavigation.js @@ -5,7 +5,7 @@ import { setActiveViewport } from './setActiveViewport'; const loadPreviousActivePanel = () => { OHIF.log.info('nextActivePanel'); - let currentIndex = Session.get('activeViewport'); + let currentIndex = window.store.getState().viewports.activeViewport; currentIndex--; const $viewports = $('.viewportContainer'); @@ -24,7 +24,7 @@ const loadPreviousActivePanel = () => { const loadNextActivePanel = () => { OHIF.log.info('nextActivePanel'); - let currentIndex = Session.get('activeViewport'); + let currentIndex = window.store.getState().viewports.activeViewport; currentIndex++; const $viewports = $('.viewportContainer'); diff --git a/Packages/ohif-viewerbase/client/lib/redux/actions.js b/Packages/ohif-viewerbase/client/lib/redux/actions.js new file mode 100644 index 000000000..4ab017db9 --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/redux/actions.js @@ -0,0 +1,9 @@ +export const setToolActive = tool => ({ + type: 'SET_TOOL_ACTIVE', + tool +}); + +export const setViewportActive = viewportIndex => ({ + type: 'SET_VIEWPORT_ACTIVE', + viewportIndex +}); diff --git a/Packages/ohif-viewerbase/client/lib/redux/index.js b/Packages/ohif-viewerbase/client/lib/redux/index.js new file mode 100644 index 000000000..406e27d3b --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/redux/index.js @@ -0,0 +1,8 @@ +import './actions.js'; +import combinedReducer from './reducers'; + +const redux = { + combinedReducer +}; + +export default redux; diff --git a/Packages/ohif-viewerbase/client/lib/redux/reducers/index.js b/Packages/ohif-viewerbase/client/lib/redux/reducers/index.js new file mode 100644 index 000000000..68f7ae11c --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/redux/reducers/index.js @@ -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; diff --git a/Packages/ohif-viewerbase/client/lib/redux/reducers/tools.js b/Packages/ohif-viewerbase/client/lib/redux/reducers/tools.js new file mode 100644 index 000000000..1a7a653cd --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/redux/reducers/tools.js @@ -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; diff --git a/Packages/ohif-viewerbase/client/lib/redux/reducers/viewports.js b/Packages/ohif-viewerbase/client/lib/redux/reducers/viewports.js new file mode 100644 index 000000000..2f8496cd4 --- /dev/null +++ b/Packages/ohif-viewerbase/client/lib/redux/reducers/viewports.js @@ -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; diff --git a/Packages/ohif-viewerbase/client/lib/setActiveViewport.js b/Packages/ohif-viewerbase/client/lib/setActiveViewport.js index 838e0d439..de876c419 100644 --- a/Packages/ohif-viewerbase/client/lib/setActiveViewport.js +++ b/Packages/ohif-viewerbase/client/lib/setActiveViewport.js @@ -30,9 +30,12 @@ export function setActiveViewport(element) { $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. - Session.set('activeViewport', viewportIndex); + window.store.dispatch({ + type: 'SET_VIEWPORT_ACTIVE', + viewportIndex + }); // Finally, enable stack prefetching and hide the reference lines from // the newly activated viewport that has a canvas diff --git a/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js b/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js index ff27a270c..349b98db4 100644 --- a/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js +++ b/Packages/ohif-viewerbase/client/lib/setFocusToActiveViewport.js @@ -12,7 +12,7 @@ const setFocusToActiveViewport = () => { const viewports = $('.imageViewerViewport'); // 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 const activeViewport = viewports.eq(activeViewportIndex); @@ -21,4 +21,4 @@ const setFocusToActiveViewport = () => { activeViewport.focus(); }; -export { setFocusToActiveViewport }; \ No newline at end of file +export { setFocusToActiveViewport }; diff --git a/Packages/ohif-viewerbase/client/lib/viewportUtils.js b/Packages/ohif-viewerbase/client/lib/viewportUtils.js index 5ee1d8a47..a8007891d 100644 --- a/Packages/ohif-viewerbase/client/lib/viewportUtils.js +++ b/Packages/ohif-viewerbase/client/lib/viewportUtils.js @@ -30,7 +30,7 @@ const getEnabledElement = element => { * @return {DOMElement} DOMElement of the current active viewport */ const getActiveViewportElement = () => { - const viewportIndex = Session.get('activeViewport') || 0; + const viewportIndex = window.store.getState().viewports.activeViewport || 0; return $('.imageViewerViewport').get(viewportIndex); }; @@ -274,7 +274,7 @@ const isPlaying = () => { // Check if a study has multiple frames const hasMultipleFrames = () => { // Its called everytime active viewport and/or layout change - Session.get('activeViewport'); + window.store.getState().viewports.activeViewport; Session.get('LayoutManagerUpdated'); const activeViewport = getActiveViewportElement(); @@ -326,7 +326,7 @@ const isStackScrollLinkingDisabled = () => { let linkableViewportsCount = 0; // Its called everytime active viewport and/or layout change - Session.get('activeViewport'); + window.store.getState().viewports.activeViewport; Session.get('LayoutManagerUpdated'); const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;