3045 lines
98 KiB
JavaScript
3045 lines
98 KiB
JavaScript
/*! cornerstone - v0.8.4 - 2015-10-09 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstone */
|
|
if(typeof cornerstone === 'undefined'){
|
|
cornerstone = {
|
|
internal : {},
|
|
rendering: {}
|
|
};
|
|
}
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function disable(element) {
|
|
if(element === undefined) {
|
|
throw "disable: element element must not be undefined";
|
|
}
|
|
|
|
// Search for this element in this list of enabled elements
|
|
var enabledElements = cornerstone.getEnabledElements();
|
|
for(var i=0; i < enabledElements.length; i++) {
|
|
if(enabledElements[i].element === element) {
|
|
// We found it!
|
|
|
|
// Fire an event so dependencies can cleanup
|
|
var eventData = {
|
|
element : element
|
|
};
|
|
$(element).trigger("CornerstoneElementDisabled", eventData);
|
|
|
|
// remove the child dom elements that we created (e.g.canvas)
|
|
enabledElements[i].element.removeChild(enabledElements[i].canvas);
|
|
|
|
// remove this element from the list of enabled elements
|
|
enabledElements.splice(i, 1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.disable = disable;
|
|
|
|
}(cornerstone));
|
|
/**
|
|
* This module is responsible for enabling an element to display images with cornerstone
|
|
*/
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* sets a new image object for a given element
|
|
* @param element
|
|
* @param image
|
|
*/
|
|
function displayImage(element, image, viewport) {
|
|
if(element === undefined) {
|
|
throw "displayImage: parameter element cannot be undefined";
|
|
}
|
|
if(image === undefined) {
|
|
throw "displayImage: parameter image cannot be undefined";
|
|
}
|
|
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
|
|
enabledElement.image = image;
|
|
|
|
if(enabledElement.viewport === undefined) {
|
|
enabledElement.viewport = cornerstone.internal.getDefaultViewport(enabledElement.canvas, image);
|
|
}
|
|
|
|
// merge viewport
|
|
if(viewport) {
|
|
for(var attrname in viewport)
|
|
{
|
|
if(viewport[attrname] !== null) {
|
|
enabledElement.viewport[attrname] = viewport[attrname];
|
|
}
|
|
}
|
|
}
|
|
|
|
var now = new Date();
|
|
var frameRate;
|
|
if(enabledElement.lastImageTimeStamp !== undefined) {
|
|
var timeSinceLastImage = now.getTime() - enabledElement.lastImageTimeStamp;
|
|
frameRate = (1000 / timeSinceLastImage).toFixed();
|
|
} else {
|
|
}
|
|
enabledElement.lastImageTimeStamp = now.getTime();
|
|
|
|
var newImageEventData = {
|
|
viewport : enabledElement.viewport,
|
|
element : enabledElement.element,
|
|
image : enabledElement.image,
|
|
enabledElement : enabledElement,
|
|
frameRate : frameRate
|
|
};
|
|
|
|
$(enabledElement.element).trigger("CornerstoneNewImage", newImageEventData);
|
|
|
|
cornerstone.updateImage(element);
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.displayImage = displayImage;
|
|
}($, cornerstone));
|
|
/**
|
|
* This module is responsible for immediately drawing an enabled element
|
|
*/
|
|
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Immediately draws the enabled element
|
|
*
|
|
* @param element
|
|
*/
|
|
function draw(element) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
|
|
if(enabledElement.image === undefined) {
|
|
throw "draw: image has not been loaded yet";
|
|
}
|
|
|
|
cornerstone.drawImage(enabledElement);
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.draw = draw;
|
|
|
|
}($, cornerstone));
|
|
/**
|
|
* This module is responsible for drawing invalidated enabled elements
|
|
*/
|
|
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Draws all invalidated enabled elements and clears the invalid flag after drawing it
|
|
*/
|
|
function drawInvalidated()
|
|
{
|
|
var enabledElements = cornerstone.getEnabledElements();
|
|
for(var i=0;i < enabledElements.length; i++) {
|
|
var ee = enabledElements[i];
|
|
if(ee.invalid === true) {
|
|
cornerstone.drawImage(ee);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.drawInvalidated = drawInvalidated;
|
|
}($, cornerstone));
|
|
/**
|
|
* This module is responsible for enabling an element to display images with cornerstone
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function enable(element, renderer) {
|
|
if(element === undefined) {
|
|
throw "enable: parameter element cannot be undefined";
|
|
}
|
|
|
|
var canvas = document.createElement('canvas');
|
|
element.appendChild(canvas);
|
|
|
|
if (typeof renderer === 'string' && renderer.toLowerCase() === 'webgl') {
|
|
renderer = cornerstone.webGL.renderer.render;
|
|
}
|
|
|
|
if (renderer === cornerstone.webGL.renderer.render) {
|
|
if (!cornerstone.webGL.renderer.isWebGLAvailable()) {
|
|
console.error('WebGL not available, falling back to Canvas renderer');
|
|
renderer = undefined;
|
|
} else {
|
|
cornerstone.webGL.renderer.initRenderer();
|
|
}
|
|
}
|
|
|
|
var el = {
|
|
element: element,
|
|
canvas: canvas,
|
|
image : undefined, // will be set once image is loaded
|
|
invalid: false, // true if image needs to be drawn, false if not
|
|
render: renderer,
|
|
data : {}
|
|
};
|
|
cornerstone.addEnabledElement(el);
|
|
|
|
cornerstone.resize(element, true);
|
|
|
|
return element;
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.enable = enable;
|
|
}(cornerstone));
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function getElementData(el, dataType) {
|
|
var ee = cornerstone.getEnabledElement(el);
|
|
if(ee.data.hasOwnProperty(dataType) === false)
|
|
{
|
|
ee.data[dataType] = {};
|
|
}
|
|
return ee.data[dataType];
|
|
}
|
|
|
|
function removeElementData(el, dataType) {
|
|
var ee = cornerstone.getEnabledElement(el);
|
|
delete ee.data[dataType];
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.getElementData = getElementData;
|
|
cornerstone.removeElementData = removeElementData;
|
|
|
|
}(cornerstone));
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
var enabledElements = [];
|
|
|
|
function getEnabledElement(element) {
|
|
if(element === undefined) {
|
|
throw "getEnabledElement: parameter element must not be undefined";
|
|
}
|
|
for(var i=0; i < enabledElements.length; i++) {
|
|
if(enabledElements[i].element == element) {
|
|
return enabledElements[i];
|
|
}
|
|
}
|
|
|
|
throw "element not enabled";
|
|
}
|
|
|
|
function addEnabledElement(enabledElement) {
|
|
if(enabledElement === undefined) {
|
|
throw "getEnabledElement: enabledElement element must not be undefined";
|
|
}
|
|
|
|
enabledElements.push(enabledElement);
|
|
}
|
|
|
|
function getEnabledElementsByImageId(imageId) {
|
|
var ees = [];
|
|
enabledElements.forEach(function(enabledElement) {
|
|
if(enabledElement.image && enabledElement.image.imageId === imageId) {
|
|
ees.push(enabledElement);
|
|
}
|
|
});
|
|
return ees;
|
|
}
|
|
|
|
function getEnabledElements() {
|
|
return enabledElements;
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.getEnabledElement = getEnabledElement;
|
|
cornerstone.addEnabledElement = addEnabledElement;
|
|
cornerstone.getEnabledElementsByImageId = getEnabledElementsByImageId;
|
|
cornerstone.getEnabledElements = getEnabledElements;
|
|
}(cornerstone));
|
|
/**
|
|
* This module will fit an image to fit inside the canvas displaying it such that all pixels
|
|
* in the image are viewable
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function getImageSize(enabledElement) {
|
|
if(enabledElement.viewport.rotation === 0 ||enabledElement.viewport.rotation === 180) {
|
|
return {
|
|
width: enabledElement.image.width,
|
|
height: enabledElement.image.height
|
|
};
|
|
} else {
|
|
return {
|
|
width: enabledElement.image.height,
|
|
height: enabledElement.image.width
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adjusts an images scale and center so the image is centered and completely visible
|
|
* @param element
|
|
*/
|
|
function fitToWindow(element)
|
|
{
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
var imageSize = getImageSize(enabledElement);
|
|
|
|
var verticalScale = enabledElement.canvas.height / imageSize.height;
|
|
var horizontalScale= enabledElement.canvas.width / imageSize.width;
|
|
if(horizontalScale < verticalScale) {
|
|
enabledElement.viewport.scale = horizontalScale;
|
|
}
|
|
else
|
|
{
|
|
enabledElement.viewport.scale = verticalScale;
|
|
}
|
|
enabledElement.viewport.translation.x = 0;
|
|
enabledElement.viewport.translation.y = 0;
|
|
cornerstone.updateImage(element);
|
|
}
|
|
|
|
cornerstone.fitToWindow = fitToWindow;
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This file is responsible for returning the default viewport for an image
|
|
*/
|
|
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* returns a default viewport for display the specified image on the specified
|
|
* enabled element. The default viewport is fit to window
|
|
*
|
|
* @param element
|
|
* @param image
|
|
*/
|
|
function getDefaultViewportForImage(element, image) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
var viewport = cornerstone.internal.getDefaultViewport(enabledElement.canvas, image);
|
|
return viewport;
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.getDefaultViewportForImage = getDefaultViewportForImage;
|
|
}($, cornerstone));
|
|
/**
|
|
* This module is responsible for returning the currently displayed image for an element
|
|
*/
|
|
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* returns the currently displayed image for an element or undefined if no image has
|
|
* been displayed yet
|
|
*
|
|
* @param element
|
|
*/
|
|
function getImage(element) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
return enabledElement.image;
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.getImage = getImage;
|
|
}($, cornerstone));
|
|
/**
|
|
* This module returns a subset of the stored pixels of an image
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Returns array of pixels with modality LUT transformation applied
|
|
*/
|
|
function getPixels(element, x, y, width, height) {
|
|
|
|
var storedPixels = cornerstone.getStoredPixels(element, x, y, width, height);
|
|
var ee = cornerstone.getEnabledElement(element);
|
|
|
|
var mlutfn = cornerstone.internal.getModalityLUT(ee.image.slope, ee.image.intercept, ee.viewport.modalityLUT);
|
|
|
|
var modalityPixels = storedPixels.map(mlutfn);
|
|
|
|
return modalityPixels;
|
|
}
|
|
|
|
// module exports
|
|
cornerstone.getPixels = getPixels;
|
|
}(cornerstone));
|
|
/**
|
|
* This module returns a subset of the stored pixels of an image
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Returns an array of stored pixels given a rectangle in the image
|
|
* @param element
|
|
* @param x
|
|
* @param y
|
|
* @param width
|
|
* @param height
|
|
* @returns {Array}
|
|
*/
|
|
function getStoredPixels(element, x, y, width, height) {
|
|
if(element === undefined) {
|
|
throw "getStoredPixels: parameter element must not be undefined";
|
|
}
|
|
|
|
x = Math.round(x);
|
|
y = Math.round(y);
|
|
var ee = cornerstone.getEnabledElement(element);
|
|
var storedPixels = [];
|
|
var index = 0;
|
|
var pixelData = ee.image.getPixelData();
|
|
for(var row=0; row < height; row++) {
|
|
for(var column=0; column < width; column++) {
|
|
var spIndex = ((row + y) * ee.image.columns) + (column + x);
|
|
storedPixels[index++] = pixelData[spIndex];
|
|
}
|
|
}
|
|
return storedPixels;
|
|
}
|
|
|
|
// module exports
|
|
cornerstone.getStoredPixels = getStoredPixels;
|
|
}(cornerstone));
|
|
/**
|
|
* This module contains functions to deal with getting and setting the viewport for an enabled element
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Returns the viewport for the specified enabled element
|
|
* @param element
|
|
* @returns {*}
|
|
*/
|
|
function getViewport(element) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
|
|
var viewport = enabledElement.viewport;
|
|
if(viewport === undefined) {
|
|
return undefined;
|
|
}
|
|
return {
|
|
scale : viewport.scale,
|
|
translation : {
|
|
x : viewport.translation.x,
|
|
y : viewport.translation.y
|
|
},
|
|
voi : {
|
|
windowWidth: viewport.voi.windowWidth,
|
|
windowCenter : viewport.voi.windowCenter
|
|
},
|
|
invert : viewport.invert,
|
|
pixelReplication: viewport.pixelReplication,
|
|
rotation: viewport.rotation,
|
|
hflip: viewport.hflip,
|
|
vflip: viewport.vflip,
|
|
modalityLUT: viewport.modalityLUT,
|
|
voiLUT: viewport.voiLUT
|
|
};
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.getViewport = getViewport;
|
|
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module deals with caching images
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
var imageCache = {};
|
|
|
|
var cachedImages = [];
|
|
|
|
var maximumSizeInBytes = 1024 * 1024 * 1024; // 1 GB
|
|
var cacheSizeInBytes = 0;
|
|
|
|
function setMaximumSizeBytes(numBytes) {
|
|
if (numBytes === undefined) {
|
|
throw "setMaximumSizeBytes: parameter numBytes must not be undefined";
|
|
}
|
|
if (numBytes.toFixed === undefined) {
|
|
throw "setMaximumSizeBytes: parameter numBytes must be a number";
|
|
}
|
|
|
|
maximumSizeInBytes = numBytes;
|
|
purgeCacheIfNecessary();
|
|
}
|
|
|
|
function purgeCacheIfNecessary() {
|
|
// if max cache size has not been exceeded, do nothing
|
|
if (cacheSizeInBytes <= maximumSizeInBytes) {
|
|
return;
|
|
}
|
|
|
|
// cache size has been exceeded, create list of images sorted by timeStamp
|
|
// so we can purge the least recently used image
|
|
function compare(a,b) {
|
|
if (a.timeStamp > b.timeStamp) {
|
|
return -1;
|
|
}
|
|
if (a.timeStamp < b.timeStamp) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
cachedImages.sort(compare);
|
|
|
|
// remove images as necessary
|
|
while(cacheSizeInBytes > maximumSizeInBytes) {
|
|
var lastCachedImage = cachedImages[cachedImages.length - 1];
|
|
cacheSizeInBytes -= lastCachedImage.sizeInBytes;
|
|
delete imageCache[lastCachedImage.imageId];
|
|
lastCachedImage.imagePromise.reject();
|
|
cachedImages.pop();
|
|
$(cornerstone).trigger('CornerstoneImageCachePromiseRemoved', {imageId: lastCachedImage.imageId});
|
|
}
|
|
|
|
var cacheInfo = cornerstone.imageCache.getCacheInfo();
|
|
$(cornerstone).trigger('CornerstoneImageCacheFull', cacheInfo);
|
|
}
|
|
|
|
function putImagePromise(imageId, imagePromise) {
|
|
if (imageId === undefined) {
|
|
throw "getImagePromise: imageId must not be undefined";
|
|
}
|
|
if (imagePromise === undefined) {
|
|
throw "getImagePromise: imagePromise must not be undefined";
|
|
}
|
|
|
|
if (imageCache.hasOwnProperty(imageId) === true) {
|
|
throw "putImagePromise: imageId already in cache";
|
|
}
|
|
|
|
var cachedImage = {
|
|
loaded : false,
|
|
imageId : imageId,
|
|
imagePromise : imagePromise,
|
|
timeStamp : new Date(),
|
|
sizeInBytes: 0
|
|
};
|
|
|
|
imageCache[imageId] = cachedImage;
|
|
cachedImages.push(cachedImage);
|
|
|
|
imagePromise.then(function(image) {
|
|
cachedImage.loaded = true;
|
|
|
|
if (image.sizeInBytes === undefined) {
|
|
throw "putImagePromise: image does not have sizeInBytes property or";
|
|
}
|
|
if (image.sizeInBytes.toFixed === undefined) {
|
|
throw "putImagePromise: image.sizeInBytes is not a number";
|
|
}
|
|
cachedImage.sizeInBytes = image.sizeInBytes;
|
|
cacheSizeInBytes += cachedImage.sizeInBytes;
|
|
purgeCacheIfNecessary();
|
|
});
|
|
}
|
|
|
|
function getImagePromise(imageId) {
|
|
if (imageId === undefined) {
|
|
throw "getImagePromise: imageId must not be undefined";
|
|
}
|
|
var cachedImage = imageCache[imageId];
|
|
if (cachedImage === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
// bump time stamp for cached image
|
|
cachedImage.timeStamp = new Date();
|
|
return cachedImage.imagePromise;
|
|
}
|
|
|
|
function removeImagePromise(imageId) {
|
|
if (imageId === undefined) {
|
|
throw "removeImagePromise: imageId must not be undefined";
|
|
}
|
|
var cachedImage = imageCache[imageId];
|
|
if (cachedImage === undefined) {
|
|
throw "removeImagePromise: imageId must not be undefined";
|
|
}
|
|
cachedImages.splice( cachedImages.indexOf(cachedImage), 1);
|
|
cacheSizeInBytes -= cachedImage.sizeInBytes;
|
|
delete imageCache[imageId];
|
|
|
|
return cachedImage.imagePromise;
|
|
}
|
|
|
|
function getCacheInfo() {
|
|
return {
|
|
maximumSizeInBytes : maximumSizeInBytes,
|
|
cacheSizeInBytes : cacheSizeInBytes,
|
|
numberOfImagesCached: cachedImages.length
|
|
};
|
|
}
|
|
|
|
function purgeCache() {
|
|
while (cachedImages.length > 0) {
|
|
var removedCachedImage = cachedImages.pop();
|
|
delete imageCache[removedCachedImage.imageId];
|
|
removedCachedImage.imagePromise.reject();
|
|
}
|
|
cacheSizeInBytes = 0;
|
|
}
|
|
|
|
// module exports
|
|
cornerstone.imageCache = {
|
|
putImagePromise : putImagePromise,
|
|
getImagePromise: getImagePromise,
|
|
removeImagePromise: removeImagePromise,
|
|
setMaximumSizeBytes: setMaximumSizeBytes,
|
|
getCacheInfo : getCacheInfo,
|
|
purgeCache: purgeCache,
|
|
cachedImages: cachedImages
|
|
};
|
|
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module deals with ImageLoaders, loading images and caching images
|
|
*/
|
|
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
var imageLoaders = {};
|
|
|
|
var unknownImageLoader;
|
|
|
|
function loadImageFromImageLoader(imageId) {
|
|
var colonIndex = imageId.indexOf(":");
|
|
var scheme = imageId.substring(0, colonIndex);
|
|
var loader = imageLoaders[scheme];
|
|
var imagePromise;
|
|
if(loader === undefined || loader === null) {
|
|
if(unknownImageLoader !== undefined) {
|
|
imagePromise = unknownImageLoader(imageId);
|
|
return imagePromise;
|
|
}
|
|
else {
|
|
return undefined;
|
|
}
|
|
}
|
|
imagePromise = loader(imageId);
|
|
|
|
// broadcast an image loaded event once the image is loaded
|
|
// This is based on the idea here: http://stackoverflow.com/questions/3279809/global-custom-events-in-jquery
|
|
imagePromise.then(function(image) {
|
|
$(cornerstone).trigger('CornerstoneImageLoaded', {image: image});
|
|
});
|
|
|
|
return imagePromise;
|
|
}
|
|
|
|
// Loads an image given an imageId and returns a promise which will resolve
|
|
// to the loaded image object or fail if an error occurred. The loaded image
|
|
// is not stored in the cache
|
|
function loadImage(imageId) {
|
|
if(imageId === undefined) {
|
|
throw "loadImage: parameter imageId must not be undefined";
|
|
}
|
|
|
|
var imagePromise = cornerstone.imageCache.getImagePromise(imageId);
|
|
if(imagePromise !== undefined) {
|
|
return imagePromise;
|
|
}
|
|
|
|
imagePromise = loadImageFromImageLoader(imageId);
|
|
if(imagePromise === undefined) {
|
|
throw "loadImage: no image loader for imageId";
|
|
}
|
|
|
|
return imagePromise;
|
|
}
|
|
|
|
// Loads an image given an imageId and returns a promise which will resolve
|
|
// to the loaded image object or fail if an error occurred. The image is
|
|
// stored in the cache
|
|
function loadAndCacheImage(imageId) {
|
|
if(imageId === undefined) {
|
|
throw "loadAndCacheImage: parameter imageId must not be undefined";
|
|
}
|
|
|
|
var imagePromise = cornerstone.imageCache.getImagePromise(imageId);
|
|
if(imagePromise !== undefined) {
|
|
return imagePromise;
|
|
}
|
|
|
|
imagePromise = loadImageFromImageLoader(imageId);
|
|
if(imagePromise === undefined) {
|
|
throw "loadAndCacheImage: no image loader for imageId";
|
|
}
|
|
|
|
cornerstone.imageCache.putImagePromise(imageId, imagePromise);
|
|
|
|
return imagePromise;
|
|
}
|
|
|
|
|
|
// registers an imageLoader plugin with cornerstone for the specified scheme
|
|
function registerImageLoader(scheme, imageLoader) {
|
|
imageLoaders[scheme] = imageLoader;
|
|
}
|
|
|
|
// Registers a new unknownImageLoader and returns the previous one (if it exists)
|
|
function registerUnknownImageLoader(imageLoader) {
|
|
var oldImageLoader = unknownImageLoader;
|
|
unknownImageLoader = imageLoader;
|
|
return oldImageLoader;
|
|
}
|
|
|
|
// module exports
|
|
|
|
cornerstone.loadImage = loadImage;
|
|
cornerstone.loadAndCacheImage = loadAndCacheImage;
|
|
cornerstone.registerImageLoader = registerImageLoader;
|
|
cornerstone.registerUnknownImageLoader = registerUnknownImageLoader;
|
|
|
|
}($, cornerstone));
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function calculateTransform(enabledElement, scale) {
|
|
|
|
var transform = new cornerstone.internal.Transform();
|
|
transform.translate(enabledElement.canvas.width/2, enabledElement.canvas.height / 2);
|
|
|
|
//Apply the rotation before scaling for non square pixels
|
|
var angle = enabledElement.viewport.rotation;
|
|
if(angle!==0) {
|
|
transform.rotate(angle*Math.PI/180);
|
|
}
|
|
|
|
// apply the scale
|
|
var widthScale = enabledElement.viewport.scale;
|
|
var heightScale = enabledElement.viewport.scale;
|
|
if(enabledElement.image.rowPixelSpacing < enabledElement.image.columnPixelSpacing) {
|
|
widthScale = widthScale * (enabledElement.image.columnPixelSpacing / enabledElement.image.rowPixelSpacing);
|
|
}
|
|
else if(enabledElement.image.columnPixelSpacing < enabledElement.image.rowPixelSpacing) {
|
|
heightScale = heightScale * (enabledElement.image.rowPixelSpacing / enabledElement.image.columnPixelSpacing);
|
|
}
|
|
transform.scale(widthScale, heightScale);
|
|
|
|
// unrotate to so we can translate unrotated
|
|
if(angle!==0) {
|
|
transform.rotate(-angle*Math.PI/180);
|
|
}
|
|
|
|
// apply the pan offset
|
|
transform.translate(enabledElement.viewport.translation.x, enabledElement.viewport.translation.y);
|
|
|
|
// rotate again so we can apply general scale
|
|
if(angle!==0) {
|
|
transform.rotate(angle*Math.PI/180);
|
|
}
|
|
|
|
if(scale !== undefined) {
|
|
// apply the font scale
|
|
transform.scale(scale, scale);
|
|
}
|
|
|
|
//Apply Flip if required
|
|
if(enabledElement.viewport.hflip) {
|
|
transform.scale(-1,1);
|
|
}
|
|
|
|
if(enabledElement.viewport.vflip) {
|
|
transform.scale(1,-1);
|
|
}
|
|
|
|
// translate the origin back to the corner of the image so the event handlers can draw in image coordinate system
|
|
transform.translate(-enabledElement.image.width / 2 , -enabledElement.image.height/ 2);
|
|
return transform;
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.internal.calculateTransform = calculateTransform;
|
|
}(cornerstone));
|
|
/**
|
|
* This module is responsible for drawing an image to an enabled elements canvas element
|
|
*/
|
|
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Internal API function to draw an image to a given enabled element
|
|
* @param enabledElement
|
|
* @param invalidated - true if pixel data has been invalidated and cached rendering should not be used
|
|
*/
|
|
function drawImage(enabledElement, invalidated) {
|
|
|
|
var start = new Date();
|
|
|
|
if (enabledElement.render) {
|
|
enabledElement.render(enabledElement, invalidated);
|
|
} else {
|
|
enabledElement.image.render(enabledElement, invalidated);
|
|
}
|
|
|
|
var context = enabledElement.canvas.getContext('2d');
|
|
|
|
var end = new Date();
|
|
var diff = end - start;
|
|
//console.log(diff + ' ms');
|
|
|
|
var eventData = {
|
|
viewport : enabledElement.viewport,
|
|
element : enabledElement.element,
|
|
image : enabledElement.image,
|
|
enabledElement : enabledElement,
|
|
canvasContext: context,
|
|
renderTimeInMs : diff
|
|
};
|
|
|
|
$(enabledElement.element).trigger("CornerstoneImageRendered", eventData);
|
|
enabledElement.invalid = false;
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.internal.drawImage = drawImage;
|
|
cornerstone.drawImage = drawImage;
|
|
|
|
}($, cornerstone));
|
|
/**
|
|
* This module generates a lut for an image
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function generateLutNew(image, windowWidth, windowCenter, invert, modalityLUT, voiLUT)
|
|
{
|
|
if(image.lut === undefined) {
|
|
image.lut = new Int16Array(image.maxPixelValue - Math.min(image.minPixelValue,0)+1);
|
|
}
|
|
var lut = image.lut;
|
|
var maxPixelValue = image.maxPixelValue;
|
|
var minPixelValue = image.minPixelValue;
|
|
|
|
var mlutfn = cornerstone.internal.getModalityLUT(image.slope, image.intercept, modalityLUT);
|
|
var vlutfn = cornerstone.internal.getVOILUT(windowWidth, windowCenter, voiLUT);
|
|
|
|
var offset = 0;
|
|
if(minPixelValue < 0) {
|
|
offset = minPixelValue;
|
|
}
|
|
var storedValue;
|
|
var modalityLutValue;
|
|
var voiLutValue;
|
|
var clampedValue;
|
|
|
|
for(storedValue = image.minPixelValue; storedValue <= maxPixelValue; storedValue++)
|
|
{
|
|
modalityLutValue = mlutfn(storedValue);
|
|
voiLutValue = vlutfn(modalityLutValue);
|
|
clampedValue = Math.min(Math.max(voiLutValue, 0), 255);
|
|
if(!invert) {
|
|
lut[storedValue+ (-offset)] = Math.round(clampedValue);
|
|
} else {
|
|
lut[storedValue + (-offset)] = Math.round(255 - clampedValue);
|
|
}
|
|
}
|
|
return lut;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Creates a LUT used while rendering to convert stored pixel values to
|
|
* display pixels
|
|
*
|
|
* @param image
|
|
* @returns {Array}
|
|
*/
|
|
function generateLut(image, windowWidth, windowCenter, invert, modalityLUT, voiLUT)
|
|
{
|
|
if(modalityLUT || voiLUT) {
|
|
return generateLutNew(image, windowWidth, windowCenter, invert, modalityLUT, voiLUT);
|
|
}
|
|
|
|
if(image.lut === undefined) {
|
|
image.lut = new Int16Array(image.maxPixelValue - Math.min(image.minPixelValue,0)+1);
|
|
}
|
|
var lut = image.lut;
|
|
|
|
var maxPixelValue = image.maxPixelValue;
|
|
var minPixelValue = image.minPixelValue;
|
|
var slope = image.slope;
|
|
var intercept = image.intercept;
|
|
var localWindowWidth = windowWidth;
|
|
var localWindowCenter = windowCenter;
|
|
var modalityLutValue;
|
|
var voiLutValue;
|
|
var clampedValue;
|
|
var storedValue;
|
|
|
|
// NOTE: As of Nov 2014, most javascript engines have lower performance when indexing negative indexes.
|
|
// We improve performance by offsetting the pixel values for signed data to avoid negative indexes
|
|
// when generating the lut and then undo it in storedPixelDataToCanvasImagedata. Thanks to @jpambrun
|
|
// for this contribution!
|
|
|
|
var offset = 0;
|
|
if(minPixelValue < 0) {
|
|
offset = minPixelValue;
|
|
}
|
|
|
|
if(invert === true) {
|
|
for(storedValue = image.minPixelValue; storedValue <= maxPixelValue; storedValue++)
|
|
{
|
|
modalityLutValue = storedValue * slope + intercept;
|
|
voiLutValue = (((modalityLutValue - (localWindowCenter)) / (localWindowWidth) + 0.5) * 255.0);
|
|
clampedValue = Math.min(Math.max(voiLutValue, 0), 255);
|
|
lut[storedValue + (-offset)] = Math.round(255 - clampedValue);
|
|
}
|
|
}
|
|
else {
|
|
for(storedValue = image.minPixelValue; storedValue <= maxPixelValue; storedValue++)
|
|
{
|
|
modalityLutValue = storedValue * slope + intercept;
|
|
voiLutValue = (((modalityLutValue - (localWindowCenter)) / (localWindowWidth) + 0.5) * 255.0);
|
|
clampedValue = Math.min(Math.max(voiLutValue, 0), 255);
|
|
lut[storedValue+ (-offset)] = Math.round(clampedValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Module exports
|
|
cornerstone.internal.generateLutNew = generateLutNew;
|
|
cornerstone.internal.generateLut = generateLut;
|
|
cornerstone.generateLutNew = generateLutNew;
|
|
cornerstone.generateLut = generateLut;
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module contains a function to get a default viewport for an image given
|
|
* a canvas element to display it in
|
|
*
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Creates a new viewport object containing default values for the image and canvas
|
|
* @param canvas
|
|
* @param image
|
|
* @returns viewport object
|
|
*/
|
|
function getDefaultViewport(canvas, image) {
|
|
if(canvas === undefined) {
|
|
throw "getDefaultViewport: parameter canvas must not be undefined";
|
|
}
|
|
if(image === undefined) {
|
|
throw "getDefaultViewport: parameter image must not be undefined";
|
|
}
|
|
var viewport = {
|
|
scale : 1.0,
|
|
translation : {
|
|
x : 0,
|
|
y : 0
|
|
},
|
|
voi : {
|
|
windowWidth: image.windowWidth,
|
|
windowCenter: image.windowCenter,
|
|
},
|
|
invert: image.invert,
|
|
pixelReplication: false,
|
|
rotation: 0,
|
|
hflip: false,
|
|
vflip: false,
|
|
modalityLUT: image.modalityLUT,
|
|
voiLUT: image.voiLUT
|
|
};
|
|
|
|
// fit image to window
|
|
var verticalScale = canvas.height / image.rows;
|
|
var horizontalScale= canvas.width / image.columns;
|
|
if(horizontalScale < verticalScale) {
|
|
viewport.scale = horizontalScale;
|
|
}
|
|
else {
|
|
viewport.scale = verticalScale;
|
|
}
|
|
return viewport;
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.internal.getDefaultViewport = getDefaultViewport;
|
|
cornerstone.getDefaultViewport = getDefaultViewport;
|
|
}(cornerstone));
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function getTransform(enabledElement)
|
|
{
|
|
// For now we will calculate it every time it is requested. In the future, we may want to cache
|
|
// it in the enabled element to speed things up
|
|
var transform = cornerstone.internal.calculateTransform(enabledElement);
|
|
return transform;
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.internal.getTransform = getTransform;
|
|
|
|
}(cornerstone));
|
|
/**
|
|
* This module is responsible for drawing an image to an enabled elements canvas element
|
|
*/
|
|
|
|
(function ($, cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
cornerstone.drawImage = cornerstone.internal.drawImage;
|
|
cornerstone.generateLut = cornerstone.internal.generateLut;
|
|
cornerstone.storedPixelDataToCanvasImageData = cornerstone.internal.storedPixelDataToCanvasImageData;
|
|
cornerstone.storedColorPixelDataToCanvasImageData = cornerstone.internal.storedColorPixelDataToCanvasImageData;
|
|
|
|
}($, cornerstone));
|
|
/**
|
|
* This module generates a Modality LUT
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
|
|
function generateLinearModalityLUT(slope, intercept) {
|
|
var localSlope = slope;
|
|
var localIntercept = intercept;
|
|
return function(sp) {
|
|
return sp * localSlope + localIntercept;
|
|
}
|
|
}
|
|
|
|
function generateNonLinearModalityLUT(modalityLUT) {
|
|
var minValue = modalityLUT.lut[0];
|
|
var maxValue = modalityLUT.lut[modalityLUT.lut.length -1];
|
|
var maxValueMapped = modalityLUT.firstValueMapped + modalityLUT.lut.length;
|
|
return function(sp) {
|
|
if(sp < modalityLUT.firstValueMapped) {
|
|
return minValue;
|
|
}
|
|
else if(sp >= maxValueMapped)
|
|
{
|
|
return maxValue;
|
|
}
|
|
else
|
|
{
|
|
return modalityLUT.lut[sp];
|
|
}
|
|
}
|
|
}
|
|
|
|
function getModalityLUT(slope, intercept, modalityLUT) {
|
|
if (modalityLUT) {
|
|
return generateNonLinearModalityLUT(modalityLUT);
|
|
} else {
|
|
return generateLinearModalityLUT(slope, intercept);
|
|
}
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.internal.getModalityLUT = getModalityLUT;
|
|
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module contains a function to convert stored pixel values to display pixel values using a LUT
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function storedColorPixelDataToCanvasImageData(image, lut, canvasImageDataData)
|
|
{
|
|
var minPixelValue = image.minPixelValue;
|
|
var canvasImageDataIndex = 0;
|
|
var storedPixelDataIndex = 0;
|
|
var numPixels = image.width * image.height * 4;
|
|
var storedPixelData = image.getPixelData();
|
|
var localLut = lut;
|
|
var localCanvasImageDataData = canvasImageDataData;
|
|
// NOTE: As of Nov 2014, most javascript engines have lower performance when indexing negative indexes.
|
|
// We have a special code path for this case that improves performance. Thanks to @jpambrun for this enhancement
|
|
if(minPixelValue < 0){
|
|
while(storedPixelDataIndex < numPixels) {
|
|
localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++] + (-minPixelValue)]; // red
|
|
localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++] + (-minPixelValue)]; // green
|
|
localCanvasImageDataData[canvasImageDataIndex] = localLut[storedPixelData[storedPixelDataIndex] + (-minPixelValue)]; // blue
|
|
storedPixelDataIndex+=2;
|
|
canvasImageDataIndex+=2;
|
|
}
|
|
}else{
|
|
while(storedPixelDataIndex < numPixels) {
|
|
localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++]]; // red
|
|
localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++]]; // green
|
|
localCanvasImageDataData[canvasImageDataIndex] = localLut[storedPixelData[storedPixelDataIndex]]; // blue
|
|
storedPixelDataIndex+=2;
|
|
canvasImageDataIndex+=2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.internal.storedColorPixelDataToCanvasImageData = storedColorPixelDataToCanvasImageData;
|
|
cornerstone.storedColorPixelDataToCanvasImageData = storedColorPixelDataToCanvasImageData;
|
|
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module contains a function to convert stored pixel values to display pixel values using a LUT
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* This function transforms stored pixel values into a canvas image data buffer
|
|
* by using a LUT. This is the most performance sensitive code in cornerstone and
|
|
* we use a special trick to make this go as fast as possible. Specifically we
|
|
* use the alpha channel only to control the luminance rather than the red, green and
|
|
* blue channels which makes it over 3x faster. The canvasImageDataData buffer needs
|
|
* to be previously filled with white pixels.
|
|
*
|
|
* NOTE: Attribution would be appreciated if you use this technique!
|
|
*
|
|
* @param pixelData the pixel data
|
|
* @param lut the lut
|
|
* @param canvasImageDataData a canvasImgageData.data buffer filled with white pixels
|
|
*/
|
|
function storedPixelDataToCanvasImageData(image, lut, canvasImageDataData)
|
|
{
|
|
var pixelData = image.getPixelData();
|
|
var minPixelValue = image.minPixelValue;
|
|
var canvasImageDataIndex = 3;
|
|
var storedPixelDataIndex = 0;
|
|
var localNumPixels = pixelData.length;
|
|
var localPixelData = pixelData;
|
|
var localLut = lut;
|
|
var localCanvasImageDataData = canvasImageDataData;
|
|
// NOTE: As of Nov 2014, most javascript engines have lower performance when indexing negative indexes.
|
|
// We have a special code path for this case that improves performance. Thanks to @jpambrun for this enhancement
|
|
if(minPixelValue < 0){
|
|
while(storedPixelDataIndex < localNumPixels) {
|
|
localCanvasImageDataData[canvasImageDataIndex] = localLut[localPixelData[storedPixelDataIndex++] + (-minPixelValue)]; // alpha
|
|
canvasImageDataIndex += 4;
|
|
}
|
|
}else{
|
|
while(storedPixelDataIndex < localNumPixels) {
|
|
localCanvasImageDataData[canvasImageDataIndex] = localLut[localPixelData[storedPixelDataIndex++]]; // alpha
|
|
canvasImageDataIndex += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.internal.storedPixelDataToCanvasImageData = storedPixelDataToCanvasImageData;
|
|
cornerstone.storedPixelDataToCanvasImageData = storedPixelDataToCanvasImageData;
|
|
|
|
}(cornerstone));
|
|
|
|
// Last updated November 2011
|
|
// By Simon Sarris
|
|
// www.simonsarris.com
|
|
// sarris@acm.org
|
|
//
|
|
// Free to use and distribute at will
|
|
// So long as you are nice to people, etc
|
|
|
|
// Simple class for keeping track of the current transformation matrix
|
|
|
|
// For instance:
|
|
// var t = new Transform();
|
|
// t.rotate(5);
|
|
// var m = t.m;
|
|
// ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
|
|
|
// Is equivalent to:
|
|
// ctx.rotate(5);
|
|
|
|
// But now you can retrieve it :)
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
|
|
// Remember that this does not account for any CSS transforms applied to the canvas
|
|
function Transform() {
|
|
this.reset();
|
|
}
|
|
|
|
Transform.prototype.reset = function() {
|
|
this.m = [1,0,0,1,0,0];
|
|
};
|
|
|
|
Transform.prototype.clone = function() {
|
|
var transform = new Transform();
|
|
transform.m[0] = this.m[0];
|
|
transform.m[1] = this.m[1];
|
|
transform.m[2] = this.m[2];
|
|
transform.m[3] = this.m[3];
|
|
transform.m[4] = this.m[4];
|
|
transform.m[5] = this.m[5];
|
|
return transform;
|
|
};
|
|
|
|
|
|
Transform.prototype.multiply = function(matrix) {
|
|
var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1];
|
|
var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1];
|
|
|
|
var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3];
|
|
var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3];
|
|
|
|
var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4];
|
|
var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5];
|
|
|
|
this.m[0] = m11;
|
|
this.m[1] = m12;
|
|
this.m[2] = m21;
|
|
this.m[3] = m22;
|
|
this.m[4] = dx;
|
|
this.m[5] = dy;
|
|
};
|
|
|
|
Transform.prototype.invert = function() {
|
|
var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]);
|
|
var m0 = this.m[3] * d;
|
|
var m1 = -this.m[1] * d;
|
|
var m2 = -this.m[2] * d;
|
|
var m3 = this.m[0] * d;
|
|
var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]);
|
|
var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]);
|
|
this.m[0] = m0;
|
|
this.m[1] = m1;
|
|
this.m[2] = m2;
|
|
this.m[3] = m3;
|
|
this.m[4] = m4;
|
|
this.m[5] = m5;
|
|
};
|
|
|
|
Transform.prototype.rotate = function(rad) {
|
|
var c = Math.cos(rad);
|
|
var s = Math.sin(rad);
|
|
var m11 = this.m[0] * c + this.m[2] * s;
|
|
var m12 = this.m[1] * c + this.m[3] * s;
|
|
var m21 = this.m[0] * -s + this.m[2] * c;
|
|
var m22 = this.m[1] * -s + this.m[3] * c;
|
|
this.m[0] = m11;
|
|
this.m[1] = m12;
|
|
this.m[2] = m21;
|
|
this.m[3] = m22;
|
|
};
|
|
|
|
Transform.prototype.translate = function(x, y) {
|
|
this.m[4] += this.m[0] * x + this.m[2] * y;
|
|
this.m[5] += this.m[1] * x + this.m[3] * y;
|
|
};
|
|
|
|
Transform.prototype.scale = function(sx, sy) {
|
|
this.m[0] *= sx;
|
|
this.m[1] *= sx;
|
|
this.m[2] *= sy;
|
|
this.m[3] *= sy;
|
|
};
|
|
|
|
Transform.prototype.transformPoint = function(px, py) {
|
|
var x = px;
|
|
var y = py;
|
|
px = x * this.m[0] + y * this.m[2] + this.m[4];
|
|
py = x * this.m[1] + y * this.m[3] + this.m[5];
|
|
return {x: px, y: py};
|
|
};
|
|
|
|
cornerstone.internal.Transform = Transform;
|
|
}(cornerstone));
|
|
/**
|
|
* This module generates a VOI LUT
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function generateLinearVOILUT(windowWidth, windowCenter) {
|
|
return function(modalityLutValue) {
|
|
return (((modalityLutValue - (windowCenter)) / (windowWidth) + 0.5) * 255.0);
|
|
}
|
|
}
|
|
|
|
function generateNonLinearVOILUT(voiLUT) {
|
|
var shift = voiLUT.numBitsPerEntry - 8;
|
|
var minValue = voiLUT.lut[0] >> shift;
|
|
var maxValue = voiLUT.lut[voiLUT.lut.length -1] >> shift;
|
|
var maxValueMapped = voiLUT.firstValueMapped + voiLUT.lut.length - 1;
|
|
return function(modalityLutValue) {
|
|
if(modalityLutValue < voiLUT.firstValueMapped) {
|
|
return minValue;
|
|
}
|
|
else if(modalityLutValue >= maxValueMapped)
|
|
{
|
|
return maxValue;
|
|
}
|
|
else
|
|
{
|
|
return voiLUT.lut[modalityLutValue - voiLUT.firstValueMapped] >> shift;
|
|
}
|
|
}
|
|
}
|
|
|
|
function getVOILUT(windowWidth, windowCenter, voiLUT) {
|
|
if(voiLUT) {
|
|
return generateNonLinearVOILUT(voiLUT);
|
|
} else {
|
|
return generateLinearVOILUT(windowWidth, windowCenter);
|
|
}
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.internal.getVOILUT = getVOILUT;
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module contains a function to make an image is invalid
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Sets the invalid flag on the enabled element and fire an event
|
|
* @param element
|
|
*/
|
|
function invalidate(element) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
enabledElement.invalid = true;
|
|
var eventData = {
|
|
element: element
|
|
};
|
|
$(enabledElement.element).trigger("CornerstoneInvalidated", eventData);
|
|
}
|
|
|
|
// module exports
|
|
cornerstone.invalidate = invalidate;
|
|
}(cornerstone));
|
|
/**
|
|
* This module contains a function to immediately invalidate an image
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Forces the image to be updated/redrawn for the specified enabled element
|
|
* @param element
|
|
*/
|
|
function invalidateImageId(imageId) {
|
|
|
|
var enabledElements = cornerstone.getEnabledElementsByImageId(imageId);
|
|
enabledElements.forEach(function(enabledElement) {
|
|
cornerstone.drawImage(enabledElement, true);
|
|
});
|
|
}
|
|
|
|
// module exports
|
|
cornerstone.invalidateImageId = invalidateImageId;
|
|
}(cornerstone));
|
|
/**
|
|
* This module contains a helper function to covert page coordinates to pixel coordinates
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Converts a point in the page coordinate system to the pixel coordinate
|
|
* system
|
|
* @param element
|
|
* @param pageX
|
|
* @param pageY
|
|
* @returns {{x: number, y: number}}
|
|
*/
|
|
function pageToPixel(element, pageX, pageY) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
|
|
if(enabledElement.image === undefined) {
|
|
throw "image has not been loaded yet";
|
|
}
|
|
|
|
var image = enabledElement.image;
|
|
|
|
// convert the pageX and pageY to the canvas client coordinates
|
|
var rect = element.getBoundingClientRect();
|
|
var clientX = pageX - rect.left - window.pageXOffset;
|
|
var clientY = pageY - rect.top - window.pageYOffset;
|
|
|
|
var pt = {x: clientX, y: clientY};
|
|
var transform = cornerstone.internal.getTransform(enabledElement);
|
|
transform.invert();
|
|
return transform.transformPoint(pt.x, pt.y);
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.pageToPixel = pageToPixel;
|
|
|
|
}(cornerstone));
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Converts a point in the pixel coordinate system to the canvas coordinate system
|
|
* system. This can be used to render using canvas context without having the weird
|
|
* side effects that come from scaling and non square pixels
|
|
* @param element
|
|
* @param pt
|
|
* @returns {x: number, y: number}
|
|
*/
|
|
function pixelToCanvas(element, pt) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
var transform = cornerstone.internal.getTransform(enabledElement);
|
|
return transform.transformPoint(pt.x, pt.y);
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.pixelToCanvas = pixelToCanvas;
|
|
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module is responsible for drawing an image to an enabled elements canvas element
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
var colorRenderCanvas = document.createElement('canvas');
|
|
var colorRenderCanvasContext;
|
|
var colorRenderCanvasData;
|
|
|
|
var lastRenderedImageId;
|
|
var lastRenderedViewport = {};
|
|
|
|
function initializeColorRenderCanvas(image)
|
|
{
|
|
// Resize the canvas
|
|
colorRenderCanvas.width = image.width;
|
|
colorRenderCanvas.height = image.height;
|
|
|
|
// get the canvas data so we can write to it directly
|
|
colorRenderCanvasContext = colorRenderCanvas.getContext('2d');
|
|
colorRenderCanvasContext.fillStyle = 'white';
|
|
colorRenderCanvasContext.fillRect(0,0, colorRenderCanvas.width, colorRenderCanvas.height);
|
|
colorRenderCanvasData = colorRenderCanvasContext.getImageData(0,0,image.width, image.height);
|
|
}
|
|
|
|
|
|
function getLut(image, viewport)
|
|
{
|
|
// if we have a cached lut and it has the right values, return it immediately
|
|
if(image.lut !== undefined &&
|
|
image.lut.windowCenter === viewport.voi.windowCenter &&
|
|
image.lut.windowWidth === viewport.voi.windowWidth &&
|
|
image.lut.invert === viewport.invert) {
|
|
return image.lut;
|
|
}
|
|
|
|
// lut is invalid or not present, regenerate it and cache it
|
|
cornerstone.generateLut(image, viewport.voi.windowWidth, viewport.voi.windowCenter, viewport.invert);
|
|
image.lut.windowWidth = viewport.voi.windowWidth;
|
|
image.lut.windowCenter = viewport.voi.windowCenter;
|
|
image.lut.invert = viewport.invert;
|
|
return image.lut;
|
|
}
|
|
|
|
function doesImageNeedToBeRendered(enabledElement, image)
|
|
{
|
|
if(image.imageId !== lastRenderedImageId ||
|
|
lastRenderedViewport.windowCenter !== enabledElement.viewport.voi.windowCenter ||
|
|
lastRenderedViewport.windowWidth !== enabledElement.viewport.voi.windowWidth ||
|
|
lastRenderedViewport.invert !== enabledElement.viewport.invert ||
|
|
lastRenderedViewport.rotation !== enabledElement.viewport.rotation ||
|
|
lastRenderedViewport.hflip !== enabledElement.viewport.hflip ||
|
|
lastRenderedViewport.vflip !== enabledElement.viewport.vflip
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getRenderCanvas(enabledElement, image, invalidated)
|
|
{
|
|
// apply the lut to the stored pixel data onto the render canvas
|
|
|
|
if(enabledElement.viewport.voi.windowWidth === enabledElement.image.windowWidth &&
|
|
enabledElement.viewport.voi.windowCenter === enabledElement.image.windowCenter &&
|
|
enabledElement.viewport.invert === false)
|
|
{
|
|
// the color image voi/invert has not been modified, request the canvas that contains
|
|
// it so we can draw it directly to the display canvas
|
|
return image.getCanvas();
|
|
}
|
|
else
|
|
{
|
|
if(doesImageNeedToBeRendered(enabledElement, image) === false && invalidated !== true) {
|
|
return colorRenderCanvas;
|
|
}
|
|
|
|
// If our render canvas does not match the size of this image reset it
|
|
// NOTE: This might be inefficient if we are updating multiple images of different
|
|
// sizes frequently.
|
|
if(colorRenderCanvas.width !== image.width || colorRenderCanvas.height != image.height) {
|
|
initializeColorRenderCanvas(image);
|
|
}
|
|
|
|
// get the lut to use
|
|
var colorLut = getLut(image, enabledElement.viewport);
|
|
|
|
// the color image voi/invert has been modified - apply the lut to the underlying
|
|
// pixel data and put it into the renderCanvas
|
|
cornerstone.storedColorPixelDataToCanvasImageData(image, colorLut, colorRenderCanvasData.data);
|
|
colorRenderCanvasContext.putImageData(colorRenderCanvasData, 0, 0);
|
|
return colorRenderCanvas;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* API function to render a color image to an enabled element
|
|
* @param enabledElement
|
|
* @param invalidated - true if pixel data has been invaldiated and cached rendering should not be used
|
|
*/
|
|
function renderColorImage(enabledElement, invalidated) {
|
|
|
|
if(enabledElement === undefined) {
|
|
throw "drawImage: enabledElement parameter must not be undefined";
|
|
}
|
|
var image = enabledElement.image;
|
|
if(image === undefined) {
|
|
throw "drawImage: image must be loaded before it can be drawn";
|
|
}
|
|
|
|
// get the canvas context and reset the transform
|
|
var context = enabledElement.canvas.getContext('2d');
|
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
// clear the canvas
|
|
context.fillStyle = 'black';
|
|
context.fillRect(0,0, enabledElement.canvas.width, enabledElement.canvas.height);
|
|
|
|
// turn off image smooth/interpolation if pixelReplication is set in the viewport
|
|
if(enabledElement.viewport.pixelReplication === true) {
|
|
context.imageSmoothingEnabled = false;
|
|
context.mozImageSmoothingEnabled = false; // firefox doesn't support imageSmoothingEnabled yet
|
|
}
|
|
else {
|
|
context.imageSmoothingEnabled = true;
|
|
context.mozImageSmoothingEnabled = true;
|
|
}
|
|
|
|
// save the canvas context state and apply the viewport properties
|
|
context.save();
|
|
cornerstone.setToPixelCoordinateSystem(enabledElement, context);
|
|
|
|
var renderCanvas = getRenderCanvas(enabledElement, image, invalidated);
|
|
|
|
context.drawImage(renderCanvas, 0,0, image.width, image.height, 0, 0, image.width, image.height);
|
|
|
|
context.restore();
|
|
|
|
lastRenderedImageId = image.imageId;
|
|
lastRenderedViewport.windowCenter = enabledElement.viewport.voi.windowCenter;
|
|
lastRenderedViewport.windowWidth = enabledElement.viewport.voi.windowWidth;
|
|
lastRenderedViewport.invert = enabledElement.viewport.invert;
|
|
lastRenderedViewport.rotation = enabledElement.viewport.rotation;
|
|
lastRenderedViewport.hflip = enabledElement.viewport.hflip;
|
|
lastRenderedViewport.vflip = enabledElement.viewport.vflip;
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.rendering.colorImage = renderColorImage;
|
|
cornerstone.renderColorImage = renderColorImage;
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module is responsible for drawing a grayscale image
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
var grayscaleRenderCanvas = document.createElement('canvas');
|
|
var grayscaleRenderCanvasContext;
|
|
var grayscaleRenderCanvasData;
|
|
|
|
var lastRenderedImageId;
|
|
var lastRenderedViewport = {};
|
|
|
|
function initializeGrayscaleRenderCanvas(image)
|
|
{
|
|
// Resize the canvas
|
|
grayscaleRenderCanvas.width = image.width;
|
|
grayscaleRenderCanvas.height = image.height;
|
|
|
|
// NOTE - we need to fill the render canvas with white pixels since we control the luminance
|
|
// using the alpha channel to improve rendering performance.
|
|
grayscaleRenderCanvasContext = grayscaleRenderCanvas.getContext('2d');
|
|
grayscaleRenderCanvasContext.fillStyle = 'white';
|
|
grayscaleRenderCanvasContext.fillRect(0,0, grayscaleRenderCanvas.width, grayscaleRenderCanvas.height);
|
|
grayscaleRenderCanvasData = grayscaleRenderCanvasContext.getImageData(0,0,image.width, image.height);
|
|
}
|
|
|
|
function lutMatches(a, b) {
|
|
// if undefined, they are equal
|
|
if(!a && !b) {
|
|
return true;
|
|
}
|
|
// if one is undefined, not equal
|
|
if(!a || !b) {
|
|
return false;
|
|
}
|
|
// check the unique ids
|
|
return (a.id !== b.id)
|
|
}
|
|
|
|
function getLut(image, viewport, invalidated)
|
|
{
|
|
// if we have a cached lut and it has the right values, return it immediately
|
|
if(image.lut !== undefined &&
|
|
image.lut.windowCenter === viewport.voi.windowCenter &&
|
|
image.lut.windowWidth === viewport.voi.windowWidth &&
|
|
lutMatches(image.lut.modalityLUT, viewport.modalityLUT) &&
|
|
lutMatches(image.lut.voiLUT, viewport.voiLUT) &&
|
|
image.lut.invert === viewport.invert &&
|
|
invalidated !== true) {
|
|
return image.lut;
|
|
}
|
|
|
|
// lut is invalid or not present, regenerate it and cache it
|
|
cornerstone.generateLut(image, viewport.voi.windowWidth, viewport.voi.windowCenter, viewport.invert, viewport.modalityLUT, viewport.voiLUT);
|
|
image.lut.windowWidth = viewport.voi.windowWidth;
|
|
image.lut.windowCenter = viewport.voi.windowCenter;
|
|
image.lut.invert = viewport.invert;
|
|
image.lut.voiLUT = viewport.voiLUT;
|
|
image.lut.modalityLUT = viewport.modalityLUT;
|
|
return image.lut;
|
|
}
|
|
|
|
function doesImageNeedToBeRendered(enabledElement, image)
|
|
{
|
|
if(image.imageId !== lastRenderedImageId ||
|
|
lastRenderedViewport.windowCenter !== enabledElement.viewport.voi.windowCenter ||
|
|
lastRenderedViewport.windowWidth !== enabledElement.viewport.voi.windowWidth ||
|
|
lastRenderedViewport.invert !== enabledElement.viewport.invert ||
|
|
lastRenderedViewport.rotation !== enabledElement.viewport.rotation ||
|
|
lastRenderedViewport.hflip !== enabledElement.viewport.hflip ||
|
|
lastRenderedViewport.vflip !== enabledElement.viewport.vflip ||
|
|
lastRenderedViewport.modalityLUT !== enabledElement.viewport.modalityLUT ||
|
|
lastRenderedViewport.voiLUT !== enabledElement.viewport.voiLUT
|
|
)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getRenderCanvas(enabledElement, image, invalidated)
|
|
{
|
|
// apply the lut to the stored pixel data onto the render canvas
|
|
|
|
if(doesImageNeedToBeRendered(enabledElement, image) === false && invalidated !== true) {
|
|
return grayscaleRenderCanvas;
|
|
}
|
|
|
|
// If our render canvas does not match the size of this image reset it
|
|
// NOTE: This might be inefficient if we are updating multiple images of different
|
|
// sizes frequently.
|
|
if(grayscaleRenderCanvas.width !== image.width || grayscaleRenderCanvas.height != image.height) {
|
|
initializeGrayscaleRenderCanvas(image);
|
|
}
|
|
|
|
// get the lut to use
|
|
var lut = getLut(image, enabledElement.viewport, invalidated);
|
|
// gray scale image - apply the lut and put the resulting image onto the render canvas
|
|
cornerstone.storedPixelDataToCanvasImageData(image, lut, grayscaleRenderCanvasData.data);
|
|
grayscaleRenderCanvasContext.putImageData(grayscaleRenderCanvasData, 0, 0);
|
|
return grayscaleRenderCanvas;
|
|
}
|
|
|
|
/**
|
|
* API function to draw a grayscale image to a given enabledElement
|
|
* @param enabledElement
|
|
* @param invalidated - true if pixel data has been invaldiated and cached rendering should not be used
|
|
*/
|
|
function renderGrayscaleImage(enabledElement, invalidated) {
|
|
|
|
if(enabledElement === undefined) {
|
|
throw "drawImage: enabledElement parameter must not be undefined";
|
|
}
|
|
var image = enabledElement.image;
|
|
if(image === undefined) {
|
|
throw "drawImage: image must be loaded before it can be drawn";
|
|
}
|
|
|
|
// get the canvas context and reset the transform
|
|
var context = enabledElement.canvas.getContext('2d');
|
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
// clear the canvas
|
|
context.fillStyle = 'black';
|
|
context.fillRect(0,0, enabledElement.canvas.width, enabledElement.canvas.height);
|
|
|
|
// turn off image smooth/interpolation if pixelReplication is set in the viewport
|
|
if(enabledElement.viewport.pixelReplication === true) {
|
|
context.imageSmoothingEnabled = false;
|
|
context.mozImageSmoothingEnabled = false; // firefox doesn't support imageSmoothingEnabled yet
|
|
}
|
|
else {
|
|
context.imageSmoothingEnabled = true;
|
|
context.mozImageSmoothingEnabled = true;
|
|
}
|
|
|
|
// save the canvas context state and apply the viewport properties
|
|
cornerstone.setToPixelCoordinateSystem(enabledElement, context);
|
|
|
|
var renderCanvas = getRenderCanvas(enabledElement, image, invalidated);
|
|
|
|
// Draw the render canvas half the image size (because we set origin to the middle of the canvas above)
|
|
context.drawImage(renderCanvas, 0,0, image.width, image.height, 0, 0, image.width, image.height);
|
|
|
|
lastRenderedImageId = image.imageId;
|
|
lastRenderedViewport.windowCenter = enabledElement.viewport.voi.windowCenter;
|
|
lastRenderedViewport.windowWidth = enabledElement.viewport.voi.windowWidth;
|
|
lastRenderedViewport.invert = enabledElement.viewport.invert;
|
|
lastRenderedViewport.rotation = enabledElement.viewport.rotation;
|
|
lastRenderedViewport.hflip = enabledElement.viewport.hflip;
|
|
lastRenderedViewport.vflip = enabledElement.viewport.vflip;
|
|
lastRenderedViewport.modalityLUT = enabledElement.viewport.modalityLUT;
|
|
lastRenderedViewport.voiLUT = enabledElement.viewport.voiLUT;
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.rendering.grayscaleImage = renderGrayscaleImage;
|
|
cornerstone.renderGrayscaleImage = renderGrayscaleImage;
|
|
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module is responsible for drawing an image to an enabled elements canvas element
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* API function to draw a standard web image (PNG, JPG) to an enabledImage
|
|
*
|
|
* @param enabledElement
|
|
* @param invalidated - true if pixel data has been invaldiated and cached rendering should not be used
|
|
*/
|
|
function renderWebImage(enabledElement, invalidated) {
|
|
|
|
if(enabledElement === undefined) {
|
|
throw "drawImage: enabledElement parameter must not be undefined";
|
|
}
|
|
var image = enabledElement.image;
|
|
if(image === undefined) {
|
|
throw "drawImage: image must be loaded before it can be drawn";
|
|
}
|
|
|
|
// get the canvas context and reset the transform
|
|
var context = enabledElement.canvas.getContext('2d');
|
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
// clear the canvas
|
|
context.fillStyle = 'black';
|
|
context.fillRect(0,0, enabledElement.canvas.width, enabledElement.canvas.height);
|
|
|
|
// turn off image smooth/interpolation if pixelReplication is set in the viewport
|
|
if(enabledElement.viewport.pixelReplication === true) {
|
|
context.imageSmoothingEnabled = false;
|
|
context.mozImageSmoothingEnabled = false; // firefox doesn't support imageSmoothingEnabled yet
|
|
}
|
|
else {
|
|
context.imageSmoothingEnabled = true;
|
|
context.mozImageSmoothingEnabled = true;
|
|
}
|
|
|
|
// save the canvas context state and apply the viewport properties
|
|
cornerstone.setToPixelCoordinateSystem(enabledElement, context);
|
|
|
|
// if the viewport ww/wc and invert all match the initial state of the image, we can draw the image
|
|
// directly. If any of those are changed, we call renderColorImage() to apply the lut
|
|
if(enabledElement.viewport.voi.windowWidth === enabledElement.image.windowWidth &&
|
|
enabledElement.viewport.voi.windowCenter === enabledElement.image.windowCenter &&
|
|
enabledElement.viewport.invert === false)
|
|
{
|
|
context.drawImage(image.getImage(), 0, 0, image.width, image.height, 0, 0, image.width, image.height);
|
|
} else {
|
|
cornerstone.renderColorImage(enabledElement, invalidated);
|
|
}
|
|
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.rendering.webImage = renderWebImage;
|
|
cornerstone.renderWebImage = renderWebImage;
|
|
|
|
}(cornerstone));
|
|
/**
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Resets the viewport to the default settings
|
|
*
|
|
* @param element
|
|
*/
|
|
function reset(element)
|
|
{
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
var defaultViewport = cornerstone.internal.getDefaultViewport(enabledElement.canvas, enabledElement.image);
|
|
enabledElement.viewport = defaultViewport;
|
|
cornerstone.updateImage(element);
|
|
}
|
|
|
|
cornerstone.reset = reset;
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module is responsible for enabling an element to display images with cornerstone
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
function setCanvasSize(element, canvas)
|
|
{
|
|
// the device pixel ratio is 1.0 for normal displays and > 1.0
|
|
// for high DPI displays like Retina
|
|
/*
|
|
|
|
This functionality is disabled due to buggy behavior on systems with mixed DPI's. If the canvas
|
|
is created on a display with high DPI (e.g. 2.0) and then the browser window is dragged to
|
|
a different display with a different DPI (e.g. 1.0), the canvas is not recreated so the pageToPixel
|
|
produces incorrect results. I couldn't find any way to determine when the DPI changed other than
|
|
by polling which is not very clean. If anyone has any ideas here, please let me know, but for now
|
|
we will disable this functionality. We may want
|
|
to add a mechanism to optionally enable this functionality if we can determine it is safe to do
|
|
so (e.g. iPad or iPhone or perhaps enumerate the displays on the system. I am choosing
|
|
to be cautious here since I would rather not have bug reports or safety issues related to this
|
|
scenario.
|
|
|
|
var devicePixelRatio = window.devicePixelRatio;
|
|
if(devicePixelRatio === undefined) {
|
|
devicePixelRatio = 1.0;
|
|
}
|
|
*/
|
|
|
|
canvas.width = element.clientWidth;
|
|
canvas.height = element.clientHeight;
|
|
canvas.style.width = element.clientWidth + "px";
|
|
canvas.style.height = element.clientHeight + "px";
|
|
}
|
|
|
|
/**
|
|
* resizes an enabled element and optionally fits the image to window
|
|
* @param element
|
|
* @param fitToWindow true to refit, false to leave viewport parameters as they are
|
|
*/
|
|
function resize(element, fitToWindow) {
|
|
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
|
|
setCanvasSize(element, enabledElement.canvas);
|
|
|
|
if(enabledElement.image === undefined ) {
|
|
return;
|
|
}
|
|
|
|
if(fitToWindow === true) {
|
|
cornerstone.fitToWindow(element);
|
|
}
|
|
else {
|
|
cornerstone.updateImage(element);
|
|
}
|
|
}
|
|
|
|
// module/private exports
|
|
cornerstone.resize = resize;
|
|
|
|
}(cornerstone));
|
|
/**
|
|
* This module contains a function that will set the canvas context to the pixel coordinates system
|
|
* making it easy to draw geometry on the image
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Sets the canvas context transformation matrix to the pixel coordinate system. This allows
|
|
* geometry to be driven using the canvas context using coordinates in the pixel coordinate system
|
|
* @param ee
|
|
* @param context
|
|
* @param scale optional scaler to apply
|
|
*/
|
|
function setToPixelCoordinateSystem(enabledElement, context, scale)
|
|
{
|
|
if(enabledElement === undefined) {
|
|
throw "setToPixelCoordinateSystem: parameter enabledElement must not be undefined";
|
|
}
|
|
if(context === undefined) {
|
|
throw "setToPixelCoordinateSystem: parameter context must not be undefined";
|
|
}
|
|
|
|
var transform = cornerstone.internal.calculateTransform(enabledElement, scale);
|
|
context.setTransform(transform.m[0],transform.m[1],transform.m[2],transform.m[3],transform.m[4],transform.m[5],transform.m[6]);
|
|
}
|
|
|
|
// Module exports
|
|
cornerstone.setToPixelCoordinateSystem = setToPixelCoordinateSystem;
|
|
}(cornerstone));
|
|
/**
|
|
* This module contains functions to deal with getting and setting the viewport for an enabled element
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Sets the viewport for an element and corrects invalid values
|
|
*
|
|
* @param element - DOM element of the enabled element
|
|
* @param viewport - Object containing the viewport properties
|
|
* @returns {*}
|
|
*/
|
|
function setViewport(element, viewport) {
|
|
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
|
|
enabledElement.viewport.scale = viewport.scale;
|
|
enabledElement.viewport.translation.x = viewport.translation.x;
|
|
enabledElement.viewport.translation.y = viewport.translation.y;
|
|
enabledElement.viewport.voi.windowWidth = viewport.voi.windowWidth;
|
|
enabledElement.viewport.voi.windowCenter = viewport.voi.windowCenter;
|
|
enabledElement.viewport.invert = viewport.invert;
|
|
enabledElement.viewport.pixelReplication = viewport.pixelReplication;
|
|
enabledElement.viewport.rotation = viewport.rotation;
|
|
enabledElement.viewport.hflip = viewport.hflip;
|
|
enabledElement.viewport.vflip = viewport.vflip;
|
|
enabledElement.viewport.modalityLUT = viewport.modalityLUT;
|
|
enabledElement.viewport.voiLUT = viewport.voiLUT;
|
|
|
|
// prevent window width from being too small (note that values close to zero are valid and can occur with
|
|
// PET images in particular)
|
|
if(enabledElement.viewport.voi.windowWidth < 0.000001) {
|
|
enabledElement.viewport.voi.windowWidth = 0.000001;
|
|
}
|
|
// prevent scale from getting too small
|
|
if(enabledElement.viewport.scale < 0.0001) {
|
|
enabledElement.viewport.scale = 0.25;
|
|
}
|
|
|
|
if(enabledElement.viewport.rotation===360 || enabledElement.viewport.rotation===-360) {
|
|
enabledElement.viewport.rotation = 0;
|
|
}
|
|
|
|
// Force the image to be updated since the viewport has been modified
|
|
cornerstone.updateImage(element);
|
|
}
|
|
|
|
|
|
// module/private exports
|
|
cornerstone.setViewport = setViewport;
|
|
|
|
}(cornerstone));
|
|
|
|
/**
|
|
* This module contains a function to immediately redraw an image
|
|
*/
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Forces the image to be updated/redrawn for the specified enabled element
|
|
* @param element
|
|
*/
|
|
function updateImage(element, invalidated) {
|
|
var enabledElement = cornerstone.getEnabledElement(element);
|
|
|
|
if(enabledElement.image === undefined) {
|
|
throw "updateImage: image has not been loaded yet";
|
|
}
|
|
|
|
cornerstone.drawImage(enabledElement, invalidated);
|
|
}
|
|
|
|
// module exports
|
|
cornerstone.updateImage = updateImage;
|
|
|
|
}(cornerstone));
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
/**
|
|
* Creates and compiles a shader.
|
|
*
|
|
* @param {!WebGLRenderingContext} gl The WebGL Context.
|
|
* @param {string} shaderSource The GLSL source code for the shader.
|
|
* @param {number} shaderType The type of shader, VERTEX_SHADER or FRAGMENT_SHADER.
|
|
*
|
|
* @return {!WebGLShader} The shader.
|
|
*/
|
|
function compileShader(gl, shaderSource, shaderType) {
|
|
|
|
// Create the shader object
|
|
var shader = gl.createShader(shaderType);
|
|
|
|
// Set the shader source code.
|
|
gl.shaderSource(shader, shaderSource);
|
|
|
|
// Compile the shader
|
|
gl.compileShader(shader);
|
|
|
|
// Check if it compiled
|
|
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
|
|
if (!success && !gl.isContextLost()) {
|
|
// Something went wrong during compilation; get the error
|
|
var infoLog = gl.getShaderInfoLog(shader);
|
|
console.error("Could not compile shader:\n" + infoLog);
|
|
}
|
|
|
|
return shader;
|
|
}
|
|
|
|
/**
|
|
* Creates a program from 2 shaders.
|
|
*
|
|
* @param {!WebGLRenderingContext) gl The WebGL context.
|
|
* @param {!WebGLShader} vertexShader A vertex shader.
|
|
* @param {!WebGLShader} fragmentShader A fragment shader.
|
|
* @return {!WebGLProgram} A program.
|
|
*/
|
|
function createProgram(gl, vertexShader, fragmentShader) {
|
|
|
|
// create a program.
|
|
var program = gl.createProgram();
|
|
|
|
// attach the shaders.
|
|
gl.attachShader(program, vertexShader);
|
|
gl.attachShader(program, fragmentShader);
|
|
|
|
// link the program.
|
|
gl.linkProgram(program);
|
|
|
|
// Check if it linked.
|
|
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
|
|
if (!success && !gl.isContextLost()) {
|
|
// something went wrong with the link
|
|
var infoLog = gl.getProgramInfoLog(program);
|
|
console.error("WebGL program filed to link:\n" + infoLog);
|
|
}
|
|
|
|
return program;
|
|
}
|
|
|
|
/**
|
|
* Creates a program from 2 shaders source (Strings)
|
|
* @param {!WebGLRenderingContext} gl The WebGL context.
|
|
* @param {!WebGLShader} vertexShaderSrc Vertex shader string
|
|
* @param {!WebGLShader} fragShaderSrc Fragment shader string
|
|
* @return {!WebGLProgram} A program
|
|
*/
|
|
function createProgramFromString(gl, vertexShaderSrc, fragShaderSrc) {
|
|
var vertexShader = compileShader(gl, vertexShaderSrc, gl.VERTEX_SHADER);
|
|
var fragShader = compileShader(gl, fragShaderSrc, gl.FRAGMENT_SHADER);
|
|
return createProgram(gl, vertexShader, fragShader);
|
|
}
|
|
|
|
cornerstone.webGL.createProgramFromString = createProgramFromString;
|
|
|
|
}(cornerstone));
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
var renderCanvas = document.createElement('canvas');
|
|
var renderCanvasContext;
|
|
var renderCanvasData;
|
|
var gl;
|
|
var programs;
|
|
var shader;
|
|
var texCoordBuffer, positionBuffer;
|
|
cornerstone.webGL.isWebGLInitialized = false;
|
|
|
|
function getRenderCanvas() {
|
|
return renderCanvas;
|
|
}
|
|
|
|
function initShaders() {
|
|
for (var id in cornerstone.webGL.shaders) {
|
|
console.log("WEBGL: Loading shader", id);
|
|
var shader = cornerstone.webGL.shaders[ id ];
|
|
shader.attributes = {};
|
|
shader.uniforms = {};
|
|
shader.vert = cornerstone.webGL.vertexShader;
|
|
|
|
shader.program = cornerstone.webGL.createProgramFromString(gl, shader.vert, shader.frag);
|
|
|
|
shader.attributes.texCoordLocation = gl.getAttribLocation(shader.program, "a_texCoord");
|
|
gl.enableVertexAttribArray(shader.attributes.texCoordLocation);
|
|
|
|
shader.attributes.positionLocation = gl.getAttribLocation(shader.program, "a_position");
|
|
gl.enableVertexAttribArray(shader.attributes.positionLocation);
|
|
|
|
shader.uniforms.resolutionLocation = gl.getUniformLocation(shader.program, "u_resolution");
|
|
}
|
|
}
|
|
|
|
function initRenderer() {
|
|
if (cornerstone.webGL.isWebGLInitialized === true) {
|
|
console.log("WEBGL Renderer already initialized");
|
|
return;
|
|
}
|
|
if ( initWebGL( renderCanvas ) ) {
|
|
initBuffers();
|
|
initShaders();
|
|
console.log("WEBGL Renderer initialized!");
|
|
cornerstone.webGL.isWebGLInitialized = true;
|
|
}
|
|
}
|
|
|
|
function updateRectangle(gl, width, height) {
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
width, height,
|
|
0, height,
|
|
width, 0,
|
|
0, 0]), gl.STATIC_DRAW);
|
|
}
|
|
|
|
function handleLostContext(event) {
|
|
event.preventDefault();
|
|
console.warn('WebGL Context Lost!');
|
|
}
|
|
|
|
function handleRestoredContext(event) {
|
|
event.preventDefault();
|
|
cornerstone.webGL.isWebGLInitialized = false;
|
|
cornerstone.webGL.textureCache.purgeCache();
|
|
initRenderer();
|
|
console.log('WebGL Context Restored.');
|
|
}
|
|
|
|
function initWebGL(canvas) {
|
|
|
|
gl = null;
|
|
try {
|
|
// Try to grab the standard context. If it fails, fallback to experimental.
|
|
var options = {
|
|
preserveDrawingBuffer: true, // preserve buffer so we can copy to display canvas element
|
|
};
|
|
|
|
// ---------------- Testing purposes -------------
|
|
if (cornerstone.webGL.debug === true && WebGLDebugUtils) {
|
|
renderCanvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(renderCanvas);
|
|
}
|
|
// ---------------- Testing purposes -------------
|
|
|
|
gl = canvas.getContext("webgl", options) || canvas.getContext("experimental-webgl", options);
|
|
|
|
// Set up event listeners for context lost / context restored
|
|
canvas.removeEventListener("webglcontextlost", handleLostContext, false);
|
|
canvas.addEventListener("webglcontextlost", handleLostContext, false);
|
|
|
|
canvas.removeEventListener("webglcontextrestored", handleRestoredContext, false);
|
|
canvas.addEventListener("webglcontextrestored", handleRestoredContext, false);
|
|
|
|
} catch(error) {
|
|
throw "Error creating WebGL context";
|
|
}
|
|
|
|
// If we don't have a GL context, give up now
|
|
if (!gl) {
|
|
console.error("Unable to initialize WebGL. Your browser may not support it.");
|
|
gl = null;
|
|
}
|
|
return gl;
|
|
}
|
|
|
|
function getImageDataType(image) {
|
|
if (image.color) {
|
|
return 'rgb';
|
|
}
|
|
|
|
var datatype = 'int';
|
|
if (image.minPixelValue >= 0) {
|
|
datatype = 'u' + datatype;
|
|
}
|
|
|
|
if (image.maxPixelValue > 255) {
|
|
datatype += '16';
|
|
} else {
|
|
datatype += '8';
|
|
}
|
|
return datatype;
|
|
}
|
|
|
|
function getShaderProgram(image) {
|
|
|
|
var datatype = getImageDataType(image);
|
|
// We need a mechanism for
|
|
// choosing the shader based on the image datatype
|
|
// console.log("Datatype: " + datatype);
|
|
if (cornerstone.webGL.shaders.hasOwnProperty(datatype)) {
|
|
return cornerstone.webGL.shaders[ datatype ];
|
|
}
|
|
|
|
var shader = cornerstone.webGL.shaders.rgb;
|
|
return shader;
|
|
}
|
|
|
|
function getImageTexture( image ) {
|
|
var imageTexture = cornerstone.webGL.textureCache.getImageTexture(image.imageId);
|
|
if (!imageTexture) {
|
|
//console.log("Generating texture for imageid: ", image.imageId);
|
|
imageTexture = generateTexture(image);
|
|
cornerstone.webGL.textureCache.putImageTexture(image, imageTexture);
|
|
}
|
|
return imageTexture.texture;
|
|
|
|
}
|
|
|
|
function generateTexture( image ) {
|
|
var TEXTURE_FORMAT = {
|
|
uint8: gl.LUMINANCE,
|
|
int8: gl.LUMINANCE_ALPHA,
|
|
uint16: gl.LUMINANCE_ALPHA,
|
|
int16: gl.RGB,
|
|
rgb: gl.RGB
|
|
};
|
|
|
|
var TEXTURE_BYTES = {
|
|
int8: 1, // Luminance
|
|
uint16: 2, // Luminance + Alpha
|
|
int16: 3, // RGB
|
|
rgb: 3 // RGB
|
|
};
|
|
|
|
var imageDataType = getImageDataType(image);
|
|
var format = TEXTURE_FORMAT[imageDataType];
|
|
|
|
// GL texture configuration
|
|
var texture = gl.createTexture();
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
|
|
var imageData = cornerstone.webGL.dataUtilities[imageDataType].storedPixelDataToImageData(image, image.width, image.height);
|
|
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, format, image.width, image.height, 0, format, gl.UNSIGNED_BYTE, imageData);
|
|
|
|
// Calculate the size in bytes of this image in memory
|
|
var sizeInBytes = image.width * image.height * TEXTURE_BYTES[imageDataType];
|
|
var imageTexture = {
|
|
texture: texture,
|
|
sizeInBytes: sizeInBytes
|
|
};
|
|
return imageTexture;
|
|
|
|
}
|
|
|
|
function initBuffers() {
|
|
|
|
positionBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
1, 1,
|
|
0, 1,
|
|
1, 0,
|
|
0, 0
|
|
]), gl.STATIC_DRAW);
|
|
|
|
|
|
texCoordBuffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
1.0, 1.0,
|
|
0.0, 1.0,
|
|
1.0, 0.0,
|
|
0.0, 0.0,
|
|
]), gl.STATIC_DRAW);
|
|
}
|
|
|
|
function renderQuad(shader, parameters, texture, width, height )
|
|
{
|
|
gl.clearColor(1.0,0.0,0.0,1.0);
|
|
gl.viewport( 0, 0, width, height );
|
|
|
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
gl.useProgram(shader.program);
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
|
gl.vertexAttribPointer(shader.attributes.texCoordLocation, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
gl.vertexAttribPointer(shader.attributes.positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
|
|
for (var key in parameters) {
|
|
var uniformLocation = gl.getUniformLocation(shader.program, key);
|
|
if ( !uniformLocation ) {
|
|
throw "Could not access location for uniform: " + key;
|
|
}
|
|
|
|
var uniform = parameters[key];
|
|
|
|
var type = uniform.type;
|
|
var value = uniform.value;
|
|
|
|
if( type == "i" ) {
|
|
gl.uniform1i( uniformLocation, value );
|
|
} else if( type == "f" ) {
|
|
gl.uniform1f( uniformLocation, value );
|
|
} else if( type == "2f" ) {
|
|
gl.uniform2f( uniformLocation, value[0], value[1] );
|
|
}
|
|
}
|
|
|
|
updateRectangle(gl, width, height);
|
|
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
|
|
}
|
|
|
|
function render(enabledElement) {
|
|
|
|
if (!enabledElement) {
|
|
throw "drawImage: enabledElement parameter must not be undefined";
|
|
}
|
|
|
|
var image = enabledElement.image;
|
|
if (!image) {
|
|
throw "drawImage: image must be loaded before it can be drawn";
|
|
}
|
|
|
|
// Resize the canvas
|
|
renderCanvas.width = image.width;
|
|
renderCanvas.height = image.height;
|
|
|
|
// Get the canvas context and reset the transform
|
|
var context = enabledElement.canvas.getContext('2d');
|
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
|
|
// Clear the canvas
|
|
context.fillStyle = 'black';
|
|
context.fillRect(0,0, enabledElement.canvas.width, enabledElement.canvas.height);
|
|
|
|
// Turn off image smooth/interpolation if pixelReplication is set in the viewport
|
|
if (enabledElement.viewport.pixelReplication === true) {
|
|
context.imageSmoothingEnabled = false;
|
|
context.mozImageSmoothingEnabled = false; // firefox doesn't support imageSmoothingEnabled yet
|
|
} else {
|
|
context.imageSmoothingEnabled = true;
|
|
context.mozImageSmoothingEnabled = true;
|
|
}
|
|
|
|
var viewport = enabledElement.viewport;
|
|
|
|
// Render the current image
|
|
var shader = getShaderProgram(image);
|
|
var texture = getImageTexture(image);
|
|
var parameters = {
|
|
"u_resolution": { type: "2f", value: [image.width, image.height] },
|
|
"wc": { type: "f", value: enabledElement.viewport.voi.windowCenter },
|
|
"ww": { type: "f", value: enabledElement.viewport.voi.windowWidth },
|
|
"slope": { type: "f", value: image.slope },
|
|
"intercept": { type: "f", value: image.intercept },
|
|
//"minPixelValue": { type: "f", value: image.minPixelValue },
|
|
"invert": { type: "i", value: enabledElement.viewport.invert ? 1 : 0 },
|
|
};
|
|
renderQuad(shader, parameters, texture, image.width, image.height );
|
|
|
|
// Save the canvas context state and apply the viewport properties
|
|
cornerstone.setToPixelCoordinateSystem(enabledElement, context);
|
|
|
|
// Copy pixels from the offscreen canvas to the onscreen canvas
|
|
context.drawImage(renderCanvas, 0,0, image.width, image.height, 0, 0, image.width, image.height);
|
|
|
|
}
|
|
|
|
function isWebGLAvailable() {
|
|
// Adapted from
|
|
// http://stackoverflow.com/questions/9899807/three-js-detect-webgl-support-and-fallback-to-regular-canvas
|
|
|
|
var options = {
|
|
failIfMajorPerformanceCaveat: true
|
|
};
|
|
|
|
try {
|
|
var canvas = document.createElement("canvas");
|
|
return !!
|
|
window.WebGLRenderingContext &&
|
|
(canvas.getContext("webgl", options) || canvas.getContext("experimental-webgl", options));
|
|
} catch(e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cornerstone.webGL.renderer = {
|
|
render: render,
|
|
initRenderer: initRenderer,
|
|
getRenderCanvas: getRenderCanvas,
|
|
isWebGLAvailable: isWebGLAvailable
|
|
};
|
|
|
|
}(cornerstone));
|
|
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.shaders) {
|
|
cornerstone.webGL.shaders = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.dataUtilities) {
|
|
cornerstone.webGL.dataUtilities = {};
|
|
}
|
|
|
|
// Pack int16 into three uint8 channels (r, g, b)
|
|
var shader = {};
|
|
|
|
function storedPixelDataToImageData(image) {
|
|
|
|
// Transfer image data to alpha and luminance channels of WebGL texture
|
|
// Credit to @jpambrun and @fernandojsg
|
|
|
|
// Pack int16 into three uint8 channels (r, g, b)
|
|
var pixelData = image.getPixelData();
|
|
var numberOfChannels = 3;
|
|
var data = new Uint8Array(image.width * image.height * numberOfChannels);
|
|
var offset = 0;
|
|
|
|
for (var i = 0; i < pixelData.length; i++) {
|
|
var val = Math.abs(pixelData[i]);
|
|
data[offset++] = parseInt(val & 0xFF, 10);
|
|
data[offset++] = parseInt(val >> 8, 10);
|
|
data[offset++] = pixelData[i] < 0 ? 0: 1; // 0 For negative, 1 for positive
|
|
}
|
|
return data;
|
|
}
|
|
|
|
cornerstone.webGL.dataUtilities.int16 = {
|
|
storedPixelDataToImageData: storedPixelDataToImageData
|
|
};
|
|
|
|
shader.frag = 'precision mediump float;' +
|
|
'uniform sampler2D u_image;' +
|
|
'uniform float ww;' +
|
|
'uniform float wc;' +
|
|
'uniform float slope;' +
|
|
'uniform float intercept;' +
|
|
'uniform int invert;' +
|
|
'varying vec2 v_texCoord;' +
|
|
|
|
'void main() {' +
|
|
// Get texture
|
|
'vec4 color = texture2D(u_image, v_texCoord);' +
|
|
|
|
// Calculate luminance from packed texture
|
|
'float intensity = color.r*256.0 + color.g*65536.0;'+
|
|
|
|
'if (color.b == 0.0)' +
|
|
'intensity = -intensity;' +
|
|
|
|
// Rescale based on slope and window settings
|
|
'intensity = intensity * slope + intercept;'+
|
|
'float center0 = wc - 0.5;'+
|
|
'float width0 = max(ww, 1.0);' +
|
|
'intensity = (intensity - center0) / width0 + 0.5;'+
|
|
|
|
// Clamp intensity
|
|
'intensity = clamp(intensity, 0.0, 1.0);' +
|
|
|
|
// RGBA output
|
|
'gl_FragColor = vec4(intensity, intensity, intensity, 1.0);' +
|
|
|
|
// Apply any inversion necessary
|
|
'if (invert == 1)' +
|
|
'gl_FragColor.rgb = 1.0 - gl_FragColor.rgb;' +
|
|
'}';
|
|
|
|
cornerstone.webGL.shaders.int16 = shader;
|
|
|
|
}(cornerstone));
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.shaders) {
|
|
cornerstone.webGL.shaders = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.dataUtilities) {
|
|
cornerstone.webGL.dataUtilities = {};
|
|
}
|
|
|
|
var shader = {};
|
|
|
|
function storedPixelDataToImageData(image) {
|
|
// Transfer image data to alpha channel of WebGL texture
|
|
// Store data in Uint8Array
|
|
var pixelData = image.getPixelData();
|
|
var numberOfChannels = 2;
|
|
var data = new Uint8Array(image.width * image.height * numberOfChannels);
|
|
var offset = 0;
|
|
|
|
for (var i = 0; i < pixelData.length; i++) {
|
|
data[offset++] = parseInt(pixelData[i], 10);
|
|
data[offset++] = pixelData[i] < 0 ? 0: 1; // 0 For negative, 1 for positive
|
|
}
|
|
return data;
|
|
}
|
|
|
|
cornerstone.webGL.dataUtilities.int8 = {
|
|
storedPixelDataToImageData: storedPixelDataToImageData
|
|
};
|
|
|
|
shader.frag = 'precision mediump float;' +
|
|
'uniform sampler2D u_image;' +
|
|
'uniform float ww;' +
|
|
'uniform float wc;' +
|
|
'uniform float slope;' +
|
|
'uniform float intercept;' +
|
|
'uniform float minPixelValue;' +
|
|
'uniform int invert;' +
|
|
'varying vec2 v_texCoord;' +
|
|
|
|
'void main() {' +
|
|
// Get texture
|
|
'vec4 color = texture2D(u_image, v_texCoord);' +
|
|
|
|
// Calculate luminance from packed texture
|
|
'float intensity = color.r*256.;'+
|
|
|
|
'if (color.a == 0.0)' +
|
|
'intensity = -intensity;' +
|
|
|
|
// Rescale based on slope and window settings
|
|
'intensity = intensity * slope + intercept;'+
|
|
'float center0 = wc - 0.5;'+
|
|
'float width0 = max(ww, 1.0);' +
|
|
'intensity = (intensity - center0) / width0 + 0.5;'+
|
|
|
|
// Clamp intensity
|
|
'intensity = clamp(intensity, 0.0, 1.0);' +
|
|
|
|
// RGBA output
|
|
'gl_FragColor = vec4(intensity, intensity, intensity, 1.0);' +
|
|
|
|
// Apply any inversion necessary
|
|
'if (invert == 1)' +
|
|
'gl_FragColor.rgb = 1.0 - gl_FragColor.rgb;' +
|
|
'}';
|
|
|
|
cornerstone.webGL.shaders.int8 = shader;
|
|
|
|
|
|
}(cornerstone));
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.shaders) {
|
|
cornerstone.webGL.shaders = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.dataUtilities) {
|
|
cornerstone.webGL.dataUtilities = {};
|
|
}
|
|
|
|
// Pack RGB images into a 3-channel RGB texture
|
|
var shader = {};
|
|
|
|
function storedPixelDataToImageData(image) {
|
|
var minPixelValue = image.minPixelValue;
|
|
var canvasImageDataIndex = 0;
|
|
var storedPixelDataIndex = 0;
|
|
// Only 3 channels, since we use WebGL's RGB texture format
|
|
var numStoredPixels = image.width * image.height * 4;
|
|
var numOutputPixels = image.width * image.height * 3;
|
|
var storedPixelData = image.getPixelData();
|
|
var data = new Uint8Array(numOutputPixels);
|
|
|
|
// NOTE: As of Nov 2014, most javascript engines have lower performance when indexing negative indexes.
|
|
// We have a special code path for this case that improves performance. Thanks to @jpambrun for this enhancement
|
|
if (minPixelValue < 0){
|
|
while (storedPixelDataIndex < numStoredPixels) {
|
|
data[canvasImageDataIndex++] = storedPixelData[storedPixelDataIndex++] + (-minPixelValue); // red
|
|
data[canvasImageDataIndex++] = storedPixelData[storedPixelDataIndex++] + (-minPixelValue); // green
|
|
data[canvasImageDataIndex++] = storedPixelData[storedPixelDataIndex++] + (-minPixelValue); // blue
|
|
storedPixelDataIndex += 1; // The stored pixel data has 4 channels
|
|
}
|
|
} else {
|
|
while (storedPixelDataIndex < numStoredPixels) {
|
|
data[canvasImageDataIndex++] = storedPixelData[storedPixelDataIndex++]; // red
|
|
data[canvasImageDataIndex++] = storedPixelData[storedPixelDataIndex++]; // green
|
|
data[canvasImageDataIndex++] = storedPixelData[storedPixelDataIndex++]; // blue
|
|
storedPixelDataIndex += 1; // The stored pixel data has 4 channels
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
cornerstone.webGL.dataUtilities.rgb = {
|
|
storedPixelDataToImageData: storedPixelDataToImageData
|
|
};
|
|
|
|
shader.frag = 'precision mediump float;' +
|
|
'uniform sampler2D u_image;' +
|
|
'uniform float ww;' +
|
|
'uniform float wc;' +
|
|
'uniform float slope;' +
|
|
'uniform float intercept;' +
|
|
'uniform float minPixelValue;' +
|
|
'uniform int invert;' +
|
|
'varying vec2 v_texCoord;' +
|
|
|
|
'void main() {' +
|
|
|
|
// Get texture
|
|
'vec3 color = texture2D(u_image, v_texCoord).xyz;' +
|
|
|
|
// Rescale based on slope and intercept
|
|
'color = color * 256.0 * slope + intercept;' +
|
|
|
|
// Apply window settings
|
|
'float center0 = wc - 0.5 - minPixelValue;'+
|
|
'float width0 = ww - 1.0;'+
|
|
'color = (color - center0) / width0 + 0.5;'+
|
|
|
|
// RGBA output
|
|
'gl_FragColor = vec4(color, 1);' +
|
|
|
|
// Apply any inversion necessary
|
|
'if (invert == 1)' +
|
|
'gl_FragColor.rgb = 1. - gl_FragColor.rgb;' +
|
|
'}';
|
|
|
|
cornerstone.webGL.shaders.rgb = shader;
|
|
|
|
}(cornerstone));
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.shaders) {
|
|
cornerstone.webGL.shaders = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.dataUtilities) {
|
|
cornerstone.webGL.dataUtilities = {};
|
|
}
|
|
|
|
// For uint16 pack uint16 into two uint8 channels (r and a)
|
|
var shader = {};
|
|
|
|
function storedPixelDataToImageData(image) {
|
|
|
|
// Transfer image data to alpha and luminance channels of WebGL texture
|
|
// Credit to @jpambrun and @fernandojsg
|
|
|
|
// Pack uint16 into two uint8 channels (r and a)
|
|
var pixelData = image.getPixelData();
|
|
var numberOfChannels = 2;
|
|
var data = new Uint8Array(image.width * image.height * numberOfChannels);
|
|
var offset = 0;
|
|
|
|
for (var i = 0; i < pixelData.length; i++) {
|
|
var val = pixelData[i];
|
|
data[offset++] = parseInt(val & 0xFF, 10);
|
|
data[offset++] = parseInt(val >> 8, 10);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
cornerstone.webGL.dataUtilities.uint16 = {
|
|
storedPixelDataToImageData: storedPixelDataToImageData
|
|
};
|
|
|
|
shader.frag = 'precision mediump float;' +
|
|
'uniform sampler2D u_image;' +
|
|
'uniform float ww;' +
|
|
'uniform float wc;' +
|
|
'uniform float slope;' +
|
|
'uniform float intercept;' +
|
|
'uniform int invert;' +
|
|
'varying vec2 v_texCoord;' +
|
|
|
|
'void main() {' +
|
|
// Get texture
|
|
'vec4 color = texture2D(u_image, v_texCoord);' +
|
|
|
|
// Calculate luminance from packed texture
|
|
'float intensity = color.r*256.0 + color.a*65536.0;'+
|
|
|
|
// Rescale based on slope and window settings
|
|
'intensity = intensity * slope + intercept;'+
|
|
'float center0 = wc - 0.5;'+
|
|
'float width0 = ww - 1.0;'+
|
|
'intensity = (intensity - center0) / width0 + 0.5;'+
|
|
|
|
// Clamp intensity
|
|
'intensity = clamp(intensity, 0.0, 1.0);' +
|
|
|
|
// RGBA output
|
|
'gl_FragColor = vec4(intensity, intensity, intensity, 1.0);' +
|
|
|
|
// Apply any inversion necessary
|
|
'if (invert == 1)' +
|
|
'gl_FragColor.rgb = 1.0 - gl_FragColor.rgb;' +
|
|
'}';
|
|
|
|
cornerstone.webGL.shaders.uint16 = shader;
|
|
|
|
}(cornerstone));
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.shaders) {
|
|
cornerstone.webGL.shaders = {};
|
|
}
|
|
|
|
if (!cornerstone.webGL.dataUtilities) {
|
|
cornerstone.webGL.dataUtilities = {};
|
|
}
|
|
|
|
var shader = {};
|
|
|
|
function storedPixelDataToImageData(image) {
|
|
// Transfer image data to alpha channel of WebGL texture
|
|
// Store data in Uuint8Array
|
|
var pixelData = image.getPixelData();
|
|
var data = new Uint8Array(pixelData.length);
|
|
for (var i = 0; i < pixelData.length; i++) {
|
|
data[i] = parseInt(pixelData[i], 10);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
cornerstone.webGL.dataUtilities.uint8 = {
|
|
storedPixelDataToImageData: storedPixelDataToImageData
|
|
};
|
|
|
|
shader.frag = 'precision mediump float;' +
|
|
'uniform sampler2D u_image;' +
|
|
'uniform float ww;' +
|
|
'uniform float wc;' +
|
|
'uniform float slope;' +
|
|
'uniform float intercept;' +
|
|
//'uniform float minPixelValue;' +
|
|
'uniform int invert;' +
|
|
'varying vec2 v_texCoord;' +
|
|
|
|
'void main() {' +
|
|
// Get texture
|
|
'vec4 color = texture2D(u_image, v_texCoord);' +
|
|
|
|
// Calculate luminance from packed texture
|
|
'float intensity = color.r*256.0;'+
|
|
|
|
// Rescale based on slope and window settings
|
|
'intensity = intensity * slope + intercept;'+
|
|
'float center0 = wc - 0.5;'+
|
|
'float width0 = max(ww, 1.0);' +
|
|
'intensity = (intensity - center0) / width0 + 0.5;'+
|
|
|
|
// Clamp intensity
|
|
'intensity = clamp(intensity, 0.0, 1.0);' +
|
|
|
|
// RGBA output
|
|
'gl_FragColor = vec4(intensity, intensity, intensity, 1.0);' +
|
|
|
|
// Apply any inversion necessary
|
|
'if (invert == 1)' +
|
|
'gl_FragColor.rgb = 1.0 - gl_FragColor.rgb;' +
|
|
'}';
|
|
|
|
cornerstone.webGL.shaders.uint8 = shader;
|
|
|
|
|
|
}(cornerstone));
|
|
/**
|
|
* This module deals with caching image textures in VRAM for WebGL
|
|
*/
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
var imageCache = {};
|
|
|
|
var cachedImages = [];
|
|
|
|
var maximumSizeInBytes = 1024 * 1024 * 256; // 256 MB
|
|
var cacheSizeInBytes = 0;
|
|
|
|
function setMaximumSizeBytes(numBytes) {
|
|
if (numBytes === undefined) {
|
|
throw "setMaximumSizeBytes: parameter numBytes must not be undefined";
|
|
}
|
|
if (numBytes.toFixed === undefined) {
|
|
throw "setMaximumSizeBytes: parameter numBytes must be a number";
|
|
}
|
|
|
|
maximumSizeInBytes = numBytes;
|
|
purgeCacheIfNecessary();
|
|
}
|
|
|
|
function purgeCacheIfNecessary() {
|
|
// if max cache size has not been exceeded, do nothing
|
|
if (cacheSizeInBytes <= maximumSizeInBytes) {
|
|
return;
|
|
}
|
|
|
|
// cache size has been exceeded, create list of images sorted by timeStamp
|
|
// so we can purge the least recently used image
|
|
function compare(a,b) {
|
|
if (a.timeStamp > b.timeStamp) {
|
|
return -1;
|
|
}
|
|
if (a.timeStamp < b.timeStamp) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
cachedImages.sort(compare);
|
|
|
|
// remove images as necessary
|
|
while(cacheSizeInBytes > maximumSizeInBytes) {
|
|
var lastCachedImage = cachedImages[cachedImages.length - 1];
|
|
cacheSizeInBytes -= lastCachedImage.sizeInBytes;
|
|
delete imageCache[lastCachedImage.imageId];
|
|
cachedImages.pop();
|
|
$(cornerstone).trigger('CornerstoneWebGLTextureRemoved', {imageId: lastCachedImage.imageId});
|
|
}
|
|
|
|
var cacheInfo = cornerstone.imageCache.getCacheInfo();
|
|
console.log('CornerstoneWebGLTextureCacheFull');
|
|
$(cornerstone).trigger('CornerstoneWebGLTextureCacheFull', cacheInfo);
|
|
}
|
|
|
|
function putImageTexture(image, imageTexture) {
|
|
var imageId = image.imageId;
|
|
if (image === undefined) {
|
|
throw "putImageTexture: image must not be undefined";
|
|
}
|
|
|
|
if (imageId === undefined) {
|
|
throw "putImageTexture: imageId must not be undefined";
|
|
}
|
|
|
|
if (imageTexture === undefined) {
|
|
throw "putImageTexture: imageTexture must not be undefined";
|
|
}
|
|
|
|
if (imageCache.hasOwnProperty(imageId) === true) {
|
|
throw "putImageTexture: imageId already in cache";
|
|
}
|
|
|
|
var cachedImage = {
|
|
imageId : imageId,
|
|
imageTexture : imageTexture,
|
|
timeStamp : new Date(),
|
|
sizeInBytes: imageTexture.sizeInBytes
|
|
};
|
|
|
|
imageCache[imageId] = cachedImage;
|
|
cachedImages.push(cachedImage);
|
|
|
|
if (imageTexture.sizeInBytes === undefined) {
|
|
throw "putImageTexture: imageTexture does not have sizeInBytes property or";
|
|
}
|
|
if (imageTexture.sizeInBytes.toFixed === undefined) {
|
|
throw "putImageTexture: imageTexture.sizeInBytes is not a number";
|
|
}
|
|
cacheSizeInBytes += cachedImage.sizeInBytes;
|
|
purgeCacheIfNecessary();
|
|
}
|
|
|
|
function getImageTexture(imageId) {
|
|
if (imageId === undefined) {
|
|
throw "getImageTexture: imageId must not be undefined";
|
|
}
|
|
var cachedImage = imageCache[imageId];
|
|
if (cachedImage === undefined) {
|
|
return undefined;
|
|
}
|
|
|
|
// bump time stamp for cached image
|
|
cachedImage.timeStamp = new Date();
|
|
return cachedImage.imageTexture;
|
|
}
|
|
|
|
function removeImageTexture(imageId) {
|
|
if (imageId === undefined) {
|
|
throw "removeImageTexture: imageId must not be undefined";
|
|
}
|
|
var cachedImage = imageCache[imageId];
|
|
if (cachedImage === undefined) {
|
|
throw "removeImageTexture: imageId must not be undefined";
|
|
}
|
|
cachedImages.splice( cachedImages.indexOf(cachedImage), 1);
|
|
cacheSizeInBytes -= cachedImage.sizeInBytes;
|
|
delete imageCache[imageId];
|
|
|
|
return cachedImage.imageTexture;
|
|
}
|
|
|
|
function getCacheInfo() {
|
|
return {
|
|
maximumSizeInBytes : maximumSizeInBytes,
|
|
cacheSizeInBytes : cacheSizeInBytes,
|
|
numberOfImagesCached: cachedImages.length
|
|
};
|
|
}
|
|
|
|
function purgeCache() {
|
|
while (cachedImages.length > 0) {
|
|
var removedCachedImage = cachedImages.pop();
|
|
delete imageCache[removedCachedImage.imageId];
|
|
}
|
|
cacheSizeInBytes = 0;
|
|
}
|
|
|
|
// module exports
|
|
cornerstone.webGL.textureCache = {
|
|
putImageTexture : putImageTexture,
|
|
getImageTexture: getImageTexture,
|
|
removeImageTexture: removeImageTexture,
|
|
setMaximumSizeBytes: setMaximumSizeBytes,
|
|
getCacheInfo : getCacheInfo,
|
|
purgeCache: purgeCache,
|
|
cachedImages: cachedImages
|
|
};
|
|
|
|
}(cornerstone));
|
|
|
|
(function (cornerstone) {
|
|
|
|
"use strict";
|
|
|
|
if (!cornerstone.webGL) {
|
|
cornerstone.webGL = {};
|
|
}
|
|
|
|
cornerstone.webGL.vertexShader = 'attribute vec2 a_position;' +
|
|
'attribute vec2 a_texCoord;' +
|
|
'uniform vec2 u_resolution;' +
|
|
'varying vec2 v_texCoord;' +
|
|
'void main() {' +
|
|
'vec2 zeroToOne = a_position / u_resolution;' +
|
|
'vec2 zeroToTwo = zeroToOne * 2.0;' +
|
|
'vec2 clipSpace = zeroToTwo - 1.0;' +
|
|
'gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);' +
|
|
'v_texCoord = a_texCoord;' +
|
|
'}';
|
|
|
|
}(cornerstone)); |