This commit is contained in:
mario
2025-03-07 13:47:44 +07:00
commit c4efec5a14
3358 changed files with 303774 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
export default function (wadoRoot) {
return {
series: (StudyInstanceUID, SeriesInstanceUID) => {
return new Promise((resolve, reject) => {
// Reject because of Quality. (Seems the most sensible out of the options)
const CodeValueAndCodeSchemeDesignator = `113001%5EDCM`;
const url = `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/reject/${CodeValueAndCodeSchemeDesignator}`;
const xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
//Send the proper header information along with the request
// TODO -> Auth when we re-add authorization.
console.log(xhr);
xhr.onreadystatechange = function () {
//Call a function when the state changes.
if (xhr.readyState == 4) {
switch (xhr.status) {
case 204:
resolve(xhr.responseText);
break;
case 404:
reject('Your dataSource does not support reject functionality');
}
}
};
xhr.send();
});
},
};
}

View File

@@ -0,0 +1,280 @@
export default [
{
'00080005': { vr: 'CS', Value: ['ISO_IR 100'] },
'00080008': { vr: 'CS', Value: ['ORIGINAL', 'PRIMARY', 'LOCALIZER'] },
'00080016': { vr: 'UI', Value: ['1.2.840.10008.5.1.4.1.1.2'] },
'00080018': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.6'],
},
'00080020': { vr: 'DA', Value: ['20141125'] },
'00080021': { vr: 'DA', Value: ['20141125'] },
'00080022': { vr: 'DA', Value: ['20141125'] },
'00080023': { vr: 'DA', Value: ['20141125'] },
'00080030': { vr: 'TM', Value: ['094528.000'] },
'00080031': { vr: 'TM', Value: ['094604.688'] },
'00080032': { vr: 'TM', Value: ['094623.600'] },
'00080033': { vr: 'TM', Value: ['094623.600'] },
'00080050': { vr: 'SH', Value: ['000092218'] },
'00080060': { vr: 'CS', Value: ['CT'] },
'00080070': { vr: 'LO', Value: ['TOSHIBA'] },
'00080080': { vr: 'LO', Value: ['Precision Imaging Metrics'] },
'00080090': { vr: 'PN' },
'00081010': { vr: 'SH' },
'00081030': { vr: 'LO', Value: ['DFCI CT CHEST W CONTRAST 6023'] },
'00081032': {
vr: 'SQ',
Value: [
{
'00080100': { vr: 'SH', Value: ['6023'] },
'00080102': { vr: 'SH', Value: ['GEIIS'] },
'00080103': { vr: 'SH', Value: ['0'] },
'00080104': { vr: 'LO', Value: ['DFCI CT CHEST W CONTRAST 6023'] },
},
],
},
'0008103E': { vr: 'LO', Value: ['2.0'] },
'00081040': { vr: 'LO' },
'00081070': { vr: 'PN' },
'00081090': { vr: 'LO', Value: ['Aquilion'] },
'00081110': {
vr: 'SQ',
Value: [
{
'00081150': { vr: 'UI', Value: ['1.2.840.100008.3.1.2.3.1'] },
'00081155': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.4'],
},
},
],
},
'00100010': { vr: 'PN', Value: [{ Alphabetic: 'Venus' }] },
'00100020': { vr: 'LO', Value: ['0000005'] },
'00100021': { vr: 'LO', Value: ['001R74:20050625:205502036:195212'] },
'00100030': { vr: 'DA' },
'00100040': { vr: 'CS', Value: ['F'] },
'00101000': { vr: 'LO' },
'00101010': { vr: 'AS' },
'00101020': { vr: 'DS' },
'00101030': { vr: 'DS' },
'00104000': { vr: 'LT' },
'00180015': { vr: 'CS', Value: ['CHEST_TO_PELVIS'] },
'00180022': { vr: 'CS', Value: ['SCANOSCOPE'] },
'00180050': { vr: 'DS', Value: [2.0] },
'00180060': { vr: 'DS', Value: [120.0] },
'00180090': { vr: 'DS', Value: [1000.0] },
'00181000': { vr: 'LO' },
'00181020': { vr: 'LO', Value: ['V4.86ER003'] },
'00181030': { vr: 'LO', Value: ['Chest / Abdomen/Pelvis 5mm'] },
'00181100': { vr: 'DS', Value: [1000.0] },
'00181120': { vr: 'DS', Value: [0.0] },
'00181130': { vr: 'DS', Value: [102.0] },
'00181140': { vr: 'CS', Value: ['CW'] },
'00181150': { vr: 'IS', Value: [6840] },
'00181151': { vr: 'IS', Value: [100] },
'00181152': { vr: 'IS', Value: [600] },
'00181160': { vr: 'SH', Value: ['LARGE'] },
'00181170': { vr: 'IS', Value: [12] },
'00181190': { vr: 'DS', Value: [1.6, 1.4] },
'00181210': { vr: 'SH', Value: ['FL03'] },
'00185100': { vr: 'CS', Value: ['FFS'] },
'0020000D': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.1'],
},
'0020000E': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.2'],
},
'00200010': { vr: 'SH' },
'00200011': { vr: 'IS', Value: [1] },
'00200012': { vr: 'IS', Value: [2] },
'00200013': { vr: 'IS', Value: [2] },
'00200020': { vr: 'CS', Value: ['F', 'P'] },
'00200032': { vr: 'DS', Value: [-1.7e-4, -512.0, 1925.0] },
'00200037': { vr: 'DS', Value: [0.0, 0.0, -1.0, 0.0, 1.0, -0.0] },
'00200052': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.5'],
},
'00201040': { vr: 'LO' },
'00201041': { vr: 'DS', Value: [342.0] },
'00280002': { vr: 'US', Value: [1] },
'00280004': { vr: 'CS', Value: ['MONOCHROME2'] },
'00280010': { vr: 'US', Value: [512] },
'00280011': { vr: 'US', Value: [512] },
'00280030': { vr: 'DS', Value: [2.0, 2.0] },
'00280100': { vr: 'US', Value: [16] },
'00280101': { vr: 'US', Value: [16] },
'00280102': { vr: 'US', Value: [15] },
'00280103': { vr: 'US', Value: [1] },
'00281050': { vr: 'DS', Value: [110.0] },
'00281051': { vr: 'DS', Value: [320.0] },
'00281052': { vr: 'DS', Value: [0.0] },
'00281053': { vr: 'DS', Value: [1.0] },
'00321033': { vr: 'LO', Value: ['OUTDFRAD'] },
'00400002': { vr: 'DA', Value: ['20141125'] },
'00400003': { vr: 'TM', Value: ['091000'] },
'00400004': { vr: 'DA', Value: ['20141125'] },
'00400005': { vr: 'TM', Value: ['094000.000'] },
'00400244': { vr: 'DA', Value: ['20141125'] },
'00400245': { vr: 'TM', Value: ['094528.000'] },
'00400253': { vr: 'SH', Value: ['3708'] },
'00400260': {
vr: 'SQ',
Value: [
{
'00080100': { vr: 'SH', Value: ['6035'] },
'00080102': { vr: 'SH', Value: ['CCG_CSTemp'] },
'00080104': { vr: 'LO', Value: ['6035/DFCT2 CT 3-SITES W/OC'] },
},
],
},
'00402017': { vr: 'LO', Value: ['14159097'] },
'7FE00010': {
vr: 'OW',
BulkDataURI:
'http://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.1/series/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.2/instances/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.6',
},
},
{
'00080005': { vr: 'CS', Value: ['ISO_IR 100'] },
'00080008': { vr: 'CS', Value: ['ORIGINAL', 'PRIMARY', 'LOCALIZER'] },
'00080016': { vr: 'UI', Value: ['1.2.840.10008.5.1.4.1.1.2'] },
'00080018': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.3'],
},
'00080020': { vr: 'DA', Value: ['20141125'] },
'00080021': { vr: 'DA', Value: ['20141125'] },
'00080022': { vr: 'DA', Value: ['20141125'] },
'00080023': { vr: 'DA', Value: ['20141125'] },
'00080030': { vr: 'TM', Value: ['094528.000'] },
'00080031': { vr: 'TM', Value: ['094604.688'] },
'00080032': { vr: 'TM', Value: ['094557.250'] },
'00080033': { vr: 'TM', Value: ['094557.250'] },
'00080050': { vr: 'SH', Value: ['000092218'] },
'00080060': { vr: 'CS', Value: ['CT'] },
'00080070': { vr: 'LO', Value: ['TOSHIBA'] },
'00080080': { vr: 'LO', Value: ['Precision Imaging Metrics'] },
'00080090': { vr: 'PN' },
'00081010': { vr: 'SH' },
'00081030': { vr: 'LO', Value: ['DFCI CT CHEST W CONTRAST 6023'] },
'00081032': {
vr: 'SQ',
Value: [
{
'00080100': { vr: 'SH', Value: ['6023'] },
'00080102': { vr: 'SH', Value: ['GEIIS'] },
'00080103': { vr: 'SH', Value: ['0'] },
'00080104': { vr: 'LO', Value: ['DFCI CT CHEST W CONTRAST 6023'] },
},
],
},
'0008103E': { vr: 'LO', Value: ['2.0'] },
'00081040': { vr: 'LO' },
'00081070': { vr: 'PN' },
'00081090': { vr: 'LO', Value: ['Aquilion'] },
'00081110': {
vr: 'SQ',
Value: [
{
'00081150': { vr: 'UI', Value: ['1.2.840.100008.3.1.2.3.1'] },
'00081155': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.4'],
},
},
],
},
'00100010': { vr: 'PN', Value: [{ Alphabetic: 'Venus' }] },
'00100020': { vr: 'LO', Value: ['0000005'] },
'00100021': { vr: 'LO', Value: ['001R74:20050625:205502036:195212'] },
'00100030': { vr: 'DA' },
'00100040': { vr: 'CS', Value: ['F'] },
'00101000': { vr: 'LO' },
'00101010': { vr: 'AS' },
'00101020': { vr: 'DS' },
'00101030': { vr: 'DS' },
'00104000': { vr: 'LT' },
'00180015': { vr: 'CS', Value: ['CHEST_TO_PELVIS'] },
'00180022': { vr: 'CS', Value: ['SCANOSCOPE'] },
'00180050': { vr: 'DS', Value: [2.0] },
'00180060': { vr: 'DS', Value: [120.0] },
'00180090': { vr: 'DS', Value: [1000.0] },
'00181000': { vr: 'LO' },
'00181020': { vr: 'LO', Value: ['V4.86ER003'] },
'00181030': { vr: 'LO', Value: ['Chest / Abdomen/Pelvis 5mm'] },
'00181100': { vr: 'DS', Value: [1000.0] },
'00181120': { vr: 'DS', Value: [0.0] },
'00181130': { vr: 'DS', Value: [102.0] },
'00181140': { vr: 'CS', Value: ['CW'] },
'00181150': { vr: 'IS', Value: [6857] },
'00181151': { vr: 'IS', Value: [50] },
'00181152': { vr: 'IS', Value: [300] },
'00181160': { vr: 'SH', Value: ['LARGE'] },
'00181170': { vr: 'IS', Value: [6] },
'00181190': { vr: 'DS', Value: [1.6, 1.4] },
'00181210': { vr: 'SH', Value: ['FL03'] },
'00185100': { vr: 'CS', Value: ['FFS'] },
'0020000D': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.1'],
},
'0020000E': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.2'],
},
'00200010': { vr: 'SH' },
'00200011': { vr: 'IS', Value: [1] },
'00200012': { vr: 'IS', Value: [1] },
'00200013': { vr: 'IS', Value: [1] },
'00200020': { vr: 'CS', Value: ['L', 'F'] },
'00200032': { vr: 'DS', Value: [-512.0, 1.7e-4, 1925.0] },
'00200037': { vr: 'DS', Value: [1.0, 0.0, 0.0, 0.0, 0.0, -1.0] },
'00200052': {
vr: 'UI',
Value: ['1.3.6.1.4.1.25403.345050719074.3824.20170126082902.5'],
},
'00201040': { vr: 'LO' },
'00201041': { vr: 'DS', Value: [342.0] },
'00280002': { vr: 'US', Value: [1] },
'00280004': { vr: 'CS', Value: ['MONOCHROME2'] },
'00280010': { vr: 'US', Value: [512] },
'00280011': { vr: 'US', Value: [512] },
'00280030': { vr: 'DS', Value: [2.0, 2.0] },
'00280100': { vr: 'US', Value: [16] },
'00280101': { vr: 'US', Value: [16] },
'00280102': { vr: 'US', Value: [15] },
'00280103': { vr: 'US', Value: [1] },
'00281050': { vr: 'DS', Value: [100.0] },
'00281051': { vr: 'DS', Value: [230.0] },
'00281052': { vr: 'DS', Value: [0.0] },
'00281053': { vr: 'DS', Value: [1.0] },
'00321033': { vr: 'LO', Value: ['OUTDFRAD'] },
'00400002': { vr: 'DA', Value: ['20141125'] },
'00400003': { vr: 'TM', Value: ['091000'] },
'00400004': { vr: 'DA', Value: ['20141125'] },
'00400005': { vr: 'TM', Value: ['094000.000'] },
'00400244': { vr: 'DA', Value: ['20141125'] },
'00400245': { vr: 'TM', Value: ['094528.000'] },
'00400253': { vr: 'SH', Value: ['3708'] },
'00400260': {
vr: 'SQ',
Value: [
{
'00080100': { vr: 'SH', Value: ['6035'] },
'00080102': { vr: 'SH', Value: ['CCG_CSTemp'] },
'00080104': { vr: 'LO', Value: ['6035/DFCT2 CT 3-SITES W/OC'] },
},
],
},
'00402017': { vr: 'LO', Value: ['14159097'] },
'7FE00010': {
vr: 'OW',
BulkDataURI:
'http://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs/studies/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.1/series/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.2/instances/1.3.6.1.4.1.25403.345050719074.3824.20170126082902.3',
},
},
];

