feat(hp): enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol (#3804)
Co-authored-by: rodrigobasilio2022 <rodrigo.basilio@radicalimaging.com> Co-authored-by: rodrigobasilio2022 <114958722+rodrigobasilio2022@users.noreply.github.com>
This commit is contained in:
parent
b5cdf88a57
commit
0049f4c030
@ -28,17 +28,35 @@ const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1';
|
|||||||
const metadataProvider = classes.MetadataProvider;
|
const metadataProvider = classes.MetadataProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Creates a DICOM Web API based on the provided configuration.
|
||||||
*
|
*
|
||||||
* @param {string} name - Data source name
|
* @param {object} dicomWebConfig - Configuration for the DICOM Web API
|
||||||
* @param {string} wadoUriRoot - Legacy? (potentially unused/replaced)
|
* @param {string} dicomWebConfig.name - Data source name
|
||||||
* @param {string} qidoRoot - Base URL to use for QIDO requests
|
* @param {string} dicomWebConfig.wadoUriRoot - Legacy? (potentially unused/replaced)
|
||||||
* @param {string} wadoRoot - Base URL to use for WADO requests
|
* @param {string} dicomWebConfig.qidoRoot - Base URL to use for QIDO requests
|
||||||
* @param {boolean} qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
|
* @param {string} dicomWebConfig.wadoRoot - Base URL to use for WADO requests
|
||||||
* @param {string} imageRengering - wadors | ? (unsure of where/how this is used)
|
* @param {string} dicomWebConfig.wadoUri - Base URL to use for WADO URI requests
|
||||||
* @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used)
|
* @param {boolean} dicomWebConfig.qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
|
||||||
* @param {bool} supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE)
|
* @param {string} dicomWebConfig.imageRendering - wadors | ? (unsure of where/how this is used)
|
||||||
* @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
* @param {string} dicomWebConfig.thumbnailRendering - wadors | ? (unsure of where/how this is used)
|
||||||
* @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true
|
* @param {boolean} dicomWebConfig.supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE)
|
||||||
|
* @param {boolean} dicomWebConfig.lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
||||||
|
* @param {string|boolean} dicomWebConfig.singlepart - indicates if the retrieves can fetch singlepart. Options are bulkdata, video, image, or boolean true
|
||||||
|
* @param {string} dicomWebConfig.requestTransferSyntaxUID - Transfer syntax to request from the server
|
||||||
|
* @param {object} dicomWebConfig.acceptHeader - Accept header to use for requests
|
||||||
|
* @param {boolean} dicomWebConfig.omitQuotationForMultipartRequest - Whether to omit quotation marks for multipart requests
|
||||||
|
* @param {boolean} dicomWebConfig.supportsFuzzyMatching - Whether the server supports fuzzy matching
|
||||||
|
* @param {boolean} dicomWebConfig.supportsWildcard - Whether the server supports wildcard matching
|
||||||
|
* @param {boolean} dicomWebConfig.supportsNativeDICOMModel - Whether the server supports the native DICOM model
|
||||||
|
* @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading
|
||||||
|
* @param {boolean} dicomWebConfig.enableRequestTag - Whether to enable request tag
|
||||||
|
* @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading
|
||||||
|
* @param {boolean} dicomWebConfig.bulkDataURI - Whether to enable bulkDataURI
|
||||||
|
* @param {function} dicomWebConfig.onConfiguration - Function that is called after the configuration is initialized
|
||||||
|
* @param {boolean} dicomWebConfig.staticWado - Whether to use the static WADO client
|
||||||
|
* @param {object} userAuthenticationService - User authentication service
|
||||||
|
* @param {object} userAuthenticationService.getAuthorizationHeader - Function that returns the authorization header
|
||||||
|
* @returns {object} - DICOM Web API object
|
||||||
*/
|
*/
|
||||||
function createDicomWebApi(dicomWebConfig, servicesManager) {
|
function createDicomWebApi(dicomWebConfig, servicesManager) {
|
||||||
const { userAuthenticationService, customizationService } = servicesManager.services;
|
const { userAuthenticationService, customizationService } = servicesManager.services;
|
||||||
@ -191,6 +209,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
|
|||||||
sortCriteria,
|
sortCriteria,
|
||||||
sortFunction,
|
sortFunction,
|
||||||
madeInClient = false,
|
madeInClient = false,
|
||||||
|
returnPromises = false,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
if (!StudyInstanceUID) {
|
if (!StudyInstanceUID) {
|
||||||
throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID');
|
throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID');
|
||||||
@ -202,7 +221,8 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
|
|||||||
filters,
|
filters,
|
||||||
sortCriteria,
|
sortCriteria,
|
||||||
sortFunction,
|
sortFunction,
|
||||||
madeInClient
|
madeInClient,
|
||||||
|
returnPromises
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +356,8 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
|
|||||||
filters,
|
filters,
|
||||||
sortCriteria,
|
sortCriteria,
|
||||||
sortFunction,
|
sortFunction,
|
||||||
madeInClient = false
|
madeInClient = false,
|
||||||
|
returnPromises = false
|
||||||
) => {
|
) => {
|
||||||
const enableStudyLazyLoad = true;
|
const enableStudyLazyLoad = true;
|
||||||
wadoDicomWebClient.headers = generateWadoHeader();
|
wadoDicomWebClient.headers = generateWadoHeader();
|
||||||
@ -416,7 +437,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
|
|||||||
const naturalizedInstances = instances.map(addRetrieveBulkData);
|
const naturalizedInstances = instances.map(addRetrieveBulkData);
|
||||||
|
|
||||||
// Adding instanceMetadata to OHIF MetadataProvider
|
// Adding instanceMetadata to OHIF MetadataProvider
|
||||||
naturalizedInstances.forEach((instance, index) => {
|
naturalizedInstances.forEach(instance => {
|
||||||
instance.wadoRoot = dicomWebConfig.wadoRoot;
|
instance.wadoRoot = dicomWebConfig.wadoRoot;
|
||||||
instance.wadoUri = dicomWebConfig.wadoUri;
|
instance.wadoUri = dicomWebConfig.wadoUri;
|
||||||
|
|
||||||
@ -443,7 +464,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setSuccessFlag() {
|
function setSuccessFlag() {
|
||||||
const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient);
|
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
|
||||||
if (!study) {
|
if (!study) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -458,13 +479,22 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
|
|||||||
|
|
||||||
DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient);
|
DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient);
|
||||||
|
|
||||||
const seriesDeliveredPromises = seriesPromises.map(promise =>
|
const seriesDeliveredPromises = seriesPromises.map(promise => {
|
||||||
promise.then(instances => {
|
if (!returnPromises) {
|
||||||
|
promise?.start();
|
||||||
|
}
|
||||||
|
return promise.then(instances => {
|
||||||
storeInstances(instances);
|
storeInstances(instances);
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
await Promise.all(seriesDeliveredPromises);
|
|
||||||
setSuccessFlag();
|
if (returnPromises) {
|
||||||
|
Promise.all(seriesDeliveredPromises).then(() => setSuccessFlag());
|
||||||
|
return seriesPromises;
|
||||||
|
} else {
|
||||||
|
await Promise.all(seriesDeliveredPromises);
|
||||||
|
setSuccessFlag();
|
||||||
|
}
|
||||||
|
|
||||||
return seriesSummaryMetadata;
|
return seriesSummaryMetadata;
|
||||||
},
|
},
|
||||||
@ -496,7 +526,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
|
|||||||
|
|
||||||
return imageIds;
|
return imageIds;
|
||||||
},
|
},
|
||||||
getImageIdsForInstance({ instance, frame }) {
|
getImageIdsForInstance({ instance, frame = undefined }) {
|
||||||
const imageIds = getImageId({
|
const imageIds = getImageId({
|
||||||
instance,
|
instance,
|
||||||
frame,
|
frame,
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { api } from 'dicomweb-client';
|
|||||||
* performing searches doesn't work. This version fixes the query issue
|
* performing searches doesn't work. This version fixes the query issue
|
||||||
* by manually implementing a query option.
|
* by manually implementing a query option.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class StaticWadoClient extends api.DICOMwebClient {
|
export default class StaticWadoClient extends api.DICOMwebClient {
|
||||||
static studyFilterKeys = {
|
static studyFilterKeys = {
|
||||||
studyinstanceuid: '0020000D',
|
studyinstanceuid: '0020000D',
|
||||||
|
|||||||
@ -11,10 +11,17 @@ export default class RetrieveMetadataLoader {
|
|||||||
* @param {Object} client The dicomweb-client.
|
* @param {Object} client The dicomweb-client.
|
||||||
* @param {Array} studyInstanceUID Study instance ui to be retrieved
|
* @param {Array} studyInstanceUID Study instance ui to be retrieved
|
||||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
* @param {string} [filters.seriesInstanceUID] - series instance uid to filter results against
|
||||||
* @param {Function} [sortSeries] - Custom sort function for series
|
* @param {Object} [sortCriteria] - Custom sort criteria used for series
|
||||||
|
* @param {Function} [sortFunction] - Custom sort function for series
|
||||||
*/
|
*/
|
||||||
constructor(client, studyInstanceUID, filters = {}, sortCriteria, sortFunction) {
|
constructor(
|
||||||
|
client,
|
||||||
|
studyInstanceUID,
|
||||||
|
filters = {},
|
||||||
|
sortCriteria = undefined,
|
||||||
|
sortFunction = undefined
|
||||||
|
) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.studyInstanceUID = studyInstanceUID;
|
this.studyInstanceUID = studyInstanceUID;
|
||||||
this.filters = filters;
|
this.filters = filters;
|
||||||
@ -26,7 +33,6 @@ export default class RetrieveMetadataLoader {
|
|||||||
const preLoadData = await this.preLoad();
|
const preLoadData = await this.preLoad();
|
||||||
const loadData = await this.load(preLoadData);
|
const loadData = await this.load(preLoadData);
|
||||||
const postLoadData = await this.posLoad(loadData);
|
const postLoadData = await this.posLoad(loadData);
|
||||||
|
|
||||||
return postLoadData;
|
return postLoadData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,13 +43,9 @@ export default class RetrieveMetadataLoader {
|
|||||||
async runLoaders(loaders) {
|
async runLoaders(loaders) {
|
||||||
let result;
|
let result;
|
||||||
for (const loader of loaders) {
|
for (const loader of loaders) {
|
||||||
try {
|
result = await loader();
|
||||||
result = await loader();
|
if (result && result.length) {
|
||||||
if (result && result.length) {
|
break; // closes iterator in case data is retrieved successfully
|
||||||
break; // closes iterator in case data is retrieved successfully
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,58 @@
|
|||||||
import dcmjs from 'dcmjs';
|
import dcmjs from 'dcmjs';
|
||||||
import { sortStudySeries, sortingCriteria } from '@ohif/core/src/utils/sortStudy';
|
import { sortStudySeries } from '@ohif/core/src/utils/sortStudy';
|
||||||
import RetrieveMetadataLoader from './retrieveMetadataLoader';
|
import RetrieveMetadataLoader from './retrieveMetadataLoader';
|
||||||
|
|
||||||
|
// Series Date, Series Time, Series Description and Series Number to be included
|
||||||
|
// in the series metadata query result
|
||||||
|
const includeField = ['00080021', '00080031', '0008103E', '00200011'].join(',');
|
||||||
|
|
||||||
|
export class DeferredPromise {
|
||||||
|
metadata = undefined;
|
||||||
|
processFunction = undefined;
|
||||||
|
internalPromise = undefined;
|
||||||
|
thenFunction = undefined;
|
||||||
|
rejectFunction = undefined;
|
||||||
|
|
||||||
|
setMetadata(metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
setProcessFunction(func) {
|
||||||
|
this.processFunction = func;
|
||||||
|
}
|
||||||
|
getPromise() {
|
||||||
|
return this.start();
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
if (this.internalPromise) {
|
||||||
|
return this.internalPromise;
|
||||||
|
}
|
||||||
|
this.internalPromise = this.processFunction();
|
||||||
|
// in case then and reject functions called before start
|
||||||
|
if (this.thenFunction) {
|
||||||
|
this.then(this.thenFunction);
|
||||||
|
this.thenFunction = undefined;
|
||||||
|
}
|
||||||
|
if (this.rejectFunction) {
|
||||||
|
this.reject(this.rejectFunction);
|
||||||
|
this.rejectFunction = undefined;
|
||||||
|
}
|
||||||
|
return this.internalPromise;
|
||||||
|
}
|
||||||
|
then(func) {
|
||||||
|
if (this.internalPromise) {
|
||||||
|
return this.internalPromise.then(func);
|
||||||
|
} else {
|
||||||
|
this.thenFunction = func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reject(func) {
|
||||||
|
if (this.internalPromise) {
|
||||||
|
return this.internalPromise.reject(func);
|
||||||
|
} else {
|
||||||
|
this.rejectFunction = func;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Creates an immutable series loader object which loads each series sequentially using the iterator interface.
|
* Creates an immutable series loader object which loads each series sequentially using the iterator interface.
|
||||||
*
|
*
|
||||||
@ -16,12 +67,17 @@ function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList)
|
|||||||
hasNext() {
|
hasNext() {
|
||||||
return seriesInstanceUIDList.length > 0;
|
return seriesInstanceUIDList.length > 0;
|
||||||
},
|
},
|
||||||
async next() {
|
next() {
|
||||||
const seriesInstanceUID = seriesInstanceUIDList.shift();
|
const { seriesInstanceUID, metadata } = seriesInstanceUIDList.shift();
|
||||||
return client.retrieveSeriesMetadata({
|
const promise = new DeferredPromise();
|
||||||
studyInstanceUID,
|
promise.setMetadata(metadata);
|
||||||
seriesInstanceUID,
|
promise.setProcessFunction(() => {
|
||||||
|
return client.retrieveSeriesMetadata({
|
||||||
|
studyInstanceUID,
|
||||||
|
seriesInstanceUID,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
return promise;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -40,15 +96,22 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
|||||||
const preLoaders = [];
|
const preLoaders = [];
|
||||||
const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this;
|
const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this;
|
||||||
|
|
||||||
|
// asking to include Series Date, Series Time, Series Description
|
||||||
|
// and Series Number in the series metadata returned to better sort series
|
||||||
|
// in preLoad function
|
||||||
|
let options = {
|
||||||
|
studyInstanceUID,
|
||||||
|
queryParams: {
|
||||||
|
includefield: includeField,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (seriesInstanceUID) {
|
if (seriesInstanceUID) {
|
||||||
const options = {
|
options.queryParams.SeriesInstanceUID = seriesInstanceUID;
|
||||||
studyInstanceUID,
|
|
||||||
queryParams: { SeriesInstanceUID: seriesInstanceUID },
|
|
||||||
};
|
|
||||||
preLoaders.push(client.searchForSeries.bind(client, options));
|
preLoaders.push(client.searchForSeries.bind(client, options));
|
||||||
}
|
}
|
||||||
// Fallback preloader
|
// Fallback preloader
|
||||||
preLoaders.push(client.searchForSeries.bind(client, { studyInstanceUID }));
|
preLoaders.push(client.searchForSeries.bind(client, options));
|
||||||
|
|
||||||
yield* preLoaders;
|
yield* preLoaders;
|
||||||
}
|
}
|
||||||
@ -62,24 +125,23 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
|
|||||||
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
|
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
|
||||||
const naturalized = result.map(naturalizeDataset);
|
const naturalized = result.map(naturalizeDataset);
|
||||||
|
|
||||||
return sortStudySeries(
|
return sortStudySeries(naturalized, sortCriteria, sortFunction);
|
||||||
naturalized,
|
|
||||||
sortCriteria || sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria,
|
|
||||||
sortFunction
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async load(preLoadData) {
|
async load(preLoadData) {
|
||||||
const { client, studyInstanceUID } = this;
|
const { client, studyInstanceUID } = this;
|
||||||
|
|
||||||
const seriesInstanceUIDs = preLoadData.map(s => s.SeriesInstanceUID);
|
const seriesInstanceUIDs = preLoadData.map(seriesMetadata => {
|
||||||
|
return { seriesInstanceUID: seriesMetadata.SeriesInstanceUID, metadata: seriesMetadata };
|
||||||
|
});
|
||||||
|
|
||||||
const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs);
|
const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs);
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
while (seriesAsyncLoader.hasNext()) {
|
while (seriesAsyncLoader.hasNext()) {
|
||||||
promises.push(seriesAsyncLoader.next());
|
const promise = seriesAsyncLoader.next();
|
||||||
|
promises.push(promise);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ const defaultProtocol = {
|
|||||||
editableBy: {},
|
editableBy: {},
|
||||||
protocolMatchingRules: [],
|
protocolMatchingRules: [],
|
||||||
toolGroupIds: ['default'],
|
toolGroupIds: ['default'],
|
||||||
|
hpInitiationCriteria: { minSeriesLoaded: 1 },
|
||||||
// -1 would be used to indicate active only, whereas other values are
|
// -1 would be used to indicate active only, whereas other values are
|
||||||
// the number of required priors referenced - so 0 means active with
|
// the number of required priors referenced - so 0 means active with
|
||||||
// 0 or more priors.
|
// 0 or more priors.
|
||||||
|
|||||||
@ -635,4 +635,4 @@ function _findTabAndStudyOfDisplaySet(displaySetInstanceUID, tabs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@ import { history } from '../../utils/history';
|
|||||||
import loadModules from '../../pluginImports';
|
import loadModules from '../../pluginImports';
|
||||||
import isSeriesFilterUsed from '../../utils/isSeriesFilterUsed';
|
import isSeriesFilterUsed from '../../utils/isSeriesFilterUsed';
|
||||||
|
|
||||||
const { getSplitParam } = utils;
|
const { getSplitParam, sortingCriteria } = utils;
|
||||||
const { TimingEnum } = Types;
|
const { TimingEnum } = Types;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,11 +26,33 @@ const { TimingEnum } = Types;
|
|||||||
* @returns array of subscriptions to cancel
|
* @returns array of subscriptions to cancel
|
||||||
*/
|
*/
|
||||||
function defaultRouteInit(
|
function defaultRouteInit(
|
||||||
{ servicesManager, studyInstanceUIDs, dataSource, filters },
|
{ servicesManager, studyInstanceUIDs, dataSource, filters, appConfig },
|
||||||
hangingProtocolId
|
hangingProtocolId
|
||||||
) {
|
) {
|
||||||
const { displaySetService, hangingProtocolService, uiNotificationService } =
|
const { displaySetService, hangingProtocolService, uiNotificationService, customizationService } =
|
||||||
servicesManager.services;
|
servicesManager.services;
|
||||||
|
/**
|
||||||
|
* Function to apply the hanging protocol when the minimum number of display sets were
|
||||||
|
* received or all display sets retrieval were completed
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function applyHangingProtocol() {
|
||||||
|
const displaySets = displaySetService.getActiveDisplaySets();
|
||||||
|
|
||||||
|
if (!displaySets || !displaySets.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the studies list to use
|
||||||
|
const studies = getStudies(studyInstanceUIDs, displaySets);
|
||||||
|
|
||||||
|
// study being displayed, and is thus the "active" study.
|
||||||
|
const activeStudy = studies[0];
|
||||||
|
|
||||||
|
// run the hanging protocol matching on the displaySets with the predefined
|
||||||
|
// hanging protocol in the mode configuration
|
||||||
|
hangingProtocolService.run({ studies, activeStudy, displaySets }, hangingProtocolId);
|
||||||
|
}
|
||||||
|
|
||||||
const unsubscriptions = [];
|
const unsubscriptions = [];
|
||||||
const issuedWarningSeries = [];
|
const issuedWarningSeries = [];
|
||||||
@ -55,6 +77,7 @@ function defaultRouteInit(
|
|||||||
duration: 7000,
|
duration: 7000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
displaySetService.makeDisplaySets(seriesMetadata.instances, madeInClient);
|
displaySetService.makeDisplaySets(seriesMetadata.instances, madeInClient);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -63,10 +86,15 @@ function defaultRouteInit(
|
|||||||
|
|
||||||
log.time(TimingEnum.STUDY_TO_DISPLAY_SETS);
|
log.time(TimingEnum.STUDY_TO_DISPLAY_SETS);
|
||||||
log.time(TimingEnum.STUDY_TO_FIRST_IMAGE);
|
log.time(TimingEnum.STUDY_TO_FIRST_IMAGE);
|
||||||
|
|
||||||
const allRetrieves = studyInstanceUIDs.map(StudyInstanceUID =>
|
const allRetrieves = studyInstanceUIDs.map(StudyInstanceUID =>
|
||||||
dataSource.retrieve.series.metadata({
|
dataSource.retrieve.series.metadata({
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
filters,
|
filters,
|
||||||
|
returnPromises: true,
|
||||||
|
sortCriteria:
|
||||||
|
customizationService.get('sortingCriteria') ||
|
||||||
|
sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -77,31 +105,34 @@ function defaultRouteInit(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// The hanging protocol matching service is fairly expensive to run multiple
|
Promise.allSettled(allRetrieves).then(promises => {
|
||||||
// times, and doesn't allow partial matches to be made (it will simply fail
|
|
||||||
// to display anything if a required match fails), so we wait here until all metadata
|
|
||||||
// is retrieved (which will synchronously trigger the display set creation)
|
|
||||||
// until we run the hanging protocol matching service.
|
|
||||||
|
|
||||||
Promise.allSettled(allRetrieves).then(() => {
|
|
||||||
log.timeEnd(TimingEnum.STUDY_TO_DISPLAY_SETS);
|
log.timeEnd(TimingEnum.STUDY_TO_DISPLAY_SETS);
|
||||||
log.time(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
|
log.time(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE);
|
||||||
log.time(TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
|
log.time(TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES);
|
||||||
const displaySets = displaySetService.getActiveDisplaySets();
|
|
||||||
|
|
||||||
if (!displaySets || !displaySets.length) {
|
const allPromises = [];
|
||||||
return;
|
const remainingPromises = [];
|
||||||
|
|
||||||
|
function startRemainingPromises(remainingPromises) {
|
||||||
|
remainingPromises.forEach(p => p.forEach(p => p.start()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the studies list to use
|
promises.forEach(promise => {
|
||||||
const studies = getStudies(studyInstanceUIDs, displaySets);
|
const retrieveSeriesMetadataPromise = promise.value;
|
||||||
|
if (Array.isArray(retrieveSeriesMetadataPromise)) {
|
||||||
|
const { requiredSeries, remaining } = hangingProtocolService.filterSeriesRequiredForRun(
|
||||||
|
hangingProtocolId,
|
||||||
|
retrieveSeriesMetadataPromise
|
||||||
|
);
|
||||||
|
const requiredSeriesPromises = requiredSeries.map(promise => promise.start());
|
||||||
|
allPromises.push(Promise.allSettled(requiredSeriesPromises));
|
||||||
|
remainingPromises.push(remaining);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// study being displayed, and is thus the "active" study.
|
Promise.allSettled(allPromises).then(applyHangingProtocol);
|
||||||
const activeStudy = studies[0];
|
startRemainingPromises(remainingPromises);
|
||||||
|
applyHangingProtocol();
|
||||||
// run the hanging protocol matching on the displaySets with the predefined
|
|
||||||
// hanging protocol in the mode configuration
|
|
||||||
hangingProtocolService.run({ studies, activeStudy, displaySets }, hangingProtocolId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return unsubscriptions;
|
return unsubscriptions;
|
||||||
@ -401,6 +432,7 @@ export default function ModeRoute({
|
|||||||
studyInstanceUIDs,
|
studyInstanceUIDs,
|
||||||
dataSource,
|
dataSource,
|
||||||
filters,
|
filters,
|
||||||
|
appConfig,
|
||||||
},
|
},
|
||||||
hangingProtocolIdToUse
|
hangingProtocolIdToUse
|
||||||
);
|
);
|
||||||
@ -482,4 +514,5 @@ ModeRoute.propTypes = {
|
|||||||
extensionManager: PropTypes.object,
|
extensionManager: PropTypes.object,
|
||||||
servicesManager: PropTypes.object,
|
servicesManager: PropTypes.object,
|
||||||
hotkeysManager: PropTypes.object,
|
hotkeysManager: PropTypes.object,
|
||||||
|
commandsManager: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -214,6 +214,32 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the series required for running a hanging protocol.
|
||||||
|
*
|
||||||
|
* This can be extended in the future with more complex selection rules e.g.
|
||||||
|
* N series of a given type, and M of a different type, such as all CT series,
|
||||||
|
* and all SR, and then everything else.
|
||||||
|
*
|
||||||
|
* @param protocolId - The ID of the hanging protocol.
|
||||||
|
* @param seriesPromises - An array of promises representing the series.
|
||||||
|
* @returns An object containing the required series and the remaining series.
|
||||||
|
*/
|
||||||
|
public filterSeriesRequiredForRun(protocolId, seriesPromises) {
|
||||||
|
if (Array.isArray(protocolId)) {
|
||||||
|
protocolId = protocolId[0];
|
||||||
|
}
|
||||||
|
const minSeriesLoadedToRunHP =
|
||||||
|
this.getProtocolById(protocolId)?.hpInitiationCriteria?.minSeriesLoaded ||
|
||||||
|
seriesPromises.length;
|
||||||
|
const requiredSeries = seriesPromises.slice(0, minSeriesLoadedToRunHP);
|
||||||
|
const remaining = seriesPromises.slice(minSeriesLoadedToRunHP);
|
||||||
|
return {
|
||||||
|
requiredSeries,
|
||||||
|
remaining,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** Gets the protocol with id 'default' */
|
/** Gets the protocol with id 'default' */
|
||||||
public getDefaultProtocol(): HangingProtocol.Protocol {
|
public getDefaultProtocol(): HangingProtocol.Protocol {
|
||||||
return this.getProtocolById('default');
|
return this.getProtocolById('default');
|
||||||
@ -543,8 +569,8 @@ export default class HangingProtocolService extends PubSubService {
|
|||||||
viewportId: existingViewportId
|
viewportId: existingViewportId
|
||||||
? existingViewportId
|
? existingViewportId
|
||||||
: index === 0
|
: index === 0
|
||||||
? 'default'
|
? 'default'
|
||||||
: uuidv4(),
|
: uuidv4(),
|
||||||
},
|
},
|
||||||
displaySets: viewport.displaySets || [],
|
displaySets: viewport.displaySets || [],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -293,6 +293,17 @@ export type Protocol = {
|
|||||||
*/
|
*/
|
||||||
numberOfPriorsReferenced?: number;
|
numberOfPriorsReferenced?: number;
|
||||||
syncDataForViewports?: boolean;
|
syncDataForViewports?: boolean;
|
||||||
|
/**
|
||||||
|
* Set of minimal conditions necessary to run the hanging protocol.
|
||||||
|
*/
|
||||||
|
hpInitiationCriteria?: {
|
||||||
|
/* If configured, sets the minimum number of series needed to run the hanging
|
||||||
|
* protocol and start displaying images. Used when OHIF needs to handle studies
|
||||||
|
* with several series and it is required that the first image should be loaded
|
||||||
|
* faster.
|
||||||
|
*/
|
||||||
|
minSeriesLoaded: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Used to dynamically generate protocols.
|
/** Used to dynamically generate protocols.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user