diff --git a/src/imageLoader/internal/xhrRequest.js b/src/imageLoader/internal/xhrRequest.js index 0eb50da9..b3714dc8 100644 --- a/src/imageLoader/internal/xhrRequest.js +++ b/src/imageLoader/internal/xhrRequest.js @@ -21,6 +21,7 @@ function xhrRequest(url, imageId, defaultHeaders = {}, params = {}) { const xhr = new XMLHttpRequest(); xhr.open('get', url, true); + const beforeSendHeaders = options.beforeSend( xhr, imageId, @@ -39,6 +40,7 @@ function xhrRequest(url, imageId, defaultHeaders = {}, params = {}) { if (key === 'Accept' && url.indexOf('accept=') !== -1) { return; } + xhr.setRequestHeader(key, headers[key]); }); diff --git a/src/imageLoader/wadors/getPixelData.js b/src/imageLoader/wadors/getPixelData.js index afade193..7e1bc769 100644 --- a/src/imageLoader/wadors/getPixelData.js +++ b/src/imageLoader/wadors/getPixelData.js @@ -1,6 +1,6 @@ import { xhrRequest } from '../internal/index.js'; import findIndexOfString from './findIndexOfString.js'; - +import { multipartAcceptHeaderFieldValue } from '../../shared/mediaTypesUtils/multipartAcceptHeaderFieldValue.js'; function findBoundary(header) { for (let i = 0; i < header.length; i++) { if (header[i].substr(0, 2) === '--') { @@ -29,9 +29,9 @@ function uint8ArrayToString(data, offset, length) { return str; } -function getPixelData(uri, imageId, mediaType = 'application/octet-stream') { +function getPixelData(uri, imageId) { const headers = { - Accept: mediaType, + Accept: multipartAcceptHeaderFieldValue, }; return new Promise((resolve, reject) => { diff --git a/src/imageLoader/wadors/loadImage.js b/src/imageLoader/wadors/loadImage.js index 9fd63a54..44615448 100644 --- a/src/imageLoader/wadors/loadImage.js +++ b/src/imageLoader/wadors/loadImage.js @@ -1,7 +1,6 @@ import external from '../../externalModules.js'; import getPixelData from './getPixelData.js'; import createImage from '../createImage.js'; - /** * Helper method to extract the transfer-syntax from the response of the server. * @param {string} contentType The value of the content-type header as returned by the WADO-RS server. @@ -72,21 +71,14 @@ function loadImage(imageId, options = {}) { const promise = new Promise((resolve, reject) => { // TODO: load bulk data items that we might need - // Uncomment this on to test jpegls codec in OHIF - // const mediaType = 'multipart/related; type="image/x-jls"'; - // const mediaType = 'multipart/related; type="application/octet-stream"; transfer-syntax="image/x-jls"'; - const mediaType = - 'multipart/related; type=application/octet-stream; transfer-syntax=*'; - // const mediaType = - // 'multipart/related; type="image/jpeg"; transfer-syntax=1.2.840.10008.1.2.4.50'; - - function sendXHR(imageURI, imageId, mediaType) { + function sendXHR(imageURI, imageId) { // get the pixel data from the server - return getPixelData(imageURI, imageId, mediaType) + return getPixelData(imageURI, imageId) .then((result) => { const transferSyntax = getTransferSyntaxForContentType( result.contentType ); + const pixelData = result.imageFrame.pixelData; const imagePromise = createImage( imageId, @@ -115,7 +107,7 @@ function loadImage(imageId, options = {}) { const uri = imageId.substring(7); imageRetrievalPool.addRequest( - sendXHR.bind(this, uri, imageId, mediaType), + sendXHR.bind(this, uri, imageId), requestType, additionalDetails, priority, diff --git a/src/shared/mediaTypesUtils/assertMediaTypeIsValid.js b/src/shared/mediaTypesUtils/assertMediaTypeIsValid.js new file mode 100644 index 00000000..ab525caa --- /dev/null +++ b/src/shared/mediaTypesUtils/assertMediaTypeIsValid.js @@ -0,0 +1,26 @@ +/** + * Asserts that a given media type is valid. + * + * @params {String} mediaType media type + */ + +export default function 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}`); + } +} diff --git a/src/shared/mediaTypesUtils/mediaTypeJLL.js b/src/shared/mediaTypesUtils/mediaTypeJLL.js new file mode 100644 index 00000000..e2b5ecaa --- /dev/null +++ b/src/shared/mediaTypesUtils/mediaTypeJLL.js @@ -0,0 +1,4 @@ +const jllMediaType = 'image/jll'; +const jllTransferSyntaxUIDlossless = '1.2.840.10008.1.2.4.70'; + +export { jllMediaType, jllTransferSyntaxUIDlossless }; diff --git a/src/shared/mediaTypesUtils/mediaTypeJLS.js b/src/shared/mediaTypesUtils/mediaTypeJLS.js new file mode 100644 index 00000000..53e0d697 --- /dev/null +++ b/src/shared/mediaTypesUtils/mediaTypeJLS.js @@ -0,0 +1,5 @@ +const jlsMediaType = 'image/jls'; +const jlsTransferSyntaxUIDlossless = '1.2.840.10008.1.2.4.80'; +const jlsTransferSyntaxUID = '1.2.840.10008.1.2.4.81'; + +export { jlsMediaType, jlsTransferSyntaxUIDlossless, jlsTransferSyntaxUID }; diff --git a/src/shared/mediaTypesUtils/mediaTypeJP2.js b/src/shared/mediaTypesUtils/mediaTypeJP2.js new file mode 100644 index 00000000..9bc56417 --- /dev/null +++ b/src/shared/mediaTypesUtils/mediaTypeJP2.js @@ -0,0 +1,5 @@ +const jp2MediaType = 'image/jp2'; +const jp2TransferSyntaxUIDlossless = '1.2.840.10008.1.2.4.90'; +const jp2TransferSyntaxUID = '1.2.840.10008.1.2.4.91'; + +export { jp2MediaType, jp2TransferSyntaxUIDlossless, jp2TransferSyntaxUID }; diff --git a/src/shared/mediaTypesUtils/mediaTypeJPEG.js b/src/shared/mediaTypesUtils/mediaTypeJPEG.js new file mode 100644 index 00000000..616a12ec --- /dev/null +++ b/src/shared/mediaTypesUtils/mediaTypeJPEG.js @@ -0,0 +1,11 @@ +const jpegMediaType = 'image/jpeg'; +const jpegTransferSyntaxUIDlossy1 = '1.2.840.10008.1.2.4.50'; +const jpegTransferSyntaxUIDlossy2 = '1.2.840.10008.1.2.4.51'; +const jpegTransferSyntaxUIDlossless = '1.2.840.10008.1.2.4.57'; + +export { + jpegMediaType, + jpegTransferSyntaxUIDlossy1, + jpegTransferSyntaxUIDlossy2, + jpegTransferSyntaxUIDlossless, +}; diff --git a/src/shared/mediaTypesUtils/mediaTypeOctetStream.js b/src/shared/mediaTypesUtils/mediaTypeOctetStream.js new file mode 100644 index 00000000..8f7190ef --- /dev/null +++ b/src/shared/mediaTypesUtils/mediaTypeOctetStream.js @@ -0,0 +1,4 @@ +const octetStreamMediaType = 'application/octet-stream'; +const octetStreamTransferSyntaxUID = '*'; + +export { octetStreamMediaType, octetStreamTransferSyntaxUID }; diff --git a/src/shared/mediaTypesUtils/mediaTypeXDicomRLE.js b/src/shared/mediaTypesUtils/mediaTypeXDicomRLE.js new file mode 100644 index 00000000..265e213c --- /dev/null +++ b/src/shared/mediaTypesUtils/mediaTypeXDicomRLE.js @@ -0,0 +1,4 @@ +const xdicomrleMediaType = 'image/x-dicom-rle'; +const xdicomrleTransferSyntaxUID = '1.2.840.10008.1.2.5'; + +export { xdicomrleMediaType, xdicomrleTransferSyntaxUID }; diff --git a/src/shared/mediaTypesUtils/mediaTypes.js b/src/shared/mediaTypesUtils/mediaTypes.js new file mode 100644 index 00000000..b585a11b --- /dev/null +++ b/src/shared/mediaTypesUtils/mediaTypes.js @@ -0,0 +1,73 @@ +import { + xdicomrleMediaType, + xdicomrleTransferSyntaxUID, +} from './mediaTypeXDicomRLE.js'; +import { + jpegMediaType, + jpegTransferSyntaxUIDlossy1, + jpegTransferSyntaxUIDlossy2, + jpegTransferSyntaxUIDlossless, +} from './mediaTypeJPEG.js'; +import { jllMediaType, jllTransferSyntaxUIDlossless } from './mediaTypeJLL.js'; +import { + jlsMediaType, + jlsTransferSyntaxUID, + jlsTransferSyntaxUIDlossless, +} from './mediaTypeJLS.js'; +import { + jp2MediaType, + jp2TransferSyntaxUID, + jp2TransferSyntaxUIDlossless, +} from './mediaTypeJP2.js'; +import { + octetStreamMediaType, + octetStreamTransferSyntaxUID, +} from './mediaTypeOctetStream.js'; + +// NOTE: the position of the elements in the array indicates the mediaType +// priority when fetching an image. An element at the beginning of the array +// has the highest priority. +const mediaTypes = [ + { + mediaType: xdicomrleMediaType, + transferSyntaxUID: xdicomrleTransferSyntaxUID, + }, + { + mediaType: jpegMediaType, + transferSyntaxUID: jpegTransferSyntaxUIDlossy1, + }, + { + mediaType: jpegMediaType, + transferSyntaxUID: jpegTransferSyntaxUIDlossy2, + }, + { + mediaType: jpegMediaType, + transferSyntaxUID: jpegTransferSyntaxUIDlossless, + }, + { + mediaType: jllMediaType, + transferSyntaxUID: jllTransferSyntaxUIDlossless, + }, + { + mediaType: jlsMediaType, + transferSyntaxUID: jlsTransferSyntaxUIDlossless, + }, + { + mediaType: jlsMediaType, + transferSyntaxUID: jlsTransferSyntaxUID, + }, + { + mediaType: jp2MediaType, + transferSyntaxUID: jp2TransferSyntaxUIDlossless, + }, + { + mediaType: jp2MediaType, + transferSyntaxUID: jp2TransferSyntaxUID, + }, + { + mediaType: octetStreamMediaType, + transferSyntaxUID: octetStreamTransferSyntaxUID, + }, +]; + +export { mediaTypes }; diff --git a/src/shared/mediaTypesUtils/multipartAcceptHeaderFieldValue.js b/src/shared/mediaTypesUtils/multipartAcceptHeaderFieldValue.js new file mode 100644 index 00000000..f236bd2d --- /dev/null +++ b/src/shared/mediaTypesUtils/multipartAcceptHeaderFieldValue.js @@ -0,0 +1,50 @@ +import assertMediaTypeIsValid from './assertMediaTypeIsValid.js'; +import { mediaTypes } from './mediaTypes.js'; + +/** + * Builds an accept header field value for HTTP GET multipart request messages. + * + * Takes in input a media types array of type [{mediaType, transferSyntaxUID}, ... ] + * and finally composes a string for the accept header field value as in example below: + * + * "multipart/related; type="image/x-dicom-rle"; transfer-syntax=1.2.840.10008.1.2.5, + * multipart/related; type="image/jpeg"; transfer-syntax=1.2.840.10008.1.2.4.50, + * multipart/related; type="application/octet-stream"; transfer-syntax=*" + * + * NOTE: the xhr request will try to fetch with all the transfer-syntax syntaxes + * specified in the accept header field value in descending order. + * The first element ("image/x-dicom-rle" in this example) has the highest priority. + * + * @param {Array} mediaTypes Acceptable media types + * + * @returns {string} accept header field value + */ + +export default function buildMultipartAcceptHeaderFieldValue(mediaTypes) { + if (!Array.isArray(mediaTypes)) { + throw new Error('Acceptable media types must be provided as an Array'); + } + + const fieldValueParts = []; + + mediaTypes.forEach((item) => { + const { transferSyntaxUID, mediaType } = item; + + assertMediaTypeIsValid(mediaType); + + let fieldValue = `multipart/related; type="${mediaType}"`; + + if (transferSyntaxUID) { + fieldValue += `; transfer-syntax=${transferSyntaxUID}`; + } + + fieldValueParts.push(fieldValue); + }); + + return fieldValueParts.join(', '); +} + +const multipartAcceptHeaderFieldValue = + buildMultipartAcceptHeaderFieldValue(mediaTypes); + +export { multipartAcceptHeaderFieldValue };