UNPKG

dicom-microscopy-viewer-changed

Version:
1,404 lines (1,189 loc) 74.5 kB
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _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, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); } /** * 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].substr(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 + footerLength; return contentArray; }); // 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 }; } /** * Decode a Multipart encoded ArrayBuffer and return the components as an Array. * * @param {ArrayBuffer} response Data encoded as a 'multipart/related' message * @returns {Array} The content */ function multipartDecode(response) { var message = 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 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); // 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; } 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" }; /** * 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 */ function DICOMwebClient(options) { _classCallCheck(this, DICOMwebClient); this.baseURL = options.url; if (!this.baseURL) { console.error("no DICOMweb base url provided - calls 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) { console.log("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) { console.log("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) { console.log("use URL prefix for STOW-RS: ".concat(options.stowURLPrefix)); this.stowURL = "".concat(this.baseURL, "/").concat(options.stowURLPrefix); } else { this.stowURL = this.baseURL; } 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 () {}; // Verbose - print to console request warnings and errors, default true this.verbose = options.verbose === false ? false : true; } /** * Sets verbose flag. * * @param {Boolean} verbose */ _createClass(DICOMwebClient, [{ 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 {Object} options * @param {Array.<RequestHook>} options.requestHooks - Request hooks. * @return {*} * @private */ value: function _httpRequest(url, method) { var _this = this; var headers = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; var errorInterceptor = this.errorInterceptor, requestHooks = this.requestHooks; return new Promise(function (resolve, reject) { var request = new XMLHttpRequest(); request.open(method, url, true); if ("responseType" in options) { request.responseType = options.responseType; } if (_typeof(headers) === "object") { Object.keys(headers).forEach(function (key) { request.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) { request.setRequestHeader(key, userHeaders[key]); }); // Event triggered when upload starts request.onloadstart = function onloadstart() {// console.log('upload started: ', url) }; // Event triggered when upload ends request.onloadend = function onloadend() {// console.log('upload finished') }; // Handle response message request.onreadystatechange = function () { if (request.readyState === 4) { if (request.status === 200) { resolve(request.response); } else if (request.status === 202) { if (_this.verbose) { console.warn("some resources already existed: ", request); } resolve(request.response); } else if (request.status === 204) { if (_this.verbose) { console.warn("empty response for request: ", request); } resolve([]); } else { var error = new Error("request failed"); error.request = request; error.response = request.response; error.status = request.status; if (_this.verbose) { console.error("request failed: ", request); console.error(error); console.error(error.response); } errorInterceptor(error); reject(error); } } }; // Event triggered while download progresses if ("progressCallback" in options) { if (typeof options.progressCallback === "function") { request.onprogress = options.progressCallback; } } if (requestHooks && areValidRequestHooks(requestHooks)) { var _headers = Object.assign({}, _headers, _this.headers); var metadata = { method: method, url: url, headers: _headers }; var pipeRequestHooks = function pipeRequestHooks(functions) { return function (args) { return functions.reduce(function (args, fn) { return fn(args, metadata); }, args); }; }; var pipedRequest = pipeRequestHooks(requestHooks); request = pipedRequest(request); } // Add withCredentials to request if needed if ("withCredentials" in options) { if (options.withCredentials) { request.withCredentials = true; } } if ("data" in options) { request.send(options.data); } else { request.send(); } }); } /** * Performs an HTTP GET request. * * @param {String} url * @param {Object} headers * @param {Object} responseType * @param {Function} progressCallback * @return {*} * @private */ }, { key: "_httpGet", value: function _httpGet(url, headers, responseType, progressCallback, withCredentials) { return this._httpRequest(url, "get", headers, { responseType: responseType, progressCallback: progressCallback, withCredentials: withCredentials }); } /** * Performs an HTTP GET request that accepts a message with application/json * media type. * * @param {String} url * @param {Object} params * @param {Function} progressCallback * @return {*} * @private */ }, { key: "_httpGetApplicationJson", value: function _httpGetApplicationJson(url) { var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var progressCallback = arguments.length > 2 ? arguments[2] : undefined; var withCredentials = arguments.length > 3 ? arguments[3] : undefined; var urlWithQueryParams = url; if (_typeof(params) === "object") { if (!isEmptyObject(params)) { urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); } } var headers = { Accept: MEDIATYPES.DICOM_JSON }; var responseType = "json"; return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback, withCredentials); } /** * Performs an HTTP GET request that accepts a message with application/pdf * media type. * * @param {String} url * @param {Object} params * @param {Function} progressCallback * @return {*} * @private */ }, { key: "_httpGetApplicationPdf", value: function _httpGetApplicationPdf(url) { var params = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var progressCallback = arguments.length > 2 ? arguments[2] : undefined; var withCredentials = arguments.length > 3 ? arguments[3] : undefined; var urlWithQueryParams = url; if (_typeof(params) === "object") { if (!isEmptyObject(params)) { urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); } } var headers = { Accept: MEDIATYPES.PDF }; var responseType = "json"; return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback, withCredentials); } /** * Performs an HTTP GET request that accepts a message with an image media type. * * @param {String} url * @param {Object[]} mediaTypes * @param {Object} params * @param {Function} progressCallback * @return {*} * @private */ }, { key: "_httpGetImage", value: function _httpGetImage(url, mediaTypes) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var progressCallback = arguments.length > 3 ? arguments[3] : undefined; var withCredentials = arguments.length > 4 ? arguments[4] : undefined; 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 }; var responseType = "arraybuffer"; return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback, withCredentials); } /** * Performs an HTTP GET request that accepts a message with a text media type. * * @param {String} url * @param {Object[]} mediaTypes * @param {Object} params * @param {Function} progressCallback * @return {*} * @private */ }, { key: "_httpGetText", value: function _httpGetText(url, mediaTypes) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var progressCallback = arguments.length > 3 ? arguments[3] : undefined; var withCredentials = arguments.length > 4 ? arguments[4] : undefined; 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 }; var responseType = "arraybuffer"; return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback, withCredentials); } /** * Performs an HTTP GET request that accepts a message with a video media type. * * @param {String} url * @param {Object[]} mediaTypes * @param {Object} params * @param {Function} progressCallback * @return {*} * @private */ }, { key: "_httpGetVideo", value: function _httpGetVideo(url, mediaTypes) { var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var progressCallback = arguments.length > 3 ? arguments[3] : undefined; var withCredentials = arguments.length > 4 ? arguments[4] : undefined; 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 }; var responseType = "arraybuffer"; return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback, withCredentials); } /** * 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 {Function} progressCallback * @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 progressCallback = arguments.length > 5 ? arguments[5] : undefined; var withCredentials = arguments.length > 6 ? arguments[6] : undefined; var headers = {}; var supportedMediaTypes; if (rendered) { supportedMediaTypes = ["image/jpeg", "image/gif", "image/png", "image/jp2"]; } 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"] }; if (byteRange) { headers.Range = DICOMwebClient._buildRangeHeaderFieldValue(byteRange); } } headers.Accept = DICOMwebClient._buildMultipartAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes); return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then(multipartDecode); } /** * 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 {Function} progressCallback * @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 progressCallback = arguments.length > 5 ? arguments[5] : undefined; var withCredentials = arguments.length > 6 ? arguments[6] : undefined; 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); return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then(multipartDecode); } /** * 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 {Function} progressCallback * @private * @returns {Promise<Array>} Content of HTTP message body parts */ }, { key: "_httpGetMultipartApplicationDicom", value: function _httpGetMultipartApplicationDicom(url, mediaTypes, params, progressCallback, withCredentials) { 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); return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then(multipartDecode); } /** * Performs an HTTP GET request that accepts a multipart message with a application/octet-stream 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 {Function} progressCallback * @private * @returns {Promise<Array>} Content of HTTP message body parts */ }, { key: "_httpGetMultipartApplicationOctetStream", value: function _httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange, params, progressCallback, withCredentials) { var headers = {}; var defaultMediaType = "application/octet-stream"; var supportedMediaTypes = { "1.2.840.10008.1.2.1": [defaultMediaType] }; var acceptableMediaTypes = mediaTypes; if (!mediaTypes) { acceptableMediaTypes = [{ mediaType: defaultMediaType }]; } if (byteRange) { headers.Range = DICOMwebClient._buildRangeHeaderFieldValue(byteRange); } headers.Accept = DICOMwebClient._buildMultipartAcceptHeaderFieldValue(acceptableMediaTypes, supportedMediaTypes); return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then(multipartDecode); } /** * 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 {Function} progressCallback * @private * @returns {Promise} Response */ }, { key: "_httpPost", value: function _httpPost(url, headers, data, progressCallback, withCredentials) { return this._httpRequest(url, "post", headers, { data: data, progressCallback: progressCallback, withCredentials: withCredentials }); } /** * 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 {Function} progressCallback * @private * @returns {Promise} Response */ }, { key: "_httpPostApplicationJson", value: function _httpPostApplicationJson(url, data, progressCallback, withCredentials) { var headers = { "Content-Type": MEDIATYPES.DICOM_JSON }; return this._httpPost(url, headers, data, progressCallback, withCredentials); } /** * 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 * @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] : {}; console.log("search for studies"); var withCredentials = false; var url = "".concat(this.qidoURL, "/studies"); if ("queryParams" in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** * Retrieves metadata for a DICOM study. * * @param {Object} options * @param {Object} studyInstanceUID - Study Instance UID * @returns {Object[]} 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"); } console.log("retrieve metadata of study ".concat(options.studyInstanceUID)); var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/metadata"); var withCredentials = false; if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** * Searches for DICOM series. * * @param {Object} options * @param {Object} [options.studyInstanceUID] - Study Instance UID * @param {Object} [options.queryParams] - HTTP query parameters * @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) { console.log("search series of study ".concat(options.studyInstanceUID)); url += "/studies/".concat(options.studyInstanceUID); } url += "/series"; if ("queryParams" in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } var withCredentials = false; if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** * Retrieves metadata for a DICOM series. * * @param {Object} options * @param {Object} options.studyInstanceUID - Study Instance UID * @param {Object} options.seriesInstanceUID - Series Instance UID * @returns {Object[]} 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"); } console.log("retrieve metadata of series ".concat(options.seriesInstanceUID)); var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/metadata"); var withCredentials = false; if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** * Searches for DICOM Instances. * * @param {Object} options * @param {Object} [options.studyInstanceUID] - Study Instance UID * @param {Object} [options.seriesInstanceUID] - Series Instance UID * @param {Object} [options.queryParams] - HTTP query parameters * @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; var withCredentials = false; if ("studyInstanceUID" in options) { url += "/studies/".concat(options.studyInstanceUID); if ("seriesInstanceUID" in options) { console.log("search for instances of series ".concat(options.seriesInstanceUID)); url += "/series/".concat(options.seriesInstanceUID); } else { console.log("search for instances of study ".concat(options.studyInstanceUID)); } } else { console.log("search for instances"); } url += "/instances"; if ("queryParams" in options) { url += DICOMwebClient._parseQueryParameters(options.queryParams); } if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** Returns a WADO-URI URL for an instance * * @param {Object} options * @param {Object} options.studyInstanceUID - Study Instance UID * @param {Object} options.seriesInstanceUID - Series Instance UID * @param {Object} 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 || "*"; var params = []; params.push("requestType=WADO"); params.push("studyUID=".concat(options.studyInstanceUID)); params.push("seriesUID=".concat(options.seriesInstanceUID)); params.push("objectUID=".concat(options.sopInstanceUID)); params.push("contentType=".concat(contentType)); params.push("transferSyntax=".concat(transferSyntax)); var paramString = params.join("&"); return "".concat(this.wadoURL, "?").concat(paramString); } /** * Retrieves metadata for a DICOM Instance. * * @param {Object} options object * @param {String} options.studyInstanceUID - Study Instance UID * @param {String} options.seriesInstanceUID - Series Instance UID * @param {String} options.sopInstanceUID - SOP Instance UID * @returns {Object} metadata elements in DICOM JSON format */ }, { key: "retrieveInstanceMetadata", value: function retrieveInstanceMetadata(options) { if (!("studyInstanceUID" in options)) { throw new Error("Study Instance UID is required for retrieval of instance metadata"); } if (!("seriesInstanceUID" in options)) { throw new Error("Series Instance UID is required for retrieval of instance metadata"); } if (!("sopInstanceUID" in options)) { throw new Error("SOP Instance UID is required for retrieval of instance metadata"); } console.log("retrieve metadata of instance ".concat(options.sopInstanceUID)); var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID, "/metadata"); var withCredentials = false; if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } return this._httpGetApplicationJson(url, {}, false, withCredentials); } /** * Retrieves frames for a DICOM Instance. * @param {Object} options options object * @param {String} options.studyInstanceUID - Study Instance UID * @param {String} options.seriesInstanceUID - Series Instance UID * @param {String} options.sopInstanceUID - SOP Instance UID * @param {String} options.frameNumbers - One-based indices of Frame Items * @returns {Array} frame items as byte arrays of the pixel data element */ }, { key: "retrieveInstanceFrames", value: function retrieveInstanceFrames(options) { if (!("studyInstanceUID" in options)) { throw new Error("Study Instance UID is required for retrieval of instance frames"); } if (!("seriesInstanceUID" in options)) { throw new Error("Series Instance UID is required for retrieval of instance frames"); } if (!("sopInstanceUID" in options)) { throw new Error("SOP Instance UID is required for retrieval of instance frames"); } if (!("frameNumbers" in options)) { throw new Error("frame numbers are required for retrieval of instance frames"); } console.log("retrieve frames ".concat(options.frameNumbers.toString(), " of instance ").concat(options.sopInstanceUID)); var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID, "/frames/").concat(options.frameNumbers.toString()); var mediaTypes = options.mediaTypes; var withCredentials = false; if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } var progressCallback = false; if ("progressCallback" in options) { progressCallback = options.progressCallback; } if (!mediaTypes) { return this._httpGetMultipartApplicationOctetStream(url, false, false, false, progressCallback, withCredentials); } var sharedMediaTypes = DICOMwebClient._getSharedMediaTypes(mediaTypes); if (sharedMediaTypes.length > 1) { /** * Enable request of frames that are stored either compressed * (image/* media type) or uncompressed (application/octet-stream * media type). */ var supportedMediaTypes = { "1.2.840.10008.1.2.1": ["application/octet-stream"], "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"] }; var headers = { Accept: DICOMwebClient._buildMultipartAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes) }; return this._httpGet(url, headers, "arraybuffer", progressCallback, withCredentials).then(multipartDecode); } var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType.startsWith("application")) { return this._httpGetMultipartApplicationOctetStream(url, mediaTypes, false, false, progressCallback, withCredentials); } else if (commonMediaType.startsWith("image")) { return this._httpGetMultipartImage(url, mediaTypes, false, false, false, progressCallback, withCredentials); } else if (commonMediaType.startsWith("video")) { return this._httpGetMultipartVideo(url, mediaTypes, false, false, false, progressCallback, withCredentials); } throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of frames.")); } /** * Retrieves an individual, server-side rendered DICOM 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 * @param {String[]} [options.mediaType] - Acceptable HTTP media types * @param {Object} [options.queryParams] - HTTP query parameters * @returns {ArrayBuffer} Rendered DICOM Instance */ }, { key: "retrieveInstanceRendered", value: function retrieveInstanceRendered(options) { if (!("studyInstanceUID" in options)) { throw new Error("Study Instance UID is required for retrieval of rendered instance"); } if (!("seriesInstanceUID" in options)) { throw new Error("Series Instance UID is required for retrieval of rendered instance"); } if (!("sopInstanceUID" in options)) { throw new Error("SOP Instance UID is required for retrieval of rendered instance"); } var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID, "/rendered"); var mediaTypes = options.mediaTypes, queryParams = options.queryParams; var headers = {}; var withCredentials = false; if ("withCredentials" in options) { if (options.withCredentials) { withCredentials = options.withCredentials; } } var progressCallback = false; if ("progressCallback" in options) { progressCallback = options.progressCallback; } if (!mediaTypes) { var responseType = "arraybuffer"; if (queryParams) { url += (_readOnlyError("url"), DICOMwebClient._parseQueryParameters(queryParams)); } return this._httpGet(url, headers, responseType, progressCallback, withCredentials); } var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); if (commonMediaType.startsWith("image")) { return this._httpGetImage(url, mediaTypes, queryParams, progressCallback, withCredentials); } else if (commonMediaType.startsWith("video")) { return this._httpGetVideo(url, mediaTypes, queryParams, progressCallback, withCredentials); } else if (commonMediaType.startsWith("text")) { return this._httpGetText(url, mediaTypes, queryParams, progressCallback, withCredentials); } else if (commonMediaType === MEDIATYPES.PDF) { return this._httpGetApplicationPdf(url, queryParams, progressCallback, withCredentials); } throw new Error("Media type ".concat(commonMediaType, " is not supported ") + 'for retrieval of rendered instance.'); } /** * Retrieves a thumbnail of an DICOM 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 * @param {String[]} [options.mediaType] - Acceptable HTTP media types * @param {Object} [options.queryParams] - HTTP query parameters * @returns {ArrayBuffer} Thumbnail */ }, { key: "retrieveInstanceThumbnail", value: function retrieveInstanceThumbnail(options) { if (!("studyInstanceUID" in options)) { throw new Error("Study Instance UID is required for retrieval of rendered instance"); } if (!("seriesInstanceUID" in options)) { throw new Error("Series Instance UID is required for retrieval of rendered instance"); } if (!("sopInstanceUID" in options)) { throw new Error("SOP Instance UID is required for retrieval of rendered instance"); } var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID, "/thumbnail"); var mediaTypes = options.mediaTypes, queryParams = options.queryParams; v