refactor how we slowly build our studies array
This commit is contained in:
parent
351e0f76d6
commit
c55d069e32
@ -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 (
|
||||
<PanelStudyBrowser
|
||||
getDataSources={extensionManager.getDataSources}
|
||||
commandsManager={commandsManager}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
59
extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx
Normal file
59
extensions/default/src/Panels/WrappedPanelStudyBrowser.jsx
Normal file
@ -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 (
|
||||
<PanelStudyBrowser
|
||||
dataSource={dataSource}
|
||||
getImageSrc={_getImageSrcFromImageId}
|
||||
getStudiesByPatientId={getStudiesByPatientId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
19
extensions/default/src/Panels/getImageSrcFromImageId.js
Normal file
19
extensions/default/src/Panels/getImageSrcFromImageId.js
Normal file
@ -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;
|
||||
@ -1,5 +1,4 @@
|
||||
import PanelStudyBrowser, {
|
||||
WrappedPanelStudyBrowser,
|
||||
} from './PanelStudyBrowser';
|
||||
import PanelStudyBrowser from './PanelStudyBrowser';
|
||||
import WrappedPanelStudyBrowser from './WrappedPanelStudyBrowser';
|
||||
|
||||
export { PanelStudyBrowser, WrappedPanelStudyBrowser };
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user