View File

@@ -0,0 +1,585 @@
import { api } from 'dicomweb-client';
import { DicomMetadataStore, IWebApiDataSource, utils, errorHandler, classes } from '@ohif/core';
import {
mapParams,
search as qidoSearch,
seriesInStudy,
processResults,
processSeriesResults,
} from './qido.js';
import dcm4cheeReject from './dcm4cheeReject.js';
import getImageId from './utils/getImageId.js';
import dcmjs from 'dcmjs';
import { retrieveStudyMetadata, deleteStudyMetadataPromise } from './retrieveStudyMetadata.js';
import StaticWadoClient from './utils/StaticWadoClient';
import getDirectURL from '../utils/getDirectURL';
import { fixBulkDataURI } from './utils/fixBulkDataURI';
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
const { naturalizeDataset, denaturalizeDataset } = DicomMetaDictionary;
const ImplementationClassUID = '2.25.270695996825855179949881587723571202391.2.0.0';
const ImplementationVersionName = 'OHIF-VIEWER-2.0.0';
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 {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;
let dicomWebConfigCopy,
qidoConfig,
wadoConfig,
qidoDicomWebClient,
wadoDicomWebClient,
getAuthrorizationHeader,
generateWadoHeader;
const implementation = {
initialize: ({ params, query }) => {
if (dicomWebConfig.onConfiguration && typeof dicomWebConfig.onConfiguration === 'function') {
dicomWebConfig = dicomWebConfig.onConfiguration(dicomWebConfig, {
params,
query,
});
}
dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig));
getAuthrorizationHeader = () => {
const xhrRequestHeaders = {};
const authHeaders = userAuthenticationService.getAuthorizationHeader();
if (authHeaders && authHeaders.Authorization) {
xhrRequestHeaders.Authorization = authHeaders.Authorization;
}
return xhrRequestHeaders;
};
generateWadoHeader = () => {
const authorizationHeader = getAuthrorizationHeader();
//Generate accept header depending on config params
const formattedAcceptHeader = utils.generateAcceptHeader(
dicomWebConfig.acceptHeader,
dicomWebConfig.requestTransferSyntaxUID,
dicomWebConfig.omitQuotationForMultipartRequest
);
return {
...authorizationHeader,
Accept: formattedAcceptHeader,
};
};
qidoConfig = {
url: dicomWebConfig.qidoRoot,
staticWado: dicomWebConfig.staticWado,
singlepart: dicomWebConfig.singlepart,
headers: userAuthenticationService.getAuthorizationHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
};
wadoConfig = {
url: dicomWebConfig.wadoRoot,
staticWado: dicomWebConfig.staticWado,
singlepart: dicomWebConfig.singlepart,
headers: userAuthenticationService.getAuthorizationHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
};
// TODO -> Two clients sucks, but its better than 1000.
// TODO -> We'll need to merge auth later.
qidoDicomWebClient = dicomWebConfig.staticWado
? new StaticWadoClient(qidoConfig)
: new api.DICOMwebClient(qidoConfig);
wadoDicomWebClient = dicomWebConfig.staticWado
? new StaticWadoClient(wadoConfig)
: new api.DICOMwebClient(wadoConfig);
},
query: {
studies: {
mapParams: mapParams.bind(),
search: async function (origParams) {
qidoDicomWebClient.headers = getAuthrorizationHeader();
const { studyInstanceUid, seriesInstanceUid, ...mappedParams } =
mapParams(origParams, {
supportsFuzzyMatching: dicomWebConfig.supportsFuzzyMatching,
supportsWildcard: dicomWebConfig.supportsWildcard,
}) || {};
const results = await qidoSearch(qidoDicomWebClient, undefined, undefined, mappedParams);
return processResults(results);
},
processResults: processResults.bind(),
},
series: {
// mapParams: mapParams.bind(),
search: async function (studyInstanceUid) {
qidoDicomWebClient.headers = getAuthrorizationHeader();
const results = await seriesInStudy(qidoDicomWebClient, studyInstanceUid);
return processSeriesResults(results);
},
// processResults: processResults.bind(),
},
instances: {
search: (studyInstanceUid, queryParameters) => {
qidoDicomWebClient.headers = getAuthrorizationHeader();
return qidoSearch.call(
undefined,
qidoDicomWebClient,
studyInstanceUid,
null,
queryParameters
);
},
},
},
retrieve: {
/**
* Generates a URL that can be used for direct retrieve of the bulkdata
*
* @param {object} params
* @param {string} params.tag is the tag name of the URL to retrieve
* @param {object} params.instance is the instance object that the tag is in
* @param {string} params.defaultType is the mime type of the response
* @param {string} params.singlepart is the type of the part to retrieve
* @returns an absolute URL to the resource, if the absolute URL can be retrieved as singlepart,
* or is already retrieved, or a promise to a URL for such use if a BulkDataURI
*/
directURL: params => {
return getDirectURL(
{
wadoRoot: dicomWebConfig.wadoRoot,
singlepart: dicomWebConfig.singlepart,
},
params
);
},
/**
* Provide direct access to the dicom web client for certain use cases
* where the dicom web client is used by an external library such as the
* microscopy viewer.
* Note this instance only needs to support the wado queries, and may not
* support any QIDO or STOW operations.
*/
getWadoDicomWebClient: () => wadoDicomWebClient,
bulkDataURI: async ({ StudyInstanceUID, BulkDataURI }) => {
qidoDicomWebClient.headers = getAuthrorizationHeader();
const options = {
multipart: false,
BulkDataURI,
StudyInstanceUID,
};
return qidoDicomWebClient.retrieveBulkData(options).then(val => {
const ret = (val && val[0]) || undefined;
return ret;
});
},
series: {
metadata: async ({
StudyInstanceUID,
filters,
sortCriteria,
sortFunction,
madeInClient = false,
returnPromises = false,
} = {}) => {
if (!StudyInstanceUID) {
throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID');
}
if (dicomWebConfig.enableStudyLazyLoad) {
return implementation._retrieveSeriesMetadataAsync(
StudyInstanceUID,
filters,
sortCriteria,
sortFunction,
madeInClient,
returnPromises
);
}
return implementation._retrieveSeriesMetadataSync(
StudyInstanceUID,
filters,
sortCriteria,
sortFunction,
madeInClient
);
},
},
},
store: {
dicom: async (dataset, request, dicomDict) => {
wadoDicomWebClient.headers = getAuthrorizationHeader();
if (dataset instanceof ArrayBuffer) {
const options = {
datasets: [dataset],
request,
};
await wadoDicomWebClient.storeInstances(options);
} else {
let effectiveDicomDict = dicomDict;
if (!dicomDict) {
const meta = {
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
MediaStorageSOPClassUID: dataset.SOPClassUID,
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
ImplementationClassUID,
ImplementationVersionName,
};
const denaturalized = denaturalizeDataset(meta);
const defaultDicomDict = new DicomDict(denaturalized);
defaultDicomDict.dict = denaturalizeDataset(dataset);
effectiveDicomDict = defaultDicomDict;
}
const part10Buffer = effectiveDicomDict.write();
const options = {
datasets: [part10Buffer],
request,
};
await wadoDicomWebClient.storeInstances(options);
}
},
},
_retrieveSeriesMetadataSync: async (
StudyInstanceUID,
filters,
sortCriteria,
sortFunction,
madeInClient
) => {
const enableStudyLazyLoad = false;
wadoDicomWebClient.headers = generateWadoHeader();
// data is all SOPInstanceUIDs
const data = await retrieveStudyMetadata(
wadoDicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction,
dicomWebConfig
);
// first naturalize the data
const naturalizedInstancesMetadata = data.map(naturalizeDataset);
const seriesSummaryMetadata = {};
const instancesPerSeries = {};
naturalizedInstancesMetadata.forEach(instance => {
if (!seriesSummaryMetadata[instance.SeriesInstanceUID]) {
seriesSummaryMetadata[instance.SeriesInstanceUID] = {
StudyInstanceUID: instance.StudyInstanceUID,
StudyDescription: instance.StudyDescription,
SeriesInstanceUID: instance.SeriesInstanceUID,
SeriesDescription: instance.SeriesDescription,
SeriesNumber: instance.SeriesNumber,
SeriesTime: instance.SeriesTime,
SOPClassUID: instance.SOPClassUID,
ProtocolName: instance.ProtocolName,
Modality: instance.Modality,
};
}
if (!instancesPerSeries[instance.SeriesInstanceUID]) {
instancesPerSeries[instance.SeriesInstanceUID] = [];
}
const imageId = implementation.getImageIdsForInstance({
instance,
});
instance.imageId = imageId;
instance.wadoRoot = dicomWebConfig.wadoRoot;
instance.wadoUri = dicomWebConfig.wadoUri;
metadataProvider.addImageIdToUIDs(imageId, {
StudyInstanceUID,
SeriesInstanceUID: instance.SeriesInstanceUID,
SOPInstanceUID: instance.SOPInstanceUID,
});
instancesPerSeries[instance.SeriesInstanceUID].push(instance);
});
// grab all the series metadata
const seriesMetadata = Object.values(seriesSummaryMetadata);
DicomMetadataStore.addSeriesMetadata(seriesMetadata, madeInClient);
Object.keys(instancesPerSeries).forEach(seriesInstanceUID =>
DicomMetadataStore.addInstances(instancesPerSeries[seriesInstanceUID], madeInClient)
);
return seriesSummaryMetadata;
},
_retrieveSeriesMetadataAsync: async (
StudyInstanceUID,
filters,
sortCriteria,
sortFunction,
madeInClient = false,
returnPromises = false
) => {
const enableStudyLazyLoad = true;
wadoDicomWebClient.headers = generateWadoHeader();
// Get Series
const { preLoadData: seriesSummaryMetadata, promises: seriesPromises } =
await retrieveStudyMetadata(
wadoDicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction,
dicomWebConfig
);
/**
* Adds the retrieve bulkdata function to naturalized DICOM data.
* This is done recursively, for sub-sequences.
*/
const addRetrieveBulkDataNaturalized = (naturalized, instance = naturalized) => {
for (const key of Object.keys(naturalized)) {
const value = naturalized[key];
if (Array.isArray(value) && typeof value[0] === 'object') {
// Fix recursive values
value.forEach(child => addRetrieveBulkDataNaturalized(child, instance));
continue;
}
// 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) {
// handle the scenarios where bulkDataURI is relative path
fixBulkDataURI(value, instance, dicomWebConfig);
// Provide a method to fetch bulkdata
value.retrieveBulkData = retrieveBulkData.bind(qidoDicomWebClient, value);
}
}
return naturalized;
};
/**
* 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);
// if we know the server doesn't use bulkDataURI, then don't
if (!dicomWebConfig.bulkDataURI?.enabled) {
return naturalized;
}
return addRetrieveBulkDataNaturalized(naturalized);
};
// Async load series, store as retrieved
function storeInstances(instances) {
const naturalizedInstances = instances.map(addRetrieveBulkData);
// Adding instanceMetadata to OHIF MetadataProvider
naturalizedInstances.forEach(instance => {
instance.wadoRoot = dicomWebConfig.wadoRoot;
instance.wadoUri = dicomWebConfig.wadoUri;
const imageId = implementation.getImageIdsForInstance({
instance,
});
// Adding imageId to each instance
// Todo: This is not the best way I can think of to let external
// metadata handlers know about the imageId that is stored in the store
instance.imageId = imageId;
// Adding UIDs to metadataProvider
// Note: storing imageURI in metadataProvider since stack viewports
// will use the same imageURI
metadataProvider.addImageIdToUIDs(imageId, {
StudyInstanceUID,
SeriesInstanceUID: instance.SeriesInstanceUID,
SOPInstanceUID: instance.SOPInstanceUID,
});
});
DicomMetadataStore.addInstances(naturalizedInstances, madeInClient);
}
function setSuccessFlag() {
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
if (!study) {
return;
}
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 seriesDeliveredPromises = seriesPromises.map(promise => {
if (!returnPromises) {
promise?.start();
}
return promise.then(instances => {
storeInstances(instances);
});
});
if (returnPromises) {
Promise.all(seriesDeliveredPromises).then(() => setSuccessFlag());
return seriesPromises;
} else {
await Promise.all(seriesDeliveredPromises);
setSuccessFlag();
}
return seriesSummaryMetadata;
},
deleteStudyMetadataPromise,
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 frame = 1; frame <= NumberOfFrames; frame++) {
const imageId = this.getImageIdsForInstance({
instance,
frame,
});
imageIds.push(imageId);
}
} else {
const imageId = this.getImageIdsForInstance({ instance });
imageIds.push(imageId);
}
});
return imageIds;
},
getImageIdsForInstance({ instance, frame = undefined }) {
const imageIds = getImageId({
instance,
frame,
config: dicomWebConfig,
});
return imageIds;
},
getConfig() {
return dicomWebConfigCopy;
},
getStudyInstanceUIDs({ params, query }) {
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params;
const queryStudyInstanceUIDs = utils.splitComma(query.getAll('StudyInstanceUIDs'));
const StudyInstanceUIDs =
(queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || paramsStudyInstanceUIDs;
const StudyInstanceUIDsAsArray =
StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs)
? StudyInstanceUIDs
: [StudyInstanceUIDs];
return StudyInstanceUIDsAsArray;
},
};
if (dicomWebConfig.supportsReject) {
implementation.reject = dcm4cheeReject(dicomWebConfig.wadoRoot);
}
return IWebApiDataSource.create(implementation);
}
/**
* A bindable function that retrieves the bulk data against this as the
* dicomweb client, and on the given value element.
*
* @param value - a bind value that stores the retrieve value to short circuit the
* next retrieve instance.
* @param options - to allow specifying the content type.
*/
function retrieveBulkData(value, options = {}) {
const { mediaType } = options;
const useOptions = {
// 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,
mediaTypes: mediaType ? [{ mediaType }, { mediaType: 'application/octet-stream' }] : undefined,
...options,
};
return this.retrieveBulkData(useOptions).then(val => {
// There are DICOM PDF cases where the first ArrayBuffer in the array is
// the bulk data and DICOM video cases where the second ArrayBuffer is
// the bulk data. Here we play it safe and do a find.
const ret =
(val instanceof Array && val.find(arrayBuffer => arrayBuffer?.byteLength)) || undefined;
value.Value = ret;
return ret;
});
}
export { createDicomWebApi };

