import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; // import { StudyBrowser, useImageViewer } from '@ohif/ui'; import { DicomMetadataStore } from '@ohif/core'; // This has to import from somewhere else... function PanelStudyBrowser({ DisplaySetService, getImageSrc, getStudiesForPatientByStudyInstanceUID, requestDisplaySetCreationForStudy, dataSource, }) { const currentDisplaySets = DisplaySetService.activeDisplaySets || []; // TODO: Deep copy? Or By IDs? // TODO: May need to be mapped to a different shape? const [displaySets, setDisplaySets] = useState(currentDisplaySets); const [activeTabName, setActiveTabName] = useState('primary'); const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState(new Map()); const [{ StudyInstanceUIDs }, dispatch] = useImageViewer(); // vv What we want to render const [studyDisplayList, setStudyDisplayList] = useState([]); useEffect(() => { // Fetch all studies for the patient in each primary study async function fetchStudiesForPatient(StudyInstanceUID) { const qidoStudiesForPatient = (await getStudiesForPatientByStudyInstanceUID(StudyInstanceUID)) || []; const mappedStudies = _mapDataSourceStudies(qidoStudiesForPatient); const updatedStudyDisplayList = _mergeStudyDisplayListAndDataSourceStudies( studyDisplayList, mappedStudies, displaySets ); addDisplaySetsToStudyDisplayList( updatedStudyDisplayList, displaySets, thumbnailImageSrcMap ); setStudyDisplayList(updatedStudyDisplayList); } StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid)); }, [ StudyInstanceUIDs, getStudiesForPatientByStudyInstanceUID, ]); const updateThumbnailMap = (displaySetInstanceUID, imageSrc) => { setThumbnailImageSrcMap( thumbnailImageSrcMap.set(displaySetInstanceUID, imageSrc) ); }; function setLocalDisplaySetsState(displaySets) { const displaySetsUI = displaySets.map(ds => { const imageSrc = thumbnailImageSrcMap.get(ds.displaySetInstanceUID); return { displaySetInstanceUID: ds.displaySetInstanceUID, description: ds.SeriesDescription, seriesNumber: ds.SeriesNumber, modality: ds.Modality, date: ds.SeriesDate, numInstances: ds.numImageFrames, StudyInstanceUID: ds.StudyInstanceUID, componentType: 'thumbnail', // 'thumbnailNoImage' || 'thumbnailTracked' // TODO: PUT THIS SOMEWHERE ELSE imageSrc, dragData: { type: 'displayset', displaySetInstanceUID: ds.displaySetInstanceUID, // .. Any other data to pass }, }; }); setDisplaySets(displaySetsUI); } function addDisplaySetsToStudyDisplayList( studies, displaySets, thumbnailImageSrcMap ) { displaySets.forEach(displaySet => { const study = studies.find( s => s.studyInstanceUid === displaySet.StudyInstanceUID ); if (!study) { return; } displaySet.imageSrc = thumbnailImageSrcMap.get( displaySet.displaySetInstanceUID ); study.displaySets.push(displaySet); }); } async function handleDisplaySetsAdded(newDisplaySets) { // First, launch requests for a thumbnail for the new display sets newDisplaySets.forEach(async dset => { const uid = dset.displaySetInstanceUID; const imageIds = dataSource.getImageIdsForDisplaySet(dset); const imageId = imageIds[Math.floor(imageIds.length / 2)]; // When the image arrives, render it and store the result in the thumbnailImgSrcMap const imageSrc = await getImageSrc(imageId); updateThumbnailMap(uid, imageSrc); }); } useEffect(() => { const subscriptions = [ DisplaySetService.subscribe( DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, handleDisplaySetsAdded ), DisplaySetService.subscribe( DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED, setLocalDisplaySetsState ), ]; return () => { subscriptions.forEach(sub => sub.unsubscribe); }; }, []); const primaryStudies = studyDisplayList.filter(study => StudyInstanceUIDs.includes(study.studyInstanceUid) ); // TODO: Filter allStudies to dates within one year of current date const recentStudies = studyDisplayList.filter(study => { return true; // TODO: check study.date }); const tabs = [ { name: 'primary', label: 'Primary', studies: primaryStudies || [], }, { name: 'recent', label: 'Recent', studies: recentStudies, }, { name: 'all', label: 'All', studies: studyDisplayList, }, ]; // TODO: Should "expand" appropriate study (already handled by component?) // TODO: Should not fire this on "close" function _handleStudyClick(StudyInstanceUID) { requestDisplaySetCreationForStudy(DisplaySetService, StudyInstanceUID); } return ( ); } PanelStudyBrowser.propTypes = { DisplaySetService: PropTypes.shape({ EVENTS: PropTypes.object.isRequired, hasDisplaySetsForStudy: PropTypes.func.isRequired, subscribe: PropTypes.func.isRequired, }).isRequired, dataSource: PropTypes.shape({ getImageIdsForDisplaySet: PropTypes.func.isRequired, }).isRequired, getImageSrc: PropTypes.func.isRequired, getStudiesForPatientByStudyInstanceUID: PropTypes.func.isRequired, requestDisplaySetCreationForStudy: PropTypes.func.isRequired, }; export default PanelStudyBrowser; /** * Maps from the DataSource's format to a naturalized object * * @param {*} studies */ function _mapDataSourceStudies(studies) { return studies.map(study => { // TODO: Why does the data source return in this format? return { AccessionNumber: study.accession, StudyDate: study.date, StudyDescription: study.description, NumInstances: study.instances, ModalitiesInStudy: study.modalities, PatientID: study.mrn, PatientName: study.patientName, StudyInstanceUID: study.studyInstanceUid, StudyTime: study.time, }; }); } function _mergeStudyDisplayListAndDataSourceStudies( studyDisplayList, mappedQidoStudies ) { return mappedQidoStudies.map(qidoStudy => { const { StudyInstanceUID } = qidoStudy; const existing = studyDisplayList.find(a => a.studyInstanceUid === StudyInstanceUID) || {}; return Object.assign( {}, existing, { displaySets: [], }, { studyInstanceUid: StudyInstanceUID, date: qidoStudy.StudyDate, description: qidoStudy.StudyDescription, modalities: qidoStudy.ModalitiesInStudy, numInstances: qidoStudy.NumInstances, } ); }); }