ohif-viewer/extensions/default/src/Panels/PanelStudyBrowser.jsx

242 lines
6.8 KiB
JavaScript

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 (
<StudyBrowser
activeTabName={activeTabName}
tabs={tabs}
onClickStudy={_handleStudyClick}
onSetTabActive={setActiveTabName}
/>
);
}
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,
}
);
});
}