View File

@@ -0,0 +1,215 @@
/**
* QIDO - Query based on ID for DICOM Objects
* search for studies, series and instances by patient ID, and receive their
* unique identifiers for further usage.
*
* Quick: https://www.dicomstandard.org/dicomweb/query-qido-rs/
* Standard: http://dicom.nema.org/medical/dicom/current/output/html/part18.html#sect_10.6
*
* Routes:
* ==========
* /studies?
* /studies/{studyInstanceUid}/series?
* /studies/{studyInstanceUid}/series/{seriesInstanceUid}/instances?
*
* Query Parameters:
* ================
* | KEY | VALUE |
* |------------------|--------------------|
* | {attributeId} | {value} |
* | includeField | {attribute} or all |
* | fuzzymatching | true OR false |
* | limit | {number} |
* | offset | {number} |
*/
import { DICOMWeb, utils } from '@ohif/core';
import { sortStudySeries } from '@ohif/core/src/utils/sortStudy';
const { getString, getName, getModalities } = DICOMWeb;
/**
* Parses resulting data from a QIDO call into a set of Study MetaData
*
* @param {Array} qidoStudies - An array of study objects. Each object contains a keys for DICOM tags.
* @param {object} qidoStudies[0].qidoStudy - An object where each key is the DICOM Tag group+element
* @param {object} qidoStudies[0].qidoStudy[dicomTag] - Optional object that represents DICOM Tag
* @param {string} qidoStudies[0].qidoStudy[dicomTag].vr - Value Representation
* @param {string[]} qidoStudies[0].qidoStudy[dicomTag].Value - Optional string array representation of the DICOM Tag's value
* @returns {Array} An array of Study MetaData objects
*/
function processResults(qidoStudies) {
if (!qidoStudies || !qidoStudies.length) {
return [];
}
const studies = [];
qidoStudies.forEach(qidoStudy =>
studies.push({
studyInstanceUid: getString(qidoStudy['0020000D']),
date: getString(qidoStudy['00080020']), // YYYYMMDD
time: getString(qidoStudy['00080030']), // HHmmss.SSS (24-hour, minutes, seconds, fractional seconds)
accession: getString(qidoStudy['00080050']) || '', // short string, probably a number?
mrn: getString(qidoStudy['00100020']) || '', // medicalRecordNumber
patientName: utils.formatPN(getName(qidoStudy['00100010'])) || '',
instances: Number(getString(qidoStudy['00201208'])) || 0, // number
description: getString(qidoStudy['00081030']) || '',
modalities: getString(getModalities(qidoStudy['00080060'], qidoStudy['00080061'])) || '',
})
);
return studies;
}
/**
* Parses resulting data from a QIDO call into a set of Study MetaData
*
* @param {Array} qidoSeries - An array of study objects. Each object contains a keys for DICOM tags.
* @param {object} qidoSeries[0].qidoSeries - An object where each key is the DICOM Tag group+element
* @param {object} qidoSeries[0].qidoSeries[dicomTag] - Optional object that represents DICOM Tag
* @param {string} qidoSeries[0].qidoSeries[dicomTag].vr - Value Representation
* @param {string[]} qidoSeries[0].qidoSeries[dicomTag].Value - Optional string array representation of the DICOM Tag's value
* @returns {Array} An array of Study MetaData objects
*/
export function processSeriesResults(qidoSeries) {
const series = [];
if (qidoSeries && qidoSeries.length) {
qidoSeries.forEach(qidoSeries =>
series.push({
studyInstanceUid: getString(qidoSeries['0020000D']),
seriesInstanceUid: getString(qidoSeries['0020000E']),
modality: getString(qidoSeries['00080060']),
seriesNumber: getString(qidoSeries['00200011']),
seriesDate: utils.formatDate(getString(qidoSeries['00080021'])),
numSeriesInstances: Number(getString(qidoSeries['00201209'])),
description: getString(qidoSeries['0008103E']),
})
);
}
sortStudySeries(series);
return series;
}
/**
*
* @param {object} dicomWebClient - Client similar to what's provided by `dicomweb-client` library
* @param {function} dicomWebClient.searchForStudies -
* @param {string} [studyInstanceUid]
* @param {string} [seriesInstanceUid]
* @param {string} [queryParamaters]
* @returns {Promise<results>} - Promise that resolves results
*/
async function search(dicomWebClient, studyInstanceUid, seriesInstanceUid, queryParameters) {
let searchResult = await dicomWebClient.searchForStudies({
studyInstanceUid: undefined,
queryParams: queryParameters,
});
return searchResult;
}
/**
*
* @param {string} studyInstanceUID - ID of study to return a list of series for
* @returns {Promise} - Resolves SeriesMetadata[] in study
*/
export function seriesInStudy(dicomWebClient, studyInstanceUID) {
// Series Description
// Already included?
const commaSeparatedFields = ['0008103E', '00080021'].join(',');
const queryParams = {
includefield: commaSeparatedFields,
};
return dicomWebClient.searchForSeries({ studyInstanceUID, queryParams });
}
export default function searchStudies(server, filter) {
const queryParams = getQIDOQueryParams(filter, server.qidoSupportsIncludeField);
const options = {
queryParams,
};
return dicomWeb.searchForStudies(options).then(resultDataToStudies);
}
/**
* Produces a QIDO URL given server details and a set of specified search filter
* items
*
* @param filter
* @param serverSupportsQIDOIncludeField
* @returns {string} The URL with encoded filter query data
*/
function mapParams(params, options = {}) {
if (!params) {
return;
}
const commaSeparatedFields = [
'00081030', // Study Description
'00080060', // Modality
// Add more fields here if you want them in the result
].join(',');
const useWildcard =
params?.disableWildcard !== undefined ? !params.disableWildcard : options.supportsWildcard;
const withWildcard = value => {
return useWildcard && value ? `*${value}*` : value;
};
const parameters = {
// Named
PatientName: withWildcard(params.patientName),
//PatientID: withWildcard(params.patientId),
'00100020': withWildcard(params.patientId), // Temporarily to make the tests pass with dicomweb-server.. Apparently it's broken?
AccessionNumber: withWildcard(params.accessionNumber),
StudyDescription: withWildcard(params.studyDescription),
ModalitiesInStudy: params.modalitiesInStudy,
// Other
limit: params.limit || 101,
offset: params.offset || 0,
fuzzymatching: options.supportsFuzzyMatching === true,
includefield: commaSeparatedFields, // serverSupportsQIDOIncludeField ? commaSeparatedFields : 'all',
};
// build the StudyDate range parameter
if (params.startDate && params.endDate) {
parameters.StudyDate = `${params.startDate}-${params.endDate}`;
} else if (params.startDate) {
const today = new Date();
const DD = String(today.getDate()).padStart(2, '0');
const MM = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
const YYYY = today.getFullYear();
const todayStr = `${YYYY}${MM}${DD}`;
parameters.StudyDate = `${params.startDate}-${todayStr}`;
} else if (params.endDate) {
const oldDateStr = `19700102`;
parameters.StudyDate = `${oldDateStr}-${params.endDate}`;
}
// Build the StudyInstanceUID parameter
if (params.studyInstanceUid) {
let studyUids = params.studyInstanceUid;
studyUids = Array.isArray(studyUids) ? studyUids.join() : studyUids;
studyUids = studyUids.replace(/[^0-9.]+/g, '\\');
parameters.StudyInstanceUID = studyUids;
}
// Clean query params of undefined values.
const final = {};
Object.keys(parameters).forEach(key => {
if (parameters[key] !== undefined && parameters[key] !== '') {
final[key] = parameters[key];
}
});
return final;
}
export { mapParams, search, processResults };

