import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { LoadingIndicatorProgress } from '@ohif/ui'; import { cleanDenaturalizedDataset } from '@ohif/extension-default'; import './DicomMicroscopyViewport.css'; import ViewportOverlay from './components/ViewportOverlay'; import getDicomWebClient from './utils/dicomWebClient'; import dcmjs from 'dcmjs'; import MicroscopyService from './services/MicroscopyService'; class DicomMicroscopyViewport extends Component { state = { error: null as any, isLoaded: false, }; microscopyService: MicroscopyService; viewer: any = null; // dicom-microscopy-viewer instance managedViewer: any = null; // managed wrapper of microscopy-dicom extension container = React.createRef(); overlayElement = React.createRef(); constructor(props: any) { super(props); const { microscopyService } = this.props.servicesManager.services; this.microscopyService = microscopyService; } static propTypes = { viewportData: PropTypes.object, activeViewportId: PropTypes.string, setViewportActive: PropTypes.func, // props from OHIF Viewport Grid displaySets: PropTypes.array, viewportId: PropTypes.string, viewportLabel: PropTypes.string, dataSource: PropTypes.object, viewportOptions: PropTypes.object, displaySetOptions: PropTypes.array, // other props from wrapping component servicesManager: PropTypes.object, extensionManager: PropTypes.object, commandsManager: PropTypes.object, resizeRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })]), }; /** * Get the nearest ROI from the mouse click point * * @param event * @param autoselect * @returns */ getNearbyROI(event: Event, autoselect = true) { const symbols = Object.getOwnPropertySymbols(this.viewer); const _drawingSource = symbols.find(p => p.description === 'drawingSource'); const _pyramid = symbols.find(p => p.description === 'pyramid'); const _map = symbols.find(p => p.description === 'map'); const _affine = symbols.find(p => p.description === 'affine'); const feature = this.viewer[_drawingSource].getClosestFeatureToCoordinate( this.viewer[_map].getEventCoordinate(event) ); if (!feature) { return null; } const roiAnnotation = this.viewer._getROIFromFeature( feature, this.viewer[_pyramid].metadata, this.viewer[_affine] ); if (roiAnnotation && autoselect) { this.microscopyService.selectAnnotation(roiAnnotation); } return roiAnnotation; } // install the microscopy renderer into the web page. // you should only do this once. async installOpenLayersRenderer(container, displaySet) { const loadViewer = async metadata => { const dicomMicroscopyModule = await this.microscopyService.importDicomMicroscopyViewer(); const { viewer: DicomMicroscopyViewer, metadata: metadataUtils } = dicomMicroscopyModule; const microscopyViewer = DicomMicroscopyViewer.VolumeImageViewer; const client = getDicomWebClient({ extensionManager: this.props.extensionManager, servicesManager: this.props.servicesManager, }); // Parse, format, and filter metadata const volumeImages: any[] = []; /** * This block of code is the original way of loading DICOM into dicom-microscopy-viewer * as in their documentation. * But we have the metadata already loaded by our loaders. * As the metadata for microscopy DIOM files tends to be big and we don't * want to double load it, below we have the mechanism to reconstruct the * DICOM JSON structure (denaturalized) from naturalized metadata. * (NOTE: Our loaders cache only naturalized metadata, not the denaturalized.) */ // { // const retrieveOptions = { // studyInstanceUID: metadata[0].StudyInstanceUID, // seriesInstanceUID: metadata[0].SeriesInstanceUID, // }; // metadata = await client.retrieveSeriesMetadata(retrieveOptions); // // Parse, format, and filter metadata // metadata.forEach(m => { // if ( // volumeImages.length > 0 && // m['00200052'].Value[0] != volumeImages[0].FrameOfReferenceUID // ) { // console.warn( // 'Expected FrameOfReferenceUID of difference instances within a series to be the same, found multiple different values', // m['00200052'].Value[0] // ); // m['00200052'].Value[0] = volumeImages[0].FrameOfReferenceUID; // } // NOTE: depending on different data source, image.ImageType sometimes // is a string, not a string array. // m['00080008'] = transformImageTypeUnnaturalized(m['00080008']); // const image = new metadataUtils.VLWholeSlideMicroscopyImage({ // metadata: m, // }); // const imageFlavor = image.ImageType[2]; // if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') { // volumeImages.push(image); // } // }); // } metadata.forEach(m => { // NOTE: depending on different data source, image.ImageType sometimes // is a string, not a string array. m.ImageType = typeof m.ImageType === 'string' ? m.ImageType.split('\\') : m.ImageType; const inst = cleanDenaturalizedDataset( dcmjs.data.DicomMetaDictionary.denaturalizeDataset(m), { StudyInstanceUID: m.StudyInstanceUID, SeriesInstanceUID: m.SeriesInstanceUID, dataSourceConfig: this.props.dataSource.getConfig(), } ); if (!inst['00480105']) { // Optical Path Sequence, no OpticalPathIdentifier? // NOTE: this is actually a not-well formatted DICOM VL Whole Slide Microscopy Image. inst['00480105'] = { vr: 'SQ', Value: [ { '00480106': { vr: 'SH', Value: ['1'], }, }, ], }; } const image = new metadataUtils.VLWholeSlideMicroscopyImage({ metadata: inst, }); const imageFlavor = image.ImageType[2]; if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') { volumeImages.push(image); } }); // format metadata for microscopy-viewer const options = { client, metadata: volumeImages, retrieveRendered: false, controls: ['overview', 'position'], }; this.viewer = new microscopyViewer(options); if (this.overlayElement && this.overlayElement.current && this.viewer.addViewportOverlay) { this.viewer.addViewportOverlay({ element: this.overlayElement.current, coordinates: [0, 0], // TODO: dicom-microscopy-viewer documentation says this can be false to be automatically, but it is not. navigate: true, className: 'OpenLayersOverlay', }); } this.viewer.render({ container }); const { StudyInstanceUID, SeriesInstanceUID } = displaySet; this.managedViewer = this.microscopyService.addViewer( this.viewer, this.props.viewportId, container, StudyInstanceUID, SeriesInstanceUID ); this.managedViewer.addContextMenuCallback((event: Event) => { // TODO: refactor this after Bill's changes on ContextMenu feature get merged // const roiAnnotationNearBy = this.getNearbyROI(event); }); }; this.microscopyService.clearAnnotations(); let smDisplaySet = displaySet; if (displaySet.Modality === 'SR') { // for SR displaySet, let's load the actual image displaySet smDisplaySet = displaySet.getSourceDisplaySet(); } console.log('Loading viewer metadata', smDisplaySet); await loadViewer(smDisplaySet.others); if (displaySet.Modality === 'SR') { displaySet.load(smDisplaySet); } } componentDidMount() { const { displaySets, viewportOptions } = this.props; // Todo-rename: this is always getting the 0 const displaySet = displaySets[0]; this.installOpenLayersRenderer(this.container.current, displaySet).then(() => { this.setState({ isLoaded: true }); }); } componentDidUpdate(prevProps: Readonly<{}>, prevState: Readonly<{}>, snapshot?: any): void { if (this.managedViewer && prevProps.displaySets !== this.props.displaySets) { const { displaySets } = this.props; const displaySet = displaySets[0]; this.microscopyService.clearAnnotations(); // loading SR if (displaySet.Modality === 'SR') { const referencedDisplaySet = displaySet.getSourceDisplaySet(); displaySet.load(referencedDisplaySet); } } } componentWillUnmount() { this.microscopyService.removeViewer(this.viewer); } setViewportActiveHandler = () => { const { setViewportActive, viewportId, activeViewportId } = this.props; if (viewportId !== activeViewportId) { setViewportActive(viewportId); } }; render() { const style = { width: '100%', height: '100%' }; const displaySet = this.props.displaySets[0]; const firstInstance = displaySet.firstInstance || displaySet.instance; return (
{displaySet && firstInstance.imageId && ( )}
{this.state.error ? (

{JSON.stringify(this.state.error)}

) : (
{ this.container.current = ref; this.props.resizeRef.current = ref; }} /> )} {this.state.isLoaded ? null : ( )}
); } } export default DicomMicroscopyViewport;