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 2023-05-11 10:01:33 -04:00 committed by GitHub
parent cba57a1b65
commit 84cb709b44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 387 additions and 211 deletions

144
DATACITATION.md Normal file
View File

@ -0,0 +1,144 @@
# OHIF public demo data sets
The OHIF Viewer's public demo page, available at https://viewer.ohif.org/, uses publicly anonymized demo datasets.
These datasets were mostly obtained from the [NIH NCI Imaging Data Commons](https://datacommons.cancer.gov/repository/imaging-data-commons)
and [NIH NCI TCIA](https://www.cancerimagingarchive.net/). Before listing the datasets,
we would like to extend a special thank you to all groups who have made their datasets publicly available.
Without them, we would not have been able to create this demo page.
Please find below the list of datasets used on the demo page, along with their respective citations.
## Platforms
### NIH NCI IDC
- Fedorov, A., Longabaugh, W.J., Pot, D., Clunie, D.A., Pieper, S., Aerts, H.J., Homeyer, A., Lewis, R., Akbarzadeh, A., Bontempi, D. and Clifford, W., 2021. NCI imaging data commons. Cancer research, 81(16), p.4188.
### NIH NCI TCIA
- Clark, K., Vendt, B., Smith, K., Freymann, J., Kirby, J., Koppel, P., Moore, S., Phillips, S., Maffitt, D., Pringle, M., Tarbox, L., & Prior, F. (2013). The Cancer Imaging Archive (TCIA): Maintaining and Operating a Public Information Repository. Journal of Digital Imaging, 26(6), 10451057. https://doi.org/10.1007/s10278-013-9622-7
## Datasets
Below you can find the StudyInstanceUID of the studies that are used in the demo page along with their citations.
### 1.3.6.1.4.1.14519.5.2.1.267424821384663813780850856506829388886
Segmentation of Vestibular Schwannoma from Magnetic Resonance Imaging: An Open Annotated Dataset and Baseline Algorithm (Vestibular-Schwannoma-SEG)
- Shapey, J., Kujawa, A., Dorent, R., Wang, G., Bisdas, S., Dimitriadis, A., Grishchuck, D., Paddick, I., Kitchen, N., Bradford, R., Saeed, S., Ourselin, S., & Vercauteren, T. (2021). Segmentation of Vestibular Schwannoma from Magnetic Resonance Imaging: An Open Annotated Dataset and Baseline Algorithm [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.9YTJ-5Q73
- Shapey, J., Kujawa, A., Dorent, R., Wang, G., Dimitriadis, A., Grishchuk, D., Paddick, I., Kitchen, N., Bradford, R., Saeed, S. R., Bisdas, S., Ourselin, S., & Vercauteren, T. (2021). Segmentation of vestibular schwannoma from MRI, an open annotated dataset and baseline algorithm. In Scientific Data (Vol. 8, Issue 1). Springer Science and Business Media LLC. https://doi.org/10.1038/s41597-021-01064-w
### 1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463
### 1.3.6.1.4.1.14519.5.2.1.7009.2403.871108593056125491804754960339
ACRIN-NSCLC-FDG-PET (ACRIN 6668)
- Kinahan, P., Muzi, M., Bialecki, B., Herman, B., & Coombs, L. (2019). Data from the ACRIN 6668 Trial NSCLC-FDG-PET (Version 2) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/tcia.2019.30ilqfcl
- Machtay, M., Duan, F., Siegel, B. A., Snyder, B. S., Gorelick, J. J., Reddin, J. S., Munden, R., Johnson, D. W., Wilf, L. H., DeNittis, A., Sherwin, N., Cho, K. H., Kim, S., Videtic, G., Neumann, D. R., Komaki, R., Macapinlac, H., Bradley, J. D., & Alavi, A. (2013). Prediction of Survival by [18F]Fluorodeoxyglucose Positron Emission Tomography in Patients With Locally Advanced NonSmall-Cell Lung Cancer Undergoing Definitive Chemoradiation Therapy: Results of the ACRIN 6668/RTOG 0235 Trial. In Journal of Clinical Oncology (Vol. 31, Issue 30, pp. 38233830). American Society of Clinical Oncology (ASCO). https://doi.org/10.1200/jco.2012.47.5947
### 2.25.103659964951665749659160840573802789777
The Cancer Genome Atlas Glioblastoma Multiforme Collection (TCGA-GBM)
- Scarpace, L., Mikkelsen, T., Cha, S., Rao, S., Tekchandani, S., Gutman, D., Saltz, J. H., Erickson, B. J., Pedano, N., Flanders, A. E., Barnholtz-Sloan, J., Ostrom, Q., Barboriak, D., & Pierce, L. J. (2016). The Cancer Genome Atlas Glioblastoma Multiforme Collection (TCGA-GBM) (Version 4) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2016.RNYFUYE9
### 1.3.6.1.4.1.14519.5.2.1.256467663913010332776401703474716742458
Abdominal or pelvic enhanced CT images within 10 days before surgery of 230 patients with stage II colorectal cancer (StageII-Colorectal-CT)
- Tong T., Li M. (2022) Abdominal or pelvic enhanced CT images within 10 days before surgery of 230 patients with stage II colorectal cancer (StageII-Colorectal-CT) [Dataset]. The Cancer Imaging Archive. DOI: https://doi.org/10.7937/p5k5-tg43
- Li, M., Gong, J., Bao, Y., Huang, D., Peng, J., & Tong, T. (2022). Special issue “The advance of solid tumor research in China”: Prognosis prediction for stage II colorectal cancer by fusing computed tomography radiomics and deeplearning features of primary lesions and peripheral lymph nodes. In International Journal of Cancer. Wiley. https://doi.org/10.1002/ijc.34053
### 1.3.6.1.4.1.14519.5.2.1.3023.4024.215308722288168917637555384485
The Cancer Genome Atlas Sarcoma Collection (TCGA-SARC)
- Roche, C., Bonaccio, E., & Filippini, J. (2016). The Cancer Genome Atlas Sarcoma Collection (TCGA-SARC) (Version 3) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2016.CX6YLSUX
### 1.3.6.1.4.1.14519.5.2.1.4792.2001.105216574054253895819671475627
BREAST-DIAGNOSIS
- Bloch, B. Nicolas, Jain, Ashali, & Jaffe, C. Carl. (2015). BREAST-DIAGNOSIS [Data set]. The Cancer Imaging Archive. http://doi.org/10.7937/K9/TCIA.2015.SDNRQXXR
### 1.3.6.1.4.1.14519.5.2.1.1706.8374.643249677828306008300337414785
Multimodality annotated HCC cases with and without advanced imaging segmentation (HCC-TACE-Seg)
- Moawad, A. W., Fuentes, D., Morshid, A., Khalaf, A. M., Elmohr, M. M., Abusaif, A., Hazle, J. D., Kaseb, A. O., Hassan, M., Mahvash, A., Szklaruk, J., Qayyom, A., & Elsayes, K. (2021). Multimodality annotated HCC cases with and without advanced imaging segmentation [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.5FNA-0924
- Morshid, A., Elsayes, K. M., Khalaf, A. M., Elmohr, M. M., Yu, J., Kaseb, A. O., Hassan, M., Mahvash, A., Wang, Z., Hazle, J. D., & Fuentes, D. (2019). A Machine Learning Model to Predict Hepatocellular Carcinoma Response to Transcatheter Arterial Chemoembolization. Radiology: Artificial Intelligence, 1(5), e180021. https://doi.org/10.1148/ryai.2019180021
### 1.3.6.1.4.1.14519.5.2.1.1188.2803.137585363493444318569098508293
Ultrasound data of a variety of liver masses (B-mode-and-CEUS-Liver)
- Eisenbrey, J., Lyshchik, A., & Wessner, C. (2021). Ultrasound data of a variety of liver masses [Data set]. The Cancer Imaging Archive. DOI: https://doi.org/10.7937/TCIA.2021.v4z7-tc39
### 1.3.6.1.4.1.32722.99.99.62087908186665265759322018723889952421
NSCLC-Radiomics
- Aerts, H. J. W. L., Wee, L., Rios Velazquez, E., Leijenaar, R. T. H., Parmar, C., Grossmann, P., Carvalho, S., Bussink, J., Monshouwer, R., Haibe-Kains, B., Rietveld, D., Hoebers, F., Rietbergen, M. M., Leemans, C. R., Dekker, A., Quackenbush, J., Gillies, R. J., Lambin, P. (2019). Data From NSCLC-Radiomics (version 4) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2015.PF0M9REI
- Aerts, H. J. W. L., Velazquez, E. R., Leijenaar, R. T. H., Parmar, C., Grossmann, P., Carvalho, S., Bussink, J., Monshouwer, R., Haibe-Kains, B., Rietveld, D., Hoebers, F., Rietbergen, M. M., Leemans, C. R., Dekker, A., Quackenbush, J., Gillies, R. J., Lambin, P. (2014, June 3). Decoding tumour phenotype by noninvasive imaging using a quantitative radiomics approach. Nature Communications. Nature Publishing Group. https://doi.org/10.1038/ncomms5006 (link)
### 1.3.6.1.4.1.14519.5.2.1.3671.4754.298665348758363466150039312520
QIN-PROSTATE-Repeatability
- Fedorov, A; Schwier, M; Clunie, D; Herz, C; Pieper, S; Kikinis, R; Tempany, C; Fennessy, F. (2018). Data From QIN-PROSTATE-Repeatability. The Cancer Imaging Archive. DOI: 10.7937/K9/TCIA.2018.MR1CKGND
- Fedorov A, Vangel MG, Tempany CM, Fennessy FM. Multiparametric Magnetic Resonance Imaging of the Prostate: Repeatability of Volume and Apparent Diffusion Coefficient Quantification. Investigative Radiology. 52, 538546 (2017). DOI: 10.1097/RLI.0000000000000382
- Fedorov, A., Schwier, M., Clunie, D., Herz, C., Pieper, S., Kikinis,R., Tempany, C. & Fennessy, F. An annotated test-retest collection of prostate multiparametric MRI. Scientific Data 5, 180281 (2018). DOI:
### 2.25.141277760791347900862109212450152067508
The Clinical Proteomic Tumor Analysis Consortium Clear Cell Renal Cell Carcinoma Collection (CPTAC-CCRCC)
- National Cancer Institute Clinical Proteomic Tumor Analysis Consortium (CPTAC). (2018). The Clinical Proteomic Tumor Analysis Consortium Clear Cell Renal Cell Carcinoma Collection (CPTAC-CCRCC) (Version 10) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/K9/TCIA.2018.OBLAMN27
- The CPTAC program requests that publications using data from this program include the following statement: “Data used in this publication were generated by the National Cancer Institute Clinical Proteomic Tumor Analysis Consortium (CPTAC).”
### 2.25.275741864483510678566144889372061815320
National Lung Screening Trial
- National Lung Screening Trial Research Team. (2013). Data from the National Lung Screening Trial (NLST) [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.HMQ8-J677
- National Lung Screening Trial Research Team*; Aberle DR, Adams AM, Berg CD, Black WC, Clapp JD, Fagerstrom RM, Gareen IF, Gatsonis C, Marcus PM, Sicks JD (2011). Reduced Lung-Cancer Mortality with Low-Dose Computed Tomographic Screening. New England Journal of Medicine, 365(5), 395409. https://doi.org/10.1056/nejmoa1102873
### 1.3.6.1.4.1.14519.5.2.1.99.1071.26968527900428638961173806140069
Stony Brook University COVID-19 Positive Cases (COVID-19-NY-SBU)
- Saltz, J., Saltz, M., Prasanna, P., Moffitt, R., Hajagos, J., Bremer, E., Balsamo, J., & Kurc, T. (2021). Stony Brook University COVID-19 Positive Cases [Data set]. The Cancer Imaging Archive. https://doi.org/10.7937/TCIA.BBAG-2923

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"
}
}

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",

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;

View File

@ -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,

View File

@ -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,
};

View File

@ -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}
/>
);

