UNPKG

dicomweb-client

Version:
1,238 lines (1,198 loc) 84.5 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.DICOMwebClient = {})); })(this, (function (exports) { 'use strict'; function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? Object(arguments[i]) : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys.push.apply(ownKeys, Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } /** * Converts a Uint8Array to a String. * @param {Uint8Array} array that should be converted * @param {Number} offset array offset in case only subset of array items should be extracted (default: 0) * @param {Number} limit maximum number of array items that should be extracted (defaults to length of array) * @returns {String} */ function uint8ArrayToString(arr) { var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var limit = arguments.length > 2 ? arguments[2] : undefined; var itemLimit = limit || arr.length - offset; var str = ''; for (var i = offset; i < offset + itemLimit; i++) { str += String.fromCharCode(arr[i]); } return str; } /** * Converts a String to a Uint8Array. * @param {String} str string that should be converted * @returns {Uint8Array} */ function stringToUint8Array(str) { var arr = new Uint8Array(str.length); for (var i = 0, j = str.length; i < j; i++) { arr[i] = str.charCodeAt(i); } return arr; } /** * Identifies the boundary in a multipart/related message header. * @param {String} header message header * @returns {String} boundary */ function identifyBoundary(header) { var parts = header.split('\r\n'); for (var i = 0; i < parts.length; i++) { if (parts[i].substring(0, 2) === '--') { return parts[i]; } } return null; } /** * Checks whether a given token is contained by a message at a given offset. * @param {Uint8Array} message message content * @param {Uint8Array} token substring that should be present * @param {Number} offset offset in message content from where search should start * @returns {Boolean} whether message contains token at offset */ function containsToken(message, token) { var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; if (offset + token.length > message.length) { return false; } var index = offset; for (var i = 0; i < token.length; i++) { if (token[i] !== message[index]) { return false; } index += 1; } return true; } /** * Finds a given token in a message at a given offset. * @param {Uint8Array} message message content * @param {Uint8Array} token substring that should be found * @param {String} offset message body offset from where search should start * @returns {Boolean} whether message has a part at given offset or not */ function findToken(message, token) { var offset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; var maxSearchLength = arguments.length > 3 ? arguments[3] : undefined; var searchLength = message.length; if (maxSearchLength) { searchLength = Math.min(offset + maxSearchLength, message.length); } for (var i = offset; i < searchLength; i++) { // If the first value of the message matches // the first value of the token, check if // this is the full token. if (message[i] === token[0]) { if (containsToken(message, token, i)) { return i; } } } return -1; } /** * Create a random GUID * * @return {string} */ function guid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); } return "".concat(s4() + s4(), "-").concat(s4(), "-").concat(s4(), "-").concat(s4(), "-").concat(s4()).concat(s4()).concat(s4()); } /** * @typedef {Object} MultipartEncodedData * @property {ArrayBuffer} data The encoded Multipart Data * @property {String} boundary The boundary used to divide pieces of the encoded data */ /** * Encode one or more DICOM datasets into a single body so it can be * sent using the Multipart Content-Type. * * @param {ArrayBuffer[]} datasets Array containing each file to be encoded in the multipart body, passed as ArrayBuffers. * @param {String} [boundary] Optional string to define a boundary between each part of the multipart body. If this is not specified, a random GUID will be generated. * @return {MultipartEncodedData} The Multipart encoded data returned as an Object. This contains both the data itself, and the boundary string used to divide it. */ function multipartEncode(datasets) { var boundary = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : guid(); var contentType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'application/dicom'; var contentTypeString = "Content-Type: ".concat(contentType); var header = "\r\n--".concat(boundary, "\r\n").concat(contentTypeString, "\r\n\r\n"); var footer = "\r\n--".concat(boundary, "--"); var headerArray = stringToUint8Array(header); var footerArray = stringToUint8Array(footer); var headerLength = headerArray.length; var footerLength = footerArray.length; var length = 0; // Calculate the total length for the final array var contentArrays = datasets.map(function (datasetBuffer) { var contentArray = new Uint8Array(datasetBuffer); var contentLength = contentArray.length; length += headerLength + contentLength; return contentArray; }); length += footerLength; // Allocate the array var multipartArray = new Uint8Array(length); // Set the initial header multipartArray.set(headerArray, 0); // Write each dataset into the multipart array var position = 0; contentArrays.forEach(function (contentArray) { multipartArray.set(headerArray, position); multipartArray.set(contentArray, position + headerLength); position += headerLength + contentArray.length; }); multipartArray.set(footerArray, position); return { data: multipartArray.buffer, boundary: boundary }; } /** * Splits the header string into parts and extracts the simple contentType * and transferSyntaxUID, assigning them, plus the headers map into the destination object. * * @param {*} destination * @param {string} headerString */ function addHeaders(destination, headerString) { if (!headerString) { return; } var headerLines = headerString.split('\r\n').filter(Boolean); var headers = new Map(); var transferSyntaxUID = null, contentType = null; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = headerLines[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var line = _step.value; var colon = line.indexOf(':'); if (colon === -1) { continue; } var name = line.substring(0, colon).toLowerCase(); var value = line.substring(colon + 1).trim(); if (headers.has(name)) { headers.get(name).push(value); } else { headers.set(name, [value]); } if (name === 'content-type') { var endSimpleType = value.indexOf(';'); contentType = value.substring(0, endSimpleType === -1 ? value.length : endSimpleType); var transferSyntaxStart = value.indexOf('transfer-syntax='); if (transferSyntaxStart !== -1) { var endTsuid = value.indexOf(';', transferSyntaxStart); transferSyntaxUID = value.substring(transferSyntaxStart + 16, endTsuid === -1 ? value.length : endTsuid); } } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } Object.defineProperty(destination, 'headers', { value: headers }); Object.defineProperty(destination, 'contentType', { value: contentType }); Object.defineProperty(destination, 'transferSyntaxUID', { value: transferSyntaxUID }); } /** * Decode a Multipart encoded ArrayBuffer and return the components as an Array. * * @param {ArrayBuffer} response Data encoded as a 'multipart/related' message * @returns {Uint8Array[]} The content as an array of Uint8Array * Each item shall have a contentType value, and a transferSyntaxUID if available, * as well as the headers Map. See parseHeaders for output. * */ function multipartDecode(response) { // Use the raw data if it is provided in an appropriate format var message = ArrayBuffer.isView(response) ? response : new Uint8Array(response); /* Set a maximum length to search for the header boundaries, otherwise findToken can run for a long time */ var maxSearchLength = 1000; // First look for the multipart mime header var separator = stringToUint8Array('\r\n\r\n'); var headerIndex = findToken(message, separator, 0, maxSearchLength); if (headerIndex === -1) { throw new Error('Response message has no multipart mime header'); } var header = uint8ArrayToString(message, 0, headerIndex); var boundaryString = identifyBoundary(header); if (!boundaryString) { throw new Error('Header of response message does not specify boundary'); } var boundary = stringToUint8Array(boundaryString); var boundaryLength = boundary.length; var components = []; var headers = header.substring(boundary.length + 2); var offset = boundaryLength; // Loop until we cannot find any more boundaries var boundaryIndex; while (boundaryIndex !== -1) { // Search for the next boundary in the message, starting // from the current offset position boundaryIndex = findToken(message, boundary, offset); // If no further boundaries are found, stop here. if (boundaryIndex === -1) { break; } var headerTokenIndex = findToken(message, separator, offset, maxSearchLength); if (headerTokenIndex === -1) { throw new Error('Response message part has no mime header'); } offset = headerTokenIndex + separator.length; // Extract data from response message, excluding "\r\n" var spacingLength = 2; var data = response.slice(offset, boundaryIndex - spacingLength); // TODO - extract header data on a per frame basis. addHeaders(data, headers); // Add the data to the array of results components.push(data); // Move the offset to the end of the current section, // plus the identified boundary offset = boundaryIndex + boundaryLength; } return components; } function isObject(obj) { return _typeof(obj) === 'object' && obj !== null; } function isEmptyObject(obj) { return Object.keys(obj).length === 0 && obj.constructor === Object; } function areValidRequestHooks(requestHooks) { var isValid = Array.isArray(requestHooks) && requestHooks.every(function (requestHook) { return typeof requestHook === 'function' && requestHook.length === 2; }); if (!isValid) { console.warn('Request hooks should have the following signature: ' + 'function requestHook(request, metadata) { return request; }'); } return isValid; } /** * @typedef {Object} Request * @property {XMLHttpRequest} [instance] - If specified, the request to use, otherwise one will be created. * @property {function(ProgressEvent):void} [progressCallback] - A callback function to handle progress events. * @property {string} [responseType] - The response type of the request. * @property {boolean} [withCredentials] - Whether to include credentials in the request. */ /** * @param {Request} request - Request options. */ var getRequestOptions = function getRequestOptions() { var request = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return { instance: request.instance || new XMLHttpRequest(), progressCallback: request.progressCallback || false, withCredentials: request.withCredentials || false, responseType: request.responseType }; }; var getFirstResult = function getFirstResult(result) { return result[0]; }; var MEDIATYPES = { DICOM: 'application/dicom', DICOM_JSON: 'application/dicom+json', OCTET_STREAM: 'application/octet-stream', PDF: 'application/pdf', JPEG: 'image/jpeg', PNG: 'image/png' }; /** * debugLog is a function that can be called with console.log arguments, and will * be conditionally displayed, only when debug logging is enabled. */ var debugLog = function debugLog() {}; /** * @typedef { import("../types/types").InstanceMetadata } InstanceMetadata */ /** * A callback with the request instance and metadata information * of the currently request being executed that should necessarily * return the given request optionally modified. * @typedef {function} RequestHook * @param {XMLHttpRequest} request - The original XMLHttpRequest instance. * @param {object} metadata - The metadata used by the request. */ /** * Class for interacting with DICOMweb RESTful services. */ var DICOMwebClient = /*#__PURE__*/function () { /** * @constructor * @param {Object} options * @param {String} options.url - URL of the DICOMweb RESTful Service endpoint * @param {String=} options.qidoURLPrefix - URL path prefix for QIDO-RS * @param {String=} options.wadoURLPrefix - URL path prefix for WADO-RS * @param {String=} options.stowURLPrefix - URL path prefix for STOW-RS * @param {String=} options.username - Username * @param {String=} options.password - Password * @param {Object=} options.headers - HTTP headers * @param {Array.<RequestHook>=} options.requestHooks - Request hooks. * @param {Object=} options.verbose - print to console request warnings and errors, default true * @param {Object=} options.debug - print to the console debug level information/status updates. * @param {boolean|String} options.singlepart - retrieve singlepart for the named types. * The available types are: bulkdata, video, image. true means all. */ function DICOMwebClient(options) { _classCallCheck(this, DICOMwebClient); this.baseURL = options.url; if (!this.baseURL) { console.error('no DICOMweb base url provided - calls that require a URL will fail'); } if ('username' in options) { this.username = options.username; if (!('password' in options)) { console.error('no password provided to authenticate with DICOMweb service'); } this.password = options.password; } if ('qidoURLPrefix' in options) { debugLog("use URL prefix for QIDO-RS: ".concat(options.qidoURLPrefix)); this.qidoURL = "".concat(this.baseURL, "/").concat(options.qidoURLPrefix); } else { this.qidoURL = this.baseURL; } if ('wadoURLPrefix' in options) { debugLog("use URL prefix for WADO-RS: ".concat(options.wadoURLPrefix)); this.wadoURL = "".concat(this.baseURL, "/").concat(options.wadoURLPrefix); } else { this.wadoURL = this.baseURL; } if ('stowURLPrefix' in options) { debugLog("use URL prefix for STOW-RS: ".concat(options.stowURLPrefix)); this.stowURL = "".concat(this.baseURL, "/").concat(options.stowURLPrefix); } else { this.stowURL = this.baseURL; } if (options.singlepart) { debugLog('use singlepart', options.singlepart); this.singlepart = options.singlepart === true ? 'bulkdata,video,image' : options.singlepart; } else { this.singlepart = ''; } if ('requestHooks' in options) { this.requestHooks = options.requestHooks; } // Headers to pass to requests. this.headers = options.headers || {}; // Optional error interceptor callback to handle any failed request. this.errorInterceptor = options.errorInterceptor || function () { return undefined; }; // Verbose - print to console request warnings and errors, default true this.verbose = options.verbose !== false; this.setDebug(options.debug); } /** * Allows setting the debug log information. * Note this is different from verbose in that verbose is whether to include warning/error information, defaulting to true * * @param {boolean} debugLevel * @param {function} debugLogFunction to call with the debug output arguments. */ _createClass(DICOMwebClient, [{ key: "setDebug", value: function setDebug() { var debugLevel = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; var debugLogFunction = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; this.debugLevel = !!debugLevel; debugLog = debugLogFunction || debugLevel ? console.log : function () {}; } /** * Gets debug flag * * @returns true if debug logging is enabled */ }, { key: "getDebug", value: function getDebug() { return this.debugLevel; } /** * Sets verbose flag. * * @param {Boolean} verbose */ }, { key: "setVerbose", value: function setVerbose(verbose) { this.verbose = verbose; } /** * Gets verbose flag. * * @return {Boolean} verbose */ }, { key: "getVerbose", value: function getVerbose() { return this.verbose; } }, { key: "_httpRequest", /** * Performs an HTTP request. * * @param {String} url * @param {String} method * @param {Object} headers * @param {Request} [request] - Request Options * @param {Array} [request.data] - Data that should be stored * @return {*} * @private */ value: function _httpRequest(url, method) { var _this = this; var headers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var request = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var errorInterceptor = this.errorInterceptor, requestHooks = this.requestHooks; return new Promise(function (resolve, reject) { var requestInstance = request.instance ? request.instance : new XMLHttpRequest(); requestInstance.open(method, url, true); if (request.responseType) { requestInstance.responseType = request.responseType; } if (_typeof(headers) === 'object') { Object.keys(headers).forEach(function (key) { requestInstance.setRequestHeader(key, headers[key]); }); } // now add custom headers from the user // (e.g. access tokens) var userHeaders = _this.headers; Object.keys(userHeaders).forEach(function (key) { requestInstance.setRequestHeader(key, userHeaders[key]); }); // Event triggered when upload starts requestInstance.onloadstart = function onloadstart() { debugLog('upload started: ', url); }; // Event triggered when upload ends requestInstance.onloadend = function onloadend() { debugLog('upload finished'); }; // Handle response message requestInstance.onreadystatechange = function () { if (requestInstance.readyState === 4) { if (requestInstance.status === 200) { var contentType = requestInstance.getResponseHeader('Content-Type'); var _headers = requestInstance.getAllResponseHeaders(); // Automatically distinguishes between multipart and singlepart in an array buffer, and // converts them into a consistent type. if (contentType && contentType.indexOf('multipart') !== -1) { resolve(multipartDecode(requestInstance.response)); } else if (requestInstance.responseType === 'arraybuffer') { addHeaders(requestInstance.response, _headers); resolve([requestInstance.response]); } else { resolve(requestInstance.response); } } else if (requestInstance.status === 202) { if (_this.verbose) { console.warn('some resources already existed: ', requestInstance); } resolve(requestInstance.response); } else if (requestInstance.status === 204) { if (_this.verbose) { console.warn('empty response for request: ', requestInstance); } resolve([]); } else { var error = new Error('request failed'); error.request = requestInstance; error.response = requestInstance.response; error.status = requestInstance.status; if (_this.verbose) { console.error('request failed: ', requestInstance); console.error(error); console.error(error.response); } errorInterceptor(error); reject(error); } } }; // Event triggered while download progresses if (typeof request.progressCallback === 'function') { requestInstance.onprogress = request.progressCallback; } if (requestHooks && areValidRequestHooks(requestHooks)) { var combinedHeaders = Object.assign({}, headers, _this.headers); var metadata = { method: method, url: url, headers: combinedHeaders }; var pipeRequestHooks = function pipeRequestHooks(functions) { return function (args) { return functions.reduce(function (props, fn) { return fn(props, metadata); }, args); }; }; var pipedRequest = pipeRequestHooks(requestHooks); requestInstance = pipedRequest(requestInstance); } // Add withCredentials to request if needed if (request.withCredentials) { requestInstance.withCredentials = true; } if (request.data) { requestInstance.send(request.data); } else { requestInstance.send(); } }); } /** * Performs an HTTP GET request. * * @param {String} url * @param {Object} headers * @param {Request} request - Request Options * @return {*} * @private */ }, { key: "_httpGet", value: function _httpGet(url, headers, request) { return this._httpRequest(url, 'get', headers, request); } /** * Performs an HTTP GET request that accepts a message with application/json * media type. * * @param {String} url * @param {Object} params * @param {Request} request - Request Options * @return {*} * @private */ }, { key: "_httpGetApplicationJson", value: function _httpGetApplicationJson(url) { var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var request = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var urlWithQueryParams = url; if (_typeof(params) === 'object') { if (!isEmptyObject(params)) { urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); } } var headers = { Accept: MEDIATYPES.DICOM_JSON }; request.responseType = 'json'; return this._httpGet(urlWithQueryParams, headers, request); } /** * Performs an HTTP GET request that accepts a message with application/pdf * media type. * * @param {String} url * @param {Object} params * @param {Request} request - Request Options * @return {*} * @private */ }, { key: "_httpGetApplicationPdf", value: function _httpGetApplicationPdf(url) { var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var request = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var urlWithQueryParams = url; if (_typeof(params) === 'object') { if (!isEmptyObject(params)) { urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); } } var headers = { Accept: MEDIATYPES.PDF }; request.responseType = 'json'; return this._httpGet(urlWithQueryParams, headers, request); } /** * Performs an HTTP GET request that accepts a message with an image media type. * * @param {String} url * @param {Object[]} mediaTypes * @param {Object} params * @param {Request} request - Request Options * @return {*} * @private */ }, { key: "_httpGetImage", value: function _httpGetImage(url, mediaTypes) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var request = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var urlWithQueryParams = url; if (_typeof(params) === 'object') { if (!isEmptyObject(params)) { urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); } } var supportedMediaTypes = ['image/', 'image/*', 'image/jpeg', 'image/jp2', 'image/gif', 'image/png']; var acceptHeaderFieldValue = DICOMwebClient._buildAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes); var headers = { Accept: acceptHeaderFieldValue }; request.responseType = 'arraybuffer'; return this._httpGet(urlWithQueryParams, headers, request); } /** * Performs an HTTP GET request that accepts a message with a text media type. * * @param {String} url * @param {Object[]} mediaTypes * @param {Object} params * @param {Request} request - Request Options * @return {*} * @private */ }, { key: "_httpGetText", value: function _httpGetText(url, mediaTypes) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var request = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var urlWithQueryParams = url; if (_typeof(params) === 'object') { if (!isEmptyObject(params)) { urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); } } var supportedMediaTypes = ['text/', 'text/*', 'text/html', 'text/plain', 'text/rtf', 'text/xml']; var acceptHeaderFieldValue = DICOMwebClient._buildAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes); var headers = { Accept: acceptHeaderFieldValue }; request.responseType = 'arraybuffer'; return this._httpGet(urlWithQueryParams, headers, request); } /** * Performs an HTTP GET request that accepts a message with a video media type. * * @param {String} url * @param {Object[]} mediaTypes * @param {Object} params * @param {Request} request - Request Options * @return {*} * @private */ }, { key: "_httpGetVideo", value: function _httpGetVideo(url, mediaTypes) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var request = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var urlWithQueryParams = url; if (_typeof(params) === 'object') { if (!isEmptyObject(params)) { urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); } } var supportedMediaTypes = ['video/', 'video/*', 'video/mpeg', 'video/mp4', 'video/H265']; var acceptHeaderFieldValue = DICOMwebClient._buildAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes); var headers = { Accept: acceptHeaderFieldValue }; request.responseType = 'arraybuffer'; return this._httpGet(urlWithQueryParams, headers, request); } /** * Asserts that a given media type is valid. * * @params {String} mediaType media type */ }, { key: "_httpGetMultipartImage", /** * Performs an HTTP GET request that accepts a multipart message with an image media type. * * @param {String} url - Unique resource locator * @param {Object[]} mediaTypes - Acceptable media types and optionally the UIDs of the corresponding transfer syntaxes * @param {Array} byteRange - Start and end of byte range * @param {Object} params - Additional HTTP GET query parameters * @param {Boolean} rendered - Whether resource should be requested using rendered media types * @param {Request} request - Request Options * @private * @returns {Promise<Array>} Content of HTTP message body parts */ value: function _httpGetMultipartImage(url, mediaTypes, byteRange, params) { var rendered = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var request = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {}; var headers = {}; var supportedMediaTypes; if (rendered) { supportedMediaTypes = ['image/jpeg', 'image/gif', 'image/png', 'image/jp2', 'image/*']; } else { supportedMediaTypes = { '1.2.840.10008.1.2.5': ['image/x-dicom-rle'], '1.2.840.10008.1.2.4.50': ['image/jpeg'], '1.2.840.10008.1.2.4.51': ['image/jpeg'], '1.2.840.10008.1.2.4.57': ['image/jpeg'], '1.2.840.10008.1.2.4.70': ['image/jpeg'], '1.2.840.10008.1.2.4.80': ['image/x-jls', 'image/jls'], '1.2.840.10008.1.2.4.81': ['image/x-jls', 'image/jls'], '1.2.840.10008.1.2.4.90': ['image/jp2'], '1.2.840.10008.1.2.4.91': ['image/jp2'], '1.2.840.10008.1.2.4.92': ['image/jpx'], '1.2.840.10008.1.2.4.93': ['image/jpx'], '*': ['image/*'] }; if (byteRange) { headers.Range = DICOMwebClient._buildRangeHeaderFieldValue(byteRange); } } headers.Accept = DICOMwebClient._buildMultipartAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes); request.responseType = 'arraybuffer'; return this._httpGet(url, headers, request); } /** * Performs an HTTP GET request that accepts a multipart message with a video media type. * * @param {String} url - Unique resource locator * @param {Object[]} mediaTypes - Acceptable media types and optionally the UIDs of the corresponding transfer syntaxes * @param {Array} byteRange - Start and end of byte range * @param {Object} params - Additional HTTP GET query parameters * @param {Boolean} rendered - Whether resource should be requested using rendered media types * @param {Request} request - Request Options * @private * @returns {Promise<Array>} Content of HTTP message body parts */ }, { key: "_httpGetMultipartVideo", value: function _httpGetMultipartVideo(url, mediaTypes, byteRange, params) { var rendered = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; var request = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {}; var headers = {}; var supportedMediaTypes; if (rendered) { supportedMediaTypes = ['video/', 'video/*', 'video/mpeg2', 'video/mp4', 'video/H265']; } else { supportedMediaTypes = { '1.2.840.10008.1.2.4.100': ['video/mpeg2'], '1.2.840.10008.1.2.4.101': ['video/mpeg2'], '1.2.840.10008.1.2.4.102': ['video/mp4'], '1.2.840.10008.1.2.4.103': ['video/mp4'], '1.2.840.10008.1.2.4.104': ['video/mp4'], '1.2.840.10008.1.2.4.105': ['video/mp4'], '1.2.840.10008.1.2.4.106': ['video/mp4'] }; if (byteRange) { headers.Range = DICOMwebClient._buildRangeHeaderFieldValue(byteRange); } } headers.Accept = DICOMwebClient._buildMultipartAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes); request.responseType = 'arraybuffer'; return this._httpGet(url, headers, request); } /** * Performs an HTTP GET request that accepts a multipart message * with a application/dicom media type. * * @param {String} url - Unique resource locator * @param {Object[]} mediaTypes - Acceptable media types and optionally the UIDs of the corresponding transfer syntaxes * @param {Object} params - Additional HTTP GET query parameters * @param {Request} request - request options * @private * @returns {Promise<Array>} Content of HTTP message body parts */ }, { key: "_httpGetMultipartApplicationDicom", value: function _httpGetMultipartApplicationDicom(url, mediaTypes, params) { var request = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var headers = {}; var defaultMediaType = 'application/dicom'; var supportedMediaTypes = { '1.2.840.10008.1.2.1': [defaultMediaType], '1.2.840.10008.1.2.5': [defaultMediaType], '1.2.840.10008.1.2.4.50': [defaultMediaType], '1.2.840.10008.1.2.4.51': [defaultMediaType], '1.2.840.10008.1.2.4.57': [defaultMediaType], '1.2.840.10008.1.2.4.70': [defaultMediaType], '1.2.840.10008.1.2.4.80': [defaultMediaType], '1.2.840.10008.1.2.4.81': [defaultMediaType], '1.2.840.10008.1.2.4.90': [defaultMediaType], '1.2.840.10008.1.2.4.91': [defaultMediaType], '1.2.840.10008.1.2.4.92': [defaultMediaType], '1.2.840.10008.1.2.4.93': [defaultMediaType], '1.2.840.10008.1.2.4.100': [defaultMediaType], '1.2.840.10008.1.2.4.101': [defaultMediaType], '1.2.840.10008.1.2.4.102': [defaultMediaType], '1.2.840.10008.1.2.4.103': [defaultMediaType], '1.2.840.10008.1.2.4.104': [defaultMediaType], '1.2.840.10008.1.2.4.105': [defaultMediaType], '1.2.840.10008.1.2.4.106': [defaultMediaType] }; var acceptableMediaTypes = mediaTypes; if (!mediaTypes) { acceptableMediaTypes = [{ mediaType: defaultMediaType }]; } headers.Accept = DICOMwebClient._buildMultipartAcceptHeaderFieldValue(acceptableMediaTypes, supportedMediaTypes); request.responseType = 'arraybuffer'; return this._httpGet(url, headers, request); } /** * Performs an HTTP GET request that accepts a multipart message * with a application/octet-stream, OR any of the equivalencies for that (eg * application/pdf etc) * * @param {String} url - Unique resource locator * @param {Object[]} mediaTypes - Acceptable media types and optionally the UIDs of the corresponding transfer syntaxes * @param {Array} byteRange start and end of byte range * @param {Object} params - Additional HTTP GET query parameters * @param {Request} request - Request Options * @private * @returns {Promise<Array>} Content of HTTP message body parts */ }, { key: "_httpGetMultipartApplicationOctetStream", value: function _httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange, params) { var request = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {}; var headers = {}; var defaultMediaType = 'application/octet-stream'; var supportedMediaTypes = { '1.2.840.10008.1.2.1': _toConsumableArray(Object.values(MEDIATYPES)) }; var acceptableMediaTypes = mediaTypes; if (!mediaTypes) { acceptableMediaTypes = [{ mediaType: defaultMediaType }]; } if (byteRange) { headers.Range = DICOMwebClient._buildRangeHeaderFieldValue(byteRange); } headers.Accept = DICOMwebClient._buildMultipartAcceptHeaderFieldValue(acceptableMediaTypes, supportedMediaTypes); request.responseType = 'arraybuffer'; return this._httpGet(url, headers, request); } /** * Performs an HTTP POST request. * * @param {String} url - Unique resource locator * @param {Object} headers - HTTP header fields * @param {Array} data - Data that should be stored * @param {Request} request - Request Options * @private * @returns {Promise} Response */ }, { key: "_httpPost", value: function _httpPost(url, headers, data, request) { return this._httpRequest(url, 'post', headers, _objectSpread({}, request, { data: data })); } /** * Performs an HTTP POST request with content-type application/dicom+json. * * @param {String} url - Unique resource locator * @param {Object} headers - HTTP header fields * @param {Array} data - Data that should be stored * @param {Request} request - Request Options * @private * @returns {Promise} Response */ }, { key: "_httpPostApplicationJson", value: function _httpPostApplicationJson(url, data, request) { var headers = { 'Content-Type': MEDIATYPES.DICOM_JSON }; return this._httpPost(url, headers, data, request); } /** * Parses media type and extracts its type and subtype. * * @param {String} mediaType - HTTP media type (e.g. image/jpeg) * @private * @returns {String[]} Media type and subtype */ }, { key: "searchForStudies", /** * Searches for DICOM studies. * * @param {Object} options * @param {Object} [options.queryParams] - HTTP query parameters * @param {Request} request - Request Options * @return {Object[]} Study representations (http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_6.7.html#table_6.7.1-2) */ value: function searchForStudies() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; debugLog('search for studies'); var url = "".concat(this.qidoURL, "/studies"); if ('queryParams' in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } var request = getRequestOptions(options.request); return this._httpGetApplicationJson(url, {}, request); } /** * Retrieves metadata for a DICOM study. * * @param {Object} options * @param {String} options.studyInstanceUID - Study Instance UID * @param {Request} options.request - Request Options * @returns {Promise<InstanceMetadata[]>} Metadata elements in DICOM JSON format for each instance belonging to the study */ }, { key: "retrieveStudyMetadata", value: function retrieveStudyMetadata(options) { if (!('studyInstanceUID' in options)) { throw new Error('Study Instance UID is required for retrieval of study metadata'); } debugLog("retrieve metadata of study ".concat(options.studyInstanceUID)); var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/metadata"); var request = getRequestOptions(options.request); return this._httpGetApplicationJson(url, {}, request); } /** * Searches for DICOM series. * * @param {Object} options * @param {String} [options.studyInstanceUID] - Study Instance UID * @param {Object} [options.queryParams] - HTTP query parameters * @param {Request} request - Request Options * @returns {Object[]} Series representations (http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_6.7.html#table_6.7.1-2a) */ }, { key: "searchForSeries", value: function searchForSeries() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var url = this.qidoURL; if ('studyInstanceUID' in options) { debugLog("search series of study ".concat(options.studyInstanceUID)); url += "/studies/".concat(options.studyInstanceUID); } url += '/series'; if ('queryParams' in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } var request = getRequestOptions(options.request); return this._httpGetApplicationJson(url, {}, request); } /** * Retrieves metadata for a DICOM series. * * @param {Object} options * @param {String} options.studyInstanceUID - Study Instance UID * @param {String} options.seriesInstanceUID - Series Instance UID * @param {Request} options.request - Request Options * @returns {Promise<InstanceMetadata[]>} Metadata elements in DICOM JSON format for each instance belonging to the series */ }, { key: "retrieveSeriesMetadata", value: function retrieveSeriesMetadata(options) { if (!('studyInstanceUID' in options)) { throw new Error('Study Instance UID is required for retrieval of series metadata'); } if (!('seriesInstanceUID' in options)) { throw new Error('Series Instance UID is required for retrieval of series metadata'); } debugLog("retrieve metadata of series ".concat(options.seriesInstanceUID)); var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/metadata"); var request = getRequestOptions(options.request); return this._httpGetApplicationJson(url, {}, request); } /** * Searches for DICOM Instances. * * @param {Object} options * @param {String} [options.studyInstanceUID] - Study Instance UID * @param {String} [options.seriesInstanceUID] - Series Instance UID * @param {Object} [options.queryParams] - HTTP query parameters * @param {Request} [options.request] - Request Options * @returns {Object[]} Instance representations (http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_6.7.html#table_6.7.1-2b) */ }, { key: "searchForInstances", value: function searchForInstances() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var url = this.qidoURL; if ('studyInstanceUID' in options) { url += "/studies/".concat(options.studyInstanceUID); if ('seriesInstanceUID' in options) { debugLog("search for instances of series ".concat(options.seriesInstanceUID)); url += "/series/".concat(options.seriesInstanceUID); } else { debugLog("search for instances of study ".concat(options.studyInstanceUID)); } } else { debugLog('search for instances'); } url += '/instances'; if ('queryParams' in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } var request = getRequestOptions(options.request); return this._httpGetApplicationJson(url, {}, request); } /** Returns a WADO-URI URL for an instance * * @param {Object} options * @param {String} options.studyInstanceUID - Study Instance UID * @param {String} options.seriesInstanceUID - Series Instance UID * @param {String} options.sopInstanceUID - SOP Instance UID * @returns {String} WADO-URI URL */ }, { key: "buildInstanceWadoURIUrl", value: function buildInstanceWadoURIUrl(options) { if (!('studyInstanceUID' in options)) { throw new Error('Study Instance UID is required.'); } if (!('seriesInstanceUID' in options)) { throw new Error('Series Instance UID is required.'); } if (!('sopInstanceUID' in options)) { throw new Error('SOP Instance UID is required.'); } var contentType = options.contentType || MEDIATYPES.DICOM; var transferSyntax = options.transferSyntax || '*';