fix: retrieveSeriesMetadata (#2703)

This commit is contained in:
James Petts authored and GitHub committed 2022-02-08 08:17:37 -05:00
1 parent 0e2c31939c
commit 509c41fbab
10 files changed
+455 -443

No files matched your search

@@ -134,8 +134,68 @@ function createDicomJSONApi(dicomJsonConfig) {
},
retrieve: {
series: {
metaData: () => {
console.debug(' DICOMJson retrieve series metadata');
metaData: ({
StudyInstanceUID,
madeInClient = false,
customSort,
} = {}) => {
if (!StudyInstanceUID) {
throw new Error(
'Unable to query for SeriesMetadata without StudyInstanceUID'
);
}
const study = findStudies('StudyInstanceUID', StudyInstanceUID)[0];
let series;
if (customSort) {
series = customSort(study.series);
} else {
series = study.series;
}
const seriesSummaryMetadata = series.map(series => {
const seriesSummary = {
StudyInstanceUID: study.StudyInstanceUID,
...series,
};
delete seriesSummary.instances;
return seriesSummary;
});
// Async load series, store as retrieved
function storeInstances(naturalizedInstances) {
DicomMetadataStore.addInstances(naturalizedInstances, madeInClient);
}
DicomMetadataStore.addSeriesMetadata(
seriesSummaryMetadata,
madeInClient
);
function setSuccessFlag() {
const study = DicomMetadataStore.getStudy(
StudyInstanceUID,
madeInClient
);
study.isLoaded = true;
}
const numberOfSeries = series.length;
series.forEach((series, index) => {
const instances = series.instances.map(instance => {
const obj = {
...instance.metadata,
url: instance.url,
imageId: instance.url,
...series,
};
delete obj.instances;
return obj;
});
storeInstances(instances);
if (index === numberOfSeries - 1) setSuccessFlag();
});
},
},
},
@@ -144,66 +204,6 @@ function createDicomJSONApi(dicomJsonConfig) {
console.debug(' DICOMJson store dicom');
},
},
retrieveSeriesMetadata: ({
StudyInstanceUID,
madeInClient = false,
customSort,
} = {}) => {
if (!StudyInstanceUID) {
throw new Error(
'Unable to query for SeriesMetadata without StudyInstanceUID'
);
}
const study = findStudies('StudyInstanceUID', StudyInstanceUID)[0];
let series;
if (customSort) {
series = customSort(study.series);
} else {
series = study.series;
}
const seriesSummaryMetadata = series.map(series => {
const seriesSummary = {
StudyInstanceUID: study.StudyInstanceUID,
...series,
};
delete seriesSummary.instances;
return seriesSummary;
});
// Async load series, store as retrieved
function storeInstances(naturalizedInstances) {
DicomMetadataStore.addInstances(naturalizedInstances, madeInClient);
}
DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient);
function setSuccessFlag() {
const study = DicomMetadataStore.getStudy(
StudyInstanceUID,
madeInClient
);
study.isLoaded = true;
}
const numberOfSeries = series.length;
series.forEach((series, index) => {
const instances = series.instances.map(instance => {
const obj = {
...instance.metadata,
url: instance.url,
imageId: instance.url,
...series,
};
delete obj.instances;
return obj;
});
storeInstances(instances);
if (index === numberOfSeries - 1) setSuccessFlag();
});
},
getImageIdsForDisplaySet(displaySet) {
const images = displaySet.images;
const imageIds = [];
@@ -1,69 +1,67 @@
import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core'
import OHIF from '@ohif/core'
import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core';
import OHIF from '@ohif/core';
import dcmjs from 'dcmjs';
const metadataProvider = OHIF.classes.MetadataProvider
const { EVENTS } = DicomMetadataStore
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
const modalityA = seriesA.instances[0].Modality;
const modalityB = seriesB.instances[0].Modality;
if (modalityA === "SR") {
if (modalityA === 'SR') {
return +1;
}
if (modalityB === "SR") {
if (modalityB === 'SR') {
return -1;
}
return 0;
}
function createDicomLocalApi(dicomLocalConfig) {
const { name } = dicomLocalConfig
const { name } = dicomLocalConfig;
const implementation = {
initialize: ({ params, query }) => {
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params
const queryStudyInstanceUIDs = query.get('StudyInstanceUIDs')
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params;
const queryStudyInstanceUIDs = query.get('StudyInstanceUIDs');
const StudyInstanceUIDs =
queryStudyInstanceUIDs || paramsStudyInstanceUIDs
queryStudyInstanceUIDs || paramsStudyInstanceUIDs;
const StudyInstanceUIDsAsArray =
StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs)
? 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)
})
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
study.series = study.series.sort(customSort);
});
return StudyInstanceUIDsAsArray
return StudyInstanceUIDsAsArray;
},
query: {
studies: {
mapParams: () => { },
search: (params) => {
const studyUIDs = DicomMetadataStore.getStudyInstanceUIDs()
mapParams: () => {},
search: params => {
const studyUIDs = DicomMetadataStore.getStudyInstanceUIDs();
return studyUIDs.map(StudyInstanceUID => {
let numInstances = 0
const modalities = new Set()
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)
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
study.series.forEach(aSeries => {
numInstances += aSeries.instances.length
numInstances += aSeries.instances.length;
modalities.add(aSeries.Modality);
})
});
// first instance in the first series
const firstInstance = study?.series[0]?.instances[0]
const firstInstance = study?.series[0]?.instances[0];
if (firstInstance) {
return {
@@ -80,33 +78,75 @@ function createDicomLocalApi(dicomLocalConfig) {
NumInstances: numInstances,
};
}
})
});
},
processResults: () => {
console.debug(' DICOMLocal QUERY processResults')
console.debug(' DICOMLocal QUERY processResults');
},
},
series: {
// mapParams: mapParams.bind(),
search: () => {
console.debug(' DICOMLocal QUERY SERIES SEARCH')
console.debug(' DICOMLocal QUERY SERIES SEARCH');
},
},
instances: {
search: () => {
console.debug(' DICOMLocal QUERY instances SEARCH')
console.debug(' DICOMLocal QUERY instances SEARCH');
},
},
},
retrieve: {
series: {
metaData: () => {
console.debug(' DICOMLocal retrieve series metadata')
metaData: 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,
});
});
},
},
},
store: {
dicom: (naturalizedReport) => {
dicom: naturalizedReport => {
const reportBlob = dcmjs.data.datasetToBlob(naturalizedReport);
//Create a URL for the binary.
@@ -114,91 +154,49 @@ function createDicomLocalApi(dicomLocalConfig) {
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 = []
const images = displaySet.images;
const imageIds = [];
if (!images) {
return imageIds
return imageIds;
}
displaySet.images.forEach((instance) => {
const NumberOfFrames = instance.NumberOfFrames
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)
});
imageIds.push(imageId);
}
} else {
const imageId = this.getImageIdsForInstance({ instance })
imageIds.push(imageId)
const imageId = this.getImageIdsForInstance({ instance });
imageIds.push(imageId);
}
})
});
return imageIds
return imageIds;
},
getImageIdsForInstance({ instance, frame }) {
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance
const storedInstance = DicomMetadataStore.getInstance(StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID)
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
const storedInstance = DicomMetadataStore.getInstance(
StudyInstanceUID,
SeriesInstanceUID,
SOPInstanceUID
);
if (storedInstance.url) {
return storedInstance.url
return storedInstance.url;
}
},
deleteStudyMetadataPromise() {
console.log("deleteStudyMetadataPromise not implemented")
}
}
return IWebApiDataSource.create(implementation)
console.log('deleteStudyMetadataPromise not implemented');
},
};
return IWebApiDataSource.create(implementation);
}
export { createDicomLocalApi }
export { createDicomLocalApi };
+101 -132
View File
@@ -7,7 +7,12 @@ import {
processSeriesResults,
} from './qido.js';
import dcm4cheeReject from './dcm4cheeReject';
import { DicomMetadataStore, IWebApiDataSource, utils, errorHandler } from '@ohif/core';
import {
DicomMetadataStore,
IWebApiDataSource,
utils,
errorHandler,
} from '@ohif/core';
import getImageId from './utils/getImageId';
import dcmjs from 'dcmjs';
@@ -65,7 +70,9 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
// TODO -> Two clients sucks, but its better than 1000.
// TODO -> We'll need to merge auth later.
const qidoDicomWebClient = staticWado ? new StaticWadoClient(qidoConfig) : new api.DICOMwebClient(qidoConfig);
const qidoDicomWebClient = staticWado
? new StaticWadoClient(qidoConfig)
: new api.DICOMwebClient(qidoConfig);
const wadoDicomWebClient = new api.DICOMwebClient(wadoConfig);
const implementation = {
@@ -84,7 +91,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
query: {
studies: {
mapParams: mapParams.bind(),
search: async function (origParams) {
search: async function(origParams) {
const headers = UserAuthenticationService.getAuthorizationHeader();
if (headers) {
qidoDicomWebClient.headers = headers;
@@ -109,7 +116,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
},
series: {
// mapParams: mapParams.bind(),
search: async function (studyInstanceUid) {
search: async function(studyInstanceUid) {
const headers = UserAuthenticationService.getAuthorizationHeader();
if (headers) {
qidoDicomWebClient.headers = headers;
@@ -143,48 +150,108 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
},
retrieve: {
series: {
// TODO: change queryParams to `StudyInstanceUID` for now?
// Conduct query, return a promise like others
// Await this call and add to DicomMetadataStore after receiving result
metadata: (queryParams, callback) => {
metadata: async ({
StudyInstanceUID,
filters,
sortCriteria,
sortFunction,
madeInClient = false,
} = {}) => {
const headers = UserAuthenticationService.getAuthorizationHeader();
if (headers) {
wadoDicomWebClient.headers = headers;
}
let { StudyInstanceUIDs } = urlUtil.parse(queryParams, true);
StudyInstanceUIDs = urlUtil.paramString.parseParam(StudyInstanceUIDs);
if (!StudyInstanceUIDs) {
if (!StudyInstanceUID) {
throw new Error(
'Incomplete queryParams, missing StudyInstanceUIDs'
'Unable to query for SeriesMetadata without StudyInstanceUID'
);
}
const storeInstances = instances => {
const naturalizedInstances = instances.map(naturalizeDataset);
DicomMetadataStore.addInstances(naturalizedInstances);
callback(naturalizedInstances);
};
const studyPromises = StudyInstanceUIDs.map(StudyInstanceUID =>
retrieveStudyMetadata(
wadoDicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad
)
// Get Series
const {
seriesSummaryMetadata,
seriesPromises,
} = await retrieveStudyMetadata(
wadoDicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction
);
studyPromises.forEach(studyPromise => {
studyPromise.then(data => {
const { seriesPromises } = data;
seriesPromises.forEach(seriesPromise => {
seriesPromise.then(instances => {
storeInstances(instances);
});
});
/**
* naturalizes the dataset, and adds a retrieve bulkdata method
* to any values containing BulkDataURI.
* @param {*} instance
* @returns naturalized dataset, with retrieveBulkData methods
*/
const addRetrieveBulkData = instance => {
const naturalized = naturalizeDataset(instance);
Object.keys(naturalized).forEach(key => {
const value = naturalized[key];
// The value.Value will be set with the bulkdata read value
// in which case it isn't necessary to re-read this.
if (value && value.BulkDataURI && !value.Value) {
// Provide a method to fetch bulkdata
value.retrieveBulkData = () => {
const options = {
// The bulkdata fetches work with either multipart or
// singlepart, so set multipart to false to let the server
// decide which type to respond with.
multipart: false,
BulkDataURI: value.BulkDataURI,
// The study instance UID is required if the bulkdata uri
// is relative - that isn't disallowed by DICOMweb, but
// isn't well specified in the standard, but is needed in
// any implementation that stores static copies of the metadata
StudyInstanceUID: naturalized.StudyInstanceUID,
};
return qidoDicomWebClient
.retrieveBulkData(options)
.then(val => {
const ret = (val && val[0]) || undefined;
value.Value = ret;
return ret;
});
};
}
});
return naturalized;
};
// Async load series, store as retrieved
function storeInstances(instances) {
const naturalizedInstances = instances.map(addRetrieveBulkData);
DicomMetadataStore.addInstances(naturalizedInstances, madeInClient);
}
function setSuccessFlag() {
const study = DicomMetadataStore.getStudy(
StudyInstanceUID,
madeInClient
);
study.isLoaded = true;
}
// Google Cloud Healthcare doesn't return StudyInstanceUID, so we need to add
// it manually here
seriesSummaryMetadata.forEach(aSeries => {
aSeries.StudyInstanceUID = StudyInstanceUID;
});
DicomMetadataStore.addSeriesMetadata(
seriesSummaryMetadata,
madeInClient
);
const numberOfSeries = seriesPromises.length;
seriesPromises.forEach(async (seriesPromise, index) => {
const instances = await seriesPromise;
storeInstances(instances);
if (index === numberOfSeries - 1) setSuccessFlag();
});
},
},
@@ -220,104 +287,6 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
await wadoDicomWebClient.storeInstances(options);
},
},
// TODO: Rename this it makes no sense at all
retrieveSeriesMetadata: async ({
StudyInstanceUID,
filters,
sortCriteria,
sortFunction,
madeInClient = false,
} = {}) => {
const headers = UserAuthenticationService.getAuthorizationHeader();
if (headers) {
wadoDicomWebClient.headers = headers;
}
if (!StudyInstanceUID) {
throw new Error(
'Unable to query for SeriesMetadata without StudyInstanceUID'
);
}
// Get Series
const {
seriesSummaryMetadata,
seriesPromises,
} = await retrieveStudyMetadata(
wadoDicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction
);
/**
* naturalizes the dataset, and adds a retrieve bulkdata method
* to any values containing BulkDataURI.
* @param {*} instance
* @returns naturalized dataset, with retrieveBulkData methods
*/
const addRetrieveBulkData = instance => {
const naturalized = naturalizeDataset(instance);
Object.keys(naturalized).forEach(key => {
const value = naturalized[key];
// The value.Value will be set with the bulkdata read value
// in which case it isn't necessary to re-read this.
if (value && value.BulkDataURI && !value.Value) {
// Provide a method to fetch bulkdata
value.retrieveBulkData = () => {
const options = {
// The bulkdata fetches work with either multipart or
// singlepart, so set multipart to false to let the server
// decide which type to respond with.
multipart: false,
BulkDataURI: value.BulkDataURI,
// The study instance UID is required if the bulkdata uri
// is relative - that isn't disallowed by DICOMweb, but
// isn't well specified in the standard, but is needed in
// any implementation that stores static copies of the metadata
StudyInstanceUID: naturalized.StudyInstanceUID,
};
return qidoDicomWebClient.retrieveBulkData(options).then(val => {
const ret = val && val[0] || undefined;
value.Value = ret;
return ret;
});
};
}
});
return naturalized
};
// Async load series, store as retrieved
function storeInstances(instances) {
const naturalizedInstances = instances.map(addRetrieveBulkData);
DicomMetadataStore.addInstances(naturalizedInstances, madeInClient);
}
function setSuccessFlag() {
const study = DicomMetadataStore.getStudy(
StudyInstanceUID,
madeInClient
);
study.isLoaded = true;
}
// Google Cloud Healthcare doesn't return StudyInstanceUID, so we need to add
// it manually here
seriesSummaryMetadata.forEach(aSeries => { aSeries.StudyInstanceUID = StudyInstanceUID })
DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient);
const numberOfSeries = seriesPromises.length;
seriesPromises.forEach(async (seriesPromise, index) => {
const instances = await seriesPromise;
storeInstances(instances);
if (index === numberOfSeries - 1) setSuccessFlag();
});
},
deleteStudyMetadataPromise,
getImageIdsForDisplaySet(displaySet) {
const images = displaySet.images;
@@ -2,7 +2,7 @@ function requestDisplaySetCreationForStudy(
dataSource,
DisplaySetService,
StudyInstanceUID,
madeInClient,
madeInClient
) {
// TODO: is this already short-circuited by the map of Retrieve promises?
if (
@@ -13,7 +13,7 @@ function requestDisplaySetCreationForStudy(
return;
}
dataSource.retrieveSeriesMetadata({ StudyInstanceUID, madeInClient });
dataSource.retrieve.series.metadata({ StudyInstanceUID, madeInClient });
}
export default requestDisplaySetCreationForStudy;