Improve study browser

This commit is contained in:
Erik Ziegler 2020-05-15 10:05:40 +02:00 committed by James A. Petts
parent 6044190ab1
commit ec2df02e83
8 changed files with 195 additions and 105 deletions

View File

@ -80,13 +80,13 @@ function createDicomWebApi(dicomWebConfig) {
// processResults: processResults.bind(),
},
instances: {
search: (studyInstanceUid, queryParamaters) =>
search: (studyInstanceUid, queryParameters) =>
qidoSearch.call(
undefined,
qidoDicomWebClient,
studyInstanceUid,
null,
queryParamaters
queryParameters
),
},
},

View File

@ -105,12 +105,12 @@ function search(
dicomWebClient,
studyInstanceUid,
seriesInstanceUid,
queryParamaters
queryParameters
) {
// Studies
return dicomWebClient.searchForStudies({
studyInstanceUid: undefined,
queryParams: queryParamaters,
queryParams: queryParameters,
});
}

View File

@ -31,7 +31,7 @@ function ViewerLayout({
const getPanelData = id => {
const entry = extensionManager.getModuleEntry(id);
// TODO, not sure why sidepanel content has to be JSX, and not a children prop?
const content = entry.component({});
const content = entry.component({ extensionManager });
return {
iconName: entry.iconName,

View File

@ -1,4 +1,3 @@
import ViewerLayout from './ViewerLayout';
/*
- Define layout for the viewer in mode configuration.

View File

@ -1,55 +1,155 @@
import React from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { StudyBrowser } from '@ohif/ui';
import { useViewModel, displaySetManager } from '@ohif/core';
import {
dicomMetadataStore,
useViewModel,
displaySetManager,
} from '@ohif/core';
function StudyBrowserPanel({ extensionManager }) {
// TODO: need to check how this is intended to be passed in
const dataSource = extensionManager.dataSourceMap.dicomweb[0];
const viewModel = useViewModel();
// TODO
const viewportData = []; //useViewportGrid();
const seriesTracking = {}; //useSeriesTracking();
const [studyData, setStudyData] = useState([]);
console.log(viewModel);
const displaySets = viewModel.displaySetInstanceUids.map(
displaySetManager.getDisplaySetByUID
);
function displaySetsToStudyPanelInfo(
displaySets,
viewportData = [],
seriesTracking = {}
) {
console.log(displaySets);
const studies = {};
displaySets.forEach(displaySet => {
if (!displaySets.length) {
return;
}
// TODO:
// - Put this in something so it only runs once
// - Have update the query update the dicom data store at the study level and then have this component use the data in the view model
useEffect(() => {
const dSets = viewModel.displaySetInstanceUids.map(
displaySetManager.getDisplaySetByUID
);
const aDisplaySet = dSets[0];
const firstStudy = dicomMetadataStore.getStudy(
aDisplaySet.StudyInstanceUID
);
const firstInstance = firstStudy.series[0].instances[0];
const PatientID = firstInstance.PatientID;
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(studies);
});
}, [viewModel.displaySetInstanceUids]);
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 === displaySet.displaySetInstanceUid
a => a.displaySetInstanceUid === ds.displaySetInstanceUid
);
if (displaySetViewportData) {
displaySet.viewportIdentificator = displaySetViewportData.identifier;
}
const trackingInfo = seriesTracking[displaySet.SeriesInstanceUID];
const trackingInfo = seriesTracking[ds.SeriesInstanceUID];
if (trackingInfo) {
displaySet.viewportIdentificator = trackingInfo.isTracked;
displaySet.isTracked = trackingInfo.isTracked;
displaySet.componentType = trackingInfo.isTracked
? 'thumbnailTracked'
: 'thumbnail';
} else {
displaySet.isTracked = false;
displaySet.componentType = 'thumbnail';
}
if (!Object.keys(studies).includes(displaySet.StudyInstanceUID)) {
studies[displaySet.StudyInstanceUID] = {
date: displaySet.SeriesDate,
description: displaySet.SeriesDescription,
seriesNumber: displaySet.SeriesNumber,
modalities: displaySet.modalities,
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(),
};
}
studies[displaySet.StudyInstanceUID].displaySets.push(displaySet);
studies[displaySet.StudyInstanceUID].numInstances +=
displaySet.numImageFrames;
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(', ');
});
const allStudies = Object.keys(studies).map(StudyInstanceUID => {
const study = studies[StudyInstanceUID];
// QIDO for all by MRN
const allStudies = studyData.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: StudyInstanceUID,
...study,
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,
};
});
@ -57,36 +157,16 @@ function displaySetsToStudyPanelInfo(
return true; // TODO: check study.StudyInstanceUID matches queryparam?
});
console.log(primary);
// TODO: Filter allStudies to dates within one year of current date
const recentStudies = allStudies.filter(study => {
return true; // TODO: check study.date
});
/*const primary = {
studyInstanceUid: '1',
date: '07-Sept-2010',
description: 'CHEST/ABD/PELVIS W/CONTRAST',
numInstances: 902,
modalities: 'CT,SR',
displaySets: [
{
displaySetInstanceUid: 'f69f6asdasd48c-223e-db7f-c4af-b8906641a66e',
description: 'Multiple line image series description lorem sit',
seriesNumber: 1,
numInstances: 68,
componentType: 'thumbnailTracked',
viewportIdentificator: 'A',
isTracked: true,
},
],
};*/
const tabs = [
{
name: 'primary',
label: 'Primary',
studies: [primary],
studies: primary ? [primary] : [],
},
{
name: 'recent',
@ -100,51 +180,27 @@ function displaySetsToStudyPanelInfo(
},
];
return tabs;
}
function onClickStudy(StudyInstanceUID) {
if (studiesFromInstanceData[StudyInstanceUID]) {
return;
}
function StudyBrowserPanel({}) {
const viewModel = useViewModel();
console.warn(`onClickStudy: ${StudyInstanceUID}`);
// TODO: This is weird, why can't the data source just be used as
// as function that doesn't expect a query string?
const queryParams = `?StudyInstanceUIDs=${StudyInstanceUID}`;
// TODO
const viewportData = []; //useViewportGrid();
const seriesTracking = {}; //useSeriesTracking();
console.log(viewModel);
const displaySets = viewModel.displaySetInstanceUids.map(
displaySetManager.getDisplaySetByUID
);
let tabs;
if (displaySets.length) {
tabs = displaySetsToStudyPanelInfo(
displaySets,
viewportData,
seriesTracking
dataSource.retrieve.series.metadata(
queryParams,
displaySetManager.makeDisplaySets
);
} else {
tabs = [
{
name: 'primary',
label: 'Primary',
studies: [],
},
{
name: 'recent',
label: 'Recent',
studies: [],
},
{
name: 'all',
label: 'All',
studies: [],
},
];
}
return <StudyBrowser tabs={tabs} />;
const memoOnClickStudy = useCallback(StudyInstanceUID => {
onClickStudy(StudyInstanceUID);
});
return <StudyBrowser tabs={tabs} onClickStudy={memoOnClickStudy} />;
}
function getPanelModule() {

View File

@ -13,13 +13,6 @@ const makeDisplaySet = instances => {
const instance = instances[0];
const imageSet = new ImageSet(instances);
const modalities = new Set();
instances.forEach(i => {
modalities.add(i.Modality);
});
const modalitiesInDisplaySet = Array.from(modalities).join(', ');
// set appropriate attributes to image set...
imageSet.setAttributes({
displaySetInstanceUid: imageSet.uid, // create a local alias for the imageSet UID
@ -30,7 +23,7 @@ const makeDisplaySet = instances => {
SeriesNumber: instance.SeriesNumber,
FrameRate: instance.FrameTime,
SeriesDescription: instance.SeriesDescription,
modalities: modalitiesInDisplaySet,
Modality: instance.Modality,
isMultiFrame: isMultiFrame(instance),
numImageFrames: instances.length,
SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`,

View File

@ -47,7 +47,9 @@ const _model = {
};
function _getStudy(StudyInstanceUID) {
return _model.studies.find(StudyInstanceUID);
return _model.studies.find(
aStudy => aStudy.StudyInstanceUID === StudyInstanceUID
);
}
function _getSeries(StudyInstanceUID, SeriesInstanceUID) {
@ -90,6 +92,27 @@ const dicomMetadataStore = {
study.addSeries(instances);
},
addStudy(study) {
const { StudyInstanceUID } = study;
let existingStudy = _model.studies.find(
study => study.StudyInstanceUID === StudyInstanceUID
);
if (!existingStudy) {
const newStudy = new StudyMetadata(StudyInstanceUID);
newStudy.PatientID = study.PatientID;
newStudy.PatientName = study.PatientName;
newStudy.StudyDate = study.StudyDate;
newStudy.ModalitiesInStudy = study.ModalitiesInStudy;
newStudy.StudyDescription = study.StudyDescription;
newStudy.AccessionNumber = study.AccessionNumber;
newStudy.NumInstances = study.NumInstances; // todo: Correct naming?
_model.studies.push(newStudy);
}
},
getStudy: _getStudy,
getSeries: _getSeries,
getInstance: _getInstance,

View File

@ -22,7 +22,7 @@ const getTrackedSeries = displaySets => {
return trackedSeries;
};
const StudyBrowser = ({ tabs }) => {
const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
const [tabActive, setTabActive] = useState(getInitialActiveTab(tabs));
const [studyActive, setStudyActive] = useState(null);
@ -54,10 +54,27 @@ const StudyBrowser = ({ tabs }) => {
isActive={isActive}
onClick={() => {
setStudyActive(isActive ? null : studyInstanceUid);
if (onClickStudy) {
onClickStudy(studyInstanceUid);
}
}}
/>
{isActive && thumbnails && (
<ThumbnailList thumbnails={thumbnails} />
{isActive && displaySets && (
<ThumbnailList
thumbnails={displaySets}
thumbnailActive={thumbnailActive}
onThumbnailClick={thumbnailId => {
setThumbnailActive(
thumbnailId === thumbnailActive ? null : thumbnailId
);
if (onClickThumbnail) {
// TODO: what is thumbnailId? Should pass display set instead
onClickThumbnail(thumbnailId);
}
}}
/>
)}
</React.Fragment>
);
@ -103,6 +120,8 @@ const StudyBrowser = ({ tabs }) => {
};
StudyBrowser.propTypes = {
onClickStudy: PropTypes.func,
onClickThumbnail: PropTypes.func,
tabs: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,