Integrate web workers and the latest cornerstone libraries
- Upgrade cornerstone to v0.10.1 - Upgrade cornerstoneTools to v0.8.1 - Upgrade cornerstoneWADOImageLoader to v0.14.1 - Upgrade dicomParser to v1.7.4 - Upgrade hammerjs to v2.0.8
This commit is contained in:
parent
8fa437b2c2
commit
33f9b8672b
21
LesionTracker/client/config.js
Normal file
21
LesionTracker/client/config.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { cornerstoneWADOImageLoader } from 'meteor/ohif:cornerstone';
|
||||
|
||||
Meteor.startup(function() {
|
||||
const maxWebWorkers = Math.max(navigator.hardwareConcurrency - 1, 1);
|
||||
const config = {
|
||||
maxWebWorkers: maxWebWorkers,
|
||||
startWebWorkersOnDemand: true,
|
||||
webWorkerPath : Meteor.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderWebWorker.es5.js'),
|
||||
taskConfiguration: {
|
||||
'decodeTask' : {
|
||||
loadCodecsOnStartup : true,
|
||||
initializeCodecsOnStartup: false,
|
||||
codecsPath: Meteor.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderCodecs.es5.js'),
|
||||
usePDFJS: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
|
||||
});
|
||||
21
OHIFViewer/client/config.js
Normal file
21
OHIFViewer/client/config.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { cornerstoneWADOImageLoader } from 'meteor/ohif:cornerstone';
|
||||
|
||||
Meteor.startup(function() {
|
||||
const maxWebWorkers = Math.max(navigator.hardwareConcurrency - 1, 1);
|
||||
const config = {
|
||||
maxWebWorkers: maxWebWorkers,
|
||||
startWebWorkersOnDemand: true,
|
||||
webWorkerPath : Meteor.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderWebWorker.es5.js'),
|
||||
taskConfiguration: {
|
||||
'decodeTask' : {
|
||||
loadCodecsOnStartup : true,
|
||||
initializeCodecsOnStartup: false,
|
||||
codecsPath: Meteor.absoluteUrl('packages/ohif_cornerstone/public/js/cornerstoneWADOImageLoaderCodecs.es5.js'),
|
||||
usePDFJS: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
/*! cornerstoneTools - v0.7.9 - 2016-10-29 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
|
||||
/*! cornerstoneTools - v0.8.1 - 2017-02-11 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
|
||||
// Begin Source: src/header.js
|
||||
if (typeof cornerstone === 'undefined') {
|
||||
cornerstone = {};
|
||||
@ -22,70 +22,81 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
|
||||
'use strict';
|
||||
|
||||
var scrollTimeout;
|
||||
var scrollTimeoutDelay = 1;
|
||||
|
||||
function mouseWheel(e) {
|
||||
clearTimeout(scrollTimeout);
|
||||
// !!!HACK/NOTE/WARNING!!!
|
||||
// for some reason I am getting mousewheel and DOMMouseScroll events on my
|
||||
// mac os x mavericks system when middle mouse button dragging.
|
||||
// I couldn't find any info about this so this might break other systems
|
||||
// webkit hack
|
||||
if (e.originalEvent.type === 'mousewheel' && e.originalEvent.wheelDeltaY === 0) {
|
||||
return;
|
||||
}
|
||||
// firefox hack
|
||||
if (e.originalEvent.type === 'DOMMouseScroll' && e.originalEvent.axis === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollTimeout = setTimeout(function() {
|
||||
var element = e.target.parentNode;
|
||||
var element = e.currentTarget;
|
||||
|
||||
if (!e.deltaY) {
|
||||
return;
|
||||
}
|
||||
var x;
|
||||
var y;
|
||||
|
||||
var x;
|
||||
var y;
|
||||
if (e.pageX !== undefined && e.pageY !== undefined) {
|
||||
x = e.pageX;
|
||||
y = e.pageY;
|
||||
}
|
||||
if (e.pageX !== undefined && e.pageY !== undefined) {
|
||||
x = e.pageX;
|
||||
y = e.pageY;
|
||||
} else if (e.originalEvent &&
|
||||
e.originalEvent.pageX !== undefined &&
|
||||
e.originalEvent.pageY !== undefined) {
|
||||
x = e.originalEvent.pageX;
|
||||
y = e.originalEvent.pageY;
|
||||
} else {
|
||||
// IE9 & IE10
|
||||
x = e.x;
|
||||
y = e.y;
|
||||
}
|
||||
|
||||
var startingCoords = cornerstone.pageToPixel(element, x, y);
|
||||
var startingCoords = cornerstone.pageToPixel(element, x, y);
|
||||
|
||||
var wheelDeltaPixels;
|
||||
var pixelsPerLine = 40;
|
||||
var pixelsPerPage = 800;
|
||||
e = window.event || e; // old IE support
|
||||
|
||||
if (e.deltaMode === 2) {
|
||||
// DeltaY is in Pages
|
||||
wheelDeltaPixels = e.deltaY * pixelsPerPage;
|
||||
} else if (e.deltaMode === 1) {
|
||||
// DeltaY is in Lines
|
||||
wheelDeltaPixels = e.deltaY * pixelsPerLine;
|
||||
} else {
|
||||
// DeltaY is already in Pixels
|
||||
wheelDeltaPixels = e.deltaY;
|
||||
}
|
||||
var wheelDelta;
|
||||
if (e.originalEvent && e.originalEvent.wheelDelta) {
|
||||
wheelDelta = -e.originalEvent.wheelDelta;
|
||||
} else if (e.originalEvent && e.originalEvent.deltaY) {
|
||||
wheelDelta = -e.originalEvent.deltaY;
|
||||
} else if (e.originalEvent && e.originalEvent.detail) {
|
||||
wheelDelta = -e.originalEvent.detail;
|
||||
} else {
|
||||
wheelDelta = e.wheelDelta;
|
||||
}
|
||||
|
||||
var direction = e.deltaY < 0 ? -1 : 1;
|
||||
var direction = wheelDelta < 0 ? -1 : 1;
|
||||
|
||||
var mouseWheelData = {
|
||||
element: element,
|
||||
viewport: cornerstone.getViewport(element),
|
||||
image: cornerstone.getEnabledElement(element).image,
|
||||
direction: direction,
|
||||
wheelDeltaPixels: wheelDeltaPixels,
|
||||
pageX: x,
|
||||
pageY: y,
|
||||
imageX: startingCoords.x,
|
||||
imageY: startingCoords.y
|
||||
};
|
||||
var mouseWheelData = {
|
||||
element: element,
|
||||
viewport: cornerstone.getViewport(element),
|
||||
image: cornerstone.getEnabledElement(element).image,
|
||||
direction: direction,
|
||||
pageX: x,
|
||||
pageY: y,
|
||||
imageX: startingCoords.x,
|
||||
imageY: startingCoords.y
|
||||
};
|
||||
|
||||
$(element).trigger('CornerstoneToolsMouseWheel', mouseWheelData);
|
||||
}, scrollTimeoutDelay);
|
||||
$(element).trigger('CornerstoneToolsMouseWheel', mouseWheelData);
|
||||
}
|
||||
|
||||
var mouseWheelEvents = 'mousewheel DOMMouseScroll';
|
||||
|
||||
function enable(element) {
|
||||
// Prevent handlers from being attached multiple times
|
||||
disable(element);
|
||||
|
||||
cornerstoneTools.addWheelListener(element, mouseWheel);
|
||||
$(element).on(mouseWheelEvents, mouseWheel);
|
||||
}
|
||||
|
||||
function disable(element) {
|
||||
cornerstoneTools.removeWheelListener(element, mouseWheel);
|
||||
$(element).unbind(mouseWheelEvents, mouseWheel);
|
||||
}
|
||||
|
||||
// module exports
|
||||
@ -1440,8 +1451,6 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
'use strict';
|
||||
|
||||
function mouseWheelTool(mouseWheelCallback) {
|
||||
var configuration = {};
|
||||
|
||||
var toolInterface = {
|
||||
activate: function(element) {
|
||||
$(element).off('CornerstoneToolsMouseWheel', mouseWheelCallback);
|
||||
@ -1451,9 +1460,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
},
|
||||
disable: function(element) {$(element).off('CornerstoneToolsMouseWheel', mouseWheelCallback);},
|
||||
enable: function(element) {$(element).off('CornerstoneToolsMouseWheel', mouseWheelCallback);},
|
||||
deactivate: function(element) {$(element).off('CornerstoneToolsMouseWheel', mouseWheelCallback);},
|
||||
getConfiguration: function() { return configuration;},
|
||||
setConfiguration: function(config) {configuration = config;}
|
||||
deactivate: function(element) {$(element).off('CornerstoneToolsMouseWheel', mouseWheelCallback);}
|
||||
};
|
||||
return toolInterface;
|
||||
}
|
||||
@ -4747,8 +4754,18 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
}
|
||||
|
||||
function dragCallback(e, eventData) {
|
||||
eventData.viewport.translation.x += (eventData.deltaPoints.page.x / eventData.viewport.scale);
|
||||
eventData.viewport.translation.y += (eventData.deltaPoints.page.y / eventData.viewport.scale);
|
||||
|
||||
// FIXME: Copied from Cornerstone src/internal/calculateTransform.js, should be exposed from there.
|
||||
var widthScale = eventData.viewport.scale;
|
||||
var heightScale = eventData.viewport.scale;
|
||||
if (eventData.image.rowPixelSpacing < eventData.image.columnPixelSpacing) {
|
||||
widthScale = widthScale * (eventData.image.columnPixelSpacing / eventData.image.rowPixelSpacing);
|
||||
} else if (eventData.image.columnPixelSpacing < eventData.image.rowPixelSpacing) {
|
||||
heightScale = heightScale * (eventData.image.rowPixelSpacing / eventData.image.columnPixelSpacing);
|
||||
}
|
||||
|
||||
eventData.viewport.translation.x += (eventData.deltaPoints.page.x / widthScale);
|
||||
eventData.viewport.translation.y += (eventData.deltaPoints.page.y / heightScale);
|
||||
cornerstone.setViewport(eventData.element, eventData.viewport);
|
||||
return false; // false = causes jquery to preventDefault() and stopPropagation() this event
|
||||
}
|
||||
@ -4926,6 +4943,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
var measurementData = {
|
||||
visible: true,
|
||||
active: true,
|
||||
invalidated: true,
|
||||
handles: {
|
||||
start: {
|
||||
x: mouseEventData.currentPoints.image.x,
|
||||
@ -4938,6 +4956,14 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
y: mouseEventData.currentPoints.image.y,
|
||||
highlight: true,
|
||||
active: true
|
||||
},
|
||||
textBox: {
|
||||
active: false,
|
||||
hasMoved: false,
|
||||
movesIndependently: false,
|
||||
drawnIndependently: true,
|
||||
allowedOutsideImage: true,
|
||||
hasBoundingBox: true
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -5000,25 +5026,35 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
};
|
||||
}
|
||||
|
||||
function onImageRendered(e, eventData) {
|
||||
function numberWithCommas(x) {
|
||||
// http://stackoverflow.com/questions/2901102/how-to-print-a-number-with-commas-as-thousands-separators-in-javascript
|
||||
var parts = x.toString().split('.');
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
return parts.join('.');
|
||||
}
|
||||
|
||||
function onImageRendered(e, eventData) {
|
||||
// if we have no toolData for this element, return immediately as there is nothing to do
|
||||
var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
|
||||
if (toolData === undefined) {
|
||||
if (!toolData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we have tool data for this element - iterate over each one and draw it
|
||||
var image = eventData.image;
|
||||
var element = eventData.element;
|
||||
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
|
||||
var config = cornerstoneTools.rectangleRoi.getConfiguration();
|
||||
var context = eventData.canvasContext.canvas.getContext('2d');
|
||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||
|
||||
//activation color
|
||||
var color;
|
||||
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
|
||||
var font = cornerstoneTools.textStyle.getFont();
|
||||
var fontHeight = cornerstoneTools.textStyle.getFontSize();
|
||||
var config = cornerstoneTools.rectangleRoi.getConfiguration();
|
||||
// Retrieve the image modality from its metadata, if available
|
||||
var modalityTag = 'x00080060';
|
||||
var modality;
|
||||
if (image.data) {
|
||||
modality = image.data.string(modalityTag);
|
||||
}
|
||||
|
||||
// If we have tool data for this element - iterate over each set and draw it
|
||||
for (var i = 0; i < toolData.data.length; i++) {
|
||||
context.save();
|
||||
|
||||
@ -5031,65 +5067,261 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
context.shadowOffsetY = config.shadowOffsetY || 1;
|
||||
}
|
||||
|
||||
//differentiate the color of activation tool
|
||||
if (data.active) {
|
||||
color = cornerstoneTools.toolColors.getActiveColor();
|
||||
} else {
|
||||
color = cornerstoneTools.toolColors.getToolColor();
|
||||
}
|
||||
// Check which color the rendered tool should be
|
||||
var color = cornerstoneTools.toolColors.getColorIfActive(data.active);
|
||||
|
||||
// draw the rectangle
|
||||
var handleStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.start);
|
||||
var handleEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.end);
|
||||
// Convert Image coordinates to Canvas coordinates given the element
|
||||
var handleStartCanvas = cornerstone.pixelToCanvas(element, data.handles.start);
|
||||
var handleEndCanvas = cornerstone.pixelToCanvas(element, data.handles.end);
|
||||
|
||||
var widthCanvas = Math.abs(handleStartCanvas.x - handleEndCanvas.x);
|
||||
var heightCanvas = Math.abs(handleStartCanvas.y - handleEndCanvas.y);
|
||||
// Retrieve the bounds of the ellipse (left, top, width, and height)
|
||||
// in Canvas coordinates
|
||||
var leftCanvas = Math.min(handleStartCanvas.x, handleEndCanvas.x);
|
||||
var topCanvas = Math.min(handleStartCanvas.y, handleEndCanvas.y);
|
||||
var centerX = (handleStartCanvas.x + handleEndCanvas.x) / 2;
|
||||
var centerY = (handleStartCanvas.y + handleEndCanvas.y) / 2;
|
||||
var widthCanvas = Math.abs(handleStartCanvas.x - handleEndCanvas.x);
|
||||
var heightCanvas = Math.abs(handleStartCanvas.y - handleEndCanvas.y);
|
||||
|
||||
// Draw the rectangle on the canvas
|
||||
context.beginPath();
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = lineWidth;
|
||||
context.rect(leftCanvas, topCanvas, widthCanvas, heightCanvas);
|
||||
context.stroke();
|
||||
|
||||
// draw the handles
|
||||
cornerstoneTools.drawHandles(context, eventData, data.handles, color);
|
||||
// If the tool configuration specifies to only draw the handles on hover / active,
|
||||
// follow this logic
|
||||
if (config && config.drawHandlesOnHover) {
|
||||
// Draw the handles if the tool is active
|
||||
if (data.active === true) {
|
||||
cornerstoneTools.drawHandles(context, eventData, data.handles, color);
|
||||
} else {
|
||||
// If the tool is inactive, draw the handles only if each specific handle is being
|
||||
// hovered over
|
||||
var handleOptions = {
|
||||
drawHandlesIfActive: true
|
||||
};
|
||||
cornerstoneTools.drawHandles(context, eventData, data.handles, color, handleOptions);
|
||||
}
|
||||
} else {
|
||||
// If the tool has no configuration settings, always draw the handles
|
||||
cornerstoneTools.drawHandles(context, eventData, data.handles, color);
|
||||
}
|
||||
|
||||
// Calculate the mean, stddev, and area
|
||||
// TODO: calculate this in web worker for large pixel counts...
|
||||
// Define variables for the area and mean/standard deviation
|
||||
var area,
|
||||
meanStdDev,
|
||||
meanStdDevSUV;
|
||||
|
||||
var width = Math.abs(data.handles.start.x - data.handles.end.x);
|
||||
var height = Math.abs(data.handles.start.y - data.handles.end.y);
|
||||
var left = Math.min(data.handles.start.x, data.handles.end.x);
|
||||
var top = Math.min(data.handles.start.y, data.handles.end.y);
|
||||
var pixels = cornerstone.getPixels(eventData.element, left, top, width, height);
|
||||
// Perform a check to see if the tool has been invalidated. This is to prevent
|
||||
// unnecessary re-calculation of the area, mean, and standard deviation if the
|
||||
// image is re-rendered but the tool has not moved (e.g. during a zoom)
|
||||
if (!data.invalidated) {
|
||||
// If the data is not invalidated, retrieve it from the toolData
|
||||
meanStdDev = data.meanStdDev;
|
||||
meanStdDevSUV = data.meanStdDevSUV;
|
||||
area = data.area;
|
||||
} else {
|
||||
// If the data has been invalidated, we need to calculate it again
|
||||
|
||||
var ellipse = {
|
||||
left: left,
|
||||
top: top,
|
||||
width: width,
|
||||
height: height
|
||||
// Retrieve the bounds of the ellipse in image coordinates
|
||||
var ellipse = {
|
||||
left: Math.min(data.handles.start.x, data.handles.end.x),
|
||||
top: Math.min(data.handles.start.y, data.handles.end.y),
|
||||
width: Math.abs(data.handles.start.x - data.handles.end.x),
|
||||
height: Math.abs(data.handles.start.y - data.handles.end.y)
|
||||
};
|
||||
|
||||
// First, make sure this is not a color image, since no mean / standard
|
||||
// deviation will be calculated for color images.
|
||||
if (!image.color) {
|
||||
// Retrieve the array of pixels that the ellipse bounds cover
|
||||
var pixels = cornerstone.getPixels(element, ellipse.left, ellipse.top, ellipse.width, ellipse.height);
|
||||
|
||||
// Calculate the mean & standard deviation from the pixels and the ellipse details
|
||||
meanStdDev = calculateMeanStdDev(pixels, ellipse);
|
||||
|
||||
if (modality === 'PT') {
|
||||
// If the image is from a PET scan, use the DICOM tags to
|
||||
// calculate the SUV from the mean and standard deviation.
|
||||
|
||||
// Note that because we are using modality pixel values from getPixels, and
|
||||
// the calculateSUV routine also rescales to modality pixel values, we are first
|
||||
// returning the values to storedPixel values before calcuating SUV with them.
|
||||
// TODO: Clean this up? Should we add an option to not scale in calculateSUV?
|
||||
meanStdDevSUV = {
|
||||
mean: cornerstoneTools.calculateSUV(image, (meanStdDev.mean - image.intercept) / image.slope),
|
||||
stdDev: cornerstoneTools.calculateSUV(image, (meanStdDev.stdDev - image.intercept) / image.slope)
|
||||
};
|
||||
}
|
||||
|
||||
// If the mean and standard deviation values are sane, store them for later retrieval
|
||||
if (meanStdDev && !isNaN(meanStdDev.mean)) {
|
||||
data.meanStdDev = meanStdDev;
|
||||
data.meanStdDevSUV = meanStdDevSUV;
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the pixel spacing values, and if they are not
|
||||
// real non-zero values, set them to 1
|
||||
var columnPixelSpacing = image.columnPixelSpacing || 1;
|
||||
var rowPixelSpacing = image.rowPixelSpacing || 1;
|
||||
|
||||
// Calculate the image area from the ellipse dimensions and pixel spacing
|
||||
area = (ellipse.width * columnPixelSpacing) * (ellipse.height * rowPixelSpacing);
|
||||
|
||||
// If the area value is sane, store it for later retrieval
|
||||
if (!isNaN(area)) {
|
||||
data.area = area;
|
||||
}
|
||||
|
||||
// Set the invalidated flag to false so that this data won't automatically be recalculated
|
||||
data.invalidated = false;
|
||||
}
|
||||
|
||||
// Define an array to store the rows of text for the textbox
|
||||
var textLines = [];
|
||||
|
||||
// If the mean and standard deviation values are present, display them
|
||||
if (meanStdDev && meanStdDev.mean) {
|
||||
// If the modality is CT, add HU to denote Hounsfield Units
|
||||
var moSuffix = '';
|
||||
if (modality === 'CT') {
|
||||
moSuffix = ' HU';
|
||||
}
|
||||
|
||||
// Create a line of text to display the mean and any units that were specified (i.e. HU)
|
||||
var meanText = 'Mean: ' + numberWithCommas(meanStdDev.mean.toFixed(2)) + moSuffix;
|
||||
// Create a line of text to display the standard deviation and any units that were specified (i.e. HU)
|
||||
var stdDevText = 'StdDev: ' + numberWithCommas(meanStdDev.stdDev.toFixed(2)) + moSuffix;
|
||||
|
||||
// If this image has SUV values to display, concatenate them to the text line
|
||||
if (meanStdDevSUV && meanStdDevSUV.mean !== undefined) {
|
||||
var SUVtext = ' SUV: ';
|
||||
meanText += SUVtext + numberWithCommas(meanStdDevSUV.mean.toFixed(2));
|
||||
stdDevText += SUVtext + numberWithCommas(meanStdDevSUV.stdDev.toFixed(2));
|
||||
}
|
||||
|
||||
// Add these text lines to the array to be displayed in the textbox
|
||||
textLines.push(meanText);
|
||||
textLines.push(stdDevText);
|
||||
}
|
||||
|
||||
// If the area is a sane value, display it
|
||||
if (area) {
|
||||
// Determine the area suffix based on the pixel spacing in the image.
|
||||
// If pixel spacing is present, use millimeters. Otherwise, use pixels.
|
||||
// This uses Char code 178 for a superscript 2
|
||||
var suffix = ' mm' + String.fromCharCode(178);
|
||||
if (!image.rowPixelSpacing || !image.columnPixelSpacing) {
|
||||
suffix = ' pixels' + String.fromCharCode(178);
|
||||
}
|
||||
|
||||
// Create a line of text to display the area and its units
|
||||
var areaText = 'Area: ' + numberWithCommas(area.toFixed(2)) + suffix;
|
||||
|
||||
// Add this text line to the array to be displayed in the textbox
|
||||
textLines.push(areaText);
|
||||
}
|
||||
|
||||
// If the textbox has not been moved by the user, it should be displayed on the right-most
|
||||
// side of the tool.
|
||||
if (!data.handles.textBox.hasMoved) {
|
||||
// Find the rightmost side of the ellipse at its vertical center, and place the textbox here
|
||||
// Note that this calculates it in image coordinates
|
||||
data.handles.textBox.x = Math.max(data.handles.start.x, data.handles.end.x);
|
||||
data.handles.textBox.y = (data.handles.start.y + data.handles.end.y) / 2;
|
||||
}
|
||||
|
||||
// Convert the textbox Image coordinates into Canvas coordinates
|
||||
var textCoords = cornerstone.pixelToCanvas(element, data.handles.textBox);
|
||||
|
||||
// Set options for the textbox drawing function
|
||||
var options = {
|
||||
centering: {
|
||||
x: false,
|
||||
y: true
|
||||
}
|
||||
};
|
||||
|
||||
var meanStdDev = calculateMeanStdDev(pixels, ellipse);
|
||||
var area = (width * eventData.image.columnPixelSpacing) * (height * eventData.image.rowPixelSpacing);
|
||||
var areaText = 'Area: ' + area.toFixed(2) + ' mm^2';
|
||||
// Draw the textbox and retrieves it's bounding box for mouse-dragging and highlighting
|
||||
var boundingBox = cornerstoneTools.drawTextBox(context, textLines, textCoords.x,
|
||||
textCoords.y, color, options);
|
||||
|
||||
// Draw text
|
||||
context.font = font;
|
||||
// Store the bounding box data in the handle for mouse-dragging and highlighting
|
||||
data.handles.textBox.boundingBox = boundingBox;
|
||||
|
||||
var textSize = context.measureText(area);
|
||||
// If the textbox has moved, we would like to draw a line linking it with the tool
|
||||
// This section decides where to draw this line to on the Ellipse based on the location
|
||||
// of the textbox relative to the ellipse.
|
||||
if (data.handles.textBox.hasMoved) {
|
||||
// Draw dashed link line between tool and text
|
||||
|
||||
var textX = centerX < (eventData.image.columns / 2) ? centerX + (widthCanvas / 2): centerX - (widthCanvas / 2) - textSize.width;
|
||||
var textY = centerY < (eventData.image.rows / 2) ? centerY + (heightCanvas / 2): centerY - (heightCanvas / 2);
|
||||
// The initial link position is at the center of the
|
||||
// textbox.
|
||||
var link = {
|
||||
start: {},
|
||||
end: {
|
||||
x: textCoords.x,
|
||||
y: textCoords.y
|
||||
}
|
||||
};
|
||||
|
||||
// First we calculate the ellipse points (top, left, right, and bottom)
|
||||
var ellipsePoints = [ {
|
||||
// Top middle point of ellipse
|
||||
x: leftCanvas + widthCanvas / 2,
|
||||
y: topCanvas
|
||||
}, {
|
||||
// Left middle point of ellipse
|
||||
x: leftCanvas,
|
||||
y: topCanvas + heightCanvas / 2
|
||||
}, {
|
||||
// Bottom middle point of ellipse
|
||||
x: leftCanvas + widthCanvas / 2,
|
||||
y: topCanvas + heightCanvas
|
||||
}, {
|
||||
// Right middle point of ellipse
|
||||
x: leftCanvas + widthCanvas,
|
||||
y: topCanvas + heightCanvas / 2
|
||||
} ];
|
||||
|
||||
// We obtain the link starting point by finding the closest point on the ellipse to the
|
||||
// center of the textbox
|
||||
link.start = cornerstoneMath.point.findClosestPoint(ellipsePoints, link.end);
|
||||
|
||||
// Next we calculate the corners of the textbox bounding box
|
||||
var boundingBoxPoints = [ {
|
||||
// Top middle point of bounding box
|
||||
x: boundingBox.left + boundingBox.width / 2,
|
||||
y: boundingBox.top
|
||||
}, {
|
||||
// Left middle point of bounding box
|
||||
x: boundingBox.left,
|
||||
y: boundingBox.top + boundingBox.height / 2
|
||||
}, {
|
||||
// Bottom middle point of bounding box
|
||||
x: boundingBox.left + boundingBox.width / 2,
|
||||
y: boundingBox.top + boundingBox.height
|
||||
}, {
|
||||
// Right middle point of bounding box
|
||||
x: boundingBox.left + boundingBox.width,
|
||||
y: boundingBox.top + boundingBox.height / 2
|
||||
}, ];
|
||||
|
||||
// Now we recalculate the link endpoint by identifying which corner of the bounding box
|
||||
// is closest to the start point we just calculated.
|
||||
link.end = cornerstoneMath.point.findClosestPoint(boundingBoxPoints, link.start);
|
||||
|
||||
// Finally we draw the dashed linking line
|
||||
context.beginPath();
|
||||
context.strokeStyle = color;
|
||||
context.lineWidth = lineWidth;
|
||||
context.setLineDash([ 2, 3 ]);
|
||||
context.moveTo(link.start.x, link.start.y);
|
||||
context.lineTo(link.end.x, link.end.y);
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
context.fillStyle = color;
|
||||
cornerstoneTools.drawTextBox(context, 'Mean: ' + meanStdDev.mean.toFixed(2), textX, textY - fontHeight - 5, color);
|
||||
cornerstoneTools.drawTextBox(context, 'StdDev: ' + meanStdDev.stdDev.toFixed(2), textX, textY, color);
|
||||
cornerstoneTools.drawTextBox(context, areaText, textX, textY + fontHeight + 5, color);
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
@ -8025,43 +8257,8 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
|
||||
'use strict';
|
||||
|
||||
// this module defines a way for tools to access various metadata about an imageId. This layer of abstraction exists
|
||||
// so metadata can be provided to the tools in different ways (e.g. by parsing DICOM P10 or by a WADO-RS document)
|
||||
// NOTE: We may want to push this function down into the cornerstone core library, not sure yet...
|
||||
|
||||
var providers = [];
|
||||
|
||||
function addProvider( provider) {
|
||||
providers.push(provider);
|
||||
}
|
||||
|
||||
function removeProvider( provider) {
|
||||
var index = providers.indexOf(provider);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
providers.splice(index, 1);
|
||||
}
|
||||
|
||||
function getMetaData(type, imageId) {
|
||||
var result;
|
||||
$.each(providers, function(index, provider) {
|
||||
result = provider(type, imageId);
|
||||
if (result !== undefined) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// module/private exports
|
||||
cornerstoneTools.metaData = {
|
||||
addProvider: addProvider,
|
||||
removeProvider: removeProvider,
|
||||
get: getMetaData
|
||||
};
|
||||
|
||||
cornerstoneTools.metaData = cornerstone.metaData;
|
||||
})($, cornerstone, cornerstoneTools);
|
||||
|
||||
// End Source; src/metaData.js
|
||||
@ -8379,12 +8576,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
}
|
||||
|
||||
setTimeout(function() {
|
||||
var requestDetails = getNextRequest();
|
||||
if (!requestDetails) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendRequest(requestDetails);
|
||||
startGrabbing();
|
||||
}, grabDelay);
|
||||
}
|
||||
|
||||
@ -8418,11 +8610,29 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
function requestTypeToLoadPriority(requestDetails) {
|
||||
if (requestDetails.type === 'prefetch') {
|
||||
return -5;
|
||||
} else if (requestDetails.type === 'interactive') {
|
||||
return 0;
|
||||
} else if (requestDetails.type === 'thumbnail') {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
var priority = requestTypeToLoadPriority(requestDetails);
|
||||
|
||||
var loader;
|
||||
if (requestDetails.preventCache === true) {
|
||||
loader = cornerstone.loadImage(imageId);
|
||||
loader = cornerstone.loadImage(imageId, {
|
||||
priority: priority,
|
||||
type: requestDetails.type
|
||||
});
|
||||
} else {
|
||||
loader = cornerstone.loadAndCacheImage(imageId);
|
||||
loader = cornerstone.loadAndCacheImage(imageId, {
|
||||
priority: priority,
|
||||
type: requestDetails.type
|
||||
});
|
||||
}
|
||||
|
||||
// Load and cache the image
|
||||
@ -8441,10 +8651,6 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
|
||||
function startGrabbing() {
|
||||
// Begin by grabbing X images
|
||||
if (awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
var maxSimultaneousRequests = cornerstoneTools.getMaxSimultaneousRequests();
|
||||
|
||||
maxNumRequests = {
|
||||
@ -8453,7 +8659,11 @@ if (typeof cornerstoneTools === 'undefined') {
|
||||
prefetch: Math.max(maxSimultaneousRequests - 1, 1)
|
||||
};
|
||||
|
||||
for (var i = 0; i < maxSimultaneousRequests; i++) {
|
||||
var currentRequests = numRequests.interaction +
|
||||
numRequests.thumbnail +
|
||||
numRequests.prefetch;
|
||||
var requestsToSend = maxSimultaneousRequests - currentRequests;
|
||||
for (var i = 0; i < requestsToSend; i++) {
|
||||
var requestDetails = getNextRequest();
|
||||
if (requestDetails) {
|
||||
sendRequest(requestDetails);
|
||||
@ -8887,14 +9097,8 @@ Display scroll progress bar across bottom of image.
|
||||
// Stop prefetching if the ImageCacheFull event is fired from cornerstone
|
||||
// console.log('CornerstoneImageCacheFull full, stopping');
|
||||
var element = e.data.element;
|
||||
var stackPrefetchData;
|
||||
|
||||
try {
|
||||
stackPrefetchData = cornerstoneTools.getToolState(element, toolType);
|
||||
} catch(error) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stackPrefetchData = cornerstoneTools.getToolState(element, toolType);
|
||||
if (!stackPrefetchData || !stackPrefetchData.data || !stackPrefetchData.data.length) {
|
||||
return;
|
||||
}
|
||||
@ -8911,15 +9115,7 @@ Display scroll progress bar across bottom of image.
|
||||
// it to the indicesToRequest list so that it will be retrieved later if the
|
||||
// currentImageIdIndex is changed to an image nearby
|
||||
var element = e.data.element;
|
||||
var stackData;
|
||||
|
||||
try {
|
||||
// It will throw an exception in some cases (eg: thumbnails)
|
||||
stackData = cornerstoneTools.getToolState(element, 'stack');
|
||||
} catch(error) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stackData = cornerstoneTools.getToolState(element, 'stack');
|
||||
if (!stackData || !stackData.data || !stackData.data.length) {
|
||||
return;
|
||||
}
|
||||
@ -9069,9 +9265,7 @@ Display scroll progress bar across bottom of image.
|
||||
}
|
||||
|
||||
function mouseWheelCallback(e, eventData) {
|
||||
var config = cornerstoneTools.stackScroll.getConfiguration();
|
||||
var pixelsPerImage = config.wheelDeltaPixelsPerImage || 100;
|
||||
var images = eventData.direction * Math.max(1, Math.round(Math.abs(eventData.wheelDeltaPixels) / pixelsPerImage));
|
||||
var images = -eventData.direction;
|
||||
cornerstoneTools.scroll(eventData.element, images);
|
||||
}
|
||||
|
||||
@ -9110,19 +9304,6 @@ Display scroll progress bar across bottom of image.
|
||||
cornerstoneTools.stackScroll = cornerstoneTools.simpleMouseButtonTool(mouseDownCallback);
|
||||
cornerstoneTools.stackScrollWheel = cornerstoneTools.mouseWheelTool(mouseWheelCallback);
|
||||
|
||||
var stackScrollWheelConfig = {
|
||||
// Smaller numbers lead to faster scrolling
|
||||
// 100 is the default here because in my empirical tests,
|
||||
// a single tick of my mouse produces a value of 100 pixels
|
||||
// on Firefox on Windows. This is the largest I noticed in my
|
||||
// tests. In some cases (e.g. >500 image stacks), the user
|
||||
// may want to speed up stack scrolling. Lowering this value
|
||||
// can do this.
|
||||
wheelDeltaPixelsPerImage: 100
|
||||
};
|
||||
|
||||
cornerstoneTools.stackScrollWheel.setConfiguration(stackScrollWheelConfig);
|
||||
|
||||
var options = {
|
||||
eventData: {
|
||||
deltaY: 0
|
||||
@ -10837,9 +11018,10 @@ Display scroll progress bar across bottom of image.
|
||||
var toolType = 'timeSeriesPlayer';
|
||||
|
||||
/**
|
||||
* Starts playing a clip or adjusts the frame rate of an already playing clip. framesPerSecond is
|
||||
* optional and defaults to 30 if not specified. A negative framesPerSecond will play the clip in reverse.
|
||||
* The element must be a stack of images
|
||||
* Starts playing a clip of different time series of the same image or adjusts the frame rate of an
|
||||
* already playing clip. framesPerSecond is optional and defaults to 30 if not specified. A negative
|
||||
* framesPerSecond will play the clip in reverse.
|
||||
* The element must have time series
|
||||
* @param element
|
||||
* @param framesPerSecond
|
||||
*/
|
||||
@ -11403,6 +11585,10 @@ Display scroll progress bar across bottom of image.
|
||||
return config.maxSimultaneousRequests;
|
||||
}
|
||||
|
||||
return getDefaultSimultaneousRequests();
|
||||
}
|
||||
|
||||
function getDefaultSimultaneousRequests() {
|
||||
var infoString = getBrowserInfo();
|
||||
var info = infoString.split(' ');
|
||||
var browserName = info[0];
|
||||
@ -11426,6 +11612,7 @@ Display scroll progress bar across bottom of image.
|
||||
}
|
||||
|
||||
// module exports
|
||||
cornerstoneTools.getDefaultSimultaneousRequests = getDefaultSimultaneousRequests;
|
||||
cornerstoneTools.getMaxSimultaneousRequests = getMaxSimultaneousRequests;
|
||||
cornerstoneTools.getBrowserInfo = getBrowserInfo;
|
||||
cornerstoneTools.isMobileDevice = isMobileDevice;
|
||||
@ -11730,14 +11917,6 @@ Display scroll progress bar across bottom of image.
|
||||
|
||||
'use strict';
|
||||
|
||||
function isNumber(value) {
|
||||
return typeof value === 'number' && !isNaN(value);
|
||||
}
|
||||
|
||||
function isInteger(number) {
|
||||
return number % 1 === 0;
|
||||
}
|
||||
|
||||
function scrollToIndex(element, newImageIdIndex) {
|
||||
var toolData = cornerstoneTools.getToolState(element, 'stack');
|
||||
if (!toolData || !toolData.data || !toolData.data.length) {
|
||||
@ -11746,12 +11925,6 @@ Display scroll progress bar across bottom of image.
|
||||
|
||||
var stackData = toolData.data[0];
|
||||
|
||||
if (!isNumber(newImageIdIndex)) {
|
||||
throw 'scrollToIndex: index provided is not numeric: ' + newImageIdIndex;
|
||||
} else if (isNumber(newImageIdIndex) && !isInteger(newImageIdIndex)) {
|
||||
throw 'scrollToIndex: index provided is not an integer: ' + newImageIdIndex;
|
||||
}
|
||||
|
||||
// Allow for negative indexing
|
||||
if (newImageIdIndex < 0) {
|
||||
newImageIdIndex += stackData.imageIds.length;
|
||||
@ -11815,16 +11988,19 @@ Display scroll progress bar across bottom of image.
|
||||
}
|
||||
}
|
||||
|
||||
var requestPoolManager = cornerstoneTools.requestPoolManager;
|
||||
|
||||
var type = 'interaction';
|
||||
requestPoolManager.clearRequestStack(type);
|
||||
|
||||
// Convert the preventCache value in stack data to a boolean
|
||||
var preventCache = !!stackData.preventCache;
|
||||
|
||||
requestPoolManager.addRequest(element, newImageId, type, preventCache, doneCallback, failCallback);
|
||||
requestPoolManager.startGrabbing();
|
||||
var imagePromise;
|
||||
if (preventCache) {
|
||||
imagePromise = cornerstone.loadImage(newImageId);
|
||||
} else {
|
||||
imagePromise = cornerstone.loadAndCacheImage(newImageId);
|
||||
}
|
||||
|
||||
imagePromise.then(doneCallback, failCallback);
|
||||
// Make sure we kick off any changed download request pools
|
||||
cornerstoneTools.requestPoolManager.startGrabbing();
|
||||
|
||||
$(element).trigger('CornerstoneStackScroll', eventData);
|
||||
}
|
||||
@ -11877,127 +12053,3 @@ Display scroll progress bar across bottom of image.
|
||||
})(cornerstone, cornerstoneTools);
|
||||
|
||||
// End Source; src/util/setContextToDisplayFontSize.js
|
||||
|
||||
// Begin Source: src/util/wheelListeners.js
|
||||
(function(cornerstoneTools) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// Thanks to Andrei Kashcha (@anvaka)
|
||||
// https://github.com/anvaka/wheel/blob/master/index.js
|
||||
|
||||
/**
|
||||
* This module unifies handling of mouse whee event across different browsers
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/Reference/Events/wheel?redirectlocale=en-US&redirectslug=DOM%2FMozilla_event_reference%2Fwheel
|
||||
* for more details
|
||||
*
|
||||
* Usage:
|
||||
* var addWheelListener = require('wheel').addWheelListener;
|
||||
* var removeWheelListener = require('wheel').removeWheelListener;
|
||||
* addWheelListener(domElement, function (e) {
|
||||
* // mouse wheel event
|
||||
* });
|
||||
* removeWheelListener(domElement, function);
|
||||
*/
|
||||
// by default we shortcut to 'addEventListener':
|
||||
|
||||
// creates a global "addWheelListener" method
|
||||
// example: addWheelListener( elem, function( e ) { console.log( e.deltaY ); e.preventDefault(); } );
|
||||
|
||||
var prefix = '',
|
||||
_addEventListener,
|
||||
_removeEventListener,
|
||||
support;
|
||||
|
||||
function detectEventModel(window, document) {
|
||||
if (window && window.addEventListener) {
|
||||
_addEventListener = 'addEventListener';
|
||||
_removeEventListener = 'removeEventListener';
|
||||
} else {
|
||||
_addEventListener = 'attachEvent';
|
||||
_removeEventListener = 'detachEvent';
|
||||
prefix = 'on';
|
||||
}
|
||||
|
||||
if (document) {
|
||||
// detect available wheel event
|
||||
support = 'onwheel' in document.createElement('div') ? 'wheel' : // Modern browsers support "wheel"
|
||||
document.onmousewheel !== undefined ? 'mousewheel' : // Webkit and IE support at least "mousewheel"
|
||||
'DOMMouseScroll'; // let's assume that remaining browsers are older Firefox
|
||||
} else {
|
||||
support = 'wheel';
|
||||
}
|
||||
}
|
||||
|
||||
function addWheelListener(elem, callback, useCapture) {
|
||||
detectEventModel(window, document);
|
||||
|
||||
_addWheelListener(elem, support, callback, useCapture);
|
||||
|
||||
// handle MozMousePixelScroll in older Firefox
|
||||
if (support === 'DOMMouseScroll') {
|
||||
_addWheelListener(elem, 'MozMousePixelScroll', callback, useCapture);
|
||||
}
|
||||
}
|
||||
|
||||
function removeWheelListener(elem, callback, useCapture) {
|
||||
detectEventModel(window, document);
|
||||
|
||||
_removeWheelListener(elem, support, callback, useCapture);
|
||||
|
||||
// handle MozMousePixelScroll in older Firefox
|
||||
if (support === 'DOMMouseScroll') {
|
||||
_removeWheelListener(elem, 'MozMousePixelScroll', callback, useCapture);
|
||||
}
|
||||
}
|
||||
|
||||
function _removeWheelListener(elem, eventName, callback, useCapture) {
|
||||
elem[_removeEventListener](prefix + eventName, callback, useCapture || false);
|
||||
}
|
||||
|
||||
function _addWheelListener(elem, eventName, callback, useCapture) {
|
||||
elem[_addEventListener](prefix + eventName, support === 'wheel' ? callback : function(originalEvent) {
|
||||
if (!originalEvent) {
|
||||
originalEvent = window.event;
|
||||
}
|
||||
|
||||
// create a normalized event object
|
||||
var event = {
|
||||
// keep a ref to the original event object
|
||||
originalEvent: originalEvent,
|
||||
target: originalEvent.target || originalEvent.srcElement,
|
||||
type: 'wheel',
|
||||
deltaMode: originalEvent.type === 'MozMousePixelScroll' ? 0 : 1,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
deltaZ: 0,
|
||||
preventDefault: function() {
|
||||
return originalEvent.preventDefault ? originalEvent.preventDefault() : false;
|
||||
}
|
||||
};
|
||||
|
||||
// calculate deltaY (and deltaX) according to the event
|
||||
if (support === 'mousewheel') {
|
||||
event.deltaY = -1 / 40 * originalEvent.wheelDelta;
|
||||
// Webkit also support wheelDeltaX
|
||||
if (originalEvent.wheelDeltaX) {
|
||||
event.deltaX = -1 / 40 * originalEvent.wheelDeltaX;
|
||||
}
|
||||
} else {
|
||||
event.deltaY = originalEvent.detail;
|
||||
}
|
||||
|
||||
// it's time to fire the callback
|
||||
return callback(event);
|
||||
|
||||
}, useCapture || false);
|
||||
}
|
||||
|
||||
// Module exports
|
||||
cornerstoneTools.addWheelListener = addWheelListener;
|
||||
cornerstoneTools.removeWheelListener = removeWheelListener;
|
||||
|
||||
})(cornerstoneTools);
|
||||
|
||||
// End Source; src/util/wheelListeners.js
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
/*! dicom-parser - v1.7.1 - 2016-06-09 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
|
||||
/*! dicom-parser - v1.7.4 - 2016-08-18 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
|
||||
(function (root, factory) {
|
||||
|
||||
// node.js
|
||||
@ -194,20 +194,6 @@ var dicomParser = (function (dicomParser)
|
||||
dataSet.byteArray[position + 1] === 0xD9);
|
||||
}
|
||||
|
||||
// Each JPEG image has a start of image marker
|
||||
function isStartOfImageMarker(dataSet, position) {
|
||||
return (dataSet.byteArray[position] === 0xFF &&
|
||||
dataSet.byteArray[position + 1] === 0xD8);
|
||||
}
|
||||
|
||||
function isFragmentStartOfImage(dataSet, pixelDataElement, fragmentIndex) {
|
||||
var fragment = pixelDataElement.fragments[fragmentIndex];
|
||||
if(isStartOfImageMarker(dataSet, fragment.position)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isFragmentEndOfImage(dataSet, pixelDataElement, fragmentIndex) {
|
||||
var fragment = pixelDataElement.fragments[fragmentIndex];
|
||||
// Need to check the last two bytes and the last three bytes for marker since odd length
|
||||
@ -222,12 +208,7 @@ var dicomParser = (function (dicomParser)
|
||||
function findLastImageFrameFragmentIndex(dataSet, pixelDataElement, startFragment) {
|
||||
for(var fragmentIndex=startFragment; fragmentIndex < pixelDataElement.fragments.length; fragmentIndex++) {
|
||||
if(isFragmentEndOfImage(dataSet, pixelDataElement, fragmentIndex)) {
|
||||
// if not last fragment, peek ahead to make sure the next fragment has a start of image marker just to
|
||||
// be safe
|
||||
if(fragmentIndex === pixelDataElement.fragments.length - 1 ||
|
||||
isFragmentStartOfImage(dataSet, pixelDataElement, fragmentIndex+1)) {
|
||||
return fragmentIndex;
|
||||
}
|
||||
return fragmentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,10 +256,6 @@ var dicomParser = (function (dicomParser)
|
||||
var basicOffsetTable = [];
|
||||
|
||||
var startFragmentIndex = 0;
|
||||
// sanity check first fragment has a JPEG start of image marker
|
||||
if(!isFragmentStartOfImage(dataSet, pixelDataElement, startFragmentIndex)) {
|
||||
throw 'first fragment does not have JPEG start of image marker';
|
||||
}
|
||||
|
||||
while(true) {
|
||||
// Add the offset for the start fragment
|
||||
@ -1417,6 +1394,65 @@ var dicomParser = (function (dicomParser)
|
||||
* Internal helper functions for parsing DICOM elements
|
||||
*/
|
||||
|
||||
var dicomParser = (function (dicomParser)
|
||||
{
|
||||
"use strict";
|
||||
|
||||
if(dicomParser === undefined)
|
||||
{
|
||||
dicomParser = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* reads from the byte stream until it finds the magic number for the Sequence Delimitation Item item
|
||||
* and then sets the length of the element
|
||||
* @param byteStream
|
||||
* @param element
|
||||
*/
|
||||
dicomParser.findAndSetUNElementLength = function(byteStream, element)
|
||||
{
|
||||
if(byteStream === undefined)
|
||||
{
|
||||
throw "dicomParser.findAndSetUNElementLength: missing required parameter 'byteStream'";
|
||||
}
|
||||
|
||||
var itemDelimitationItemLength = 8; // group, element, length
|
||||
var maxPosition = byteStream.byteArray.length - itemDelimitationItemLength;
|
||||
while(byteStream.position <= maxPosition)
|
||||
{
|
||||
var groupNumber;
|
||||
groupNumber = byteStream.readUint16();
|
||||
if(groupNumber === 0xfffe)
|
||||
{
|
||||
var elementNumber;
|
||||
elementNumber = byteStream.readUint16();
|
||||
if(elementNumber === 0xe0dd)
|
||||
{
|
||||
// NOTE: It would be better to also check for the length to be 0 as part of the check above
|
||||
// but we will just log a warning for now
|
||||
var itemDelimiterLength;
|
||||
itemDelimiterLength = byteStream.readUint32(); // the length
|
||||
if(itemDelimiterLength !== 0) {
|
||||
byteStream.warnings('encountered non zero length following item delimiter at position' + byteStream.position - 4 + " while reading element of undefined length with tag ' + element.tag");
|
||||
}
|
||||
element.length = byteStream.position - element.dataOffset;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No item delimitation item - silently set the length to the end of the buffer and set the position past the end of the buffer
|
||||
element.length = byteStream.byteArray.length - element.dataOffset;
|
||||
byteStream.seek(byteStream.byteArray.length - byteStream.position);
|
||||
};
|
||||
|
||||
|
||||
return dicomParser;
|
||||
}(dicomParser));
|
||||
/**
|
||||
* Internal helper functions for parsing DICOM elements
|
||||
*/
|
||||
|
||||
var dicomParser = (function (dicomParser)
|
||||
{
|
||||
"use strict";
|
||||
@ -1891,11 +1927,16 @@ var dicomParser = (function (dicomParser)
|
||||
dicomParser.readSequenceItemsExplicit(byteStream, element, warnings);
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
if(element.length === 4294967295)
|
||||
{
|
||||
if(element.tag === 'x7fe00010') {
|
||||
dicomParser.findEndOfEncapsulatedElement(byteStream, element, warnings);
|
||||
return element;
|
||||
} else if(element.vr === 'UN') {
|
||||
dicomParser.findAndSetUNElementLength(byteStream, element);
|
||||
return element;
|
||||
} else {
|
||||
dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element);
|
||||
return element;
|
||||
@ -2633,7 +2674,7 @@ var dicomParser = (function (dicomParser)
|
||||
dicomParser = {};
|
||||
}
|
||||
|
||||
dicomParser.version = "1.7.1";
|
||||
dicomParser.version = "1.7.3";
|
||||
|
||||
return dicomParser;
|
||||
}(dicomParser));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -25,12 +25,6 @@ Package.onUse(function(api) {
|
||||
api.addFiles('client/cornerstoneWADOImageLoader.js', 'client', {
|
||||
bare: true
|
||||
});
|
||||
api.addFiles('client/libopenjpeg.js', 'client', {
|
||||
bare: true
|
||||
});
|
||||
api.addFiles('client/libCharLS.js', 'client', {
|
||||
bare: true
|
||||
});
|
||||
api.addFiles('client/dicomParser.js', 'client', {
|
||||
bare: true
|
||||
});
|
||||
@ -41,6 +35,9 @@ Package.onUse(function(api) {
|
||||
bare: true
|
||||
});
|
||||
|
||||
api.addAssets('public/js/cornerstoneWADOImageLoaderCodecs.es5.js', 'client');
|
||||
api.addAssets('public/js/cornerstoneWADOImageLoaderWebWorker.es5.js', 'client');
|
||||
|
||||
api.export('cornerstone', 'client');
|
||||
api.export('cornerstoneMath', 'client');
|
||||
api.export('cornerstoneTools', 'client');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,861 @@
|
||||
/*! cornerstone-wado-image-loader - v0.14.1 - 2017-02-11 | (c) 2016 Chris Hafey | https://github.com/chafey/cornerstoneWADOImageLoader */
|
||||
|
||||
cornerstoneWADOImageLoaderWebWorker = {
|
||||
registerTaskHandler : undefined
|
||||
};
|
||||
|
||||
(function () {
|
||||
|
||||
|
||||
// an object of task handlers
|
||||
var taskHandlers = {};
|
||||
|
||||
// Flag to ensure web worker is only initialized once
|
||||
var initialized = false;
|
||||
|
||||
// the configuration object passed in when the web worker manager is initialized
|
||||
var config;
|
||||
|
||||
/**
|
||||
* Initialization function that loads additional web workers and initializes them
|
||||
* @param data
|
||||
*/
|
||||
function initialize(data) {
|
||||
//console.log('web worker initialize ', data.workerIndex);
|
||||
// prevent initialization from happening more than once
|
||||
if(initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// save the config data
|
||||
config = data.config;
|
||||
|
||||
// load any additional web worker tasks
|
||||
if(data.config.webWorkerTaskPaths) {
|
||||
for(var i=0; i < data.config.webWorkerTaskPaths.length; i++) {
|
||||
self.importScripts(data.config.webWorkerTaskPaths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// initialize each task handler
|
||||
Object.keys(taskHandlers).forEach(function(key) {
|
||||
taskHandlers[key].initialize(config.taskConfiguration);
|
||||
});
|
||||
|
||||
// tell main ui thread that we have completed initialization
|
||||
self.postMessage({
|
||||
taskType: 'initialize',
|
||||
status: 'success',
|
||||
result: {
|
||||
},
|
||||
workerIndex: data.workerIndex
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function exposed to web worker tasks to register themselves
|
||||
* @param taskHandler
|
||||
*/
|
||||
cornerstoneWADOImageLoaderWebWorker.registerTaskHandler = function(taskHandler) {
|
||||
if(taskHandlers[taskHandler.taskType]) {
|
||||
console.log('attempt to register duplicate task handler "', taskHandler.taskType, '"');
|
||||
return false;
|
||||
}
|
||||
taskHandlers[taskHandler.taskType] = taskHandler;
|
||||
if(initialized) {
|
||||
taskHandler.initialize(config.taskConfiguration);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to load a new web worker task with updated configuration
|
||||
* @param data
|
||||
*/
|
||||
function loadWebWorkerTask(data) {
|
||||
config = data.config;
|
||||
self.importScripts(data.sourcePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Web worker message handler - dispatches messages to the registered task handlers
|
||||
* @param msg
|
||||
*/
|
||||
self.onmessage = function(msg) {
|
||||
//console.log('web worker onmessage', msg.data);
|
||||
|
||||
// handle initialize message
|
||||
if(msg.data.taskType === 'initialize') {
|
||||
initialize(msg.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// handle loadWebWorkerTask message
|
||||
if(msg.data.taskType === 'loadWebWorkerTask') {
|
||||
loadWebWorkerTask(msg.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// dispatch the message if there is a handler registered for it
|
||||
if(taskHandlers[msg.data.taskType]) {
|
||||
taskHandlers[msg.data.taskType].handler(msg.data, function(result, transferList) {
|
||||
self.postMessage({
|
||||
taskType: msg.data.taskType,
|
||||
status: 'success',
|
||||
result: result,
|
||||
workerIndex: msg.data.workerIndex
|
||||
}, transferList);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// not task handler registered - send a failure message back to ui thread
|
||||
console.log('no task handler for ', msg.data.taskType);
|
||||
console.log(taskHandlers);
|
||||
self.postMessage({
|
||||
taskType: msg.data.taskType,
|
||||
status: 'failed - no task handler registered',
|
||||
workerIndex: msg.data.workerIndex
|
||||
});
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
cornerstoneWADOImageLoader = {};
|
||||
|
||||
(function () {
|
||||
|
||||
// flag to ensure codecs are loaded only once
|
||||
var codecsLoaded = false;
|
||||
|
||||
// the configuration object for the decodeTask
|
||||
var decodeConfig;
|
||||
|
||||
/**
|
||||
* Function to control loading and initializing the codecs
|
||||
* @param config
|
||||
*/
|
||||
function loadCodecs(config) {
|
||||
// prevent loading codecs more than once
|
||||
if (codecsLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load the codecs
|
||||
//console.time('loadCodecs');
|
||||
self.importScripts(config.decodeTask.codecsPath);
|
||||
codecsLoaded = true;
|
||||
//console.timeEnd('loadCodecs');
|
||||
|
||||
// Initialize the codecs
|
||||
if (config.decodeTask.initializeCodecsOnStartup) {
|
||||
//console.time('initializeCodecs');
|
||||
cornerstoneWADOImageLoader.initializeJPEG2000(config.decodeTask);
|
||||
cornerstoneWADOImageLoader.initializeJPEGLS(config.decodeTask);
|
||||
//console.timeEnd('initializeCodecs');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task initialization function
|
||||
*/
|
||||
function decodeTaskInitialize(config) {
|
||||
decodeConfig = config;
|
||||
if (config.decodeTask.loadCodecsOnStartup) {
|
||||
loadCodecs(config);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateMinMax(imageFrame) {
|
||||
if (imageFrame.smallestPixelValue !== undefined && imageFrame.largestPixelValue !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var minMax = cornerstoneWADOImageLoader.getMinMax(imageFrame.pixelData);
|
||||
imageFrame.smallestPixelValue = minMax.min;
|
||||
imageFrame.largestPixelValue = minMax.max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task handler function
|
||||
*/
|
||||
function decodeTaskHandler(data, doneCallback) {
|
||||
// Load the codecs if they aren't already loaded
|
||||
loadCodecs(decodeConfig);
|
||||
|
||||
var imageFrame = data.data.imageFrame;
|
||||
|
||||
// convert pixel data from ArrayBuffer to Uint8Array since web workers support passing ArrayBuffers but
|
||||
// not typed arrays
|
||||
var pixelData = new Uint8Array(data.data.pixelData);
|
||||
|
||||
cornerstoneWADOImageLoader.decodeImageFrame(
|
||||
imageFrame,
|
||||
data.data.transferSyntax,
|
||||
pixelData,
|
||||
decodeConfig.decodeTask,
|
||||
data.data.options);
|
||||
|
||||
calculateMinMax(imageFrame);
|
||||
|
||||
// convert from TypedArray to ArrayBuffer since web workers support passing ArrayBuffers but not
|
||||
// typed arrays
|
||||
imageFrame.pixelData = imageFrame.pixelData.buffer;
|
||||
|
||||
// invoke the callback with our result and pass the pixelData in the transferList to move it to
|
||||
// UI thread without making a copy
|
||||
doneCallback(imageFrame, [imageFrame.pixelData]);
|
||||
}
|
||||
|
||||
// register our task
|
||||
cornerstoneWADOImageLoaderWebWorker.registerTaskHandler({
|
||||
taskType: 'decodeTask',
|
||||
handler: decodeTaskHandler,
|
||||
initialize: decodeTaskInitialize
|
||||
});
|
||||
}());
|
||||
|
||||
/**
|
||||
*/
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
"use strict";
|
||||
|
||||
function decodeImageFrame(imageFrame, transferSyntax, pixelData, decodeConfig, options) {
|
||||
var start = new Date().getTime();
|
||||
|
||||
// Implicit VR Little Endian
|
||||
if(transferSyntax === "1.2.840.10008.1.2") {
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeLittleEndian(imageFrame, pixelData);
|
||||
}
|
||||
// Explicit VR Little Endian
|
||||
else if(transferSyntax === "1.2.840.10008.1.2.1") {
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeLittleEndian(imageFrame, pixelData);
|
||||
}
|
||||
// Explicit VR Big Endian (retired)
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.2" ) {
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeBigEndian(imageFrame, pixelData);
|
||||
}
|
||||
// Deflate transfer syntax (deflated by dicomParser)
|
||||
else if(transferSyntax === '1.2.840.10008.1.2.1.99') {
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeLittleEndian(imageFrame, pixelData);
|
||||
}
|
||||
// RLE Lossless
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.5" )
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeRLE(imageFrame, pixelData);
|
||||
}
|
||||
// JPEG Baseline lossy process 1 (8 bit)
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.50")
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEGBaseline(imageFrame, pixelData);
|
||||
}
|
||||
// JPEG Baseline lossy process 2 & 4 (12 bit)
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.51")
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEGBaseline(imageFrame, pixelData);
|
||||
}
|
||||
// JPEG Lossless, Nonhierarchical (Processes 14)
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.57")
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEGLossless(imageFrame, pixelData);
|
||||
}
|
||||
// JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1])
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.70" )
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEGLossless(imageFrame, pixelData);
|
||||
}
|
||||
// JPEG-LS Lossless Image Compression
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.80" )
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEGLS(imageFrame, pixelData);
|
||||
}
|
||||
// JPEG-LS Lossy (Near-Lossless) Image Compression
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.81" )
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEGLS(imageFrame, pixelData);
|
||||
}
|
||||
// JPEG 2000 Lossless
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.90")
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEG2000(imageFrame, pixelData, decodeConfig, options);
|
||||
}
|
||||
// JPEG 2000 Lossy
|
||||
else if (transferSyntax === "1.2.840.10008.1.2.4.91")
|
||||
{
|
||||
imageFrame = cornerstoneWADOImageLoader.decodeJPEG2000(imageFrame, pixelData, decodeConfig, options);
|
||||
}
|
||||
/* Don't know if these work...
|
||||
// JPEG 2000 Part 2 Multicomponent Image Compression (Lossless Only)
|
||||
else if(transferSyntax === "1.2.840.10008.1.2.4.92")
|
||||
{
|
||||
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
|
||||
}
|
||||
// JPEG 2000 Part 2 Multicomponent Image Compression
|
||||
else if(transferSyntax === "1.2.840.10008.1.2.4.93")
|
||||
{
|
||||
return cornerstoneWADOImageLoader.decodeJPEG2000(dataSet, frame);
|
||||
}
|
||||
*/
|
||||
else
|
||||
{
|
||||
if(console && console.log) {
|
||||
console.log("Image cannot be decoded due to Unsupported transfer syntax " + transferSyntax);
|
||||
}
|
||||
throw "no decoder for transfer syntax " + transferSyntax;
|
||||
}
|
||||
|
||||
var end = new Date().getTime();
|
||||
imageFrame.decodeTimeInMS = end - start;
|
||||
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
cornerstoneWADOImageLoader.decodeImageFrame = decodeImageFrame;
|
||||
}(cornerstoneWADOImageLoader));
|
||||
/**
|
||||
*/
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
function swap16(val) {
|
||||
return ((val & 0xFF) << 8)
|
||||
| ((val >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
function decodeBigEndian(imageFrame, pixelData) {
|
||||
if(imageFrame.bitsAllocated === 16) {
|
||||
var arrayBuffer = pixelData.buffer;
|
||||
var offset = pixelData.byteOffset;
|
||||
var length = pixelData.length;
|
||||
// if pixel data is not aligned on even boundary, shift it so we can create the 16 bit array
|
||||
// buffers on it
|
||||
if(offset % 2) {
|
||||
arrayBuffer = arrayBuffer.slice(offset);
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if(imageFrame.pixelRepresentation === 0) {
|
||||
imageFrame.pixelData = new Uint16Array(arrayBuffer, offset, length / 2);
|
||||
} else {
|
||||
imageFrame.pixelData = new Int16Array(arrayBuffer, offset, length / 2);
|
||||
}
|
||||
// Do the byte swap
|
||||
for(var i=0; i < imageFrame.pixelData.length; i++) {
|
||||
imageFrame[i] = swap16(imageFrame.pixelData[i]);
|
||||
}
|
||||
|
||||
} else if(imageFrame.bitsAllocated === 8) {
|
||||
imageFrame.pixelData = pixelData;
|
||||
}
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
// module exports
|
||||
cornerstoneWADOImageLoader.decodeBigEndian = decodeBigEndian;
|
||||
|
||||
}(cornerstoneWADOImageLoader));
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
"use strict";
|
||||
|
||||
function decodeJpx(imageFrame, pixelData) {
|
||||
|
||||
var jpxImage = new JpxImage();
|
||||
jpxImage.parse(pixelData);
|
||||
|
||||
var tileCount = jpxImage.tiles.length;
|
||||
if(tileCount !== 1) {
|
||||
throw 'JPEG2000 decoder returned a tileCount of ' + tileCount + ', when 1 is expected';
|
||||
}
|
||||
|
||||
imageFrame.columns = jpxImage.width;
|
||||
imageFrame.rows = jpxImage.height;
|
||||
imageFrame.pixelData = jpxImage.tiles[0].items;
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
var openJPEG;
|
||||
|
||||
function decodeOpenJPEG(data, bytesPerPixel, signed) {
|
||||
var dataPtr = openJPEG._malloc(data.length);
|
||||
openJPEG.writeArrayToMemory(data, dataPtr);
|
||||
|
||||
// create param outpout
|
||||
var imagePtrPtr=openJPEG._malloc(4);
|
||||
var imageSizePtr=openJPEG._malloc(4);
|
||||
var imageSizeXPtr=openJPEG._malloc(4);
|
||||
var imageSizeYPtr=openJPEG._malloc(4);
|
||||
var imageSizeCompPtr=openJPEG._malloc(4);
|
||||
|
||||
var t0 = Date.now();
|
||||
var ret = openJPEG.ccall('jp2_decode','number', ['number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
||||
[dataPtr, data.length, imagePtrPtr, imageSizePtr, imageSizeXPtr, imageSizeYPtr, imageSizeCompPtr]);
|
||||
// add num vomp..etc
|
||||
if(ret !== 0){
|
||||
console.log('[opj_decode] decoding failed!');
|
||||
openJPEG._free(dataPtr);
|
||||
openJPEG._free(openJPEG.getValue(imagePtrPtr, '*'));
|
||||
openJPEG._free(imageSizeXPtr);
|
||||
openJPEG._free(imageSizeYPtr);
|
||||
openJPEG._free(imageSizePtr);
|
||||
openJPEG._free(imageSizeCompPtr);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var imagePtr = openJPEG.getValue(imagePtrPtr, '*')
|
||||
|
||||
var image = {
|
||||
length : openJPEG.getValue(imageSizePtr,'i32'),
|
||||
sx : openJPEG.getValue(imageSizeXPtr,'i32'),
|
||||
sy : openJPEG.getValue(imageSizeYPtr,'i32'),
|
||||
nbChannels : openJPEG.getValue(imageSizeCompPtr,'i32'), // hard coded for now
|
||||
perf_timetodecode : undefined,
|
||||
pixelData : undefined
|
||||
};
|
||||
|
||||
// Copy the data from the EMSCRIPTEN heap into the correct type array
|
||||
var length = image.sx*image.sy*image.nbChannels;
|
||||
var src32 = new Int32Array(openJPEG.HEAP32.buffer, imagePtr, length);
|
||||
if(bytesPerPixel === 1) {
|
||||
if(Uint8Array.from) {
|
||||
image.pixelData = Uint8Array.from(src32);
|
||||
} else {
|
||||
image.pixelData = new Uint8Array(length);
|
||||
for(var i=0; i < length; i++) {
|
||||
image.pixelData[i] = src32[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (signed) {
|
||||
if(Int16Array.from) {
|
||||
image.pixelData = Int16Array.from(src32);
|
||||
} else {
|
||||
image.pixelData = new Int16Array(length);
|
||||
for(var i=0; i < length; i++) {
|
||||
image.pixelData[i] = src32[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(Uint16Array.from) {
|
||||
image.pixelData = Uint16Array.from(src32);
|
||||
} else {
|
||||
image.pixelData = new Uint16Array(length);
|
||||
for(var i=0; i < length; i++) {
|
||||
image.pixelData[i] = src32[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var t1 = Date.now();
|
||||
image.perf_timetodecode = t1-t0;
|
||||
|
||||
// free
|
||||
openJPEG._free(dataPtr);
|
||||
openJPEG._free(imagePtrPtr);
|
||||
openJPEG._free(imagePtr);
|
||||
openJPEG._free(imageSizePtr);
|
||||
openJPEG._free(imageSizeXPtr);
|
||||
openJPEG._free(imageSizeYPtr);
|
||||
openJPEG._free(imageSizeCompPtr);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
function decodeOpenJpeg2000(imageFrame, pixelData) {
|
||||
var bytesPerPixel = imageFrame.bitsAllocated <= 8 ? 1 : 2;
|
||||
var signed = imageFrame.pixelRepresentation === 1;
|
||||
|
||||
var image = decodeOpenJPEG(pixelData, bytesPerPixel, signed);
|
||||
|
||||
imageFrame.columns = image.sx;
|
||||
imageFrame.rows = image.sy;
|
||||
imageFrame.pixelData = image.pixelData;
|
||||
if(image.nbChannels > 1) {
|
||||
imageFrame.photometricInterpretation = "RGB";
|
||||
}
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
function initializeJPEG2000(decodeConfig) {
|
||||
// check to make sure codec is loaded
|
||||
if(!decodeConfig.usePDFJS) {
|
||||
if(typeof OpenJPEG === 'undefined') {
|
||||
throw 'OpenJPEG decoder not loaded';
|
||||
}
|
||||
}
|
||||
|
||||
if (!openJPEG) {
|
||||
openJPEG = OpenJPEG();
|
||||
if (!openJPEG || !openJPEG._jp2_decode) {
|
||||
throw 'OpenJPEG failed to initialize';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decodeJPEG2000(imageFrame, pixelData, decodeConfig, options)
|
||||
{
|
||||
options = options || {};
|
||||
|
||||
initializeJPEG2000(decodeConfig);
|
||||
|
||||
if(options.usePDFJS || decodeConfig.usePDFJS) {
|
||||
// OHIF image-JPEG2000 https://github.com/OHIF/image-JPEG2000
|
||||
//console.log('PDFJS')
|
||||
return decodeJpx(imageFrame, pixelData);
|
||||
} else {
|
||||
// OpenJPEG2000 https://github.com/jpambrun/openjpeg
|
||||
//console.log('OpenJPEG')
|
||||
return decodeOpenJpeg2000(imageFrame, pixelData);
|
||||
}
|
||||
}
|
||||
|
||||
cornerstoneWADOImageLoader.decodeJPEG2000 = decodeJPEG2000;
|
||||
cornerstoneWADOImageLoader.initializeJPEG2000 = initializeJPEG2000;
|
||||
|
||||
}(cornerstoneWADOImageLoader));
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
"use strict";
|
||||
|
||||
function decodeJPEGBaseline(imageFrame, pixelData)
|
||||
{
|
||||
// check to make sure codec is loaded
|
||||
if(typeof JpegImage === 'undefined') {
|
||||
throw 'No JPEG Baseline decoder loaded';
|
||||
}
|
||||
var jpeg = new JpegImage();
|
||||
jpeg.parse(pixelData);
|
||||
if(imageFrame.bitsAllocated === 8) {
|
||||
imageFrame.pixelData = jpeg.getData(imageFrame.columns, imageFrame.rows);
|
||||
return imageFrame;
|
||||
}
|
||||
else if(imageFrame.bitsAllocated === 16) {
|
||||
imageFrame.pixelData = jpeg.getData16(imageFrame.columns, imageFrame.rows);
|
||||
return imageFrame;
|
||||
}
|
||||
}
|
||||
|
||||
cornerstoneWADOImageLoader.decodeJPEGBaseline = decodeJPEGBaseline;
|
||||
}(cornerstoneWADOImageLoader));
|
||||
"use strict";
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
function decodeJPEGLossless(imageFrame, pixelData) {
|
||||
// check to make sure codec is loaded
|
||||
if(typeof jpeg === 'undefined' ||
|
||||
typeof jpeg.lossless === 'undefined' ||
|
||||
typeof jpeg.lossless.Decoder === 'undefined') {
|
||||
throw 'No JPEG Lossless decoder loaded';
|
||||
}
|
||||
|
||||
var byteOutput = imageFrame.bitsAllocated <= 8 ? 1 : 2;
|
||||
//console.time('jpeglossless');
|
||||
var buffer = pixelData.buffer;
|
||||
var decoder = new jpeg.lossless.Decoder();
|
||||
var decompressedData = decoder.decode(buffer, buffer.byteOffset, buffer.length, byteOutput);
|
||||
//console.timeEnd('jpeglossless');
|
||||
if (imageFrame.pixelRepresentation === 0) {
|
||||
if (imageFrame.bitsAllocated === 16) {
|
||||
imageFrame.pixelData = new Uint16Array(decompressedData.buffer);
|
||||
return imageFrame;
|
||||
} else {
|
||||
// untested!
|
||||
imageFrame.pixelData = new Uint8Array(decompressedData.buffer);
|
||||
return imageFrame;
|
||||
}
|
||||
} else {
|
||||
imageFrame.pixelData = new Int16Array(decompressedData.buffer);
|
||||
return imageFrame;
|
||||
}
|
||||
}
|
||||
// module exports
|
||||
cornerstoneWADOImageLoader.decodeJPEGLossless = decodeJPEGLossless;
|
||||
|
||||
}(cornerstoneWADOImageLoader));
|
||||
"use strict";
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
|
||||
var charLS;
|
||||
|
||||
function jpegLSDecode(data, isSigned) {
|
||||
|
||||
// prepare input parameters
|
||||
var dataPtr = charLS._malloc(data.length);
|
||||
charLS.writeArrayToMemory(data, dataPtr);
|
||||
|
||||
// prepare output parameters
|
||||
var imagePtrPtr=charLS._malloc(4);
|
||||
var imageSizePtr=charLS._malloc(4);
|
||||
var widthPtr=charLS._malloc(4);
|
||||
var heightPtr=charLS._malloc(4);
|
||||
var bitsPerSamplePtr=charLS._malloc(4);
|
||||
var stridePtr=charLS._malloc(4);
|
||||
var allowedLossyErrorPtr =charLS._malloc(4);
|
||||
var componentsPtr=charLS._malloc(4);
|
||||
var interleaveModePtr=charLS._malloc(4);
|
||||
|
||||
// Decode the image
|
||||
var result = charLS.ccall(
|
||||
'jpegls_decode',
|
||||
'number',
|
||||
['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
||||
[dataPtr, data.length, imagePtrPtr, imageSizePtr, widthPtr, heightPtr, bitsPerSamplePtr, stridePtr, componentsPtr, allowedLossyErrorPtr, interleaveModePtr]
|
||||
);
|
||||
|
||||
// Extract result values into object
|
||||
var image = {
|
||||
result : result,
|
||||
width : charLS.getValue(widthPtr,'i32'),
|
||||
height : charLS.getValue(heightPtr,'i32'),
|
||||
bitsPerSample : charLS.getValue(bitsPerSamplePtr,'i32'),
|
||||
stride : charLS.getValue(stridePtr,'i32'),
|
||||
components : charLS.getValue(componentsPtr, 'i32'),
|
||||
allowedLossyError : charLS.getValue(allowedLossyErrorPtr, 'i32'),
|
||||
interleaveMode: charLS.getValue(interleaveModePtr, 'i32'),
|
||||
pixelData: undefined
|
||||
};
|
||||
|
||||
// Copy image from emscripten heap into appropriate array buffer type
|
||||
var imagePtr = charLS.getValue(imagePtrPtr, '*');
|
||||
if(image.bitsPerSample <= 8) {
|
||||
image.pixelData = new Uint8Array(image.width * image.height * image.components);
|
||||
image.pixelData.set(new Uint8Array(charLS.HEAP8.buffer, imagePtr, image.pixelData.length));
|
||||
} else {
|
||||
// I have seen 16 bit signed images, but I don't know if 16 bit unsigned is valid, hoping to get
|
||||
// answer here:
|
||||
// https://github.com/team-charls/charls/issues/14
|
||||
if(isSigned) {
|
||||
image.pixelData = new Int16Array(image.width * image.height * image.components);
|
||||
image.pixelData.set(new Int16Array(charLS.HEAP16.buffer, imagePtr, image.pixelData.length));
|
||||
} else {
|
||||
image.pixelData = new Uint16Array(image.width * image.height * image.components);
|
||||
image.pixelData.set(new Uint16Array(charLS.HEAP16.buffer, imagePtr, image.pixelData.length));
|
||||
}
|
||||
}
|
||||
|
||||
// free memory and return image object
|
||||
charLS._free(dataPtr);
|
||||
charLS._free(imagePtr);
|
||||
charLS._free(imagePtrPtr);
|
||||
charLS._free(imageSizePtr);
|
||||
charLS._free(widthPtr);
|
||||
charLS._free(heightPtr);
|
||||
charLS._free(bitsPerSamplePtr);
|
||||
charLS._free(stridePtr);
|
||||
charLS._free(componentsPtr);
|
||||
charLS._free(interleaveModePtr);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
function initializeJPEGLS() {
|
||||
// check to make sure codec is loaded
|
||||
if(typeof CharLS === 'undefined') {
|
||||
throw 'No JPEG-LS decoder loaded';
|
||||
}
|
||||
|
||||
// Try to initialize CharLS
|
||||
// CharLS https://github.com/chafey/charls
|
||||
if(!charLS) {
|
||||
charLS = CharLS();
|
||||
if(!charLS || !charLS._jpegls_decode) {
|
||||
throw 'JPEG-LS failed to initialize';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function decodeJPEGLS(imageFrame, pixelData)
|
||||
{
|
||||
initializeJPEGLS();
|
||||
|
||||
var image = jpegLSDecode(pixelData, imageFrame.pixelRepresentation === 1);
|
||||
//console.log(image);
|
||||
|
||||
// throw error if not success or too much data
|
||||
if(image.result !== 0 && image.result !== 6) {
|
||||
throw 'JPEG-LS decoder failed to decode frame (error code ' + image.result + ')';
|
||||
}
|
||||
|
||||
imageFrame.columns = image.width;
|
||||
imageFrame.rows = image.height;
|
||||
imageFrame.pixelData = image.pixelData;
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
// module exports
|
||||
cornerstoneWADOImageLoader.decodeJPEGLS = decodeJPEGLS;
|
||||
cornerstoneWADOImageLoader.initializeJPEGLS = initializeJPEGLS;
|
||||
|
||||
}(cornerstoneWADOImageLoader));
|
||||
/**
|
||||
*/
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
function decodeLittleEndian(imageFrame, pixelData) {
|
||||
if(imageFrame.bitsAllocated === 16) {
|
||||
var arrayBuffer = pixelData.buffer;
|
||||
var offset = pixelData.byteOffset;
|
||||
var length = pixelData.length;
|
||||
// if pixel data is not aligned on even boundary, shift it so we can create the 16 bit array
|
||||
// buffers on it
|
||||
if(offset % 2) {
|
||||
arrayBuffer = arrayBuffer.slice(offset);
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if(imageFrame.pixelRepresentation === 0) {
|
||||
imageFrame.pixelData = new Uint16Array(arrayBuffer, offset, length / 2);
|
||||
} else {
|
||||
imageFrame.pixelData = new Int16Array(arrayBuffer, offset, length / 2);
|
||||
}
|
||||
} else if(imageFrame.bitsAllocated === 8) {
|
||||
imageFrame.pixelData = pixelData;
|
||||
}
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
// module exports
|
||||
cornerstoneWADOImageLoader.decodeLittleEndian = decodeLittleEndian;
|
||||
|
||||
}(cornerstoneWADOImageLoader));
|
||||
/**
|
||||
*/
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
function decodeRLE(imageFrame, pixelData) {
|
||||
|
||||
if(imageFrame.bitsAllocated === 8) {
|
||||
return decode8(imageFrame, pixelData);
|
||||
} else if( imageFrame.bitsAllocated === 16) {
|
||||
return decode16(imageFrame, pixelData);
|
||||
} else {
|
||||
throw 'unsupported pixel format for RLE'
|
||||
}
|
||||
}
|
||||
|
||||
function decode8(imageFrame, pixelData ) {
|
||||
var frameData = pixelData;
|
||||
var frameSize = imageFrame.rows * imageFrame.columns;
|
||||
var outFrame = new ArrayBuffer(frameSize*imageFrame.samplesPerPixel);
|
||||
var header=new DataView(frameData.buffer, frameData.byteOffset);
|
||||
var data=new DataView( frameData.buffer, frameData.byteOffset );
|
||||
var out=new DataView( outFrame );
|
||||
|
||||
var outIndex=0;
|
||||
var numSegments = header.getInt32(0,true);
|
||||
for( var s=0 ; s < numSegments ; ++s ) {
|
||||
outIndex = s;
|
||||
|
||||
var inIndex=header.getInt32( (s+1)*4,true);
|
||||
var maxIndex=header.getInt32( (s+2)*4,true);
|
||||
if( maxIndex===0 )
|
||||
maxIndex = frameData.length;
|
||||
|
||||
var endOfSegment = frameSize * numSegments;
|
||||
|
||||
while( inIndex < maxIndex ) {
|
||||
var n=data.getInt8(inIndex++);
|
||||
if( n >=0 && n <=127 ) {
|
||||
// copy n bytes
|
||||
for( var i=0 ; i < n+1 && outIndex < endOfSegment; ++i ) {
|
||||
out.setInt8(outIndex, data.getInt8(inIndex++));
|
||||
outIndex+=imageFrame.samplesPerPixel;
|
||||
}
|
||||
} else if( n<= -1 && n>=-127 ) {
|
||||
var value=data.getInt8(inIndex++);
|
||||
// run of n bytes
|
||||
for( var j=0 ; j < -n+1 && outIndex < endOfSegment; ++j ) {
|
||||
out.setInt8(outIndex, value );
|
||||
outIndex+=imageFrame.samplesPerPixel;
|
||||
}
|
||||
} else if (n===-128)
|
||||
; // do nothing
|
||||
}
|
||||
}
|
||||
imageFrame.pixelData = new Uint8Array(outFrame);
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
function decode16( imageFrame, pixelData ) {
|
||||
var frameData = pixelData;
|
||||
var frameSize = imageFrame.rows * imageFrame.columns;
|
||||
var outFrame = new ArrayBuffer(frameSize*imageFrame.samplesPerPixel*2);
|
||||
|
||||
var header=new DataView(frameData.buffer, frameData.byteOffset);
|
||||
var data=new DataView( frameData.buffer, frameData.byteOffset );
|
||||
var out=new DataView( outFrame );
|
||||
|
||||
var numSegments = header.getInt32(0,true);
|
||||
for( var s=0 ; s < numSegments ; ++s ) {
|
||||
var outIndex=0;
|
||||
var highByte=( s===0 ? 1 : 0);
|
||||
|
||||
var inIndex=header.getInt32( (s+1)*4,true);
|
||||
var maxIndex=header.getInt32( (s+2)*4,true);
|
||||
if( maxIndex===0 )
|
||||
maxIndex = frameData.length;
|
||||
|
||||
while( inIndex < maxIndex ) {
|
||||
var n=data.getInt8(inIndex++);
|
||||
if( n >=0 && n <=127 ) {
|
||||
for( var i=0 ; i < n+1 && outIndex < frameSize ; ++i ) {
|
||||
out.setInt8( (outIndex*2)+highByte, data.getInt8(inIndex++) );
|
||||
outIndex++;
|
||||
}
|
||||
} else if( n<= -1 && n>=-127 ) {
|
||||
var value=data.getInt8(inIndex++);
|
||||
for( var j=0 ; j < -n+1 && outIndex < frameSize ; ++j ) {
|
||||
out.setInt8( (outIndex*2)+highByte, value );
|
||||
outIndex++;
|
||||
}
|
||||
} else if (n===-128)
|
||||
; // do nothing
|
||||
}
|
||||
}
|
||||
if(imageFrame.pixelRepresentation === 0) {
|
||||
imageFrame.pixelData = new Uint16Array(outFrame);
|
||||
} else {
|
||||
imageFrame.pixelData = new Int16Array(outFrame);
|
||||
}
|
||||
return imageFrame;
|
||||
}
|
||||
|
||||
// module exports
|
||||
cornerstoneWADOImageLoader.decodeRLE = decodeRLE;
|
||||
|
||||
}(cornerstoneWADOImageLoader));
|
||||
(function (cornerstoneWADOImageLoader) {
|
||||
|
||||
"use strict";
|
||||
|
||||
function getMinMax(storedPixelData)
|
||||
{
|
||||
// we always calculate the min max values since they are not always
|
||||
// present in DICOM and we don't want to trust them anyway as cornerstone
|
||||
// depends on us providing reliable values for these
|
||||
var min = storedPixelData[0];
|
||||
var max = storedPixelData[0];
|
||||
var storedPixel;
|
||||
var numPixels = storedPixelData.length;
|
||||
for(var index = 1; index < numPixels; index++) {
|
||||
storedPixel = storedPixelData[index];
|
||||
min = Math.min(min, storedPixel);
|
||||
max = Math.max(max, storedPixel);
|
||||
}
|
||||
|
||||
return {
|
||||
min: min,
|
||||
max: max
|
||||
};
|
||||
}
|
||||
|
||||
// module exports
|
||||
cornerstoneWADOImageLoader.getMinMax = getMinMax;
|
||||
|
||||
}(cornerstoneWADOImageLoader));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user