Add the ability to merge two different series queries to generate a complete study query result. Provides basic support for other types of merges, but those aren't yet added as full features.
253 lines
8.0 KiB
TypeScript
253 lines
8.0 KiB
TypeScript
import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core';
|
|
import { get, uniqBy } from 'lodash';
|
|
import {
|
|
MergeConfig,
|
|
CallForAllDataSourcesAsyncOptions,
|
|
CallForAllDataSourcesOptions,
|
|
CallForDefaultDataSourceOptions,
|
|
CallByRetrieveAETitleOptions,
|
|
MergeMap,
|
|
} from './types';
|
|
|
|
export const mergeMap: MergeMap = {
|
|
'query.studies.search': {
|
|
mergeKey: 'studyInstanceUid',
|
|
tagFunc: x => x,
|
|
},
|
|
'query.series.search': {
|
|
mergeKey: 'seriesInstanceUid',
|
|
tagFunc: (series, sourceName) => {
|
|
series.forEach(series => {
|
|
series.RetrieveAETitle = sourceName;
|
|
DicomMetadataStore.updateSeriesMetadata(series);
|
|
});
|
|
return series;
|
|
},
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Calls all data sources asynchronously and merges the results.
|
|
* @param {CallForAllDataSourcesAsyncOptions} options - The options for calling all data sources.
|
|
* @param {string} options.path - The path to the function to be called on each data source.
|
|
* @param {unknown[]} options.args - The arguments to be passed to the function.
|
|
* @param {ExtensionManager} options.extensionManager - The extension manager.
|
|
* @param {string[]} options.dataSourceNames - The names of the data sources to be called.
|
|
* @returns {Promise<unknown[]>} - A promise that resolves to the merged data from all data sources.
|
|
*/
|
|
export const callForAllDataSourcesAsync = async ({
|
|
mergeMap,
|
|
path,
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}: CallForAllDataSourcesAsyncOptions) => {
|
|
const { mergeKey, tagFunc } = mergeMap[path] || { tagFunc: x => x };
|
|
|
|
const dataSourceDefs = Object.values(extensionManager.dataSourceDefs);
|
|
const promises = [];
|
|
const mergedData = [];
|
|
|
|
for (const dataSourceDef of dataSourceDefs) {
|
|
const { configuration, sourceName } = dataSourceDef;
|
|
if (!!configuration && dataSourceNames.includes(sourceName)) {
|
|
const [dataSource] = extensionManager.getDataSources(sourceName);
|
|
const func = get(dataSource, path);
|
|
const promise = func.apply(dataSource, args);
|
|
promises.push(promise.then(data => mergedData.push(tagFunc(data, sourceName))));
|
|
}
|
|
}
|
|
|
|
await Promise.allSettled(promises);
|
|
|
|
return uniqBy(mergedData.flat(), obj => obj[mergeKey]);
|
|
};
|
|
|
|
/**
|
|
* Calls all data sources that match the provided names and merges their data.
|
|
* @param options - The options for calling all data sources.
|
|
* @param options.path - The path to the function to be called on each data source.
|
|
* @param options.args - The arguments to be passed to the function.
|
|
* @param options.extensionManager - The extension manager instance.
|
|
* @param options.dataSourceNames - The names of the data sources to be called.
|
|
* @returns The merged data from all the matching data sources.
|
|
*/
|
|
export const callForAllDataSources = ({
|
|
path,
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}: CallForAllDataSourcesOptions) => {
|
|
const dataSourceDefs = Object.values(extensionManager.dataSourceDefs);
|
|
const mergedData = [];
|
|
for (const dataSourceDef of dataSourceDefs) {
|
|
const { configuration, sourceName } = dataSourceDef;
|
|
if (!!configuration && dataSourceNames.includes(sourceName)) {
|
|
const [dataSource] = extensionManager.getDataSources(sourceName);
|
|
const func = get(dataSource, path);
|
|
const data = func.apply(dataSource, args);
|
|
mergedData.push(data);
|
|
}
|
|
}
|
|
return mergedData.flat();
|
|
};
|
|
|
|
/**
|
|
* Calls the default data source function specified by the given path with the provided arguments.
|
|
* @param {CallForDefaultDataSourceOptions} options - The options for calling the default data source.
|
|
* @param {string} options.path - The path to the function within the default data source.
|
|
* @param {unknown[]} options.args - The arguments to pass to the function.
|
|
* @param {string} options.defaultDataSourceName - The name of the default data source.
|
|
* @param {ExtensionManager} options.extensionManager - The extension manager instance.
|
|
* @returns {unknown} - The result of calling the default data source function.
|
|
*/
|
|
export const callForDefaultDataSource = ({
|
|
path,
|
|
args,
|
|
defaultDataSourceName,
|
|
extensionManager,
|
|
}: CallForDefaultDataSourceOptions) => {
|
|
const [dataSource] = extensionManager.getDataSources(defaultDataSourceName);
|
|
const func = get(dataSource, path);
|
|
return func.apply(dataSource, args);
|
|
};
|
|
|
|
/**
|
|
* Calls the data source specified by the RetrieveAETitle of the given display set.
|
|
* @typedef {Object} CallByRetrieveAETitleOptions
|
|
* @property {string} path - The path of the method to call on the data source.
|
|
* @property {unknown[]} args - The arguments to pass to the method.
|
|
* @property {string} defaultDataSourceName - The name of the default data source.
|
|
* @property {ExtensionManager} extensionManager - The extension manager.
|
|
*/
|
|
export const callByRetrieveAETitle = ({
|
|
path,
|
|
args,
|
|
defaultDataSourceName,
|
|
extensionManager,
|
|
}: CallByRetrieveAETitleOptions) => {
|
|
const [displaySet] = args;
|
|
const seriesMetadata = DicomMetadataStore.getSeries(
|
|
displaySet.StudyInstanceUID,
|
|
displaySet.SeriesInstanceUID
|
|
);
|
|
const [dataSource] = extensionManager.getDataSources(
|
|
seriesMetadata.RetrieveAETitle || defaultDataSourceName
|
|
);
|
|
return dataSource[path](...args);
|
|
};
|
|
|
|
function createMergeDataSourceApi(
|
|
mergeConfig: MergeConfig,
|
|
UserAuthenticationService: unknown,
|
|
extensionManager
|
|
) {
|
|
const { seriesMerge } = mergeConfig;
|
|
const { dataSourceNames, defaultDataSourceName } = seriesMerge;
|
|
|
|
const implementation = {
|
|
initialize: (...args: unknown[]) =>
|
|
callForAllDataSources({ path: 'initialize', args, extensionManager, dataSourceNames }),
|
|
query: {
|
|
studies: {
|
|
search: (...args: unknown[]) =>
|
|
callForAllDataSourcesAsync({
|
|
mergeMap,
|
|
path: 'query.studies.search',
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}),
|
|
},
|
|
series: {
|
|
search: (...args: unknown[]) =>
|
|
callForAllDataSourcesAsync({
|
|
mergeMap,
|
|
path: 'query.series.search',
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}),
|
|
},
|
|
instances: {
|
|
search: (...args: unknown[]) =>
|
|
callForAllDataSourcesAsync({
|
|
mergeMap,
|
|
path: 'query.instances.search',
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}),
|
|
},
|
|
},
|
|
retrieve: {
|
|
bulkDataURI: (...args: unknown[]) =>
|
|
callForAllDataSourcesAsync({
|
|
mergeMap,
|
|
path: 'retrieve.bulkDataURI',
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}),
|
|
directURL: (...args: unknown[]) =>
|
|
callForDefaultDataSource({
|
|
path: 'retrieve.directURL',
|
|
args,
|
|
defaultDataSourceName,
|
|
extensionManager,
|
|
}),
|
|
series: {
|
|
metadata: (...args: unknown[]) =>
|
|
callForAllDataSourcesAsync({
|
|
mergeMap,
|
|
path: 'retrieve.series.metadata',
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}),
|
|
},
|
|
},
|
|
store: {
|
|
dicom: (...args: unknown[]) =>
|
|
callForDefaultDataSource({
|
|
path: 'store.dicom',
|
|
args,
|
|
defaultDataSourceName,
|
|
extensionManager,
|
|
}),
|
|
},
|
|
deleteStudyMetadataPromise: (...args: unknown[]) =>
|
|
callForAllDataSources({
|
|
path: 'deleteStudyMetadataPromise',
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}),
|
|
getImageIdsForDisplaySet: (...args: unknown[]) =>
|
|
callByRetrieveAETitle({
|
|
path: 'getImageIdsForDisplaySet',
|
|
args,
|
|
defaultDataSourceName,
|
|
extensionManager,
|
|
}),
|
|
getImageIdsForInstance: (...args: unknown[]) =>
|
|
callByRetrieveAETitle({
|
|
path: 'getImageIdsForDisplaySet',
|
|
args,
|
|
defaultDataSourceName,
|
|
extensionManager,
|
|
}),
|
|
getStudyInstanceUIDs: (...args: unknown[]) =>
|
|
callForAllDataSources({
|
|
path: 'getStudyInstanceUIDs',
|
|
args,
|
|
extensionManager,
|
|
dataSourceNames,
|
|
}),
|
|
};
|
|
|
|
return IWebApiDataSource.create(implementation);
|
|
}
|
|
|
|
export { createMergeDataSourceApi };
|