View File

@@ -0,0 +1,91 @@
import retrieveMetadataFiltered from './utils/retrieveMetadataFiltered.js';
import RetrieveMetadata from './wado/retrieveMetadata.js';
const moduleName = 'RetrieveStudyMetadata';
// Cache for promises. Prevents unnecessary subsequent calls to the server
const StudyMetaDataPromises = new Map();
/**
* Retrieves study metadata.
*
* @param {Object} dicomWebClient The DICOMWebClient instance to be used for series load
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
* @param {boolean} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously.
* @param {Object} [filters] Object containing filters to be applied on retrieve metadata process
* @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against
* @param {function} [sortCriteria] Sort criteria function
* @param {function} [sortFunction] Sort function
*
* @returns {Promise} that will be resolved with the metadata or rejected with the error
*/
export function retrieveStudyMetadata(
dicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction,
dicomWebConfig = {}
) {
// @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all
// and further requests for that metadata will always fail. On failure, we probably need to remove the
// corresponding promise from the "StudyMetaDataPromises" map...
if (!dicomWebClient) {
throw new Error(`${moduleName}: Required 'dicomWebClient' parameter not provided.`);
}
if (!StudyInstanceUID) {
throw new Error(`${moduleName}: Required 'StudyInstanceUID' parameter not provided.`);
}
const promiseId = `${dicomWebConfig.name}:${StudyInstanceUID}`;
// Already waiting on result? Return cached promise
if (StudyMetaDataPromises.has(promiseId)) {
return StudyMetaDataPromises.get(promiseId);
}
let promise;
if (filters && filters.seriesInstanceUID && Array.isArray(filters.seriesInstanceUID)) {
promise = retrieveMetadataFiltered(
dicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction
);
} else {
// Create a promise to handle the data retrieval
promise = new Promise((resolve, reject) => {
RetrieveMetadata(
dicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction
).then(function (data) {
resolve(data);
}, reject);
});
}
// Store the promise in cache
StudyMetaDataPromises.set(promiseId, promise);
return promise;
}
/**
* Delete the cached study metadata retrieval promise to ensure that the browser will
* re-retrieve the study metadata when it is next requested.
*
* @param {String} StudyInstanceUID The UID of the Study to be removed from cache
*/
export function deleteStudyMetadataPromise(StudyInstanceUID) {
if (StudyMetaDataPromises.has(StudyInstanceUID)) {
StudyMetaDataPromises.delete(StudyInstanceUID);
}
}

