Add measurement table functions (#415)

* fix(measurements): Add measurement table functions
- Support for labeling measurements and adding measurement description
- Jump to measurement when clicked on measurement table

* fix(layout): Fix the issues with layout when switched with different plugins

* fix(measurements): Support for deleting measurements in measurement table

* fix(measurements): Add context menu for measurements

* fix(dependencies): Bump ohif-core to 0.4.2
This commit is contained in:
Evren Ozkan authored and GitHub committed 2019-05-02 17:12:06 -04:00
1 parent 0bab19985e
commit c9ddb23d9c
33 files changed
+2219 -151

No files matched your search

@@ -1,35 +1,41 @@
import { connect } from 'react-redux';
import { MeasurementTable } from 'react-viewerbase';
import OHIF from 'ohif-core';
import moment from 'moment';
import { connect } from 'react-redux'
import { MeasurementTable } from 'react-viewerbase'
import OHIF from 'ohif-core'
import moment from 'moment'
import cornerstone from 'cornerstone-core'
import jumpToRowItem from '../lib/jumpToRowItem.js'
import getMeasurementLocationCallback from '../lib/getMeasurementLocationCallback'
const { setViewportSpecificData } = OHIF.redux.actions;
const { MeasurementApi } = OHIF.measurements
function groupBy(list, props) {
return list.reduce((a, b) => {
(a[b[props]] = a[b[props]] || []).push(b);
return a;
}, {});
(a[b[props]] = a[b[props]] || []).push(b)
return a
}, {})
}
function getAllTools() {
const config = OHIF.measurements.MeasurementApi.getConfiguration();
let tools = [];
const config = OHIF.measurements.MeasurementApi.getConfiguration()
let tools = []
config.measurementTools.forEach(
toolGroup => (tools = tools.concat(toolGroup.childTools))
);
)
return tools;
return tools
}
function getMeasurementText(measurementData) {
const { location, description } = measurementData;
let text = '...';
const { location, description } = measurementData
let text = '...'
if (location) {
text = location;
text = location
if (description) {
text += `(${description})`;
text += `(${description})`
}
}
return text;
return text
}
function getDataForEachMeasurementNumber(
@@ -37,51 +43,62 @@ function getDataForEachMeasurementNumber(
timepoints,
displayFunction
) {
const data = [];
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);
displayText: '...',
}
data.push(eachData);
});
});
if (measurement.timepointId === timepoint.timepointId) {
eachData.displayText = displayFunction(measurement)
}
data.push(eachData)
})
})
return data;
return data
}
function convertMeasurementsToTableData(toolCollections, timepoints) {
const config = OHIF.measurements.MeasurementApi.getConfiguration();
const toolGroups = config.measurementTools;
const tools = getAllTools();
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: []
};
});
measurements: [],
}
})
Object.keys(toolCollections).forEach(toolId => {
const toolMeasurements = toolCollections[toolId];
const tool = tools.find(tool => tool.id === toolId);
const { displayFunction } = tool.options.measurementTable;
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');
const groupedMeasurements = groupBy(toolMeasurements, 'measurementNumber')
Object.keys(groupedMeasurements).forEach(groupedMeasurementsIndex => {
const measurementNumberList =
groupedMeasurements[groupedMeasurementsIndex];
groupedMeasurements[groupedMeasurementsIndex]
const {
measurementNumber,
lesionNamingNumber,
toolType,
} = measurementNumberList[0]
const measurementId = measurementNumberList[0]._id
//check if all measurements with same measurementNumber will have same LABEL
const tableMeasurement = {
measurementId: measurementNumberList[0]._id,
label: getMeasurementText(measurementNumberList[0]),
measurementId,
measurementNumber,
lesionNamingNumber,
toolType,
hasWarnings: false, //TODO
warningTitle: '', //TODO
isSplitLesion: false, //TODO
@@ -90,48 +107,239 @@ function convertMeasurementsToTableData(toolCollections, timepoints) {
measurementNumberList,
timepoints,
displayFunction
)
};
),
}
// find the group object for the tool
const toolGroupMeasurements = tableMeasurements.find(group => {
return group.groupId === tool.toolGroup;
});
return group.groupId === tool.toolGroup
})
// inject the new measurement for this measurementNumer
toolGroupMeasurements.measurements.push(tableMeasurement);
});
});
toolGroupMeasurements.measurements.push(tableMeasurement)
})
})
return tableMeasurements;
return tableMeasurements
}
function convertTimepointsToTableData(timepoints) {
if (!timepoints || !timepoints.length) {
return [];
return []
}
return [
{
label: 'Study date:',
date: moment(timepoints[0].latestDate).format('DD-MMM-YY')
}
];
date: moment(timepoints[0].latestDate).format('DD-MMM-YY'),
},
]
}
const mapStateToProps = state => {
const { timepoints, measurements } = state.timepointManager;
const { timepoints, measurements } = state.timepointManager
return {
timepoints: convertTimepointsToTableData(timepoints),
measurementCollection: convertMeasurementsToTableData(
measurements,
timepoints
)
};
};
),
timepointManager: state.timepointManager,
viewports: state.viewports,
}
}
const mapDispatchToProps = dispatch => {
return {
dispatchRelabel: (event, measurementData, viewportsState) => {
const activeViewportIndex =
(viewportsState && viewportsState.activeViewportIndex) || 0
const enabledElements = cornerstone.getEnabledElements()
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
OHIF.log.error('Failed to find the enabled element')
return
}
const { element } = enabledElements[activeViewportIndex]
const eventData = {
event: {
clientX: event.clientX,
clientY: event.clientY,
},
element,
}
const { toolType, measurementId } = measurementData
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
return measurement._id === measurementId
})
const options = {
skipAddLabelButton: true,
editLocation: true,
}
// Clone the tool not to set empty location initially
const toolForLocation = Object.assign({}, tool, { location: null })
getMeasurementLocationCallback(eventData, toolForLocation, options)
},
dispatchEditDescription: (event, measurementData, viewportsState) => {
const activeViewportIndex =
(viewportsState && viewportsState.activeViewportIndex) || 0
const enabledElements = cornerstone.getEnabledElements()
if (!enabledElements || enabledElements.length <= activeViewportIndex) {
OHIF.log.error('Failed to find the enabled element')
return
}
const { element } = enabledElements[activeViewportIndex]
const eventData = {
event: {
clientX: event.clientX,
clientY: event.clientY,
},
element,
}
const { toolType, measurementId } = measurementData
const tool = MeasurementApi.Instance.tools[toolType].find(measurement => {
return measurement._id === measurementId
})
const options = {
editDescriptionOnDialog: true,
}
getMeasurementLocationCallback(eventData, tool, options)
},
dispatchJumpToRowItem: (
measurementData,
viewportsState,
timepointManagerState,
options
) => {
const actionData = jumpToRowItem(
measurementData,
viewportsState,
timepointManagerState,
dispatch,
options
)
actionData.viewportSpecificData.forEach(viewportSpecificData => {
const { viewportIndex, displaySet } = viewportSpecificData;
dispatch(setViewportSpecificData(viewportIndex, displaySet));
})
const { toolType, measurementNumber } = measurementData
const measurementApi = MeasurementApi.Instance
Object.keys(measurementApi.tools).forEach(toolType => {
const measurements = measurementApi.tools[toolType]
measurements.forEach(measurement => {
measurement.active = false
})
})
const measurementsToActive = measurementApi.tools[toolType].filter(
measurement => {
return measurement.measurementNumber === measurementNumber
}
)
measurementsToActive.forEach(measurementToActive => {
measurementToActive.active = true
})
measurementApi.syncMeasurementsAndToolData()
cornerstone.getEnabledElements().forEach(enabledElement => {
cornerstone.updateImage(enabledElement.element)
})
// Needs to update viewports.layout state to set layout
//const layout = actionData.layout;
//dispatch(setLayout(layout))
// Needs to update viewports.activeViewportIndex to the first updated viewport
//const viewportIndex = actionData.viewportIndex;
//dispatch(setViewportActive(viewportIndex));
// Needs to update timepointsManager.measurements state to set active measurementId
// TODO: Not yet implemented
//dispatch(setActiveMeasurement(measurementData.measurementId))
// (later): Needs to set some property on state.extensions.cornerstone to synchronize viewport scrolling
},
}
}
const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
return {
timepoints: propsFromState.timepoints,
measurementCollection: propsFromState.measurementCollection,
selectedMeasurementNumber: ownProps.selectedMeasurementNumber,
...propsFromDispatch,
onItemClick: (event, measurementData) => {
// TODO: Add timepointId to .data for measurementData?
// TODO: Tooltype should be on the level below? This should
// provide the entire row item?
const viewportsState = propsFromState.viewports
const timepointManagerState = propsFromState.timepointManager
// TODO: invertViewportTimepointsOrder should be stored in / read from user preferences
// TODO: childToolKey should come from the measurement table when it supports child tools
const options = {
invertViewportTimepointsOrder: false,
childToolKey: null,
}
propsFromDispatch.dispatchJumpToRowItem(
measurementData,
viewportsState,
timepointManagerState,
options
)
},
onRelabelClick: (event, measurementData) => {
const viewportsState = propsFromState.viewports
propsFromDispatch.dispatchRelabel(event, measurementData, viewportsState)
},
onEditDescriptionClick: (event, measurementData) => {
const viewportsState = propsFromState.viewports
propsFromDispatch.dispatchEditDescription(
event,
measurementData,
viewportsState
)
},
onDeleteClick: (event, measurementData) => {
const { MeasurementHandlers } = OHIF.measurements;
MeasurementHandlers.onRemoved({
detail: {
toolType: measurementData.toolType,
measurementData: {
_id: measurementData.measurementId,
lesionNamingNumber: measurementData.lesionNamingNumber,
measurementNumber: measurementData.measurementNumber
}
}
});
}
}
}
const ConnectedMeasurementTable = connect(
mapStateToProps,
null
)(MeasurementTable);
mapDispatchToProps,
mergeProps
)(MeasurementTable)
export default ConnectedMeasurementTable;
export default ConnectedMeasurementTable