feat: Support for local DICOM drag and drop (#2445)
* Added drag and drop from local and local data source * fix: dicom SR drag and drop from local
This commit is contained in:
parent
99b8dc4759
commit
fdb6ca41d6
@ -43,7 +43,7 @@ function createDicomJSONApi(dicomJsonConfig) {
|
|||||||
const { name } = dicomJsonConfig;
|
const { name } = dicomJsonConfig;
|
||||||
|
|
||||||
const implementation = {
|
const implementation = {
|
||||||
parseRouteParams: async ({ params, query, url }) => {
|
initialize: async ({ params, query, url }) => {
|
||||||
if (!url) url = query.get('url');
|
if (!url) url = query.get('url');
|
||||||
let metaData = getMetaDataByURL(url);
|
let metaData = getMetaDataByURL(url);
|
||||||
|
|
||||||
@ -93,11 +93,12 @@ function createDicomJSONApi(dicomJsonConfig) {
|
|||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
studies: {
|
studies: {
|
||||||
mapParams: () => {},
|
mapParams: () => { },
|
||||||
search: async param => {
|
search: async param => {
|
||||||
const [key, value] = Object.entries(param)[0];
|
const [key, value] = Object.entries(param)[0];
|
||||||
const mappedParam = mappings[key];
|
const mappedParam = mappings[key];
|
||||||
|
|
||||||
|
// todo: should fetch from dicomMetadataStore
|
||||||
const studies = findStudies(mappedParam, value);
|
const studies = findStudies(mappedParam, value);
|
||||||
|
|
||||||
return studies.map(aStudy => {
|
return studies.map(aStudy => {
|
||||||
|
|||||||
204
extensions/default/src/DicomLocalDataSource/index.js
Normal file
204
extensions/default/src/DicomLocalDataSource/index.js
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core'
|
||||||
|
import OHIF from '@ohif/core'
|
||||||
|
import dcmjs from 'dcmjs';
|
||||||
|
|
||||||
|
const metadataProvider = OHIF.classes.MetadataProvider
|
||||||
|
const { EVENTS } = DicomMetadataStore
|
||||||
|
|
||||||
|
// Sorting SR modalities to be at the end of series list
|
||||||
|
function customSort(seriesA, seriesB) {
|
||||||
|
const modalityA = seriesA.instances[0].Modality
|
||||||
|
const modalityB = seriesB.instances[0].Modality
|
||||||
|
|
||||||
|
if (modalityA === "SR") {
|
||||||
|
return +1;
|
||||||
|
}
|
||||||
|
if (modalityB === "SR") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createDicomLocalApi(dicomLocalConfig) {
|
||||||
|
const { name } = dicomLocalConfig
|
||||||
|
|
||||||
|
const implementation = {
|
||||||
|
initialize: ({ params, query }) => {
|
||||||
|
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params
|
||||||
|
const queryStudyInstanceUIDs = query.get('StudyInstanceUIDs')
|
||||||
|
|
||||||
|
const StudyInstanceUIDs =
|
||||||
|
queryStudyInstanceUIDs || paramsStudyInstanceUIDs
|
||||||
|
const StudyInstanceUIDsAsArray =
|
||||||
|
StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs)
|
||||||
|
? StudyInstanceUIDs
|
||||||
|
: [StudyInstanceUIDs]
|
||||||
|
|
||||||
|
|
||||||
|
// Put SRs at the end of series list to make sure images are loaded first
|
||||||
|
StudyInstanceUIDsAsArray.forEach(StudyInstanceUID => {
|
||||||
|
const study = DicomMetadataStore.getStudy(StudyInstanceUID)
|
||||||
|
study.series = study.series.sort(customSort)
|
||||||
|
})
|
||||||
|
|
||||||
|
return StudyInstanceUIDsAsArray
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
studies: {
|
||||||
|
mapParams: () => { },
|
||||||
|
search: (params) => {
|
||||||
|
const studyUIDs = DicomMetadataStore.getStudyInstanceUIDs()
|
||||||
|
|
||||||
|
return studyUIDs.map(StudyInstanceUID => {
|
||||||
|
let numInstances = 0
|
||||||
|
const modalities = new Set()
|
||||||
|
|
||||||
|
// Calculating the number of instances in the study and modalities
|
||||||
|
// present in the study
|
||||||
|
const study = DicomMetadataStore.getStudy(StudyInstanceUID)
|
||||||
|
study.series.forEach(aSeries => {
|
||||||
|
numInstances += aSeries.instances.length
|
||||||
|
modalities.add(aSeries.Modality);
|
||||||
|
})
|
||||||
|
|
||||||
|
// first instance in the first series
|
||||||
|
const firstInstance = study?.series[0]?.instances[0]
|
||||||
|
|
||||||
|
if (firstInstance) {
|
||||||
|
return {
|
||||||
|
accession: firstInstance.AccessionNumber,
|
||||||
|
date: firstInstance.StudyDate,
|
||||||
|
description: firstInstance.StudyDescription,
|
||||||
|
mrn: firstInstance.PatientID,
|
||||||
|
patientName: { Alphabetic: firstInstance.PatientName },
|
||||||
|
studyInstanceUid: firstInstance.StudyInstanceUID,
|
||||||
|
time: firstInstance.StudyTime,
|
||||||
|
//
|
||||||
|
instances: numInstances,
|
||||||
|
modalities: Array.from(modalities).join('/'),
|
||||||
|
NumInstances: numInstances,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
processResults: () => {
|
||||||
|
console.debug(' DICOMLocal QUERY processResults')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: {
|
||||||
|
// mapParams: mapParams.bind(),
|
||||||
|
search: () => {
|
||||||
|
console.debug(' DICOMLocal QUERY SERIES SEARCH')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
instances: {
|
||||||
|
search: () => {
|
||||||
|
console.debug(' DICOMLocal QUERY instances SEARCH')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
retrieve: {
|
||||||
|
series: {
|
||||||
|
metaData: () => {
|
||||||
|
console.debug(' DICOMLocal retrieve series metadata')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
store: {
|
||||||
|
dicom: (naturalizedReport) => {
|
||||||
|
const reportBlob = dcmjs.data.datasetToBlob(naturalizedReport);
|
||||||
|
|
||||||
|
//Create a URL for the binary.
|
||||||
|
var objectUrl = URL.createObjectURL(reportBlob);
|
||||||
|
window.location.assign(objectUrl);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
retrieveSeriesMetadata: async ({
|
||||||
|
StudyInstanceUID,
|
||||||
|
madeInClient = false,
|
||||||
|
} = {}) => {
|
||||||
|
if (!StudyInstanceUID) {
|
||||||
|
throw new Error(
|
||||||
|
'Unable to query for SeriesMetadata without StudyInstanceUID'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instances metadata already added via local upload
|
||||||
|
const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient)
|
||||||
|
|
||||||
|
// Series metadata already added via local upload
|
||||||
|
DicomMetadataStore._broadcastEvent(EVENTS.SERIES_ADDED, {
|
||||||
|
StudyInstanceUID,
|
||||||
|
madeInClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
study.series.forEach((aSeries) => {
|
||||||
|
const { SeriesInstanceUID } = aSeries
|
||||||
|
|
||||||
|
aSeries.instances.forEach((instance) => {
|
||||||
|
const {
|
||||||
|
url: imageId,
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
} = instance
|
||||||
|
|
||||||
|
// Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI.
|
||||||
|
metadataProvider.addImageIdToUIDs(imageId, {
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
SOPInstanceUID,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
DicomMetadataStore._broadcastEvent(EVENTS.INSTANCES_ADDED, {
|
||||||
|
StudyInstanceUID,
|
||||||
|
SeriesInstanceUID,
|
||||||
|
madeInClient,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
getImageIdsForDisplaySet(displaySet) {
|
||||||
|
const images = displaySet.images
|
||||||
|
const imageIds = []
|
||||||
|
|
||||||
|
if (!images) {
|
||||||
|
return imageIds
|
||||||
|
}
|
||||||
|
|
||||||
|
displaySet.images.forEach((instance) => {
|
||||||
|
const NumberOfFrames = instance.NumberOfFrames
|
||||||
|
|
||||||
|
if (NumberOfFrames > 1) {
|
||||||
|
for (let i = 0; i < NumberOfFrames; i++) {
|
||||||
|
const imageId = this.getImageIdsForInstance({
|
||||||
|
instance,
|
||||||
|
frame: i,
|
||||||
|
})
|
||||||
|
imageIds.push(imageId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const imageId = this.getImageIdsForInstance({ instance })
|
||||||
|
imageIds.push(imageId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return imageIds
|
||||||
|
},
|
||||||
|
getImageIdsForInstance({ instance, frame }) {
|
||||||
|
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance
|
||||||
|
const storedInstance = DicomMetadataStore.getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID)
|
||||||
|
if (storedInstance.url) {
|
||||||
|
return storedInstance.url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteStudyMetadataPromise() {
|
||||||
|
console.log("deleteStudyMetadataPromise not implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return IWebApiDataSource.create(implementation)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createDicomLocalApi }
|
||||||
@ -63,7 +63,7 @@ function createDicomWebApi(dicomWebConfig) {
|
|||||||
const wadoDicomWebClient = new api.DICOMwebClient(wadoConfig);
|
const wadoDicomWebClient = new api.DICOMwebClient(wadoConfig);
|
||||||
|
|
||||||
const implementation = {
|
const implementation = {
|
||||||
parseRouteParams: ({ params, query }) => {
|
initialize: ({ params, query }) => {
|
||||||
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params;
|
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params;
|
||||||
const queryStudyInstanceUIDs = query.get('StudyInstanceUIDs');
|
const queryStudyInstanceUIDs = query.get('StudyInstanceUIDs');
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ function createDicomWebApi(dicomWebConfig) {
|
|||||||
query: {
|
query: {
|
||||||
studies: {
|
studies: {
|
||||||
mapParams: mapParams.bind(),
|
mapParams: mapParams.bind(),
|
||||||
search: async function(origParams) {
|
search: async function (origParams) {
|
||||||
const { studyInstanceUid, seriesInstanceUid, ...mappedParams } =
|
const { studyInstanceUid, seriesInstanceUid, ...mappedParams } =
|
||||||
mapParams(origParams, {
|
mapParams(origParams, {
|
||||||
supportsFuzzyMatching,
|
supportsFuzzyMatching,
|
||||||
@ -98,7 +98,7 @@ function createDicomWebApi(dicomWebConfig) {
|
|||||||
},
|
},
|
||||||
series: {
|
series: {
|
||||||
// mapParams: mapParams.bind(),
|
// mapParams: mapParams.bind(),
|
||||||
search: async function(studyInstanceUid) {
|
search: async function (studyInstanceUid) {
|
||||||
const results = await seriesInStudy(
|
const results = await seriesInStudy(
|
||||||
qidoDicomWebClient,
|
qidoDicomWebClient,
|
||||||
studyInstanceUid
|
studyInstanceUid
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { createDicomWebApi } from './DicomWebDataSource/index.js';
|
import { createDicomWebApi } from './DicomWebDataSource/index.js';
|
||||||
import { createDicomJSONApi } from './DicomJSONDataSource/index.js';
|
import { createDicomJSONApi } from './DicomJSONDataSource/index.js';
|
||||||
|
import { createDicomLocalApi } from './DicomLocalDataSource/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -20,6 +21,11 @@ function getDataSourcesModule() {
|
|||||||
type: 'jsonApi',
|
type: 'jsonApi',
|
||||||
createDataSource: createDicomJSONApi,
|
createDataSource: createDicomJSONApi,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'dicomlocal',
|
||||||
|
type: 'localApi',
|
||||||
|
createDataSource: createDicomLocalApi,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ function create({
|
|||||||
retrieve,
|
retrieve,
|
||||||
store,
|
store,
|
||||||
reject,
|
reject,
|
||||||
parseRouteParams,
|
initialize,
|
||||||
retrieveSeriesMetadata,
|
retrieveSeriesMetadata,
|
||||||
deleteStudyMetadataPromise,
|
deleteStudyMetadataPromise,
|
||||||
getImageIdsForDisplaySet,
|
getImageIdsForDisplaySet,
|
||||||
@ -38,7 +38,7 @@ function create({
|
|||||||
* @param {number} params.resultsPerPage
|
* @param {number} params.resultsPerPage
|
||||||
*/
|
*/
|
||||||
mapParams: params => params,
|
mapParams: params => params,
|
||||||
requestResults: () => {},
|
requestResults: () => { },
|
||||||
processResults: results => results,
|
processResults: results => results,
|
||||||
},
|
},
|
||||||
series: {},
|
series: {},
|
||||||
@ -63,7 +63,7 @@ function create({
|
|||||||
retrieve: retrieve || defaultRetrieve,
|
retrieve: retrieve || defaultRetrieve,
|
||||||
reject: reject || defaultReject,
|
reject: reject || defaultReject,
|
||||||
store: store || defaultStore,
|
store: store || defaultStore,
|
||||||
parseRouteParams,
|
initialize,
|
||||||
retrieveSeriesMetadata,
|
retrieveSeriesMetadata,
|
||||||
deleteStudyMetadataPromise,
|
deleteStudyMetadataPromise,
|
||||||
getImageIdsForDisplaySet,
|
getImageIdsForDisplaySet,
|
||||||
|
|||||||
@ -71,47 +71,47 @@ class MetadataProvider {
|
|||||||
this.imageIdToUIDs.set(imageId, uids);
|
this.imageIdToUIDs.set(imageId, uids);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getAndCacheStudy(StudyInstanceUID) {
|
// _getAndCacheStudy(StudyInstanceUID) {
|
||||||
const studies = this.studies;
|
// const studies = this.studies;
|
||||||
|
|
||||||
let study = studies.get(StudyInstanceUID);
|
// let study = studies.get(StudyInstanceUID);
|
||||||
|
|
||||||
if (!study) {
|
// if (!study) {
|
||||||
study = { series: new Map() };
|
// study = { series: new Map() };
|
||||||
studies.set(StudyInstanceUID, study);
|
// studies.set(StudyInstanceUID, study);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return study;
|
// return study;
|
||||||
}
|
// }
|
||||||
_getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
|
// _getAndCacheSeriesFromStudy(study, SeriesInstanceUID) {
|
||||||
let series = study.series.get(SeriesInstanceUID);
|
// let series = study.series.get(SeriesInstanceUID);
|
||||||
|
|
||||||
if (!series) {
|
// if (!series) {
|
||||||
series = { instances: new Map() };
|
// series = { instances: new Map() };
|
||||||
study.series.set(SeriesInstanceUID, series);
|
// study.series.set(SeriesInstanceUID, series);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return series;
|
// return series;
|
||||||
}
|
// }
|
||||||
|
|
||||||
_getAndCacheInstanceFromStudy(series, SOPInstanceUID) {
|
// _getAndCacheInstanceFromStudy(series, SOPInstanceUID) {
|
||||||
let instance = series.instances.get(SOPInstanceUID);
|
// let instance = series.instances.get(SOPInstanceUID);
|
||||||
|
|
||||||
if (!instance) {
|
// if (!instance) {
|
||||||
instance = {};
|
// instance = {};
|
||||||
series.instances.set(SOPInstanceUID, instance);
|
// series.instances.set(SOPInstanceUID, instance);
|
||||||
}
|
// }
|
||||||
|
|
||||||
return instance;
|
// return instance;
|
||||||
}
|
// }
|
||||||
|
|
||||||
async _checkBulkDataAndInlineBinaries(instance, server) {
|
// async _checkBulkDataAndInlineBinaries(instance, server) {
|
||||||
await fetchOverlayData(instance, server);
|
// await fetchOverlayData(instance, server);
|
||||||
|
|
||||||
if (instance.PhotometricInterpretation === 'PALETTE COLOR') {
|
// if (instance.PhotometricInterpretation === 'PALETTE COLOR') {
|
||||||
await fetchPaletteColorLookupTableData(instance, server);
|
// await fetchPaletteColorLookupTableData(instance, server);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
_getInstance(imageId) {
|
_getInstance(imageId) {
|
||||||
const uids = this._getUIDsFromImageID(imageId);
|
const uids = this._getUIDsFromImageID(imageId);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import dcmjs from 'dcmjs'
|
||||||
|
|
||||||
import pubSubServiceInterface from '../_shared/pubSubServiceInterface';
|
import pubSubServiceInterface from '../_shared/pubSubServiceInterface';
|
||||||
import createStudyMetadata from './createStudyMetadata';
|
import createStudyMetadata from './createStudyMetadata';
|
||||||
import EVENTS from './EVENTS';
|
import EVENTS from './EVENTS';
|
||||||
@ -24,6 +26,10 @@ const _model = {
|
|||||||
// }]
|
// }]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function _getStudyInstanceUIDs() {
|
||||||
|
return _model.studies.map(aStudy => aStudy.StudyInstanceUID)
|
||||||
|
}
|
||||||
|
|
||||||
function _getStudy(StudyInstanceUID) {
|
function _getStudy(StudyInstanceUID) {
|
||||||
return _model.studies.find(
|
return _model.studies.find(
|
||||||
aStudy => aStudy.StudyInstanceUID === StudyInstanceUID
|
aStudy => aStudy.StudyInstanceUID === StudyInstanceUID
|
||||||
@ -69,6 +75,41 @@ function _getInstanceFromImageId(imageId) {
|
|||||||
const BaseImplementation = {
|
const BaseImplementation = {
|
||||||
EVENTS,
|
EVENTS,
|
||||||
listeners: {},
|
listeners: {},
|
||||||
|
addInstance(dicomJSONDatasetOrP10ArrayBuffer) {
|
||||||
|
let dicomJSONDataset;
|
||||||
|
|
||||||
|
// If Arraybuffer, parse to DICOMJSON before naturalizing.
|
||||||
|
if (dicomJSONDatasetOrP10ArrayBuffer instanceof ArrayBuffer) {
|
||||||
|
const dicomData = dcmjs.data.DicomMessage.readFile(dicomJSONDatasetOrP10ArrayBuffer);
|
||||||
|
|
||||||
|
dicomJSONDataset = dicomData.dict;
|
||||||
|
} else {
|
||||||
|
dicomJSONDataset = dicomJSONDatasetOrP10ArrayBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let naturalizedDataset;
|
||||||
|
|
||||||
|
if (dicomJSONDataset['SeriesInstanceUID'] === undefined) {
|
||||||
|
naturalizedDataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||||
|
dicomJSONDataset
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
naturalizedDataset = dicomJSONDataset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { StudyInstanceUID } = naturalizedDataset;
|
||||||
|
|
||||||
|
let study = _model.studies.find(
|
||||||
|
study => study.StudyInstanceUID === StudyInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!study) {
|
||||||
|
_model.studies.push(createStudyMetadata(StudyInstanceUID));
|
||||||
|
study = _model.studies[_model.studies.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
study.addInstanceToSeries(naturalizedDataset);
|
||||||
|
},
|
||||||
addInstances(instances, madeInClient = false) {
|
addInstances(instances, madeInClient = false) {
|
||||||
const { StudyInstanceUID, SeriesInstanceUID } = instances[0];
|
const { StudyInstanceUID, SeriesInstanceUID } = instances[0];
|
||||||
|
|
||||||
@ -134,6 +175,7 @@ const BaseImplementation = {
|
|||||||
_model.studies.push(newStudy);
|
_model.studies.push(newStudy);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getStudyInstanceUIDs: _getStudyInstanceUIDs,
|
||||||
getStudy: _getStudy,
|
getStudy: _getStudy,
|
||||||
getSeries: _getSeries,
|
getSeries: _getSeries,
|
||||||
getInstance: _getInstance,
|
getInstance: _getInstance,
|
||||||
@ -146,8 +188,6 @@ const DicomMetadataStore = Object.assign(
|
|||||||
pubSubServiceInterface
|
pubSubServiceInterface
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO => Add instances
|
|
||||||
//_addInstance(input) // arraybuffer, or other stuff
|
|
||||||
|
|
||||||
export { DicomMetadataStore };
|
export { DicomMetadataStore };
|
||||||
export default DicomMetadataStore;
|
export default DicomMetadataStore;
|
||||||
|
|||||||
@ -5,13 +5,31 @@ function createStudyMetadata(StudyInstanceUID) {
|
|||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
series: [],
|
series: [],
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {object} instance
|
||||||
|
* @returns {bool} true if series were added; false if series already exist
|
||||||
|
*/
|
||||||
|
addInstanceToSeries: function (instance) {
|
||||||
|
const { SeriesInstanceUID } = instance;
|
||||||
|
const existingSeries = this.series.find(
|
||||||
|
s => s.SeriesInstanceUID === SeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingSeries) {
|
||||||
|
existingSeries.instances.push(instance);
|
||||||
|
} else {
|
||||||
|
const series = createSeriesMetadata([instance]);
|
||||||
|
this.series.push(series);
|
||||||
|
}
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object[]} instances
|
* @param {object[]} instances
|
||||||
* @param {string} instances[].SeriesInstanceUID
|
* @param {string} instances[].SeriesInstanceUID
|
||||||
* @returns {bool} true if series were added; false if series already exist
|
* @returns {bool} true if series were added; false if series already exist
|
||||||
*/
|
*/
|
||||||
addInstancesToSeries: function(instances) {
|
addInstancesToSeries: function (instances) {
|
||||||
const { SeriesInstanceUID } = instances[0];
|
const { SeriesInstanceUID } = instances[0];
|
||||||
const existingSeries = this.series.find(
|
const existingSeries = this.series.find(
|
||||||
s => s.SeriesInstanceUID === SeriesInstanceUID
|
s => s.SeriesInstanceUID === SeriesInstanceUID
|
||||||
@ -25,7 +43,7 @@ function createStudyMetadata(StudyInstanceUID) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setSeriesMetadata: function(SeriesInstanceUID, seriesMetadata) {
|
setSeriesMetadata: function (SeriesInstanceUID, seriesMetadata) {
|
||||||
let existingSeries = this.series.find(
|
let existingSeries = this.series.find(
|
||||||
s => s.SeriesInstanceUID === SeriesInstanceUID
|
s => s.SeriesInstanceUID === SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|||||||
@ -179,7 +179,7 @@ export function ViewportGridProvider({ children, service }) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
// getState,
|
getState,
|
||||||
setActiveViewportIndex,
|
setActiveViewportIndex,
|
||||||
setDisplaysetForViewport,
|
setDisplaysetForViewport,
|
||||||
setLayout,
|
setLayout,
|
||||||
|
|||||||
@ -68,6 +68,7 @@
|
|||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"query-string": "^6.12.1",
|
"query-string": "^6.12.1",
|
||||||
|
"react-dropzone": "^10.1.7",
|
||||||
"react-i18next": "^10.11.0",
|
"react-i18next": "^10.11.0",
|
||||||
"react-resize-detector": "^4.2.0",
|
"react-resize-detector": "^4.2.0",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
|
|||||||
@ -24,6 +24,20 @@ window.config = {
|
|||||||
supportsWildcard: true,
|
supportsWildcard: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
friendlyName: 'dicom json',
|
||||||
|
namespace: 'org.ohif.default.dataSourcesModule.dicomjson',
|
||||||
|
sourceName: 'dicomjson',
|
||||||
|
configuration: {
|
||||||
|
name: 'json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
friendlyName: 'dicom local',
|
||||||
|
namespace: 'org.ohif.default.dataSourcesModule.dicomlocal',
|
||||||
|
sourceName: 'dicomlocal',
|
||||||
|
configuration: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
// whiteLabeling: {
|
// whiteLabeling: {
|
||||||
// /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */
|
// /* Optional: Should return a React component to be rendered in the "Logo" section of the application's Top Navigation bar */
|
||||||
|
|||||||
@ -75,7 +75,7 @@
|
|||||||
<title>OHIF Viewer</title>
|
<title>OHIF Viewer</title>
|
||||||
|
|
||||||
<!-- WEB FONTS -->
|
<!-- WEB FONTS -->
|
||||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css?family=Lato:100,300,400,500,700&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
<!-- EXTENSIONS -->
|
<!-- EXTENSIONS -->
|
||||||
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
<!-- <script type="text/javascript" src="path/to/some-extension.js"></script>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* CSS Grid Reference: http://grid.malven.co/
|
* CSS Grid Reference: http://grid.malven.co/
|
||||||
*/
|
*/
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
import { ViewportGrid, ViewportPane, useViewportGrid } from '@ohif/ui';
|
||||||
import EmptyViewport from './EmptyViewport';
|
import EmptyViewport from './EmptyViewport';
|
||||||
@ -27,53 +27,69 @@ function ViewerViewportGrid(props) {
|
|||||||
} = servicesManager.services;
|
} = servicesManager.services;
|
||||||
|
|
||||||
|
|
||||||
// Using Hanging protocol engine to match the displaysets
|
const updateDisplaysetForViewports = useCallback(
|
||||||
|
(displaySets) => {
|
||||||
|
const [
|
||||||
|
matchDetails,
|
||||||
|
hpAlreadyApplied,
|
||||||
|
] = HangingProtocolService.getState();
|
||||||
|
|
||||||
|
if (!matchDetails.length) return;
|
||||||
|
// Match each viewport individually
|
||||||
|
|
||||||
|
const numViewports = viewportGrid.numRows * viewportGrid.numCols;
|
||||||
|
for (let i = 0; i < numViewports; i++) {
|
||||||
|
if (hpAlreadyApplied[i] === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if current viewport doesn't have a match
|
||||||
|
if (matchDetails[i] === undefined) return
|
||||||
|
|
||||||
|
const { SeriesInstanceUID } = matchDetails[i];
|
||||||
|
const matchingDisplaySet = displaySets.find(ds => {
|
||||||
|
return ds.SeriesInstanceUID === SeriesInstanceUID;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!matchingDisplaySet) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewportGridService.setDisplaysetForViewport({
|
||||||
|
viewportIndex: i,
|
||||||
|
displaySetInstanceUID: matchingDisplaySet.displaySetInstanceUID,
|
||||||
|
});
|
||||||
|
|
||||||
|
HangingProtocolService.setHangingProtocolAppliedForViewport(i);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[viewportGrid, numRows, numCols],
|
||||||
|
)
|
||||||
|
|
||||||
|
// Using Hanging protocol engine to match the displaySets
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { unsubscribe } = DisplaySetService.subscribe(
|
const { unsubscribe } = DisplaySetService.subscribe(
|
||||||
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
DisplaySetService.EVENTS.DISPLAY_SETS_ADDED,
|
||||||
eventData => {
|
eventData => {
|
||||||
const { displaySetsAdded } = eventData;
|
const { displaySetsAdded } = eventData;
|
||||||
|
updateDisplaysetForViewports(displaySetsAdded)
|
||||||
const [
|
|
||||||
matchDetails,
|
|
||||||
hpAlreadyApplied,
|
|
||||||
] = HangingProtocolService.getState();
|
|
||||||
|
|
||||||
if (!matchDetails.length) return;
|
|
||||||
// Match each viewport individually
|
|
||||||
|
|
||||||
const numViewports = viewportGrid.numRows * viewportGrid.numCols;
|
|
||||||
for (let i = 0; i < numViewports; i++) {
|
|
||||||
if (hpAlreadyApplied[i] === true) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if current viewport doesn't have a match
|
|
||||||
if (matchDetails[i] === undefined) return
|
|
||||||
|
|
||||||
const { SeriesInstanceUID } = matchDetails[i];
|
|
||||||
const matchingDisplaySet = displaySetsAdded.find(ds => {
|
|
||||||
return ds.SeriesInstanceUID === SeriesInstanceUID;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!matchingDisplaySet) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewportGridService.setDisplaysetForViewport({
|
|
||||||
viewportIndex: i,
|
|
||||||
displaySetInstanceUID: matchingDisplaySet.displaySetInstanceUID,
|
|
||||||
});
|
|
||||||
|
|
||||||
HangingProtocolService.setHangingProtocolAppliedForViewport(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [numRows, numCols]);
|
}, [viewportGrid]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Changing the Hanging protocol while viewing
|
||||||
|
useEffect(() => {
|
||||||
|
const displaySets = DisplaySetService.getActiveDisplaySets();
|
||||||
|
updateDisplaysetForViewports(displaySets)
|
||||||
|
}, [viewportGrid])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Layout change based on hanging protocols
|
// Layout change based on hanging protocols
|
||||||
|
|||||||
116
platform/viewer/src/routes/Local/Local.jsx
Normal file
116
platform/viewer/src/routes/Local/Local.jsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import { MODULE_TYPES } from '@ohif/core'
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Dropzone from 'react-dropzone'
|
||||||
|
import filesToStudies from './filesToStudies'
|
||||||
|
|
||||||
|
import { extensionManager } from '../../App.jsx'
|
||||||
|
|
||||||
|
import { Icon, Button } from '@ohif/ui'
|
||||||
|
|
||||||
|
const getLoadButton = (onDrop, text, isDir) => {
|
||||||
|
return (
|
||||||
|
<Dropzone onDrop={onDrop} noDrag>
|
||||||
|
{({ getRootProps, getInputProps }) => (
|
||||||
|
<div {...getRootProps()}>
|
||||||
|
<Button
|
||||||
|
rounded="full"
|
||||||
|
variant="contained" // outlined
|
||||||
|
disabled={false}
|
||||||
|
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
|
||||||
|
className={classnames('font-bold', 'ml-2')}
|
||||||
|
onClick={() => { }}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
{isDir ? (
|
||||||
|
<input
|
||||||
|
{...getInputProps()}
|
||||||
|
webkitdirectory="true"
|
||||||
|
mozdirectory="true"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<input {...getInputProps()} />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Dropzone>)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Local(props) {
|
||||||
|
const { history } = props
|
||||||
|
const dropzoneRef = useRef()
|
||||||
|
|
||||||
|
// Initializing the dicom local dataSource
|
||||||
|
const dataSourceModules = extensionManager.modules[MODULE_TYPES.DATA_SOURCE]
|
||||||
|
const localDataSources = dataSourceModules.reduce((acc, curr) => {
|
||||||
|
const mods = []
|
||||||
|
curr.module.forEach((mod) => {
|
||||||
|
if (mod.type === 'localApi') {
|
||||||
|
mods.push(mod)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return acc.concat(mods)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const fistLocalDataSource = localDataSources[0]
|
||||||
|
const dataSource = fistLocalDataSource.createDataSource({})
|
||||||
|
|
||||||
|
const onDrop = async (acceptedFiles) => {
|
||||||
|
const studies = await filesToStudies(acceptedFiles, dataSource)
|
||||||
|
// Todo: navigate to work list and let user select a mode
|
||||||
|
history.push(`/viewer/dicomlocal?StudyInstanceUIDs=${studies[0]}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set body style
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.classList.add('bg-black')
|
||||||
|
return () => {
|
||||||
|
document.body.classList.remove('bg-black')
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropzone ref={dropzoneRef} onDrop={onDrop} noClick>
|
||||||
|
{({ getRootProps, getInputProps }) => (
|
||||||
|
<div {...getRootProps()} style={{ width: '100%', height: '100%' }}>
|
||||||
|
<div className="h-screen w-screen flex justify-center items-center ">
|
||||||
|
<div className="py-8 px-8 mx-auto bg-secondary-dark shadow-md space-y-2 rounded-lg">
|
||||||
|
<img
|
||||||
|
className="block mx-auto h-10"
|
||||||
|
src="./customLogo.svg"
|
||||||
|
alt="OHIF"
|
||||||
|
/>
|
||||||
|
<div className="text-center space-y-2 pt-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-blue-300 text-base">
|
||||||
|
Note: You data is not uploaded to any server, it will stay
|
||||||
|
in your local browser application
|
||||||
|
</p>
|
||||||
|
<p className="text-xg text-primary-active font-semibold pt-8">
|
||||||
|
Drag and Drop DICOM files here to load them in the Viewer
|
||||||
|
</p>
|
||||||
|
<p className="text-blue-300 text-lg">Or click to </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-around pt-4">
|
||||||
|
{getLoadButton(onDrop, 'Load files', false)}
|
||||||
|
{getLoadButton(onDrop, 'Load folders', true)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Dropzone>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Local.propTypes = {
|
||||||
|
history: PropTypes.shape({
|
||||||
|
push: PropTypes.func,
|
||||||
|
}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Local
|
||||||
29
platform/viewer/src/routes/Local/dicomFileLoader.js
Normal file
29
platform/viewer/src/routes/Local/dicomFileLoader.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import dcmjs from 'dcmjs';
|
||||||
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
import FileLoader from './fileLoader';
|
||||||
|
|
||||||
|
|
||||||
|
const DICOMFileLoader = new (class extends FileLoader {
|
||||||
|
fileType = 'application/dicom';
|
||||||
|
loadFile(file, imageId) {
|
||||||
|
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataset(image, imageId) {
|
||||||
|
const dicomData = dcmjs.data.DicomMessage.readFile(image);
|
||||||
|
|
||||||
|
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
|
||||||
|
dicomData.dict
|
||||||
|
);
|
||||||
|
|
||||||
|
dataset.url = imageId
|
||||||
|
|
||||||
|
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(
|
||||||
|
dicomData.meta
|
||||||
|
);
|
||||||
|
|
||||||
|
return dataset
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default DICOMFileLoader;
|
||||||
6
platform/viewer/src/routes/Local/fileLoader.js
Normal file
6
platform/viewer/src/routes/Local/fileLoader.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default class FileLoader {
|
||||||
|
fileType;
|
||||||
|
loadFile(file, imageId) { }
|
||||||
|
getDataset(image, imageId) { }
|
||||||
|
getStudies(dataset, imageId) { }
|
||||||
|
}
|
||||||
40
platform/viewer/src/routes/Local/fileLoaderService.js
Normal file
40
platform/viewer/src/routes/Local/fileLoaderService.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
import { DicomMetadataStore } from '@ohif/core';
|
||||||
|
|
||||||
|
import FileLoader from './fileLoader';
|
||||||
|
import PDFFileLoader from './pdfFileLoader';
|
||||||
|
import DICOMFileLoader from './dicomFileLoader';
|
||||||
|
|
||||||
|
class FileLoaderService extends FileLoader {
|
||||||
|
fileType;
|
||||||
|
loader;
|
||||||
|
constructor(file) {
|
||||||
|
super();
|
||||||
|
const fileType = file && file.type;
|
||||||
|
this.loader = this.getLoader(fileType);
|
||||||
|
this.fileType = this.loader.fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
addFile(file) {
|
||||||
|
return cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadFile(file, imageId) {
|
||||||
|
return this.loader.loadFile(file, imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataset(image, imageId) {
|
||||||
|
return this.loader.getDataset(image, imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoader(fileType) {
|
||||||
|
if (fileType === 'application/pdf') {
|
||||||
|
return PDFFileLoader;
|
||||||
|
} else {
|
||||||
|
// Default to dicom loader
|
||||||
|
return DICOMFileLoader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileLoaderService;
|
||||||
26
platform/viewer/src/routes/Local/filesToStudies.js
Normal file
26
platform/viewer/src/routes/Local/filesToStudies.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import FileLoaderService from './fileLoaderService';
|
||||||
|
import { DicomMetadataStore } from '@ohif/core';
|
||||||
|
|
||||||
|
const processFile = async file => {
|
||||||
|
try {
|
||||||
|
const fileLoaderService = new FileLoaderService(file);
|
||||||
|
const imageId = fileLoaderService.addFile(file);
|
||||||
|
const image = await fileLoaderService.loadFile(file, imageId);
|
||||||
|
const dicomJSONDataset = await fileLoaderService.getDataset(image, imageId);
|
||||||
|
|
||||||
|
DicomMetadataStore.addInstance(dicomJSONDataset);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(
|
||||||
|
error.name,
|
||||||
|
':Error when trying to load and process local files:',
|
||||||
|
error.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function filesToStudies(files) {
|
||||||
|
const processFilesPromises = files.map(processFile);
|
||||||
|
await Promise.all(processFilesPromises);
|
||||||
|
|
||||||
|
return DicomMetadataStore.getStudyInstanceUIDs();
|
||||||
|
}
|
||||||
1
platform/viewer/src/routes/Local/index.js
Normal file
1
platform/viewer/src/routes/Local/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './Local';
|
||||||
17
platform/viewer/src/routes/Local/pdfFileLoader.js
Normal file
17
platform/viewer/src/routes/Local/pdfFileLoader.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
|
||||||
|
import FileLoader from './fileLoader';
|
||||||
|
|
||||||
|
const PDFFileLoader = new (class extends FileLoader {
|
||||||
|
fileType = 'application/pdf';
|
||||||
|
loadFile(file, imageId) {
|
||||||
|
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataset(image, imageId) {
|
||||||
|
const dataset = {};
|
||||||
|
dataset.imageId = image.imageId || imageId;
|
||||||
|
return dataset;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default PDFFileLoader;
|
||||||
@ -19,7 +19,7 @@ async function defaultRouteInit({
|
|||||||
} = servicesManager.services;
|
} = servicesManager.services;
|
||||||
|
|
||||||
const unsubscriptions = [];
|
const unsubscriptions = [];
|
||||||
// TODO: This should be baked into core, not manuall?
|
// TODO: This should be baked into core, not manual?
|
||||||
// DisplaySetService would wire this up?
|
// DisplaySetService would wire this up?
|
||||||
const {
|
const {
|
||||||
unsubscribe: instanceAddedUnsubscribe,
|
unsubscribe: instanceAddedUnsubscribe,
|
||||||
@ -37,10 +37,6 @@ async function defaultRouteInit({
|
|||||||
|
|
||||||
unsubscriptions.push(instanceAddedUnsubscribe);
|
unsubscriptions.push(instanceAddedUnsubscribe);
|
||||||
|
|
||||||
studyInstanceUIDs.forEach(StudyInstanceUID => {
|
|
||||||
dataSource.retrieveSeriesMetadata({ StudyInstanceUID });
|
|
||||||
});
|
|
||||||
|
|
||||||
const { unsubscribe: seriesAddedUnsubscribe } = DicomMetadataStore.subscribe(
|
const { unsubscribe: seriesAddedUnsubscribe } = DicomMetadataStore.subscribe(
|
||||||
DicomMetadataStore.EVENTS.SERIES_ADDED,
|
DicomMetadataStore.EVENTS.SERIES_ADDED,
|
||||||
({ StudyInstanceUID }) => {
|
({ StudyInstanceUID }) => {
|
||||||
@ -50,6 +46,10 @@ async function defaultRouteInit({
|
|||||||
);
|
);
|
||||||
unsubscriptions.push(seriesAddedUnsubscribe);
|
unsubscriptions.push(seriesAddedUnsubscribe);
|
||||||
|
|
||||||
|
studyInstanceUIDs.forEach(StudyInstanceUID => {
|
||||||
|
dataSource.retrieveSeriesMetadata({ StudyInstanceUID });
|
||||||
|
});
|
||||||
|
|
||||||
return unsubscriptions;
|
return unsubscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ export default function ModeRoute({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Todo: this should not be here, data source should not care about params
|
// Todo: this should not be here, data source should not care about params
|
||||||
const initializeDataSource = async (params, query) => {
|
const initializeDataSource = async (params, query) => {
|
||||||
const studyInstanceUIDs = await dataSource.parseRouteParams({
|
const studyInstanceUIDs = await dataSource.initialize({
|
||||||
params,
|
params,
|
||||||
query,
|
query,
|
||||||
});
|
});
|
||||||
@ -261,7 +261,7 @@ export default function ModeRoute({
|
|||||||
<ImageViewerProvider
|
<ImageViewerProvider
|
||||||
// initialState={{ StudyInstanceUIDs: StudyInstanceUIDs }}
|
// initialState={{ StudyInstanceUIDs: StudyInstanceUIDs }}
|
||||||
StudyInstanceUIDs={studyInstanceUIDs}
|
StudyInstanceUIDs={studyInstanceUIDs}
|
||||||
// reducer={reducer}
|
// reducer={reducer}
|
||||||
>
|
>
|
||||||
<CombinedContextProvider>
|
<CombinedContextProvider>
|
||||||
<DragAndDropProvider>
|
<DragAndDropProvider>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Switch, Route } from 'react-router-dom';
|
|||||||
// Route Components
|
// Route Components
|
||||||
import DataSourceWrapper from './DataSourceWrapper';
|
import DataSourceWrapper from './DataSourceWrapper';
|
||||||
import WorkList from './WorkList';
|
import WorkList from './WorkList';
|
||||||
|
import Local from './Local';
|
||||||
import NotFound from './NotFound';
|
import NotFound from './NotFound';
|
||||||
import buildModeRoutes from './buildModeRoutes';
|
import buildModeRoutes from './buildModeRoutes';
|
||||||
import { ErrorBoundary } from '@ohif/ui';
|
import { ErrorBoundary } from '@ohif/ui';
|
||||||
@ -17,6 +18,11 @@ const bakedInRoutes = [
|
|||||||
component: DataSourceWrapper,
|
component: DataSourceWrapper,
|
||||||
props: { children: WorkList },
|
props: { children: WorkList },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/local',
|
||||||
|
exact: true,
|
||||||
|
component: Local,
|
||||||
|
},
|
||||||
// NOT FOUND (404)
|
// NOT FOUND (404)
|
||||||
{ component: NotFound },
|
{ component: NotFound },
|
||||||
];
|
];
|
||||||
|
|||||||
26
yarn.lock
26
yarn.lock
@ -4333,6 +4333,11 @@ atob@^2.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||||
|
|
||||||
|
attr-accept@^2.0.0:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
|
||||||
|
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
|
||||||
|
|
||||||
autoprefixer@10.2.4:
|
autoprefixer@10.2.4:
|
||||||
version "10.2.4"
|
version "10.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.4.tgz#c0e7cf24fcc6a1ae5d6250c623f0cb8beef2f7e1"
|
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.4.tgz#c0e7cf24fcc6a1ae5d6250c623f0cb8beef2f7e1"
|
||||||
@ -8955,6 +8960,13 @@ file-loader@^1.1.11:
|
|||||||
loader-utils "^1.0.2"
|
loader-utils "^1.0.2"
|
||||||
schema-utils "^0.4.5"
|
schema-utils "^0.4.5"
|
||||||
|
|
||||||
|
file-selector@^0.1.12:
|
||||||
|
version "0.1.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.19.tgz#8ecc9d069a6f544f2e4a096b64a8052e70ec8abf"
|
||||||
|
integrity sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==
|
||||||
|
dependencies:
|
||||||
|
tslib "^2.0.1"
|
||||||
|
|
||||||
file-type@^16.0.0:
|
file-type@^16.0.0:
|
||||||
version "16.2.0"
|
version "16.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.2.0.tgz#d4f1da71ddda758db7f15f93adfaed09ce9e2715"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.2.0.tgz#d4f1da71ddda758db7f15f93adfaed09ce9e2715"
|
||||||
@ -16999,6 +17011,15 @@ react-draggable@4.4.3, react-draggable@^4.1.0:
|
|||||||
classnames "^2.2.5"
|
classnames "^2.2.5"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
|
react-dropzone@^10.1.7:
|
||||||
|
version "10.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.2.2.tgz#67b4db7459589a42c3b891a82eaf9ade7650b815"
|
||||||
|
integrity sha512-U5EKckXVt6IrEyhMMsgmHQiWTGLudhajPPG77KFSvgsMqNEHSyGpqWvOMc5+DhEah/vH4E1n+J5weBNLd5VtyA==
|
||||||
|
dependencies:
|
||||||
|
attr-accept "^2.0.0"
|
||||||
|
file-selector "^0.1.12"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
react-error-boundary@2.2.x:
|
react-error-boundary@2.2.x:
|
||||||
version "2.2.3"
|
version "2.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-2.2.3.tgz#34c8238012d3b4148cec47a1b3cec669d5206578"
|
resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-2.2.3.tgz#34c8238012d3b4148cec47a1b3cec669d5206578"
|
||||||
@ -20234,6 +20255,11 @@ tslib@^2, tslib@^2.0.0, tslib@^2.0.3, tslib@~2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||||
|
|
||||||
|
tslib@^2.0.1:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
|
||||||
|
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
|
||||||
|
|
||||||
tslib@~2.0.1:
|
tslib@~2.0.1:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user