This commit is contained in:
James A. Petts 2020-06-10 09:09:33 +01:00
parent 83497795ca
commit c8b3e130d0
7 changed files with 366 additions and 204 deletions

View File

@ -1,27 +1,12 @@
import React, { Component } from 'react'; import React, { useEffect, useState } from 'react';
import CornerstoneViewport from 'react-cornerstone-viewport';
//import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport';
import OHIF from '@ohif/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cornerstone from 'cornerstone-core'; import cornerstoneTools from 'cornerstone-tools';
import debounce from 'lodash.debounce';
import throttle from 'lodash.throttle';
// const { import CornerstoneViewport from 'react-cornerstone-viewport';
// onAdded, import OHIF from '@ohif/core';
// onRemoved, import { ViewportActionBar, useViewportGrid } from '@ohif/ui';
// onModified, import TOOL_NAMES from './constants/toolNames';
// } = OHIF.measurements.MeasurementHandlers; import id from './id';
// // TODO: Transition to enums for the action names so that we can ensure they stay up to date
// // everywhere they're used.
// const MEASUREMENT_ACTION_MAP = {
// added: onAdded,
// removed: onRemoved,
// modified: throttle(event => {
// return onModified(event);
// }, 300),
// };
// const cine = viewportSpecificData.cine; // const cine = viewportSpecificData.cine;
@ -30,96 +15,53 @@ import throttle from 'lodash.throttle';
const { StackManager } = OHIF.utils; const { StackManager } = OHIF.utils;
class OHIFCornerstoneViewport extends Component { function OHIFCornerstoneSRViewport({
state = { children,
viewportData: null, dataSource,
};
static defaultProps = {
customProps: {},
};
static propTypes = {
displaySet: PropTypes.object,
viewportIndex: PropTypes.number,
dataSource: PropTypes.object,
children: PropTypes.node,
customProps: PropTypes.object,
};
static name = 'OHIFCornerstoneViewport';
static init() {
console.log('OHIFCornerstoneViewport init()');
}
static destroy() {
console.log('OHIFCornerstoneViewport destroy()');
StackManager.clearStacks();
}
/**
* Obtain the CornerstoneTools Stack for the specified display set.
*
* @param {Object} displaySet
* @param {Object} dataSource
* @return {Object} CornerstoneTools Stack
*/
static getCornerstoneStack(displaySet, dataSource) {
const { frameIndex } = displaySet;
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(displaySet, dataSource);
// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);
stack.currentImageIdIndex = frameIndex;
// TODO -> Do we ever use this like this?
// if (SOPInstanceUID) {
// const index = stack.imageIds.findIndex(imageId => {
// const imageIdSOPInstanceUID = cornerstone.metaData.get(
// 'SOPInstanceUID',
// imageId
// );
// return imageIdSOPInstanceUID === SOPInstanceUID;
// });
// if (index > -1) {
// stack.currentImageIdIndex = index;
// } else {
// console.warn(
// 'SOPInstanceUID provided was not found in specified DisplaySet'
// );
// }
// }
return stack;
}
getViewportData = async displaySet => {
let viewportData;
const { dataSource } = this.props;
const stack = OHIFCornerstoneViewport.getCornerstoneStack(
displaySet, displaySet,
dataSource viewportIndex,
DisplaySetService,
}) {
const [viewportGrid, dispatchViewportGrid] = useViewportGrid();
const [measurementSelected, setMeasurementSelected] = useState(0);
const [measurementCount, setMeasurementCount] = useState(1);
const [viewportData, setViewportData] = useState(null);
const [activeDisplaySetData, setActiveDisplaySetData] = useState({});
const [element, setElement] = useState(null);
const { viewports } = viewportGrid;
const onElementEnabled = evt => {
const eventData = evt.detail;
const { element } = eventData;
const { measurements } = displaySet;
const srModule = cornerstoneTools.getModule(id);
srModule.setters.trackingUniqueIdentifiersForElement(
element,
measurements.map(measurement => measurement.TrackingUniqueIdentifier),
measurementSelected
); );
viewportData = { setElement(element);
StudyInstanceUID: displaySet.StudyInstanceUID,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
stack,
}; };
return viewportData; useEffect(() => {
}; const numMeasurements = displaySet.measurements.length;
setStateFromProps() { console.log(`MEASUREMENT COUNT: ${numMeasurements}`);
const { displaySet } = this.props;
setMeasurementCount(numMeasurements);
}, [
dataSource,
displaySet,
displaySet.StudyInstanceUID,
displaySet.displaySetInstanceUID,
]);
const updateViewport = () => {
const { const {
StudyInstanceUID, StudyInstanceUID,
displaySetInstanceUID, displaySetInstanceUID,
@ -136,84 +78,129 @@ class OHIFCornerstoneViewport extends Component {
); );
} }
this.getViewportData(displaySet).then(viewportData => { console.log(measurementSelected);
this.setState({
viewportData, _getViewportData(
dataSource,
displaySet,
measurementSelected,
DisplaySetService,
element
).then(viewportData => {
setViewportData({ ...viewportData });
}); });
}); };
}
componentDidMount() { useEffect(() => {
this.setStateFromProps(); updateViewport();
} }, [
dataSource,
displaySet,
displaySet.StudyInstanceUID,
displaySet.displaySetInstanceUID,
measurementSelected,
]);
componentDidUpdate(prevProps) { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const { displaySet } = this.props;
const prevDisplaySet = prevProps.displaySet;
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
this.setStateFromProps();
}
}
render() {
let childrenWithProps = null; let childrenWithProps = null;
if (!this.state.viewportData) { if (!viewportData) {
return null; return null;
} }
const { viewportIndex } = this.props;
const { const {
imageIds, imageIds,
currentImageIdIndex, currentImageIdIndex,
// If this comes from the instance, would be a better default // If this comes from the instance, would be a better default
// `FrameTime` in the instance // `FrameTime` in the instance
// frameRate = 0, // frameRate = 0,
} = this.state.viewportData.stack; } = viewportData.stack;
// TODO: Does it make more sense to use Context? // TODO: Does it make more sense to use Context?
if (this.props.children && this.props.children.length) { if (children && children.length) {
childrenWithProps = this.props.children.map((child, index) => { childrenWithProps = children.map((child, index) => {
return ( return (
child && child &&
React.cloneElement(child, { React.cloneElement(child, {
viewportIndex: this.props.viewportIndex, viewportIndex,
key: index, key: index,
}) })
); );
}); });
} }
const debouncedNewImageHandler = debounce( const {
({ currentImageIdIndex, sopInstanceUid }) => { Modality,
const { displaySet } = this.props; SeriesDate,
const { StudyInstanceUID } = displaySet; SeriesDescription,
if (currentImageIdIndex > 0) { SeriesInstanceUID,
this.props.onNewImage({ SeriesNumber,
StudyInstanceUID, } = displaySet;
SOPInstanceUID: sopInstanceUid,
frameIndex: currentImageIdIndex,
activeViewportIndex: viewportIndex,
});
}
},
700
);
// TODO -> We may still want a wrapped component to define all the measurement api stuff. // TODO -> Get this from the associated stack.
const {
PatientID,
PatientName,
PatientSex,
PatientAge,
SliceThickness,
} = activeDisplaySetData;
const onMeasurementChange = direction => {
let newMeausrementSelected = measurementSelected;
if (direction === 'right') {
newMeausrementSelected++;
if (newMeausrementSelected >= measurementCount) {
newMeausrementSelected = 0;
}
} else {
newMeausrementSelected--;
if (newMeausrementSelected < 0) {
newMeausrementSelected = measurementCount - 1;
}
}
if (newMeausrementSelected === measurementSelected) {
updateViewport();
}
setMeasurementSelected(newMeausrementSelected);
};
console.log(currentImageIdIndex);
return ( return (
<> <>
<ViewportActionBar
onSeriesChange={onMeasurementChange}
studyData={{
label: '',
isTracked: false,
isLocked: false,
studyDate: SeriesDate, // TODO: This is series date. Is that ok?
currentSeries: SeriesNumber,
seriesDescription: SeriesDescription,
modality: Modality,
patientInformation: {
patientName: PatientName ? PatientName.Alphabetic || '' : '',
patientSex: PatientSex || '',
patientAge: PatientAge || '',
MRN: PatientID || '',
thickness: `${SliceThickness}mm`,
spacing: '',
scanner: '',
},
}}
/>
<CornerstoneViewport <CornerstoneViewport
onElementEnabled={onElementEnabled}
viewportIndex={viewportIndex} viewportIndex={viewportIndex}
imageIds={imageIds} imageIds={imageIds}
imageIdIndex={currentImageIdIndex} imageIdIndex={currentImageIdIndex}
onNewImage={debouncedNewImageHandler}
// TODO: ViewportGrid Context? // TODO: ViewportGrid Context?
isActive={true} // todo isActive={true} // todo
isStackPrefetchEnabled={true} // todo isStackPrefetchEnabled={true} // todo
@ -224,9 +211,86 @@ class OHIFCornerstoneViewport extends Component {
</> </>
); );
} }
OHIFCornerstoneSRViewport.propTypes = {
displaySet: PropTypes.object.isRequired,
viewportIndex: PropTypes.number.isRequired,
dataSource: PropTypes.object,
children: PropTypes.node,
customProps: PropTypes.object,
};
OHIFCornerstoneSRViewport.defaultProps = {
customProps: {},
};
/**
* Obtain the CornerstoneTools Stack for the specified display set.
*
* @param {Object} displaySet
* @param {Object} dataSource
* @return {Object} CornerstoneTools Stack
*/
function _getCornerstoneStack(
measurement,
dataSource,
DisplaySetService,
element
) {
const { displaySetInstanceUID, TrackingUniqueIdentifier } = measurement;
const displaySet = DisplaySetService.getDisplaySetByUID(
displaySetInstanceUID
);
// Get stack from Stack Manager
const storedStack = StackManager.findOrCreateStack(displaySet, dataSource);
// Clone the stack here so we don't mutate it
const stack = Object.assign({}, storedStack);
const { imageId } = measurement;
stack.currentImageIdIndex = stack.imageIds.findIndex(i => i === imageId);
if (element) {
const srModule = cornerstoneTools.getModule(id);
srModule.setters.activeTrackingUniqueIdentifierForElement(
element,
TrackingUniqueIdentifier
);
} }
const temp = () => <div>Hello SR Viewport!</div>; return stack;
}
//export default OHIFCornerstoneViewport; async function _getViewportData(
export default temp; dataSource,
displaySet,
measurementSelected,
DisplaySetService,
element
) {
let viewportData;
const { measurements } = displaySet;
const measurement = measurements[measurementSelected];
const stack = _getCornerstoneStack(
measurement,
dataSource,
DisplaySetService,
element
);
viewportData = {
StudyInstanceUID: displaySet.StudyInstanceUID,
displaySetInstanceUID: displaySet.displaySetInstanceUID,
stack,
};
return viewportData;
}
export default OHIFCornerstoneSRViewport;

