casing change
This commit is contained in:
1 parent
591e21e287
commit
d510cc8ad2
10 files changed
+2
-2
No files matched your search
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
MeasurementsPanel,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Icon,
|
||||
IconButton,
|
||||
} from '@ohif/ui';
|
||||
|
||||
export default function MeasurementTable({ servicesManager, commandsManager }) {
|
||||
const { MeasurementService } = servicesManager.services;
|
||||
|
||||
console.log('MeasurementTable rendering!!!!!!!!!!!!!');
|
||||
|
||||
const actionButtons = (
|
||||
<React.Fragment>
|
||||
<ButtonGroup onClick={() => alert('Export')}>
|
||||
<Button
|
||||
className="px-2 py-2 text-base text-white bg-black border-primary-main"
|
||||
size="initial"
|
||||
color="inherit"
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
<IconButton
|
||||
className="px-2 text-white bg-black border-primary-main"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
>
|
||||
<Icon name="arrow-down" />
|
||||
</IconButton>
|
||||
</ButtonGroup>
|
||||
<Button
|
||||
className="px-2 py-2 ml-2 text-base text-white bg-black border border-primary-main"
|
||||
variant="outlined"
|
||||
size="initial"
|
||||
color="inherit"
|
||||
onClick={() => alert('Create Report')}
|
||||
>
|
||||
Create Report
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const descriptionData = {
|
||||
date: '07-Sep-2010',
|
||||
modality: 'CT',
|
||||
description: 'CHEST/ABD/PELVIS W CONTRAST',
|
||||
};
|
||||
|
||||
const activeMeasurementItem = 0;
|
||||
|
||||
const measurementTableData = {
|
||||
title: 'Measurements',
|
||||
amount: 10,
|
||||
data: new Array(10).fill({}).map((el, i) => ({
|
||||
id: i + 1,
|
||||
label: 'Label short description',
|
||||
displayText: '24.0 x 24.0 mm (S:4, I:22)',
|
||||
isActive: activeMeasurementItem === i + 1,
|
||||
})),
|
||||
// onClick: id => setActiveMeasurementItem(s => (s === id ? null : id)),
|
||||
onClick: () => {},
|
||||
onEdit: id => alert(`Edit: ${id}`),
|
||||
};
|
||||
|
||||
return (
|
||||
<MeasurementsPanel
|
||||
descriptionData={descriptionData}
|
||||
measurementTableData={measurementTableData}
|
||||
actionButtons={actionButtons}
|
||||
/>
|
||||
);
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StudyBrowser, useImageViewer } from '@ohif/ui';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} param0
|
||||
*/
|
||||
function PanelStudyBrowser({
|
||||
DisplaySetService,
|
||||
getImageSrc,
|
||||
getStudiesForPatientByStudyInstanceUID,
|
||||
requestDisplaySetCreationForStudy,
|
||||
dataSource,
|
||||
}) {
|
||||
// Normally you nest the components so the tree isn't so deep, and the data
|
||||
// doesn't have to have such an intense shape. This works well enough for now.
|
||||
// Tabs --> Studies --> DisplaySets --> Thumbnails
|
||||
const [{ StudyInstanceUIDs }, dispatch] = useImageViewer();
|
||||
const [activeTabName, setActiveTabName] = useState('primary');
|
||||
const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([]);
|
||||
const [studyDisplayList, setStudyDisplayList] = useState([]);
|
||||
const [displaySets, setDisplaySets] = useState([]);
|
||||
const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({});
|
||||
|
||||
// ~~ 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: []
|
||||
};
|
||||
});
|
||||
|
||||
setStudyDisplayList(actuallyMappedStudies);
|
||||
}
|
||||
|
||||
StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid));
|
||||
}, [StudyInstanceUIDs, getStudiesForPatientByStudyInstanceUID]);
|
||||
|
||||
// ~~ Initial Thumbnails
|
||||
useEffect(() => {
|
||||
const currentDisplaySets = DisplaySetService.activeDisplaySets;
|
||||
currentDisplaySets.forEach(async dSet => {
|
||||
const newImageSrcEntry = {};
|
||||
const displaySet = DisplaySetService.getDisplaySetByUID(
|
||||
dSet.displaySetInstanceUID
|
||||
);
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
|
||||
const imageId = imageIds[Math.floor(imageIds.length / 2)];
|
||||
|
||||
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
|
||||
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId);
|
||||
setThumbnailImageSrcMap(prevState => {
|
||||
return { ...prevState, ...newImageSrcEntry };
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
// ~~ displaySets
|
||||
useEffect(() => {
|
||||
// TODO: Are we sure `activeDisplaySets` will always be accurate?
|
||||
const currentDisplaySets = DisplaySetService.activeDisplaySets;
|
||||
const mappedDisplaySets = _mapDisplaySets(
|
||||
currentDisplaySets,
|
||||
thumbnailImageSrcMap
|
||||
);
|
||||
|
||||
setDisplaySets(mappedDisplaySets);
|
||||
}, [thumbnailImageSrcMap]);
|
||||
|
||||
// ~~ subscriptions --> displaySets
|
||||
useEffect(() => {
|
||||
// DISPLAY_SETS_ADDED returns an array of DisplaySets that were added
|
||||
const SubscriptionDisplaySetsAdded = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||
newDisplaySets => {
|
||||
newDisplaySets.forEach(async dSet => {
|
||||
const newImageSrcEntry = {};
|
||||
const displaySet = DisplaySetService.getDisplaySetByUID(
|
||||
dSet.displaySetInstanceUID
|
||||
);
|
||||
const imageIds = dataSource.getImageIdsForDisplaySet(displaySet);
|
||||
const imageId = imageIds[Math.floor(imageIds.length / 2)];
|
||||
|
||||
// When the image arrives, render it and store the result in the thumbnailImgSrcMap
|
||||
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(
|
||||
imageId
|
||||
);
|
||||
setThumbnailImageSrcMap(prevState => {
|
||||
return { ...prevState, ...newImageSrcEntry };
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Will this always hold _all_ the displaySets we care about?
|
||||
// DISPLAY_SETS_CHANGED returns `DisplaySerService.activeDisplaySets`
|
||||
const SubscriptionDisplaySetsChanged = DisplaySetService.subscribe(
|
||||
DisplaySetService.EVENTS.DISPLAY_SETS_CHANGED,
|
||||
changedDisplaySets => {
|
||||
const mappedDisplaySets = _mapDisplaySets(
|
||||
changedDisplaySets,
|
||||
thumbnailImageSrcMap
|
||||
);
|
||||
|
||||
setDisplaySets(mappedDisplaySets);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
SubscriptionDisplaySetsAdded.unsubscribe();
|
||||
SubscriptionDisplaySetsChanged.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tabs = _createStudyBrowserTabs(
|
||||
StudyInstanceUIDs,
|
||||
studyDisplayList,
|
||||
displaySets
|
||||
);
|
||||
|
||||
// TODO: Should not fire this on "close"
|
||||
function _handleStudyClick(StudyInstanceUID) {
|
||||
const shouldCollapseStudy = expandedStudyInstanceUIDs.includes(
|
||||
StudyInstanceUID
|
||||
);
|
||||
const updatedExpandedStudyInstanceUIDs = shouldCollapseStudy
|
||||
// eslint-disable-next-line prettier/prettier
|
||||
? [...expandedStudyInstanceUIDs.filter(stdyUid => stdyUid !== StudyInstanceUID)]
|
||||
: [...expandedStudyInstanceUIDs, StudyInstanceUID];
|
||||
|
||||
setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs);
|
||||
|
||||
if (!shouldCollapseStudy) {
|
||||
requestDisplaySetCreationForStudy(DisplaySetService, StudyInstanceUID);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<StudyBrowser
|
||||
tabs={tabs}
|
||||
activeTabName={activeTabName}
|
||||
expandedStudyInstanceUIDs={expandedStudyInstanceUIDs}
|
||||
onClickStudy={_handleStudyClick}
|
||||
onClickTab={clickedTabName => {
|
||||
setActiveTabName(clickedTabName);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
PanelStudyBrowser.propTypes = {
|
||||
DisplaySetService: PropTypes.shape({
|
||||
EVENTS: PropTypes.object.isRequired,
|
||||
activeDisplaySets: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
getDisplaySetByUID: PropTypes.func.isRequired,
|
||||
hasDisplaySetsForStudy: PropTypes.func.isRequired,
|
||||
subscribe: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
dataSource: PropTypes.shape({
|
||||
getImageIdsForDisplaySet: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
getImageSrc: PropTypes.func.isRequired,
|
||||
getStudiesForPatientByStudyInstanceUID: PropTypes.func.isRequired,
|
||||
requestDisplaySetCreationForStudy: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function _mapDisplaySets(displaySets, thumbnailImageSrcMap) {
|
||||
return displaySets.map(ds => {
|
||||
const imageSrc = thumbnailImageSrcMap[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: 'thumbnailTracked', // 'thumbnailNoImage' || 'thumbnail' // TODO: PUT THIS SOMEWHERE ELSE
|
||||
imageSrc,
|
||||
dragData: {
|
||||
type: 'displayset',
|
||||
displaySetInstanceUID: ds.displaySetInstanceUID,
|
||||
// .. Any other data to pass
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
+19
@@ -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;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
async function getStudiesForPatientByStudyInstanceUID(
|
||||
dataSource,
|
||||
StudyInstanceUID
|
||||
) {
|
||||
// TODO: The `DicomMetadataStore` should short-circuit both of these requests
|
||||
// Data _could_ be here from route query, or if using JSON data source
|
||||
// We could also force this to "await" these values being available in the DICOMStore?
|
||||
// Kind of like promise fulfillment in cornerstone-wado-image-loader when there are multiple
|
||||
// outgoing requests for the same data
|
||||
const getStudyResult = await dataSource.query.studies.search({
|
||||
studyInstanceUid: StudyInstanceUID,
|
||||
});
|
||||
|
||||
// TODO: To Erik's point, the data source likely shouldn't deviate from
|
||||
// Naturalized DICOM JSON when returning. It makes things like this awkward (mrn)
|
||||
if (getStudyResult && getStudyResult.length && getStudyResult[0].mrn) {
|
||||
return dataSource.query.studies.search({
|
||||
patientId: getStudyResult[0].mrn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default getStudiesForPatientByStudyInstanceUID;
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
//
|
||||
import PanelStudyBrowser from './PanelStudyBrowser';
|
||||
import getImageSrcFromImageId from './getImageSrcFromImageId';
|
||||
import getStudiesForPatientByStudyInstanceUID from './getStudiesForPatientByStudyInstanceUID';
|
||||
import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStudy';
|
||||
|
||||
/**
|
||||
* Wraps the PanelStudyBrowser and provides features afforded by managers/services
|
||||
*
|
||||
* @param {object} params
|
||||
* @param {object} commandsManager
|
||||
* @param {object} extensionManager
|
||||
*/
|
||||
function WrappedPanelStudyBrowser({
|
||||
commandsManager,
|
||||
extensionManager,
|
||||
servicesManager,
|
||||
}) {
|
||||
// TODO: This should be made available a different way; route should have
|
||||
// already determined our datasource
|
||||
const dataSource = extensionManager.getDataSources('dicomweb')[0];
|
||||
const _getStudiesForPatientByStudyInstanceUID = getStudiesForPatientByStudyInstanceUID.bind(
|
||||
null,
|
||||
dataSource
|
||||
);
|
||||
const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn(
|
||||
commandsManager.getCommand.bind(commandsManager)
|
||||
);
|
||||
const _requestDisplaySetCreationForStudy = requestDisplaySetCreationForStudy.bind(
|
||||
null,
|
||||
dataSource
|
||||
);
|
||||
|
||||
return (
|
||||
<PanelStudyBrowser
|
||||
DisplaySetService={servicesManager.services.DisplaySetService}
|
||||
dataSource={dataSource}
|
||||
getImageSrc={_getImageSrcFromImageId}
|
||||
getStudiesForPatientByStudyInstanceUID={
|
||||
_getStudiesForPatientByStudyInstanceUID
|
||||
}
|
||||
requestDisplaySetCreationForStudy={_requestDisplaySetCreationForStudy}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
servicesManager: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default WrappedPanelStudyBrowser;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
function requestDisplaySetCreationForStudy(
|
||||
dataSource,
|
||||
DisplaySetService,
|
||||
StudyInstanceUID
|
||||
) {
|
||||
// TODO: is this already short-circuited by the map of Retrieve promises?
|
||||
if (DisplaySetService.hasDisplaySetsForStudy(StudyInstanceUID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataSource.retrieveSeriesMetadata({ StudyInstanceUID });
|
||||
}
|
||||
|
||||
export default requestDisplaySetCreationForStudy;
|
||||
@@ -0,0 +1,4 @@
|
||||
import PanelStudyBrowserTracking from './PanelStudyBrowserTracking';
|
||||
import PanelMeasurementTableTracking from './PanelMeasurementTableTracking';
|
||||
|
||||
export { PanelMeasurementTableTracking, PanelStudyBrowserTracking };
|
||||
Reference in new issue
Block a user