2065 lines
63 KiB
JavaScript
2065 lines
63 KiB
JavaScript
/*! cornerstone-wado-image-loader - v0.14.2 - 2017-02-22 | (c) 2016 Chris Hafey | https://github.com/chafey/cornerstoneWADOImageLoader */
|
|
//
|
|
// This is a cornerstone image loader for WADO-URI requests.
|
|
//
|
|
|
|
if(typeof cornerstone === 'undefined'){
|
|
cornerstone = {};
|
|
}
|
|
if(typeof cornerstoneWADOImageLoader === 'undefined'){
|
|
cornerstoneWADOImageLoader = {
|
|
wadouri: {
|
|
|
|
},
|
|
wadors: {
|
|
|
|
},
|
|
internal: {
|
|
options : {
|
|
// callback allowing customization of the xhr (e.g. adding custom auth headers, cors, etc)
|
|
beforeSend: function (xhr) {
|
|
},
|
|
// callback allowing modification of newly created image objects
|
|
imageCreated : function(image) {
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
// add a decache callback function to clear out our dataSetCacheManager
|
|
function addDecache(image) {
|
|
image.decache = function() {
|
|
//console.log('decache');
|
|
var parsedImageId = cornerstoneWADOImageLoader.wadouri.parseImageId(image.imageId);
|
|
cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.unload(parsedImageId.url);
|
|
};
|
|
}
|
|
|
|
function getPixelData(dataSet, frameIndex) {
|
|
var pixelDataElement = dataSet.elements.x7fe00010;
|
|
|
|
if(pixelDataElement.encapsulatedPixelData) {
|
|
return cornerstoneWADOImageLoader.wadouri.getEncapsulatedImageFrame(dataSet, frameIndex);
|
|
} else {
|
|
return cornerstoneWADOImageLoader.wadouri.getUncompressedImageFrame(dataSet, frameIndex);
|
|
}
|
|
}
|
|
|
|
function loadDataSetFromPromise(xhrRequestPromise, imageId, frame, sharedCacheKey, options) {
|
|
|
|
var start = new Date().getTime();
|
|
frame = frame || 0;
|
|
var deferred = $.Deferred();
|
|
xhrRequestPromise.then(function(dataSet/*, xhr*/) {
|
|
var pixelData = getPixelData(dataSet, frame);
|
|
var transferSyntax = dataSet.string('x00020010');
|
|
var loadEnd = new Date().getTime();
|
|
var imagePromise = cornerstoneWADOImageLoader.createImage(imageId, pixelData, transferSyntax, options);
|
|
imagePromise.then(function(image) {
|
|
image.data = dataSet;
|
|
var end = new Date().getTime();
|
|
image.loadTimeInMS = loadEnd - start;
|
|
image.totalTimeInMS = end - start;
|
|
addDecache(image);
|
|
deferred.resolve(image);
|
|
});
|
|
}, function(error) {
|
|
deferred.reject(error);
|
|
});
|
|
return deferred.promise();
|
|
}
|
|
|
|
function getLoaderForScheme(scheme) {
|
|
if(scheme === 'dicomweb' || scheme === 'wadouri') {
|
|
return cornerstoneWADOImageLoader.internal.xhrRequest;
|
|
}
|
|
else if(scheme === 'dicomfile') {
|
|
return cornerstoneWADOImageLoader.wadouri.loadFileRequest;
|
|
}
|
|
}
|
|
|
|
function loadImage(imageId, options) {
|
|
var parsedImageId = cornerstoneWADOImageLoader.wadouri.parseImageId(imageId);
|
|
|
|
var loader = getLoaderForScheme(parsedImageId.scheme);
|
|
|
|
// if the dataset for this url is already loaded, use it
|
|
if(cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.isLoaded(parsedImageId.url)) {
|
|
return loadDataSetFromPromise(cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.load(parsedImageId.url, loader, imageId), imageId, parsedImageId.frame, parsedImageId.url, options);
|
|
}
|
|
|
|
// load the dataSet via the dataSetCacheManager
|
|
return loadDataSetFromPromise(cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.load(parsedImageId.url, loader, imageId), imageId, parsedImageId.frame, parsedImageId.url, options);
|
|
}
|
|
|
|
// register dicomweb and wadouri image loader prefixes
|
|
cornerstone.registerImageLoader('dicomweb', loadImage);
|
|
cornerstone.registerImageLoader('wadouri', loadImage);
|
|
cornerstone.registerImageLoader('dicomfile', loadImage);
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function convertPALETTECOLOR( imageFrame, rgbaBuffer ) {
|
|
var numPixels = imageFrame.columns * imageFrame.rows;
|
|
var palIndex=0;
|
|
var rgbaIndex=0;
|
|
var pixelData = imageFrame.pixelData;
|
|
var start = imageFrame.redPaletteColorLookupTableDescriptor[1];
|
|
var rData = imageFrame.redPaletteColorLookupTableData;
|
|
var gData = imageFrame.greenPaletteColorLookupTableData;
|
|
var bData = imageFrame.bluePaletteColorLookupTableData;
|
|
var shift = imageFrame.redPaletteColorLookupTableDescriptor[2] === 8 ? 0 : 8;
|
|
var len = imageFrame.redPaletteColorLookupTableData.length;
|
|
if(len === 0) {
|
|
len = 65535;
|
|
}
|
|
|
|
for( var i=0 ; i < numPixels ; ++i ) {
|
|
var value=pixelData[palIndex++];
|
|
if( value < start )
|
|
value=0;
|
|
else if( value > start + len -1 )
|
|
value=len-1;
|
|
else
|
|
value=value-start;
|
|
|
|
rgbaBuffer[ rgbaIndex++ ] = rData[value] >> shift;
|
|
rgbaBuffer[ rgbaIndex++ ] = gData[value] >> shift;
|
|
rgbaBuffer[ rgbaIndex++ ] = bData[value] >> shift;
|
|
rgbaBuffer[ rgbaIndex++ ] = 255;
|
|
}
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.convertPALETTECOLOR = convertPALETTECOLOR;
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function convertRGBColorByPixel(imageFrame, rgbaBuffer) {
|
|
if(imageFrame === undefined) {
|
|
throw "decodeRGB: rgbBuffer must not be undefined";
|
|
}
|
|
if(imageFrame.length % 3 !== 0) {
|
|
throw "decodeRGB: rgbBuffer length must be divisible by 3";
|
|
}
|
|
|
|
var numPixels = imageFrame.length / 3;
|
|
var rgbIndex = 0;
|
|
var rgbaIndex = 0;
|
|
for(var i= 0; i < numPixels; i++) {
|
|
rgbaBuffer[rgbaIndex++] = imageFrame[rgbIndex++]; // red
|
|
rgbaBuffer[rgbaIndex++] = imageFrame[rgbIndex++]; // green
|
|
rgbaBuffer[rgbaIndex++] = imageFrame[rgbIndex++]; // blue
|
|
rgbaBuffer[rgbaIndex++] = 255; //alpha
|
|
}
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.convertRGBColorByPixel = convertRGBColorByPixel;
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function convertRGBColorByPlane(imageFrame, rgbaBuffer) {
|
|
if(imageFrame === undefined) {
|
|
throw "decodeRGB: rgbBuffer must not be undefined";
|
|
}
|
|
if(imageFrame.length % 3 !== 0) {
|
|
throw "decodeRGB: rgbBuffer length must be divisible by 3";
|
|
}
|
|
|
|
var numPixels = imageFrame.length / 3;
|
|
var rgbaIndex = 0;
|
|
var rIndex = 0;
|
|
var gIndex = numPixels;
|
|
var bIndex = numPixels*2;
|
|
for(var i= 0; i < numPixels; i++) {
|
|
rgbaBuffer[rgbaIndex++] = imageFrame[rIndex++]; // red
|
|
rgbaBuffer[rgbaIndex++] = imageFrame[gIndex++]; // green
|
|
rgbaBuffer[rgbaIndex++] = imageFrame[bIndex++]; // blue
|
|
rgbaBuffer[rgbaIndex++] = 255; //alpha
|
|
}
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.convertRGBColorByPlane = convertRGBColorByPlane;
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function convertYBRFullByPixel(imageFrame, rgbaBuffer) {
|
|
if(imageFrame === undefined) {
|
|
throw "decodeRGB: ybrBuffer must not be undefined";
|
|
}
|
|
if(imageFrame.length % 3 !== 0) {
|
|
throw "decodeRGB: ybrBuffer length must be divisble by 3";
|
|
}
|
|
|
|
var numPixels = imageFrame.length / 3;
|
|
var ybrIndex = 0;
|
|
var rgbaIndex = 0;
|
|
for(var i= 0; i < numPixels; i++) {
|
|
var y = imageFrame[ybrIndex++];
|
|
var cb = imageFrame[ybrIndex++];
|
|
var cr = imageFrame[ybrIndex++];
|
|
rgbaBuffer[rgbaIndex++] = y + 1.40200 * (cr - 128);// red
|
|
rgbaBuffer[rgbaIndex++] = y - 0.34414 * (cb -128) - 0.71414 * (cr- 128); // green
|
|
rgbaBuffer[rgbaIndex++] = y + 1.77200 * (cb - 128); // blue
|
|
rgbaBuffer[rgbaIndex++] = 255; //alpha
|
|
}
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.convertYBRFullByPixel = convertYBRFullByPixel;
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function convertYBRFullByPlane(imageFrame, rgbaBuffer) {
|
|
if (imageFrame === undefined) {
|
|
throw "decodeRGB: ybrBuffer must not be undefined";
|
|
}
|
|
if (imageFrame.length % 3 !== 0) {
|
|
throw "decodeRGB: ybrBuffer length must be divisble by 3";
|
|
}
|
|
|
|
|
|
var numPixels = imageFrame.length / 3;
|
|
var rgbaIndex = 0;
|
|
var yIndex = 0;
|
|
var cbIndex = numPixels;
|
|
var crIndex = numPixels * 2;
|
|
for (var i = 0; i < numPixels; i++) {
|
|
var y = imageFrame[yIndex++];
|
|
var cb = imageFrame[cbIndex++];
|
|
var cr = imageFrame[crIndex++];
|
|
rgbaBuffer[rgbaIndex++] = y + 1.40200 * (cr - 128);// red
|
|
rgbaBuffer[rgbaIndex++] = y - 0.34414 * (cb - 128) - 0.71414 * (cr - 128); // green
|
|
rgbaBuffer[rgbaIndex++] = y + 1.77200 * (cb - 128); // blue
|
|
rgbaBuffer[rgbaIndex++] = 255; //alpha
|
|
}
|
|
}
|
|
// module exports
|
|
cornerstoneWADOImageLoader.convertYBRFullByPlane = convertYBRFullByPlane;
|
|
}(cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function configure(options) {
|
|
cornerstoneWADOImageLoader.internal.options = options;
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.configure = configure;
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function convertRGB(imageFrame, rgbaBuffer) {
|
|
if(imageFrame.planarConfiguration === 0) {
|
|
cornerstoneWADOImageLoader.convertRGBColorByPixel(imageFrame.pixelData, rgbaBuffer);
|
|
} else {
|
|
cornerstoneWADOImageLoader.convertRGBColorByPlane(imageFrame.pixelData, rgbaBuffer);
|
|
}
|
|
}
|
|
|
|
function convertYBRFull(imageFrame, rgbaBuffer) {
|
|
if(imageFrame.planarConfiguration === 0) {
|
|
cornerstoneWADOImageLoader.convertYBRFullByPixel(imageFrame.pixelData, rgbaBuffer);
|
|
} else {
|
|
cornerstoneWADOImageLoader.convertYBRFullByPlane(imageFrame.pixelData, rgbaBuffer);
|
|
}
|
|
}
|
|
|
|
function convertColorSpace(imageFrame, imageData) {
|
|
var rgbaBuffer = imageData.data;
|
|
console.time('convertColorSpace');
|
|
// convert based on the photometric interpretation
|
|
if (imageFrame.photometricInterpretation === "RGB" )
|
|
{
|
|
convertRGB(imageFrame, rgbaBuffer);
|
|
}
|
|
else if (imageFrame.photometricInterpretation === "YBR_RCT")
|
|
{
|
|
convertRGB(imageFrame, rgbaBuffer);
|
|
}
|
|
else if (imageFrame.photometricInterpretation === "YBR_ICT")
|
|
{
|
|
convertRGB(imageFrame, rgbaBuffer);
|
|
}
|
|
else if( imageFrame.photometricInterpretation === "PALETTE COLOR" )
|
|
{
|
|
cornerstoneWADOImageLoader.convertPALETTECOLOR(imageFrame, rgbaBuffer);
|
|
}
|
|
else if( imageFrame.photometricInterpretation === "YBR_FULL_422" )
|
|
{
|
|
convertRGB(imageFrame, rgbaBuffer);
|
|
}
|
|
else if(imageFrame.photometricInterpretation === "YBR_FULL" )
|
|
{
|
|
convertYBRFull(imageFrame, rgbaBuffer);
|
|
}
|
|
else
|
|
{
|
|
throw "no color space conversion for photometric interpretation " + imageFrame.photometricInterpretation;
|
|
}
|
|
console.timeEnd('convertColorSpace');
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.convertColorSpace = convertColorSpace;
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
var canvas = document.createElement('canvas');
|
|
var lastImageIdDrawn = "";
|
|
|
|
function isModalityLUTForDisplay(sopClassUid) {
|
|
// special case for XA and XRF
|
|
// https://groups.google.com/forum/#!searchin/comp.protocols.dicom/Modality$20LUT$20XA/comp.protocols.dicom/UBxhOZ2anJ0/D0R_QP8V2wIJ
|
|
return sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.1' && // XA
|
|
sopClassUid !== '1.2.840.10008.5.1.4.1.1.12.2.1 '; // XRF
|
|
}
|
|
|
|
function getSizeInBytes(imageFrame) {
|
|
var bytesPerPixel = Math.round(imageFrame.bitsAllocated / 8);
|
|
return imageFrame.rows * imageFrame.columns * bytesPerPixel * imageFrame.samplesPerPixel;
|
|
}
|
|
|
|
/**
|
|
* Helper function to set pixel data to the right typed array. This is needed because web workers
|
|
* can transfer array buffers but not typed arrays
|
|
* @param imageFrame
|
|
*/
|
|
function setPixelDataType(imageFrame) {
|
|
if(imageFrame.bitsAllocated === 16) {
|
|
if(imageFrame.pixelRepresentation === 0) {
|
|
imageFrame.pixelData = new Uint16Array(imageFrame.pixelData);
|
|
} else {
|
|
imageFrame.pixelData = new Int16Array(imageFrame.pixelData);
|
|
}
|
|
} else {
|
|
imageFrame.pixelData = new Uint8Array(imageFrame.pixelData);
|
|
}
|
|
}
|
|
|
|
function createImage(imageId, pixelData, transferSyntax, options) {
|
|
var deferred = $.Deferred();
|
|
var imageFrame = cornerstoneWADOImageLoader.getImageFrame(imageId);
|
|
var decodePromise = cornerstoneWADOImageLoader.decodeImageFrame(imageFrame, transferSyntax, pixelData, canvas, options);
|
|
decodePromise.then(function(imageFrame) {
|
|
setPixelDataType(imageFrame);
|
|
|
|
//var imagePixelModule = metaDataProvider('imagePixelModule', imageId);
|
|
var imagePlaneModule = cornerstone.metaData.get('imagePlaneModule', imageId);
|
|
var voiLutModule = cornerstone.metaData.get('voiLutModule', imageId);
|
|
var modalityLutModule = cornerstone.metaData.get('modalityLutModule', imageId);
|
|
var sopCommonModule = cornerstone.metaData.get('sopCommonModule', imageId);
|
|
|
|
var image = {
|
|
imageId: imageId,
|
|
color: cornerstoneWADOImageLoader.isColorImage(imageFrame.photometricInterpretation),
|
|
columnPixelSpacing: imagePlaneModule.pixelSpacing ? imagePlaneModule.pixelSpacing[1] : undefined,
|
|
columns: imageFrame.columns,
|
|
height: imageFrame.rows,
|
|
intercept: modalityLutModule.rescaleIntercept ? modalityLutModule.rescaleIntercept: 0,
|
|
invert: imageFrame.photometricInterpretation === "MONOCHROME1",
|
|
minPixelValue : imageFrame.smallestPixelValue,
|
|
maxPixelValue : imageFrame.largestPixelValue,
|
|
render: undefined, // set below
|
|
rowPixelSpacing: imagePlaneModule.pixelSpacing ? imagePlaneModule.pixelSpacing[0] : undefined,
|
|
rows: imageFrame.rows,
|
|
sizeInBytes: getSizeInBytes(imageFrame),
|
|
slope: modalityLutModule.rescaleSlope ? modalityLutModule.rescaleSlope: 1,
|
|
width: imageFrame.columns,
|
|
windowCenter: voiLutModule.windowCenter ? voiLutModule.windowCenter[0] : undefined,
|
|
windowWidth: voiLutModule.windowWidth ? voiLutModule.windowWidth[0] : undefined,
|
|
decodeTimeInMS : imageFrame.decodeTimeInMS,
|
|
webWorkerTimeInMS: imageFrame.webWorkerTimeInMS
|
|
};
|
|
|
|
|
|
// add function to return pixel data
|
|
image.getPixelData = function() {
|
|
return imageFrame.pixelData;
|
|
};
|
|
|
|
// convert color space
|
|
if(image.color) {
|
|
// setup the canvas context
|
|
canvas.height = imageFrame.rows;
|
|
canvas.width = imageFrame.columns;
|
|
|
|
var context = canvas.getContext('2d');
|
|
var imageData = context.createImageData(imageFrame.columns, imageFrame.rows);
|
|
cornerstoneWADOImageLoader.convertColorSpace(imageFrame, imageData);
|
|
imageFrame.imageData = imageData;
|
|
imageFrame.pixelData = imageData.data;
|
|
}
|
|
|
|
// Setup the renderer
|
|
if(image.color) {
|
|
image.render = cornerstone.renderColorImage;
|
|
image.getCanvas = function() {
|
|
if(lastImageIdDrawn === imageId) {
|
|
return canvas;
|
|
}
|
|
|
|
canvas.height = image.rows;
|
|
canvas.width = image.columns;
|
|
var context = canvas.getContext('2d');
|
|
context.putImageData(imageFrame.imageData, 0, 0 );
|
|
lastImageIdDrawn = imageId;
|
|
return canvas;
|
|
};
|
|
|
|
} else {
|
|
image.render = cornerstone.renderGrayscaleImage;
|
|
}
|
|
|
|
// calculate min/max if not supplied
|
|
if(image.minPixelValue === undefined || image.maxPixelValue === undefined) {
|
|
var minMax = cornerstoneWADOImageLoader.getMinMax(imageFrame.pixelData);
|
|
image.minPixelValue = minMax.min;
|
|
image.maxPixelValue = minMax.max;
|
|
}
|
|
|
|
// Modality LUT
|
|
if(modalityLutModule.modalityLUTSequence &&
|
|
modalityLutModule.modalityLUTSequence.length > 0 &&
|
|
isModalityLUTForDisplay(sopCommonModule.sopClassUID)) {
|
|
image.modalityLUT = modalityLutModule.modalityLUTSequence[0];
|
|
}
|
|
|
|
// VOI LUT
|
|
if(voiLutModule.voiLUTSequence &&
|
|
voiLutModule.voiLUTSequence.length > 0) {
|
|
image.voiLUT = voiLutModule.voiLUTSequence[0];
|
|
}
|
|
|
|
// set the ww/wc to cover the dynamic range of the image if no values are supplied
|
|
if(image.windowCenter === undefined || image.windowWidth === undefined) {
|
|
if(image.color) {
|
|
image.windowWidth = 255;
|
|
image.windowCenter = 128;
|
|
} else {
|
|
var maxVoi = image.maxPixelValue * image.slope + image.intercept;
|
|
var minVoi = image.minPixelValue * image.slope + image.intercept;
|
|
image.windowWidth = maxVoi - minVoi;
|
|
image.windowCenter = (maxVoi + minVoi) / 2;
|
|
}
|
|
}
|
|
deferred.resolve(image);
|
|
});
|
|
return deferred.promise();
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.createImage = createImage;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function addDecodeTask(imageFrame, transferSyntax, pixelData, options) {
|
|
var priority = options.priority || undefined;
|
|
var transferList = options.transferPixelData ? [pixelData.buffer] : undefined;
|
|
|
|
return cornerstoneWADOImageLoader.webWorkerManager.addTask(
|
|
'decodeTask',
|
|
{
|
|
imageFrame : imageFrame,
|
|
transferSyntax : transferSyntax,
|
|
pixelData : pixelData,
|
|
options: options
|
|
}, priority, transferList).promise;
|
|
}
|
|
|
|
function decodeImageFrame(imageFrame, transferSyntax, pixelData, canvas, options) {
|
|
options = options || {};
|
|
|
|
// Implicit VR Little Endian
|
|
if(transferSyntax === "1.2.840.10008.1.2") {
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// Explicit VR Little Endian
|
|
else if(transferSyntax === "1.2.840.10008.1.2.1") {
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// Explicit VR Big Endian (retired)
|
|
else if (transferSyntax === "1.2.840.10008.1.2.2" ) {
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// Deflate transfer syntax (deflated by dicomParser)
|
|
else if(transferSyntax === '1.2.840.10008.1.2.1.99') {
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// RLE Lossless
|
|
else if (transferSyntax === "1.2.840.10008.1.2.5" )
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// JPEG Baseline lossy process 1 (8 bit)
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.50")
|
|
{
|
|
if(imageFrame.bitsAllocated === 8)
|
|
{
|
|
return cornerstoneWADOImageLoader.decodeJPEGBaseline8Bit(imageFrame, canvas);
|
|
} else {
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
}
|
|
// JPEG Baseline lossy process 2 & 4 (12 bit)
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.51")
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// JPEG Lossless, Nonhierarchical (Processes 14)
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.57")
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.70" )
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// JPEG-LS Lossless Image Compression
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.80" )
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// JPEG-LS Lossy (Near-Lossless) Image Compression
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.81" )
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// JPEG 2000 Lossless
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.90")
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
// JPEG 2000 Lossy
|
|
else if (transferSyntax === "1.2.840.10008.1.2.4.91")
|
|
{
|
|
return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
|
|
}
|
|
/* Don't know if these work...
|
|
// JPEG 2000 Part 2 Multicomponent Image Compression (Lossless Only)
|
|
else if(transferSyntax === "1.2.840.10008.1.2.4.92")
|
|
{
|
|
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
|
|
}
|
|
// JPEG 2000 Part 2 Multicomponent Image Compression
|
|
else if(transferSyntax === "1.2.840.10008.1.2.4.93")
|
|
{
|
|
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
|
|
}
|
|
*/
|
|
else
|
|
{
|
|
if(console && console.log) {
|
|
console.log("Image cannot be decoded due to Unsupported transfer syntax " + transferSyntax);
|
|
}
|
|
throw "no decoder for transfer syntax " + transferSyntax;
|
|
}
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.decodeImageFrame = decodeImageFrame;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
* Special decoder for 8 bit jpeg that leverages the browser's built in JPEG decoder for increased performance
|
|
*/
|
|
(function ($, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function arrayBufferToString(buffer) {
|
|
return binaryToString(String.fromCharCode.apply(null, Array.prototype.slice.apply(new Uint8Array(buffer))));
|
|
}
|
|
|
|
function binaryToString(binary) {
|
|
var error;
|
|
|
|
try {
|
|
return decodeURIComponent(escape(binary));
|
|
} catch (_error) {
|
|
error = _error;
|
|
if (error instanceof URIError) {
|
|
return binary;
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
function decodeJPEGBaseline8Bit(imageFrame, canvas) {
|
|
var start = new Date().getTime();
|
|
var deferred = $.Deferred();
|
|
|
|
var imgBlob = new Blob([imageFrame.pixelData], {type: "image/jpeg"});
|
|
|
|
var r = new FileReader();
|
|
if(r.readAsBinaryString === undefined) {
|
|
r.readAsArrayBuffer(imgBlob);
|
|
}
|
|
else {
|
|
r.readAsBinaryString(imgBlob); // doesn't work on IE11
|
|
}
|
|
|
|
r.onload = function(){
|
|
var img=new Image();
|
|
img.onload = function() {
|
|
canvas.height = img.height;
|
|
canvas.width = img.width;
|
|
imageFrame.rows = img.height;
|
|
imageFrame.columns = img.width;
|
|
var context = canvas.getContext('2d');
|
|
context.drawImage(this, 0, 0);
|
|
var imageData = context.getImageData(0, 0, img.width, img.height);
|
|
var end = new Date().getTime();
|
|
imageFrame.pixelData = imageData.data;
|
|
imageFrame.imageData = imageData;
|
|
imageFrame.decodeTimeInMS = end - start;
|
|
deferred.resolve(imageFrame);
|
|
};
|
|
img.onerror = function(error) {
|
|
deferred.reject(error);
|
|
};
|
|
if(r.readAsBinaryString === undefined) {
|
|
img.src = "data:image/jpeg;base64,"+window.btoa(arrayBufferToString(r.result));
|
|
}
|
|
else {
|
|
img.src = "data:image/jpeg;base64,"+window.btoa(r.result); // doesn't work on IE11
|
|
}
|
|
|
|
};
|
|
return deferred.promise();
|
|
}
|
|
|
|
function isJPEGBaseline8Bit(imageFrame) {
|
|
if((imageFrame.bitsAllocated === 8) &&
|
|
imageFrame.transferSyntax === "1.2.840.10008.1.2.4.50")
|
|
{
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.decodeJPEGBaseline8Bit = decodeJPEGBaseline8Bit;
|
|
cornerstoneWADOImageLoader.isJPEGBaseline8Bit = isJPEGBaseline8Bit;
|
|
|
|
}($, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getImageFrame(imageId) {
|
|
var imagePixelModule = cornerstone.metaData.get('imagePixelModule', imageId);
|
|
|
|
return {
|
|
samplesPerPixel : imagePixelModule.samplesPerPixel,
|
|
photometricInterpretation : imagePixelModule.photometricInterpretation,
|
|
planarConfiguration : imagePixelModule.planarConfiguration,
|
|
rows : imagePixelModule.rows,
|
|
columns : imagePixelModule.columns,
|
|
bitsAllocated : imagePixelModule.bitsAllocated,
|
|
pixelRepresentation : imagePixelModule.pixelRepresentation, // 0 = unsigned,
|
|
smallestPixelValue: imagePixelModule.smallestPixelValue,
|
|
largestPixelValue: imagePixelModule.largestPixelValue,
|
|
redPaletteColorLookupTableDescriptor : imagePixelModule.redPaletteColorLookupTableDescriptor,
|
|
greenPaletteColorLookupTableDescriptor : imagePixelModule.greenPaletteColorLookupTableDescriptor,
|
|
bluePaletteColorLookupTableDescriptor : imagePixelModule.bluePaletteColorLookupTableDescriptor,
|
|
redPaletteColorLookupTableData : imagePixelModule.redPaletteColorLookupTableData,
|
|
greenPaletteColorLookupTableData : imagePixelModule.greenPaletteColorLookupTableData,
|
|
bluePaletteColorLookupTableData : imagePixelModule.bluePaletteColorLookupTableData,
|
|
pixelData: undefined // populated later after decoding
|
|
};
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.getImageFrame = getImageFrame;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getMinMax(storedPixelData)
|
|
{
|
|
// we always calculate the min max values since they are not always
|
|
// present in DICOM and we don't want to trust them anyway as cornerstone
|
|
// depends on us providing reliable values for these
|
|
var min = storedPixelData[0];
|
|
var max = storedPixelData[0];
|
|
var storedPixel;
|
|
var numPixels = storedPixelData.length;
|
|
for(var index = 0; index < numPixels; index++) {
|
|
storedPixel = storedPixelData[index];
|
|
min = Math.min(min, storedPixel);
|
|
max = Math.max(max, storedPixel);
|
|
}
|
|
|
|
return {
|
|
min: min,
|
|
max: max
|
|
};
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.getMinMax = getMinMax;
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
|
|
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function isColorImage(photoMetricInterpretation)
|
|
{
|
|
if(photoMetricInterpretation === "RGB" ||
|
|
photoMetricInterpretation === "PALETTE COLOR" ||
|
|
photoMetricInterpretation === "YBR_FULL" ||
|
|
photoMetricInterpretation === "YBR_FULL_422" ||
|
|
photoMetricInterpretation === "YBR_PARTIAL_422" ||
|
|
photoMetricInterpretation === "YBR_PARTIAL_420" ||
|
|
photoMetricInterpretation === "YBR_RCT" ||
|
|
photoMetricInterpretation === "YBR_ICT")
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.isColorImage = isColorImage;
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.version = '0.13.3';
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
function checkToken(token, data, dataOffset) {
|
|
|
|
if(dataOffset + token.length > data.length) {
|
|
return false;
|
|
}
|
|
|
|
var endIndex = dataOffset;
|
|
|
|
for(var i = 0; i < token.length; i++) {
|
|
if(token[i] !== data[endIndex++]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function stringToUint8Array(str) {
|
|
var uint=new Uint8Array(str.length);
|
|
for(var i=0,j=str.length;i<j;i++){
|
|
uint[i]=str.charCodeAt(i);
|
|
}
|
|
return uint;
|
|
}
|
|
|
|
function findIndexOfString(data, str, offset) {
|
|
|
|
offset = offset || 0;
|
|
|
|
var token = stringToUint8Array(str);
|
|
|
|
for(var i=offset; i < data.length; i++) {
|
|
if(token[0] === data[i]) {
|
|
//console.log('match @', i);
|
|
if(checkToken(token, data, i)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
cornerstoneWADOImageLoader.wadors.findIndexOfString = findIndexOfString;
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function findBoundary(header) {
|
|
for(var i=0; i < header.length; i++) {
|
|
if(header[i].substr(0,2) === '--') {
|
|
return header[i];
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function findContentType(header) {
|
|
for(var i=0; i < header.length; i++) {
|
|
if(header[i].substr(0,13) === 'Content-Type:') {
|
|
return header[i].substr(13).trim();
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function uint8ArrayToString(data, offset, length) {
|
|
offset = offset || 0;
|
|
length = length || data.length - offset;
|
|
var str = "";
|
|
for(var i=offset; i < offset + length; i++) {
|
|
str += String.fromCharCode(data[i]);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.wadors.getPixelData = function(uri, imageId, mediaType) {
|
|
mediaType = mediaType || 'application/octet-stream';
|
|
var headers = {
|
|
accept : mediaType
|
|
};
|
|
|
|
var deferred = $.Deferred();
|
|
|
|
var loadPromise = cornerstoneWADOImageLoader.internal.xhrRequest(uri, imageId, headers);
|
|
loadPromise.then(function(imageFrameAsArrayBuffer/*, xhr*/) {
|
|
|
|
// request succeeded, Parse the multi-part mime response
|
|
var response = new Uint8Array(imageFrameAsArrayBuffer);
|
|
|
|
// First look for the multipart mime header
|
|
var tokenIndex = cornerstoneWADOImageLoader.wadors.findIndexOfString(response, '\n\r\n');
|
|
if(tokenIndex === -1) {
|
|
deferred.reject('invalid response - no multipart mime header');
|
|
}
|
|
var header = uint8ArrayToString(response, 0, tokenIndex);
|
|
// Now find the boundary marker
|
|
var split = header.split('\r\n');
|
|
var boundary = findBoundary(split);
|
|
if(!boundary) {
|
|
deferred.reject('invalid response - no boundary marker')
|
|
}
|
|
var offset = tokenIndex + 3; // skip over the \n\r\n
|
|
|
|
// find the terminal boundary marker
|
|
var endIndex = cornerstoneWADOImageLoader.wadors.findIndexOfString(response, boundary, offset);
|
|
if(endIndex === -1) {
|
|
deferred.reject('invalid response - terminating boundary not found');
|
|
}
|
|
// return the info for this pixel data
|
|
var length = endIndex - offset;
|
|
deferred.resolve({
|
|
contentType: findContentType(split),
|
|
imageFrame: new Uint8Array(imageFrameAsArrayBuffer, offset, length)
|
|
});
|
|
});
|
|
return deferred.promise();
|
|
|
|
};
|
|
}(cornerstoneWADOImageLoader));
|
|
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getTransferSyntaxForContentType(contentType) {
|
|
return '1.2.840.10008.1.2'; // hard code to ILE for now
|
|
}
|
|
|
|
function loadImage(imageId, options) {
|
|
var start = new Date().getTime();
|
|
|
|
var deferred = $.Deferred();
|
|
|
|
var uri = imageId.substring(7);
|
|
|
|
// check to make sure we have metadata for this imageId
|
|
var metaData = cornerstoneWADOImageLoader.wadors.metaDataManager.get(imageId);
|
|
if(metaData === undefined) {
|
|
deferred.reject('no metadata for imageId ' + imageId);
|
|
return deferred.promise();
|
|
}
|
|
|
|
// TODO: load bulk data items that we might need
|
|
|
|
var mediaType;// = 'image/dicom+jp2';
|
|
|
|
// get the pixel data from the server
|
|
cornerstoneWADOImageLoader.wadors.getPixelData(uri, imageId, mediaType).then(function(result) {
|
|
|
|
var transferSyntax = getTransferSyntaxForContentType(result.contentType);
|
|
var pixelData = result.imageFrame.pixelData;
|
|
var imagePromise = cornerstoneWADOImageLoader.createImage(imageId, pixelData, transferSyntax, options);
|
|
imagePromise.then(function(image) {
|
|
// add the loadTimeInMS property
|
|
var end = new Date().getTime();
|
|
image.loadTimeInMS = end - start;
|
|
deferred.resolve(image);
|
|
})
|
|
}).fail(function(reason) {
|
|
deferred.reject(reason);
|
|
});
|
|
|
|
return deferred;
|
|
}
|
|
|
|
// register wadors scheme
|
|
cornerstone.registerImageLoader('wadors', loadImage);
|
|
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
/**
|
|
* Returns the first string value as a Javascript number
|
|
*
|
|
* @param element - The javascript object for the specified element in the metadata
|
|
* @param [index] - the index of the value in a multi-valued element, default is 0
|
|
* @param [defaultValue] - The default value to return if the element does not exist
|
|
* @returns {*}
|
|
*/
|
|
function getNumberString(element, index, defaultValue) {
|
|
var value = cornerstoneWADOImageLoader.wadors.getValue(element, index, defaultValue);
|
|
if(value === undefined) {
|
|
return;
|
|
}
|
|
return parseFloat(value);
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.wadors.getNumberString = getNumberString;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getNumberValue(element, index) {
|
|
var value = cornerstoneWADOImageLoader.wadors.getValue(element, index);
|
|
if(value === undefined) {
|
|
return;
|
|
}
|
|
return parseFloat(value);
|
|
}
|
|
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadors.getNumberValue = getNumberValue
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
/**
|
|
* Returns the values as an array of javascript numbers
|
|
*
|
|
* @param element - The javascript object for the specified element in the metadata
|
|
* @param [minimumLength] - the minimum number of values
|
|
* @returns {*}
|
|
*/
|
|
function getNumberValues(element, minimumLength) {
|
|
if (!element) {
|
|
return;
|
|
}
|
|
// Value is not present if the attribute has a zero length value
|
|
if (!element.Value) {
|
|
return;
|
|
}
|
|
// make sure we have the expected length
|
|
if (minimumLength && element.Value.length < minimumLength) {
|
|
return;
|
|
}
|
|
|
|
var values = [];
|
|
for(var i=0; i < element.Value.length; i++) {
|
|
values.push(parseFloat(element.Value[i]));
|
|
}
|
|
return values;
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.wadors.getNumberValues = getNumberValues;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
/**
|
|
* Returns the raw value
|
|
*
|
|
* @param element - The javascript object for the specified element in the metadata
|
|
* @param [index] - the index of the value in a multi-valued element, default is 0
|
|
* @param [defaultValue] - The default value to return if the element does not exist
|
|
* @returns {*}
|
|
*/
|
|
function getValue(element, index, defaultValue) {
|
|
index = index || 0;
|
|
if (!element) {
|
|
return defaultValue;
|
|
}
|
|
// Value is not present if the attribute has a zero length value
|
|
if (!element.Value) {
|
|
return defaultValue;
|
|
}
|
|
// make sure we have the specified index
|
|
if (element.Value.length <= index ) {
|
|
return defaultValue;
|
|
}
|
|
return element.Value[index];
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.wadors.getValue = getValue;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
var getNumberValues = cornerstoneWADOImageLoader.wadors.getNumberValues;
|
|
var getValue = cornerstoneWADOImageLoader.wadors.getValue;
|
|
var getNumberValue = cornerstoneWADOImageLoader.wadors.getNumberValue;
|
|
|
|
function metaDataProvider(type, imageId) {
|
|
var metaData = cornerstoneWADOImageLoader.wadors.metaDataManager.get(imageId);
|
|
if(!metaData) {
|
|
return;
|
|
}
|
|
|
|
if (type === 'imagePlaneModule') {
|
|
return {
|
|
pixelSpacing: getNumberValues(metaData['00280030'], 2),
|
|
imageOrientationPatient: getNumberValues(metaData['00200037'], 6),
|
|
imagePositionPatient: getNumberValues(metaData['00200032'], 3),
|
|
sliceThickness: getNumberValue(metaData['00180050']),
|
|
sliceLocation: getNumberValue(metaData['00201041'])
|
|
};
|
|
}
|
|
|
|
if (type === 'imagePixelModule') {
|
|
return {
|
|
samplesPerPixel: getValue(metaData['00280002']),
|
|
photometricInterpretation: getValue(metaData['00280004']),
|
|
rows: getValue(metaData['00280010']),
|
|
columns: getValue(metaData['00280011']),
|
|
bitsAllocated: getValue(metaData['00280100']),
|
|
bitsStored: getValue(metaData['00280101']),
|
|
highBit: getValue(metaData['00280102']),
|
|
pixelRepresentation: getValue(metaData['00280103']),
|
|
planarConfiguration: getValue(metaData['00280006']),
|
|
pixelAspectRatio: getValue(metaData['00280034']),
|
|
smallestPixelValue: getValue(metaData['00280106']),
|
|
largestPixelValue: getValue(metaData['00280107'])
|
|
// TODO Color Palette
|
|
};
|
|
}
|
|
|
|
if (type === 'voiLutModule') {
|
|
return {
|
|
// TODO VOT LUT Sequence
|
|
windowCenter : getNumberValues(metaData['00281050'], 1),
|
|
windowWidth : getNumberValues(metaData['00281051'], 1)
|
|
};
|
|
}
|
|
|
|
if (type === 'modalityLutModule') {
|
|
return {
|
|
// TODO VOT LUT Sequence
|
|
rescaleIntercept : getNumberValue(metaData['00281052']),
|
|
rescaleSlope : getNumberValue(metaData['00281053']),
|
|
rescaleType: getValue(metaData['00281054'])
|
|
};
|
|
}
|
|
|
|
if (type === 'sopCommonModule') {
|
|
return {
|
|
sopClassUID : getValue(metaData['00080016']),
|
|
sopInstanceUID : getValue(metaData['00080018'])
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
|
|
cornerstone.metaData.addProvider(metaDataProvider);
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadors.metaDataProvider = metaDataProvider
|
|
|
|
}(cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
var imageIds = [];
|
|
|
|
function add(imageId, metadata) {
|
|
imageIds[imageId] = metadata;
|
|
}
|
|
|
|
function get(imageId) {
|
|
return imageIds[imageId];
|
|
}
|
|
|
|
function remove(imageId) {
|
|
imageIds[imageId] = undefined;
|
|
}
|
|
|
|
function purge() {
|
|
imageIds = [];
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadors.metaDataManager = {
|
|
add : add,
|
|
get : get,
|
|
remove:remove,
|
|
purge: purge
|
|
};
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
* This object supports loading of DICOM P10 dataset from a uri and caching it so it can be accessed
|
|
* by the caller. This allows a caller to access the datasets without having to go through cornerstone's
|
|
* image loader mechanism. One reason a caller may need to do this is to determine the number of frames
|
|
* in a multiframe sop instance so it can create the imageId's correctly.
|
|
*/
|
|
(function ($, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
var loadedDataSets = {};
|
|
var promises = {};
|
|
|
|
// returns true if the wadouri for the specified index has been loaded
|
|
function isLoaded(uri) {
|
|
return loadedDataSets[uri] !== undefined;
|
|
}
|
|
|
|
function get(uri) {
|
|
|
|
// if already loaded return it right away
|
|
if(!loadedDataSets[uri]) {
|
|
return;
|
|
}
|
|
|
|
return loadedDataSets[uri].dataSet;
|
|
}
|
|
|
|
|
|
// loads the dicom dataset from the wadouri sp
|
|
function load(uri, loadRequest, imageId) {
|
|
|
|
loadRequest = loadRequest || cornerstoneWADOImageLoader.internal.xhrRequest;
|
|
|
|
// if already loaded return it right away
|
|
if(loadedDataSets[uri]) {
|
|
//console.log('using loaded dataset ' + uri);
|
|
var alreadyLoadedpromise = $.Deferred();
|
|
loadedDataSets[uri].cacheCount++;
|
|
alreadyLoadedpromise.resolve(loadedDataSets[uri].dataSet);
|
|
return alreadyLoadedpromise;
|
|
}
|
|
|
|
// if we are currently loading this uri, return its promise
|
|
if(promises[uri]) {
|
|
//console.log('returning existing load promise for ' + uri);
|
|
return promises[uri];
|
|
}
|
|
|
|
//console.log('loading ' + uri);
|
|
|
|
// This uri is not loaded or being loaded, load it via an xhrRequest
|
|
var promise = loadRequest(uri, imageId);
|
|
promises[uri] = promise;
|
|
|
|
// handle success and failure of the XHR request load
|
|
var loadDeferred = $.Deferred();
|
|
promise.then(function(dicomPart10AsArrayBuffer/*, xhr*/) {
|
|
var byteArray = new Uint8Array(dicomPart10AsArrayBuffer);
|
|
var dataSet = dicomParser.parseDicom(byteArray);
|
|
|
|
loadedDataSets[uri] = {
|
|
dataSet: dataSet,
|
|
cacheCount: 1
|
|
};
|
|
loadDeferred.resolve(dataSet);
|
|
// done loading, remove the promise
|
|
delete promises[uri];
|
|
}, function () {
|
|
}).always(function() {
|
|
// error thrown, remove the promise
|
|
delete promises[uri];
|
|
});
|
|
return loadDeferred;
|
|
}
|
|
|
|
// remove the cached/loaded dicom dataset for the specified wadouri to free up memory
|
|
function unload(uri) {
|
|
//console.log('unload for ' + uri);
|
|
if(loadedDataSets[uri]) {
|
|
loadedDataSets[uri].cacheCount--;
|
|
if(loadedDataSets[uri].cacheCount === 0) {
|
|
//console.log('removing loaded dataset for ' + uri);
|
|
delete loadedDataSets[uri];
|
|
}
|
|
}
|
|
}
|
|
|
|
// removes all cached datasets from memory
|
|
function purge() {
|
|
loadedDataSets = {};
|
|
promises = {};
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.dataSetCacheManager = {
|
|
isLoaded: isLoaded,
|
|
load: load,
|
|
unload: unload,
|
|
purge: purge,
|
|
get: get
|
|
};
|
|
|
|
}($, cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
var files = [];
|
|
|
|
function add(file) {
|
|
var fileIndex = files.push(file);
|
|
return 'dicomfile:' + (fileIndex - 1);
|
|
}
|
|
|
|
function get(index) {
|
|
return files[index];
|
|
}
|
|
|
|
function remove(index) {
|
|
files[index] = undefined;
|
|
}
|
|
|
|
function purge() {
|
|
files = [];
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.fileManager = {
|
|
add : add,
|
|
get : get,
|
|
remove:remove,
|
|
purge: purge
|
|
};
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
* Function to deal with extracting an image frame from an encapsulated data set.
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function framesAreFragmented(dataSet) {
|
|
var numberOfFrames = dataSet.intString('x00280008');
|
|
var pixelDataElement = dataSet.elements.x7fe00010;
|
|
if(numberOfFrames != pixelDataElement.fragments.length) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function getEncodedImageFrame(dataSet, frame) {
|
|
// Empty basic offset table
|
|
if(!dataSet.elements.x7fe00010.basicOffsetTable.length) {
|
|
if(framesAreFragmented(dataSet)) {
|
|
var basicOffsetTable = dicomParser.createJPEGBasicOffsetTable(dataSet, dataSet.elements.x7fe00010);
|
|
return dicomParser.readEncapsulatedImageFrame(dataSet, dataSet.elements.x7fe00010, frame, basicOffsetTable);
|
|
} else {
|
|
return dicomParser.readEncapsulatedPixelDataFromFragments(dataSet, dataSet.elements.x7fe00010, frame);
|
|
}
|
|
}
|
|
|
|
// Basic Offset Table is not empty
|
|
return dicomParser.readEncapsulatedImageFrame(dataSet, dataSet.elements.x7fe00010, frame);
|
|
}
|
|
|
|
function getEncapsulatedImageFrame(dataSet, frameIndex) {
|
|
return getEncodedImageFrame(dataSet, frameIndex);
|
|
}
|
|
cornerstoneWADOImageLoader.wadouri.getEncapsulatedImageFrame = getEncapsulatedImageFrame;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
/**
|
|
* Function to deal with extracting an image frame from an encapsulated data set.
|
|
*/
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getUncompressedImageFrame(dataSet, frameIndex) {
|
|
var pixelDataElement = dataSet.elements.x7fe00010;
|
|
var bitsAllocated = dataSet.uint16('x00280100');
|
|
var rows = dataSet.uint16('x00280010');
|
|
var columns = dataSet.uint16('x00280011');
|
|
var samplesPerPixel = dataSet.uint16('x00280002');
|
|
|
|
var pixelDataOffset = pixelDataElement.dataOffset;
|
|
var pixelsPerFrame = rows * columns * samplesPerPixel;
|
|
|
|
var frameOffset;
|
|
if(bitsAllocated === 8) {
|
|
frameOffset = pixelDataOffset + frameIndex * pixelsPerFrame;
|
|
if(frameOffset >= dataSet.byteArray.length) {
|
|
throw 'frame exceeds size of pixelData';
|
|
}
|
|
return new Uint8Array(dataSet.byteArray.buffer, frameOffset, pixelsPerFrame);
|
|
}
|
|
else if(bitsAllocated === 16) {
|
|
frameOffset = pixelDataOffset + frameIndex * pixelsPerFrame * 2;
|
|
if(frameOffset >= dataSet.byteArray.length) {
|
|
throw 'frame exceeds size of pixelData';
|
|
}
|
|
return new Uint8Array(dataSet.byteArray.buffer, frameOffset,pixelsPerFrame * 2);
|
|
}
|
|
|
|
throw 'unsupported pixel format';
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.wadouri.getUncompressedImageFrame = getUncompressedImageFrame;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function loadFileRequest(uri) {
|
|
|
|
var parsedImageId = cornerstoneWADOImageLoader.wadouri.parseImageId(uri);
|
|
var fileIndex = parseInt(parsedImageId.url);
|
|
var file = cornerstoneWADOImageLoader.wadouri.fileManager.get(fileIndex);
|
|
|
|
// create a deferred object
|
|
var deferred = $.Deferred();
|
|
|
|
var fileReader = new FileReader();
|
|
fileReader.onload = function (e) {
|
|
var dicomPart10AsArrayBuffer = e.target.result;
|
|
deferred.resolve(dicomPart10AsArrayBuffer);
|
|
};
|
|
fileReader.readAsArrayBuffer(file);
|
|
|
|
return deferred.promise();
|
|
}
|
|
cornerstoneWADOImageLoader.wadouri.loadFileRequest = loadFileRequest;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|
|
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getLutDescriptor(dataSet, tag) {
|
|
if(!dataSet.elements[tag] || dataSet.elements[tag].length != 6) {
|
|
return;
|
|
}
|
|
return [dataSet.uint16(tag, 0),dataSet.uint16(tag, 1), dataSet.uint16(tag, 2)]
|
|
}
|
|
|
|
function getLutData(lutDataSet, tag, lutDescriptor) {
|
|
var lut = [];
|
|
var lutData = lutDataSet.elements[tag];
|
|
var numLutEntries = lutDescriptor[0];
|
|
for (var i = 0; i < numLutEntries; i++) {
|
|
// Output range is always unsigned
|
|
if(lutDescriptor[2] === 16) {
|
|
lut[i] = lutDataSet.uint16(tag, i);
|
|
}
|
|
else {
|
|
lut[i] = lutDataSet.byteArray[i + lutData.dataOffset];
|
|
}
|
|
}
|
|
return lut;
|
|
}
|
|
|
|
function populatePaletteColorLut(dataSet, imagePixelModule) {
|
|
// return immediately if no palette lut elements
|
|
if(!dataSet.elements['x00281101']) {
|
|
return;
|
|
}
|
|
imagePixelModule.redPaletteColorLookupTableDescriptor = getLutDescriptor(dataSet, 'x00281101');
|
|
imagePixelModule.greenPaletteColorLookupTableDescriptor = getLutDescriptor(dataSet, 'x00281102');
|
|
imagePixelModule.bluePaletteColorLookupTableDescriptor = getLutDescriptor(dataSet, 'x00281103');
|
|
|
|
imagePixelModule.redPaletteColorLookupTableData = getLutData(dataSet, 'x00281201', imagePixelModule.redPaletteColorLookupTableDescriptor);
|
|
imagePixelModule.greenPaletteColorLookupTableData = getLutData(dataSet, 'x00281202', imagePixelModule.greenPaletteColorLookupTableDescriptor);
|
|
imagePixelModule.bluePaletteColorLookupTableData = getLutData(dataSet, 'x00281203', imagePixelModule.bluePaletteColorLookupTableDescriptor);
|
|
}
|
|
|
|
function populateSmallestLargestPixelValues(dataSet, imagePixelModule) {
|
|
var pixelRepresentation = dataSet.uint16('x00280103');
|
|
if(pixelRepresentation === 0) {
|
|
imagePixelModule.smallestPixelValue = dataSet.uint16('x00280106');
|
|
imagePixelModule.largestPixelValue = dataSet.uint16('x00280107');
|
|
} else {
|
|
imagePixelModule.smallestPixelValue = dataSet.int16('x00280106');
|
|
imagePixelModule.largestPixelValue = dataSet.int16('x00280107');
|
|
}
|
|
}
|
|
|
|
function getImagePixelModule(dataSet) {
|
|
|
|
var imagePixelModule = {
|
|
samplesPerPixel: dataSet.uint16('x00280002'),
|
|
photometricInterpretation: dataSet.string('x00280004'),
|
|
rows: dataSet.uint16('x00280010'),
|
|
columns: dataSet.uint16('x00280011'),
|
|
bitsAllocated: dataSet.uint16('x00280100'),
|
|
bitsStored: dataSet.uint16('x00280101'),
|
|
highBit: dataSet.uint16('x00280102'),
|
|
pixelRepresentation: dataSet.uint16('x00280103'),
|
|
planarConfiguration: dataSet.uint16('x00280006'),
|
|
pixelAspectRatio: dataSet.string('x00280034')
|
|
};
|
|
populateSmallestLargestPixelValues(dataSet, imagePixelModule);
|
|
populatePaletteColorLut(dataSet, imagePixelModule);
|
|
return imagePixelModule;
|
|
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.getImagePixelModule = getImagePixelModule
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getLUT(pixelRepresentation, lutDataSet) {
|
|
var numLUTEntries = lutDataSet.uint16('x00283002', 0);
|
|
if(numLUTEntries === 0) {
|
|
numLUTEntries = 65535;
|
|
}
|
|
var firstValueMapped = 0;
|
|
if(pixelRepresentation === 0) {
|
|
firstValueMapped = lutDataSet.uint16('x00283002', 1);
|
|
} else {
|
|
firstValueMapped = lutDataSet.int16('x00283002', 1);
|
|
}
|
|
var numBitsPerEntry = lutDataSet.uint16('x00283002', 2);
|
|
//console.log('LUT(', numLUTEntries, ',', firstValueMapped, ',', numBitsPerEntry, ')');
|
|
var lut = {
|
|
id : '1',
|
|
firstValueMapped: firstValueMapped,
|
|
numBitsPerEntry : numBitsPerEntry,
|
|
lut : []
|
|
};
|
|
|
|
//console.log("minValue=", minValue, "; maxValue=", maxValue);
|
|
for (var i = 0; i < numLUTEntries; i++) {
|
|
if(pixelRepresentation === 0) {
|
|
lut.lut[i] = lutDataSet.uint16('x00283006', i);
|
|
} else {
|
|
lut.lut[i] = lutDataSet.int16('x00283006', i);
|
|
}
|
|
}
|
|
return lut;
|
|
}
|
|
|
|
|
|
function getLUTs(pixelRepresentation, lutSequence) {
|
|
if(!lutSequence || !lutSequence.items.length) {
|
|
return;
|
|
}
|
|
var luts = [];
|
|
for(var i=0; i < lutSequence.items.length; i++) {
|
|
var lutDataSet = lutSequence.items[i].dataSet;
|
|
var lut = getLUT(pixelRepresentation, lutDataSet);
|
|
if(lut) {
|
|
luts.push(lut);
|
|
}
|
|
}
|
|
return luts;
|
|
}
|
|
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.getLUTs = getLUTs
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getMinStoredPixelValue(dataSet) {
|
|
var pixelRepresentation = dataSet.uint16('x00280103');
|
|
var bitsStored = dataSet.uint16('x00280101');
|
|
if(pixelRepresentation === 0) {
|
|
return 0;
|
|
}
|
|
return -1 << (bitsStored -1);
|
|
}
|
|
|
|
// 0 = unsigned / US, 1 = signed / SS
|
|
function getModalityLUTOutputPixelRepresentation(dataSet) {
|
|
|
|
// CT SOP Classes are always signed
|
|
var sopClassUID = dataSet.string('x00080016');
|
|
if(sopClassUID === '1.2.840.10008.5.1.4.1.1.2' ||
|
|
sopClassUID === '1.2.840.10008.5.1.4.1.1.2.1') {
|
|
return 1;
|
|
}
|
|
|
|
// if rescale intercept and rescale slope are present, pass the minimum stored
|
|
// pixel value through them to see if we get a signed output range
|
|
var rescaleIntercept = dataSet.floatString('x00281052');
|
|
var rescaleSlope = dataSet.floatString('x00281053');
|
|
if(rescaleIntercept !== undefined && rescaleSlope !== undefined) {
|
|
var minStoredPixelValue = getMinStoredPixelValue(dataSet); //
|
|
var minModalityLutValue = minStoredPixelValue * rescaleSlope + rescaleIntercept;
|
|
if (minModalityLutValue < 0) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Output of non linear modality lut is always unsigned
|
|
if(dataSet.elements.x00283000 && dataSet.elements.x00283000.length > 0) {
|
|
return 0;
|
|
}
|
|
|
|
// If no modality lut transform, output is same as pixel representation
|
|
return dataSet.uint16('x00280103');
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.getModalityLUTOutputPixelRepresentation = getModalityLUTOutputPixelRepresentation
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function getNumberValues(dataSet, tag, minimumLength) {
|
|
var values = [];
|
|
var valueAsString = dataSet.string(tag);
|
|
if(!valueAsString) {
|
|
return;
|
|
}
|
|
var split = valueAsString.split('\\');
|
|
if(minimumLength && split.length < minimumLength) {
|
|
return;
|
|
}
|
|
for(var i=0;i < split.length; i++) {
|
|
values.push(parseFloat(split[i]));
|
|
}
|
|
return values;
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.getNumberValues = getNumberValues
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
/**
|
|
*/
|
|
(function (cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
var getNumberValues = cornerstoneWADOImageLoader.wadouri.getNumberValues;
|
|
|
|
function metaDataProvider(type, imageId) {
|
|
var parsedImageId = cornerstoneWADOImageLoader.wadouri.parseImageId(imageId);
|
|
|
|
var dataSet = cornerstoneWADOImageLoader.wadouri.dataSetCacheManager.get(parsedImageId.url);
|
|
if(!dataSet) {
|
|
return;
|
|
}
|
|
|
|
if (type === 'imagePlaneModule') {
|
|
return {
|
|
pixelSpacing: getNumberValues(dataSet, 'x00280030', 2),
|
|
imageOrientationPatient: getNumberValues(dataSet, 'x00200037', 6),
|
|
imagePositionPatient: getNumberValues(dataSet, 'x00200032', 3),
|
|
sliceThickness: dataSet.floatString('x00180050'),
|
|
sliceLocation: dataSet.floatString('x00201041'),
|
|
frameOfReferenceUID: dataSet.string('x00200052')
|
|
};
|
|
}
|
|
|
|
if (type === 'imagePixelModule') {
|
|
return cornerstoneWADOImageLoader.wadouri.getImagePixelModule(dataSet);
|
|
}
|
|
|
|
if (type === 'modalityLutModule') {
|
|
return {
|
|
rescaleIntercept : dataSet.floatString('x00281052'),
|
|
rescaleSlope : dataSet.floatString('x00281053'),
|
|
rescaleType: dataSet.string('x00281054'),
|
|
modalityLUTSequence : cornerstoneWADOImageLoader.wadouri.getLUTs(dataSet.uint16('x00280103'), dataSet.elements.x00283000)
|
|
};
|
|
}
|
|
|
|
if (type === 'voiLutModule') {
|
|
var modalityLUTOutputPixelRepresentation = cornerstoneWADOImageLoader.wadouri.getModalityLUTOutputPixelRepresentation(dataSet);
|
|
return {
|
|
windowCenter : getNumberValues(dataSet, 'x00281050', 1),
|
|
windowWidth : getNumberValues(dataSet, 'x00281051', 1),
|
|
voiLUTSequence : cornerstoneWADOImageLoader.wadouri.getLUTs(modalityLUTOutputPixelRepresentation, dataSet.elements.x00283010)
|
|
};
|
|
}
|
|
|
|
if (type === 'sopCommonModule') {
|
|
return {
|
|
sopClassUID : dataSet.string('x00080016'),
|
|
sopInstanceUID : dataSet.string('x00080018')
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// register our metadata provider
|
|
cornerstone.metaData.addProvider(metaDataProvider);
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.metaDataProvider = metaDataProvider
|
|
|
|
}(cornerstone, cornerstoneWADOImageLoader));
|
|
(function (cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
function parseImageId(imageId) {
|
|
// build a url by parsing out the url scheme and frame index from the imageId
|
|
var firstColonIndex = imageId.indexOf(':');
|
|
var url = imageId.substring(firstColonIndex + 1);
|
|
var frameIndex = url.indexOf('frame=');
|
|
var frame;
|
|
if(frameIndex !== -1) {
|
|
var frameStr = url.substr(frameIndex + 6);
|
|
frame = parseInt(frameStr);
|
|
url = url.substr(0, frameIndex-1);
|
|
}
|
|
return {
|
|
scheme: imageId.substr(0, firstColonIndex),
|
|
url : url,
|
|
frame: frame
|
|
};
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.wadouri.parseImageId = parseImageId;
|
|
|
|
}(cornerstoneWADOImageLoader));
|
|
(function ($, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
// the taskId to assign to the next task added via addTask()
|
|
var nextTaskId = 0;
|
|
|
|
// array of queued tasks sorted with highest priority task first
|
|
var tasks = [];
|
|
|
|
// array of web workers to dispatch decode tasks to
|
|
var webWorkers = [];
|
|
|
|
var defaultConfig = {
|
|
maxWebWorkers: navigator.hardwareConcurrency || 1,
|
|
startWebWorkersOnDemand: true,
|
|
webWorkerPath : '../../dist/cornerstoneWADOImageLoaderWebWorker.js',
|
|
webWorkerTaskPaths: [],
|
|
taskConfiguration: {
|
|
'decodeTask' : {
|
|
loadCodecsOnStartup : true,
|
|
initializeCodecsOnStartup: false,
|
|
codecsPath: '../dist/cornerstoneWADOImageLoaderCodecs.js',
|
|
usePDFJS: false
|
|
}
|
|
}
|
|
};
|
|
|
|
var config;
|
|
|
|
var statistics = {
|
|
maxWebWorkers : 0,
|
|
numWebWorkers : 0,
|
|
numTasksQueued : 0,
|
|
numTasksExecuting : 0,
|
|
numTasksCompleted: 0,
|
|
totalTaskTimeInMS: 0,
|
|
totalTimeDelayedInMS: 0
|
|
};
|
|
|
|
/**
|
|
* Function to start a task on a web worker
|
|
*/
|
|
function startTaskOnWebWorker() {
|
|
// return immediately if no decode tasks to do
|
|
if(!tasks.length) {
|
|
return;
|
|
}
|
|
|
|
// look for a web worker that is ready
|
|
for(var i=0; i < webWorkers.length; i++) {
|
|
{
|
|
if(webWorkers[i].status === 'ready') {
|
|
// mark it as busy so tasks are not assigned to it
|
|
webWorkers[i].status = 'busy';
|
|
|
|
// get the highest priority task
|
|
var task = tasks.shift();
|
|
task.start = new Date().getTime();
|
|
|
|
// update stats with how long this task was delayed (waiting in queue)
|
|
var end = new Date().getTime();
|
|
statistics.totalTimeDelayedInMS += end - task.added;
|
|
|
|
// assign this task to this web worker and send the web worker
|
|
// a message to execute it
|
|
webWorkers[i].task = task;
|
|
webWorkers[i].worker.postMessage({
|
|
taskType: task.taskType,
|
|
workerIndex: i,
|
|
data: task.data
|
|
}, task.transferList);
|
|
statistics.numTasksExecuting++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if no available web workers and we haven't started max web workers, start a new one
|
|
if(webWorkers.length < config.maxWebWorkers) {
|
|
spawnWebWorker();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to handle a message from a web worker
|
|
* @param msg
|
|
*/
|
|
function handleMessageFromWorker(msg) {
|
|
//console.log('handleMessageFromWorker', msg.data);
|
|
if(msg.data.taskType === 'initialize') {
|
|
webWorkers[msg.data.workerIndex].status = 'ready';
|
|
startTaskOnWebWorker();
|
|
} else {
|
|
statistics.numTasksExecuting--;
|
|
webWorkers[msg.data.workerIndex].status = 'ready';
|
|
statistics.numTasksCompleted++;
|
|
var end = new Date().getTime();
|
|
statistics.totalTaskTimeInMS += end - webWorkers[msg.data.workerIndex].task.start;
|
|
webWorkers[msg.data.workerIndex].task.deferred.resolve(msg.data.result);
|
|
webWorkers[msg.data.workerIndex].task = undefined;
|
|
startTaskOnWebWorker();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Spawns a new web worker
|
|
*/
|
|
function spawnWebWorker() {
|
|
// prevent exceeding maxWebWorkers
|
|
if(webWorkers.length >= config.maxWebWorkers) {
|
|
return;
|
|
}
|
|
|
|
// spawn the webworker
|
|
var worker = new Worker(config.webWorkerPath);
|
|
webWorkers.push({
|
|
worker: worker,
|
|
status: 'initializing'
|
|
});
|
|
worker.addEventListener('message', handleMessageFromWorker);
|
|
worker.postMessage({
|
|
taskType: 'initialize',
|
|
workerIndex: webWorkers.length - 1,
|
|
config: config
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialization function for the web worker manager - spawns web workers
|
|
* @param configObject
|
|
*/
|
|
function initialize(configObject) {
|
|
configObject = configObject || defaultConfig;
|
|
|
|
// prevent being initialized more than once
|
|
if(config) {
|
|
throw new Error('WebWorkerManager already initialized');
|
|
}
|
|
|
|
config = configObject;
|
|
|
|
config.maxWebWorkers = config.maxWebWorkers || (navigator.hardwareConcurrency || 1);
|
|
|
|
// Spawn new web workers
|
|
if(!config.startWebWorkersOnDemand) {
|
|
for(var i=0; i < config.maxWebWorkers; i++) {
|
|
spawnWebWorker();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* dynamically loads a web worker task
|
|
* @param sourcePath
|
|
* @param taskConfig
|
|
*/
|
|
function loadWebWorkerTask(sourcePath, taskConfig) {
|
|
// add it to the list of web worker tasks paths so on demand web workers
|
|
// load this properly
|
|
config.webWorkerTaskPaths.push(sourcePath);
|
|
|
|
// if a task specific configuration is provided, merge it into the config
|
|
if(taskConfig) {
|
|
config.taskConfiguration = Object.assign(config.taskConfiguration, taskConfig);
|
|
}
|
|
|
|
// tell each spawned web worker to load this task
|
|
for(var i=0; i < webWorkers.length; i++) {
|
|
webWorkers[i].worker.postMessage({
|
|
taskType: 'loadWebWorkerTask',
|
|
workerIndex: webWorkers.length - 1,
|
|
sourcePath: sourcePath,
|
|
config: config
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to add a decode task to be performed
|
|
*
|
|
* @param taskType - the taskType for this task
|
|
* @param data - data specific to the task
|
|
* @param priority - optional priority of the task (defaults to 0), > 0 is higher, < 0 is lower
|
|
* @param transferList - optional array of data to transfer to web worker
|
|
* @returns {*}
|
|
*/
|
|
function addTask(taskType, data, priority, transferList) {
|
|
if (!config) {
|
|
initialize();
|
|
}
|
|
|
|
priority = priority || 0;
|
|
var deferred = $.Deferred();
|
|
|
|
// find the right spot to insert this decode task (based on priority)
|
|
for (var i = 0; i < tasks.length; i++) {
|
|
if (tasks[i].priority <= priority) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
var taskId = nextTaskId++;
|
|
|
|
// insert the decode task at position i
|
|
tasks.splice(i, 0, {
|
|
taskId: taskId,
|
|
taskType: taskType,
|
|
status: 'ready',
|
|
added: new Date().getTime(),
|
|
data: data,
|
|
deferred: deferred,
|
|
priority: priority,
|
|
transferList: transferList
|
|
});
|
|
|
|
// try to start a task on the web worker since we just added a new task and a web worker may be available
|
|
startTaskOnWebWorker();
|
|
|
|
return {
|
|
taskId: taskId,
|
|
promise: deferred.promise()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Changes the priority of a queued task
|
|
* @param taskId - the taskId to change the priority of
|
|
* @param priority - priority of the task (defaults to 0), > 0 is higher, < 0 is lower
|
|
* @returns boolean - true on success, false if taskId not found
|
|
*/
|
|
function setTaskPriority(taskId, priority) {
|
|
// search for this taskId
|
|
for (var i = 0; i < tasks.length; i++) {
|
|
if (tasks[i].taskId === taskId) {
|
|
// taskId found, remove it
|
|
var task = tasks.splice(i, 1)[0];
|
|
|
|
// set its prioirty
|
|
task.priority = priority;
|
|
|
|
// find the right spot to insert this decode task (based on priority)
|
|
for (i = 0; i < tasks.length; i++) {
|
|
if (tasks[i].priority <= priority) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// insert the decode task at position i
|
|
tasks.splice(i, 0, task);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Cancels a queued task and rejects
|
|
* @param taskId - the taskId to cancel
|
|
* @param reason - optional reason the task was rejected
|
|
* @returns boolean - true on success, false if taskId not found
|
|
*/
|
|
function cancelTask(taskId, reason) {
|
|
// search for this taskId
|
|
for (var i = 0; i < tasks.length; i++) {
|
|
if (tasks[i].taskId === taskId) {
|
|
// taskId found, remove it
|
|
var task = tasks.splice(i, 1);
|
|
task.promise.reject(reason);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Function to return the statistics on running web workers
|
|
* @returns object containing statistics
|
|
*/
|
|
function getStatistics() {
|
|
statistics.maxWebWorkers = config.maxWebWorkers;
|
|
statistics.numWebWorkers = webWorkers.length;
|
|
statistics.numTasksQueued = tasks.length;
|
|
return statistics;
|
|
}
|
|
|
|
// module exports
|
|
cornerstoneWADOImageLoader.webWorkerManager = {
|
|
initialize : initialize,
|
|
loadWebWorkerTask: loadWebWorkerTask,
|
|
addTask : addTask,
|
|
getStatistics: getStatistics,
|
|
setTaskPriority: setTaskPriority,
|
|
cancelTask: cancelTask
|
|
};
|
|
|
|
}($, cornerstoneWADOImageLoader));
|
|
(function ($, cornerstone, cornerstoneWADOImageLoader) {
|
|
|
|
"use strict";
|
|
|
|
function xhrRequest(url, imageId, headers) {
|
|
headers = headers || {};
|
|
|
|
var deferred = $.Deferred();
|
|
|
|
// Make the request for the DICOM P10 SOP Instance
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("get", url, true);
|
|
xhr.responseType = "arraybuffer";
|
|
cornerstoneWADOImageLoader.internal.options.beforeSend(xhr);
|
|
Object.keys(headers).forEach(function (key) {
|
|
xhr.setRequestHeader(key, headers[key]);
|
|
});
|
|
|
|
// handle response data
|
|
xhr.onreadystatechange = function () {
|
|
// TODO: consider sending out progress messages here as we receive the pixel data
|
|
if (xhr.readyState === 4) {
|
|
if (xhr.status === 200) {
|
|
deferred.resolve(xhr.response, xhr);
|
|
}
|
|
else {
|
|
// request failed, reject the deferred
|
|
deferred.reject(xhr);
|
|
}
|
|
}
|
|
};
|
|
xhr.onprogress = function (oProgress) {
|
|
// console.log('progress:',oProgress)
|
|
|
|
if (oProgress.lengthComputable) { //evt.loaded the bytes browser receive
|
|
//evt.total the total bytes seted by the header
|
|
//
|
|
var loaded = oProgress.loaded;
|
|
var total = oProgress.total;
|
|
var percentComplete = Math.round((loaded / total) * 100);
|
|
|
|
$(cornerstone).trigger('CornerstoneImageLoadProgress', {
|
|
imageId: imageId,
|
|
loaded: loaded,
|
|
total: total,
|
|
percentComplete: percentComplete
|
|
});
|
|
}
|
|
};
|
|
|
|
xhr.send();
|
|
|
|
return deferred.promise();
|
|
}
|
|
|
|
cornerstoneWADOImageLoader.internal.xhrRequest = xhrRequest;
|
|
}($, cornerstone, cornerstoneWADOImageLoader));
|