View File

@@ -0,0 +1,241 @@
import { api } from 'dicomweb-client';
import fixMultipart from './fixMultipart';
const { DICOMwebClient } = api;
const anyDicomwebClient = DICOMwebClient as any;
// Ugly over-ride, but the internals aren't otherwise accessible.
if (!anyDicomwebClient._orig_buildMultipartAcceptHeaderFieldValue) {
anyDicomwebClient._orig_buildMultipartAcceptHeaderFieldValue =
anyDicomwebClient._buildMultipartAcceptHeaderFieldValue;
anyDicomwebClient._buildMultipartAcceptHeaderFieldValue = function (mediaTypes, acceptableTypes) {
if (mediaTypes.length === 1 && mediaTypes[0].mediaType.endsWith('/*')) {
return '*/*';
} else {
return anyDicomwebClient._orig_buildMultipartAcceptHeaderFieldValue(
mediaTypes,
acceptableTypes
);
}
};
}
/**
* An implementation of the static wado client, that fetches data from
* a static response rather than actually doing real queries. This allows
* fast encoding of test data, but because it is static, anything actually
* 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',
patientname: '00100010',
'00100020': 'mrn',
studydescription: '00081030',
studydate: '00080020',
modalitiesinstudy: '00080061',
accessionnumber: '00080050',
};
static seriesFilterKeys = {
seriesinstanceuid: '0020000E',
seriesnumber: '00200011',
modality: '00080060',
};
protected config;
protected staticWado;
constructor(config) {
super(config);
this.staticWado = config.staticWado;
this.config = config;
}
/**
* Handle improperly specified multipart/related return type.
* Note if the response is SUPPOSED to be multipart encoded already, then this
* will double-decode it.
*
* @param options
* @returns De-multiparted response data.
*
*/
public retrieveBulkData(options): Promise<any[]> {
const shouldFixMultipart = this.config.fixBulkdataMultipart !== false;
const useOptions = {
...options,
};
if (this.staticWado) {
useOptions.mediaTypes = [{ mediaType: 'application/*' }];
}
return super
.retrieveBulkData(useOptions)
.then(result => (shouldFixMultipart ? fixMultipart(result) : result));
}
/**
* Retrieves instance frames using the image/* media type when configured
* to do so (static wado back end).
*/
public retrieveInstanceFrames(options) {
if (this.staticWado) {
return super.retrieveInstanceFrames({
...options,
mediaTypes: [{ mediaType: 'image/*' }],
});
} else {
return super.retrieveInstanceFrames(options);
}
}
/**
* Replace the search for studies remote query with a local version which
* retrieves a complete query list and then sub-selects from it locally.
* @param {*} options
* @returns
*/
async searchForStudies(options) {
if (!this.staticWado) {
return super.searchForStudies(options);
}
const searchResult = await super.searchForStudies(options);
const { queryParams } = options;
if (!queryParams) {
return searchResult;
}
const lowerParams = this.toLowerParams(queryParams);
const filtered = searchResult.filter(study => {
for (const key of Object.keys(StaticWadoClient.studyFilterKeys)) {
if (!this.filterItem(key, lowerParams, study, StaticWadoClient.studyFilterKeys)) {
return false;
}
}
return true;
});
return filtered;
}
async searchForSeries(options) {
if (!this.staticWado) {
return super.searchForSeries(options);
}
const searchResult = await super.searchForSeries(options);
const { queryParams } = options;
if (!queryParams) {
return searchResult;
}
const lowerParams = this.toLowerParams(queryParams);
const filtered = searchResult.filter(series => {
for (const key of Object.keys(StaticWadoClient.seriesFilterKeys)) {
if (!this.filterItem(key, lowerParams, series, StaticWadoClient.seriesFilterKeys)) {
return false;
}
}
return true;
});
return filtered;
}
/**
* Compares values, matching any instance of desired to any instance of
* actual by recursively go through the paired set of values. That is,
* this is O(m*n) where m is how many items in desired and n is the length of actual
* Then, at the individual item node, compares the Alphabetic name if present,
* and does a sub-string matching on string values, and otherwise does an
* exact match comparison.
*
* @param {*} desired
* @param {*} actual
* @returns true if the values match
*/
compareValues(desired, actual) {
if (Array.isArray(desired)) {
return desired.find(item => this.compareValues(item, actual));
}
if (Array.isArray(actual)) {
return actual.find(actualItem => this.compareValues(desired, actualItem));
}
if (actual?.Alphabetic) {
actual = actual.Alphabetic;
}
if (typeof actual == 'string') {
if (actual.length === 0) {
return true;
}
if (desired.length === 0 || desired === '*') {
return true;
}
if (desired[0] === '*' && desired[desired.length - 1] === '*') {
// console.log(`Comparing ${actual} to ${desired.substring(1, desired.length - 1)}`)
return actual.indexOf(desired.substring(1, desired.length - 1)) != -1;
} else if (desired[desired.length - 1] === '*') {
return actual.indexOf(desired.substring(0, desired.length - 1)) != -1;
} else if (desired[0] === '*') {
return actual.indexOf(desired.substring(1)) === actual.length - desired.length + 1;
}
}
return desired === actual;
}
/** Compares a pair of dates to see if the value is within the range */
compareDateRange(range, value) {
if (!value) {
return true;
}
const dash = range.indexOf('-');
if (dash === -1) {
return this.compareValues(range, value);
}
const start = range.substring(0, dash);
const end = range.substring(dash + 1);
return (!start || value >= start) && (!end || value <= end);
}
/**
* Filters the return list by the query parameters.
*
* @param anyCaseKey - a possible search key
* @param queryParams -
* @param {*} study
* @param {*} sourceFilterMap
* @returns
*/
filterItem(key: string, queryParams, study, sourceFilterMap) {
const altKey = sourceFilterMap[key] || key;
if (!queryParams) {
return true;
}
const testValue = queryParams[key] || queryParams[altKey];
if (!testValue) {
return true;
}
const valueElem = study[key] || study[altKey];
if (!valueElem) {
return false;
}
if (valueElem.vr === 'DA' && valueElem.Value?.[0]) {
return this.compareDateRange(testValue, valueElem.Value[0]);
}
const value = valueElem.Value;
return this.compareValues(testValue, value);
}
/** Converts the query parameters to lower case query parameters */
toLowerParams(queryParams: Record<string, unknown>): Record<string, unknown> {
const lowerParams = {};
Object.entries(queryParams).forEach(([key, value]) => {
lowerParams[key.toLowerCase()] = value;
});
return lowerParams;
}
}

