diff --git a/Packages-react/ohif-viewer/package.json b/Packages-react/ohif-viewer/package.json
index 7fc8dfb02..183db3978 100644
--- a/Packages-react/ohif-viewer/package.json
+++ b/Packages-react/ohif-viewer/package.json
@@ -110,6 +110,7 @@
"ohif-dicom-html-extension": "^0.0.2",
"ohif-dicom-microscopy-extension": "^0.0.5",
"ohif-dicom-pdf-extension": "^0.0.5",
+ "moment": "^2.24.0",
"oidc-client": "^1.6.1",
"prop-types": "^15.6.2",
"react-bootstrap-modal": "^4.2.0",
diff --git a/Packages-react/ohif-viewer/src/App.css b/Packages-react/ohif-viewer/src/App.css
index 68bca728d..25ef88265 100644
--- a/Packages-react/ohif-viewer/src/App.css
+++ b/Packages-react/ohif-viewer/src/App.css
@@ -5,6 +5,7 @@ body,
height: 100%;
width: 100%;
margin: 0;
+ overflow: hidden;
}
body {
diff --git a/Packages-react/ohif-viewer/src/App.js b/Packages-react/ohif-viewer/src/App.js
index bc7ebbcc0..134c91a3e 100644
--- a/Packages-react/ohif-viewer/src/App.js
+++ b/Packages-react/ohif-viewer/src/App.js
@@ -19,11 +19,11 @@ import {
reducer as oidcReducer
} from 'redux-oidc';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
+import './App.css';
const { ExtensionManager } = OHIF.extensions;
//import Icons from "./images/icons.svg"
-
const Icons = '/icons.svg';
const { reducers, localStorage } = OHIF.redux;
diff --git a/Packages-react/ohif-viewer/src/connectedComponents/ConnectedMeasurementTable.js b/Packages-react/ohif-viewer/src/connectedComponents/ConnectedMeasurementTable.js
new file mode 100644
index 000000000..d3759194d
--- /dev/null
+++ b/Packages-react/ohif-viewer/src/connectedComponents/ConnectedMeasurementTable.js
@@ -0,0 +1,48 @@
+import { connect } from 'react-redux';
+import { MeasurementTable } from 'react-viewerbase';
+import moment from 'moment';
+
+function convertTimepointsToTableData(timepoints) {
+ if (!timepoints || !timepoints.length) {
+ return [];
+ }
+
+ return [
+ {
+ label: 'Study date:',
+ date: moment(timepoints[0].latestDate).format('DD-MMM-YY')
+ }
+ ];
+}
+
+function convertMeasurementsToTableData(measurements) {
+ const tableData = [
+ {
+ groupName: 'Measurements',
+ measurements: []
+ }
+ ];
+
+ if (!measurements || !measurements.length) {
+ return tableData;
+ }
+
+ // TODO: Add measurements into tableData
+
+ return tableData;
+}
+
+const mapStateToProps = state => {
+ const { timepoints, measurements } = state;
+ return {
+ timepoints: convertTimepointsToTableData(timepoints.timepoints),
+ measurementCollection: convertMeasurementsToTableData(measurements)
+ };
+};
+
+const ConnectedMeasurementTable = connect(
+ mapStateToProps,
+ null
+)(MeasurementTable);
+
+export default ConnectedMeasurementTable;
diff --git a/Packages-react/ohif-viewer/src/connectedComponents/FlexboxLayout.js b/Packages-react/ohif-viewer/src/connectedComponents/FlexboxLayout.js
index eb272e5b8..a0618960d 100644
--- a/Packages-react/ohif-viewer/src/connectedComponents/FlexboxLayout.js
+++ b/Packages-react/ohif-viewer/src/connectedComponents/FlexboxLayout.js
@@ -2,89 +2,107 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ViewerMain from './ViewerMain.js';
+import ConnectedMeasurementTable from './ConnectedMeasurementTable';
import './FlexboxLayout.css';
class FlexboxLayout extends Component {
- static propTypes = {
- studies: PropTypes.array.isRequired,
- leftSidebarOpen: PropTypes.bool.isRequired,
- rightSidebarOpen: PropTypes.bool.isRequired,
- };
+ static propTypes = {
+ studies: PropTypes.array.isRequired,
+ leftSidebarOpen: PropTypes.bool.isRequired,
+ rightSidebarOpen: PropTypes.bool.isRequired
+ };
- state = {
- studiesForBrowser: []
- };
+ state = {
+ studiesForBrowser: []
+ };
- componentDidMount() {
- const studiesForBrowser = this.getStudiesForBrowser();
+ componentDidMount() {
+ const studiesForBrowser = this.getStudiesForBrowser();
- this.setState({
- studiesForBrowser
- });
- }
+ this.setState({
+ studiesForBrowser
+ });
+ }
- getStudiesForBrowser = () => {
- const { studies } = this.props;
+ getStudiesForBrowser = () => {
+ const { studies } = this.props;
- // TODO[react]:
- // - Add sorting of display sets
- // - Add useMiddleSeriesInstanceAsThumbnail
- // - Add showStackLoadingProgressBar option
- return studies.map((study) => {
- const { studyInstanceUid } = study;
+ // TODO[react]:
+ // - Add sorting of display sets
+ // - Add useMiddleSeriesInstanceAsThumbnail
+ // - Add showStackLoadingProgressBar option
+ return studies.map(study => {
+ const { studyInstanceUid } = study;
- const thumbnails = study.displaySets.map((displaySet) => {
- const { displaySetInstanceUid, seriesDescription, seriesNumber, instanceNumber, numImageFrames } = displaySet;
+ const thumbnails = study.displaySets.map(displaySet => {
+ const {
+ displaySetInstanceUid,
+ seriesDescription,
+ seriesNumber,
+ instanceNumber,
+ numImageFrames
+ } = displaySet;
+ let imageId;
- let imageId;
-
- if (displaySet.images && displaySet.images.length) {
- imageId = displaySet.images[0].getImageId();
- }
-
- return {
- imageId,
- displaySetInstanceUid,
- seriesDescription,
- seriesNumber,
- instanceNumber,
- numImageFrames,
- };
- });
-
- return {
- studyInstanceUid,
- thumbnails
- };
- });
- }
-
- render() {
- let mainContentClassName = "main-content"
- if (this.props.leftSidebarOpen) {
- mainContentClassName += ' sidebar-left-open';
+ if (displaySet.images && displaySet.images.length) {
+ imageId = displaySet.images[0].getImageId();
}
- if (this.props.rightSidebarOpen) {
- mainContentClassName += ' sidebar-right-open';
- }
+ return {
+ imageId,
+ displaySetInstanceUid,
+ seriesDescription,
+ seriesNumber,
+ instanceNumber,
+ numImageFrames
+ };
+ });
- // TODO[react]: Add measurementLightTable
- return (
-
-
-
-
-
-
-
- {/*
- {{> measurementLightTable (clone this)}}
-
*/}
-
- );
+ return {
+ studyInstanceUid,
+ thumbnails
+ };
+ });
+ };
+
+ render() {
+ let mainContentClassName = 'main-content';
+ if (this.props.leftSidebarOpen) {
+ mainContentClassName += ' sidebar-left-open';
}
+
+ if (this.props.rightSidebarOpen) {
+ mainContentClassName += ' sidebar-right-open';
+ }
+
+ // TODO[react]: Add measurementLightTable
+ return (
+
+ );
+ }
}
export default FlexboxLayout;
diff --git a/Packages-react/ohif-viewer/src/connectedComponents/ToolbarRow.js b/Packages-react/ohif-viewer/src/connectedComponents/ToolbarRow.js
index 9eebf7494..89623fd8c 100644
--- a/Packages-react/ohif-viewer/src/connectedComponents/ToolbarRow.js
+++ b/Packages-react/ohif-viewer/src/connectedComponents/ToolbarRow.js
@@ -1,4 +1,4 @@
-import React, { Component } from "react";
+import React, { Component } from 'react';
import ConnectedToolbarSection from './ConnectedToolbarSection';
import ConnectedLayoutButton from './ConnectedLayoutButton';
import ConnectedCineDialog from './ConnectedCineDialog.js';
@@ -10,68 +10,99 @@ import './ToolbarRow.css';
const Icons = '/icons.svg';
class ToolbarRow extends Component {
- state = {
- cineDialogOpen: false
+ state = {
+ cineDialogOpen: false
+ };
+
+ static propTypes = {
+ leftSidebarOpen: PropTypes.bool.isRequired,
+ rightSidebarOpen: PropTypes.bool.isRequired,
+ setLeftSidebarOpen: PropTypes.func,
+ setRightSidebarOpen: PropTypes.func
+ };
+
+ static defaultProps = {
+ leftSidebarOpen: false,
+ rightSidebarOpen: false
+ };
+
+ onLeftSidebarValueChanged = value => {
+ this.props.setLeftSidebarOpen(!!value);
+ };
+
+ onRightSidebarValueChanged = value => {
+ this.props.setRightSidebarOpen(!!value);
+ };
+
+ onClickCineToolbarButton = () => {
+ this.setState({
+ cineDialogOpen: !this.state.cineDialogOpen
+ });
+ };
+
+ render() {
+ const leftSidebarToggle = [
+ {
+ value: 'studies',
+ svgLink: `${Icons}#icon-studies`,
+ svgWidth: 15,
+ svgHeight: 13,
+ bottomLabel: 'Series'
+ }
+ ];
+
+ const rightSidebarToggle = [
+ {
+ value: 'measurements',
+ svgLink: `${Icons}#icon-measurements-lesions`,
+ svgWidth: 15,
+ svgHeight: 13,
+ bottomLabel: 'Measurements'
+ }
+ ];
+
+ const leftSidebarValue = this.props.leftSidebarOpen
+ ? leftSidebarToggle[0].value
+ : null;
+
+ const rightSidebarValue = this.props.rightSidebarOpen
+ ? rightSidebarToggle[0].value
+ : null;
+
+ const cineDialogContainerStyle = {
+ display: this.state.cineDialogOpen ? 'inline-block' : 'none'
};
- static propTypes = {
- leftSidebarOpen: PropTypes.bool.isRequired,
- rightSidebarOpen: PropTypes.bool.isRequired,
- setLeftSidebarOpen: PropTypes.func,
- setRightSidebarOpen: PropTypes.func
- };
-
- static defaultProps = {
- leftSidebarOpen: false,
- rightSidebarOpen: false
- };
-
- onLeftSidebarValueChanged = (value) => {
- this.props.setLeftSidebarOpen(!!value);
- }
-
- onClickCineToolbarButton = () => {
- this.setState({
- cineDialogOpen: !this.state.cineDialogOpen
- });
- }
-
- render() {
- const leftSidebarToggle = [{
- value: 'studies',
- svgLink: `${Icons}#icon-studies`,
- svgWidth: 15,
- svgHeight: 13,
- bottomLabel: 'Series',
- }];
-
- const leftSidebarValue = this.props.leftSidebarOpen ? leftSidebarToggle[0].value : null;
-
- const cineDialogContainerStyle = {
- display: this.state.cineDialogOpen ? 'inline-block' : 'none'
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
- {/* > roundedButtonGroup rightSidebarToggleButtonData */}
-
-
- );
- }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
}
export default ToolbarRow;
diff --git a/Packages-react/ohif-viewer/src/connectedComponents/Viewer.js b/Packages-react/ohif-viewer/src/connectedComponents/Viewer.js
index 0af643745..2cda2a7d6 100644
--- a/Packages-react/ohif-viewer/src/connectedComponents/Viewer.js
+++ b/Packages-react/ohif-viewer/src/connectedComponents/Viewer.js
@@ -3,12 +3,15 @@ import PropTypes from 'prop-types';
//import OHIF from 'ohif-core';
//import { CineDialog } from 'react-viewerbase';
+import OHIF from 'ohif-core';
+import moment from 'moment';
import WhiteLabellingContext from '../WhiteLabellingContext.js';
import ConnectedHeader from './ConnectedHeader.js';
import ConnectedFlexboxLayout from './ConnectedFlexboxLayout.js';
import ConnectedToolbarRow from './ConnectedToolbarRow.js';
import ConnectedStudyLoadingMonitor from './ConnectedStudyLoadingMonitor.js';
import StudyPrefetcher from '../components/StudyPrefetcher.js';
+import measurementTools from '../measurementTools';
import './Viewer.css';
/**
* Inits OHIF Hanging Protocol's onReady.
@@ -51,6 +54,117 @@ class Viewer extends Component {
studies: PropTypes.array
};
+ constructor(props) {
+ super(props);
+
+ const retrieveMeasurements = (patientId, timepointIds) => {
+ OHIF.log.info('retrieveMeasurements');
+ // TODO: Retrieve the measurements from the latest available SR
+ return Promise.resolve();
+ };
+
+ const storeMeasurements = (measurementData, timepointIds) => {
+ OHIF.log.info('storeMeasurements');
+ // TODO: Store the measurements into a new SR sent to the active server
+ return Promise.resolve();
+ };
+
+ const retrieveTimepoints = filter => {
+ OHIF.log.info('retrieveTimepoints');
+
+ // Get the earliest and latest study date
+ let earliestDate = new Date().toISOString();
+ let latestDate = new Date().toISOString();
+ if (props.studies) {
+ latestDate = new Date('1000-01-01').toISOString();
+ props.studies.forEach(study => {
+ const studyDate = moment(study.studyDate, 'YYYYMMDD').toISOString();
+ if (studyDate < earliestDate) {
+ earliestDate = studyDate;
+ }
+ if (studyDate > latestDate) {
+ latestDate = studyDate;
+ }
+ });
+ }
+
+ // Return a generic timepoint
+ return Promise.resolve([
+ {
+ timepointType: 'baseline',
+ timepointId: 'TimepointId',
+ studyInstanceUids: props.studyInstanceUids,
+ patientId: filter.patientId,
+ earliestDate,
+ latestDate,
+ isLocked: false
+ }
+ ]);
+ };
+
+ const storeTimepoints = timepointData => {
+ OHIF.log.info('storeTimepoints');
+ return Promise.resolve();
+ };
+
+ const updateTimepoint = (timepointData, query) => {
+ OHIF.log.info('updateTimepoint');
+ return Promise.resolve();
+ };
+
+ const removeTimepoint = timepointId => {
+ OHIF.log.info('removeTimepoint');
+ return Promise.resolve();
+ };
+
+ const disassociateStudy = (timepointIds, studyInstanceUid) => {
+ OHIF.log.info('disassociateStudy');
+ return Promise.resolve();
+ };
+
+ OHIF.measurements.MeasurementApi.setConfiguration({
+ measurementTools,
+ dataExchange: {
+ retrieve: retrieveMeasurements,
+ store: storeMeasurements
+ }
+ });
+
+ OHIF.measurements.TimepointApi.setConfiguration({
+ dataExchange: {
+ retrieve: retrieveTimepoints,
+ store: storeTimepoints,
+ remove: removeTimepoint,
+ update: updateTimepoint,
+ disassociate: disassociateStudy
+ }
+ });
+ }
+
+ componentDidMount() {
+ const { studies } = this.props;
+ const { TimepointApi, MeasurementApi } = OHIF.measurements;
+
+ // TODO: Get the Redux store from somewhere else
+ const { store } = window;
+
+ const timepointApi = new TimepointApi(store, 'TimepointId');
+ const measurementApi = new MeasurementApi(store, timepointApi);
+ const apis = {
+ timepointApi,
+ measurementApi
+ };
+
+ Object.assign(OHIF.viewer, apis);
+
+ const patientId = studies[0] && studies[0].patientId;
+ timepointApi.retrieveTimepoints({ patientId });
+
+ // TODO: Retrieve measurements and sync them with tool data
+ // measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
+ // measurementApi.syncMeasurementsAndToolData();
+ }
+
render() {
return (
<>
diff --git a/Packages-react/ohif-viewer/src/measurementTools/arrowAnnotate.js b/Packages-react/ohif-viewer/src/measurementTools/arrowAnnotate.js
new file mode 100644
index 000000000..e5568fe1a
--- /dev/null
+++ b/Packages-react/ohif-viewer/src/measurementTools/arrowAnnotate.js
@@ -0,0 +1,15 @@
+const displayFunction = data => {
+ return data.text || '';
+};
+
+export default {
+ id: 'arrowAnnotate',
+ name: 'ArrowAnnotate',
+ toolGroup: 'allTools',
+ cornerstoneToolType: 'arrowAnnotate',
+ options: {
+ measurementTable: {
+ displayFunction
+ }
+ }
+};
diff --git a/Packages-react/ohif-viewer/src/measurementTools/ellipticalRoi.js b/Packages-react/ohif-viewer/src/measurementTools/ellipticalRoi.js
new file mode 100644
index 000000000..93096f43b
--- /dev/null
+++ b/Packages-react/ohif-viewer/src/measurementTools/ellipticalRoi.js
@@ -0,0 +1,22 @@
+const displayFunction = data => {
+ let meanValue = '';
+ if (data.meanStdDev && data.meanStdDev.mean) {
+ meanValue = data.meanStdDev.mean.toFixed(2) + ' HU';
+ }
+ return meanValue;
+ // let meanValue = data.meanStdDev && data.meanStdDev.mean || 0;
+ // return numberWithCommas(meanValue).toFixed(2) + ' HU';
+ //return data.meanStdDev.mean.toFixed(2);
+};
+
+export default {
+ id: 'ellipticalRoi',
+ name: 'Ellipse',
+ toolGroup: 'allTools',
+ cornerstoneToolType: 'ellipticalRoi',
+ options: {
+ measurementTable: {
+ displayFunction
+ }
+ }
+};
diff --git a/Packages-react/ohif-viewer/src/measurementTools/index.js b/Packages-react/ohif-viewer/src/measurementTools/index.js
new file mode 100644
index 000000000..803422ca4
--- /dev/null
+++ b/Packages-react/ohif-viewer/src/measurementTools/index.js
@@ -0,0 +1,21 @@
+import length from './length';
+import ellipticalRoi from './ellipticalRoi';
+import rectangleRoi from './rectangleRoi';
+import simpleAngle from './simpleAngle';
+import arrowAnnotate from './arrowAnnotate';
+
+const trackedTools = [
+ length,
+ ellipticalRoi,
+ rectangleRoi,
+ simpleAngle,
+ arrowAnnotate
+];
+
+export default [
+ {
+ id: 'allTools',
+ name: 'Measurements',
+ childTools: trackedTools
+ }
+];
diff --git a/Packages-react/ohif-viewer/src/measurementTools/length.js b/Packages-react/ohif-viewer/src/measurementTools/length.js
new file mode 100644
index 000000000..5348dd859
--- /dev/null
+++ b/Packages-react/ohif-viewer/src/measurementTools/length.js
@@ -0,0 +1,19 @@
+const displayFunction = data => {
+ let lengthValue = '';
+ if (data.length) {
+ lengthValue = data.length.toFixed(2) + ' mm';
+ }
+ return lengthValue;
+};
+
+export default {
+ id: 'length',
+ name: 'Length',
+ toolGroup: 'allTools',
+ cornerstoneToolType: 'length',
+ options: {
+ measurementTable: {
+ displayFunction
+ }
+ }
+};
diff --git a/Packages-react/ohif-viewer/src/measurementTools/rectangleRoi.js b/Packages-react/ohif-viewer/src/measurementTools/rectangleRoi.js
new file mode 100644
index 000000000..1248bdc2c
--- /dev/null
+++ b/Packages-react/ohif-viewer/src/measurementTools/rectangleRoi.js
@@ -0,0 +1,19 @@
+const displayFunction = data => {
+ let meanValue = '';
+ if (data.meanStdDev && data.meanStdDev.mean) {
+ meanValue = data.meanStdDev.mean.toFixed(2) + ' HU';
+ }
+ return meanValue;
+};
+
+export default {
+ id: 'rectangleRoi',
+ name: 'Rectangle',
+ toolGroup: 'allTools',
+ cornerstoneToolType: 'rectangleRoi',
+ options: {
+ measurementTable: {
+ displayFunction
+ }
+ }
+};
diff --git a/Packages-react/ohif-viewer/src/measurementTools/simpleAngle.js b/Packages-react/ohif-viewer/src/measurementTools/simpleAngle.js
new file mode 100644
index 000000000..3197c4043
--- /dev/null
+++ b/Packages-react/ohif-viewer/src/measurementTools/simpleAngle.js
@@ -0,0 +1,19 @@
+const displayFunction = data => {
+ let text = '';
+ if (data.rAngle) {
+ text = data.rAngle.toFixed(2) + String.fromCharCode(parseInt('00B0', 16));
+ }
+ return text;
+};
+
+export default {
+ id: 'simpleAngle',
+ name: 'SimpleAngle',
+ toolGroup: 'allTools',
+ cornerstoneToolType: 'simpleAngle',
+ options: {
+ measurementTable: {
+ displayFunction
+ }
+ }
+};