2020-05-07 15:56:26 +02:00
|
|
|
import { api } from 'dicomweb-client';
|
2023-09-01 22:19:39 +02:00
|
|
|
import { DicomMetadataStore, IWebApiDataSource, utils, errorHandler, classes } from '@ohif/core';
|
2022-07-27 18:39:04 +02:00
|
|
|
|
2020-05-13 05:10:21 +02:00
|
|
|
import {
|
|
|
|
|
mapParams,
|
|
|
|
|
search as qidoSearch,
|
|
|
|
|
seriesInStudy,
|
|
|
|
|
processResults,
|
|
|
|
|
processSeriesResults,
|
|
|
|
|
} from './qido.js';
|
2024-05-08 18:12:49 +02:00
|
|
|
import dcm4cheeReject from './dcm4cheeReject.js';
|
2020-05-12 16:00:27 +02:00
|
|
|
|
2024-05-08 18:12:49 +02:00
|
|
|
import getImageId from './utils/getImageId.js';
|
2021-03-01 16:52:54 +01:00
|
|
|
import dcmjs from 'dcmjs';
|
2023-09-01 22:19:39 +02:00
|
|
|
import { retrieveStudyMetadata, deleteStudyMetadataPromise } from './retrieveStudyMetadata.js';
|
2023-04-05 18:59:56 +02:00
|
|
|
import StaticWadoClient from './utils/StaticWadoClient';
|
2024-06-18 04:36:03 +02:00
|
|
|
import getDirectURL from '../utils/getDirectURL';
|
2023-05-29 15:04:49 +02:00
|
|
|
import { fixBulkDataURI } from './utils/fixBulkDataURI';
|
2025-10-17 02:01:34 +02:00
|
|
|
import {HeadersInterface} from '@ohif/core/src/types/RequestHeaders';
|
2020-05-11 18:56:46 +02:00
|
|
|
|
2020-06-25 18:21:03 +02:00
|
|
|
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
|
|
|
|
|
|
|
|
|
|
const { naturalizeDataset, denaturalizeDataset } = DicomMetaDictionary;
|
2020-05-11 18:56:46 +02:00
|
|
|
|
2023-09-01 22:19:39 +02:00
|
|
|
const ImplementationClassUID = '2.25.270695996825855179949881587723571202391.2.0.0';
|
2025-07-31 06:21:26 +02:00
|
|
|
const ImplementationVersionName = 'OHIF-3.11.0';
|
2020-06-26 11:24:19 +02:00
|
|
|
const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1';
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
const metadataProvider = classes.MetadataProvider;
|
|
|
|
|
|
2024-12-17 19:49:42 +01:00
|
|
|
export type DicomWebConfig = {
|
|
|
|
|
/** Data source name */
|
|
|
|
|
name: string;
|
|
|
|
|
// wadoUriRoot - Legacy? (potentially unused/replaced)
|
|
|
|
|
/** Base URL to use for QIDO requests */
|
|
|
|
|
qidoRoot?: string;
|
|
|
|
|
wadoRoot?: string; // - Base URL to use for WADO requests
|
|
|
|
|
wadoUri?: string; // - Base URL to use for WADO URI requests
|
|
|
|
|
qidoSupportsIncludeField?: boolean; // - Whether QIDO supports the "Include" option to request additional fields in response
|
|
|
|
|
imageRendering?: string; // - wadors | ? (unsure of where/how this is used)
|
2025-05-08 18:44:56 +02:00
|
|
|
thumbnailRendering?: string;
|
|
|
|
|
/**
|
|
|
|
|
wadors - render using the wadors fetch. The full image is retrieved and rendered in cornerstone to thumbnail size png and returned as binary data to the src attribute of the image tag.
|
|
|
|
|
for example, <img src=data:image/png;base64,sdlfk;adkfadfk....asldfjkl;asdkf>
|
|
|
|
|
thumbnailDirect - get the direct url endpoint for the thumbnail as the image src (eg not authentication required).
|
|
|
|
|
for example, <img src=http://server:port/wadors/studies/1.2.3/thumbnail?accept=image/jpeg>
|
|
|
|
|
thumbnail - render using the thumbnail endpoint on wadors using bulkDataURI, passing authentication params to the url.
|
|
|
|
|
rendered - should use the rendered endpoint instead of the thumbnail endpoint
|
|
|
|
|
*/
|
2024-12-17 19:49:42 +01:00
|
|
|
/** Whether the server supports reject calls (i.e. DCM4CHEE) */
|
|
|
|
|
supportsReject?: boolean;
|
|
|
|
|
/** indicates if the retrieves can fetch singlepart. Options are bulkdata, video, image, or true */
|
|
|
|
|
singlepart?: boolean | string;
|
|
|
|
|
/** Transfer syntax to request from the server */
|
|
|
|
|
requestTransferSyntaxUID?: string;
|
|
|
|
|
acceptHeader?: string[]; // - Accept header to use for requests
|
|
|
|
|
/** Whether to omit quotation marks for multipart requests */
|
|
|
|
|
omitQuotationForMultipartRequest?: boolean;
|
|
|
|
|
/** Whether the server supports fuzzy matching */
|
|
|
|
|
supportsFuzzyMatching?: boolean;
|
|
|
|
|
/** Whether the server supports wildcard matching */
|
|
|
|
|
supportsWildcard?: boolean;
|
|
|
|
|
/** Whether the server supports the native DICOM model */
|
|
|
|
|
supportsNativeDICOMModel?: boolean;
|
|
|
|
|
/** Whether to enable request tag */
|
|
|
|
|
enableRequestTag?: boolean;
|
|
|
|
|
/** Whether to enable study lazy loading */
|
|
|
|
|
enableStudyLazyLoad?: boolean;
|
|
|
|
|
/** Whether to enable bulkDataURI */
|
|
|
|
|
bulkDataURI?: BulkDataURIConfig;
|
|
|
|
|
/** Function that is called after the configuration is initialized */
|
|
|
|
|
onConfiguration: (config: DicomWebConfig, params) => DicomWebConfig;
|
|
|
|
|
/** Whether to use the static WADO client */
|
|
|
|
|
staticWado?: boolean;
|
|
|
|
|
/** User authentication service */
|
|
|
|
|
userAuthenticationService: Record<string, unknown>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type BulkDataURIConfig = {
|
|
|
|
|
/** Enable bulkdata uri configuration */
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
/**
|
|
|
|
|
* Remove the startsWith string.
|
|
|
|
|
* This is used to correct reverse proxied URLs by removing the startsWith path
|
|
|
|
|
*/
|
|
|
|
|
startsWith?: string;
|
|
|
|
|
/**
|
|
|
|
|
* Adds this prefix path. Only used if the startsWith is defined and has
|
|
|
|
|
* been removed. This allows replacing the base path.
|
|
|
|
|
*/
|
|
|
|
|
prefixWith?: string;
|
|
|
|
|
/** Transform the bulkdata path. Used to replace a portion of the path */
|
|
|
|
|
transform?: (uri: string) => string;
|
|
|
|
|
/**
|
|
|
|
|
* Adds relative resolution to the path handling.
|
|
|
|
|
* series is the default, as the metadata retrieved is series level.
|
|
|
|
|
*/
|
|
|
|
|
relativeResolution?: 'studies' | 'series';
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-17 02:01:34 +02:00
|
|
|
/**
|
|
|
|
|
* The header options are the options passed into the generateWadoHeader
|
|
|
|
|
* command. This takes an extensible set of attributes to allow future enhancements.
|
|
|
|
|
*/
|
|
|
|
|
export interface HeaderOptions {
|
|
|
|
|
includeTransferSyntax?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Metadata and some other requests don't permit the transfer syntax to be included,
|
|
|
|
|
* so pass in the excludeTransferSyntax parameter.
|
|
|
|
|
*/
|
|
|
|
|
export const excludeTransferSyntax: HeaderOptions = { includeTransferSyntax: false };
|
|
|
|
|
|
2020-05-07 15:56:26 +02:00
|
|
|
/**
|
2024-01-09 20:22:00 +01:00
|
|
|
* Creates a DICOM Web API based on the provided configuration.
|
2020-05-07 15:56:26 +02:00
|
|
|
*
|
2024-12-17 19:49:42 +01:00
|
|
|
* @param dicomWebConfig - Configuration for the DICOM Web API
|
|
|
|
|
* @returns DICOM Web API object
|
2020-05-07 15:56:26 +02:00
|
|
|
*/
|
2024-12-17 19:49:42 +01:00
|
|
|
function createDicomWebApi(dicomWebConfig: DicomWebConfig, servicesManager) {
|
|
|
|
|
const { userAuthenticationService } = servicesManager.services;
|
2023-07-20 17:43:05 +02:00
|
|
|
let dicomWebConfigCopy,
|
|
|
|
|
qidoConfig,
|
|
|
|
|
wadoConfig,
|
|
|
|
|
qidoDicomWebClient,
|
|
|
|
|
wadoDicomWebClient,
|
2024-12-17 19:49:42 +01:00
|
|
|
getAuthorizationHeader,
|
2023-07-20 17:43:05 +02:00
|
|
|
generateWadoHeader;
|
2024-12-17 19:49:42 +01:00
|
|
|
// Default to enabling bulk data retrieves, with no other customization as
|
|
|
|
|
// this is part of hte base standard.
|
|
|
|
|
dicomWebConfig.bulkDataURI ||= { enabled: true };
|
2023-05-29 10:38:04 +02:00
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
const implementation = {
|
|
|
|
|
initialize: ({ params, query }) => {
|
2023-09-01 22:19:39 +02:00
|
|
|
if (dicomWebConfig.onConfiguration && typeof dicomWebConfig.onConfiguration === 'function') {
|
2023-07-20 17:43:05 +02:00
|
|
|
dicomWebConfig = dicomWebConfig.onConfiguration(dicomWebConfig, {
|
|
|
|
|
params,
|
|
|
|
|
query,
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-05-29 10:38:04 +02:00
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig));
|
2020-05-07 15:56:26 +02:00
|
|
|
|
2024-12-17 19:49:42 +01:00
|
|
|
getAuthorizationHeader = () => {
|
2025-10-17 02:01:34 +02:00
|
|
|
const xhrRequestHeaders: HeadersInterface = {};
|
2023-07-20 17:43:05 +02:00
|
|
|
const authHeaders = userAuthenticationService.getAuthorizationHeader();
|
|
|
|
|
if (authHeaders && authHeaders.Authorization) {
|
|
|
|
|
xhrRequestHeaders.Authorization = authHeaders.Authorization;
|
|
|
|
|
}
|
|
|
|
|
return xhrRequestHeaders;
|
|
|
|
|
};
|
2020-05-11 18:56:46 +02:00
|
|
|
|
2025-10-17 02:01:34 +02:00
|
|
|
/**
|
|
|
|
|
* Generates the wado header for requesting resources from DICOMweb.
|
|
|
|
|
* These are classified into those that are dependent on the transfer syntax
|
|
|
|
|
* and those that aren't, as defined by the include transfer syntax attribute.
|
|
|
|
|
*/
|
|
|
|
|
generateWadoHeader = (options: HeaderOptions): HeadersInterface => {
|
2024-12-17 19:49:42 +01:00
|
|
|
const authorizationHeader = getAuthorizationHeader();
|
2025-10-17 02:01:34 +02:00
|
|
|
if (options?.includeTransferSyntax!==false) {
|
|
|
|
|
//Generate accept header depending on config params
|
|
|
|
|
const formattedAcceptHeader = utils.generateAcceptHeader(
|
|
|
|
|
dicomWebConfig.acceptHeader,
|
|
|
|
|
dicomWebConfig.requestTransferSyntaxUID,
|
|
|
|
|
dicomWebConfig.omitQuotationForMultipartRequest
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
...authorizationHeader,
|
|
|
|
|
Accept: formattedAcceptHeader,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
// The base header will be included in the request. We simply skip customization options around
|
|
|
|
|
// transfer syntaxes and whether the request is multipart. In other words, a request in
|
|
|
|
|
// which the server expects Accept: application/dicom+json will still include that in the
|
|
|
|
|
// header.
|
|
|
|
|
return {
|
|
|
|
|
...authorizationHeader
|
|
|
|
|
};
|
|
|
|
|
}
|
2023-07-20 17:43:05 +02:00
|
|
|
};
|
2020-05-07 15:56:26 +02:00
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
qidoConfig = {
|
|
|
|
|
url: dicomWebConfig.qidoRoot,
|
|
|
|
|
staticWado: dicomWebConfig.staticWado,
|
|
|
|
|
singlepart: dicomWebConfig.singlepart,
|
|
|
|
|
headers: userAuthenticationService.getAuthorizationHeader(),
|
|
|
|
|
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
2025-01-27 17:20:00 +01:00
|
|
|
supportsFuzzyMatching: dicomWebConfig.supportsFuzzyMatching,
|
2023-07-20 17:43:05 +02:00
|
|
|
};
|
2021-05-28 20:32:48 +02:00
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
wadoConfig = {
|
|
|
|
|
url: dicomWebConfig.wadoRoot,
|
|
|
|
|
staticWado: dicomWebConfig.staticWado,
|
|
|
|
|
singlepart: dicomWebConfig.singlepart,
|
|
|
|
|
headers: userAuthenticationService.getAuthorizationHeader(),
|
|
|
|
|
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
2025-01-27 17:20:00 +01:00
|
|
|
supportsFuzzyMatching: dicomWebConfig.supportsFuzzyMatching,
|
2023-07-20 17:43:05 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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);
|
2021-05-28 20:32:48 +02:00
|
|
|
},
|
2020-05-07 15:56:26 +02:00
|
|
|
query: {
|
|
|
|
|
studies: {
|
|
|
|
|
mapParams: mapParams.bind(),
|
2023-09-01 22:19:39 +02:00
|
|
|
search: async function (origParams) {
|
2024-12-17 19:49:42 +01:00
|
|
|
qidoDicomWebClient.headers = getAuthorizationHeader();
|
2020-05-10 06:25:20 +02:00
|
|
|
const { studyInstanceUid, seriesInstanceUid, ...mappedParams } =
|
2020-07-24 12:51:57 +02:00
|
|
|
mapParams(origParams, {
|
2023-07-20 17:43:05 +02:00
|
|
|
supportsFuzzyMatching: dicomWebConfig.supportsFuzzyMatching,
|
|
|
|
|
supportsWildcard: dicomWebConfig.supportsWildcard,
|
2020-07-24 12:51:57 +02:00
|
|
|
}) || {};
|
2020-05-10 06:25:20 +02:00
|
|
|
|
2023-09-01 22:19:39 +02:00
|
|
|
const results = await qidoSearch(qidoDicomWebClient, undefined, undefined, mappedParams);
|
2020-05-10 06:25:20 +02:00
|
|
|
|
|
|
|
|
return processResults(results);
|
|
|
|
|
},
|
2020-05-07 15:56:26 +02:00
|
|
|
processResults: processResults.bind(),
|
|
|
|
|
},
|
2020-05-12 21:27:59 +02:00
|
|
|
series: {
|
|
|
|
|
// mapParams: mapParams.bind(),
|
2023-09-01 22:19:39 +02:00
|
|
|
search: async function (studyInstanceUid) {
|
2024-12-17 19:49:42 +01:00
|
|
|
qidoDicomWebClient.headers = getAuthorizationHeader();
|
2023-09-01 22:19:39 +02:00
|
|
|
const results = await seriesInStudy(qidoDicomWebClient, studyInstanceUid);
|
2020-05-12 21:27:59 +02:00
|
|
|
|
2020-05-13 05:10:21 +02:00
|
|
|
return processSeriesResults(results);
|
2020-05-12 21:27:59 +02:00
|
|
|
},
|
|
|
|
|
// processResults: processResults.bind(),
|
|
|
|
|
},
|
2020-05-07 15:56:26 +02:00
|
|
|
instances: {
|
2021-06-23 20:41:27 +02:00
|
|
|
search: (studyInstanceUid, queryParameters) => {
|
2024-12-17 19:49:42 +01:00
|
|
|
qidoDicomWebClient.headers = getAuthorizationHeader();
|
2023-12-13 20:21:46 +01:00
|
|
|
return qidoSearch.call(
|
|
|
|
|
undefined,
|
|
|
|
|
qidoDicomWebClient,
|
|
|
|
|
studyInstanceUid,
|
|
|
|
|
null,
|
|
|
|
|
queryParameters
|
|
|
|
|
);
|
2021-06-23 20:41:27 +02:00
|
|
|
},
|
2020-05-07 15:56:26 +02:00
|
|
|
},
|
|
|
|
|
},
|
2020-05-11 18:56:46 +02:00
|
|
|
retrieve: {
|
2022-04-04 15:48:14 +02:00
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2025-05-08 18:44:56 +02:00
|
|
|
|
|
|
|
|
getGetThumbnailSrc: function (instance, imageId) {
|
|
|
|
|
if (dicomWebConfig.thumbnailRendering === 'wadors') {
|
|
|
|
|
return function getThumbnailSrc(options) {
|
|
|
|
|
if (!imageId) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (!options?.getImageSrc) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return options.getImageSrc(imageId);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (dicomWebConfig.thumbnailRendering === 'thumbnailDirect') {
|
|
|
|
|
return function getThumbnailSrc() {
|
|
|
|
|
return this.directURL({
|
|
|
|
|
instance: instance,
|
|
|
|
|
defaultPath: '/thumbnail',
|
|
|
|
|
defaultType: 'image/jpeg',
|
|
|
|
|
singlepart: true,
|
|
|
|
|
tag: 'Absent',
|
|
|
|
|
});
|
|
|
|
|
}.bind(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dicomWebConfig.thumbnailRendering === 'thumbnail') {
|
|
|
|
|
return async function getThumbnailSrc() {
|
|
|
|
|
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
|
|
|
|
|
const bulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/thumbnail?accept=image/jpeg`;
|
|
|
|
|
return URL.createObjectURL(
|
|
|
|
|
new Blob(
|
|
|
|
|
[
|
|
|
|
|
await this.bulkDataURI({
|
|
|
|
|
BulkDataURI: bulkDataURI.replace('wadors:', ''),
|
|
|
|
|
defaultType: 'image/jpeg',
|
|
|
|
|
mediaTypes: ['image/jpeg'],
|
|
|
|
|
thumbnail: true,
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
{ type: 'image/jpeg' }
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}.bind(this);
|
|
|
|
|
}
|
|
|
|
|
if (dicomWebConfig.thumbnailRendering === 'rendered') {
|
|
|
|
|
return async function getThumbnailSrc() {
|
|
|
|
|
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
|
|
|
|
|
const bulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered?accept=image/jpeg`;
|
|
|
|
|
return URL.createObjectURL(
|
|
|
|
|
new Blob(
|
|
|
|
|
[
|
|
|
|
|
await this.bulkDataURI({
|
|
|
|
|
BulkDataURI: bulkDataURI.replace('wadors:', ''),
|
|
|
|
|
defaultType: 'image/jpeg',
|
|
|
|
|
mediaTypes: ['image/jpeg'],
|
|
|
|
|
thumbnail: true,
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
|
{ type: 'image/jpeg' }
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}.bind(this);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
directURL: params => {
|
2023-07-20 17:43:05 +02:00
|
|
|
return getDirectURL(
|
|
|
|
|
{
|
|
|
|
|
wadoRoot: dicomWebConfig.wadoRoot,
|
|
|
|
|
singlepart: dicomWebConfig.singlepart,
|
|
|
|
|
},
|
|
|
|
|
params
|
|
|
|
|
);
|
2022-02-24 17:55:12 +01:00
|
|
|
},
|
2024-08-15 20:36:53 +02:00
|
|
|
/**
|
|
|
|
|
* 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,
|
|
|
|
|
|
feat(RT): add dicom RT support via volume viewports (#3310)
* feat: initial RT support
* make the segmentation service work with representation data
* feat: make segmentation service work with representations
* fix rtss vis
* fix: rt hydration
* fix the rendering of rt names
* fix imports
* refactor: Modify status and click handling for hydration of RTStructures
Modify status and click handling for hydration of RTStructures by renaming `onPillClick` to `onStatusClick` in `OHIFCornerstoneRTViewport.tsx` and `_getStatusComponent.tsx` files. Also, update initial segmentation configurations in `PanelSegmentation.tsx` and simplify configuration changes and values for segmentation service in `SegmentationService.ts`. Finally, remove console debug in `CornerstoneViewportService.ts`.
* wip for highlighting contours
* refactor rt displayset code
* review code update
* update cornerstone dependencies
* refactor: Update license year, version number, and minor code cleanup
This commit updates the license year in several files, updates the version number in package.json, and contains minor code cleanup in two files.
* add bulkdataURI retrieve for RT
* fix package version
* apply review comments
* apply review comments
* apply review comments
* feat(panels): refactor and streamline segmentation configuration and inputs
Rewrote state hooks and streamlined the configuration input for `PanelSegmentation` to be more verbose and reusable. Included several new input types, including the `InputRange` component which now shows a fixed floating value based on the step provided. The `SegmentationConfig` component now works with dynamic values controlled by `initialConfig`. These changes should improve function usability and make the code more maintainable going forward.
* fix various bugs
* fix contour delete by upgrade cs3d version
* feat(viewport, inputNumber, segmentationConfig, orthanc): Implement minimum and maximum values for input number components, and useBulkDataURI for Orthanc configuration. Compare measurement view planes with absolute viewport view planes in Cornerstone viewport.
* update yarn lock
2023-04-26 18:21:08 +02:00
|
|
|
bulkDataURI: async ({ StudyInstanceUID, BulkDataURI }) => {
|
2024-12-17 19:49:42 +01:00
|
|
|
qidoDicomWebClient.headers = getAuthorizationHeader();
|
feat(RT): add dicom RT support via volume viewports (#3310)
* feat: initial RT support
* make the segmentation service work with representation data
* feat: make segmentation service work with representations
* fix rtss vis
* fix: rt hydration
* fix the rendering of rt names
* fix imports
* refactor: Modify status and click handling for hydration of RTStructures
Modify status and click handling for hydration of RTStructures by renaming `onPillClick` to `onStatusClick` in `OHIFCornerstoneRTViewport.tsx` and `_getStatusComponent.tsx` files. Also, update initial segmentation configurations in `PanelSegmentation.tsx` and simplify configuration changes and values for segmentation service in `SegmentationService.ts`. Finally, remove console debug in `CornerstoneViewportService.ts`.
* wip for highlighting contours
* refactor rt displayset code
* review code update
* update cornerstone dependencies
* refactor: Update license year, version number, and minor code cleanup
This commit updates the license year in several files, updates the version number in package.json, and contains minor code cleanup in two files.
* add bulkdataURI retrieve for RT
* fix package version
* apply review comments
* apply review comments
* apply review comments
* feat(panels): refactor and streamline segmentation configuration and inputs
Rewrote state hooks and streamlined the configuration input for `PanelSegmentation` to be more verbose and reusable. Included several new input types, including the `InputRange` component which now shows a fixed floating value based on the step provided. The `SegmentationConfig` component now works with dynamic values controlled by `initialConfig`. These changes should improve function usability and make the code more maintainable going forward.
* fix various bugs
* fix contour delete by upgrade cs3d version
* feat(viewport, inputNumber, segmentationConfig, orthanc): Implement minimum and maximum values for input number components, and useBulkDataURI for Orthanc configuration. Compare measurement view planes with absolute viewport view planes in Cornerstone viewport.
* update yarn lock
2023-04-26 18:21:08 +02:00
|
|
|
const options = {
|
|
|
|
|
multipart: false,
|
|
|
|
|
BulkDataURI,
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
};
|
|
|
|
|
return qidoDicomWebClient.retrieveBulkData(options).then(val => {
|
|
|
|
|
const ret = (val && val[0]) || undefined;
|
|
|
|
|
return ret;
|
|
|
|
|
});
|
|
|
|
|
},
|
2020-05-11 18:56:46 +02:00
|
|
|
series: {
|
2022-02-08 14:17:37 +01:00
|
|
|
metadata: async ({
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
|
|
|
|
sortFunction,
|
|
|
|
|
madeInClient = false,
|
2024-01-09 20:22:00 +01:00
|
|
|
returnPromises = false,
|
2022-02-08 14:17:37 +01:00
|
|
|
} = {}) => {
|
|
|
|
|
if (!StudyInstanceUID) {
|
2023-09-01 22:19:39 +02:00
|
|
|
throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID');
|
2020-05-11 18:56:46 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
if (dicomWebConfig.enableStudyLazyLoad) {
|
2022-07-27 18:39:04 +02:00
|
|
|
return implementation._retrieveSeriesMetadataAsync(
|
2020-05-12 14:49:32 +02:00
|
|
|
StudyInstanceUID,
|
2022-07-27 18:39:04 +02:00
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
|
|
|
|
sortFunction,
|
2024-01-09 20:22:00 +01:00
|
|
|
madeInClient,
|
|
|
|
|
returnPromises
|
2022-02-08 14:17:37 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
return implementation._retrieveSeriesMetadataSync(
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
|
|
|
|
sortFunction,
|
2022-02-08 14:17:37 +01:00
|
|
|
madeInClient
|
2020-05-12 14:49:32 +02:00
|
|
|
);
|
2020-05-12 16:00:27 +02:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2022-07-27 18:39:04 +02:00
|
|
|
|
2020-06-25 18:21:03 +02:00
|
|
|
store: {
|
2023-12-13 20:21:46 +01:00
|
|
|
dicom: async (dataset, request, dicomDict) => {
|
2024-12-17 19:49:42 +01:00
|
|
|
wadoDicomWebClient.headers = getAuthorizationHeader();
|
2023-04-28 22:13:47 +02:00
|
|
|
if (dataset instanceof ArrayBuffer) {
|
|
|
|
|
const options = {
|
|
|
|
|
datasets: [dataset],
|
|
|
|
|
request,
|
|
|
|
|
};
|
|
|
|
|
await wadoDicomWebClient.storeInstances(options);
|
|
|
|
|
} else {
|
2023-12-13 20:21:46 +01:00
|
|
|
let effectiveDicomDict = dicomDict;
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2023-12-13 20:21:46 +01:00
|
|
|
if (!dicomDict) {
|
|
|
|
|
const meta = {
|
|
|
|
|
FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value,
|
|
|
|
|
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
|
|
|
|
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
|
|
|
|
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
|
|
|
|
|
ImplementationClassUID,
|
|
|
|
|
ImplementationVersionName,
|
|
|
|
|
};
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2023-12-13 20:21:46 +01:00
|
|
|
const denaturalized = denaturalizeDataset(meta);
|
|
|
|
|
const defaultDicomDict = new DicomDict(denaturalized);
|
|
|
|
|
defaultDicomDict.dict = denaturalizeDataset(dataset);
|
|
|
|
|
|
|
|
|
|
effectiveDicomDict = defaultDicomDict;
|
|
|
|
|
}
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2023-12-13 20:21:46 +01:00
|
|
|
const part10Buffer = effectiveDicomDict.write();
|
2023-04-28 22:13:47 +02:00
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
|
datasets: [part10Buffer],
|
|
|
|
|
request,
|
|
|
|
|
};
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2023-04-28 22:13:47 +02:00
|
|
|
await wadoDicomWebClient.storeInstances(options);
|
|
|
|
|
}
|
2020-06-25 18:21:03 +02:00
|
|
|
},
|
|
|
|
|
},
|
2023-04-28 22:13:47 +02:00
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
_retrieveSeriesMetadataSync: async (
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
|
|
|
|
sortFunction,
|
|
|
|
|
madeInClient
|
|
|
|
|
) => {
|
|
|
|
|
const enableStudyLazyLoad = false;
|
2025-10-17 02:01:34 +02:00
|
|
|
wadoDicomWebClient.headers = generateWadoHeader(excludeTransferSyntax);
|
2022-07-27 18:39:04 +02:00
|
|
|
// data is all SOPInstanceUIDs
|
|
|
|
|
const data = await retrieveStudyMetadata(
|
|
|
|
|
wadoDicomWebClient,
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
enableStudyLazyLoad,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
2023-11-24 14:46:54 +01:00
|
|
|
sortFunction,
|
|
|
|
|
dicomWebConfig
|
2022-07-27 18:39:04 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 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;
|
2023-09-22 16:23:44 +02:00
|
|
|
instance.wadoRoot = dicomWebConfig.wadoRoot;
|
|
|
|
|
instance.wadoUri = dicomWebConfig.wadoUri;
|
2022-07-27 18:39:04 +02:00
|
|
|
|
|
|
|
|
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 =>
|
2023-09-01 22:19:39 +02:00
|
|
|
DicomMetadataStore.addInstances(instancesPerSeries[seriesInstanceUID], madeInClient)
|
2022-07-27 18:39:04 +02:00
|
|
|
);
|
2023-11-24 14:46:54 +01:00
|
|
|
|
|
|
|
|
return seriesSummaryMetadata;
|
2022-07-27 18:39:04 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_retrieveSeriesMetadataAsync: async (
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
|
|
|
|
sortFunction,
|
2024-01-09 20:22:00 +01:00
|
|
|
madeInClient = false,
|
|
|
|
|
returnPromises = false
|
2022-07-27 18:39:04 +02:00
|
|
|
) => {
|
|
|
|
|
const enableStudyLazyLoad = true;
|
2025-10-17 02:01:34 +02:00
|
|
|
wadoDicomWebClient.headers = generateWadoHeader(excludeTransferSyntax);
|
2022-07-27 18:39:04 +02:00
|
|
|
// Get Series
|
2023-09-01 22:19:39 +02:00
|
|
|
const { preLoadData: seriesSummaryMetadata, promises: seriesPromises } =
|
|
|
|
|
await retrieveStudyMetadata(
|
|
|
|
|
wadoDicomWebClient,
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
enableStudyLazyLoad,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
2023-11-24 14:46:54 +01:00
|
|
|
sortFunction,
|
|
|
|
|
dicomWebConfig
|
2023-09-01 22:19:39 +02:00
|
|
|
);
|
2022-07-27 18:39:04 +02:00
|
|
|
|
2024-08-15 20:36:53 +02:00
|
|
|
/**
|
|
|
|
|
* Adds the retrieve bulkdata function to naturalized DICOM data.
|
|
|
|
|
* This is done recursively, for sub-sequences.
|
|
|
|
|
*/
|
|
|
|
|
const addRetrieveBulkDataNaturalized = (naturalized, instance = naturalized) => {
|
2025-03-19 16:52:56 +01:00
|
|
|
if (!naturalized) {
|
|
|
|
|
return naturalized;
|
|
|
|
|
}
|
2024-08-15 20:36:53 +02:00
|
|
|
for (const key of Object.keys(naturalized)) {
|
|
|
|
|
const value = naturalized[key];
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(value) && typeof value[0] === 'object') {
|
|
|
|
|
// Fix recursive values
|
2024-12-05 17:30:21 +01:00
|
|
|
const validValues = value.filter(Boolean);
|
|
|
|
|
validValues.forEach(child => addRetrieveBulkDataNaturalized(child, instance));
|
2024-08-15 20:36:53 +02:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
/**
|
|
|
|
|
* 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);
|
2023-05-29 15:04:49 +02:00
|
|
|
|
2023-09-12 13:40:38 +02:00
|
|
|
// if we know the server doesn't use bulkDataURI, then don't
|
2023-05-29 15:04:49 +02:00
|
|
|
if (!dicomWebConfig.bulkDataURI?.enabled) {
|
|
|
|
|
return naturalized;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-15 20:36:53 +02:00
|
|
|
return addRetrieveBulkDataNaturalized(naturalized);
|
2022-07-27 18:39:04 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Async load series, store as retrieved
|
|
|
|
|
function storeInstances(instances) {
|
|
|
|
|
const naturalizedInstances = instances.map(addRetrieveBulkData);
|
|
|
|
|
|
|
|
|
|
// Adding instanceMetadata to OHIF MetadataProvider
|
2024-01-09 20:22:00 +01:00
|
|
|
naturalizedInstances.forEach(instance => {
|
2022-11-16 20:19:52 +01:00
|
|
|
instance.wadoRoot = dicomWebConfig.wadoRoot;
|
|
|
|
|
instance.wadoUri = dicomWebConfig.wadoUri;
|
|
|
|
|
|
2024-11-28 19:22:40 +01:00
|
|
|
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
|
|
|
|
|
const numberOfFrames = instance.NumberOfFrames || 1;
|
|
|
|
|
// Process all frames consistently, whether single or multiframe
|
|
|
|
|
for (let i = 0; i < numberOfFrames; i++) {
|
|
|
|
|
const frameNumber = i + 1;
|
|
|
|
|
const frameImageId = implementation.getImageIdsForInstance({
|
|
|
|
|
instance,
|
|
|
|
|
frame: frameNumber,
|
|
|
|
|
});
|
|
|
|
|
// Add imageId specific mapping to this data as the URL isn't necessarily WADO-URI.
|
|
|
|
|
metadataProvider.addImageIdToUIDs(frameImageId, {
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
SeriesInstanceUID,
|
|
|
|
|
SOPInstanceUID,
|
|
|
|
|
frameNumber: numberOfFrames > 1 ? frameNumber : undefined,
|
|
|
|
|
});
|
|
|
|
|
}
|
2022-07-27 18:39:04 +02:00
|
|
|
|
|
|
|
|
// 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
|
2024-11-28 19:22:40 +01:00
|
|
|
const imageId = implementation.getImageIdsForInstance({
|
|
|
|
|
instance,
|
2022-07-27 18:39:04 +02:00
|
|
|
});
|
2024-11-28 19:22:40 +01:00
|
|
|
instance.imageId = imageId;
|
2022-07-27 18:39:04 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
DicomMetadataStore.addInstances(naturalizedInstances, madeInClient);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function setSuccessFlag() {
|
2024-01-09 20:22:00 +01:00
|
|
|
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
|
2023-11-02 17:07:52 +01:00
|
|
|
if (!study) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-07-27 18:39:04 +02:00
|
|
|
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);
|
|
|
|
|
|
2024-01-09 20:22:00 +01:00
|
|
|
const seriesDeliveredPromises = seriesPromises.map(promise => {
|
|
|
|
|
if (!returnPromises) {
|
|
|
|
|
promise?.start();
|
|
|
|
|
}
|
|
|
|
|
return promise.then(instances => {
|
2022-09-07 17:59:11 +02:00
|
|
|
storeInstances(instances);
|
2024-01-09 20:22:00 +01:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (returnPromises) {
|
|
|
|
|
Promise.all(seriesDeliveredPromises).then(() => setSuccessFlag());
|
|
|
|
|
return seriesPromises;
|
|
|
|
|
} else {
|
|
|
|
|
await Promise.all(seriesDeliveredPromises);
|
|
|
|
|
setSuccessFlag();
|
|
|
|
|
}
|
2023-11-24 14:46:54 +01:00
|
|
|
|
|
|
|
|
return seriesSummaryMetadata;
|
2022-07-27 18:39:04 +02:00
|
|
|
},
|
2020-06-30 16:10:27 +02:00
|
|
|
deleteStudyMetadataPromise,
|
2020-05-19 09:11:16 +02:00
|
|
|
getImageIdsForDisplaySet(displaySet) {
|
2020-05-12 16:00:27 +02:00
|
|
|
const images = displaySet.images;
|
|
|
|
|
const imageIds = [];
|
|
|
|
|
|
|
|
|
|
if (!images) {
|
|
|
|
|
return imageIds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
displaySet.images.forEach(instance => {
|
|
|
|
|
const NumberOfFrames = instance.NumberOfFrames;
|
|
|
|
|
|
|
|
|
|
if (NumberOfFrames > 1) {
|
2022-10-13 17:07:56 +02:00
|
|
|
for (let frame = 1; frame <= NumberOfFrames; frame++) {
|
2021-05-28 20:32:48 +02:00
|
|
|
const imageId = this.getImageIdsForInstance({
|
2020-05-12 16:00:27 +02:00
|
|
|
instance,
|
2022-10-13 17:07:56 +02:00
|
|
|
frame,
|
2020-05-12 16:00:27 +02:00
|
|
|
});
|
|
|
|
|
imageIds.push(imageId);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2021-05-28 20:32:48 +02:00
|
|
|
const imageId = this.getImageIdsForInstance({ instance });
|
2020-05-12 16:00:27 +02:00
|
|
|
imageIds.push(imageId);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return imageIds;
|
2020-05-11 18:56:46 +02:00
|
|
|
},
|
2024-01-09 20:22:00 +01:00
|
|
|
getImageIdsForInstance({ instance, frame = undefined }) {
|
2021-05-28 20:32:48 +02:00
|
|
|
const imageIds = getImageId({
|
|
|
|
|
instance,
|
|
|
|
|
frame,
|
|
|
|
|
config: dicomWebConfig,
|
|
|
|
|
});
|
|
|
|
|
return imageIds;
|
|
|
|
|
},
|
2023-04-28 22:13:47 +02:00
|
|
|
getConfig() {
|
|
|
|
|
return dicomWebConfigCopy;
|
|
|
|
|
},
|
2023-07-20 17:43:05 +02:00
|
|
|
getStudyInstanceUIDs({ params, query }) {
|
2025-01-23 20:24:13 +01:00
|
|
|
const paramsStudyInstanceUIDs = params.StudyInstanceUIDs || params.studyInstanceUIDs;
|
|
|
|
|
|
|
|
|
|
const queryStudyInstanceUIDs = utils.splitComma(
|
|
|
|
|
query.getAll('StudyInstanceUIDs').concat(query.getAll('studyInstanceUIDs'))
|
|
|
|
|
);
|
2023-07-20 17:43:05 +02:00
|
|
|
|
|
|
|
|
const StudyInstanceUIDs =
|
2023-09-01 22:19:39 +02:00
|
|
|
(queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || paramsStudyInstanceUIDs;
|
2023-07-20 17:43:05 +02:00
|
|
|
const StudyInstanceUIDsAsArray =
|
|
|
|
|
StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs)
|
|
|
|
|
? StudyInstanceUIDs
|
|
|
|
|
: [StudyInstanceUIDs];
|
|
|
|
|
|
|
|
|
|
return StudyInstanceUIDsAsArray;
|
|
|
|
|
},
|
2020-08-19 21:27:34 +02:00
|
|
|
};
|
|
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
if (dicomWebConfig.supportsReject) {
|
2024-12-20 16:23:33 +01:00
|
|
|
implementation.reject = dcm4cheeReject(dicomWebConfig.wadoRoot, getAuthorizationHeader);
|
2020-08-19 21:27:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return IWebApiDataSource.create(implementation);
|
2020-05-07 15:56:26 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-15 20:36:53 +02:00
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-07 15:56:26 +02:00
|
|
|
export { createDicomWebApi };
|