diff --git a/extensions/cornerstone-3d/src/Viewport/OHIFCornerstone3DViewport.tsx b/extensions/cornerstone-3d/src/Viewport/OHIFCornerstone3DViewport.tsx index 6620b1b070a..6ff6bafb4a1 100644 --- a/extensions/cornerstone-3d/src/Viewport/OHIFCornerstone3DViewport.tsx +++ b/extensions/cornerstone-3d/src/Viewport/OHIFCornerstone3DViewport.tsx @@ -1,21 +1,15 @@ import React, { useEffect, useRef, useCallback, useState } from 'react'; import ReactResizeDetector from 'react-resize-detector'; -import { useViewportGrid, ImageScrollbar } from '@ohif/ui'; -import OHIF from '@ohif/core'; +import PropTypes from 'prop-types'; +import { useViewportGrid } from '@ohif/ui'; import * as cs3DTools from '@cornerstonejs/tools'; import { Enums, eventTarget } from '@cornerstonejs/core'; -import PropTypes from 'prop-types'; - import { setEnabledElement } from '../state'; -import Cornerstone3DViewportService from '../services/ViewportService/Cornerstone3DViewportService'; -import CornerstoneOverlay from './CornerstoneOverlay'; -import ViewportLoadingIndicator from './ViewportLoadingIndicator'; -import ViewportOrientationMarkers from './ViewportOrientationMarkers'; +import Cornerstone3DCacheService from '../services/ViewportService/Cornerstone3DCacheService'; import './OHIFCornerstone3DViewport.css'; - -const { StackManager } = OHIF.utils; +import CornerstoneOverlays from './Overlays/CornerstoneOverlays'; const STACK = 'stack'; @@ -28,8 +22,6 @@ function areEqual(prevProps, nextProps) { return false; } - // Todo: handle fusion - // Todo: handle orientation const prevDisplaySets = prevProps.displaySets[0]; const nextDisplaySets = nextProps.displaySets[0]; @@ -67,9 +59,8 @@ const OHIFCornerstoneViewport = React.memo(props => { initialImageIdOrIndex, } = props; - const [viewportData, setViewportData] = useState(null); - const [imageIndex, setImageIndex] = useState(0); const [scrollbarHeight, setScrollbarHeight] = useState('100px'); + const [viewportData, setViewportData] = useState(null); const [_, viewportGridService] = useViewportGrid(); const elementRef = useRef(); @@ -79,6 +70,8 @@ const OHIFCornerstoneViewport = React.memo(props => { DisplaySetService, ToolBarService, ToolGroupService, + SyncGroupService, + Cornerstone3DViewportService, } = servicesManager.services; // useCallback for scroll bar height calculation @@ -103,7 +96,7 @@ const OHIFCornerstoneViewport = React.memo(props => { } const { viewportId, element } = evt.detail; - const viewportInfo = Cornerstone3DViewportService.getViewportInfoById( + const viewportInfo = Cornerstone3DViewportService.getViewportInfo( viewportId ); const viewportIndex = viewportInfo.getViewportIndex(); @@ -112,12 +105,20 @@ const OHIFCornerstoneViewport = React.memo(props => { const renderingEngineId = viewportInfo.getRenderingEngineId(); const toolGroupId = viewportInfo.getToolGroupId(); - ToolGroupService.addToolGroupViewport( + const syncGroups = viewportInfo.getSyncGroups(); + + ToolGroupService.addViewportToToolGroup( viewportId, renderingEngineId, toolGroupId ); + SyncGroupService.addViewportToSyncGroup( + viewportId, + renderingEngineId, + syncGroups + ); + if (onElementEnabled) { onElementEnabled(evt); } @@ -129,6 +130,7 @@ const OHIFCornerstoneViewport = React.memo(props => { useEffect(() => { Cornerstone3DViewportService.enableElement( viewportIndex, + viewportOptions, elementRef.current ); @@ -138,8 +140,25 @@ const OHIFCornerstoneViewport = React.memo(props => { ); setImageScrollBarHeight(); + return () => { + const viewportInfo = Cornerstone3DViewportService.getViewportInfoByIndex( + viewportIndex + ); + + const viewportId = viewportInfo.getViewportId(); + const renderingEngineId = viewportInfo.getRenderingEngineId(); + const syncGroups = viewportInfo.getSyncGroups(); + + ToolGroupService.disable(viewportId, renderingEngineId); + SyncGroupService.removeViewportFromSyncGroup( + viewportId, + renderingEngineId, + syncGroups + ); + Cornerstone3DViewportService.disableElement(viewportIndex); + eventTarget.removeEventListener( Enums.Events.ELEMENT_ENABLED, elementEnabledHandler @@ -147,40 +166,70 @@ const OHIFCornerstoneViewport = React.memo(props => { }; }, []); + // subscribe to displaySet metadata invalidation (updates) + // Currently, if the metadata changes we need to re-render the display set + // for it to take effect in the viewport. As we deal with scaling in the loading, + // we need to remove the old volume from the cache, and let the + // viewport to re-add it which will use the new metadata. Otherwise, the + // viewport will use the cached volume and the new metadata will not be used. + // Note: this approach does not actually end of sending network requests + // and it uses the network cache useEffect(() => { - const viewportData = _getViewportData( - dataSource, - displaySets, - viewportOptions.viewportType, - initialImageIdOrIndex + const { unsubscribe } = DisplaySetService.subscribe( + DisplaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, + async invalidatedDisplaySetInstanceUID => { + if ( + viewportData.displaySetInstanceUIDs.includes( + invalidatedDisplaySetInstanceUID + ) + ) { + const newViewportData = await Cornerstone3DCacheService.invalidateViewportData( + viewportData, + invalidatedDisplaySetInstanceUID, + dataSource, + DisplaySetService + ); + + Cornerstone3DViewportService.updateViewport( + viewportIndex, + newViewportData + ); + + setViewportData(newViewportData); + } + } ); + return () => { + unsubscribe(); + }; + }, [viewportData, viewportIndex]); - setViewportData(viewportData); - - Cornerstone3DViewportService.setViewportDisplaySets( - viewportIndex, - viewportData, - viewportOptions, - displaySetOptions - ); + useEffect(() => { + // handle the default viewportType to be stack + if (!viewportOptions.viewportType) { + viewportOptions.viewportType = STACK; + } - const element = elementRef.current; + const loadViewportData = async () => { + const viewportData = await Cornerstone3DCacheService.getViewportData( + viewportIndex, + displaySets, + viewportOptions.viewportType, + dataSource, + initialImageIdOrIndex + ); - const updateIndex = event => { - const { imageId } = event.detail; - // find the index of imageId in the imageIds - const index = viewportData.stack?.imageIds.indexOf(imageId); + Cornerstone3DViewportService.setViewportDisplaySets( + viewportIndex, + viewportData, + viewportOptions, + displaySetOptions + ); - if (index !== -1) { - setImageIndex(index); - } + setViewportData(viewportData); }; - element.addEventListener(Enums.Events.STACK_NEW_IMAGE, updateIndex); - - return () => { - element.removeEventListener(Enums.Events.STACK_NEW_IMAGE, updateIndex); - }; + loadViewportData(); }, [viewportOptions, displaySets, dataSource]); /** @@ -194,10 +243,6 @@ const OHIFCornerstoneViewport = React.memo(props => { * the cache for jumping to see if there is any jump queued, then we jump to the correct slice. */ useEffect(() => { - if (!viewportData) { - return; - } - const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents( MeasurementService, DisplaySetService, @@ -219,34 +264,7 @@ const OHIFCornerstoneViewport = React.memo(props => { return () => { unsubscribeFromJumpToMeasurementEvents(); }; - }, [displaySets, elementRef, viewportIndex, viewportData]); - - const onImageScrollbarChange = useCallback( - (imageIndex, viewportIndex) => { - const viewportInfo = Cornerstone3DViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const viewportId = viewportInfo.getViewportId(); - const viewport = Cornerstone3DViewportService.getCornerstone3DViewport( - viewportId - ); - - // if getCurrentImageId is not a method on viewport - if (!viewport.getCurrentImageId) { - throw new Error('cannot use scrollbar for non-stack viewports'); - } - - // Later scrollThroughStack should return two values the current index - // and the total number of indices (volume it is different) - viewport.setImageIdIndex(imageIndex).then(() => { - // Update scrollbar index - const currentIndex = viewport.getCurrentImageIdIndex(); - setImageIndex(currentIndex); - }); - }, - [viewportIndex, viewportData] - ); + }, [displaySets, elementRef, viewportIndex]); return (