dicomweb-client
Advanced tools
Comparing version 0.4.4 to 0.5.0
@@ -5,4 +5,10 @@ module.exports = { | ||
"rules": { | ||
"import/extensions": { "js": "always" } // Better for native ES Module usage | ||
"import/extensions": { "js": "always" }, // Better for native ES Module usage | ||
"no-console": 0, // We can remove this later | ||
"no-underscore-dangle": 0, | ||
"no-plusplus": ["error", { "allowForLoopAfterthoughts": true }] | ||
}, | ||
"env": { | ||
"browser": 1 | ||
} | ||
}; | ||
}; |
@@ -40,12 +40,15 @@ function _typeof(obj) { | ||
* @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) | ||
* @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, offset, limit) { | ||
offset = offset || 0; | ||
limit = limit || arr.length - offset; | ||
var str = ''; | ||
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 + limit; i++) { | ||
for (var i = offset; i < offset + itemLimit; i++) { | ||
str += String.fromCharCode(arr[i]); | ||
@@ -80,9 +83,11 @@ } | ||
function identifyBoundary(header) { | ||
var parts = header.split('\r\n'); | ||
var parts = header.split("\r\n"); | ||
for (var i = 0; i < parts.length; i++) { | ||
if (parts[i].substr(0, 2) === '--') { | ||
if (parts[i].substr(0, 2) === "--") { | ||
return parts[i]; | ||
} | ||
} | ||
return null; | ||
} | ||
@@ -108,5 +113,7 @@ /** | ||
for (var i = 0; i < token.length; i++) { | ||
if (token[i] !== message[index++]) { | ||
if (token[i] !== message[index]) { | ||
return false; | ||
} | ||
index += 1; | ||
} | ||
@@ -148,2 +155,16 @@ | ||
/** | ||
* 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 | ||
@@ -158,5 +179,10 @@ * @property {ArrayBuffer} data The encoded Multipart Data | ||
* | ||
* @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. | ||
* @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. | ||
*/ | ||
@@ -167,3 +193,3 @@ | ||
var boundary = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : guid(); | ||
var contentType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'application/dicom'; | ||
var contentType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "application/dicom"; | ||
var contentTypeString = "Content-Type: ".concat(contentType); | ||
@@ -191,3 +217,2 @@ var header = "\r\n--".concat(boundary, "\r\n").concat(contentTypeString, "\r\n\r\n"); | ||
contentArrays.forEach(function (contentArray) { | ||
var contentLength = contentArray.length; | ||
multipartArray.set(headerArray, position); | ||
@@ -210,15 +235,16 @@ multipartArray.set(contentArray, position + headerLength); | ||
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 | ||
*/ | ||
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 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'); | ||
throw new Error("Response message has no multipart mime header"); | ||
} | ||
@@ -230,3 +256,3 @@ | ||
if (!boundaryString) { | ||
throw new Error('Header of response message does not specify boundary'); | ||
throw new Error("Header of response message does not specify boundary"); | ||
} | ||
@@ -250,9 +276,9 @@ | ||
var _headerIndex = findToken(message, separator, offset, maxSearchLength); | ||
var headerTokenIndex = findToken(message, separator, offset, maxSearchLength); | ||
if (_headerIndex === -1) { | ||
throw new Error('Response message part has no mime header'); | ||
if (headerTokenIndex === -1) { | ||
throw new Error("Response message part has no mime header"); | ||
} | ||
offset = _headerIndex + separator.length; // Extract data from response message, excluding "\r\n" | ||
offset = headerTokenIndex + separator.length; // Extract data from response message, excluding "\r\n" | ||
@@ -270,15 +296,5 @@ var spacingLength = 2; | ||
} | ||
/** | ||
* Create a random GUID | ||
* | ||
* @return {string} | ||
*/ | ||
function guid() { | ||
function s4() { | ||
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); | ||
} | ||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); | ||
function isObject(obj) { | ||
return _typeof(obj) === "object" && obj !== null; | ||
} | ||
@@ -294,12 +310,21 @@ | ||
var MIMETYPES = { | ||
DICOM: 'application/dicom', | ||
DICOM_JSON: 'application/dicom+json', | ||
OCTET_STREAM: 'application/octet-stream', | ||
JPEG: 'image/jpeg', | ||
PNG: 'image/png' | ||
var getFirstResultIfLengthGtOne = function getFirstResultIfLengthGtOne(result) { | ||
if (result.length > 1) { | ||
return 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" | ||
}; | ||
/** | ||
* Class for interacting with DICOMweb RESTful services. | ||
*/ | ||
* Class for interacting with DICOMweb RESTful services. | ||
*/ | ||
@@ -310,5 +335,5 @@ var DICOMwebClient = | ||
/** | ||
* @constructor | ||
* @param {Object} options (choices: "url", "username", "password", "headers") | ||
*/ | ||
* @constructor | ||
* @param {Object} options (choices: "url", "username", "password", "headers") | ||
*/ | ||
function DICOMwebClient(options) { | ||
@@ -320,10 +345,10 @@ _classCallCheck(this, DICOMwebClient); | ||
if (!this.baseURL) { | ||
console.error('no DICOMweb base url provided - calls will fail'); | ||
console.error("no DICOMweb base url provided - calls will fail"); | ||
} | ||
if ('username' in options) { | ||
if ("username" in options) { | ||
this.username = options.username; | ||
if (!('password' in options)) { | ||
console.error('no password provided to authenticate with DICOMweb service'); | ||
if (!("password" in options)) { | ||
console.error("no password provided to authenticate with DICOMweb service"); | ||
} | ||
@@ -334,5 +359,5 @@ | ||
if ('qidoURLPrefix' in options) { | ||
if ("qidoURLPrefix" in options) { | ||
console.log("use URL prefix for QIDO-RS: ".concat(options.qidoURLPrefix)); | ||
this.qidoURL = this.baseURL + '/' + options.qidoURLPrefix; | ||
this.qidoURL = "".concat(this.baseURL, "/").concat(options.qidoURLPrefix); | ||
} else { | ||
@@ -342,5 +367,5 @@ this.qidoURL = this.baseURL; | ||
if ('wadoURLPrefix' in options) { | ||
if ("wadoURLPrefix" in options) { | ||
console.log("use URL prefix for WADO-RS: ".concat(options.wadoURLPrefix)); | ||
this.wadoURL = this.baseURL + '/' + options.wadoURLPrefix; | ||
this.wadoURL = "".concat(this.baseURL, "/").concat(options.wadoURLPrefix); | ||
} else { | ||
@@ -350,5 +375,5 @@ this.wadoURL = this.baseURL; | ||
if ('stowURLPrefix' in options) { | ||
if ("stowURLPrefix" in options) { | ||
console.log("use URL prefix for STOW-RS: ".concat(options.stowURLPrefix)); | ||
this.stowURL = this.baseURL + '/' + options.stowURLPrefix; | ||
this.stowURL = "".concat(this.baseURL, "/").concat(options.stowURLPrefix); | ||
} else { | ||
@@ -371,7 +396,7 @@ this.stowURL = this.baseURL; | ||
if ('responseType' in options) { | ||
if ("responseType" in options) { | ||
request.responseType = options.responseType; | ||
} | ||
if (_typeof(headers) === 'object') { | ||
if (_typeof(headers) === "object") { | ||
Object.keys(headers).forEach(function (key) { | ||
@@ -389,11 +414,11 @@ request.setRequestHeader(key, headers[key]); | ||
request.onloadstart = function (event) {//console.log('upload started: ', url) | ||
request.onloadstart = function onloadstart() {// console.log('upload started: ', url) | ||
}; // Event triggered when upload ends | ||
request.onloadend = function (event) {//console.log('upload finished') | ||
request.onloadend = function onloadend() {// console.log('upload finished') | ||
}; // Handle response message | ||
request.onreadystatechange = function (event) { | ||
request.onreadystatechange = function onreadystatechange() { | ||
if (request.readyState === 4) { | ||
@@ -403,10 +428,10 @@ if (request.status === 200) { | ||
} else if (request.status === 202) { | ||
console.warn('some resources already existed: ', request); | ||
console.warn("some resources already existed: ", request); | ||
resolve(request.response); | ||
} else if (request.status === 204) { | ||
console.warn('empty response for request: ', request); | ||
console.warn("empty response for request: ", request); | ||
resolve([]); | ||
} else { | ||
console.error('request failed: ', request); | ||
var error = new Error('request failed'); | ||
console.error("request failed: ", request); | ||
var error = new Error("request failed"); | ||
error.request = request; | ||
@@ -423,4 +448,4 @@ error.response = request.response; | ||
if ('progressCallback' in options) { | ||
if (typeof options.progressCallback === 'function') { | ||
if ("progressCallback" in options) { | ||
if (typeof options.progressCallback === "function") { | ||
request.onprogress = options.progressCallback; | ||
@@ -441,3 +466,3 @@ } | ||
if ('data' in options) { | ||
if ("data" in options) { | ||
request.send(options.data); | ||
@@ -452,3 +477,3 @@ } else { | ||
value: function _httpGet(url, headers, responseType, progressCallback) { | ||
return this._httpRequest(url, 'get', headers, { | ||
return this._httpRequest(url, "get", headers, { | ||
responseType: responseType, | ||
@@ -463,6 +488,7 @@ progressCallback: progressCallback | ||
var progressCallback = arguments.length > 2 ? arguments[2] : undefined; | ||
var urlWithQueryParams = url; | ||
if (_typeof(params) === 'object') { | ||
if (_typeof(params) === "object") { | ||
if (!isEmptyObject(params)) { | ||
url += DICOMwebClient._parseQueryParameters(params); | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
@@ -472,16 +498,28 @@ } | ||
var headers = { | ||
'Accept': MIMETYPES.DICOM_JSON | ||
Accept: MEDIATYPES.DICOM_JSON | ||
}; | ||
var responseType = 'json'; | ||
return this._httpGet(url, headers, responseType, progressCallback); | ||
var responseType = "json"; | ||
return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback); | ||
} | ||
/** | ||
* Performs an HTTP GET request that accepts a message with | ||
"application/pdf" media type. | ||
* @param {String} url | ||
* @param {Object[]} mediaTypes | ||
* @param {Object} params | ||
* @param {Function} progressCallback | ||
* @return {*} | ||
* @private | ||
*/ | ||
}, { | ||
key: "_httpGetByMimeType", | ||
value: function _httpGetByMimeType(url, mimeType, params) { | ||
var responseType = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'arraybuffer'; | ||
var progressCallback = arguments.length > 4 ? arguments[4] : undefined; | ||
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 urlWithQueryParams = url; | ||
if (_typeof(params) === 'object') { | ||
if (_typeof(params) === "object") { | ||
if (!isEmptyObject(params)) { | ||
url += DICOMwebClient._parseQueryParameters(params); | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
@@ -491,10 +529,297 @@ } | ||
var headers = { | ||
'Accept': "multipart/related; type=\"".concat(mimeType, "\"") | ||
Accept: MEDIATYPES.PDF | ||
}; | ||
return this._httpGet(url, headers, responseType, progressCallback); | ||
var responseType = "json"; | ||
return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback); | ||
} | ||
/** | ||
* 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 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); | ||
} | ||
/** | ||
* 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 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); | ||
} | ||
/** | ||
* 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 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); | ||
} | ||
/** | ||
* 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 {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 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).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 {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 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).then(multipartDecode); | ||
} | ||
/** | ||
* Performs a HTTP GET request that accepts a multipart message with "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 {Array} content of HTTP message body parts | ||
*/ | ||
}, { | ||
key: "_httpGetMultipartApplicationDicom", | ||
value: function _httpGetMultipartApplicationDicom(url, mediaTypes, params, progressCallback) { | ||
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).then(multipartDecode); | ||
} | ||
/** | ||
* Performs a HTTP GET request that accepts a multipart message with "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 {Array} content of HTTP message body parts | ||
*/ | ||
}, { | ||
key: "_httpGetMultipartApplicationOctetStream", | ||
value: function _httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange, params, progressCallback) { | ||
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).then(multipartDecode); | ||
} | ||
}, { | ||
key: "_httpPost", | ||
value: function _httpPost(url, headers, data, progressCallback) { | ||
return this._httpRequest(url, 'post', headers, { | ||
return this._httpRequest(url, "post", headers, { | ||
data: data, | ||
@@ -508,3 +833,3 @@ progressCallback: progressCallback | ||
var headers = { | ||
'Content-Type': MIMETYPES.DICOM_JSON | ||
"Content-Type": MEDIATYPES.DICOM_JSON | ||
}; | ||
@@ -514,2 +839,12 @@ return this._httpPost(url, headers, data, progressCallback); | ||
/** | ||
* Parses media type and extracts its type and subtype. | ||
* | ||
* @param mediaType e.g. image/jpeg | ||
* @private | ||
*/ | ||
}, { | ||
key: "searchForStudies", | ||
/** | ||
* Searches for DICOM studies. | ||
@@ -519,11 +854,8 @@ * @param {Object} options options object | ||
*/ | ||
}, { | ||
key: "searchForStudies", | ||
value: function searchForStudies() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
console.log('search for studies'); | ||
var url = this.qidoURL + '/studies'; | ||
console.log("search for studies"); | ||
var url = "".concat(this.qidoURL, "/studies"); | ||
if ('queryParams' in options) { | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
@@ -537,3 +869,4 @@ } | ||
* @param {Object} options options object | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance belonging to the study | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance | ||
belonging to the study | ||
*/ | ||
@@ -544,8 +877,8 @@ | ||
value: function retrieveStudyMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of study metadata'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/metadata'; | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/metadata"); | ||
return this._httpGetApplicationJson(url); | ||
@@ -565,10 +898,10 @@ } | ||
if ('studyInstanceUID' in options) { | ||
if ("studyInstanceUID" in options) { | ||
console.log("search series of study ".concat(options.studyInstanceUID)); | ||
url += '/studies/' + options.studyInstanceUID; | ||
url += "/studies/".concat(options.studyInstanceUID); | ||
} | ||
url += '/series'; | ||
url += "/series"; | ||
if ('queryParams' in options) { | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
@@ -582,3 +915,4 @@ } | ||
* @param {Object} options options object | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance belonging to the series | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance | ||
belonging to the series | ||
*/ | ||
@@ -589,12 +923,12 @@ | ||
value: function retrieveSeriesMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of series metadata'); | ||
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'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/metadata'; | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/metadata"); | ||
return this._httpGetApplicationJson(url); | ||
@@ -614,8 +948,8 @@ } | ||
if ('studyInstanceUID' in options) { | ||
url += '/studies/' + options.studyInstanceUID; | ||
if ("studyInstanceUID" in options) { | ||
url += "/studies/".concat(options.studyInstanceUID); | ||
if ('seriesInstanceUID' in options) { | ||
if ("seriesInstanceUID" in options) { | ||
console.log("search for instances of series ".concat(options.seriesInstanceUID)); | ||
url += '/series/' + options.seriesInstanceUID; | ||
url += "/series/".concat(options.seriesInstanceUID); | ||
} else { | ||
@@ -625,8 +959,8 @@ console.log("search for instances of study ".concat(options.studyInstanceUID)); | ||
} else { | ||
console.log('search for instances'); | ||
console.log("search for instances"); | ||
} | ||
url += '/instances'; | ||
url += "/instances"; | ||
if ('queryParams' in options) { | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
@@ -645,18 +979,18 @@ } | ||
value: function buildInstanceWadoURIUrl(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required.'); | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required."); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required.'); | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required."); | ||
} | ||
var contentType = options.contentType || MIMETYPES.DICOM; | ||
var transferSyntax = options.transferSyntax || '*'; | ||
var contentType = options.contentType || MEDIATYPES.DICOM; | ||
var transferSyntax = options.transferSyntax || "*"; | ||
var params = []; | ||
params.push('requestType=WADO'); | ||
params.push("requestType=WADO"); | ||
params.push("studyUID=".concat(options.studyInstanceUID)); | ||
@@ -667,3 +1001,3 @@ params.push("seriesUID=".concat(options.seriesInstanceUID)); | ||
params.push("transferSyntax=".concat(transferSyntax)); | ||
var paramString = params.join('&'); | ||
var paramString = params.join("&"); | ||
return "".concat(this.wadoURL, "?").concat(paramString); | ||
@@ -681,16 +1015,16 @@ } | ||
value: function retrieveInstanceMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of instance metadata'); | ||
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 (!("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'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID + '/metadata'; | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID, "/metadata"); | ||
return this._httpGetApplicationJson(url); | ||
@@ -707,24 +1041,85 @@ } | ||
value: function retrieveInstanceFrames(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of instance frames'); | ||
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 (!("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 (!("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'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID + '/frames/' + options.frameNumbers.toString(); | ||
var mimeType = options.mimeType ? "".concat(options.mimeType) : MIMETYPES.OCTET_STREAM; | ||
return this._httpGetByMimeType(url, mimeType).then(multipartDecode); | ||
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; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationOctetStream(url); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
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 options object | ||
* @returns {Array} frame items as byte arrays of the pixel data element | ||
*/ | ||
}, { | ||
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, | ||
params = options.params; | ||
var headers = {}; | ||
if (!mediaTypes) { | ||
var responseType = "arraybuffer"; | ||
return this._httpGet(url, headers, responseType); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType.startsWith("image")) { | ||
return this._httpGetImage(url, mediaTypes, params); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetVideo(url, mediaTypes, params); | ||
} else if (commonMediaType.startsWith("text")) { | ||
return this._httpGetText(url, mediaTypes, params); | ||
} else if (commonMediaType === MEDIATYPES.PDF) { | ||
return this._httpGetApplicationPdf(url, params); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of rendered instance.")); | ||
} | ||
/** | ||
* Retrieves rendered frames for a DICOM instance. | ||
@@ -738,29 +1133,37 @@ * @param {Object} options options object | ||
value: function retrieveInstanceFramesRendered(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of rendered instance frames'); | ||
if (!("studyInstanceUID" in options)) { | ||
throw new Error("Study Instance UID is required for retrieval of rendered instance frames"); | ||
} | ||
if (!('seriesInstanceUID' in options)) { | ||
throw new Error('Series Instance UID is required for retrieval of rendered instance frames'); | ||
if (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required for retrieval of rendered instance frames"); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required for retrieval of rendered instance frames'); | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required for retrieval of rendered instance frames"); | ||
} | ||
if (!('frameNumbers' in options)) { | ||
throw new Error('frame numbers are required for retrieval of rendered instance frames'); | ||
if (!("frameNumbers" in options)) { | ||
throw new Error("frame numbers are required for retrieval of rendered instance frames"); | ||
} | ||
console.log("retrieve rendered frames ".concat(options.frameNumbers.toString(), " of instance ").concat(options.sopInstanceUID)); | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID + '/frames/' + options.frameNumbers.toString() + '/rendered'; | ||
var headers = {}; // The choice of an acceptable media type depends on a variety of things: | ||
// http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_6.html#table_6.1.1-3 | ||
console.debug("retrieve rendered 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(), "/rendered"); | ||
var mediaTypes = options.mediaTypes; | ||
var headers = {}; | ||
if ('mimeType' in options) { | ||
headers['Accept'] = options.mimeType; | ||
if (!mediaTypes) { | ||
var responseType = "arraybuffer"; | ||
return this._httpGet(url, headers, responseType); | ||
} | ||
var responseType = 'arraybuffer'; | ||
return this._httpGet(url, headers, responseType); | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType.startsWith("image")) { | ||
return this._httpGetImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetVideo(url, mediaTypes); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of rendered frame.")); | ||
} | ||
@@ -770,3 +1173,3 @@ /** | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer} DICOM Part 10 file as Arraybuffer | ||
* @returns {ArrayBuffer} DICOM Part 10 file as Arraybuffer | ||
*/ | ||
@@ -777,16 +1180,34 @@ | ||
value: function retrieveInstance(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required'); | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required"); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required'); | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required"); | ||
} | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode).then(getFirstResult); | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID); | ||
var mediaTypes = options.mediaTypes; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url).then(getFirstResult); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes).then(getFirstResult); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes).then(getFirstResult); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes).then(getFirstResultIfLengthGtOne); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes).then(getFirstResultIfLengthGtOne); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of instance.")); | ||
} | ||
@@ -796,3 +1217,3 @@ /** | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
* @returns {ArrayBuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
*/ | ||
@@ -803,12 +1224,30 @@ | ||
value: function retrieveSeries(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required'); | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required"); | ||
} | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode); | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID); | ||
var mediaTypes = options.mediaTypes; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of series.")); | ||
} | ||
@@ -818,3 +1257,3 @@ /** | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
* @returns {ArrayBuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
*/ | ||
@@ -825,8 +1264,26 @@ | ||
value: function retrieveStudy(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required'); | ||
if (!("studyInstanceUID" in options)) { | ||
throw new Error("Study Instance UID is required"); | ||
} | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode); | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID); | ||
var mediaTypes = options.mediaTypes; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of study.")); | ||
} | ||
@@ -847,7 +1304,23 @@ /** | ||
value: function retrieveBulkData(options) { | ||
if (!('BulkDataURI' in options)) { | ||
throw new Error('BulkDataURI is required.'); | ||
if (!("BulkDataURI" in options)) { | ||
throw new Error("BulkDataURI is required."); | ||
} | ||
return this._httpGetByMimeType(options.BulkDataURI, MIMETYPES.OCTET_STREAM).then(multipartDecode).then(getFirstResult); | ||
var url = options.BulkDataURI; | ||
var mediaTypes = options.mediaTypes, | ||
byteRange = options.byteRange; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes, byteRange); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of bulk data.")); | ||
} | ||
@@ -863,4 +1336,4 @@ /** | ||
value: function storeInstances(options) { | ||
if (!('datasets' in options)) { | ||
throw new Error('datasets are required for storing'); | ||
if (!("datasets" in options)) { | ||
throw new Error("datasets are required for storing"); | ||
} | ||
@@ -870,3 +1343,3 @@ | ||
if ('studyInstanceUID' in options) { | ||
if ("studyInstanceUID" in options) { | ||
url += "/".concat(options.studyInstanceUID); | ||
@@ -880,3 +1353,3 @@ } | ||
var headers = { | ||
'Content-Type': "multipart/related; type=application/dicom; boundary=".concat(boundary) | ||
"Content-Type": "multipart/related; type=application/dicom; boundary=".concat(boundary) | ||
}; | ||
@@ -889,12 +1362,205 @@ return this._httpPost(url, headers, data, options.progressCallback); | ||
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var queryString = '?'; | ||
var queryString = "?"; | ||
Object.keys(params).forEach(function (key, index) { | ||
if (index !== 0) { | ||
queryString += '&'; | ||
queryString += "&"; | ||
} | ||
queryString += key + '=' + encodeURIComponent(params[key]); | ||
queryString += "".concat(key, "=").concat(encodeURIComponent(params[key])); | ||
}); | ||
return queryString; | ||
} | ||
}, { | ||
key: "_assertMediaTypeIsValid", | ||
value: function _assertMediaTypeIsValid(mediaType) { | ||
if (!mediaType) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
var sepIndex = mediaType.indexOf("/"); | ||
if (sepIndex === -1) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
var mediaTypeType = mediaType.slice(0, sepIndex); | ||
var types = ["application", "image", "text", "video"]; | ||
if (!types.includes(mediaTypeType)) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
if (mediaType.slice(sepIndex + 1).includes("/")) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
} | ||
}, { | ||
key: "_parseMediaType", | ||
value: function _parseMediaType(mediaType) { | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
return mediaType.split("/"); | ||
} | ||
/** | ||
* Builds an accept header field value for HTTP GET request messages. | ||
* | ||
* @param {Object[]} mediaTypes Acceptable media types | ||
* @param {Object[]} supportedMediaTypes Supported media types | ||
* @return {*} | ||
* @private | ||
*/ | ||
}, { | ||
key: "_buildAcceptHeaderFieldValue", | ||
value: function _buildAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes) { | ||
if (!Array.isArray(mediaTypes)) { | ||
throw new Error("Acceptable media types must be provided as an Array"); | ||
} | ||
var fieldValueParts = mediaTypes.map(function (item) { | ||
var mediaType = item.mediaType; | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
if (!supportedMediaTypes.includes(mediaType)) { | ||
throw new Error("Media type ".concat(mediaType, " is not supported for requested resource")); | ||
} | ||
return mediaType; | ||
}); | ||
return fieldValueParts.join(", "); | ||
} | ||
/** | ||
* Builds an accept header field value for HTTP GET multipart request | ||
messages. | ||
* | ||
* @param {Object[]} mediaTypes Acceptable media types | ||
* @param {Object[]} supportedMediaTypes Supported media types | ||
* @private | ||
*/ | ||
}, { | ||
key: "_buildMultipartAcceptHeaderFieldValue", | ||
value: function _buildMultipartAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes) { | ||
if (!Array.isArray(mediaTypes)) { | ||
throw new Error("Acceptable media types must be provided as an Array"); | ||
} | ||
if (!Array.isArray(supportedMediaTypes) && !isObject(supportedMediaTypes)) { | ||
throw new Error("Supported media types must be provided as an Array or an Object"); | ||
} | ||
var fieldValueParts = []; | ||
mediaTypes.forEach(function (item) { | ||
var transferSyntaxUID = item.transferSyntaxUID, | ||
mediaType = item.mediaType; | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
var fieldValue = "multipart/related; type=\"".concat(mediaType, "\""); | ||
if (isObject(supportedMediaTypes)) { | ||
// SupportedMediaTypes is a lookup table that maps Transfer Syntax UID | ||
// to one or more Media Types | ||
if (!Object.values(supportedMediaTypes).flat(1).includes(mediaType)) { | ||
if (!mediaType.endsWith("/*") || !mediaType.endsWith("/")) { | ||
throw new Error("Media type ".concat(mediaType, " is not supported for requested resource")); | ||
} | ||
} | ||
if (transferSyntaxUID) { | ||
if (transferSyntaxUID !== "*") { | ||
if (!Object.keys(supportedMediaTypes).includes(transferSyntaxUID)) { | ||
throw new Error("Transfer syntax ".concat(transferSyntaxUID, " is not supported for requested resource")); | ||
} | ||
var expectedMediaTypes = supportedMediaTypes[transferSyntaxUID]; | ||
if (!expectedMediaTypes.includes(mediaType)) { | ||
var actualType = DICOMwebClient._parseMediaType(mediaType)[0]; | ||
expectedMediaTypes.map(function (expectedMediaType) { | ||
var expectedType = DICOMwebClient._parseMediaType(expectedMediaType)[0]; | ||
var haveSameType = actualType === expectedType; | ||
if (haveSameType && (mediaType.endsWith("/*") || mediaType.endsWith("/"))) { | ||
return; | ||
} | ||
throw new Error("Transfer syntax ".concat(transferSyntaxUID, " is not supported for requested resource")); | ||
}); | ||
} | ||
} | ||
fieldValue += "; transfer-syntax=".concat(transferSyntaxUID); | ||
} | ||
} else if (Array.isArray(supportedMediaTypes) && !supportedMediaTypes.includes(mediaType)) { | ||
throw new Error("Media type ".concat(mediaType, " is not supported for requested resource")); | ||
} | ||
fieldValueParts.push(fieldValue); | ||
}); | ||
return fieldValueParts.join(", "); | ||
} | ||
/** | ||
* Builds a range header field value for HTTP GET request messages. | ||
* | ||
* @param {Array} byteRange start and end of byte range | ||
* @returns {String} range header field value | ||
*/ | ||
}, { | ||
key: "_buildRangeHeaderFieldValue", | ||
value: function _buildRangeHeaderFieldValue() { | ||
var byteRange = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; | ||
if (byteRange.length === 1) { | ||
return "bytes=".concat(byteRange[0], "-"); | ||
} | ||
if (byteRange.length === 2) { | ||
return "bytes=".concat(byteRange[0], "-").concat(byteRange[1]); | ||
} | ||
return "bytes=0-"; | ||
} | ||
/** | ||
* Gets common type of acceptable media types and asserts that only | ||
one type is specified. For example, ``("image/jpeg", "image/jp2")`` | ||
will pass, but ``("image/jpeg", "video/mpeg2")`` will raise an | ||
exception. | ||
* @param {String[]} acceptable media types and optionally the UIDs of the | ||
corresponding transfer syntaxes | ||
* | ||
*/ | ||
}, { | ||
key: "_getCommonMediaType", | ||
value: function _getCommonMediaType(mediaTypes) { | ||
if (!mediaTypes || !mediaTypes.length) { | ||
throw new Error("No acceptable media types provided"); | ||
} | ||
var commonMediaTypes = new Set(); | ||
mediaTypes.forEach(function (item) { | ||
var mediaType = item.mediaType; | ||
if (mediaType.startsWith("application")) { | ||
commonMediaTypes.add(mediaType); | ||
} else { | ||
var type = DICOMwebClient._parseMediaType(mediaType)[0]; | ||
commonMediaTypes.add("".concat(type, "/")); | ||
} | ||
}); | ||
if (commonMediaTypes.size === 0) { | ||
throw new Error("No common acceptable media type could be identified."); | ||
} else if (commonMediaTypes.size > 1) { | ||
throw new Error("Acceptable media types must have the same type."); | ||
} | ||
return Array.from(commonMediaTypes)[0]; | ||
} | ||
}]); | ||
@@ -917,5 +1583,5 @@ | ||
return null; | ||
} else { | ||
return str.substring(beforeIndex, afterIndex); | ||
} | ||
return str.substring(beforeIndex, afterIndex); | ||
} | ||
@@ -934,3 +1600,3 @@ | ||
if (!uid) { | ||
console.debug('Study Instance UID could not be dertermined from URI "' + uri + '"'); | ||
console.debug("Study Instance UID could not be dertermined from URI \"".concat(uri, "\"")); | ||
} | ||
@@ -949,3 +1615,3 @@ | ||
if (!uid) { | ||
console.debug('Series Instance UID could not be dertermined from URI "' + uri + '"'); | ||
console.debug("Series Instance UID could not be dertermined from URI \"".concat(uri, "\"")); | ||
} | ||
@@ -968,3 +1634,3 @@ | ||
if (!uid) { | ||
console.debug('SOP Instance UID could not be dertermined from URI"' + uri + '"'); | ||
console.debug("SOP Instance UID could not be dertermined from URI\"".concat(uri, "\"")); | ||
} | ||
@@ -983,9 +1649,9 @@ | ||
if (numbers === undefined) { | ||
console.debug('Frames Numbers could not be dertermined from URI"' + uri + '"'); | ||
console.debug("Frames Numbers could not be dertermined from URI\"".concat(uri, "\"")); | ||
} | ||
return numbers.split(','); | ||
return numbers.split(","); | ||
} | ||
var version = '0.3.2'; | ||
var version = "0.5.0"; | ||
@@ -992,0 +1658,0 @@ var api = { |
@@ -46,12 +46,15 @@ (function (global, factory) { | ||
* @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) | ||
* @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, offset, limit) { | ||
offset = offset || 0; | ||
limit = limit || arr.length - offset; | ||
var str = ''; | ||
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 + limit; i++) { | ||
for (var i = offset; i < offset + itemLimit; i++) { | ||
str += String.fromCharCode(arr[i]); | ||
@@ -86,9 +89,11 @@ } | ||
function identifyBoundary(header) { | ||
var parts = header.split('\r\n'); | ||
var parts = header.split("\r\n"); | ||
for (var i = 0; i < parts.length; i++) { | ||
if (parts[i].substr(0, 2) === '--') { | ||
if (parts[i].substr(0, 2) === "--") { | ||
return parts[i]; | ||
} | ||
} | ||
return null; | ||
} | ||
@@ -114,5 +119,7 @@ /** | ||
for (var i = 0; i < token.length; i++) { | ||
if (token[i] !== message[index++]) { | ||
if (token[i] !== message[index]) { | ||
return false; | ||
} | ||
index += 1; | ||
} | ||
@@ -154,2 +161,16 @@ | ||
/** | ||
* 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 | ||
@@ -164,5 +185,10 @@ * @property {ArrayBuffer} data The encoded Multipart Data | ||
* | ||
* @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. | ||
* @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. | ||
*/ | ||
@@ -173,3 +199,3 @@ | ||
var boundary = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : guid(); | ||
var contentType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'application/dicom'; | ||
var contentType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "application/dicom"; | ||
var contentTypeString = "Content-Type: ".concat(contentType); | ||
@@ -197,3 +223,2 @@ var header = "\r\n--".concat(boundary, "\r\n").concat(contentTypeString, "\r\n\r\n"); | ||
contentArrays.forEach(function (contentArray) { | ||
var contentLength = contentArray.length; | ||
multipartArray.set(headerArray, position); | ||
@@ -216,15 +241,16 @@ multipartArray.set(contentArray, position + headerLength); | ||
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 | ||
*/ | ||
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 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'); | ||
throw new Error("Response message has no multipart mime header"); | ||
} | ||
@@ -236,3 +262,3 @@ | ||
if (!boundaryString) { | ||
throw new Error('Header of response message does not specify boundary'); | ||
throw new Error("Header of response message does not specify boundary"); | ||
} | ||
@@ -256,9 +282,9 @@ | ||
var _headerIndex = findToken(message, separator, offset, maxSearchLength); | ||
var headerTokenIndex = findToken(message, separator, offset, maxSearchLength); | ||
if (_headerIndex === -1) { | ||
throw new Error('Response message part has no mime header'); | ||
if (headerTokenIndex === -1) { | ||
throw new Error("Response message part has no mime header"); | ||
} | ||
offset = _headerIndex + separator.length; // Extract data from response message, excluding "\r\n" | ||
offset = headerTokenIndex + separator.length; // Extract data from response message, excluding "\r\n" | ||
@@ -276,15 +302,5 @@ var spacingLength = 2; | ||
} | ||
/** | ||
* Create a random GUID | ||
* | ||
* @return {string} | ||
*/ | ||
function guid() { | ||
function s4() { | ||
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); | ||
} | ||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); | ||
function isObject(obj) { | ||
return _typeof(obj) === "object" && obj !== null; | ||
} | ||
@@ -300,12 +316,21 @@ | ||
var MIMETYPES = { | ||
DICOM: 'application/dicom', | ||
DICOM_JSON: 'application/dicom+json', | ||
OCTET_STREAM: 'application/octet-stream', | ||
JPEG: 'image/jpeg', | ||
PNG: 'image/png' | ||
var getFirstResultIfLengthGtOne = function getFirstResultIfLengthGtOne(result) { | ||
if (result.length > 1) { | ||
return 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" | ||
}; | ||
/** | ||
* Class for interacting with DICOMweb RESTful services. | ||
*/ | ||
* Class for interacting with DICOMweb RESTful services. | ||
*/ | ||
@@ -316,5 +341,5 @@ var DICOMwebClient = | ||
/** | ||
* @constructor | ||
* @param {Object} options (choices: "url", "username", "password", "headers") | ||
*/ | ||
* @constructor | ||
* @param {Object} options (choices: "url", "username", "password", "headers") | ||
*/ | ||
function DICOMwebClient(options) { | ||
@@ -326,10 +351,10 @@ _classCallCheck(this, DICOMwebClient); | ||
if (!this.baseURL) { | ||
console.error('no DICOMweb base url provided - calls will fail'); | ||
console.error("no DICOMweb base url provided - calls will fail"); | ||
} | ||
if ('username' in options) { | ||
if ("username" in options) { | ||
this.username = options.username; | ||
if (!('password' in options)) { | ||
console.error('no password provided to authenticate with DICOMweb service'); | ||
if (!("password" in options)) { | ||
console.error("no password provided to authenticate with DICOMweb service"); | ||
} | ||
@@ -340,5 +365,5 @@ | ||
if ('qidoURLPrefix' in options) { | ||
if ("qidoURLPrefix" in options) { | ||
console.log("use URL prefix for QIDO-RS: ".concat(options.qidoURLPrefix)); | ||
this.qidoURL = this.baseURL + '/' + options.qidoURLPrefix; | ||
this.qidoURL = "".concat(this.baseURL, "/").concat(options.qidoURLPrefix); | ||
} else { | ||
@@ -348,5 +373,5 @@ this.qidoURL = this.baseURL; | ||
if ('wadoURLPrefix' in options) { | ||
if ("wadoURLPrefix" in options) { | ||
console.log("use URL prefix for WADO-RS: ".concat(options.wadoURLPrefix)); | ||
this.wadoURL = this.baseURL + '/' + options.wadoURLPrefix; | ||
this.wadoURL = "".concat(this.baseURL, "/").concat(options.wadoURLPrefix); | ||
} else { | ||
@@ -356,5 +381,5 @@ this.wadoURL = this.baseURL; | ||
if ('stowURLPrefix' in options) { | ||
if ("stowURLPrefix" in options) { | ||
console.log("use URL prefix for STOW-RS: ".concat(options.stowURLPrefix)); | ||
this.stowURL = this.baseURL + '/' + options.stowURLPrefix; | ||
this.stowURL = "".concat(this.baseURL, "/").concat(options.stowURLPrefix); | ||
} else { | ||
@@ -377,7 +402,7 @@ this.stowURL = this.baseURL; | ||
if ('responseType' in options) { | ||
if ("responseType" in options) { | ||
request.responseType = options.responseType; | ||
} | ||
if (_typeof(headers) === 'object') { | ||
if (_typeof(headers) === "object") { | ||
Object.keys(headers).forEach(function (key) { | ||
@@ -395,11 +420,11 @@ request.setRequestHeader(key, headers[key]); | ||
request.onloadstart = function (event) {//console.log('upload started: ', url) | ||
request.onloadstart = function onloadstart() {// console.log('upload started: ', url) | ||
}; // Event triggered when upload ends | ||
request.onloadend = function (event) {//console.log('upload finished') | ||
request.onloadend = function onloadend() {// console.log('upload finished') | ||
}; // Handle response message | ||
request.onreadystatechange = function (event) { | ||
request.onreadystatechange = function onreadystatechange() { | ||
if (request.readyState === 4) { | ||
@@ -409,10 +434,10 @@ if (request.status === 200) { | ||
} else if (request.status === 202) { | ||
console.warn('some resources already existed: ', request); | ||
console.warn("some resources already existed: ", request); | ||
resolve(request.response); | ||
} else if (request.status === 204) { | ||
console.warn('empty response for request: ', request); | ||
console.warn("empty response for request: ", request); | ||
resolve([]); | ||
} else { | ||
console.error('request failed: ', request); | ||
var error = new Error('request failed'); | ||
console.error("request failed: ", request); | ||
var error = new Error("request failed"); | ||
error.request = request; | ||
@@ -429,4 +454,4 @@ error.response = request.response; | ||
if ('progressCallback' in options) { | ||
if (typeof options.progressCallback === 'function') { | ||
if ("progressCallback" in options) { | ||
if (typeof options.progressCallback === "function") { | ||
request.onprogress = options.progressCallback; | ||
@@ -447,3 +472,3 @@ } | ||
if ('data' in options) { | ||
if ("data" in options) { | ||
request.send(options.data); | ||
@@ -458,3 +483,3 @@ } else { | ||
value: function _httpGet(url, headers, responseType, progressCallback) { | ||
return this._httpRequest(url, 'get', headers, { | ||
return this._httpRequest(url, "get", headers, { | ||
responseType: responseType, | ||
@@ -469,6 +494,7 @@ progressCallback: progressCallback | ||
var progressCallback = arguments.length > 2 ? arguments[2] : undefined; | ||
var urlWithQueryParams = url; | ||
if (_typeof(params) === 'object') { | ||
if (_typeof(params) === "object") { | ||
if (!isEmptyObject(params)) { | ||
url += DICOMwebClient._parseQueryParameters(params); | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
@@ -478,16 +504,28 @@ } | ||
var headers = { | ||
'Accept': MIMETYPES.DICOM_JSON | ||
Accept: MEDIATYPES.DICOM_JSON | ||
}; | ||
var responseType = 'json'; | ||
return this._httpGet(url, headers, responseType, progressCallback); | ||
var responseType = "json"; | ||
return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback); | ||
} | ||
/** | ||
* Performs an HTTP GET request that accepts a message with | ||
"application/pdf" media type. | ||
* @param {String} url | ||
* @param {Object[]} mediaTypes | ||
* @param {Object} params | ||
* @param {Function} progressCallback | ||
* @return {*} | ||
* @private | ||
*/ | ||
}, { | ||
key: "_httpGetByMimeType", | ||
value: function _httpGetByMimeType(url, mimeType, params) { | ||
var responseType = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'arraybuffer'; | ||
var progressCallback = arguments.length > 4 ? arguments[4] : undefined; | ||
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 urlWithQueryParams = url; | ||
if (_typeof(params) === 'object') { | ||
if (_typeof(params) === "object") { | ||
if (!isEmptyObject(params)) { | ||
url += DICOMwebClient._parseQueryParameters(params); | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
@@ -497,10 +535,297 @@ } | ||
var headers = { | ||
'Accept': "multipart/related; type=\"".concat(mimeType, "\"") | ||
Accept: MEDIATYPES.PDF | ||
}; | ||
return this._httpGet(url, headers, responseType, progressCallback); | ||
var responseType = "json"; | ||
return this._httpGet(urlWithQueryParams, headers, responseType, progressCallback); | ||
} | ||
/** | ||
* 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 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); | ||
} | ||
/** | ||
* 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 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); | ||
} | ||
/** | ||
* 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 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); | ||
} | ||
/** | ||
* 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 {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 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).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 {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 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).then(multipartDecode); | ||
} | ||
/** | ||
* Performs a HTTP GET request that accepts a multipart message with "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 {Array} content of HTTP message body parts | ||
*/ | ||
}, { | ||
key: "_httpGetMultipartApplicationDicom", | ||
value: function _httpGetMultipartApplicationDicom(url, mediaTypes, params, progressCallback) { | ||
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).then(multipartDecode); | ||
} | ||
/** | ||
* Performs a HTTP GET request that accepts a multipart message with "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 {Array} content of HTTP message body parts | ||
*/ | ||
}, { | ||
key: "_httpGetMultipartApplicationOctetStream", | ||
value: function _httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange, params, progressCallback) { | ||
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).then(multipartDecode); | ||
} | ||
}, { | ||
key: "_httpPost", | ||
value: function _httpPost(url, headers, data, progressCallback) { | ||
return this._httpRequest(url, 'post', headers, { | ||
return this._httpRequest(url, "post", headers, { | ||
data: data, | ||
@@ -514,3 +839,3 @@ progressCallback: progressCallback | ||
var headers = { | ||
'Content-Type': MIMETYPES.DICOM_JSON | ||
"Content-Type": MEDIATYPES.DICOM_JSON | ||
}; | ||
@@ -520,2 +845,12 @@ return this._httpPost(url, headers, data, progressCallback); | ||
/** | ||
* Parses media type and extracts its type and subtype. | ||
* | ||
* @param mediaType e.g. image/jpeg | ||
* @private | ||
*/ | ||
}, { | ||
key: "searchForStudies", | ||
/** | ||
* Searches for DICOM studies. | ||
@@ -525,11 +860,8 @@ * @param {Object} options options object | ||
*/ | ||
}, { | ||
key: "searchForStudies", | ||
value: function searchForStudies() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
console.log('search for studies'); | ||
var url = this.qidoURL + '/studies'; | ||
console.log("search for studies"); | ||
var url = "".concat(this.qidoURL, "/studies"); | ||
if ('queryParams' in options) { | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
@@ -543,3 +875,4 @@ } | ||
* @param {Object} options options object | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance belonging to the study | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance | ||
belonging to the study | ||
*/ | ||
@@ -550,8 +883,8 @@ | ||
value: function retrieveStudyMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of study metadata'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/metadata'; | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/metadata"); | ||
return this._httpGetApplicationJson(url); | ||
@@ -571,10 +904,10 @@ } | ||
if ('studyInstanceUID' in options) { | ||
if ("studyInstanceUID" in options) { | ||
console.log("search series of study ".concat(options.studyInstanceUID)); | ||
url += '/studies/' + options.studyInstanceUID; | ||
url += "/studies/".concat(options.studyInstanceUID); | ||
} | ||
url += '/series'; | ||
url += "/series"; | ||
if ('queryParams' in options) { | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
@@ -588,3 +921,4 @@ } | ||
* @param {Object} options options object | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance belonging to the series | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance | ||
belonging to the series | ||
*/ | ||
@@ -595,12 +929,12 @@ | ||
value: function retrieveSeriesMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of series metadata'); | ||
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'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/metadata'; | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/metadata"); | ||
return this._httpGetApplicationJson(url); | ||
@@ -620,8 +954,8 @@ } | ||
if ('studyInstanceUID' in options) { | ||
url += '/studies/' + options.studyInstanceUID; | ||
if ("studyInstanceUID" in options) { | ||
url += "/studies/".concat(options.studyInstanceUID); | ||
if ('seriesInstanceUID' in options) { | ||
if ("seriesInstanceUID" in options) { | ||
console.log("search for instances of series ".concat(options.seriesInstanceUID)); | ||
url += '/series/' + options.seriesInstanceUID; | ||
url += "/series/".concat(options.seriesInstanceUID); | ||
} else { | ||
@@ -631,8 +965,8 @@ console.log("search for instances of study ".concat(options.studyInstanceUID)); | ||
} else { | ||
console.log('search for instances'); | ||
console.log("search for instances"); | ||
} | ||
url += '/instances'; | ||
url += "/instances"; | ||
if ('queryParams' in options) { | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
@@ -651,18 +985,18 @@ } | ||
value: function buildInstanceWadoURIUrl(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required.'); | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required."); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required.'); | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required."); | ||
} | ||
var contentType = options.contentType || MIMETYPES.DICOM; | ||
var transferSyntax = options.transferSyntax || '*'; | ||
var contentType = options.contentType || MEDIATYPES.DICOM; | ||
var transferSyntax = options.transferSyntax || "*"; | ||
var params = []; | ||
params.push('requestType=WADO'); | ||
params.push("requestType=WADO"); | ||
params.push("studyUID=".concat(options.studyInstanceUID)); | ||
@@ -673,3 +1007,3 @@ params.push("seriesUID=".concat(options.seriesInstanceUID)); | ||
params.push("transferSyntax=".concat(transferSyntax)); | ||
var paramString = params.join('&'); | ||
var paramString = params.join("&"); | ||
return "".concat(this.wadoURL, "?").concat(paramString); | ||
@@ -687,16 +1021,16 @@ } | ||
value: function retrieveInstanceMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of instance metadata'); | ||
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 (!("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'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID + '/metadata'; | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID, "/metadata"); | ||
return this._httpGetApplicationJson(url); | ||
@@ -713,24 +1047,85 @@ } | ||
value: function retrieveInstanceFrames(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of instance frames'); | ||
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 (!("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 (!("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'); | ||
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 = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID + '/frames/' + options.frameNumbers.toString(); | ||
var mimeType = options.mimeType ? "".concat(options.mimeType) : MIMETYPES.OCTET_STREAM; | ||
return this._httpGetByMimeType(url, mimeType).then(multipartDecode); | ||
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; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationOctetStream(url); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
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 options object | ||
* @returns {Array} frame items as byte arrays of the pixel data element | ||
*/ | ||
}, { | ||
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, | ||
params = options.params; | ||
var headers = {}; | ||
if (!mediaTypes) { | ||
var responseType = "arraybuffer"; | ||
return this._httpGet(url, headers, responseType); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType.startsWith("image")) { | ||
return this._httpGetImage(url, mediaTypes, params); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetVideo(url, mediaTypes, params); | ||
} else if (commonMediaType.startsWith("text")) { | ||
return this._httpGetText(url, mediaTypes, params); | ||
} else if (commonMediaType === MEDIATYPES.PDF) { | ||
return this._httpGetApplicationPdf(url, params); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of rendered instance.")); | ||
} | ||
/** | ||
* Retrieves rendered frames for a DICOM instance. | ||
@@ -744,29 +1139,37 @@ * @param {Object} options options object | ||
value: function retrieveInstanceFramesRendered(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of rendered instance frames'); | ||
if (!("studyInstanceUID" in options)) { | ||
throw new Error("Study Instance UID is required for retrieval of rendered instance frames"); | ||
} | ||
if (!('seriesInstanceUID' in options)) { | ||
throw new Error('Series Instance UID is required for retrieval of rendered instance frames'); | ||
if (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required for retrieval of rendered instance frames"); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required for retrieval of rendered instance frames'); | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required for retrieval of rendered instance frames"); | ||
} | ||
if (!('frameNumbers' in options)) { | ||
throw new Error('frame numbers are required for retrieval of rendered instance frames'); | ||
if (!("frameNumbers" in options)) { | ||
throw new Error("frame numbers are required for retrieval of rendered instance frames"); | ||
} | ||
console.log("retrieve rendered frames ".concat(options.frameNumbers.toString(), " of instance ").concat(options.sopInstanceUID)); | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID + '/frames/' + options.frameNumbers.toString() + '/rendered'; | ||
var headers = {}; // The choice of an acceptable media type depends on a variety of things: | ||
// http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_6.html#table_6.1.1-3 | ||
console.debug("retrieve rendered 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(), "/rendered"); | ||
var mediaTypes = options.mediaTypes; | ||
var headers = {}; | ||
if ('mimeType' in options) { | ||
headers['Accept'] = options.mimeType; | ||
if (!mediaTypes) { | ||
var responseType = "arraybuffer"; | ||
return this._httpGet(url, headers, responseType); | ||
} | ||
var responseType = 'arraybuffer'; | ||
return this._httpGet(url, headers, responseType); | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType.startsWith("image")) { | ||
return this._httpGetImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetVideo(url, mediaTypes); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of rendered frame.")); | ||
} | ||
@@ -776,3 +1179,3 @@ /** | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer} DICOM Part 10 file as Arraybuffer | ||
* @returns {ArrayBuffer} DICOM Part 10 file as Arraybuffer | ||
*/ | ||
@@ -783,16 +1186,34 @@ | ||
value: function retrieveInstance(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required'); | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required"); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required'); | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required"); | ||
} | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID + '/instances/' + options.sopInstanceUID; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode).then(getFirstResult); | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID, "/instances/").concat(options.sopInstanceUID); | ||
var mediaTypes = options.mediaTypes; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url).then(getFirstResult); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes).then(getFirstResult); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes).then(getFirstResult); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes).then(getFirstResultIfLengthGtOne); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes).then(getFirstResultIfLengthGtOne); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of instance.")); | ||
} | ||
@@ -802,3 +1223,3 @@ /** | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
* @returns {ArrayBuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
*/ | ||
@@ -809,12 +1230,30 @@ | ||
value: function retrieveSeries(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required'); | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required"); | ||
} | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID + '/series/' + options.seriesInstanceUID; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode); | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID, "/series/").concat(options.seriesInstanceUID); | ||
var mediaTypes = options.mediaTypes; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of series.")); | ||
} | ||
@@ -824,3 +1263,3 @@ /** | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
* @returns {ArrayBuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
*/ | ||
@@ -831,8 +1270,26 @@ | ||
value: function retrieveStudy(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required'); | ||
if (!("studyInstanceUID" in options)) { | ||
throw new Error("Study Instance UID is required"); | ||
} | ||
var url = this.wadoURL + '/studies/' + options.studyInstanceUID; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode); | ||
var url = "".concat(this.wadoURL, "/studies/").concat(options.studyInstanceUID); | ||
var mediaTypes = options.mediaTypes; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of study.")); | ||
} | ||
@@ -853,7 +1310,23 @@ /** | ||
value: function retrieveBulkData(options) { | ||
if (!('BulkDataURI' in options)) { | ||
throw new Error('BulkDataURI is required.'); | ||
if (!("BulkDataURI" in options)) { | ||
throw new Error("BulkDataURI is required."); | ||
} | ||
return this._httpGetByMimeType(options.BulkDataURI, MIMETYPES.OCTET_STREAM).then(multipartDecode).then(getFirstResult); | ||
var url = options.BulkDataURI; | ||
var mediaTypes = options.mediaTypes, | ||
byteRange = options.byteRange; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange); | ||
} | ||
var commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes, byteRange); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes, byteRange); | ||
} | ||
throw new Error("Media type ".concat(commonMediaType, " is not supported for retrieval of bulk data.")); | ||
} | ||
@@ -869,4 +1342,4 @@ /** | ||
value: function storeInstances(options) { | ||
if (!('datasets' in options)) { | ||
throw new Error('datasets are required for storing'); | ||
if (!("datasets" in options)) { | ||
throw new Error("datasets are required for storing"); | ||
} | ||
@@ -876,3 +1349,3 @@ | ||
if ('studyInstanceUID' in options) { | ||
if ("studyInstanceUID" in options) { | ||
url += "/".concat(options.studyInstanceUID); | ||
@@ -886,3 +1359,3 @@ } | ||
var headers = { | ||
'Content-Type': "multipart/related; type=application/dicom; boundary=".concat(boundary) | ||
"Content-Type": "multipart/related; type=application/dicom; boundary=".concat(boundary) | ||
}; | ||
@@ -895,12 +1368,205 @@ return this._httpPost(url, headers, data, options.progressCallback); | ||
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var queryString = '?'; | ||
var queryString = "?"; | ||
Object.keys(params).forEach(function (key, index) { | ||
if (index !== 0) { | ||
queryString += '&'; | ||
queryString += "&"; | ||
} | ||
queryString += key + '=' + encodeURIComponent(params[key]); | ||
queryString += "".concat(key, "=").concat(encodeURIComponent(params[key])); | ||
}); | ||
return queryString; | ||
} | ||
}, { | ||
key: "_assertMediaTypeIsValid", | ||
value: function _assertMediaTypeIsValid(mediaType) { | ||
if (!mediaType) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
var sepIndex = mediaType.indexOf("/"); | ||
if (sepIndex === -1) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
var mediaTypeType = mediaType.slice(0, sepIndex); | ||
var types = ["application", "image", "text", "video"]; | ||
if (!types.includes(mediaTypeType)) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
if (mediaType.slice(sepIndex + 1).includes("/")) { | ||
throw new Error("Not a valid media type: ".concat(mediaType)); | ||
} | ||
} | ||
}, { | ||
key: "_parseMediaType", | ||
value: function _parseMediaType(mediaType) { | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
return mediaType.split("/"); | ||
} | ||
/** | ||
* Builds an accept header field value for HTTP GET request messages. | ||
* | ||
* @param {Object[]} mediaTypes Acceptable media types | ||
* @param {Object[]} supportedMediaTypes Supported media types | ||
* @return {*} | ||
* @private | ||
*/ | ||
}, { | ||
key: "_buildAcceptHeaderFieldValue", | ||
value: function _buildAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes) { | ||
if (!Array.isArray(mediaTypes)) { | ||
throw new Error("Acceptable media types must be provided as an Array"); | ||
} | ||
var fieldValueParts = mediaTypes.map(function (item) { | ||
var mediaType = item.mediaType; | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
if (!supportedMediaTypes.includes(mediaType)) { | ||
throw new Error("Media type ".concat(mediaType, " is not supported for requested resource")); | ||
} | ||
return mediaType; | ||
}); | ||
return fieldValueParts.join(", "); | ||
} | ||
/** | ||
* Builds an accept header field value for HTTP GET multipart request | ||
messages. | ||
* | ||
* @param {Object[]} mediaTypes Acceptable media types | ||
* @param {Object[]} supportedMediaTypes Supported media types | ||
* @private | ||
*/ | ||
}, { | ||
key: "_buildMultipartAcceptHeaderFieldValue", | ||
value: function _buildMultipartAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes) { | ||
if (!Array.isArray(mediaTypes)) { | ||
throw new Error("Acceptable media types must be provided as an Array"); | ||
} | ||
if (!Array.isArray(supportedMediaTypes) && !isObject(supportedMediaTypes)) { | ||
throw new Error("Supported media types must be provided as an Array or an Object"); | ||
} | ||
var fieldValueParts = []; | ||
mediaTypes.forEach(function (item) { | ||
var transferSyntaxUID = item.transferSyntaxUID, | ||
mediaType = item.mediaType; | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
var fieldValue = "multipart/related; type=\"".concat(mediaType, "\""); | ||
if (isObject(supportedMediaTypes)) { | ||
// SupportedMediaTypes is a lookup table that maps Transfer Syntax UID | ||
// to one or more Media Types | ||
if (!Object.values(supportedMediaTypes).flat(1).includes(mediaType)) { | ||
if (!mediaType.endsWith("/*") || !mediaType.endsWith("/")) { | ||
throw new Error("Media type ".concat(mediaType, " is not supported for requested resource")); | ||
} | ||
} | ||
if (transferSyntaxUID) { | ||
if (transferSyntaxUID !== "*") { | ||
if (!Object.keys(supportedMediaTypes).includes(transferSyntaxUID)) { | ||
throw new Error("Transfer syntax ".concat(transferSyntaxUID, " is not supported for requested resource")); | ||
} | ||
var expectedMediaTypes = supportedMediaTypes[transferSyntaxUID]; | ||
if (!expectedMediaTypes.includes(mediaType)) { | ||
var actualType = DICOMwebClient._parseMediaType(mediaType)[0]; | ||
expectedMediaTypes.map(function (expectedMediaType) { | ||
var expectedType = DICOMwebClient._parseMediaType(expectedMediaType)[0]; | ||
var haveSameType = actualType === expectedType; | ||
if (haveSameType && (mediaType.endsWith("/*") || mediaType.endsWith("/"))) { | ||
return; | ||
} | ||
throw new Error("Transfer syntax ".concat(transferSyntaxUID, " is not supported for requested resource")); | ||
}); | ||
} | ||
} | ||
fieldValue += "; transfer-syntax=".concat(transferSyntaxUID); | ||
} | ||
} else if (Array.isArray(supportedMediaTypes) && !supportedMediaTypes.includes(mediaType)) { | ||
throw new Error("Media type ".concat(mediaType, " is not supported for requested resource")); | ||
} | ||
fieldValueParts.push(fieldValue); | ||
}); | ||
return fieldValueParts.join(", "); | ||
} | ||
/** | ||
* Builds a range header field value for HTTP GET request messages. | ||
* | ||
* @param {Array} byteRange start and end of byte range | ||
* @returns {String} range header field value | ||
*/ | ||
}, { | ||
key: "_buildRangeHeaderFieldValue", | ||
value: function _buildRangeHeaderFieldValue() { | ||
var byteRange = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; | ||
if (byteRange.length === 1) { | ||
return "bytes=".concat(byteRange[0], "-"); | ||
} | ||
if (byteRange.length === 2) { | ||
return "bytes=".concat(byteRange[0], "-").concat(byteRange[1]); | ||
} | ||
return "bytes=0-"; | ||
} | ||
/** | ||
* Gets common type of acceptable media types and asserts that only | ||
one type is specified. For example, ``("image/jpeg", "image/jp2")`` | ||
will pass, but ``("image/jpeg", "video/mpeg2")`` will raise an | ||
exception. | ||
* @param {String[]} acceptable media types and optionally the UIDs of the | ||
corresponding transfer syntaxes | ||
* | ||
*/ | ||
}, { | ||
key: "_getCommonMediaType", | ||
value: function _getCommonMediaType(mediaTypes) { | ||
if (!mediaTypes || !mediaTypes.length) { | ||
throw new Error("No acceptable media types provided"); | ||
} | ||
var commonMediaTypes = new Set(); | ||
mediaTypes.forEach(function (item) { | ||
var mediaType = item.mediaType; | ||
if (mediaType.startsWith("application")) { | ||
commonMediaTypes.add(mediaType); | ||
} else { | ||
var type = DICOMwebClient._parseMediaType(mediaType)[0]; | ||
commonMediaTypes.add("".concat(type, "/")); | ||
} | ||
}); | ||
if (commonMediaTypes.size === 0) { | ||
throw new Error("No common acceptable media type could be identified."); | ||
} else if (commonMediaTypes.size > 1) { | ||
throw new Error("Acceptable media types must have the same type."); | ||
} | ||
return Array.from(commonMediaTypes)[0]; | ||
} | ||
}]); | ||
@@ -923,5 +1589,5 @@ | ||
return null; | ||
} else { | ||
return str.substring(beforeIndex, afterIndex); | ||
} | ||
return str.substring(beforeIndex, afterIndex); | ||
} | ||
@@ -940,3 +1606,3 @@ | ||
if (!uid) { | ||
console.debug('Study Instance UID could not be dertermined from URI "' + uri + '"'); | ||
console.debug("Study Instance UID could not be dertermined from URI \"".concat(uri, "\"")); | ||
} | ||
@@ -955,3 +1621,3 @@ | ||
if (!uid) { | ||
console.debug('Series Instance UID could not be dertermined from URI "' + uri + '"'); | ||
console.debug("Series Instance UID could not be dertermined from URI \"".concat(uri, "\"")); | ||
} | ||
@@ -974,3 +1640,3 @@ | ||
if (!uid) { | ||
console.debug('SOP Instance UID could not be dertermined from URI"' + uri + '"'); | ||
console.debug("SOP Instance UID could not be dertermined from URI\"".concat(uri, "\"")); | ||
} | ||
@@ -989,9 +1655,9 @@ | ||
if (numbers === undefined) { | ||
console.debug('Frames Numbers could not be dertermined from URI"' + uri + '"'); | ||
console.debug("Frames Numbers could not be dertermined from URI\"".concat(uri, "\"")); | ||
} | ||
return numbers.split(','); | ||
return numbers.split(","); | ||
} | ||
var version = '0.3.2'; | ||
var version = "0.5.0"; | ||
@@ -998,0 +1664,0 @@ var api = { |
{ | ||
"name": "dicomweb-client", | ||
"version": "0.4.4", | ||
"version": "0.5.0", | ||
"description": "Implementation of DICOMweb client code", | ||
@@ -13,3 +13,4 @@ "main": "build/dicomweb-client.js", | ||
"watch": "rollup -c -w", | ||
"version": "node -p -e \"'export default \\'' + require('./package.json').version + '\\';'\" > src/version.js" | ||
"version": "node -p -e \"'export default \\'' + require('./package.json').version + '\\';'\" > src/version.js", | ||
"lint": "eslint -c .eslintrc.js --fix src && prettier --write src/**/*.js" | ||
}, | ||
@@ -38,3 +39,6 @@ "repository": { | ||
"chai": "^4.1.2", | ||
"karma": "^3.0.0", | ||
"eslint": "^5.16.0", | ||
"eslint-config-airbnb-base": "^13.1.0", | ||
"eslint-plugin-import": "^2.16.0", | ||
"karma": "^4.2.0", | ||
"karma-chai": "^0.1.0", | ||
@@ -44,3 +48,4 @@ "karma-chrome-launcher": "^2.2.0", | ||
"mocha": "^5.2.0", | ||
"puppeteer": "^1.8.0", | ||
"prettier": "^1.16.4", | ||
"puppeteer": "^1.18.1", | ||
"rollup": "^0.63.2", | ||
@@ -47,0 +52,0 @@ "rollup-plugin-babel": "^4.0.3" |
1199
src/api.js
@@ -1,45 +0,49 @@ | ||
import { | ||
containsToken, | ||
findToken, | ||
identifyBoundary, | ||
uint8ArrayToString, | ||
stringToUint8Array, | ||
multipartEncode, | ||
multipartDecode | ||
} from './message.js'; | ||
import { multipartEncode, multipartDecode } from "./message.js"; | ||
function isEmptyObject (obj) { | ||
return Object.keys(obj).length === 0 && obj.constructor === Object; | ||
function isObject(obj) { | ||
return typeof obj === "object" && obj !== null; | ||
} | ||
function isEmptyObject(obj) { | ||
return Object.keys(obj).length === 0 && obj.constructor === Object; | ||
} | ||
const getFirstResult = result => result[0]; | ||
const getFirstResultIfLengthGtOne = result => { | ||
if (result.length > 1) { | ||
return result; | ||
} | ||
const MIMETYPES = { | ||
DICOM: 'application/dicom', | ||
DICOM_JSON: 'application/dicom+json', | ||
OCTET_STREAM: 'application/octet-stream', | ||
JPEG: 'image/jpeg', | ||
PNG: 'image/png' | ||
return result[0] | ||
}; | ||
const MEDIATYPES = { | ||
DICOM: "application/dicom", | ||
DICOM_JSON: "application/dicom+json", | ||
OCTET_STREAM: "application/octet-stream", | ||
PDF: "application/pdf", | ||
JPEG: "image/jpeg", | ||
PNG: "image/png" | ||
}; | ||
/** | ||
* Class for interacting with DICOMweb RESTful services. | ||
*/ | ||
* Class for interacting with DICOMweb RESTful services. | ||
*/ | ||
class DICOMwebClient { | ||
/** | ||
* @constructor | ||
* @param {Object} options (choices: "url", "username", "password", "headers") | ||
*/ | ||
* @constructor | ||
* @param {Object} options (choices: "url", "username", "password", "headers") | ||
*/ | ||
constructor(options) { | ||
this.baseURL = options.url; | ||
if (!this.baseURL) { | ||
console.error('no DICOMweb base url provided - calls will fail') | ||
console.error("no DICOMweb base url provided - calls will fail"); | ||
} | ||
if ('username' in options) { | ||
if ("username" in options) { | ||
this.username = options.username; | ||
if (!('password' in options)) { | ||
console.error('no password provided to authenticate with DICOMweb service') | ||
if (!("password" in options)) { | ||
console.error( | ||
"no password provided to authenticate with DICOMweb service" | ||
); | ||
} | ||
@@ -49,5 +53,5 @@ this.password = options.password; | ||
if ('qidoURLPrefix' in options) { | ||
if ("qidoURLPrefix" in options) { | ||
console.log(`use URL prefix for QIDO-RS: ${options.qidoURLPrefix}`); | ||
this.qidoURL = this.baseURL + '/' + options.qidoURLPrefix; | ||
this.qidoURL = `${this.baseURL}/${options.qidoURLPrefix}`; | ||
} else { | ||
@@ -57,5 +61,5 @@ this.qidoURL = this.baseURL; | ||
if ('wadoURLPrefix' in options) { | ||
if ("wadoURLPrefix" in options) { | ||
console.log(`use URL prefix for WADO-RS: ${options.wadoURLPrefix}`); | ||
this.wadoURL = this.baseURL + '/' + options.wadoURLPrefix; | ||
this.wadoURL = `${this.baseURL}/${options.wadoURLPrefix}`; | ||
} else { | ||
@@ -65,5 +69,5 @@ this.wadoURL = this.baseURL; | ||
if ('stowURLPrefix' in options) { | ||
if ("stowURLPrefix" in options) { | ||
console.log(`use URL prefix for STOW-RS: ${options.stowURLPrefix}`); | ||
this.stowURL = this.baseURL + '/' + options.stowURLPrefix; | ||
this.stowURL = `${this.baseURL}/${options.stowURLPrefix}`; | ||
} else { | ||
@@ -76,23 +80,23 @@ this.stowURL = this.baseURL; | ||
static _parseQueryParameters(params={}) { | ||
let queryString = '?'; | ||
Object.keys(params).forEach(function (key, index) { | ||
static _parseQueryParameters(params = {}) { | ||
let queryString = "?"; | ||
Object.keys(params).forEach((key, index) => { | ||
if (index !== 0) { | ||
queryString += '&' | ||
queryString += "&"; | ||
} | ||
queryString += key + '=' + encodeURIComponent(params[key]); | ||
queryString += `${key}=${encodeURIComponent(params[key])}`; | ||
}); | ||
return queryString | ||
return queryString; | ||
} | ||
_httpRequest(url, method, headers, options={}) { | ||
return new Promise( (resolve, reject) => { | ||
_httpRequest(url, method, headers, options = {}) { | ||
return new Promise((resolve, reject) => { | ||
const request = new XMLHttpRequest(); | ||
request.open(method, url, true); | ||
if ('responseType' in options) { | ||
if ("responseType" in options) { | ||
request.responseType = options.responseType; | ||
} | ||
if (typeof(headers) === 'object') { | ||
Object.keys(headers).forEach(function (key) { | ||
if (typeof headers === "object") { | ||
Object.keys(headers).forEach(key => { | ||
request.setRequestHeader(key, headers[key]); | ||
@@ -105,3 +109,3 @@ }); | ||
const userHeaders = this.headers; | ||
Object.keys(userHeaders).forEach(function (key) { | ||
Object.keys(userHeaders).forEach(key => { | ||
request.setRequestHeader(key, userHeaders[key]); | ||
@@ -111,13 +115,13 @@ }); | ||
// Event triggered when upload starts | ||
request.onloadstart = function (event) { | ||
//console.log('upload started: ', url) | ||
request.onloadstart = function onloadstart() { | ||
// console.log('upload started: ', url) | ||
}; | ||
// Event triggered when upload ends | ||
request.onloadend = function (event) { | ||
//console.log('upload finished') | ||
request.onloadend = function onloadend() { | ||
// console.log('upload finished') | ||
}; | ||
// Handle response message | ||
request.onreadystatechange = function (event) { | ||
request.onreadystatechange = function onreadystatechange() { | ||
if (request.readyState === 4) { | ||
@@ -127,10 +131,10 @@ if (request.status === 200) { | ||
} else if (request.status === 202) { | ||
console.warn('some resources already existed: ', request); | ||
console.warn("some resources already existed: ", request); | ||
resolve(request.response); | ||
} else if (request.status === 204) { | ||
console.warn('empty response for request: ', request); | ||
console.warn("empty response for request: ", request); | ||
resolve([]); | ||
} else { | ||
console.error('request failed: ', request); | ||
const error = new Error('request failed'); | ||
console.error("request failed: ", request); | ||
const error = new Error("request failed"); | ||
error.request = request; | ||
@@ -148,4 +152,4 @@ error.response = request.response; | ||
// Event triggered while download progresses | ||
if ('progressCallback' in options) { | ||
if (typeof(options.progressCallback) === 'function') { | ||
if ("progressCallback" in options) { | ||
if (typeof options.progressCallback === "function") { | ||
request.onprogress = options.progressCallback; | ||
@@ -167,3 +171,3 @@ } | ||
if ('data' in options) { | ||
if ("data" in options) { | ||
request.send(options.data); | ||
@@ -177,36 +181,429 @@ } else { | ||
_httpGet(url, headers, responseType, progressCallback) { | ||
return this._httpRequest(url, 'get', headers, {responseType, progressCallback}); | ||
return this._httpRequest(url, "get", headers, { | ||
responseType, | ||
progressCallback | ||
}); | ||
} | ||
_httpGetApplicationJson(url, params={}, progressCallback) { | ||
if (typeof(params) === 'object') { | ||
_httpGetApplicationJson(url, params = {}, progressCallback) { | ||
let urlWithQueryParams = url; | ||
if (typeof params === "object") { | ||
if (!isEmptyObject(params)) { | ||
url += DICOMwebClient._parseQueryParameters(params) | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
} | ||
const headers = {'Accept': MIMETYPES.DICOM_JSON}; | ||
const responseType = 'json'; | ||
return this._httpGet(url, headers, responseType, progressCallback); | ||
const headers = { Accept: MEDIATYPES.DICOM_JSON }; | ||
const responseType = "json"; | ||
return this._httpGet( | ||
urlWithQueryParams, | ||
headers, | ||
responseType, | ||
progressCallback | ||
); | ||
} | ||
_httpGetByMimeType(url, mimeType, params, responseType='arraybuffer', progressCallback) { | ||
if (typeof(params) === 'object') { | ||
/** | ||
* Performs an HTTP GET request that accepts a message with | ||
"application/pdf" media type. | ||
* @param {String} url | ||
* @param {Object[]} mediaTypes | ||
* @param {Object} params | ||
* @param {Function} progressCallback | ||
* @return {*} | ||
* @private | ||
*/ | ||
_httpGetApplicationPdf(url, params = {}, progressCallback) { | ||
let urlWithQueryParams = url; | ||
if (typeof params === "object") { | ||
if (!isEmptyObject(params)) { | ||
url += DICOMwebClient._parseQueryParameters(params) | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
} | ||
const headers = { Accept: MEDIATYPES.PDF }; | ||
const responseType = "json"; | ||
return this._httpGet( | ||
urlWithQueryParams, | ||
headers, | ||
responseType, | ||
progressCallback | ||
); | ||
} | ||
const headers = { | ||
'Accept': `multipart/related; type="${mimeType}"` | ||
/** | ||
* 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 | ||
*/ | ||
_httpGetImage(url, mediaTypes, params = {}, progressCallback) { | ||
let urlWithQueryParams = url; | ||
if (typeof params === "object") { | ||
if (!isEmptyObject(params)) { | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
} | ||
const supportedMediaTypes = [ | ||
"image/", | ||
"image/*", | ||
"image/jpeg", | ||
"image/jp2", | ||
"image/gif", | ||
"image/png" | ||
]; | ||
const acceptHeaderFieldValue = DICOMwebClient._buildAcceptHeaderFieldValue( | ||
mediaTypes, | ||
supportedMediaTypes | ||
); | ||
const headers = { Accept: acceptHeaderFieldValue }; | ||
const responseType = "arraybuffer"; | ||
return this._httpGet( | ||
urlWithQueryParams, | ||
headers, | ||
responseType, | ||
progressCallback | ||
); | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
_httpGetText(url, mediaTypes, params = {}, progressCallback) { | ||
let urlWithQueryParams = url; | ||
if (typeof params === "object") { | ||
if (!isEmptyObject(params)) { | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
} | ||
const supportedMediaTypes = [ | ||
"text/", | ||
"text/*", | ||
"text/html", | ||
"text/plain", | ||
"text/rtf", | ||
"text/xml" | ||
]; | ||
const acceptHeaderFieldValue = DICOMwebClient._buildAcceptHeaderFieldValue( | ||
mediaTypes, | ||
supportedMediaTypes | ||
); | ||
const headers = { Accept: acceptHeaderFieldValue }; | ||
const responseType = "arraybuffer"; | ||
return this._httpGet( | ||
urlWithQueryParams, | ||
headers, | ||
responseType, | ||
progressCallback | ||
); | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
_httpGetVideo(url, mediaTypes, params = {}, progressCallback) { | ||
let urlWithQueryParams = url; | ||
if (typeof params === "object") { | ||
if (!isEmptyObject(params)) { | ||
urlWithQueryParams += DICOMwebClient._parseQueryParameters(params); | ||
} | ||
} | ||
const supportedMediaTypes = [ | ||
"video/", | ||
"video/*", | ||
"video/mpeg", | ||
"video/mp4", | ||
"video/H265" | ||
]; | ||
const acceptHeaderFieldValue = DICOMwebClient._buildAcceptHeaderFieldValue( | ||
mediaTypes, | ||
supportedMediaTypes | ||
); | ||
const headers = { Accept: acceptHeaderFieldValue }; | ||
const responseType = "arraybuffer"; | ||
return this._httpGet( | ||
urlWithQueryParams, | ||
headers, | ||
responseType, | ||
progressCallback | ||
); | ||
} | ||
/** | ||
* Asserts that a given media type is valid. | ||
* | ||
* @params {String} mediaType media type | ||
*/ | ||
static _assertMediaTypeIsValid(mediaType) { | ||
if (!mediaType) { | ||
throw new Error(`Not a valid media type: ${mediaType}`); | ||
} | ||
const sepIndex = mediaType.indexOf("/"); | ||
if (sepIndex === -1) { | ||
throw new Error(`Not a valid media type: ${mediaType}`); | ||
} | ||
const mediaTypeType = mediaType.slice(0, sepIndex); | ||
const types = ["application", "image", "text", "video"]; | ||
if (!types.includes(mediaTypeType)) { | ||
throw new Error(`Not a valid media type: ${mediaType}`); | ||
} | ||
if (mediaType.slice(sepIndex + 1).includes("/")) { | ||
throw new Error(`Not a valid media type: ${mediaType}`); | ||
} | ||
} | ||
/** | ||
* 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 {Array} content of HTTP message body parts | ||
*/ | ||
_httpGetMultipartImage( | ||
url, | ||
mediaTypes, | ||
byteRange, | ||
params, | ||
rendered = false, | ||
progressCallback | ||
) { | ||
const headers = {}; | ||
let 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).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 {Array} content of HTTP message body parts | ||
*/ | ||
_httpGetMultipartVideo( | ||
url, | ||
mediaTypes, | ||
byteRange, | ||
params, | ||
rendered = false, | ||
progressCallback | ||
) { | ||
const headers = {}; | ||
let 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).then( | ||
multipartDecode | ||
); | ||
} | ||
/** | ||
* Performs a HTTP GET request that accepts a multipart message with "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 {Array} content of HTTP message body parts | ||
*/ | ||
_httpGetMultipartApplicationDicom(url, mediaTypes, params, progressCallback) { | ||
const headers = {}; | ||
const defaultMediaType = "application/dicom"; | ||
const 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] | ||
}; | ||
return this._httpGet(url, headers, responseType, progressCallback); | ||
let acceptableMediaTypes = mediaTypes; | ||
if (!mediaTypes) { | ||
acceptableMediaTypes = [{ mediaType: defaultMediaType }]; | ||
} | ||
headers.Accept = DICOMwebClient._buildMultipartAcceptHeaderFieldValue( | ||
acceptableMediaTypes, | ||
supportedMediaTypes | ||
); | ||
return this._httpGet(url, headers, "arraybuffer", progressCallback).then( | ||
multipartDecode | ||
); | ||
} | ||
/** | ||
* Performs a HTTP GET request that accepts a multipart message with "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 {Array} content of HTTP message body parts | ||
*/ | ||
_httpGetMultipartApplicationOctetStream( | ||
url, | ||
mediaTypes, | ||
byteRange, | ||
params, | ||
progressCallback | ||
) { | ||
const headers = {}; | ||
const defaultMediaType = "application/octet-stream"; | ||
const supportedMediaTypes = { | ||
"1.2.840.10008.1.2.1": [defaultMediaType] | ||
}; | ||
let 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).then( | ||
multipartDecode | ||
); | ||
} | ||
_httpPost(url, headers, data, progressCallback) { | ||
return this._httpRequest(url, 'post', headers, {data, progressCallback}); | ||
return this._httpRequest(url, "post", headers, { | ||
data, | ||
progressCallback | ||
}); | ||
} | ||
_httpPostApplicationJson(url, data, progressCallback) { | ||
const headers = {'Content-Type': MIMETYPES.DICOM_JSON}; | ||
const headers = { "Content-Type": MEDIATYPES.DICOM_JSON }; | ||
return this._httpPost(url, headers, data, progressCallback); | ||
@@ -216,2 +613,185 @@ } | ||
/** | ||
* Parses media type and extracts its type and subtype. | ||
* | ||
* @param mediaType e.g. image/jpeg | ||
* @private | ||
*/ | ||
static _parseMediaType(mediaType) { | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
return mediaType.split("/"); | ||
} | ||
/** | ||
* Builds an accept header field value for HTTP GET request messages. | ||
* | ||
* @param {Object[]} mediaTypes Acceptable media types | ||
* @param {Object[]} supportedMediaTypes Supported media types | ||
* @return {*} | ||
* @private | ||
*/ | ||
static _buildAcceptHeaderFieldValue(mediaTypes, supportedMediaTypes) { | ||
if (!Array.isArray(mediaTypes)) { | ||
throw new Error("Acceptable media types must be provided as an Array"); | ||
} | ||
const fieldValueParts = mediaTypes.map(item => { | ||
const { mediaType } = item; | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
if (!supportedMediaTypes.includes(mediaType)) { | ||
throw new Error( | ||
`Media type ${mediaType} is not supported for requested resource` | ||
); | ||
} | ||
return mediaType; | ||
}); | ||
return fieldValueParts.join(", "); | ||
} | ||
/** | ||
* Builds an accept header field value for HTTP GET multipart request | ||
messages. | ||
* | ||
* @param {Object[]} mediaTypes Acceptable media types | ||
* @param {Object[]} supportedMediaTypes Supported media types | ||
* @private | ||
*/ | ||
static _buildMultipartAcceptHeaderFieldValue( | ||
mediaTypes, | ||
supportedMediaTypes | ||
) { | ||
if (!Array.isArray(mediaTypes)) { | ||
throw new Error("Acceptable media types must be provided as an Array"); | ||
} | ||
if (!Array.isArray(supportedMediaTypes) && !isObject(supportedMediaTypes)) { | ||
throw new Error( | ||
"Supported media types must be provided as an Array or an Object" | ||
); | ||
} | ||
const fieldValueParts = []; | ||
mediaTypes.forEach(item => { | ||
const { transferSyntaxUID, mediaType } = item; | ||
DICOMwebClient._assertMediaTypeIsValid(mediaType); | ||
let fieldValue = `multipart/related; type="${mediaType}"`; | ||
if (isObject(supportedMediaTypes)) { | ||
// SupportedMediaTypes is a lookup table that maps Transfer Syntax UID | ||
// to one or more Media Types | ||
if (!Object.values(supportedMediaTypes).flat(1).includes(mediaType)) { | ||
if (!mediaType.endsWith("/*") || !mediaType.endsWith("/")) { | ||
throw new Error( | ||
`Media type ${mediaType} is not supported for requested resource` | ||
); | ||
} | ||
} | ||
if (transferSyntaxUID) { | ||
if (transferSyntaxUID !== "*") { | ||
if (!Object.keys(supportedMediaTypes).includes(transferSyntaxUID)) { | ||
throw new Error( | ||
`Transfer syntax ${transferSyntaxUID} is not supported for requested resource` | ||
); | ||
} | ||
const expectedMediaTypes = supportedMediaTypes[transferSyntaxUID]; | ||
if (!expectedMediaTypes.includes(mediaType)) { | ||
const actualType = DICOMwebClient._parseMediaType(mediaType)[0]; | ||
expectedMediaTypes.map(expectedMediaType => { | ||
const expectedType = DICOMwebClient._parseMediaType( | ||
expectedMediaType | ||
)[0]; | ||
const haveSameType = actualType === expectedType; | ||
if ( | ||
haveSameType && | ||
(mediaType.endsWith("/*") || mediaType.endsWith("/")) | ||
) { | ||
return; | ||
} | ||
throw new Error( | ||
`Transfer syntax ${transferSyntaxUID} is not supported for requested resource` | ||
); | ||
}) | ||
} | ||
} | ||
fieldValue += `; transfer-syntax=${transferSyntaxUID}`; | ||
} | ||
} else if ( | ||
Array.isArray(supportedMediaTypes) && | ||
!supportedMediaTypes.includes(mediaType) | ||
) { | ||
throw new Error( | ||
`Media type ${mediaType} is not supported for requested resource` | ||
); | ||
} | ||
fieldValueParts.push(fieldValue); | ||
}); | ||
return fieldValueParts.join(", "); | ||
} | ||
/** | ||
* Builds a range header field value for HTTP GET request messages. | ||
* | ||
* @param {Array} byteRange start and end of byte range | ||
* @returns {String} range header field value | ||
*/ | ||
static _buildRangeHeaderFieldValue(byteRange = []) { | ||
if (byteRange.length === 1) { | ||
return `bytes=${byteRange[0]}-`; | ||
} | ||
if (byteRange.length === 2) { | ||
return `bytes=${byteRange[0]}-${byteRange[1]}`; | ||
} | ||
return "bytes=0-"; | ||
} | ||
/** | ||
* Gets common type of acceptable media types and asserts that only | ||
one type is specified. For example, ``("image/jpeg", "image/jp2")`` | ||
will pass, but ``("image/jpeg", "video/mpeg2")`` will raise an | ||
exception. | ||
* @param {String[]} acceptable media types and optionally the UIDs of the | ||
corresponding transfer syntaxes | ||
* | ||
*/ | ||
static _getCommonMediaType(mediaTypes) { | ||
if (!mediaTypes || !mediaTypes.length) { | ||
throw new Error("No acceptable media types provided"); | ||
} | ||
const commonMediaTypes = new Set(); | ||
mediaTypes.forEach(item => { | ||
const { mediaType } = item; | ||
if (mediaType.startsWith("application")) { | ||
commonMediaTypes.add(mediaType); | ||
} else { | ||
const type = DICOMwebClient._parseMediaType(mediaType)[0]; | ||
commonMediaTypes.add(`${type}/`); | ||
} | ||
}); | ||
if (commonMediaTypes.size === 0) { | ||
throw new Error("No common acceptable media type could be identified."); | ||
} else if (commonMediaTypes.size > 1) { | ||
throw new Error("Acceptable media types must have the same type."); | ||
} | ||
return Array.from(commonMediaTypes)[0]; | ||
} | ||
/** | ||
* Searches for DICOM studies. | ||
@@ -221,10 +801,9 @@ * @param {Object} options options object | ||
*/ | ||
searchForStudies(options={}) { | ||
console.log('search for studies'); | ||
let url = this.qidoURL + | ||
'/studies'; | ||
if ('queryParams' in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
searchForStudies(options = {}) { | ||
console.log("search for studies"); | ||
let url = `${this.qidoURL}/studies`; | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
} | ||
return(this._httpGetApplicationJson(url)); | ||
return this._httpGetApplicationJson(url); | ||
} | ||
@@ -235,13 +814,14 @@ | ||
* @param {Object} options options object | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance belonging to the study | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance | ||
belonging to the study | ||
*/ | ||
retrieveStudyMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of study metadata') | ||
if (!("studyInstanceUID" in options)) { | ||
throw new Error( | ||
"Study Instance UID is required for retrieval of study metadata" | ||
); | ||
} | ||
console.log(`retrieve metadata of study ${options.studyInstanceUID}`); | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID + | ||
'/metadata'; | ||
return(this._httpGetApplicationJson(url)); | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/metadata`; | ||
return this._httpGetApplicationJson(url); | ||
} | ||
@@ -254,13 +834,13 @@ | ||
*/ | ||
searchForSeries(options={}) { | ||
searchForSeries(options = {}) { | ||
let url = this.qidoURL; | ||
if ('studyInstanceUID' in options) { | ||
if ("studyInstanceUID" in options) { | ||
console.log(`search series of study ${options.studyInstanceUID}`); | ||
url += '/studies/' + options.studyInstanceUID; | ||
url += `/studies/${options.studyInstanceUID}`; | ||
} | ||
url += '/series'; | ||
if ('queryParams' in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
url += "/series"; | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
} | ||
return(this._httpGetApplicationJson(url)); | ||
return this._httpGetApplicationJson(url); | ||
} | ||
@@ -271,18 +851,22 @@ | ||
* @param {Object} options options object | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance belonging to the series | ||
* @returns {Array} metadata elements in DICOM JSON format for each instance | ||
belonging to the series | ||
*/ | ||
retrieveSeriesMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of series metadata') | ||
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') | ||
if (!("seriesInstanceUID" in options)) { | ||
throw new Error( | ||
"Series Instance UID is required for retrieval of series metadata" | ||
); | ||
} | ||
console.log(`retrieve metadata of series ${options.seriesInstanceUID}`); | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID + | ||
'/series/' + options.seriesInstanceUID + | ||
'/metadata'; | ||
return(this._httpGetApplicationJson(url)); | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ | ||
options.seriesInstanceUID | ||
}/metadata`; | ||
return this._httpGetApplicationJson(url); | ||
} | ||
@@ -295,20 +879,24 @@ | ||
*/ | ||
searchForInstances(options={}) { | ||
searchForInstances(options = {}) { | ||
let url = this.qidoURL; | ||
if ('studyInstanceUID' in options) { | ||
url += '/studies/' + options.studyInstanceUID; | ||
if ('seriesInstanceUID' in options) { | ||
console.log(`search for instances of series ${options.seriesInstanceUID}`); | ||
url += '/series/' + options.seriesInstanceUID; | ||
if ("studyInstanceUID" in options) { | ||
url += `/studies/${options.studyInstanceUID}`; | ||
if ("seriesInstanceUID" in options) { | ||
console.log( | ||
`search for instances of series ${options.seriesInstanceUID}` | ||
); | ||
url += `/series/${options.seriesInstanceUID}`; | ||
} else { | ||
console.log(`search for instances of study ${options.studyInstanceUID}`); | ||
console.log( | ||
`search for instances of study ${options.studyInstanceUID}` | ||
); | ||
} | ||
} else { | ||
console.log('search for instances'); | ||
console.log("search for instances"); | ||
} | ||
url += '/instances'; | ||
if ('queryParams' in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
url += "/instances"; | ||
if ("queryParams" in options) { | ||
url += DICOMwebClient._parseQueryParameters(options.queryParams); | ||
} | ||
return(this._httpGetApplicationJson(url)); | ||
return this._httpGetApplicationJson(url); | ||
} | ||
@@ -321,17 +909,17 @@ | ||
buildInstanceWadoURIUrl(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required.') | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required."); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required.') | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required."); | ||
} | ||
const contentType = options.contentType || MIMETYPES.DICOM; | ||
const transferSyntax = options.transferSyntax || '*'; | ||
const contentType = options.contentType || MEDIATYPES.DICOM; | ||
const transferSyntax = options.transferSyntax || "*"; | ||
const params = []; | ||
params.push('requestType=WADO'); | ||
params.push("requestType=WADO"); | ||
params.push(`studyUID=${options.studyInstanceUID}`); | ||
@@ -343,3 +931,3 @@ params.push(`seriesUID=${options.seriesInstanceUID}`); | ||
const paramString = params.join('&'); | ||
const paramString = params.join("&"); | ||
@@ -356,17 +944,21 @@ return `${this.wadoURL}?${paramString}`; | ||
retrieveInstanceMetadata(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of instance metadata') | ||
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 (!("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') | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error( | ||
"SOP Instance UID is required for retrieval of instance metadata" | ||
); | ||
} | ||
console.log(`retrieve metadata of instance ${options.sopInstanceUID}`); | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID + | ||
'/series/' + options.seriesInstanceUID + | ||
'/instances/' + options.sopInstanceUID + | ||
'/metadata'; | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ | ||
options.seriesInstanceUID | ||
}/instances/${options.sopInstanceUID}/metadata`; | ||
@@ -382,27 +974,106 @@ return this._httpGetApplicationJson(url); | ||
retrieveInstanceFrames(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of instance frames') | ||
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 (!("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 (!("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') | ||
if (!("frameNumbers" in options)) { | ||
throw new Error( | ||
"frame numbers are required for retrieval of instance frames" | ||
); | ||
} | ||
console.log(`retrieve frames ${options.frameNumbers.toString()} of instance ${options.sopInstanceUID}`) | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID + | ||
'/series/' + options.seriesInstanceUID + | ||
'/instances/' + options.sopInstanceUID + | ||
'/frames/' + options.frameNumbers.toString(); | ||
console.log( | ||
`retrieve frames ${options.frameNumbers.toString()} of instance ${ | ||
options.sopInstanceUID | ||
}` | ||
); | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ | ||
options.seriesInstanceUID | ||
}/instances/${ | ||
options.sopInstanceUID | ||
}/frames/${options.frameNumbers.toString()}`; | ||
const mimeType = options.mimeType ? `${options.mimeType}` : MIMETYPES.OCTET_STREAM; | ||
const { mediaTypes } = options; | ||
return this._httpGetByMimeType(url, mimeType).then(multipartDecode); | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationOctetStream(url); | ||
} | ||
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
throw new Error( | ||
`Media type ${commonMediaType} is not supported for retrieval of frames.` | ||
); | ||
} | ||
/** | ||
* Retrieves an individual, server-side rendered DICOM instance. | ||
* | ||
* @param {Object} options options object | ||
* @returns {Array} frame items as byte arrays of the pixel data element | ||
*/ | ||
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" | ||
); | ||
} | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ | ||
options.seriesInstanceUID | ||
}/instances/${options.sopInstanceUID}/rendered`; | ||
const { mediaTypes, params } = options; | ||
const headers = {}; | ||
if (!mediaTypes) { | ||
const responseType = "arraybuffer"; | ||
return this._httpGet(url, headers, responseType); | ||
} | ||
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType.startsWith("image")) { | ||
return this._httpGetImage(url, mediaTypes, params); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetVideo(url, mediaTypes, params); | ||
} else if (commonMediaType.startsWith("text")) { | ||
return this._httpGetText(url, mediaTypes, params); | ||
} else if (commonMediaType === MEDIATYPES.PDF) { | ||
return this._httpGetApplicationPdf(url, params); | ||
} | ||
throw new Error( | ||
`Media type ${commonMediaType} is not supported for retrieval of rendered instance.` | ||
); | ||
} | ||
/** | ||
* Retrieves rendered frames for a DICOM instance. | ||
@@ -413,32 +1084,52 @@ * @param {Object} options options object | ||
retrieveInstanceFramesRendered(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required for retrieval of rendered instance frames') | ||
if (!("studyInstanceUID" in options)) { | ||
throw new Error( | ||
"Study Instance UID is required for retrieval of rendered instance frames" | ||
); | ||
} | ||
if (!('seriesInstanceUID' in options)) { | ||
throw new Error('Series Instance UID is required for retrieval of rendered instance frames') | ||
if (!("seriesInstanceUID" in options)) { | ||
throw new Error( | ||
"Series Instance UID is required for retrieval of rendered instance frames" | ||
); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required for retrieval of rendered instance frames') | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error( | ||
"SOP Instance UID is required for retrieval of rendered instance frames" | ||
); | ||
} | ||
if (!('frameNumbers' in options)) { | ||
throw new Error('frame numbers are required for retrieval of rendered instance frames') | ||
if (!("frameNumbers" in options)) { | ||
throw new Error( | ||
"frame numbers are required for retrieval of rendered instance frames" | ||
); | ||
} | ||
console.log(`retrieve rendered frames ${options.frameNumbers.toString()} of instance ${options.sopInstanceUID}`) | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID + | ||
'/series/' + options.seriesInstanceUID + | ||
'/instances/' + options.sopInstanceUID + | ||
'/frames/' + options.frameNumbers.toString() + | ||
'/rendered'; | ||
console.debug( | ||
`retrieve rendered frames ${options.frameNumbers.toString()} of instance ${ | ||
options.sopInstanceUID | ||
}` | ||
); | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ | ||
options.seriesInstanceUID | ||
}/instances/${ | ||
options.sopInstanceUID | ||
}/frames/${options.frameNumbers.toString()}/rendered`; | ||
let headers = {}; | ||
// The choice of an acceptable media type depends on a variety of things: | ||
// http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_6.html#table_6.1.1-3 | ||
if ('mimeType' in options) { | ||
headers['Accept'] = options.mimeType; | ||
const { mediaTypes } = options; | ||
const headers = {}; | ||
if (!mediaTypes) { | ||
const responseType = "arraybuffer"; | ||
return this._httpGet(url, headers, responseType); | ||
} | ||
const responseType = 'arraybuffer'; | ||
return this._httpGet(url, headers, responseType); | ||
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType.startsWith("image")) { | ||
return this._httpGetImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetVideo(url, mediaTypes); | ||
} | ||
throw new Error( | ||
`Media type ${commonMediaType} is not supported for retrieval of rendered frame.` | ||
); | ||
} | ||
@@ -449,22 +1140,42 @@ | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer} DICOM Part 10 file as Arraybuffer | ||
* @returns {ArrayBuffer} DICOM Part 10 file as Arraybuffer | ||
*/ | ||
retrieveInstance(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required') | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required"); | ||
} | ||
if (!('sopInstanceUID' in options)) { | ||
throw new Error('SOP Instance UID is required') | ||
if (!("sopInstanceUID" in options)) { | ||
throw new Error("SOP Instance UID is required"); | ||
} | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID + | ||
'/series/' + options.seriesInstanceUID + | ||
'/instances/' + options.sopInstanceUID; | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ | ||
options.seriesInstanceUID | ||
}/instances/${options.sopInstanceUID}`; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM) | ||
.then(multipartDecode) | ||
.then(getFirstResult); | ||
const { mediaTypes } = options; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url).then(getFirstResult); | ||
} | ||
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes).then( | ||
getFirstResult | ||
); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes).then( | ||
getFirstResult | ||
); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes).then(getFirstResultIfLengthGtOne); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes).then(getFirstResultIfLengthGtOne); | ||
} | ||
throw new Error( | ||
`Media type ${commonMediaType} is not supported for retrieval of instance.` | ||
); | ||
} | ||
@@ -475,16 +1186,36 @@ | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
* @returns {ArrayBuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
*/ | ||
retrieveSeries(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required') | ||
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 (!("seriesInstanceUID" in options)) { | ||
throw new Error("Series Instance UID is required"); | ||
} | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID + | ||
'/series/' + options.seriesInstanceUID; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode); | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}/series/${ | ||
options.seriesInstanceUID | ||
}`; | ||
const { mediaTypes } = options; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url); | ||
} | ||
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
throw new Error( | ||
`Media type ${commonMediaType} is not supported for retrieval of series.` | ||
); | ||
} | ||
@@ -495,13 +1226,31 @@ | ||
* @param {Object} options options object | ||
* @returns {Arraybuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
* @returns {ArrayBuffer[]} Array of DICOM Part 10 files as Arraybuffers | ||
*/ | ||
retrieveStudy(options) { | ||
if (!('studyInstanceUID' in options)) { | ||
throw new Error('Study Instance UID is required') | ||
if (!("studyInstanceUID" in options)) { | ||
throw new Error("Study Instance UID is required"); | ||
} | ||
const url = this.wadoURL + | ||
'/studies/' + options.studyInstanceUID; | ||
const url = `${this.wadoURL}/studies/${options.studyInstanceUID}`; | ||
return this._httpGetByMimeType(url, MIMETYPES.DICOM).then(multipartDecode); | ||
const { mediaTypes } = options; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationDicom(url); | ||
} | ||
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.DICOM) { | ||
return this._httpGetMultipartApplicationDicom(url, mediaTypes); | ||
} else if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes); | ||
} else if (commonMediaType.startsWith("video")) { | ||
return this._httpGetMultipartVideo(url, mediaTypes); | ||
} | ||
throw new Error( | ||
`Media type ${commonMediaType} is not supported for retrieval of study.` | ||
); | ||
} | ||
@@ -520,9 +1269,32 @@ | ||
retrieveBulkData(options) { | ||
if (!('BulkDataURI' in options)) { | ||
throw new Error('BulkDataURI is required.'); | ||
if (!("BulkDataURI" in options)) { | ||
throw new Error("BulkDataURI is required."); | ||
} | ||
return this._httpGetByMimeType(options.BulkDataURI, MIMETYPES.OCTET_STREAM) | ||
.then(multipartDecode) | ||
.then(getFirstResult); | ||
const url = options.BulkDataURI; | ||
const { mediaTypes, byteRange } = options; | ||
if (!mediaTypes) { | ||
return this._httpGetMultipartApplicationOctetStream( | ||
url, | ||
mediaTypes, | ||
byteRange | ||
); | ||
} | ||
const commonMediaType = DICOMwebClient._getCommonMediaType(mediaTypes); | ||
if (commonMediaType === MEDIATYPES.OCTET_STREAM) { | ||
return this._httpGetMultipartApplicationOctetStream( | ||
url, | ||
mediaTypes, | ||
byteRange | ||
); | ||
} else if (commonMediaType.startsWith("image")) { | ||
return this._httpGetMultipartImage(url, mediaTypes, byteRange); | ||
} | ||
throw new Error( | ||
`Media type ${commonMediaType} is not supported for retrieval of bulk data.` | ||
); | ||
} | ||
@@ -536,8 +1308,8 @@ | ||
storeInstances(options) { | ||
if (!('datasets' in options)) { | ||
throw new Error('datasets are required for storing') | ||
if (!("datasets" in options)) { | ||
throw new Error("datasets are required for storing"); | ||
} | ||
let url = `${this.stowURL}/studies`; | ||
if ('studyInstanceUID' in options) { | ||
if ("studyInstanceUID" in options) { | ||
url += `/${options.studyInstanceUID}`; | ||
@@ -548,3 +1320,3 @@ } | ||
const headers = { | ||
'Content-Type': `multipart/related; type=application/dicom; boundary=${boundary}` | ||
"Content-Type": `multipart/related; type=application/dicom; boundary=${boundary}` | ||
}; | ||
@@ -557,1 +1329,2 @@ | ||
export { DICOMwebClient }; | ||
export default DICOMwebClient; |
@@ -1,11 +0,13 @@ | ||
import { DICOMwebClient } from './api.js'; | ||
import { | ||
getStudyInstanceUIDFromUri, getSeriesInstanceUIDFromUri, | ||
getSOPInstanceUIDFromUri, getFrameNumbersFromUri | ||
} from './utils.js'; | ||
import { DICOMwebClient } from "./api.js"; | ||
import { | ||
getStudyInstanceUIDFromUri, | ||
getSeriesInstanceUIDFromUri, | ||
getSOPInstanceUIDFromUri, | ||
getFrameNumbersFromUri | ||
} from "./utils.js"; | ||
let api = { | ||
DICOMwebClient, | ||
const api = { | ||
DICOMwebClient | ||
}; | ||
let utils = { | ||
const utils = { | ||
getStudyInstanceUIDFromUri, | ||
@@ -17,4 +19,4 @@ getSeriesInstanceUIDFromUri, | ||
export { default as version } from './version.js'; | ||
export { default as version } from "./version.js"; | ||
export { api, utils }; |
/** | ||
* 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) | ||
* @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, offset, limit) { | ||
offset = offset || 0; | ||
limit = limit || arr.length - offset; | ||
let str = ''; | ||
for (let i = offset; i < offset + limit; i++) { | ||
function uint8ArrayToString(arr, offset = 0, limit) { | ||
const itemLimit = limit || arr.length - offset; | ||
let str = ""; | ||
for (let i = offset; i < offset + itemLimit; i++) { | ||
str += String.fromCharCode(arr[i]); | ||
@@ -18,3 +19,2 @@ } | ||
/** | ||
@@ -33,3 +33,2 @@ * Converts a String to a Uint8Array. | ||
/** | ||
@@ -41,12 +40,13 @@ * Identifies the boundary in a multipart/related message header. | ||
function identifyBoundary(header) { | ||
const parts = header.split('\r\n'); | ||
const parts = header.split("\r\n"); | ||
for (let i = 0; i < parts.length; i++) { | ||
if (parts[i].substr(0, 2) === '--') { | ||
if (parts[i].substr(0, 2) === "--") { | ||
return parts[i]; | ||
} | ||
} | ||
return null; | ||
} | ||
/** | ||
@@ -59,3 +59,3 @@ * Checks whether a given token is contained by a message at a given offset. | ||
*/ | ||
function containsToken(message, token, offset=0) { | ||
function containsToken(message, token, offset = 0) { | ||
if (offset + token.length > message.length) { | ||
@@ -67,5 +67,7 @@ return false; | ||
for (let i = 0; i < token.length; i++) { | ||
if (token[i] !== message[index++]) { | ||
if (token[i] !== message[index]) { | ||
return false; | ||
} | ||
index += 1; | ||
} | ||
@@ -75,3 +77,2 @@ return true; | ||
/** | ||
@@ -84,3 +85,3 @@ * Finds a given token in a message at a given offset. | ||
*/ | ||
function findToken(message, token, offset=0, maxSearchLength) { | ||
function findToken(message, token, offset = 0, maxSearchLength) { | ||
let searchLength = message.length; | ||
@@ -106,2 +107,16 @@ if (maxSearchLength) { | ||
/** | ||
* Create a random GUID | ||
* | ||
* @return {string} | ||
*/ | ||
function guid() { | ||
function s4() { | ||
return Math.floor((1 + Math.random()) * 0x10000) | ||
.toString(16) | ||
.substring(1); | ||
} | ||
return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; | ||
} | ||
/** | ||
* @typedef {Object} MultipartEncodedData | ||
@@ -116,7 +131,16 @@ * @property {ArrayBuffer} data The encoded Multipart Data | ||
* | ||
* @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. | ||
* @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, boundary=guid(), contentType='application/dicom') { | ||
function multipartEncode( | ||
datasets, | ||
boundary = guid(), | ||
contentType = "application/dicom" | ||
) { | ||
const contentTypeString = `Content-Type: ${contentType}`; | ||
@@ -140,3 +164,3 @@ const header = `\r\n--${boundary}\r\n${contentTypeString}\r\n\r\n`; | ||
return contentArray; | ||
}) | ||
}); | ||
@@ -152,4 +176,2 @@ // Allocate the array | ||
contentArrays.forEach(contentArray => { | ||
const contentLength = contentArray.length; | ||
multipartArray.set(headerArray, position); | ||
@@ -167,3 +189,3 @@ multipartArray.set(contentArray, position + headerLength); | ||
}; | ||
}; | ||
} | ||
@@ -177,77 +199,67 @@ /** | ||
function multipartDecode(response) { | ||
const message = new Uint8Array(response); | ||
const message = new Uint8Array(response); | ||
/* Set a maximum length to search for the header boundaries, otherwise | ||
/* Set a maximum length to search for the header boundaries, otherwise | ||
findToken can run for a long time | ||
*/ | ||
const maxSearchLength = 1000; | ||
const maxSearchLength = 1000; | ||
// First look for the multipart mime header | ||
const separator = stringToUint8Array("\r\n\r\n"); | ||
const headerIndex = findToken(message, separator, 0, maxSearchLength); | ||
if (headerIndex === -1) { | ||
throw new Error("Response message has no multipart mime header"); | ||
} | ||
// First look for the multipart mime header | ||
let separator = stringToUint8Array('\r\n\r\n'); | ||
let headerIndex = findToken(message, separator, 0, maxSearchLength); | ||
if (headerIndex === -1) { | ||
throw new Error('Response message has no multipart mime header'); | ||
} | ||
const header = uint8ArrayToString(message, 0, headerIndex); | ||
const boundaryString = identifyBoundary(header); | ||
if (!boundaryString) { | ||
throw new Error("Header of response message does not specify boundary"); | ||
} | ||
const header = uint8ArrayToString(message, 0, headerIndex); | ||
const boundaryString = identifyBoundary(header); | ||
if (!boundaryString) { | ||
throw new Error('Header of response message does not specify boundary'); | ||
} | ||
const boundary = stringToUint8Array(boundaryString); | ||
const boundaryLength = boundary.length; | ||
const components = []; | ||
const boundary = stringToUint8Array(boundaryString); | ||
const boundaryLength = boundary.length; | ||
const components = []; | ||
let offset = boundaryLength; | ||
let offset = boundaryLength; | ||
// Loop until we cannot find any more boundaries | ||
let boundaryIndex; | ||
// Loop until we cannot find any more boundaries | ||
let boundaryIndex; | ||
while (boundaryIndex !== -1) { | ||
// Search for the next boundary in the message, starting | ||
// from the current offset position | ||
boundaryIndex = findToken(message, boundary, offset); | ||
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; | ||
} | ||
// If no further boundaries are found, stop here. | ||
if (boundaryIndex === -1) { | ||
break; | ||
} | ||
let headerIndex = findToken(message, separator, offset, maxSearchLength); | ||
if (headerIndex === -1) { | ||
throw new Error('Response message part has no mime header'); | ||
} | ||
offset = headerIndex + separator.length; | ||
const 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" | ||
const spacingLength = 2; | ||
const data = response.slice(offset, boundaryIndex - spacingLength); | ||
// Extract data from response message, excluding "\r\n" | ||
const spacingLength = 2; | ||
const data = response.slice(offset, boundaryIndex - spacingLength); | ||
// Add the data to the array of results | ||
components.push(data); | ||
// 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; | ||
} | ||
// Move the offset to the end of the current section, | ||
// plus the identified boundary | ||
offset = boundaryIndex + boundaryLength; | ||
} | ||
return components; | ||
return components; | ||
} | ||
/** | ||
* Create a random GUID | ||
* | ||
* @return {string} | ||
*/ | ||
function guid() { | ||
function s4() { | ||
return Math.floor((1 + Math.random()) * 0x10000) | ||
.toString(16) | ||
.substring(1); | ||
} | ||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); | ||
} | ||
export { | ||
@@ -261,3 +273,3 @@ containsToken, | ||
multipartDecode, | ||
guid, | ||
guid | ||
}; |
function findSubstring(str, before, after) { | ||
const beforeIndex = str.lastIndexOf(before) + before.length; | ||
if (beforeIndex < before.length) { | ||
return(null); | ||
const beforeIndex = str.lastIndexOf(before) + before.length; | ||
if (beforeIndex < before.length) { | ||
return null; | ||
} | ||
if (after !== undefined) { | ||
const afterIndex = str.lastIndexOf(after); | ||
if (afterIndex < 0) { | ||
return null; | ||
} | ||
if (after !== undefined) { | ||
const afterIndex = str.lastIndexOf(after); | ||
if (afterIndex < 0) { | ||
return(null); | ||
} else{ | ||
return(str.substring(beforeIndex, afterIndex)); | ||
} | ||
} | ||
return(str.substring(beforeIndex)); | ||
return str.substring(beforeIndex, afterIndex); | ||
} | ||
return str.substring(beforeIndex); | ||
} | ||
function getStudyInstanceUIDFromUri(uri) { | ||
@@ -24,8 +22,9 @@ let uid = findSubstring(uri, "studies/", "/series"); | ||
if (!uid) { | ||
console.debug('Study Instance UID could not be dertermined from URI "' + uri + '"'); | ||
console.debug( | ||
`Study Instance UID could not be dertermined from URI "${uri}"` | ||
); | ||
} | ||
return(uid); | ||
return uid; | ||
} | ||
function getSeriesInstanceUIDFromUri(uri) { | ||
@@ -37,8 +36,9 @@ let uid = findSubstring(uri, "series/", "/instances"); | ||
if (!uid) { | ||
console.debug('Series Instance UID could not be dertermined from URI "' + uri + '"'); | ||
console.debug( | ||
`Series Instance UID could not be dertermined from URI "${uri}"` | ||
); | ||
} | ||
return(uid); | ||
return uid; | ||
} | ||
function getSOPInstanceUIDFromUri(uri) { | ||
@@ -53,8 +53,7 @@ let uid = findSubstring(uri, "/instances/", "/frames"); | ||
if (!uid) { | ||
console.debug('SOP Instance UID could not be dertermined from URI"' + uri + '"'); | ||
console.debug(`SOP Instance UID could not be dertermined from URI"${uri}"`); | ||
} | ||
return(uid); | ||
return uid; | ||
} | ||
function getFrameNumbersFromUri(uri) { | ||
@@ -66,5 +65,5 @@ let numbers = findSubstring(uri, "/frames/", "/rendered"); | ||
if (numbers === undefined) { | ||
console.debug('Frames Numbers could not be dertermined from URI"' + uri + '"'); | ||
console.debug(`Frames Numbers could not be dertermined from URI"${uri}"`); | ||
} | ||
return(numbers.split(',')); | ||
return numbers.split(","); | ||
} | ||
@@ -76,3 +75,3 @@ | ||
getSOPInstanceUIDFromUri, | ||
getFrameNumbersFromUri, | ||
getFrameNumbersFromUri | ||
}; |
@@ -1,1 +0,1 @@ | ||
export default '0.3.2'; | ||
export default "0.5.0"; |
@@ -25,2 +25,3 @@ const { expect } = chai; | ||
url: 'http://localhost:8008/dcm4chee-arc/aets/DCM4CHEE/rs', | ||
retrieveRendered: false | ||
}); | ||
@@ -151,4 +152,6 @@ | ||
expect(bulkData).to.be.an('arraybuffer'); | ||
expect(bulkData).to.be.an('array'); | ||
expect(bulkData).to.to.have.length(1); | ||
expect(bulkData[0]).to.be.an('arraybuffer'); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1279484
4385
15