feat(panel and auth): fix study panel to not rely on priors and add token param to url (#3381)

* fix: multiframe

* fix the unnecessary use of prior since some servers dont like it

* add authentication via token in url

* add data citation

* add resources

* upgrade packages

* yarn lock

* apply review comments
This commit is contained in:
Alireza authored and GitHub committed 2023-05-11 10:01:33 -04:00
1 parent cba57a1b65
commit 84cb709b44
24 files changed
+387 -211

No files matched your search

+2 -2
View File
@@ -46,7 +46,7 @@
"@babel/runtime": "^7.20.13",
"classnames": "^2.3.2",
"@cornerstonejs/adapters": "^0.6.0",
"@cornerstonejs/core": "^0.44.2",
"@cornerstonejs/tools": "^0.66.2"
"@cornerstonejs/core": "^0.46.2",
"@cornerstonejs/tools": "^0.67.2"
}
}
+4 -4
View File
@@ -34,7 +34,7 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0",
"@cornerstonejs/dicom-image-loader": "^0.4.0",
"@cornerstonejs/dicom-image-loader": "^0.6.4",
"@cornerstonejs/codec-charls": "^1.2.3",
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
@@ -53,9 +53,9 @@
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^0.6.0",
"@cornerstonejs/core": "^0.44.2",
"@cornerstonejs/streaming-image-volume-loader": "^0.19.2",
"@cornerstonejs/tools": "^0.66.2",
"@cornerstonejs/core": "^0.46.2",
"@cornerstonejs/streaming-image-volume-loader": "^0.20.2",
"@cornerstonejs/tools": "^0.67.2",
"@kitware/vtk.js": "27.3.1",
"html2canvas": "^1.4.1",
"lodash.debounce": "4.0.8",
+2 -1
View File
@@ -23,6 +23,7 @@ import * as CornerstoneExtensionTypes from './types';
import { toolNames } from './initCornerstoneTools';
import { getEnabledElement, reset as enabledElementReset } from './state';
import dicomLoaderService from './utils/dicomLoaderService';
import getActiveViewportEnabledElement from './utils/getActiveViewportEnabledElement';
import { registerColormap } from './utils/colormap/transferFunctionHelpers';
import { id } from './id';
@@ -141,5 +142,5 @@ const cornerstoneExtension: Types.Extensions.Extension = {
};
export type { PublicViewportOptions };
export { measurementMappingUtils, CornerstoneExtensionTypes, toolNames };
export { measurementMappingUtils, CornerstoneExtensionTypes, toolNames , getActiveViewportEnabledElement};
export default cornerstoneExtension;
@@ -200,6 +200,7 @@ function createDicomLocalApi(dicomLocalConfig) {
displaySet.images.forEach(instance => {
const NumberOfFrames = instance.NumberOfFrames;
if (NumberOfFrames > 1) {
// in multiframe we start at frame 1
for (let i = 1; i <= NumberOfFrames; i++) {
const imageId = this.getImageIdsForInstance({
instance,
@@ -12,7 +12,7 @@ const { sortStudyInstances, formatDate } = utils;
function PanelStudyBrowser({
servicesManager,
getImageSrc,
getStudiesForPatientByStudyInstanceUID,
getStudiesForPatientByMRN,
requestDisplaySetCreationForStudy,
dataSource,
}) {
@@ -64,12 +64,24 @@ function PanelStudyBrowser({
useEffect(() => {
// Fetch all studies for the patient in each primary study
async function fetchStudiesForPatient(StudyInstanceUID) {
const qidoStudiesForPatient =
(await getStudiesForPatientByStudyInstanceUID(StudyInstanceUID)) || [];
// current study qido
const qidoForStudyUID = await dataSource.query.studies.search({
studyInstanceUid: StudyInstanceUID,
});
let qidoStudiesForPatient = qidoForStudyUID;
// try to fetch the prior studies based on the patientID if the
// server can respond.
try {
qidoStudiesForPatient = await getStudiesForPatientByMRN(
qidoForStudyUID
);
} catch (error) {
console.warn(error);
}
// TODO: This should be "naturalized DICOM JSON" studies
const mappedStudies = _mapDataSourceStudies(qidoStudiesForPatient);
const actuallyMappedStudies = mappedStudies.map(qidoStudy => {
return {
studyInstanceUid: qidoStudy.StudyInstanceUID,
@@ -77,29 +89,27 @@ function PanelStudyBrowser({
description: qidoStudy.StudyDescription,
modalities: qidoStudy.ModalitiesInStudy,
numInstances: qidoStudy.NumInstances,
// displaySets: []
};
});
if (isMounted.current) {
setStudyDisplayList(prevArray => {
const ret = [...prevArray];
for (const study of actuallyMappedStudies) {
if (
!prevArray.find(
it => it.studyInstanceUid === study.studyInstanceUid
)
) {
ret.push(study);
}
setStudyDisplayList(prevArray => {
const ret = [...prevArray];
for (const study of actuallyMappedStudies) {
if (
!prevArray.find(
it => it.studyInstanceUid === study.studyInstanceUid
)
) {
ret.push(study);
}
return ret;
});
}
}
return ret;
});
}
StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [StudyInstanceUIDs, getStudiesForPatientByStudyInstanceUID]);
}, [StudyInstanceUIDs, getStudiesForPatientByMRN]);
// // ~~ Initial Thumbnails
useEffect(() => {
@@ -254,7 +264,7 @@ PanelStudyBrowser.propTypes = {
getImageIdsForDisplaySet: PropTypes.func.isRequired,
}).isRequired,
getImageSrc: PropTypes.func.isRequired,
getStudiesForPatientByStudyInstanceUID: PropTypes.func.isRequired,
getStudiesForPatientByMRN: PropTypes.func.isRequired,
requestDisplaySetCreationForStudy: PropTypes.func.isRequired,
};
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
//
import PanelStudyBrowser from './PanelStudyBrowser';
import getImageSrcFromImageId from './getImageSrcFromImageId';
import getStudiesForPatientByStudyInstanceUID from './getStudiesForPatientByStudyInstanceUID';
import getStudiesForPatientByMRN from './getStudiesForPatientByMRN';
import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStudy';
/**
@@ -21,7 +21,7 @@ function WrappedPanelStudyBrowser({
// TODO: This should be made available a different way; route should have
// already determined our datasource
const dataSource = extensionManager.getDataSources()[0];
const _getStudiesForPatientByStudyInstanceUID = getStudiesForPatientByStudyInstanceUID.bind(
const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind(
null,
dataSource
);
@@ -38,9 +38,7 @@ function WrappedPanelStudyBrowser({
servicesManager={servicesManager}
dataSource={dataSource}
getImageSrc={_getImageSrcFromImageId}
getStudiesForPatientByStudyInstanceUID={
_getStudiesForPatientByStudyInstanceUID
}
getStudiesForPatientByMRN={_getStudiesForPatientByMRN}
requestDisplaySetCreationForStudy={_requestDisplaySetCreationForStudy}
/>
);
@@ -6,7 +6,7 @@ function getImageSrcFromImageId(cornerstone, imageId) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
cornerstone.utilities
.loadImageToCanvas(canvas, imageId)
.loadImageToCanvas({ canvas, imageId })
.then(imageId => {
resolve(canvas.toDataURL());
})
@@ -0,0 +1,11 @@
async function getStudiesForPatientByMRN(dataSource, qidoForStudyUID) {
if (qidoForStudyUID && qidoForStudyUID.length && qidoForStudyUID[0].mrn) {
return dataSource.query.studies.search({
patientId: qidoForStudyUID[0].mrn,
});
}
console.log('No mrn found for', qidoForStudyUID);
return qidoForStudyUID;
}
export default getStudiesForPatientByMRN;
@@ -1,29 +0,0 @@
async function getStudiesForPatientByStudyInstanceUID(
dataSource,
StudyInstanceUID
) {
if (StudyInstanceUID === undefined) {
return;
}
// 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 @cornerstonejs/dicom-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,
});
}
console.log('No mrn found for', getStudyResult);
// The original study we KNOW belongs to the same set, so just return it
return getStudyResult;
}
export default getStudiesForPatientByStudyInstanceUID;
+7 -3
View File
@@ -7,7 +7,7 @@ import getSopClassHandlerModule from './getSopClassHandlerModule.js';
import getToolbarModule from './getToolbarModule';
import getCommandsModule from './commandsModule';
import getHangingProtocolModule from './getHangingProtocolModule';
import getStudiesForPatientByStudyInstanceUID from './Panels/getStudiesForPatientByStudyInstanceUID';
import getStudiesForPatientByMRN from './Panels/getStudiesForPatientByMRN';
import getCustomizationModule from './getCustomizationModule';
import { id } from './id.js';
import preRegistration from './init';
@@ -34,7 +34,7 @@ const defaultExtension: Types.Extensions.Extension = {
{
name: 'common',
exports: {
getStudiesForPatientByStudyInstanceUID,
getStudiesForPatientByMRN,
},
},
];
@@ -45,4 +45,8 @@ const defaultExtension: Types.Extensions.Extension = {
export default defaultExtension;
export { ContextMenuController, CustomizeableContextMenuTypes };
export {
ContextMenuController,
CustomizeableContextMenuTypes,
getStudiesForPatientByMRN,
};
+2 -2
View File
@@ -32,8 +32,8 @@
"peerDependencies": {
"@ohif/core": "^3.0.0",
"classnames": "^2.3.2",
"@cornerstonejs/core": "^0.44.2",
"@cornerstonejs/tools": "^0.66.2",
"@cornerstonejs/core": "^0.46.2",
"@cornerstonejs/tools": "^0.67.2",
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
"dcmjs": "^0.29.5",
"lodash.debounce": "^4.17.21",
@@ -18,7 +18,7 @@ const { formatDate } = utils;
function PanelStudyBrowserTracking({
servicesManager,
getImageSrc,
getStudiesForPatientByStudyInstanceUID,
getStudiesForPatientByMRN,
requestDisplaySetCreationForStudy,
dataSource,
}) {
@@ -76,8 +76,6 @@ function PanelStudyBrowserTracking({
const activeViewportDisplaySetInstanceUIDs =
viewports[activeViewportIndex]?.displaySetInstanceUIDs;
const isSingleViewport = numCols === 1 && numRows === 1;
useEffect(() => {
const added = measurementService.EVENTS.MEASUREMENT_ADDED;
const addedRaw = measurementService.EVENTS.RAW_MEASUREMENT_ADDED;
@@ -108,15 +106,29 @@ function PanelStudyBrowserTracking({
};
}, [measurementService, activeViewportIndex, sendTrackedMeasurementsEvent]);
const { trackedStudy, trackedSeries } = trackedMeasurements.context;
const { trackedSeries } = trackedMeasurements.context;
// ~~ 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
// current study qido
const qidoForStudyUID = await dataSource.query.studies.search({
studyInstanceUid: StudyInstanceUID,
});
let qidoStudiesForPatient = qidoForStudyUID;
// try to fetch the prior studies based on the patientID if the
// server can respond.
try {
qidoStudiesForPatient = await getStudiesForPatientByMRN(
qidoForStudyUID
);
} catch (error) {
console.warn(error);
}
const mappedStudies = _mapDataSourceStudies(qidoStudiesForPatient);
const actuallyMappedStudies = mappedStudies.map(qidoStudy => {
return {
@@ -125,7 +137,6 @@ function PanelStudyBrowserTracking({
description: qidoStudy.StudyDescription,
modalities: qidoStudy.ModalitiesInStudy,
numInstances: qidoStudy.NumInstances,
// displaySets: []
};
});
@@ -146,11 +157,16 @@ function PanelStudyBrowserTracking({
StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [StudyInstanceUIDs, getStudiesForPatientByStudyInstanceUID]);
}, [StudyInstanceUIDs, getStudiesForPatientByMRN]);
// ~~ Initial Thumbnails
useEffect(() => {
const currentDisplaySets = displaySetService.activeDisplaySets;
if (!currentDisplaySets.length) {
return;
}
currentDisplaySets.forEach(async dSet => {
const newImageSrcEntry = {};
const displaySet = displaySetService.getDisplaySetByUID(
@@ -170,13 +186,16 @@ function PanelStudyBrowserTracking({
});
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [displaySetService, dataSource, getImageSrc]);
// ~~ displaySets
useEffect(() => {
// TODO: Are we sure `activeDisplaySets` will always be accurate?
const currentDisplaySets = displaySetService.activeDisplaySets;
if (!currentDisplaySets.length) {
return;
}
const mappedDisplaySets = _mapDisplaySets(
currentDisplaySets,
thumbnailImageSrcMap,
@@ -194,9 +213,9 @@ function PanelStudyBrowserTracking({
}, [
displaySetService.activeDisplaySets,
trackedSeries,
thumbnailImageSrcMap,
viewports,
dataSource,
thumbnailImageSrcMap,
]);
// ~~ subscriptions --> displaySets
@@ -379,7 +398,7 @@ PanelStudyBrowserTracking.propTypes = {
getImageIdsForDisplaySet: PropTypes.func.isRequired,
}).isRequired,
getImageSrc: PropTypes.func.isRequired,
getStudiesForPatientByStudyInstanceUID: PropTypes.func.isRequired,
getStudiesForPatientByMRN: PropTypes.func.isRequired,
requestDisplaySetCreationForStudy: PropTypes.func.isRequired,
};
@@ -6,7 +6,7 @@ function getImageSrcFromImageId(cornerstone, imageId) {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
cornerstone.utilities
.loadImageToCanvas(canvas, imageId)
.loadImageToCanvas({ canvas, imageId })
.then(imageId => {
resolve(canvas.toDataURL());
})
@@ -10,8 +10,8 @@ function _getStudyForPatientUtility(extensionManager) {
'@ohif/extension-default.utilityModule.common'
);
const { getStudiesForPatientByStudyInstanceUID } = utilityModule.exports;
return getStudiesForPatientByStudyInstanceUID;
const { getStudiesForPatientByMRN } = utilityModule.exports;
return getStudiesForPatientByMRN;
}
/**
@@ -28,10 +28,10 @@ function WrappedPanelStudyBrowserTracking({
}) {
const dataSource = extensionManager.getActiveDataSource()[0];
const getStudiesForPatientByStudyInstanceUID = _getStudyForPatientUtility(
const getStudiesForPatientByMRN = _getStudyForPatientUtility(
extensionManager
);
const _getStudiesForPatientByStudyInstanceUID = getStudiesForPatientByStudyInstanceUID.bind(
const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind(
null,
dataSource
);
@@ -48,9 +48,7 @@ function WrappedPanelStudyBrowserTracking({
servicesManager={servicesManager}
dataSource={dataSource}
getImageSrc={_getImageSrcFromImageId}
getStudiesForPatientByStudyInstanceUID={
_getStudiesForPatientByStudyInstanceUID
}
getStudiesForPatientByMRN={_getStudiesForPatientByMRN}
requestDisplaySetCreationForStudy={_requestDisplaySetCreationForStudy}
/>
);
@@ -1,29 +0,0 @@
async function getStudiesForPatientByStudyInstanceUID(
dataSource,
StudyInstanceUID
) {
if (StudyInstanceUID === undefined) {
return;
}
// 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 @cornerstonejs/dicom-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,
});
}
console.log('No mrn found for', getStudyResult);
// The original study we KNOW belongs to the same set, so just return it
return getStudyResult;
}
export default getStudiesForPatientByStudyInstanceUID;