diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js
index d20be0301..f609a2d3e 100644
--- a/extensions/default/src/DicomWebDataSource/index.js
+++ b/extensions/default/src/DicomWebDataSource/index.js
@@ -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
),
},
},
diff --git a/extensions/default/src/DicomWebDataSource/qido.js b/extensions/default/src/DicomWebDataSource/qido.js
index 3b2ac3592..83613dc0c 100644
--- a/extensions/default/src/DicomWebDataSource/qido.js
+++ b/extensions/default/src/DicomWebDataSource/qido.js
@@ -105,12 +105,12 @@ function search(
dicomWebClient,
studyInstanceUid,
seriesInstanceUid,
- queryParamaters
+ queryParameters
) {
// Studies
return dicomWebClient.searchForStudies({
studyInstanceUid: undefined,
- queryParams: queryParamaters,
+ queryParams: queryParameters,
});
}
diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx
index 37f03ba51..b163d7d94 100644
--- a/extensions/default/src/ViewerLayout/index.jsx
+++ b/extensions/default/src/ViewerLayout/index.jsx
@@ -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,
diff --git a/extensions/default/src/getLayoutTemplateModule.js b/extensions/default/src/getLayoutTemplateModule.js
index 23ad78db0..6df430358 100644
--- a/extensions/default/src/getLayoutTemplateModule.js
+++ b/extensions/default/src/getLayoutTemplateModule.js
@@ -1,4 +1,3 @@
-
import ViewerLayout from './ViewerLayout';
/*
- Define layout for the viewer in mode configuration.
diff --git a/extensions/default/src/getPanelModule.js b/extensions/default/src/getPanelModule.js
index d0068aa44..392392bd9 100644
--- a/extensions/default/src/getPanelModule.js
+++ b/extensions/default/src/getPanelModule.js
@@ -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 ;
+ const memoOnClickStudy = useCallback(StudyInstanceUID => {
+ onClickStudy(StudyInstanceUID);
+ });
+
+ return ;
}
function getPanelModule() {
diff --git a/extensions/default/src/getSopClassHandlerModule.js b/extensions/default/src/getSopClassHandlerModule.js
index cc08f6d47..b700a3ef1 100644
--- a/extensions/default/src/getSopClassHandlerModule.js
+++ b/extensions/default/src/getSopClassHandlerModule.js
@@ -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}`,
diff --git a/platform/core/src/dicomMetadataStore.js b/platform/core/src/dicomMetadataStore.js
index 5084ffcea..eb824df25 100644
--- a/platform/core/src/dicomMetadataStore.js
+++ b/platform/core/src/dicomMetadataStore.js
@@ -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,
diff --git a/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx b/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx
index d2b019be7..782e57bd7 100644
--- a/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx
+++ b/platform/ui/src/components/StudyBrowser/StudyBrowser.jsx
@@ -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 && (
-
+ {isActive && displaySets && (
+ {
+ setThumbnailActive(
+ thumbnailId === thumbnailActive ? null : thumbnailId
+ );
+
+ if (onClickThumbnail) {
+ // TODO: what is thumbnailId? Should pass display set instead
+ onClickThumbnail(thumbnailId);
+ }
+ }}
+ />
)}
);
@@ -103,6 +120,8 @@ const StudyBrowser = ({ tabs }) => {
};
StudyBrowser.propTypes = {
+ onClickStudy: PropTypes.func,
+ onClickThumbnail: PropTypes.func,
tabs: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,