feat(measurement-table): Improve measurement table support. Still missing labelling
This commit is contained in:
parent
becdfee1ed
commit
113c20c346
@ -18,7 +18,6 @@ import {
|
|||||||
createUserManager,
|
createUserManager,
|
||||||
reducer as oidcReducer
|
reducer as oidcReducer
|
||||||
} from 'redux-oidc';
|
} from 'redux-oidc';
|
||||||
import timepointManager from './redux/timepointManager';
|
|
||||||
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
|
||||||
const { ExtensionManager } = OHIF.extensions;
|
const { ExtensionManager } = OHIF.extensions;
|
||||||
@ -28,7 +27,6 @@ const Icons = 'icons.svg';
|
|||||||
const { reducers, localStorage } = OHIF.redux;
|
const { reducers, localStorage } = OHIF.redux;
|
||||||
reducers.ui = ui;
|
reducers.ui = ui;
|
||||||
reducers.oidc = oidcReducer;
|
reducers.oidc = oidcReducer;
|
||||||
reducers.timepointManager = timepointManager;
|
|
||||||
|
|
||||||
const combined = combineReducers(reducers);
|
const combined = combineReducers(reducers);
|
||||||
const store = createStore(combined, localStorage.loadState());
|
const store = createStore(combined, localStorage.loadState());
|
||||||
|
|||||||
@ -1,7 +1,100 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { MeasurementTable } from 'react-viewerbase';
|
import { MeasurementTable } from 'react-viewerbase';
|
||||||
|
import OHIF from 'ohif-core';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
function groupBy(list, props) {
|
||||||
|
return list.reduce((a, b) => {
|
||||||
|
(a[b[props]] = a[b[props]] || []).push(b);
|
||||||
|
return a;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAllTools() {
|
||||||
|
const config = OHIF.measurements.MeasurementApi.getConfiguration();
|
||||||
|
let tools = [];
|
||||||
|
config.measurementTools.forEach( toolGroup =>
|
||||||
|
tools = tools.concat(toolGroup.childTools)
|
||||||
|
);
|
||||||
|
|
||||||
|
return tools;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMeasurementText(measurementData) {
|
||||||
|
const { location, description} = measurementData;
|
||||||
|
let text = '...';
|
||||||
|
if (location) {
|
||||||
|
text = location;
|
||||||
|
if (description) {
|
||||||
|
text += `(${description})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataForEachMeasurementNumber(measurementNumberList, timepoints, displayFunction) {
|
||||||
|
const data = [];
|
||||||
|
// on each measurement number we should get each measurement data by available timepoint
|
||||||
|
measurementNumberList.forEach( measurement => {
|
||||||
|
timepoints.forEach( timepoint => {
|
||||||
|
const eachData = {
|
||||||
|
displayText: '...'
|
||||||
|
}
|
||||||
|
if (measurement.timepointId === timepoint.timepointId) {
|
||||||
|
eachData.displayText = displayFunction(measurement);
|
||||||
|
}
|
||||||
|
data.push(eachData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertMeasurementsToTableData(toolCollections, timepoints) {
|
||||||
|
const config = OHIF.measurements.MeasurementApi.getConfiguration();
|
||||||
|
const toolGroups = config.measurementTools;
|
||||||
|
const tools = getAllTools();
|
||||||
|
|
||||||
|
const tableMeasurements = toolGroups.map( toolGroup => {
|
||||||
|
return {
|
||||||
|
groupName: toolGroup.name,
|
||||||
|
groupId: toolGroup.id,
|
||||||
|
measurements: []
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(toolCollections).forEach(toolId => {
|
||||||
|
const toolMeasurements = toolCollections[toolId];
|
||||||
|
const tool = tools.find(tool => tool.id === toolId);
|
||||||
|
const { displayFunction } = tool.options.measurementTable;
|
||||||
|
|
||||||
|
// Group by measurementNumber so we can display then all in the same line
|
||||||
|
const groupedMeasurements = groupBy(toolMeasurements, 'measurementNumber');
|
||||||
|
|
||||||
|
Object.keys(groupedMeasurements).forEach( groupedMeasurementsIndex => {
|
||||||
|
const measurementNumberList = groupedMeasurements[groupedMeasurementsIndex];
|
||||||
|
//check if all measurements with same measurementNumber will have same LABEL
|
||||||
|
const tableMeasurement = {
|
||||||
|
label: getMeasurementText(measurementNumberList[0]),
|
||||||
|
hasWarnings: false, //TODO
|
||||||
|
warningTitle: '', //TODO
|
||||||
|
isSplitLesion: false, //TODO
|
||||||
|
warningList: [], //TODO
|
||||||
|
data: getDataForEachMeasurementNumber(measurementNumberList, timepoints, displayFunction)
|
||||||
|
};
|
||||||
|
|
||||||
|
// find the group object for the tool
|
||||||
|
const toolGroupMeasurements = tableMeasurements.find( group => {
|
||||||
|
return group.groupId === tool.toolGroup;
|
||||||
|
});
|
||||||
|
// inject the new measurement for this measurementNumer
|
||||||
|
toolGroupMeasurements.measurements.push(tableMeasurement);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return tableMeasurements;
|
||||||
|
}
|
||||||
|
|
||||||
function convertTimepointsToTableData(timepoints) {
|
function convertTimepointsToTableData(timepoints) {
|
||||||
if (!timepoints || !timepoints.length) {
|
if (!timepoints || !timepoints.length) {
|
||||||
return [];
|
return [];
|
||||||
@ -15,41 +108,11 @@ function convertTimepointsToTableData(timepoints) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertMeasurementsToTableData(measurements) {
|
|
||||||
const tableData = [
|
|
||||||
{
|
|
||||||
groupName: 'Measurements',
|
|
||||||
measurements: [],
|
|
||||||
measurements1: measurements
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
if (measurements && measurements.allTools) {
|
|
||||||
measurements.allTools.forEach(measurement => {
|
|
||||||
const tableMeasurement = {
|
|
||||||
label: '...',
|
|
||||||
hasWarnings: false,
|
|
||||||
warningTitle: '',
|
|
||||||
isSplitLesion: false,
|
|
||||||
warningList: [],
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
displayText: '...'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
tableData[0].measurements.push(tableMeasurement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return tableData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const { timepoints, measurements } = state.timepointManager;
|
const { timepoints, measurements } = state.timepointManager;
|
||||||
return {
|
return {
|
||||||
timepoints: convertTimepointsToTableData(timepoints),
|
timepoints: convertTimepointsToTableData(timepoints),
|
||||||
measurementCollection: convertMeasurementsToTableData(measurements)
|
measurementCollection: convertMeasurementsToTableData(measurements, timepoints)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Viewer from './Viewer.js';
|
import Viewer from './Viewer.js';
|
||||||
import actions from '../redux/actions.js';
|
import OHIF from 'ohif-core';
|
||||||
|
|
||||||
|
const { setTimepoints, setMeasurements } = OHIF.redux.actions;
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
const mapDispatchToProps = dispatch => {
|
||||||
return {
|
return {
|
||||||
onTimepointsUpdated: timepoints => {
|
onTimepointsUpdated: timepoints => {
|
||||||
dispatch(actions.setTimepoints(timepoints));
|
dispatch(setTimepoints(timepoints));
|
||||||
},
|
},
|
||||||
onMeasurementsUpdated: measurements => {
|
onMeasurementsUpdated: measurements => {
|
||||||
dispatch(actions.setMeasurements(measurements));
|
dispatch(setMeasurements(measurements));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import WhiteLabellingContext from '../WhiteLabellingContext.js';
|
|||||||
import ConnectedHeader from './ConnectedHeader.js';
|
import ConnectedHeader from './ConnectedHeader.js';
|
||||||
import ConnectedFlexboxLayout from './ConnectedFlexboxLayout.js';
|
import ConnectedFlexboxLayout from './ConnectedFlexboxLayout.js';
|
||||||
import ConnectedToolbarRow from './ConnectedToolbarRow.js';
|
import ConnectedToolbarRow from './ConnectedToolbarRow.js';
|
||||||
import measurementTools from '../measurementTools';
|
|
||||||
import './Viewer.css';
|
import './Viewer.css';
|
||||||
/**
|
/**
|
||||||
* Inits OHIF Hanging Protocol's onReady.
|
* Inits OHIF Hanging Protocol's onReady.
|
||||||
@ -57,9 +56,7 @@ class Viewer extends Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
OHIF.measurements.MeasurementApi.setConfiguration({
|
OHIF.measurements.MeasurementApi.setConfiguration({
|
||||||
measurementTools,
|
|
||||||
dataExchange: {
|
dataExchange: {
|
||||||
retrieve: this.retrieveMeasurements,
|
retrieve: this.retrieveMeasurements,
|
||||||
store: this.storeMeasurements
|
store: this.storeMeasurements
|
||||||
@ -169,10 +166,7 @@ class Viewer extends Component {
|
|||||||
|
|
||||||
const patientId = studies[0] && studies[0].patientId;
|
const patientId = studies[0] && studies[0].patientId;
|
||||||
timepointApi.retrieveTimepoints({ patientId });
|
timepointApi.retrieveTimepoints({ patientId });
|
||||||
|
|
||||||
// TODO: Retrieve measurements and sync them with tool data
|
|
||||||
measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
|
measurementApi.retrieveMeasurements(patientId, [currentTimepointId]);
|
||||||
//measurementApi.syncMeasurementsAndToolData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
const displayFunction = data => {
|
|
||||||
return data.text || '';
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
id: 'ArrowAnnotate',
|
|
||||||
name: 'ArrowAnnotate',
|
|
||||||
toolGroup: 'allTools',
|
|
||||||
cornerstoneToolType: 'ArrowAnnotate',
|
|
||||||
options: {
|
|
||||||
measurementTable: {
|
|
||||||
displayFunction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -13,22 +13,10 @@ export const setUserPreferencesModalOpen = state => ({
|
|||||||
state
|
state
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setTimepoints = state => ({
|
|
||||||
type: 'SET_TIMEPOINTS',
|
|
||||||
state
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setMeasurements = state => ({
|
|
||||||
type: 'SET_MEASUREMENTS',
|
|
||||||
state
|
|
||||||
});
|
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
setLeftSidebarOpen,
|
setLeftSidebarOpen,
|
||||||
setRightSidebarOpen,
|
setRightSidebarOpen,
|
||||||
setUserPreferencesModalOpen,
|
setUserPreferencesModalOpen,
|
||||||
setTimepoints,
|
|
||||||
setMeasurements
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
const defaultState = {
|
|
||||||
timepoints: [],
|
|
||||||
measurements: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const timepointManager = (state = defaultState, action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'SET_TIMEPOINTS':
|
|
||||||
return Object.assign({}, state, { timepoints: action.state });
|
|
||||||
case 'SET_MEASUREMENTS':
|
|
||||||
return Object.assign({}, state, { measurements: action.state });
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default timepointManager;
|
|
||||||
Loading…
Reference in New Issue
Block a user