Skip to main content
Version: 3.9.0-beta.44 (Latest)

Module: Viewport

Overview​

Viewports consume a displaySet and display/allow the user to interact with data. An extension can register a Viewport Module by defining a getViewportModule method that returns a React component. Currently, we use viewport components to add support for:

  • 2D Medical Image Viewing (cornerstone ext.)
  • Structured Reports as SR (DICOM SR ext.)
  • Encapsulated PDFs as PDFs (DICOM pdf ext.)

The general pattern is that a mode can define which Viewport to use for which specific SOPClassHandlerUID, so if you want to fork just a single Viewport component for a specialized mode, this is possible.

// displaySet, dataSource
const getViewportModule = () => {
const wrappedViewport = props => {
return (
<ExampleViewport
{...props}
onEvent={data => {
commandsManager.runCommand('commandName', data);
}}
/>
);
};

return [{ name: 'example', component: wrappedViewport }];
};

Example Viewport Component​

A simplified version of the tracked OHIFCornerstoneViewport is shown below, which creates a cornerstone viewport:

function TrackedCornerstoneViewport({
children,
dataSource,
displaySets,
viewportId,
servicesManager,
extensionManager,
commandsManager,
}) {

return (
<div className="viewport-wrapper">
/** Resize Detector */
<ReactResizeDetector
handleWidth
handleHeight
skipOnMount={true} // Todo: make these configurable
refreshMode={'debounce'}
refreshRate={100}
onResize={onResize}
targetRef={elementRef.current}
/>
/** Div For displaying image */
<div
className="cornerstone-viewport-element"
style={{ height: '100%', width: '100%' }}
onContextMenu={e => e.preventDefault()}
onMouseDown={e => e.preventDefault()}
ref={elementRef}
></div>
</div>
);
}

Viewport re-rendering optimizations​

We make use of the React memoization pattern to prevent unnecessary re-renders for the viewport unless certain aspects of the Viewport props change. You can take a look into the areEqual function in the OHIFCornerstoneViewport component to see how this is done.

function areEqual(prevProps, nextProps) {
if (prevProps.displaySets.length !== nextProps.displaySets.length) {
return false;
}

if (
prevProps.viewportOptions.orientation !==
nextProps.viewportOptions.orientation
) {
return false;
}

// rest of the code

as you see, we check if the needsRerendering prop is true, and if so, we will re-render the viewport if the displaySets prop changes or the orientation changes.

We use viewportId to identify a viewport and we use it as a key in React rendering. This is important because it allows us to keep track of the viewport and its state, and also let React optimize and move the viewport around in the grid without re-rendering it. However, there are some cases where we need to force re-render the viewport, for example, when the viewport is hydrated with a new Segmentation. For these cases, we use the needsRerendering prop to force re-render the viewport. You can add it to the viewportOptions

@ohif/app​

Viewport components are managed by the ViewportGrid Component. Which Viewport component is used depends on:

  • Hanging Protocols
  • The Layout Configuration
  • Registered SopClassHandlers

viewportModule-layout

An example of three cornerstone Viewports