From c55d069e320fad80c4c383004bfd632ee8b87b25 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Mon, 18 May 2020 16:20:39 -0400 Subject: [PATCH] refactor how we slowly build our studies array --- .../default/src/Panels/PanelStudyBrowser.jsx | 281 +++++++++--------- .../src/Panels/WrappedPanelStudyBrowser.jsx | 59 ++++ .../src/Panels/getImageSrcFromImageId.js | 19 ++ extensions/default/src/Panels/index.js | 5 +- extensions/default/src/getPanelModule.js | 14 - 5 files changed, 214 insertions(+), 164 deletions(-) create mode 100644 extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx create mode 100644 extensions/default/src/Panels/getImageSrcFromImageId.js diff --git a/extensions/default/src/Panels/PanelStudyBrowser.jsx b/extensions/default/src/Panels/PanelStudyBrowser.jsx index 533312b99..7256695bc 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.jsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.jsx @@ -2,8 +2,9 @@ import React, { useState, useEffect, useCallback } from 'react'; import PropTypes from 'prop-types'; import { StudyBrowser } from '@ohif/ui'; import { dicomMetadataStore, useViewModel } from '@ohif/core'; +// -function PanelStudyBrowser({ getDataSources, commandsManager }) { +function PanelStudyBrowser({ getImageSrc, getStudiesByPatientId, dataSource }) { console.warn('StudyBrowserPanel rerender'); const [activeTabName, setActiveTabName] = useState('primary'); const [studyData, setStudyData] = useState([]); @@ -12,8 +13,8 @@ function PanelStudyBrowser({ getDataSources, commandsManager }) { setThumbnailImageSrcMap(thumbnailImageSrcMap.set(k, v)); }; + const isSubscribed = true; const viewModel = useViewModel(); - const dataSource = getDataSources('dicomweb')[0]; const viewportData = []; //useViewportGrid(); const seriesTracking = {}; //useSeriesTracking(); @@ -23,29 +24,15 @@ function PanelStudyBrowser({ getDataSources, commandsManager }) { return; } - let isSubscribed = true; + if (getImageSrc) { + viewModel.displaySetInstanceUIDs.forEach(async uid => { + const imageIds = dataSource.getImageIdsForDisplaySet(uid); + const imageId = imageIds[Math.floor(imageIds.length / 2)]; + const imageSrc = await getImageSrc(imageId); - const command = commandsManager.getCommand( - 'getCornerstoneLibraries', - 'VIEWER' - ); - - if (!command) { - throw new Error('Required command not found'); - } - - const { cornerstone, cornerstoneTools } = command.commandFn(); - - viewModel.displaySetInstanceUIDs.forEach(uid => { - const imageIds = dataSource.getImageIdsForDisplaySet(uid); - const imageId = imageIds[Math.floor(imageIds.length / 2)]; - - getImageSrc(imageId, { cornerstone }).then(imageSrc => { - if (isSubscribed) { - updateThumbnailMap(uid, imageSrc); - } + updateThumbnailMap(uid, imageSrc); }); - }); + } const displaySets = viewModel.displaySetInstanceUIDs.map( displaySetManager.getDisplaySetByUID @@ -57,121 +44,27 @@ function PanelStudyBrowser({ getDataSources, commandsManager }) { ); const firstInstance = firstStudy.series[0].instances[0]; const PatientID = firstInstance.PatientID; + // + async function getData() { + const studiesForPatient = await getStudiesByPatientId(PatientID); + const mappedStudiesFromDataSource = _mapDataSourceStudies( + studiesForPatient + ); + const mappedStudiesFromInstances = _getMappedStudiesFromDisplaySets( + displaySets + ); + const ourFinalFormIGuess = _mergeDataSourceAndInstanceStudies( + mappedStudiesFromDataSource, + mappedStudiesFromInstances + ); - dataSource.query.studies.search({ patientId: PatientID }).then(results => { - const studies = results.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, - }; - }); + setStudyData(ourFinalFormIGuess); + } - const studiesFromInstanceData = {}; - displaySets.forEach(ds => { - const displaySet = { - displaySetInstanceUID: ds.displaySetInstanceUID, - description: ds.SeriesDescription, - seriesNumber: ds.SeriesNumber, - modality: ds.Modality, - date: ds.SeriesDate, - numInstances: ds.numImageFrames, - }; - - const displaySetViewportData = viewportData.find( - a => a.displaySetInstanceUID === ds.displaySetInstanceUID - ); - - if (displaySetViewportData) { - displaySet.viewportIdentificator = displaySetViewportData.identifier; - } - - const trackingInfo = seriesTracking[ds.SeriesInstanceUID]; - if (trackingInfo) { - displaySet.isTracked = trackingInfo.isTracked; - } else { - displaySet.isTracked = false; - } - - displaySet.componentType = 'thumbnailTracked'; - - if ( - !Object.keys(studiesFromInstanceData).includes(ds.StudyInstanceUID) - ) { - const study = dicomMetadataStore.getStudy(ds.StudyInstanceUID); - const anInstance = study.series[0].instances[0]; - - studiesFromInstanceData[ds.StudyInstanceUID] = { - date: anInstance.StudyDate, // TODO: Format this date to DD-MMM-YYYY - description: anInstance.StudyDescription, - displaySets: [], - numInstances: 0, - modalitiesSet: new Set(), - }; - } - - studiesFromInstanceData[ds.StudyInstanceUID].displaySets.push( - displaySet - ); - studiesFromInstanceData[ds.StudyInstanceUID].numInstances += - displaySet.numInstances; - - studiesFromInstanceData[ds.StudyInstanceUID].modalitiesSet.add( - displaySet.modality - ); - - const modalitiesSet = - studiesFromInstanceData[ds.StudyInstanceUID].modalitiesSet; - studiesFromInstanceData[ds.StudyInstanceUID].modalities = Array.from( - modalitiesSet - ).join(', '); - }); - - // QIDO for all by MRN - const allStudies = studies.map(studyLevelData => { - const studyFromInstanceData = - studiesFromInstanceData[studyLevelData.StudyInstanceUID]; - - if (!studyFromInstanceData) { - return { - studyInstanceUid: studyLevelData.StudyInstanceUID, - date: studyLevelData.StudyDate, - description: studyLevelData.StudyDescription, - modalities: studyLevelData.ModalitiesInStudy, - numInstances: studyLevelData.NumInstances, - displaySets: [], - }; - } - - return { - studyInstanceUid: studyLevelData.StudyInstanceUID, - date: studyLevelData.StudyDate || studyFromInstanceData.date, - description: - studyLevelData.StudyDescription || - studyFromInstanceData.description, - modalities: - studyFromInstanceData.modalities || - studyLevelData.ModalitiesInStudy, - numInstances: - studyLevelData.NumInstances || studyFromInstanceData.numInstances, - displaySets: studyFromInstanceData.displaySets, - }; - }); - - if (isSubscribed) { - setStudyData(allStudies); - } - }); + getData(); return () => (isSubscribed = false); - }, [viewModel.displaySetInstanceUIDs, thumbnailImageSrcMap, setStudyData]); + }, [viewModel.displaySetInstanceUIDs]); studyData.forEach(study => { study.displaySets.forEach(ds => { @@ -240,18 +133,112 @@ function PanelStudyBrowser({ getDataSources, commandsManager }) { ); } -function WrappedPanelStudyBrowser({ commandsManager, extensionManager }) { - return ( - - ); -} - -WrappedPanelStudyBrowser.propTypes = { - commandsManager: PropTypes.object.isRequired, - extensionManager: PropTypes.object.isRequired, +PanelStudyBrowser.propTypes = { + dataSource: PropTypes.shape({ + getImageIdsForDisplaySet: PropTypes.func.isRequired, + }).isRequired, + getImageSrc: PropTypes.func.isRequired, + getStudiesByPatientId: PropTypes.func.isRequired, }; -export { WrappedPanelStudyBrowser }; +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, + }; + }); +} + +/** + * Iterates over displaysets and creates mapped studies from + * instance metadata. + * + * @param {*} displaySets + */ +function _getMappedStudiesFromDisplaySets(displaySets) { + const studiesFromInstanceData = {}; + + displaySets.forEach(ds => { + const displaySet = { + displaySetInstanceUID: ds.displaySetInstanceUID, + description: ds.SeriesDescription, + seriesNumber: ds.SeriesNumber, + modality: ds.Modality, + date: ds.SeriesDate, + numInstances: ds.numImageFrames, + }; + + studiesFromInstanceData[ds.StudyInstanceUID] = + studiesFromInstanceData[ds.StudyInstanceUID] || + _mapStudyFromInstance(ds.StudyInstanceUID); + + const mappedStudy = studiesFromInstanceData[ds.StudyInstanceUID]; + + mappedStudy.displaySets.push(displaySet); + mappedStudy.numInstances += displaySet.numInstances; + mappedStudy.modalitiesSet.add(displaySet.modality); + + const modalitiesSet = mappedStudy.modalitiesSet; + mappedStudy.modalities = Array.from(modalitiesSet).join(', '); + }); + + return studiesFromInstanceData; +} + +function _mergeDataSourceAndInstanceStudies(mappedQidoStudies, mappedInstanceStudies) { + const allStudies = mappedQidoStudies.map(qidoStudy => { + const studyFromInstances = + mappedInstanceStudies[qidoStudy.StudyInstanceUID]; + + if (!studyFromInstances) { + return { + studyInstanceUid: qidoStudy.StudyInstanceUID, + date: qidoStudy.StudyDate, + description: qidoStudy.StudyDescription, + modalities: qidoStudy.ModalitiesInStudy, + numInstances: qidoStudy.NumInstances, + displaySets: [], + }; + } + + return { + studyInstanceUid: qidoStudy.StudyInstanceUID, + date: qidoStudy.StudyDate || studyFromInstances.date, + description: qidoStudy.StudyDescription || studyFromInstances.description, + modalities: studyFromInstances.modalities || qidoStudy.ModalitiesInStudy, + numInstances: qidoStudy.NumInstances || studyFromInstances.numInstances, + displaySets: studyFromInstances.displaySets, + }; + }); + + return allStudies; +} + +function _mapStudyFromInstance(StudyInstanceUID) { + const study = dicomMetadataStore.getStudy(StudyInstanceUID); + const anInstance = study.series[0].instances[0]; + + return { + date: anInstance.StudyDate, // TODO: Format this date to DD-MMM-YYYY + description: anInstance.StudyDescription, + displaySets: [], + numInstances: 0, + modalitiesSet: new Set(), + }; +} diff --git a/extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx b/extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx new file mode 100644 index 000000000..b25fc9d52 --- /dev/null +++ b/extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +// +import PanelStudyBrowser from './PanelStudyBrowser'; +import getImageSrcFromImageId from './getImageSrcFromImageId'; + +/** + * Wraps the PanelStudyBrowser and provides features afforded by managers/services + * + * @param {object} params + * @param {object} commandsManager + * @param {object} extensionManager + */ +function WrappedPanelStudyBrowser({ commandsManager, extensionManager }) { + // Note: this feels odd + const dataSource = extensionManager.getDataSources('dicomweb')[0]; + const getStudiesByPatientId = patientId => + dataSource.query.studies.search(patientId); + const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn( + commandsManager.getCommand + ); + + + return ( + + ); +} + +/** + * Grabs cornerstone library reference using a dependent command from + * the @ohif/extension-cornerstone extension. Then creates a helper function + * that can take an imageId and return an image src. + * + * @param {func} getCommand - CommandManager's getCommand method + * @returns {func} getImageSrcFromImageId - A utility function powered by + * cornerstone + */ +function _createGetImageSrcFromImageIdFn(getCommand) { + try { + const command = getCommand('getCornerstoneLibraries', 'VIEWER'); + if(!command) { return; } + const { cornerstone } = command.commandFn(); + + return getImageSrcFromImageId.bind(null, cornerstone); + } catch (ex) { + // throw new Error('Required command not found'); + } +} + +WrappedPanelStudyBrowser.propTypes = { + commandsManager: PropTypes.object.isRequired, + extensionManager: PropTypes.object.isRequired, +}; + +export default WrappedPanelStudyBrowser; diff --git a/extensions/default/src/Panels/getImageSrcFromImageId.js b/extensions/default/src/Panels/getImageSrcFromImageId.js new file mode 100644 index 000000000..baf46c84a --- /dev/null +++ b/extensions/default/src/Panels/getImageSrcFromImageId.js @@ -0,0 +1,19 @@ +/** + * @param {*} cornerstone + * @param {*} imageId + */ +function getImageSrcFromImageId(cornerstone, imageId) { + return new Promise((resolve, reject) => { + cornerstone + .loadAndCacheImage(imageId) + .then(image => { + const canvas = document.createElement('canvas'); + cornerstone.renderToCanvas(canvas, image); + + resolve(canvas.toDataURL()); + }) + .catch(reject); + }); +} + +export default getImageSrcFromImageId; diff --git a/extensions/default/src/Panels/index.js b/extensions/default/src/Panels/index.js index b2847a9c1..c596e03a0 100644 --- a/extensions/default/src/Panels/index.js +++ b/extensions/default/src/Panels/index.js @@ -1,5 +1,4 @@ -import PanelStudyBrowser, { - WrappedPanelStudyBrowser, -} from './PanelStudyBrowser'; +import PanelStudyBrowser from './PanelStudyBrowser'; +import WrappedPanelStudyBrowser from './WrappedPanelStudyBrowser'; export { PanelStudyBrowser, WrappedPanelStudyBrowser }; diff --git a/extensions/default/src/getPanelModule.js b/extensions/default/src/getPanelModule.js index 4054cb166..ab3cfb939 100644 --- a/extensions/default/src/getPanelModule.js +++ b/extensions/default/src/getPanelModule.js @@ -8,20 +8,6 @@ import MeasurementTable from './MeasurementTable.js'; // - cancel promises when component is destroyed // - show errors in UI for thumbnails if promise fails -function getImageSrc(imageId, { cornerstone }) { - return new Promise((resolve, reject) => { - cornerstone - .loadAndCacheImage(imageId) - .then(image => { - const canvas = document.createElement('canvas'); - cornerstone.renderToCanvas(canvas, image); - - resolve(canvas.toDataURL()); - }) - .catch(reject); - }); -} - function getPanelModule({ commandsManager, extensionManager,