View File

@ -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());
})

View File

@ -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;

View File

@ -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;

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,
};

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",

View File

@ -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,
};

View File

@ -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());
})

View File

@ -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}
/>
);

View File

@ -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;

View File

@ -32,7 +32,7 @@
},
"peerDependencies": {
"cornerstone-math": "0.1.9",
"@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",

View File

@ -481,6 +481,9 @@ class MetadataProvider {
imageURI = imageIdToURI(imageId);
}
// remove &frame=number from imageId
imageURI = imageURI.split('&frame=')[0];
const uids = this.imageURIToUIDs.get(imageURI);
let frameNumber = this.getFrameInformationFromURL(imageId) || '1';

View File

@ -63,7 +63,7 @@ export default class CustomizationService extends PubSubService {
modeCustomizations: Record<string, Customization> = {};
globalCustomizations: Record<string, Customization> = {};
configuration: CustomizationConfiguration;
configuration: any;
constructor({ configuration, commandsManager }) {
super(EVENTS);

View File

@ -0,0 +1,67 @@
---
sidebar_position: 8
sidebar_label: Authorization
---
# Authorization
The OHIF Viewer can be configured to work with authorization servers that support one or more of the OpenID-Connect authorization flows. The Viewer finds it's OpenID-Connect settings on the oidc configuration key. You can set these values in your configuration files. For instance you can take a look at our
`google.js` configuration file.
```js
oidc: [
{
// ~ REQUIRED
// Authorization Server URL
authority: 'https://accounts.google.com',
client_id:
'723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com',
redirect_uri: '/callback',
response_type: 'id_token token',
scope:
'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
// ~ OPTIONAL
post_logout_redirect_uri: '/logout-redirect.html',
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
automaticSilentRenew: true,
revokeAccessTokenOnSignout: true,
},
],
```
You need to provide the following information:
- authority: The URL of the authorization server.
- client_id: The client id of your application (provided by the authorization server).
- redirect_uri: The callback URL of your application.
- response_type: The response type of the authorization flow (e.g. id_token token, [learn more about different flows](https://darutk.medium.com/diagrams-of-all-the-openid-connect-flows-6968e3990660)).
- scope: The scopes that your application needs to access
- post_logout_redirect_uri: The URL that the user will be redirected to after logout.
- revoke_uri: The URL that the user will be redirected to after logout.
- automaticSilentRenew: If true, the user will be automatically logged in after the token expires.
- revokeAccessTokenOnSignout: If true, the access token will be revoked on logout.
## How it works
The Viewer uses the `userAuthenticationService` to set the OpenID-Connect settings. The `userAuthenticationService` is a singleton service that is responsible for authentication and authorization. It is initialized by the app and you can grab it
from the `servicesManager`
```js
const userAuthenticationService = servicesManager.services.userAuthenticationService;
```
Then the userAuthenticationService will inject the token as Authorization header in the requests that are sent to the server (both metadata
and pixelData).
## Token based authentication
Sometimes (although not recommended), some servers like to send the token
in the query string. In this case, the viewer will automatically grab the token from the query string
and add it to the userAuthenticationService and remove it from the query string (to prevent it from being logged in the console
in future requests).
and example would be
```js
http://localhost:3000/viewer?StudyInstanceUIDs=1.2.3.4.5.6.6.7&token=e123125jsdfahsdf
```

View File

@ -9,6 +9,23 @@ Throughout the development of the OHIF Viewer, we have participated in various
conferences and "hackathons". In this page, we will provide the presentations
and other resources that we have provided to the community in the past:
## 2023
### SIIM 2023 Tech Tools Webinar | April 12th, 2023
Free, Open Source Tools for Research: MONAI and OHIF Viewer
[[Slides](https://docs.google.com/presentation/d/1afJ5Y9Tzukgn7eAbaO1oiCtN7XvIimFdmZP-HcOUofA/edit?usp=sharing)][[Video](https://www.youtube.com/watch?v=lo8J5w5jUJI)]
### [NA-MIC Project Week 38th 2023 - Remote]
We participated in the 38th Project Week with three projects around OHIF. [[Website](https://projectweek.na-mic.org/PW38_2023_GranCanaria/)]
- PolySeg representations for OHIF Viewer ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_PolySeg/))
- Cross study sychronizer for OHIF Crosshair ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_SyncCrosshair/))
- DATSCAN Viewer implementation in OHIF ([link](https://projectweek.na-mic.org/PW38_2023_GranCanaria/Projects/OHIF_DATSCAN/))
## 2022
### OHIF Demo to Interns

View File

@ -68,7 +68,7 @@
"config-point": "^0.4.8",
"core-js": "^3.16.1",
"cornerstone-math": "^0.1.9",
"@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",

View File

@ -99,7 +99,6 @@ export default function ModeRoute({
const params = useParams();
const searchParams = useSearchParams();
const runTimeHangingProtocolId = searchParams.get('hangingprotocolid');
const [studyInstanceUIDs, setStudyInstanceUIDs] = useState();
const [refresh, setRefresh] = useState(false);
@ -123,6 +122,7 @@ export default function ModeRoute({
const {
displaySetService,
hangingProtocolService,
userAuthenticationService,
} = (servicesManager as ServicesManager).services;
const {
@ -131,6 +131,34 @@ export default function ModeRoute({
hotkeys: hotkeyObj,
hangingProtocol,
} = mode;
const runTimeHangingProtocolId = searchParams.get('hangingprotocolid');
const token = searchParams.get('token');
if (token) {
// if a token is passed in, set the userAuthenticationService to use it
// for the Authorization header for all requests
userAuthenticationService.setServiceImplementation({
getAuthorizationHeader: () => ({
Authorization: 'Bearer ' + token,
}),
});
// Create a URL object with the current location
const urlObj = new URL(
window.location.origin + location.pathname + location.search
);
// Remove the token from the URL object
urlObj.searchParams.delete('token');
const cleanUrl = urlObj.toString();
// Update the browser's history without the token
if (window.history && window.history.replaceState) {
window.history.replaceState(null, '', cleanUrl);
}
}
// Preserve the old array interface for hotkeys
const hotkeys = Array.isArray(hotkeyObj) ? hotkeyObj : hotkeyObj?.hotkeys;
const hotkeyName = hotkeyObj?.name || 'hotkey-definitions-v2';

109
yarn.lock
View File

@ -223,15 +223,6 @@
json5 "^2.2.2"
semver "^6.3.0"
"@babel/eslint-parser@^7.19.1":
version "7.21.3"
resolved "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.21.3.tgz#d79e822050f2de65d7f368a076846e7184234af7"
integrity sha512-kfhmPimwo6k4P8zxNs8+T7yR44q1LdpsZdE1NkCsVlfiuTPRfnGgjaF8Qgug9q9Pou17u6wneYF0lDCZJATMFg==
dependencies:
"@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
eslint-visitor-keys "^2.1.0"
semver "^6.3.0"
"@babel/generator@7.0.0-beta.44":
version "7.0.0-beta.44"
resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42"
@ -1478,54 +1469,43 @@
resolved "https://registry.npmjs.org/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.2.tgz#e96721d56f6ec96f7f95c16321d88cc8467d8d81"
integrity sha512-lgdvBvvNezleY+4pIe2ceUsJzlZe/0PipdeubQ3vZZOz3xxtHHMR1XFCl4fgd8gosR8COHuD7h6q+MwgrwBsng==
"@cornerstonejs/core@^0.44.0":
version "0.44.0"
resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.44.0.tgz#f1a9af5407b25510e91e3e17b55dd9f3c61c557b"
integrity sha512-EZURQAuILsmiUdp5nusUpL6pJjteVgY7DqrqkFSqeeSlDjt2j+0h+kFvvbpRAhjyWz/1OjK9vtvbRFCpwhnzJw==
dependencies:
detect-gpu "^4.0.45"
lodash.clonedeep "4.5.0"
"@cornerstonejs/core@^0.44.2":
version "0.44.2"
resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.44.2.tgz#13febc0455c833b029fac4a00a2e98f67ef50609"
integrity sha512-McwG04aFbxhA8LAAgZuh4ttFFW35CMvHwNrfYnQqaExCb62w1lzQBIHDopX05aGe9VAmmIRhaAuhaxxnH8ZsfA==
"@cornerstonejs/core@^0.46.2":
version "0.46.2"
resolved "https://registry.npmjs.org/@cornerstonejs/core/-/core-0.46.2.tgz#89ba0d27f491aab8b0a22d15ce5ed601ee23d2ec"
integrity sha512-HHActbdv3np/QXmQPl+DOqVMXPeOXbkqDov1SagF1om6Me4V7O8N2HbM4CgKCdf2VrEId6h0BL3+MG3Cxn7LPQ==
dependencies:
"@kitware/vtk.js" "27.3.1"
detect-gpu "^5.0.22"
gl-matrix "^3.4.3"
lodash.clonedeep "4.5.0"
"@cornerstonejs/dicom-image-loader@^0.4.0":
version "0.4.0"
resolved "https://registry.npmjs.org/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-0.4.0.tgz#c7f5c5659a10a6f4f5e96367ab6d27784daeb424"
integrity sha512-ORnF/4qAbTninkesz7ADQu832PW+/xcbUhxSPU1ZPukjr9cglynsDzKjsEYJlY10+rWzbIQ+JEgefrXZgVoT1g==
"@cornerstonejs/dicom-image-loader@^0.6.4":
version "0.6.4"
resolved "https://registry.npmjs.org/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-0.6.4.tgz#8020402b11dd78df52957345ca57ed8c05b2999b"
integrity sha512-+y9NklTFYlq61frxKcJ2u+0h9zfKKiv20N28N5qBr3Ie6CFcNrY51ETkujNnNKKRUBvxXDidjVdY+j+MSPPJKQ==
dependencies:
"@babel/eslint-parser" "^7.19.1"
"@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
"@cornerstonejs/codec-openjpeg" "^1.2.2"
"@cornerstonejs/codec-openjph" "^2.4.2"
"@cornerstonejs/core" "^0.44.0"
coverage-istanbul-loader "^3.0.5"
date-format "^4.0.14"
"@cornerstonejs/core" "^0.46.2"
dicom-parser "^1.8.9"
pako "^2.0.4"
uuid "^9.0.0"
"@cornerstonejs/streaming-image-volume-loader@^0.19.2":
version "0.19.2"
resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.19.2.tgz#2d83fda24d9fe7a4a1b8fda334a82bddd000fb40"
integrity sha512-N2ws5GBsBnvdQB1gNI03WdiF71LeL5CGcdmkgAZMqFdtiiNbOlf0jW8ZmDwALY3+eL8kv2viHM3aLraXVT9Wwg==
"@cornerstonejs/streaming-image-volume-loader@^0.20.2":
version "0.20.2"
resolved "https://registry.npmjs.org/@cornerstonejs/streaming-image-volume-loader/-/streaming-image-volume-loader-0.20.2.tgz#0df2ed5afd012d2851356af01eed48646d3a6c60"
integrity sha512-2TYoJ0wpE9KDpn8adfmTq0xOb/Xg7jhEyOuBnrr3il/NFvOfMlJieJVCYM0v3hyoANfqmDcFBqqtlm346VOMdw==
dependencies:
"@cornerstonejs/core" "^0.44.2"
"@cornerstonejs/core" "^0.46.2"
"@cornerstonejs/tools@^0.66.2":
version "0.66.2"
resolved "https://registry.npmjs.org/@cornerstonejs/tools/-/tools-0.66.2.tgz#58e76ea1c239bcf87a7c00ff661429804e09533a"
integrity sha512-ykSqhjJUIjgFDi9Ua0HwDTbXip8IMeeyM/3oMUlIINKURwvhipzJY6eWrmgwFzIlsXID4oLFPZq3V/V9lSqkaQ==
"@cornerstonejs/tools@^0.67.2":
version "0.67.2"
resolved "https://registry.npmjs.org/@cornerstonejs/tools/-/tools-0.67.2.tgz#7b7c6aac4ec7eaa84a438198837e657328e3c8a2"
integrity sha512-nOg2byAVrdMj3DqLYVT7qbrl2Sz6V82xtfFYJ4oQeGul/u/1AvHj4SSZs7MTV63Li3KP2ax/ZghCjral+exaVg==
dependencies:
"@cornerstonejs/core" "^0.44.2"
"@cornerstonejs/core" "^0.46.2"
lodash.clonedeep "4.5.0"
lodash.get "^4.4.2"
@ -2721,17 +2701,6 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@jsdevtools/coverage-istanbul-loader@3.0.5":
version "3.0.5"
resolved "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz#2a4bc65d0271df8d4435982db4af35d81754ee26"
integrity sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==
dependencies:
convert-source-map "^1.7.0"
istanbul-lib-instrument "^4.0.3"
loader-utils "^2.0.0"
merge-source-map "^1.1.0"
schema-utils "^2.7.0"
"@juggle/resize-observer@^3.3.1":
version "3.4.0"
resolved "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
@ -2955,13 +2924,6 @@
pump "^3.0.0"
tar-fs "^2.1.1"
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==
dependencies:
eslint-scope "5.1.1"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@ -7805,13 +7767,6 @@ cosmiconfig@^8.1.3:
parse-json "^5.0.0"
path-type "^4.0.0"
coverage-istanbul-loader@^3.0.5:
version "3.0.5"
resolved "https://registry.npmjs.org/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz#bf942efc0f4e3ac27565203c17dca5008eae6637"
integrity sha512-xsw2phF0VNqUPk47V/vHXkdcTyl0tkMSmaZfLrTOhoPhPMXFelNju7utl5s7I93KXzipqDEK0YwofQSSflPz8A==
dependencies:
"@jsdevtools/coverage-istanbul-loader" "3.0.5"
cross-env@^5.2.0:
version "5.2.1"
resolved "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz#b2c76c1ca7add66dc874d11798466094f551b34d"
@ -8263,11 +8218,6 @@ date-fns@^1.27.2:
resolved "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
date-format@^4.0.14:
version "4.0.14"
resolved "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400"
integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==
dateformat@^3.0.0:
version "3.0.3"
resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
@ -8569,7 +8519,7 @@ detab@2.0.4:
dependencies:
repeat-string "^1.5.4"
detect-gpu@^4.0.16, detect-gpu@^4.0.45:
detect-gpu@^4.0.16:
version "4.0.50"
resolved "https://registry.npmjs.org/detect-gpu/-/detect-gpu-4.0.50.tgz#ba9df7e6b8416b1160303811015102bbad283aa0"
integrity sha512-T67HE5+ONONN8rPXCBJPupyCg2QT8+l2NUUMuPxAppsMJBDPG/Jg0URLs6GyDzLm2niUE+oncIHSuy3VinoPeQ==
@ -9543,7 +9493,7 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0:
eslint-visitor-keys@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
@ -12369,16 +12319,6 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
istanbul-lib-instrument@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==
dependencies:
"@babel/core" "^7.7.5"
"@istanbuljs/schema" "^0.1.2"
istanbul-lib-coverage "^3.0.0"
semver "^6.3.0"
istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
version "5.2.1"
resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
@ -14070,13 +14010,6 @@ merge-descriptors@1.0.1:
resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
merge-source-map@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
dependencies:
source-map "^0.6.1"
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"