From f87d18936e8394852dace9a423d1bcdbdb11c029 Mon Sep 17 00:00:00 2001 From: dannyrb Date: Thu, 21 May 2020 20:34:53 -0400 Subject: [PATCH] basic pub/sub refresh for tabs from displaysets from dicommetadata --- .../default/src/Panels/PanelStudyBrowser.jsx | 252 +++++++++--------- .../DicomMetadataStore/DicomMetadataStore.js | 12 +- .../src/services/DicomMetadataStore/EVENTS.js | 3 +- .../DicomMetadataStore/SeriesMetadata.js | 9 - .../DicomMetadataStore/StudyMetadata.js | 14 - .../createSeriesMetadata.js | 10 + .../DicomMetadataStore/createStudyMetadata.js | 14 + .../viewer/src/routes/DataSourceWrapper.jsx | 1 - platform/viewer/src/routes/Mode/Mode.jsx | 34 ++- 9 files changed, 191 insertions(+), 158 deletions(-) delete mode 100644 platform/core/src/services/DicomMetadataStore/SeriesMetadata.js delete mode 100644 platform/core/src/services/DicomMetadataStore/StudyMetadata.js create mode 100644 platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js create mode 100644 platform/core/src/services/DicomMetadataStore/createStudyMetadata.js diff --git a/extensions/default/src/Panels/PanelStudyBrowser.jsx b/extensions/default/src/Panels/PanelStudyBrowser.jsx index 80248f4e5..09a71d8df 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.jsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.jsx @@ -12,107 +12,66 @@ function PanelStudyBrowser({ 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()); + // Tabs --> Studies --> DisplaySets --> Thumbnails const [{ StudyInstanceUIDs }, dispatch] = useImageViewer(); - // vv What we want to render + const [activeTabName, setActiveTabName] = useState('primary'); const [studyDisplayList, setStudyDisplayList] = useState([]); + const [displaySets, setDisplaySets] = useState([]); + const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState(new Map()); + // ~~ studyDisplayList useEffect(() => { // Fetch all studies for the patient in each primary study async function fetchStudiesForPatient(StudyInstanceUID) { const qidoStudiesForPatient = (await getStudiesForPatientByStudyInstanceUID(StudyInstanceUID)) || []; + // TODO: This should be "naturalized DICOM JSON" studies const mappedStudies = _mapDataSourceStudies(qidoStudiesForPatient); + const actuallyMappedStudies = mappedStudies.map(qidoStudy => { + return { + studyInstanceUid: qidoStudy.StudyInstanceUID, + date: qidoStudy.StudyDate, + description: qidoStudy.StudyDescription, + modalities: qidoStudy.ModalitiesInStudy, + numInstances: qidoStudy.NumInstances, + // displaySets: [] + }; + }); - const updatedStudyDisplayList = _mergeStudyDisplayListAndDataSourceStudies( - studyDisplayList, - mappedStudies, - displaySets - ); - - addDisplaySetsToStudyDisplayList( - updatedStudyDisplayList, - displaySets, - thumbnailImageSrcMap - ); - - setStudyDisplayList(updatedStudyDisplayList); + setStudyDisplayList(actuallyMappedStudies); } StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid)); - }, [ - StudyInstanceUIDs, - getStudiesForPatientByStudyInstanceUID, - ]); + }, [StudyInstanceUIDs, getStudiesForPatientByStudyInstanceUID]); - const updateThumbnailMap = (displaySetInstanceUID, imageSrc) => { - setThumbnailImageSrcMap( - thumbnailImageSrcMap.set(displaySetInstanceUID, imageSrc) + // ~~ displaySets + useEffect(() => { + // TODO: Deep copy? Or By IDs? + // TODO: May need to be mapped to a different shape? + // TODO: Iterate over `studyDisplayList` and map these for all studies in list? + const currentDisplaySets = DisplaySetService.activeDisplaySets || []; + const mappedDisplaySets = _mapDisplaySets( + currentDisplaySets, + thumbnailImageSrcMap ); - }; - 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); - }); - } + setDisplaySets(mappedDisplaySets); + }, [thumbnailImageSrcMap]); async function handleDisplaySetsAdded(newDisplaySets) { + console.warn('~~ handleDisplaySetsAdded'); // 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); + + setThumbnailImageSrcMap( + thumbnailImageSrcMap.set(dset.displaySetInstanceUID, imageSrc) + ); }); } @@ -122,9 +81,21 @@ function PanelStudyBrowser({ DisplaySetService.EVENTS.DISPLAY_SETS_ADDED, handleDisplaySetsAdded ), + // TODO: Should this event indicate batch/series/study? + // Naming feels odd, and result is non-obvious + // Will this always contain _all_ displaySets we care about? DisplaySetService.subscribe( DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED, - setLocalDisplaySetsState + changedDisplaySets => { + console.warn('DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED', changedDisplaySets); + + const mappedDisplaySets = _mapDisplaySets( + changedDisplaySets, + thumbnailImageSrcMap + ); + + setDisplaySets(mappedDisplaySets); + } ), ]; @@ -133,33 +104,12 @@ function PanelStudyBrowser({ }; }, []); - const primaryStudies = studyDisplayList.filter(study => - StudyInstanceUIDs.includes(study.studyInstanceUid) + const tabs = _createStudyBrowserTabs( + StudyInstanceUIDs, + studyDisplayList, + displaySets ); - // 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) { @@ -214,28 +164,84 @@ function _mapDataSourceStudies(studies) { }); } -function _mergeStudyDisplayListAndDataSourceStudies( - studyDisplayList, - mappedQidoStudies -) { - return mappedQidoStudies.map(qidoStudy => { - const { StudyInstanceUID } = qidoStudy; - const existing = - studyDisplayList.find(a => a.studyInstanceUid === StudyInstanceUID) || {}; - - return Object.assign( - {}, - existing, - { - displaySets: [], +function _mapDisplaySets(displaySets, thumbnailImageSrcMap) { + console.warn('~~ setLocalDisplaySetsState'); + return 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 }, - { - studyInstanceUid: StudyInstanceUID, - date: qidoStudy.StudyDate, - description: qidoStudy.StudyDescription, - modalities: qidoStudy.ModalitiesInStudy, - numInstances: qidoStudy.NumInstances, - } - ); + }; }); } + +/** + * + * @param {string[]} primaryStudyInstanceUIDs + * @param {object[]} studyDisplayList + * @param {string} studyDisplayList.studyInstanceUid + * @param {string} studyDisplayList.date + * @param {string} studyDisplayList.description + * @param {string} studyDisplayList.modalities + * @param {number} studyDisplayList.numInstances + * @param {object[]} displaySets + * @returns tabs - The prop object expected by the StudyBrowser component + */ +function _createStudyBrowserTabs( + primaryStudyInstanceUIDs, + studyDisplayList, + displaySets +) { + const primaryStudies = []; + const recentStudies = []; + const allStudies = []; + + studyDisplayList.forEach(study => { + const displaySetsForStudy = displaySets.filter( + ds => ds.StudyInstanceUID === study.studyInstanceUid + ); + const tabStudy = Object.assign({}, study, { + displaySets: displaySetsForStudy, + }); + + if (primaryStudyInstanceUIDs.includes(study.studyInstanceUid)) { + primaryStudies.push(tabStudy); + } else { + // TODO: Filter allStudies to dates within one year of current date + recentStudies.push(tabStudy); + allStudies.push(tabStudy); + } + }); + + const tabs = [ + { + name: 'primary', + label: 'Primary', + studies: primaryStudies, + }, + { + name: 'recent', + label: 'Recent', + studies: recentStudies, + }, + { + name: 'all', + label: 'All', + studies: allStudies, + }, + ]; + + return tabs; +} diff --git a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js index 76081ce03..55bcf70f2 100644 --- a/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js +++ b/platform/core/src/services/DicomMetadataStore/DicomMetadataStore.js @@ -1,5 +1,5 @@ import pubSubServiceInterface from '../_shared/pubSubServiceInterface'; -import StudyMetadata from './StudyMetadata'; +import createStudyMetadata from './createStudyMetadata'; import EVENTS from './EVENTS'; const _model = { @@ -57,20 +57,26 @@ function _getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID) { const BaseImplementation = { EVENTS, listeners: {}, + // TODO: The assumption is that this is called per Study per Series + // We should do more to verify/clarify that addInstances(instances) { - const { StudyInstanceUID } = instances[0]; + const { StudyInstanceUID, SeriesInstanceUID } = instances[0]; let study = _model.studies.find( study => study.StudyInstanceUID === StudyInstanceUID ); if (!study) { - _model.studies.push(new StudyMetadata(StudyInstanceUID)); + _model.studies.push(createStudyMetadata(StudyInstanceUID)); study = _model.studies[_model.studies.length - 1]; } study.addSeries(instances); + this._broadcastEvent(EVENTS.INSTANCES_ADDED, { + StudyInstanceUID, + SeriesInstanceUID, + }); }, addStudy(study) { const { StudyInstanceUID } = study; diff --git a/platform/core/src/services/DicomMetadataStore/EVENTS.js b/platform/core/src/services/DicomMetadataStore/EVENTS.js index 6b964f703..e701c9011 100644 --- a/platform/core/src/services/DicomMetadataStore/EVENTS.js +++ b/platform/core/src/services/DicomMetadataStore/EVENTS.js @@ -1,6 +1,5 @@ const EVENTS = { - DISPLAY_SETS_ADDED: 'event::displaySetService:displaySetsAdded', - DISPLAY_SETS_CHANGED: 'event::displaySetService:displaySetsChanged', + INSTANCES_ADDED: 'event::dicomMetadataStore:instancesAdded', }; export default EVENTS; diff --git a/platform/core/src/services/DicomMetadataStore/SeriesMetadata.js b/platform/core/src/services/DicomMetadataStore/SeriesMetadata.js deleted file mode 100644 index bc6fca4d4..000000000 --- a/platform/core/src/services/DicomMetadataStore/SeriesMetadata.js +++ /dev/null @@ -1,9 +0,0 @@ -class SeriesMetadata { - constructor(instances) { - const { SeriesInstanceUID } = instances[0]; - this.SeriesInstanceUID = SeriesInstanceUID; - this.instances = instances; - } -} - -export default SeriesMetadata; diff --git a/platform/core/src/services/DicomMetadataStore/StudyMetadata.js b/platform/core/src/services/DicomMetadataStore/StudyMetadata.js deleted file mode 100644 index bf3ca5b0f..000000000 --- a/platform/core/src/services/DicomMetadataStore/StudyMetadata.js +++ /dev/null @@ -1,14 +0,0 @@ -import SeriesMetadata from './SeriesMetadata'; - -class StudyMetadata { - constructor(StudyInstanceUID) { - this.StudyInstanceUID = StudyInstanceUID; - this.series = []; - } - - addSeries(instances) { - this.series.push(new SeriesMetadata(instances)); - } -} - -export default StudyMetadata; diff --git a/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js b/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js new file mode 100644 index 000000000..51bbf0a54 --- /dev/null +++ b/platform/core/src/services/DicomMetadataStore/createSeriesMetadata.js @@ -0,0 +1,10 @@ +function createSeriesMetadata(instances) { + const { SeriesInstanceUID } = instances[0]; + + return { + SeriesInstanceUID, + instances, + }; +} + +export default createSeriesMetadata; diff --git a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js new file mode 100644 index 000000000..d645218b1 --- /dev/null +++ b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js @@ -0,0 +1,14 @@ +import createSeriesMetadata from './createSeriesMetadata'; + +function createStudyMetadata(StudyInstanceUID) { + return { + StudyInstanceUID, + series: [], + addSeries: function(instances) { + const series = createSeriesMetadata(instances); + this.series.push(series); + }, + }; +} + +export default createStudyMetadata; diff --git a/platform/viewer/src/routes/DataSourceWrapper.jsx b/platform/viewer/src/routes/DataSourceWrapper.jsx index d89831440..d9a5348e1 100644 --- a/platform/viewer/src/routes/DataSourceWrapper.jsx +++ b/platform/viewer/src/routes/DataSourceWrapper.jsx @@ -38,7 +38,6 @@ function DataSourceWrapper(props) { const firstAppConfigDataSource = appConfig.dataSources[0]; const dataSourceConfig = firstAppConfigDataSource.configuration; const firstWebApiDataSource = webApiDataSources[0]; - debugger; const dataSource = firstWebApiDataSource.createDataSource(dataSourceConfig); // Route props --> studies.mapParams diff --git a/platform/viewer/src/routes/Mode/Mode.jsx b/platform/viewer/src/routes/Mode/Mode.jsx index 22cc1c5df..17b5b81ef 100644 --- a/platform/viewer/src/routes/Mode/Mode.jsx +++ b/platform/viewer/src/routes/Mode/Mode.jsx @@ -1,8 +1,8 @@ import React, { useEffect } from 'react'; import { useParams } from 'react-router'; import PropTypes from 'prop-types'; -// -import { ToolBarManager } from '@ohif/core'; +// TODO: DicomMetadataStore should be injected? +import { DicomMetadataStore, ToolBarManager } from '@ohif/core'; import { DragAndDropProvider, ImageViewerProvider } from '@ohif/ui'; // import { useQuery } from '@hooks'; @@ -91,10 +91,31 @@ export default function ModeRoute({ console.log('queryParams: ', queryParams); // Call the data source to start building the view model? - dataSource.retrieve.series.metadata( - queryParams, - DisplaySetService.makeDisplaySets + // TODO: This should be called on subscription to DicomMetadataStore? + // dataSource.retrieve.series.metadata( + // queryParams, + // DisplaySetService.makeDisplaySets + // ); + + // TODO: This should be baked into core, not manuel? + // DisplaySetService would wire this up? + DicomMetadataStore.subscribe( + DicomMetadataStore.EVENTS.INSTANCES_ADDED, + ({ StudyInstanceUID, SeriesInstanceUID }) => { + console.warn(`INSTANCES_ADDED::\nStudy:${StudyInstanceUID}\nSeries:${SeriesInstanceUID}`); + + const seriesMetadata = DicomMetadataStore.getSeries( + StudyInstanceUID, + SeriesInstanceUID + ); + + DisplaySetService.makeDisplaySets(seriesMetadata.instances); + } ); + + StudyInstanceUIDsAsArray.forEach(StudyInstanceUID => { + dataSource.retrieveSeriesMetadata({ StudyInstanceUID }); + }); }, [ mode, dataSourceName, @@ -102,7 +123,8 @@ export default function ModeRoute({ DisplaySetService, extensionManager, sopClassHandlers, - dataSource.retrieve.series, + StudyInstanceUIDsAsArray, + dataSource, ]); const reducer = (state, action) => {