diff --git a/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js b/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js index ad1cf72f9..2a1c80254 100644 --- a/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js +++ b/extensions/dicom-sr/src/OHIFCornerstoneSRViewport.js @@ -1,27 +1,12 @@ -import React, { Component } from 'react'; -import CornerstoneViewport from 'react-cornerstone-viewport'; -//import ConnectedCornerstoneViewport from './ConnectedCornerstoneViewport'; -import OHIF from '@ohif/core'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import cornerstone from 'cornerstone-core'; -import debounce from 'lodash.debounce'; -import throttle from 'lodash.throttle'; +import cornerstoneTools from 'cornerstone-tools'; -// const { -// onAdded, -// onRemoved, -// onModified, -// } = OHIF.measurements.MeasurementHandlers; - -// // 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), -// }; +import CornerstoneViewport from 'react-cornerstone-viewport'; +import OHIF from '@ohif/core'; +import { ViewportActionBar, useViewportGrid } from '@ohif/ui'; +import TOOL_NAMES from './constants/toolNames'; +import id from './id'; // const cine = viewportSpecificData.cine; @@ -30,96 +15,53 @@ import throttle from 'lodash.throttle'; const { StackManager } = OHIF.utils; -class OHIFCornerstoneViewport extends Component { - state = { - viewportData: null, - }; +function OHIFCornerstoneSRViewport({ + children, + dataSource, + displaySet, + 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); - static defaultProps = { - customProps: {}, - }; + const { viewports } = viewportGrid; - static propTypes = { - displaySet: PropTypes.object, - viewportIndex: PropTypes.number, - dataSource: PropTypes.object, - children: PropTypes.node, - customProps: PropTypes.object, - }; + const onElementEnabled = evt => { + const eventData = evt.detail; + const { element } = eventData; - static name = 'OHIFCornerstoneViewport'; + const { measurements } = displaySet; - static init() { - console.log('OHIFCornerstoneViewport init()'); - } + const srModule = cornerstoneTools.getModule(id); - 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, - dataSource + srModule.setters.trackingUniqueIdentifiersForElement( + element, + measurements.map(measurement => measurement.TrackingUniqueIdentifier), + measurementSelected ); - viewportData = { - StudyInstanceUID: displaySet.StudyInstanceUID, - displaySetInstanceUID: displaySet.displaySetInstanceUID, - stack, - }; - - return viewportData; + setElement(element); }; - setStateFromProps() { - const { displaySet } = this.props; + useEffect(() => { + const numMeasurements = displaySet.measurements.length; + + console.log(`MEASUREMENT COUNT: ${numMeasurements}`); + + setMeasurementCount(numMeasurements); + }, [ + dataSource, + displaySet, + displaySet.StudyInstanceUID, + displaySet.displaySetInstanceUID, + ]); + + const updateViewport = () => { const { StudyInstanceUID, displaySetInstanceUID, @@ -136,97 +78,219 @@ class OHIFCornerstoneViewport extends Component { ); } - this.getViewportData(displaySet).then(viewportData => { - this.setState({ - viewportData, - }); + console.log(measurementSelected); + + _getViewportData( + dataSource, + displaySet, + measurementSelected, + DisplaySetService, + element + ).then(viewportData => { + setViewportData({ ...viewportData }); + }); + }; + + useEffect(() => { + updateViewport(); + }, [ + dataSource, + displaySet, + displaySet.StudyInstanceUID, + displaySet.displaySetInstanceUID, + measurementSelected, + ]); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + let childrenWithProps = null; + + if (!viewportData) { + return null; + } + + const { + imageIds, + currentImageIdIndex, + // If this comes from the instance, would be a better default + // `FrameTime` in the instance + // frameRate = 0, + } = viewportData.stack; + + // TODO: Does it make more sense to use Context? + if (children && children.length) { + childrenWithProps = children.map((child, index) => { + return ( + child && + React.cloneElement(child, { + viewportIndex, + key: index, + }) + ); }); } - componentDidMount() { - this.setStateFromProps(); - } + const { + Modality, + SeriesDate, + SeriesDescription, + SeriesInstanceUID, + SeriesNumber, + } = displaySet; - componentDidUpdate(prevProps) { - const { displaySet } = this.props; - const prevDisplaySet = prevProps.displaySet; + // TODO -> Get this from the associated stack. - if ( - displaySet.displaySetInstanceUID !== - prevDisplaySet.displaySetInstanceUID || - displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID || - displaySet.frameIndex !== prevDisplaySet.frameIndex - ) { - this.setStateFromProps(); - } - } + const { + PatientID, + PatientName, + PatientSex, + PatientAge, + SliceThickness, + } = activeDisplaySetData; - render() { - let childrenWithProps = null; + const onMeasurementChange = direction => { + let newMeausrementSelected = measurementSelected; - if (!this.state.viewportData) { - return null; - } - const { viewportIndex } = this.props; - const { - imageIds, - currentImageIdIndex, - // If this comes from the instance, would be a better default - // `FrameTime` in the instance - // frameRate = 0, - } = this.state.viewportData.stack; + if (direction === 'right') { + newMeausrementSelected++; - // TODO: Does it make more sense to use Context? - if (this.props.children && this.props.children.length) { - childrenWithProps = this.props.children.map((child, index) => { - return ( - child && - React.cloneElement(child, { - viewportIndex: this.props.viewportIndex, - key: index, - }) - ); - }); + if (newMeausrementSelected >= measurementCount) { + newMeausrementSelected = 0; + } + } else { + newMeausrementSelected--; + + if (newMeausrementSelected < 0) { + newMeausrementSelected = measurementCount - 1; + } } - const debouncedNewImageHandler = debounce( - ({ currentImageIdIndex, sopInstanceUid }) => { - const { displaySet } = this.props; - const { StudyInstanceUID } = displaySet; - if (currentImageIdIndex > 0) { - this.props.onNewImage({ - StudyInstanceUID, - SOPInstanceUID: sopInstanceUid, - frameIndex: currentImageIdIndex, - activeViewportIndex: viewportIndex, - }); - } - }, - 700 - ); + if (newMeausrementSelected === measurementSelected) { + updateViewport(); + } - // TODO -> We may still want a wrapped component to define all the measurement api stuff. + setMeasurementSelected(newMeausrementSelected); + }; - return ( - <> - - {childrenWithProps} - - ); - } + console.log(currentImageIdIndex); + + return ( + <> + + + {childrenWithProps} + + ); } -const temp = () =>
Hello SR Viewport!
; +OHIFCornerstoneSRViewport.propTypes = { + displaySet: PropTypes.object.isRequired, + viewportIndex: PropTypes.number.isRequired, + dataSource: PropTypes.object, + children: PropTypes.node, + customProps: PropTypes.object, +}; -//export default OHIFCornerstoneViewport; -export default temp; +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 + ); + } + + return stack; +} + +async function _getViewportData( + 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; diff --git a/extensions/dicom-sr/src/getSopClassHandlerModule.js b/extensions/dicom-sr/src/getSopClassHandlerModule.js index 79e27edac..561fdba6a 100644 --- a/extensions/dicom-sr/src/getSopClassHandlerModule.js +++ b/extensions/dicom-sr/src/getSopClassHandlerModule.js @@ -78,6 +78,7 @@ function _getDisplaySetsFromSeries( SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, referencedImages: _getReferencedImagesList(ContentSequence), measurements: _getMeasurements(ContentSequence), + sopClassUids, }; console.log(DisplaySetService); @@ -386,8 +387,6 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { TrackingUniqueIdentifier: UIDREFContentItem.UID, }; - debugger; - NUMContentItems.forEach(item => { const { ConceptNameCodeSequence, diff --git a/extensions/dicom-sr/src/index.js b/extensions/dicom-sr/src/index.js index 17e7a43a1..8a2a6d98a 100644 --- a/extensions/dicom-sr/src/index.js +++ b/extensions/dicom-sr/src/index.js @@ -34,13 +34,15 @@ export default { * @param {object} [configuration={}] * @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools` */ - getViewportModule({ commandsManager }) { + getViewportModule({ servicesManager }) { const ExtendedOHIFCornerstoneSRViewport = props => { - const onNewImageHandler = jumpData => { - commandsManager.runCommand('jumpToImage', jumpData); - }; + const { DisplaySetService } = servicesManager.services; + return ( - + ); }; diff --git a/extensions/dicom-sr/src/init.js b/extensions/dicom-sr/src/init.js index 5734103f0..03d203167 100644 --- a/extensions/dicom-sr/src/init.js +++ b/extensions/dicom-sr/src/init.js @@ -1,5 +1,7 @@ import cornerstoneTools from 'cornerstone-tools'; import DICOMSRDisplayTool from './tools/DICOMSRDisplayTool'; +import dicomSRModule from './tools/modules/dicomSRModule'; +import id from './id'; 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; + cornerstoneTools.register('module', id, dicomSRModule); cornerstoneTools.addTool(DICOMSRDisplayTool); + cornerstoneTools.setToolEnabled(TOOL_NAMES.DICOM_SR_DISPLAY_TOOL); } diff --git a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js index bc36c2dcf..cc0c8a51f 100644 --- a/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js +++ b/extensions/dicom-sr/src/tools/DICOMSRDisplayTool.js @@ -2,6 +2,7 @@ import { importInternal, getToolState, toolColors } from 'cornerstone-tools'; import TOOL_NAMES from '../constants/toolNames'; import SCOORD_TYPES from '../constants/scoordTypes'; +import id from '../id'; // Cornerstone 3rd party dev kit imports const draw = importInternal('drawing/draw'); @@ -25,11 +26,14 @@ export default class DICOMSRDisplayTool extends BaseTool { const initialProps = Object.assign(defaultProps, props); super(initialProps); + + this._module = cornerstoneTools.getModule(id); } renderToolData(evt) { const eventData = evt.detail; const { element } = eventData; + const module = this._module; const toolState = getToolState(element, this.name); @@ -37,36 +41,66 @@ export default class DICOMSRDisplayTool extends BaseTool { return; } - const options = { - color: toolColors.setToolColor(), - lineWidth: 2, - handleRadius: 6, - }; + const trackingUniqueIdentifiersForElement = module.getters.trackingUniqueIdentifiersForElement( + element + ); - for (let i = 0; i < toolState.data.length; i++) { - const data = toolState.data[i]; + 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 = { + color, + lineWidth: 2, + handleRadius: 6, + }; + Object.keys(renderableData).forEach(GraphicType => { const renderableDataForGraphicType = renderableData[GraphicType]; switch (GraphicType) { case SCOORD_TYPES.POINT: case SCOORD_TYPES.MULTIPOINT: - renderPointOrMultipoint( + this.renderPointOrMultipoint( renderableDataForGraphicType, eventData, options ); break; case SCOORD_TYPES.POLYLINE: - renderPolyLine(renderableDataForGraphicType, eventData, options); + this.renderPolyLine( + renderableDataForGraphicType, + eventData, + options + ); break; case SCOORD_TYPES.CIRCLE: - renderCircle(renderableDataForGraphicType, eventData, options); + this.renderCircle(renderableDataForGraphicType, eventData, options); break; case SCOORD_TYPES.ELLIPSE: - renderEllipse(renderableDataForGraphicType, eventData, options); + this.renderEllipse( + renderableDataForGraphicType, + eventData, + options + ); break; } }); diff --git a/extensions/dicom-sr/src/tools/modules/dicomSRModule.js b/extensions/dicom-sr/src/tools/modules/dicomSRModule.js new file mode 100644 index 000000000..3fbb3c360 --- /dev/null +++ b/extensions/dicom-sr/src/tools/modules/dicomSRModule.js @@ -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, + }, +}; diff --git a/extensions/dicom-sr/src/utils/addMeasurement.js b/extensions/dicom-sr/src/utils/addMeasurement.js index d42858543..825769523 100644 --- a/extensions/dicom-sr/src/utils/addMeasurement.js +++ b/extensions/dicom-sr/src/utils/addMeasurement.js @@ -6,6 +6,8 @@ import { cornerstone } from '../../../../platform/core/src'; const globalImageIdSpecificToolStateManager = cornerstoneTools.globalImageIdSpecificToolStateManager; +console.log(globalImageIdSpecificToolStateManager); + export default function addMeasurement( measurement, imageId, @@ -18,8 +20,6 @@ export default function addMeasurement( // TODO -> Render rotated ellipse . - debugger; - const toolName = TOOL_NAMES.DICOM_SR_DISPLAY_TOOL; const measurementData = { @@ -62,8 +62,6 @@ export default function addMeasurement( measurement.imageId = imageId; measurement.displaySetInstanceUID = displaySetInstanceUID; delete measurement.coords; - - debugger; } function _getRenderableData(GraphicType, GraphicData) {