354 lines
10 KiB
JavaScript
354 lines
10 KiB
JavaScript
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;
|
|
}, {});
|
|
}
|
|
|
|
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];
|
|
const measurementData = measurementNumberList[0];
|
|
const {
|
|
measurementNumber,
|
|
lesionNamingNumber,
|
|
toolType,
|
|
} = measurementData;
|
|
const measurementId = measurementData._id;
|
|
|
|
//check if all measurements with same measurementNumber will have same LABEL
|
|
const tableMeasurement = {
|
|
itemNumber: lesionNamingNumber,
|
|
label: getMeasurementText(measurementData),
|
|
measurementId,
|
|
measurementNumber,
|
|
lesionNamingNumber,
|
|
toolType,
|
|
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);
|
|
});
|
|
});
|
|
|
|
// Sort measurements by lesion naming number
|
|
tableMeasurements.forEach(tm => {
|
|
tm.measurements.sort((m1, m2) =>
|
|
m1.lesionNamingNumber > m2.lesionNamingNumber ? 1 : -1
|
|
);
|
|
});
|
|
|
|
return tableMeasurements;
|
|
}
|
|
|
|
function convertTimepointsToTableData(timepoints) {
|
|
if (!timepoints || !timepoints.length) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
{
|
|
label: 'Study date:',
|
|
date: moment(timepoints[0].latestDate).format('DD-MMM-YY'),
|
|
},
|
|
];
|
|
}
|
|
|
|
const mapStateToProps = state => {
|
|
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,
|
|
mapDispatchToProps,
|
|
mergeProps
|
|
)(MeasurementTable);
|
|
|
|
export default ConnectedMeasurementTable;
|