Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/imageLoader/internal/xhrRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -39,6 +40,7 @@ function xhrRequest(url, imageId, defaultHeaders = {}, params = {}) {
if (key === 'Accept' && url.indexOf('accept=') !== -1) {
return;
}

xhr.setRequestHeader(key, headers[key]);
});

Expand Down
22 changes: 20 additions & 2 deletions src/imageLoader/wadors/getPixelData.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { xhrRequest } from '../internal/index.js';
import findIndexOfString from './findIndexOfString.js';
import buildMultipartAcceptHeaderFieldValue from '../../shared/mediaTypesUtils/buildMultipartAcceptHeaderFieldValue.js';

function findBoundary(header) {
for (let i = 0; i < header.length; i++) {
Expand Down Expand Up @@ -29,9 +30,26 @@ function uint8ArrayToString(data, offset, length) {
return str;
}

function getPixelData(uri, imageId, mediaType = 'application/octet-stream') {
function getPixelData(uri, imageId, mediaTypes) {
const 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', 'image/jll'],
'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'],
};

const headers = {
Accept: mediaType,
Accept: buildMultipartAcceptHeaderFieldValue(
mediaTypes,
supportedMediaTypes
),
};

return new Promise((resolve, reject) => {
Expand Down
17 changes: 5 additions & 12 deletions src/imageLoader/wadors/loadImage.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import external from '../../externalModules.js';
import getPixelData from './getPixelData.js';
import createImage from '../createImage.js';

import { mediaTypes } from '../../shared/mediaTypesUtils/mediaTypes.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.
Expand Down Expand Up @@ -72,21 +72,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, mediaTypes) {
// get the pixel data from the server
return getPixelData(imageURI, imageId, mediaType)
return getPixelData(imageURI, imageId, mediaTypes)
.then((result) => {
const transferSyntax = getTransferSyntaxForContentType(
result.contentType
);

const pixelData = result.imageFrame.pixelData;
const imagePromise = createImage(
imageId,
Expand Down Expand Up @@ -115,7 +108,7 @@ function loadImage(imageId, options = {}) {
const uri = imageId.substring(7);

imageRetrievalPool.addRequest(
sendXHR.bind(this, uri, imageId, mediaType),
sendXHR.bind(this, uri, imageId, mediaTypes),
requestType,
additionalDetails,
priority,
Expand Down
26 changes: 26 additions & 0 deletions src/shared/mediaTypesUtils/assertMediaTypeIsValid.js
Original file line number Diff line number Diff line change
@@ -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}`);
}
}
102 changes: 102 additions & 0 deletions src/shared/mediaTypesUtils/buildMultipartAcceptHeaderFieldValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import assertMediaTypeIsValid from './assertMediaTypeIsValid.js';

/**
* Builds an accept header field value for HTTP GET multipart request messages.
*
* Takes in input a media types array of type [{mediaType, transferSyntaxUID}, ... ],
* cross-compares with a map defining the supported media types per 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
* @param {Object} supportedMediaTypes Supported media types
*
* @returns {string} accept header field value
*/

export default function buildMultipartAcceptHeaderFieldValue(
mediaTypes,
supportedMediaTypes
) {
if (!Array.isArray(mediaTypes)) {
throw new Error('Acceptable media types must be provided as an Array');
}

if (typeof supportedMediaTypes !== 'object') {
throw new Error(
'Supported media types must be provided as an Array or an Object'
);
}

const supportedMediaTypesArray = Object.values(supportedMediaTypes).flat(1);

supportedMediaTypesArray.forEach((supportedMediaType) => {
assertMediaTypeIsValid(supportedMediaType);
});

const fieldValueParts = [];

mediaTypes.forEach((item) => {
const { transferSyntaxUID, mediaType } = item;

assertMediaTypeIsValid(mediaType);

let fieldValue = `multipart/related; type="${mediaType}"`;

// supportedMediaTypesArray is a lookup table that maps Transfer Syntax UID
// to one or more Media Types
if (!supportedMediaTypesArray.includes(mediaType)) {
if (
(!mediaType.endsWith('/*') || !mediaType.endsWith('/')) &&
mediaType !== 'application/octet-stream'
) {
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 = mediaType.split('/')[0];

expectedMediaTypes.map((expectedMediaType) => {
const expectedType = expectedMediaType.split('/')[0];

const haveSameType = actualType === expectedType;

if (
haveSameType &&
(mediaType.endsWith('/*') || mediaType.endsWith('/'))
) {
return null;
}

throw new Error(
`Transfer syntax ${transferSyntaxUID} is not supported for requested resource`
);
});
}
}
fieldValue += `; transfer-syntax=${transferSyntaxUID}`;
}

fieldValueParts.push(fieldValue);
});

return fieldValueParts.join(', ');
}
4 changes: 4 additions & 0 deletions src/shared/mediaTypesUtils/mediaTypeJLL.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const jllMediaType = 'image/jll';
const jllTransferSyntaxUIDlossless = '1.2.840.10008.1.2.4.70';

export { jllMediaType, jllTransferSyntaxUIDlossless };
5 changes: 5 additions & 0 deletions src/shared/mediaTypesUtils/mediaTypeJLS.js
Original file line number Diff line number Diff line change
@@ -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 };
5 changes: 5 additions & 0 deletions src/shared/mediaTypesUtils/mediaTypeJP2.js
Original file line number Diff line number Diff line change
@@ -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 };
11 changes: 11 additions & 0 deletions src/shared/mediaTypesUtils/mediaTypeJPEG.js
Original file line number Diff line number Diff line change
@@ -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,
};
4 changes: 4 additions & 0 deletions src/shared/mediaTypesUtils/mediaTypeOctetStream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const octetStreamMediaType = 'application/octet-stream';
const octetStreamTransferSyntaxUID = '*';

export { octetStreamMediaType, octetStreamTransferSyntaxUID };
4 changes: 4 additions & 0 deletions src/shared/mediaTypesUtils/mediaTypeXDicomRLE.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const xdicomrleMediaType = 'image/x-dicom-rle';
const xdicomrleTransferSyntaxUID = '1.2.840.10008.1.2.5';

export { xdicomrleMediaType, xdicomrleTransferSyntaxUID };
73 changes: 73 additions & 0 deletions src/shared/mediaTypesUtils/mediaTypes.js
Original file line number Diff line number Diff line change
@@ -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 };