Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hp): enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol #3804

Merged
merged 44 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6aa03c8
new variable to speed up first image loading
rodrigobasilio2022 Aug 22, 2023
8b5b9d9
Add more comments
rodrigobasilio2022 Aug 23, 2023
0469cf2
Merge branch 'master' of https://github.com/rodrigobasilio2022/Viewer…
rodrigobasilio2022 Aug 24, 2023
aa81431
Update retrieve metadata async loader
rodrigobasilio2022 Aug 24, 2023
0819984
Applying reviewers suggestions
rodrigobasilio2022 Aug 25, 2023
d57b2ba
Add appConfig on modeEnter
rodrigobasilio2022 Aug 25, 2023
9ae7883
Merge branch 'master' of https://github.com/rodrigobasilio2022/Viewer…
rodrigobasilio2022 Aug 30, 2023
e60777e
Merge branch 'master' of https://github.com/rodrigobasilio2022/Viewer…
rodrigobasilio2022 Aug 31, 2023
bf5827d
Refactoring series promises gathering
rodrigobasilio2022 Aug 31, 2023
ac000af
Refactor the code according reviewers comments
rodrigobasilio2022 Aug 31, 2023
1a0a8fc
Refactoring promises to speed up required series fetch
rodrigobasilio2022 Sep 1, 2023
7f741ad
Fix promise array bug
rodrigobasilio2022 Sep 4, 2023
a154bd6
Merge branch 'master' of https://github.com/rodrigobasilio2022/Viewer…
rodrigobasilio2022 Sep 5, 2023
f0d4d87
Fix problem in SM display
rodrigobasilio2022 Sep 5, 2023
32e2d90
Merge branch 'master' into fix/firstImageLoadingTime
rodrigobasilio2022 Sep 5, 2023
fe1b286
Merge branch 'master' into fix/firstImageLoadingTime
rodrigobasilio2022 Sep 8, 2023
440e9a2
Refactor minimal conditions to run HP
rodrigobasilio2022 Sep 8, 2023
166cb28
Fire remaining series after hp run
rodrigobasilio2022 Sep 8, 2023
6f86424
Fix typo
rodrigobasilio2022 Sep 11, 2023
82ea84b
Merge branch 'master' of https://github.com/rodrigobasilio2022/Viewer…
rodrigobasilio2022 Sep 14, 2023
b8cf53a
Removing variables from dicomweb server
rodrigobasilio2022 Sep 14, 2023
557a7e5
Merge
igoroctaviano Nov 22, 2023
c252f6e
Merge branch 'master' of github.com:ohif/Viewers into fix/first-image…
igoroctaviano Nov 22, 2023
eed3f62
Eslint
igoroctaviano Nov 23, 2023
8751988
Address loading issue
igoroctaviano Nov 23, 2023
1f11a23
Rollback panel changes
igoroctaviano Nov 23, 2023
1ae406d
Update hanging protocol
igoroctaviano Nov 23, 2023
8a90620
Merge branch 'master' of github.com:ohif/Viewers into fix/first-image…
igoroctaviano Nov 27, 2023
d638a46
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Nov 27, 2023
5499eb1
Merge branch 'master' of github.com:ohif/Viewers into fix/first-image…
igoroctaviano Nov 28, 2023
8bdef33
Address PR comments
igoroctaviano Nov 28, 2023
6312a25
Merge branch 'fix/first-image-load-time' of github.com:ohif/Viewers i…
igoroctaviano Nov 28, 2023
c206a33
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Dec 6, 2023
1e01b1e
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Dec 6, 2023
80ea04c
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Dec 12, 2023
c9417c6
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Dec 13, 2023
6301e76
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Dec 20, 2023
43d94a0
Address comments
igoroctaviano Dec 20, 2023
73c79a5
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Jan 8, 2024
b26ef06
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Jan 8, 2024
ed1ab7a
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Jan 8, 2024
1cbf9db
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Jan 8, 2024
b8e06c0
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Jan 8, 2024
1225067
Merge branch 'master' into fix/first-image-load-time
igoroctaviano Jan 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 51 additions & 21 deletions extensions/default/src/DicomWebDataSource/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,35 @@ const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1';
const metadataProvider = classes.MetadataProvider;

