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';
|
2020-08-19 21:27:34 +02:00
|
|
|
import dcm4cheeReject from './dcm4cheeReject';
|
2020-05-12 16:00:27 +02:00
|
|
|
|
|
|
|
|
import getImageId from './utils/getImageId';
|
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';
|
|
|
|
|
import getDirectURL from '../utils/getDirectURL';
|
2023-05-29 15:04:49 +02:00
|
|
|
import { fixBulkDataURI } from './utils/fixBulkDataURI';
|
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';
|
2020-06-26 11:24:19 +02:00
|
|
|
const ImplementationVersionName = 'OHIF-VIEWER-2.0.0';
|
|
|
|
|
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;
|
|
|
|
|
|
2020-05-07 15:56:26 +02:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {string} name - Data source name
|
|
|
|
|
* @param {string} wadoUriRoot - Legacy? (potentially unused/replaced)
|
|
|
|
|
* @param {string} qidoRoot - Base URL to use for QIDO requests
|
|
|
|
|
* @param {string} wadoRoot - Base URL to use for WADO requests
|
|
|
|
|
* @param {boolean} qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response
|
|
|
|
|
* @param {string} imageRengering - wadors | ? (unsure of where/how this is used)
|
|
|
|
|
* @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used)
|
2020-08-19 21:27:34 +02:00
|
|
|
* @param {bool} supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE)
|
2020-05-07 15:56:26 +02:00
|
|
|
* @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
2022-04-04 15:48:14 +02:00
|
|
|
* @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true
|
2020-05-07 15:56:26 +02:00
|
|
|
*/
|
2023-02-13 14:55:55 +01:00
|
|
|
function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
|
2023-07-20 17:43:05 +02:00
|
|
|
let dicomWebConfigCopy,
|
|
|
|
|
qidoConfig,
|
|
|
|
|
wadoConfig,
|
|
|
|
|
qidoDicomWebClient,
|
|
|
|
|
wadoDicomWebClient,
|
|
|
|
|
getAuthrorizationHeader,
|
|
|
|
|
generateWadoHeader;
|
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
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
getAuthrorizationHeader = () => {
|
|
|
|
|
const xhrRequestHeaders = {};
|
|
|
|
|
const authHeaders = userAuthenticationService.getAuthorizationHeader();
|
|
|
|
|
if (authHeaders && authHeaders.Authorization) {
|
|
|
|
|
xhrRequestHeaders.Authorization = authHeaders.Authorization;
|
|
|
|
|
}
|
|
|
|
|
return xhrRequestHeaders;
|
|
|
|
|
};
|
2020-05-11 18:56:46 +02:00
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
generateWadoHeader = () => {
|
|
|
|
|
let authorizationHeader = getAuthrorizationHeader();
|
|
|
|
|
//Generate accept header depending on config params
|
|
|
|
|
let formattedAcceptHeader = utils.generateAcceptHeader(
|
|
|
|
|
dicomWebConfig.acceptHeader,
|
|
|
|
|
dicomWebConfig.requestTransferSyntaxUID,
|
|
|
|
|
dicomWebConfig.omitQuotationForMultipartRequest
|
|
|
|
|
);
|
2022-10-25 16:20:32 +02:00
|
|
|
|
2023-07-20 17:43:05 +02:00
|
|
|
return {
|
|
|
|
|
...authorizationHeader,
|
|
|
|
|
Accept: formattedAcceptHeader,
|
|
|
|
|
};
|
|
|
|
|
};
|
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(),
|
|
|
|
|
};
|
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(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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) {
|
2023-06-20 11:34:20 +02:00
|
|
|
qidoDicomWebClient.headers = getAuthrorizationHeader();
|
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) {
|
2023-06-20 11:34:20 +02:00
|
|
|
qidoDicomWebClient.headers = getAuthrorizationHeader();
|
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) => {
|
2023-06-20 11:34:20 +02:00
|
|
|
qidoDicomWebClient.headers = getAuthrorizationHeader();
|
2023-09-01 22:19:39 +02:00
|
|
|
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
|
|
|
|
|
*/
|
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
|
|
|
},
|
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 }) => {
|
2023-06-20 11:34:20 +02:00
|
|
|
qidoDicomWebClient.headers = getAuthrorizationHeader();
|
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,
|
|
|
|
|
} = {}) => {
|
|
|
|
|
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,
|
2022-02-08 14:17:37 +01:00
|
|
|
madeInClient
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
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-04-28 22:13:47 +02:00
|
|
|
dicom: async (dataset, request) => {
|
2023-06-27 17:44:46 +02:00
|
|
|
wadoDicomWebClient.headers = getAuthrorizationHeader();
|
2023-04-28 22:13:47 +02:00
|
|
|
if (dataset instanceof ArrayBuffer) {
|
|
|
|
|
const options = {
|
|
|
|
|
datasets: [dataset],
|
|
|
|
|
request,
|
|
|
|
|
};
|
|
|
|
|
await wadoDicomWebClient.storeInstances(options);
|
|
|
|
|
} else {
|
|
|
|
|
const meta = {
|
2023-09-01 22:19:39 +02:00
|
|
|
FileMetaInformationVersion: dataset._meta.FileMetaInformationVersion.Value,
|
2023-04-28 22:13:47 +02:00
|
|
|
MediaStorageSOPClassUID: dataset.SOPClassUID,
|
|
|
|
|
MediaStorageSOPInstanceUID: dataset.SOPInstanceUID,
|
|
|
|
|
TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN,
|
|
|
|
|
ImplementationClassUID,
|
|
|
|
|
ImplementationVersionName,
|
|
|
|
|
};
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2023-04-28 22:13:47 +02:00
|
|
|
const denaturalized = denaturalizeDataset(meta);
|
|
|
|
|
const dicomDict = new DicomDict(denaturalized);
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2023-04-28 22:13:47 +02:00
|
|
|
dicomDict.dict = denaturalizeDataset(dataset);
|
2020-06-25 18:21:03 +02:00
|
|
|
|
2023-04-28 22:13:47 +02:00
|
|
|
const part10Buffer = dicomDict.write();
|
|
|
|
|
|
|
|
|
|
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;
|
2023-06-20 11:34:20 +02:00
|
|
|
wadoDicomWebClient.headers = generateWadoHeader();
|
2022-07-27 18:39:04 +02:00
|
|
|
// data is all SOPInstanceUIDs
|
|
|
|
|
const data = await retrieveStudyMetadata(
|
|
|
|
|
wadoDicomWebClient,
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
enableStudyLazyLoad,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
|
|
|
|
sortFunction
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
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
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_retrieveSeriesMetadataAsync: async (
|
|
|
|
|
StudyInstanceUID,
|
|
|
|
|
filters,
|
|
|
|
|
sortCriteria,
|
|
|
|
|
sortFunction,
|
|
|
|
|
madeInClient = false
|
|
|
|
|
) => {
|
|
|
|
|
const enableStudyLazyLoad = true;
|
2023-06-20 11:34:20 +02:00
|
|
|
wadoDicomWebClient.headers = generateWadoHeader();
|
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,
|
|
|
|
|
sortFunction
|
|
|
|
|
);
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
Object.keys(naturalized).forEach(key => {
|
|
|
|
|
const value = naturalized[key];
|
2023-05-29 15:04:49 +02:00
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
// 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) {
|
|
|
|
|
// Provide a method to fetch bulkdata
|
|
|
|
|
value.retrieveBulkData = () => {
|
2023-05-29 15:04:49 +02:00
|
|
|
// handle the scenarios where bulkDataURI is relative path
|
|
|
|
|
fixBulkDataURI(value, naturalized, dicomWebConfig);
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
const options = {
|
|
|
|
|
// 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,
|
|
|
|
|
// The study instance UID is required if the bulkdata uri
|
|
|
|
|
// is relative - that isn't disallowed by DICOMweb, but
|
|
|
|
|
// isn't well specified in the standard, but is needed in
|
|
|
|
|
// any implementation that stores static copies of the metadata
|
|
|
|
|
StudyInstanceUID: naturalized.StudyInstanceUID,
|
|
|
|
|
};
|
2022-11-16 20:19:52 +01:00
|
|
|
// Todo: this needs to be from wado dicom web client
|
2022-07-27 18:39:04 +02:00
|
|
|
return qidoDicomWebClient.retrieveBulkData(options).then(val => {
|
2023-05-10 18:42:48 +02:00
|
|
|
// 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 =
|
2023-09-01 22:19:39 +02:00
|
|
|
(val instanceof Array && val.find(arrayBuffer => arrayBuffer?.byteLength)) ||
|
2023-05-10 18:42:48 +02:00
|
|
|
undefined;
|
2022-07-27 18:39:04 +02:00
|
|
|
value.Value = ret;
|
|
|
|
|
return ret;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return naturalized;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Async load series, store as retrieved
|
|
|
|
|
function storeInstances(instances) {
|
|
|
|
|
const naturalizedInstances = instances.map(addRetrieveBulkData);
|
|
|
|
|
|
|
|
|
|
// Adding instanceMetadata to OHIF MetadataProvider
|
|
|
|
|
naturalizedInstances.forEach((instance, index) => {
|
2022-11-16 20:19:52 +01:00
|
|
|
instance.wadoRoot = dicomWebConfig.wadoRoot;
|
|
|
|
|
instance.wadoUri = dicomWebConfig.wadoUri;
|
|
|
|
|
|
2022-07-27 18:39:04 +02:00
|
|
|
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() {
|
2023-09-01 22:19:39 +02:00
|
|
|
const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient);
|
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);
|
|
|
|
|
|
2022-09-07 17:59:11 +02:00
|
|
|
const seriesDeliveredPromises = seriesPromises.map(promise =>
|
|
|
|
|
promise.then(instances => {
|
|
|
|
|
storeInstances(instances);
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
await Promise.all(seriesDeliveredPromises);
|
|
|
|
|
setSuccessFlag();
|
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
|
|
|
},
|
2021-05-28 20:32:48 +02:00
|
|
|
getImageIdsForInstance({ instance, frame }) {
|
|
|
|
|
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 }) {
|
|
|
|
|
const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params;
|
2023-09-01 22:19:39 +02:00
|
|
|
const queryStudyInstanceUIDs = utils.splitComma(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) {
|
|
|
|
|
implementation.reject = dcm4cheeReject(dicomWebConfig.wadoRoot);
|
2020-08-19 21:27:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return IWebApiDataSource.create(implementation);
|
2020-05-07 15:56:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { createDicomWebApi };
|