diff --git a/extensions/measurement-tracking/src/getViewportModule.js b/extensions/measurement-tracking/src/getViewportModule.js index 65203872e..5991db1f0 100644 --- a/extensions/measurement-tracking/src/getViewportModule.js +++ b/extensions/measurement-tracking/src/getViewportModule.js @@ -14,17 +14,17 @@ const OHIFCornerstoneViewport = props => { function getViewportModule({ servicesManager }) { const ExtendedOHIFCornerstoneSRViewport = props => { - const { ToolBarService } = servicesManager.services; - return ( - + ); }; - return [{ name: 'cornerstone-tracked', component: ExtendedOHIFCornerstoneSRViewport }]; + return [ + { + name: 'cornerstone-tracked', + component: ExtendedOHIFCornerstoneSRViewport, + }, + ]; } export default getViewportModule; diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 35a1ee8ef..8c4a2d61b 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -44,8 +44,9 @@ function TrackedCornerstoneViewport({ dataSource, displaySet, viewportIndex, - ToolBarService, + servicesManager, }) { + const { ToolBarService, DisplaySetService } = servicesManager.services; const [trackedMeasurements] = useTrackedMeasurements(); const [ { activeViewportIndex, viewports }, @@ -55,7 +56,10 @@ function TrackedCornerstoneViewport({ const [viewportDialogState, viewportDialogApi] = useViewportDialog(); const [viewportData, setViewportData] = useState(null); const [element, setElement] = useState(null); + const [isTracked, setIsTracked] = useState(false); + const [trackedMeasurementId, setTrackedMeasurementId] = useState(null); + // TODO: Still needed? Better way than import `OHIF` and destructure? // Why is this managed by `core`? useEffect(() => { @@ -246,14 +250,56 @@ function TrackedCornerstoneViewport({ ? _viewportLabels[firstViewportIndexWithMatchingDisplaySetUid] : ''; + function switchMeasurement(direction) { + if (!element) { + // Element not yet enabled. + return; + } + + const newTrackedMeasurementId = _getNextMeasurementId( + direction, + servicesManager, + trackedMeasurementId, + trackedMeasurements + ); + + if (!newTrackedMeasurementId) { + return; + } + + setTrackedMeasurementId(newTrackedMeasurementId); + + const { MeasurementService } = servicesManager.services; + const measurements = MeasurementService.getMeasurements(); + const measurement = measurements.find( + m => m.id === newTrackedMeasurementId + ); + + const { + displaySetInstanceUID, + imageIndex, + } = _getViewportDataFromTrackedMeasurementId( + measurement, + DisplaySetService + ); + + viewportGridService.setDisplaysetForViewport({ + viewportIndex, + displaySetInstanceUID, + imageIndex, + }); + } + + const showNavArrows = isTracked && viewportIndex === activeViewportIndex; + return ( <> alert(`Series ${direction}`)} - showNavArrows={viewportIndex === activeViewportIndex} + onSeriesChange={direction => switchMeasurement(direction)} + showNavArrows={showNavArrows} studyData={{ label, - isTracked: trackedSeries.includes(SeriesInstanceUID), + isTracked, isLocked: false, studyDate: formatDate(SeriesDate), // TODO: This is series date. Is that ok? currentSeries: SeriesNumber, @@ -270,8 +316,8 @@ function TrackedCornerstoneViewport({ spacing: PixelSpacing && PixelSpacing.length ? `${PixelSpacing[0].toFixed(2)}mm x ${PixelSpacing[1].toFixed( - 2 - )}mm` + 2 + )}mm` : '', scanner: ManufacturerModelName || '', }, @@ -379,4 +425,79 @@ async function _getViewportData(dataSource, displaySet) { return viewportData; } +function _getNextMeasurementId( + direction, + servicesManager, + trackedMeasurementId, + trackedMeasurements +) { + const { MeasurementService } = servicesManager.services; + const measurements = MeasurementService.getMeasurements(); + + const { trackedSeries } = trackedMeasurements.context; + + // Get the potentially trackable measurements for this series, + // The measurements to jump between are the same + // regardless if this series is tracked or not. + + const filteredMeasurements = measurements.filter(m => + trackedSeries.includes(m.referenceSeriesUID) + ); + + if (!filteredMeasurements.length) { + // No measurements on this series. + return; + } + + const measurementCount = filteredMeasurements.length; + + const ids = filteredMeasurements.map(fm => fm.id); + let measurementIndex = ids.findIndex(id => id === trackedMeasurementId); + + if (measurementIndex === -1) { + // Not tracking a measurement, or previous measurement now deleted, revert to 0. + measurementIndex = 0; + } else { + if (direction === 'left') { + measurementIndex--; + + if (measurementIndex < 0) { + measurementIndex = measurementCount - 1; + } + } else if (direction === 'right') { + measurementIndex++; + + if (measurementIndex === measurementCount) { + measurementIndex = 0; + } + } + } + + const newTrackedMeasurementId = ids[measurementIndex]; + + return newTrackedMeasurementId; +} + +function _getViewportDataFromTrackedMeasurementId( + measurement, + DisplaySetService +) { + const { referenceSeriesUID: SeriesInstanceUID, SOPInstanceUID } = measurement; + + const activeDisplaySets = DisplaySetService.getActiveDisplaySets(); + + const displaySet = activeDisplaySets.find( + ds => ds.SeriesInstanceUID === SeriesInstanceUID + ); + + const imageIndex = displaySet.images.findIndex( + image => image.SOPInstanceUID === SOPInstanceUID + ); + + return { + imageIndex, + displaySetInstanceUID: displaySet.displaySetInstanceUID, + }; +} + export default TrackedCornerstoneViewport;