View File

@@ -0,0 +1,78 @@
import { fixBulkDataURI } from './fixBulkDataURI';
function isPrimitive(v: any) {
return !(typeof v == 'object' || Array.isArray(v));
}
const vrNumerics = new Set([
'DS',
'FL',
'FD',
'IS',
'OD',
'OF',
'OL',
'OV',
'SL',
'SS',
'SV',
'UL',
'US',
'UV',
]);
/**
* Specialized for DICOM JSON format dataset cleaning.
* @param obj
* @returns
*/
export function cleanDenaturalizedDataset(
obj: any,
options?: {
StudyInstanceUID: string;
SeriesInstanceUID: string;
dataSourceConfig: unknown;
}
): any {
if (Array.isArray(obj)) {
const newAry = obj.map(o => (isPrimitive(o) ? o : cleanDenaturalizedDataset(o, options)));
return newAry;
}
if (isPrimitive(obj)) {
return obj;
}
Object.keys(obj).forEach(key => {
if (obj[key].Value === null && obj[key].vr) {
delete obj[key].Value;
} else if (Array.isArray(obj[key].Value) && obj[key].vr) {
if (obj[key].Value.length === 1 && obj[key].Value[0].BulkDataURI) {
if (options?.dataSourceConfig) {
// Not needed unless data source is directly used for loading data.
fixBulkDataURI(obj[key].Value[0], options, options.dataSourceConfig);
}
obj[key].BulkDataURI = obj[key].Value[0].BulkDataURI;
// prevent mixed-content blockage
if (window.location.protocol === 'https:' && obj[key].BulkDataURI.startsWith('http:')) {
obj[key].BulkDataURI = obj[key].BulkDataURI.replace('http:', 'https:');
}
delete obj[key].Value;
} else if (vrNumerics.has(obj[key].vr)) {
obj[key].Value = obj[key].Value.map(v => +v);
} else {
obj[key].Value = obj[key].Value.map(entry => cleanDenaturalizedDataset(entry, options));
}
}
});
return obj;
}
/**
* This is required to make the denaturalized data transferrable when it has
* added proxy values.
*/
export function transferDenaturalizedDataset(dataset) {
const noNull = cleanDenaturalizedDataset(dataset);
return JSON.parse(JSON.stringify(noNull));
}

View File

@@ -0,0 +1,47 @@
function checkToken(token, data, dataOffset): boolean {
if (dataOffset + token.length > data.length) {
return false;
}
let endIndex = dataOffset;
for (let i = 0; i < token.length; i++) {
if (token[i] !== data[endIndex++]) {
return false;
}
}
return true;
}
function stringToUint8Array(str: string): Uint8Array {
const uint = new Uint8Array(str.length);
for (let i = 0, j = str.length; i < j; i++) {
uint[i] = str.charCodeAt(i);
}
return uint;
}
function findIndexOfString(
data: Uint8Array,
str: string,
offset?: number
): number {
offset = offset || 0;
const token = stringToUint8Array(str);
for (let i = offset; i < data.length; i++) {
if (token[0] === data[i]) {
// console.log('match @', i);
if (checkToken(token, data, i)) {
return i;
}
}
}
return -1;
}
export default findIndexOfString;

View File

