dicomweb-client
Version:
Implementation of DICOMweb client code
1,238 lines (1,198 loc) • 84.5 kB
JavaScript
(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 || '*';