/**
* Creates a DICOM Web API based on the provided configuration.
*
* @param {string} name - Data source name
* @param {string} wadoUriRoot - Legacy? (potentially unused/replaced)
* @param {string} qidoRoot - Base URL to use for QIDO requests
* @param {string} wadoRoot - Base URL to use for WADO requests
* @param {boolean} qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
* @param {string} imageRengering - wadors | ? (unsure of where/how this is used)
* @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used)
* @param {bool} supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE)
* @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
* @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true
* @param {object} dicomWebConfig - Configuration for the DICOM Web API
* @param {string} dicomWebConfig.name - Data source name
* @param {string} dicomWebConfig.wadoUriRoot - Legacy? (potentially unused/replaced)
* @param {string} dicomWebConfig.qidoRoot - Base URL to use for QIDO requests
* @param {string} dicomWebConfig.wadoRoot - Base URL to use for WADO requests
* @param {string} dicomWebConfig.wadoUri - Base URL to use for WADO URI requests
* @param {boolean} dicomWebConfig.qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
* @param {string} dicomWebConfig.imageRendering - wadors | ? (unsure of where/how this is used)
* @param {string} dicomWebConfig.thumbnailRendering - wadors | ? (unsure of where/how this is used)
* @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) {
const { userAuthenticationService, customizationService } = servicesManager.services;
Expand Down Expand Up @@ -191,6 +209,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
sortCriteria,
sortFunction,
madeInClient = false,
returnPromises = false,
} = {}) => {
if (!StudyInstanceUID) {
throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID');
Expand All @@ -202,7 +221,8 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
filters,
sortCriteria,
sortFunction,
madeInClient
madeInClient,
returnPromises
);
}

Expand Down Expand Up @@ -336,7 +356,8 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
filters,
sortCriteria,
sortFunction,
madeInClient = false
madeInClient = false,
returnPromises = false
) => {
const enableStudyLazyLoad = true;
wadoDicomWebClient.headers = generateWadoHeader();
Expand Down Expand Up @@ -411,7 +432,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
const naturalizedInstances = instances.map(addRetrieveBulkData);

// Adding instanceMetadata to OHIF MetadataProvider
naturalizedInstances.forEach((instance, index) => {
naturalizedInstances.forEach(instance => {
instance.wadoRoot = dicomWebConfig.wadoRoot;
instance.wadoUri = dicomWebConfig.wadoUri;

Expand All @@ -438,7 +459,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {
}

function setSuccessFlag() {
const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient);
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
if (!study) {
return;
}
Expand All @@ -453,13 +474,22 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {

DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient);

const seriesDeliveredPromises = seriesPromises.map(promise =>
promise.then(instances => {
const seriesDeliveredPromises = seriesPromises.map(promise => {
if (!returnPromises) {
promise?.start();
}
return promise.then(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;
},
Expand Down Expand Up @@ -491,7 +521,7 @@ function createDicomWebApi(dicomWebConfig, servicesManager) {

return imageIds;
},
getImageIdsForInstance({ instance, frame }) {
getImageIdsForInstance({ instance, frame = undefined }) {
const imageIds = getImageId({
instance,
frame,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { api } from 'dicomweb-client';
* performing searches doesn't work. This version fixes the query issue
* by manually implementing a query option.
*/

export default class StaticWadoClient extends api.DICOMwebClient {
static studyFilterKeys = {
studyinstanceuid: '0020000D',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ export default class RetrieveMetadataLoader {
* @param {Object} client The dicomweb-client.
* @param {Array} studyInstanceUID Study instance ui to be retrieved
* @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 {Function} [sortSeries] - Custom sort function for series
* @param {string} [filters.seriesInstanceUID] - series instance uid to filter results against
* @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.studyInstanceUID = studyInstanceUID;
this.filters = filters;
Expand All @@ -26,7 +33,6 @@ export default class RetrieveMetadataLoader {
const preLoadData = await this.preLoad();
const loadData = await this.load(preLoadData);
const postLoadData = await this.posLoad(loadData);

return postLoadData;
}

Expand All @@ -37,13 +43,9 @@ export default class RetrieveMetadataLoader {
async runLoaders(loaders) {
let result;
for (const loader of loaders) {
try {
result = await loader();
if (result && result.length) {
break; // closes iterator in case data is retrieved successfully
}
} catch (e) {
throw e;
result = await loader();
igoroctaviano marked this conversation as resolved.
Show resolved Hide resolved
if (result && result.length) {
break; // closes iterator in case data is retrieved successfully
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,58 @@
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';

// 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.
*
Expand All @@ -16,12 +67,17 @@ function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList)
hasNext() {
return seriesInstanceUIDList.length > 0;
},
async next() {
const seriesInstanceUID = seriesInstanceUIDList.shift();
return client.retrieveSeriesMetadata({
studyInstanceUID,
seriesInstanceUID,
next() {
const { seriesInstanceUID, metadata } = seriesInstanceUIDList.shift();
const promise = new DeferredPromise();
promise.setMetadata(metadata);
promise.setProcessFunction(() => {
return client.retrieveSeriesMetadata({
studyInstanceUID,
seriesInstanceUID,
});
});
return promise;
},
});
}
Expand All @@ -40,15 +96,22 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
const preLoaders = [];
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) {
const options = {
studyInstanceUID,
queryParams: { SeriesInstanceUID: seriesInstanceUID },
};
options.queryParams.SeriesInstanceUID = seriesInstanceUID;
preLoaders.push(client.searchForSeries.bind(client, options));
}
// Fallback preloader
preLoaders.push(client.searchForSeries.bind(client, { studyInstanceUID }));
preLoaders.push(client.searchForSeries.bind(client, options));

yield* preLoaders;
}
Expand All @@ -62,24 +125,23 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
const naturalized = result.map(naturalizeDataset);

return sortStudySeries(
naturalized,
sortCriteria || sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria,
sortFunction
);
return sortStudySeries(naturalized, sortCriteria, sortFunction);
}

async load(preLoadData) {
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 promises = [];

while (seriesAsyncLoader.hasNext()) {
promises.push(seriesAsyncLoader.next());
const promise = seriesAsyncLoader.next();
promises.push(promise);
}

return {
Expand Down
1 change: 1 addition & 0 deletions extensions/default/src/getHangingProtocolModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const defaultProtocol = {
editableBy: {},
protocolMatchingRules: [],
toolGroupIds: ['default'],
hpInitiationCriteria: { minSeriesLoaded: 1 },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a comment above this line explain what this means

// -1 would be used to indicate active only, whereas other values are
// the number of required priors referenced - so 0 means active with
// 0 or more priors.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -635,4 +635,4 @@ function _findTabAndStudyOfDisplaySet(displaySetInstanceUID, tabs) {
}
}
}
}
}
Loading
Loading