@@ -0,0 +1,71 @@
/**
* Modifies a bulkDataURI to ensure it is absolute based on the DICOMWeb configuration and
* instance data. The modification is in-place.
*
* If the bulkDataURI is relative to the series or study (according to the DICOM standard),
* it is made absolute by prepending the relevant paths.
*
* In scenarios where the bulkDataURI is a server-relative path (starting with '/'), the function
* handles two cases:
*
* 1. If the wado root is absolute (starts with 'http'), it prepends the wado root to the bulkDataURI.
* 2. If the wado root is relative, no changes are needed as the bulkDataURI is already correctly relative to the server root.
*
* @param value - The object containing BulkDataURI to be fixed.
* @param instance - The object (DICOM instance data) containing StudyInstanceUID and SeriesInstanceUID.
* @param dicomWebConfig - The DICOMWeb configuration object, containing wadoRoot and potentially bulkDataURI.relativeResolution.
* @returns The function modifies `value` in-place, it does not return a value.
*/
function fixBulkDataURI(value, instance, dicomWebConfig) {
// in case of the relative path, make it absolute. The current DICOM standard says
// the bulkdataURI is relative to the series. However, there are situations where
// it can be relative to the study too
let { BulkDataURI } = value;
const { bulkDataURI: uriConfig = {} } = dicomWebConfig;
BulkDataURI = uriConfig.transform?.(BulkDataURI) || BulkDataURI;
// Handle incorrectly prefixed origins
const { startsWith, prefixWith = '' } = uriConfig;
if (startsWith && BulkDataURI.startsWith(startsWith)) {
BulkDataURI = prefixWith + BulkDataURI.substring(startsWith.length);
value.BulkDataURI = BulkDataURI;
}
if (!BulkDataURI.startsWith('http') && !value.BulkDataURI.startsWith('/')) {
const { StudyInstanceUID, SeriesInstanceUID } = instance;
const isInstanceStart = BulkDataURI.startsWith('instances/') || BulkDataURI.startsWith('../');
if (
BulkDataURI.startsWith('series/') ||
BulkDataURI.startsWith('bulkdata/') ||
(uriConfig.relativeResolution === 'studies' && !isInstanceStart)
) {
value.BulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${StudyInstanceUID}/${BulkDataURI}`;
} else if (
isInstanceStart ||
uriConfig.relativeResolution === 'series' ||
!uriConfig.relativeResolution
) {
value.BulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/${BulkDataURI}`;
}
return;
}
// in case it is relative path but starts at the server (e.g., /bulk/1e, note the missing http
// in the beginning and the first character is /) There are two scenarios, whether the wado root
// is absolute or relative. In case of absolute, we need to prepend the wado root to the bulkdata
// uri (e.g., bulkData: /bulk/1e, wado root: http://myserver.com/dicomweb, output: http://myserver.com/bulk/1e)
// and in case of relative wado root, we need to prepend the bulkdata uri to the wado root (e.g,. bulkData: /bulk/1e
// wado root: /dicomweb, output: /bulk/1e)
if (BulkDataURI[0] === '/') {
if (dicomWebConfig.wadoRoot.startsWith('http')) {
// Absolute wado root
const url = new URL(dicomWebConfig.wadoRoot);
value.BulkDataURI = `${url.origin}${BulkDataURI}`;
} else {
// Relative wado root, we don't need to do anything, bulkdata uri is already correct
}
}
}
export { fixBulkDataURI };

View File

@@ -0,0 +1,12 @@
/**
* Fix multi-valued keys so that those which are strings split by
* a backslash are returned as arrays.
*/
export function fixMultiValueKeys(naturalData, keys = ['ImageType']) {
for (const key of keys) {
if (typeof naturalData[key] === 'string') {
naturalData[key] = naturalData[key].split('\\');
}
}
return naturalData;
}

View File

@@ -0,0 +1,70 @@
import findIndexOfString from './findIndexOfString';
/**
* Fix multipart data coming back from the retrieve bulkdata request, but
* incorrectly tagged as application/octet-stream. Some servers don't handle
* the response type correctly, and this method is relatively robust about
* detecting multipart data correctly. It will only extract one value.
*/
export default function fixMultipart(arrayData) {
const data = new Uint8Array(arrayData[0]);
// Don't know the exact minimum length, but it is at least 25 to encode multipart
if (data.length < 25) {
return arrayData;
}
const dashIndex = findIndexOfString(data, '--');
if (dashIndex > 6) {
return arrayData;
}
const tokenIndex = findIndexOfString(data, '\r\n\r\n', dashIndex);
if (tokenIndex > 512) {
// Allow for 512 characters in the header - there is no apriori limit, but
// this seems ok for now as we only expect it to have content type in it.
return arrayData;
}
const header = uint8ArrayToString(data, 0, tokenIndex);
// Now find the boundary marker
const responseHeaders = header.split('\r\n');
const boundary = findBoundary(responseHeaders);
if (!boundary) {
return arrayData;
}
// Start of actual data is 4 characters after the token
const offset = tokenIndex + 4;
const endIndex = findIndexOfString(data, boundary, offset);
if (endIndex === -1) {
return arrayData;
}
return [data.slice(offset, endIndex - 2).buffer];
}
export function findBoundary(header: string[]): string {
for (let i = 0; i < header.length; i++) {
if (header[i].substr(0, 2) === '--') {
return header[i];
}
}
}
export function findContentType(header: string[]): string {
for (let i = 0; i < header.length; i++) {
if (header[i].substr(0, 13) === 'Content-Type:') {
return header[i].substr(13).trim();
}
}
}
export function uint8ArrayToString(data, offset, length) {
offset = offset || 0;
length = length || data.length - offset;
let str = '';
for (let i = offset; i < offset + length; i++) {
str += String.fromCharCode(data[i]);
}
return str;
}

View File

@@ -0,0 +1,54 @@
import getWADORSImageId from './getWADORSImageId';
function buildInstanceWadoUrl(config, instance) {
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
const params = [];
params.push('requestType=WADO');
params.push(`studyUID=${StudyInstanceUID}`);
params.push(`seriesUID=${SeriesInstanceUID}`);
params.push(`objectUID=${SOPInstanceUID}`);
params.push('contentType=application/dicom');
params.push('transferSyntax=*');
const paramString = params.join('&');
return `${config.wadoUriRoot}?${paramString}`;
}
/**
* Obtain an imageId for Cornerstone from an image instance
*
* @param instance
* @param frame
* @param thumbnail
* @returns {string} The imageId to be used by Cornerstone
*/
export default function getImageId({ instance, frame, config, thumbnail = false }) {
if (!instance) {
return;
}
if (instance.imageId && frame === undefined) {
return instance.imageId;
}
if (instance.url) {
return instance.url;
}
const renderingAttr = thumbnail ? 'thumbnailRendering' : 'imageRendering';
if (!config[renderingAttr] || config[renderingAttr] === 'wadouri') {
const wadouri = buildInstanceWadoUrl(config, instance);
let imageId = 'dicomweb:' + wadouri;
if (frame !== undefined) {
imageId += '&frame=' + frame;
}
return imageId;
} else {
return getWADORSImageId(instance, config, frame); // WADO-RS Retrieve Frame
}
}

View File

@@ -0,0 +1,51 @@
function buildInstanceWadoRsUri(instance, config) {
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
return `${config.wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}`;
}
function buildInstanceFrameWadoRsUri(instance, config, frame) {
const baseWadoRsUri = buildInstanceWadoRsUri(instance, config);
frame = frame || 1;
return `${baseWadoRsUri}/frames/${frame}`;
}
// function getWADORSImageUrl(instance, frame) {
// const wadorsuri = buildInstanceFrameWadoRsUri(instance, config, frame);
// if (!wadorsuri) {
// return;
// }
// // Use null to obtain an imageId which represents the instance
// if (frame === null) {
// wadorsuri = wadorsuri.replace(/frames\/(\d+)/, '');
// } else {
// // We need to sum 1 because WADO-RS frame number is 1-based
// frame = frame ? parseInt(frame) + 1 : 1;
// // Replaces /frame/1 by /frame/{frame}
// wadorsuri = wadorsuri.replace(/frames\/(\d+)/, `frames/${frame}`);
// }
// return wadorsuri;
// }
/**
* Obtain an imageId for Cornerstone based on the WADO-RS scheme
*
* @param {object} instanceMetada metadata object (InstanceMetadata)
* @param {(string\|number)} [frame] the frame number
* @returns {string} The imageId to be used by Cornerstone
*/
export default function getWADORSImageId(instance, config, frame) {
//const uri = getWADORSImageUrl(instance, frame);
const uri = buildInstanceFrameWadoRsUri(instance, config, frame);
if (!uri) {
return;
}
return `wadors:${uri}`;
}

View File