View File

@ -78,6 +78,7 @@ function _getDisplaySetsFromSeries(
SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`,
referencedImages: _getReferencedImagesList(ContentSequence), referencedImages: _getReferencedImagesList(ContentSequence),
measurements: _getMeasurements(ContentSequence), measurements: _getMeasurements(ContentSequence),
sopClassUids,
}; };
console.log(DisplaySetService); console.log(DisplaySetService);
@ -386,8 +387,6 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) {
TrackingUniqueIdentifier: UIDREFContentItem.UID, TrackingUniqueIdentifier: UIDREFContentItem.UID,
}; };
debugger;
NUMContentItems.forEach(item => { NUMContentItems.forEach(item => {
const { const {
ConceptNameCodeSequence, ConceptNameCodeSequence,

View File

@ -34,13 +34,15 @@ export default {
* @param {object} [configuration={}] * @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools` * @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/ */
getViewportModule({ commandsManager }) { getViewportModule({ servicesManager }) {
const ExtendedOHIFCornerstoneSRViewport = props => { const ExtendedOHIFCornerstoneSRViewport = props => {
const onNewImageHandler = jumpData => { const { DisplaySetService } = servicesManager.services;
commandsManager.runCommand('jumpToImage', jumpData);
};
return ( return (
<OHIFCornerstoneSRViewport {...props} onNewImage={onNewImageHandler} /> <OHIFCornerstoneSRViewport
DisplaySetService={DisplaySetService}
{...props}
/>
); );
}; };

View File

@ -1,5 +1,7 @@
import cornerstoneTools from 'cornerstone-tools'; import cornerstoneTools from 'cornerstone-tools';
import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool'; import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool';
import dicomSRModule from './tools/modules/dicomSRModule';
import id from './id';
import TOOL_NAMES from './constants/toolNames'; import TOOL_NAMES from './constants/toolNames';
@ -17,5 +19,7 @@ export default function init({ configuration = {} }) {
TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = conifg.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL; TOOL_NAMES.DICOM_SR_DISPLAY_TOOL = conifg.TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
cornerstoneTools.register('module', id, dicomSRModule);
cornerstoneTools.addTool(DICOMSRDisplayTool); cornerstoneTools.addTool(DICOMSRDisplayTool);
cornerstoneTools.setToolEnabled(TOOL_NAMES.DICOM_SR_DISPLAY_TOOL);
} }

View File

@ -2,6 +2,7 @@ import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
import TOOL_NAMES from '../constants/toolNames'; import TOOL_NAMES from '../constants/toolNames';
import SCOORD_TYPES from '../constants/scoordTypes'; import SCOORD_TYPES from '../constants/scoordTypes';
import id from '../id';
// Cornerstone 3rd party dev kit imports // Cornerstone 3rd party dev kit imports
const draw = importInternal('drawing/draw'); const draw = importInternal('drawing/draw');
@ -25,11 +26,14 @@ export default class DICOMSRDisplayTool extends BaseTool {
const initialProps = Object.assign(defaultProps, props); const initialProps = Object.assign(defaultProps, props);
super(initialProps); super(initialProps);
this._module = cornerstoneTools.getModule(id);
} }
renderToolData(evt) { renderToolData(evt) {
const eventData = evt.detail; const eventData = evt.detail;
const { element } = eventData; const { element } = eventData;
const module = this._module;
const toolState = getToolState(element, this.name); const toolState = getToolState(element, this.name);
@ -37,36 +41,66 @@ export default class DICOMSRDisplayTool extends BaseTool {
return; return;
} }
const trackingUniqueIdentifiersForElement = module.getters.trackingUniqueIdentifiersForElement(
element
);
const {
activeIndex,
trackingUniqueIdentifiers,
} = trackingUniqueIdentifiersForElement;
const activeTrackingUniqueIdentifier =
trackingUniqueIdentifiers[activeIndex];
// Filter toolData to only render the data for the active SR.
const filteredToolData = toolState.data.filter(td =>
trackingUniqueIdentifiers.includes(td.TrackingUniqueIdentifier)
);
for (let i = 0; i < filteredToolData.length; i++) {
const data = filteredToolData[i];
const { renderableData } = data;
const color =
data.TrackingUniqueIdentifier === activeTrackingUniqueIdentifier
? toolColors.getActiveColor()
: toolColors.getToolColor();
const options = { const options = {
color: toolColors.setToolColor(), color,
lineWidth: 2, lineWidth: 2,
handleRadius: 6, handleRadius: 6,
}; };
for (let i = 0; i < toolState.data.length; i++) {
const data = toolState.data[i];
const { renderableData } = data;
Object.keys(renderableData).forEach(GraphicType => { Object.keys(renderableData).forEach(GraphicType => {
const renderableDataForGraphicType = renderableData[GraphicType]; const renderableDataForGraphicType = renderableData[GraphicType];
switch (GraphicType) { switch (GraphicType) {
case SCOORD_TYPES.POINT: case SCOORD_TYPES.POINT:
case SCOORD_TYPES.MULTIPOINT: case SCOORD_TYPES.MULTIPOINT:
renderPointOrMultipoint( this.renderPointOrMultipoint(
renderableDataForGraphicType, renderableDataForGraphicType,
eventData, eventData,
options options
); );
break; break;
case SCOORD_TYPES.POLYLINE: case SCOORD_TYPES.POLYLINE:
renderPolyLine(renderableDataForGraphicType, eventData, options); this.renderPolyLine(
renderableDataForGraphicType,
eventData,
options
);
break; break;
case SCOORD_TYPES.CIRCLE: case SCOORD_TYPES.CIRCLE:
renderCircle(renderableDataForGraphicType, eventData, options); this.renderCircle(renderableDataForGraphicType, eventData, options);
break; break;
case SCOORD_TYPES.ELLIPSE: case SCOORD_TYPES.ELLIPSE:
renderEllipse(renderableDataForGraphicType, eventData, options); this.renderEllipse(
renderableDataForGraphicType,
eventData,
options
);
break; break;
} }
}); });

View File

@ -0,0 +1,61 @@
import cornerstone from 'cornerstone-core';
const state = {
TrackingUniqueIdentifier: null,
trackingIdentifiersByEnabledElementUUID: {},
};
function setTrackingUniqueIdentifiersForElement(
element,
trackingUniqueIdentifiers,
activeIndex = 0
) {
const enabledElement = cornerstone.getEnabledElement(element);
const { uuid } = enabledElement;
state.trackingIdentifiersByEnabledElementUUID[uuid] = {
trackingUniqueIdentifiers,
activeIndex,
};
}
function setActiveTrackingUniqueIdentifierForElement(
element,
TrackingUniqueIdentifier
) {
const enabledElement = cornerstone.getEnabledElement(element);
const { uuid } = enabledElement;
const trackingIdentifiersForElement =
state.trackingIdentifiersByEnabledElementUUID[uuid];
if (trackingIdentifiersForElement) {
const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex(
tuid => tuid === TrackingUniqueIdentifier
);
trackingIdentifiersForElement.activeIndex = activeIndex;
}
}
function getTrackingUniqueIdentifiersForElement(element) {
const enabledElement = cornerstone.getEnabledElement(element);
const { uuid } = enabledElement;
if (state.trackingIdentifiersByEnabledElementUUID[uuid]) {
return state.trackingIdentifiersByEnabledElementUUID[uuid];
}
return { trackingUniqueIdentifiers: [] };
}
export default {
state,
getters: {
trackingUniqueIdentifiersForElement: getTrackingUniqueIdentifiersForElement,
},
setters: {
trackingUniqueIdentifiersForElement: setTrackingUniqueIdentifiersForElement,
activeTrackingUniqueIdentifierForElement: setActiveTrackingUniqueIdentifierForElement,
},
};

View File

@ -6,6 +6,8 @@ import { cornerstone } from '../../../../platform/core/src';
const globalImageIdSpecificToolStateManager = const globalImageIdSpecificToolStateManager =
cornerstoneTools.globalImageIdSpecificToolStateManager; cornerstoneTools.globalImageIdSpecificToolStateManager;
console.log(globalImageIdSpecificToolStateManager);
export default function addMeasurement( export default function addMeasurement(
measurement, measurement,
imageId, imageId,
@ -18,8 +20,6 @@ export default function addMeasurement(
// TODO -> Render rotated ellipse . // TODO -> Render rotated ellipse .
debugger;
const toolName = TOOL_NAMES.DICOM_SR_DISPLAY_TOOL; const toolName = TOOL_NAMES.DICOM_SR_DISPLAY_TOOL;
const measurementData = { const measurementData = {
@ -62,8 +62,6 @@ export default function addMeasurement(
measurement.imageId = imageId; measurement.imageId = imageId;
measurement.displaySetInstanceUID = displaySetInstanceUID; measurement.displaySetInstanceUID = displaySetInstanceUID;
delete measurement.coords; delete measurement.coords;
debugger;
} }
function _getRenderableData(GraphicType, GraphicData) { function _getRenderableData(GraphicType, GraphicData) {