casing change

This commit is contained in:
dannyrb committed 2020-05-26 10:50:59 -04:00
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}
/>
);
}
@@ -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;
}
@@ -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;
@@ -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;
@@ -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 };