@@ -0,0 +1,9 @@
import { fixBulkDataURI } from './fixBulkDataURI';
import {
cleanDenaturalizedDataset,
transferDenaturalizedDataset,
} from './cleanDenaturalizedDataset';
export { fixMultiValueKeys } from './fixMultiValueKeys';
export { fixBulkDataURI, cleanDenaturalizedDataset, transferDenaturalizedDataset };

View File

@@ -0,0 +1,61 @@
import RetrieveMetadata from '../wado/retrieveMetadata';
/**
* Retrieve metadata filtered.
*
* @param {*} dicomWebClient The DICOMWebClient instance to be used for series load
* @param {*} StudyInstanceUID The UID of the Study to be retrieved
* @param {*} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously
* @param {object} filters Object containing filters to be applied on retrieve metadata process
* @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against
* @param {function} [sortCriteria] Sort criteria function
* @param {function} [sortFunction] Sort function
*
* @returns
*/
function retrieveMetadataFiltered(
dicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters,
sortCriteria,
sortFunction
) {
const { seriesInstanceUID } = filters;
return new Promise((resolve, reject) => {
const promises = seriesInstanceUID.map(uid => {
const seriesSpecificFilters = Object.assign({}, filters, {
seriesInstanceUID: uid,
});
return RetrieveMetadata(
dicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
seriesSpecificFilters,
sortCriteria,
sortFunction
);
});
if (enableStudyLazyLoad === true) {
Promise.all(promises).then(results => {
const aggregatedResult = { preLoadData: [], promises: [] };
results.forEach(({ preLoadData, promises }) => {
aggregatedResult.preLoadData = aggregatedResult.preLoadData.concat(preLoadData);
aggregatedResult.promises = aggregatedResult.promises.concat(promises);
});
resolve(aggregatedResult);
}, reject);
} else {
Promise.all(promises).then(results => {
resolve(results.flat());
}, reject);
}
});
}
export default retrieveMetadataFiltered;

View File

@@ -0,0 +1,41 @@
import RetrieveMetadataLoaderSync from './retrieveMetadataLoaderSync';
import RetrieveMetadataLoaderAsync from './retrieveMetadataLoaderAsync';
/**
* Retrieve Study metadata from a DICOM server. If the server is configured to use lazy load, only the first series
* will be loaded and the property "studyLoader" will be set to let consumer load remaining series as needed.
*
* @param {*} dicomWebClient The DICOMWebClient instance to be used for series load
* @param {*} StudyInstanceUID The UID of the Study to be retrieved
* @param {*} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously
* @param {object} filters Object containing filters to be applied on retrieve metadata process
* @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against
* @param {function} [sortCriteria] Sort criteria function
* @param {function} [sortFunction] Sort function
*
* @returns {Promise} A promises that resolves the study descriptor object
*/
async function RetrieveMetadata(
dicomWebClient,
StudyInstanceUID,
enableStudyLazyLoad,
filters = {},
sortCriteria,
sortFunction
) {
const RetrieveMetadataLoader =
enableStudyLazyLoad !== false ? RetrieveMetadataLoaderAsync : RetrieveMetadataLoaderSync;
const retrieveMetadataLoader = new RetrieveMetadataLoader(
dicomWebClient,
StudyInstanceUID,
filters,
sortCriteria,
sortFunction
);
const data = await retrieveMetadataLoader.execLoad();
return data;
}
export default RetrieveMetadata;

View File

@@ -0,0 +1,64 @@
/**
* Class to define inheritance of load retrieve strategy.
* The process can be async load (lazy) or sync load
*
* There are methods that must be implemented at consumer level
* To retrieve study call execLoad
*/
export default class RetrieveMetadataLoader {
/**
* @constructor
* @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} [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 = undefined,
sortFunction = undefined
) {
this.client = client;
this.studyInstanceUID = studyInstanceUID;
this.filters = filters;
this.sortCriteria = sortCriteria;
this.sortFunction = sortFunction;
}
async execLoad() {
const preLoadData = await this.preLoad();
const loadData = await this.load(preLoadData);
const postLoadData = await this.posLoad(loadData);
return postLoadData;
}
/**
* It iterates over given loaders running each one. Loaders parameters must be bind when getting it.
* @param {Array} loaders - array of loader to retrieve data.
*/
async runLoaders(loaders) {
let result;
for (const loader of loaders) {
result = await loader();
if (result && result.length) {
break; // closes iterator in case data is retrieved successfully
}
}
if (loaders.next().done && !result) {
throw new Error('RetrieveMetadataLoader failed');
}
return result;
}
// Methods to be overwrite
async configLoad() {}
async preLoad() {}
async load(preLoadData) {}
async posLoad(loadData) {}
}

View File

@@ -0,0 +1,159 @@
import dcmjs from 'dcmjs';
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.
*
* @param {DICOMWebClient} dicomWebClient The DICOMWebClient instance to be used for series load
* @param {string} studyInstanceUID The Study Instance UID from which series will be loaded
* @param {Array} seriesInstanceUIDList A list of Series Instance UIDs
*
* @returns {Object} Returns an object which supports loading of instances from each of given Series Instance UID
*/
function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList) {
return Object.freeze({
hasNext() {
return seriesInstanceUIDList.length > 0;
},
next() {
const { seriesInstanceUID, metadata } = seriesInstanceUIDList.shift();
const promise = new DeferredPromise();
promise.setMetadata(metadata);
promise.setProcessFunction(() => {
return client.retrieveSeriesMetadata({
studyInstanceUID,
seriesInstanceUID,
});
});
return promise;
},
});
}
/**
* Class for async load of study metadata.
* It inherits from RetrieveMetadataLoader
*
* It loads the one series and then append to seriesLoader the others to be consumed/loaded
*/
export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader {
/**
* @returns {Array} Array of preLoaders. To be consumed as queue
*/
*getPreLoaders() {
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) {
options.queryParams.SeriesInstanceUID = seriesInstanceUID;
preLoaders.push(client.searchForSeries.bind(client, options));
}
// Fallback preloader
preLoaders.push(client.searchForSeries.bind(client, options));
yield* preLoaders;
}
async preLoad() {
const preLoaders = this.getPreLoaders();
const result = await this.runLoaders(preLoaders);
const sortCriteria = this.sortCriteria;
const sortFunction = this.sortFunction;
const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary;
const naturalized = result.map(naturalizeDataset);
return sortStudySeries(naturalized, sortCriteria, sortFunction);
}
async load(preLoadData) {
const { client, studyInstanceUID } = this;
const seriesInstanceUIDs = preLoadData.map(seriesMetadata => {
return { seriesInstanceUID: seriesMetadata.SeriesInstanceUID, metadata: seriesMetadata };
});
const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs);
const promises = [];
while (seriesAsyncLoader.hasNext()) {
const promise = seriesAsyncLoader.next();
promises.push(promise);
}
return {
preLoadData,
promises,
};
}
async posLoad({ preLoadData, promises }) {
return {
preLoadData,
promises,
};
}
}

View File

@@ -0,0 +1,59 @@
// import { api } from 'dicomweb-client';
// import DICOMWeb from '../../../DICOMWeb/';
import { createStudyFromSOPInstanceList } from './studyInstanceHelpers';
import RetrieveMetadataLoader from './retrieveMetadataLoader';
/**
* Class for sync load of study metadata.
* It inherits from RetrieveMetadataLoader
*
* A list of loaders (getLoaders) can be created so, it will be applied a fallback load strategy.
* I.e Retrieve metadata using all loaders possibilities.
*/
export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader {
getOptions() {
const { studyInstanceUID, filters } = this;
const options = {
studyInstanceUID,
};
const { seriesInstanceUID } = filters;
if (seriesInstanceUID) {
options['seriesInstanceUID'] = seriesInstanceUID;
}
return options;
}
/**
* @returns {Array} Array of loaders. To be consumed as queue
*/
*getLoaders() {
const loaders = [];
const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this;
if (seriesInstanceUID) {
loaders.push(
client.retrieveSeriesMetadata.bind(client, {
studyInstanceUID,
seriesInstanceUID,
})
);
}
loaders.push(client.retrieveStudyMetadata.bind(client, { studyInstanceUID }));
yield* loaders;
}
async load(preLoadData) {
const loaders = this.getLoaders();
const result = this.runLoaders(loaders);
return result;
}
async posLoad(loadData) {
return loadData;
}
}