ohif-viewer/extensions/default/src/getPanelModule.js

295 lines
8.4 KiB
JavaScript
Raw Normal View History

2020-05-15 10:05:40 +02:00
import React, { useState, useEffect, useCallback } from 'react';
2020-05-14 13:04:39 +02:00
import { StudyBrowser } from '@ohif/ui';
2020-05-15 10:05:40 +02:00
import {
dicomMetadataStore,
useViewModel,
displaySetManager,
} from '@ohif/core';
2020-05-15 14:31:18 +02:00
import MeasurementTable from './MeasurementTable.js';
2020-05-15 12:48:00 +02:00
// TODO:
// - No loading UI exists yet
// - 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);
2020-05-15 12:48:00 +02:00
});
}
2020-05-15 10:05:40 +02:00
2020-05-15 12:48:00 +02:00
function StudyBrowserPanel({ getDataSources, commandsManager }) {
2020-05-15 10:05:40 +02:00
const viewModel = useViewModel();
2020-05-15 12:48:00 +02:00
const dataSource = getDataSources('dicomweb')[0];
const [studyData, setStudyData] = useState([]);
const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState(new Map());
const updateThumbnailMap = (k, v) => {
setThumbnailImageSrcMap(thumbnailImageSrcMap.set(k, v));
};
const viewportData = []; //useViewportGrid();
const seriesTracking = {}; //useSeriesTracking();
2020-05-15 12:48:00 +02:00
useEffect(() => {
2020-05-15 19:07:21 +02:00
if (!viewModel.displaySetInstanceUIDs.length) {
return;
}
let isSubscribed = true;
2020-05-15 12:48:00 +02:00
const command = commandsManager.getCommand(
'getCornerstoneLibraries',
'VIEWER'
);
if (!command) {
throw new Error('Required command not found');
}
const { cornerstone, cornerstoneTools } = command.commandFn();
2020-05-15 19:07:21 +02:00
viewModel.displaySetInstanceUIDs.forEach(uid => {
2020-05-15 12:48:00 +02:00
const imageIds = dataSource.getImageIdsForDisplaySet(uid);
const imageId = imageIds[Math.floor(imageIds.length / 2)];
2020-05-15 12:48:00 +02:00
getImageSrc(imageId, { cornerstone }).then(imageSrc => {
if (isSubscribed) {
updateThumbnailMap(uid, imageSrc);
}
2020-05-15 12:48:00 +02:00
});
});
2020-05-15 19:07:21 +02:00
const displaySets = viewModel.displaySetInstanceUIDs.map(
2020-05-15 10:05:40 +02:00
displaySetManager.getDisplaySetByUID
);
2020-05-15 12:48:00 +02:00
const aDisplaySet = displaySets[0];
2020-05-15 10:05:40 +02:00
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,
};
});
const studiesFromInstanceData = {};
displaySets.forEach(ds => {
const displaySet = {
2020-05-15 19:07:21 +02:00
displaySetInstanceUID: ds.displaySetInstanceUID,
description: ds.SeriesDescription,
seriesNumber: ds.SeriesNumber,
modality: ds.Modality,
date: ds.SeriesDate,
numInstances: ds.numImageFrames,
};
2020-05-14 13:04:39 +02:00
const displaySetViewportData = viewportData.find(
2020-05-15 19:07:21 +02:00
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(', ');
});
2020-05-14 13:04:39 +02:00
// 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: [],
};
}
2020-05-15 10:05:40 +02:00
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,
};
});
2020-05-15 10:05:40 +02:00
if (isSubscribed) {
setStudyData(allStudies);
}
});
2020-05-14 13:04:39 +02:00
return () => (isSubscribed = false);
2020-05-15 19:07:21 +02:00
}, [viewModel.displaySetInstanceUIDs]);
2020-05-15 10:05:40 +02:00
studyData.forEach(study => {
study.displaySets.forEach(ds => {
2020-05-15 19:07:21 +02:00
ds.imageSrc = thumbnailImageSrcMap.get(ds.displaySetInstanceUID);
});
2020-05-14 13:04:39 +02:00
});
2020-05-13 17:37:27 +02:00
const primary = studyData.find(study => {
2020-05-14 13:04:39 +02:00
return true; // TODO: check study.StudyInstanceUID matches queryparam?
2020-05-13 17:37:27 +02:00
});
2020-05-15 10:05:40 +02:00
// TODO: Filter allStudies to dates within one year of current date
const recentStudies = studyData.filter(study => {
2020-05-14 13:04:39 +02:00
return true; // TODO: check study.date
});
const tabs = [
{
name: 'primary',
label: 'Primary',
2020-05-15 10:05:40 +02:00
studies: primary ? [primary] : [],
2020-05-14 13:04:39 +02:00
},
{
name: 'recent',
label: 'Recent',
studies: recentStudies,
},
{
name: 'all',
label: 'All',
studies: studyData,
2020-05-14 13:04:39 +02:00
},
];
2020-05-15 10:05:40 +02:00
function onClickStudy(StudyInstanceUID) {
const study = studyData.find(a => a.studyInstanceUid === StudyInstanceUID);
if (study && study.displaySets && study.displaySets.length) {
2020-05-15 10:05:40 +02:00
return;
}
2020-05-14 13:04:39 +02:00
2020-05-15 10:05:40 +02:00
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}`;
2020-05-14 13:04:39 +02:00
2020-05-15 10:05:40 +02:00
dataSource.retrieve.series.metadata(
queryParams,
displaySetManager.makeDisplaySets
);
2020-05-14 13:04:39 +02:00
}
const memoOnClickStudy = useCallback(
StudyInstanceUID => {
onClickStudy(StudyInstanceUID);
},
[studyData]
);
2020-05-15 10:05:40 +02:00
return <StudyBrowser tabs={tabs} onClickStudy={memoOnClickStudy} />;
}
2020-05-15 14:31:18 +02:00
function getPanelModule({ getDataSources, commandsManager, servicesManager }) {
2020-05-15 12:48:00 +02:00
const wrappedStudyBrowserPanel = () => {
return (
<StudyBrowserPanel
getDataSources={getDataSources}
commandsManager={commandsManager}
/>
);
};
2020-05-15 14:31:18 +02:00
const wrappedMeasurementPanel = () => {
return (
<MeasurementTable
commandsManager={commandsManager}
servicesManager={servicesManager}
/>
);
};
return [
{
name: 'seriesList',
iconName: 'group-layers',
iconLabel: 'Studies',
label: 'Studies',
2020-05-15 12:48:00 +02:00
component: wrappedStudyBrowserPanel,
},
{
name: 'measure',
iconName: 'list-bullets',
iconLabel: 'Measure',
label: 'Measurements',
2020-05-15 14:31:18 +02:00
component: wrappedMeasurementPanel,
},
];
}
export default getPanelModule;