Update Cornerstone libraries

This commit is contained in:
Erik Ziegler 2017-04-05 20:45:02 +02:00
parent 62fccc5928
commit 143d65b8c8
4 changed files with 1508 additions and 291 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,13 @@
/*! cornerstoneTools - v0.8.3 - 2017-02-21 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */ /*! cornerstoneTools - v0.8.4 - 2017-04-05 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
// Begin Source: src/header.js // Begin Source: src/header.js
if (typeof cornerstone === 'undefined') { if (typeof cornerstone === 'undefined') {
cornerstone = {}; cornerstone = {};
} }
if (typeof cornerstoneMath === 'undefined') {
cornerstoneMath = {};
}
if (typeof dicomParser === 'undefined') { if (typeof dicomParser === 'undefined') {
dicomParser = {}; dicomParser = {};
} }
@ -36,6 +40,8 @@ if (typeof cornerstoneTools === 'undefined') {
return; return;
} }
e.preventDefault();
var element = e.currentTarget; var element = e.currentTarget;
var x; var x;
@ -114,7 +120,7 @@ if (typeof cornerstoneTools === 'undefined') {
'use strict'; 'use strict';
var isClickEvent; var isClickEvent = true;
var preventClickTimeout; var preventClickTimeout;
var clickDelay = 200; var clickDelay = 200;
@ -410,11 +416,11 @@ if (typeof cornerstoneTools === 'undefined') {
isPress = false, isPress = false,
pressMaxDistance = 5, pressMaxDistance = 5,
pageDistanceMoved, pageDistanceMoved,
preventNextPinch = false; preventNextPinch = false,
lastDelta;
function onTouch(e) { function onTouch(e) {
console.log(e.type); var element = e.currentTarget || e.srcEvent.currentTarget,
var element = e.target.parentNode,
event, event,
eventType; eventType;
@ -659,13 +665,30 @@ if (typeof cornerstoneTools === 'undefined') {
break; break;
case 'panmove': case 'panmove':
// using the delta-value of HammerJS, because it takes all pointers into account
// this is very important when using panning in combination with pinch-zooming
// but HammerJS' delta is relative to the start of the pan event
// so it needs to be converted to a per-event-delta for CornerstoneTools
var delta = {
x: e.deltaX - lastDelta.x,
y: e.deltaY - lastDelta.y
};
lastDelta = {
x: e.deltaX,
y: e.deltaY
};
// calculate our current points in page and image coordinates // calculate our current points in page and image coordinates
currentPoints = { currentPoints = {
page: cornerstoneMath.point.pageToPoint(e.pointers[0]), page: {
image: cornerstone.pageToPixel(element, e.pointers[0].pageX, e.pointers[0].pageY), x: lastPoints.page.x + delta.x,
y: lastPoints.page.y + delta.y
},
image: cornerstone.pageToPixel(element, lastPoints.page.x + delta.x, lastPoints.page.y + delta.y),
client: { client: {
x: e.pointers[0].clientX, x: lastPoints.client.x + delta.x,
y: e.pointers[0].clientY y: lastPoints.client.y + delta.y
} }
}; };
currentPoints.canvas = cornerstone.pixelToCanvas(element, currentPoints.image); currentPoints.canvas = cornerstone.pixelToCanvas(element, currentPoints.image);
@ -711,6 +734,11 @@ if (typeof cornerstoneTools === 'undefined') {
break; break;
case 'panstart': case 'panstart':
lastDelta = {
x: e.deltaX,
y: e.deltaY
};
currentPoints = { currentPoints = {
page: cornerstoneMath.point.pageToPoint(e.pointers[0]), page: cornerstoneMath.point.pageToPoint(e.pointers[0]),
image: cornerstone.pageToPixel(element, e.pointers[0].pageX, e.pointers[0].pageY), image: cornerstone.pageToPixel(element, e.pointers[0].pageX, e.pointers[0].pageY),
@ -1664,7 +1692,7 @@ if (typeof cornerstoneTools === 'undefined') {
} }
cornerstone.updateImage(element); cornerstone.updateImage(element);
$(element).on('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).on('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); $(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
} }
@ -1675,7 +1703,7 @@ if (typeof cornerstoneTools === 'undefined') {
var distanceSq = 25; // Should probably make this a settable property later var distanceSq = 25; // Should probably make this a settable property later
var handle = cornerstoneTools.getHandleNearImagePoint(element, data.handles, coords, distanceSq); var handle = cornerstoneTools.getHandleNearImagePoint(element, data.handles, coords, distanceSq);
if (handle) { if (handle) {
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); $(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
data.active = true; data.active = true;
handle.active = true; handle.active = true;
@ -1692,7 +1720,7 @@ if (typeof cornerstoneTools === 'undefined') {
for (i = 0; i < toolData.data.length; i++) { for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i]; data = toolData.data[i];
if (touchToolInterface.pointNearTool(element, data, coords)) { if (touchToolInterface.pointNearTool(element, data, coords)) {
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); $(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
data.active = true; data.active = true;
cornerstone.updateImage(element); cornerstone.updateImage(element);
@ -1732,7 +1760,7 @@ if (typeof cornerstoneTools === 'undefined') {
} }
cornerstone.updateImage(eventData.element); cornerstone.updateImage(eventData.element);
$(element).on('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).on('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); $(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
if (touchToolInterface.pressCallback) { if (touchToolInterface.pressCallback) {
@ -1759,7 +1787,7 @@ if (typeof cornerstoneTools === 'undefined') {
var handle = cornerstoneTools.getHandleNearImagePoint(eventData.element, data.handles, coords, distance); var handle = cornerstoneTools.getHandleNearImagePoint(eventData.element, data.handles, coords, distance);
if (handle) { if (handle) {
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); $(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
if (touchToolInterface.pressCallback) { if (touchToolInterface.pressCallback) {
$(element).off('CornerstoneToolsTouchPress', touchToolInterface.pressCallback); $(element).off('CornerstoneToolsTouchPress', touchToolInterface.pressCallback);
@ -1781,7 +1809,7 @@ if (typeof cornerstoneTools === 'undefined') {
data = toolData.data[i]; data = toolData.data[i];
if (touchToolInterface.pointNearTool(eventData.element, data, coords)) { if (touchToolInterface.pointNearTool(eventData.element, data, coords)) {
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback); $(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); $(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
if (touchToolInterface.pressCallback) { if (touchToolInterface.pressCallback) {
$(element).off('CornerstoneToolsTouchPress', touchToolInterface.pressCallback); $(element).off('CornerstoneToolsTouchPress', touchToolInterface.pressCallback);
@ -1877,6 +1905,7 @@ if (typeof cornerstoneTools === 'undefined') {
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); $(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
$(element).on('CornerstoneImageRendered', touchToolInterface.onImageRendered); $(element).on('CornerstoneImageRendered', touchToolInterface.onImageRendered);
$(element).on('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
//$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback); //$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
if (touchToolInterface.doubleTapCallback) { if (touchToolInterface.doubleTapCallback) {
@ -2913,6 +2942,12 @@ if (typeof cornerstoneTools === 'undefined') {
context.shadowOffsetY = config.shadowOffsetY || 1; context.shadowOffsetY = config.shadowOffsetY || 1;
} }
var seriesModule = cornerstone.metaData.get('generalSeriesModule', image.imageId);
var modality;
if (seriesModule) {
modality = seriesModule.modality;
}
var toolCoords; var toolCoords;
if (eventData.isTouchEvent === true) { if (eventData.isTouchEvent === true) {
toolCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x, toolCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x,
@ -2937,18 +2972,18 @@ if (typeof cornerstoneTools === 'undefined') {
storedPixels = cornerstone.getStoredPixels(element, toolCoords.x, toolCoords.y, 1, 1); storedPixels = cornerstone.getStoredPixels(element, toolCoords.x, toolCoords.y, 1, 1);
var sp = storedPixels[0]; var sp = storedPixels[0];
var mo = sp * eventData.image.slope + eventData.image.intercept; var mo = sp * eventData.image.slope + eventData.image.intercept;
var suv = cornerstoneTools.calculateSUV(eventData.image, sp);
var seriesModule = cornerstone.metaData.get('generalSeriesModule', image.imageId);
var modality = seriesModule.modality;
var modalityPixelValueText = parseFloat(mo.toFixed(2));
if (modality === 'CT') { if (modality === 'CT') {
text += 'HU: '; text += 'HU: ' + modalityPixelValueText;
} } else if (modality === 'PT') {
text += modalityPixelValueText;
// Draw text var suv = cornerstoneTools.calculateSUV(eventData.image, sp);
text += parseFloat(mo.toFixed(2)); if (suv) {
if (suv) { text += ' SUV: ' + parseFloat(suv.toFixed(2));
text += ' SUV: ' + parseFloat(suv.toFixed(2)); }
} else {
text += modalityPixelValueText;
} }
} }
@ -3003,7 +3038,7 @@ if (typeof cornerstoneTools === 'undefined') {
} }
function imageRenderedCallback() { function imageRenderedCallback() {
if(dragEventData) { if (dragEventData) {
cornerstoneTools.dragProbe.strategy(dragEventData); cornerstoneTools.dragProbe.strategy(dragEventData);
dragEventData = null; dragEventData = null;
} }
@ -3081,76 +3116,6 @@ if (typeof cornerstoneTools === 'undefined') {
///////// END ACTIVE TOOL /////// ///////// END ACTIVE TOOL ///////
///////// BEGIN IMAGE RENDERING /////// ///////// BEGIN IMAGE RENDERING ///////
function pointInEllipse(ellipse, location) {
var xRadius = ellipse.width / 2;
var yRadius = ellipse.height / 2;
if (xRadius <= 0.0 || yRadius <= 0.0) {
return false;
}
var center = {
x: ellipse.left + xRadius,
y: ellipse.top + yRadius
};
/* This is a more general form of the circle equation
*
* X^2/a^2 + Y^2/b^2 <= 1
*/
var normalized = {
x: location.x - center.x,
y: location.y - center.y
};
var inEllipse = ((normalized.x * normalized.x) / (xRadius * xRadius)) + ((normalized.y * normalized.y) / (yRadius * yRadius)) <= 1.0;
return inEllipse;
}
function calculateMeanStdDev(sp, ellipse) {
// TODO: Get a real statistics library here that supports large counts
var sum = 0;
var sumSquared = 0;
var count = 0;
var index = 0;
for (var y = ellipse.top; y < ellipse.top + ellipse.height; y++) {
for (var x = ellipse.left; x < ellipse.left + ellipse.width; x++) {
if (pointInEllipse(ellipse, {
x: x,
y: y
}) === true) {
sum += sp[index];
sumSquared += sp[index] * sp[index];
count++;
}
index++;
}
}
if (count === 0) {
return {
count: count,
mean: 0.0,
variance: 0.0,
stdDev: 0.0
};
}
var mean = sum / count;
var variance = sumSquared / count - mean * mean;
return {
count: count,
mean: mean,
variance: variance,
stdDev: Math.sqrt(variance)
};
}
function pointNearEllipse(element, data, coords, distance) { function pointNearEllipse(element, data, coords, distance) {
var startCanvas = cornerstone.pixelToCanvas(element, data.handles.start); var startCanvas = cornerstone.pixelToCanvas(element, data.handles.start);
var endCanvas = cornerstone.pixelToCanvas(element, data.handles.end); var endCanvas = cornerstone.pixelToCanvas(element, data.handles.end);
@ -3169,8 +3134,8 @@ if (typeof cornerstoneTools === 'undefined') {
height: Math.abs(startCanvas.y - endCanvas.y) + distance height: Math.abs(startCanvas.y - endCanvas.y) + distance
}; };
var pointInMinorEllipse = pointInEllipse(minorEllipse, coords); var pointInMinorEllipse = cornerstoneTools.pointInEllipse(minorEllipse, coords);
var pointInMajorEllipse = pointInEllipse(majorEllipse, coords); var pointInMajorEllipse = cornerstoneTools.pointInEllipse(majorEllipse, coords);
if (pointInMajorEllipse && !pointInMinorEllipse) { if (pointInMajorEllipse && !pointInMinorEllipse) {
return true; return true;
@ -3207,7 +3172,10 @@ if (typeof cornerstoneTools === 'undefined') {
var config = cornerstoneTools.ellipticalRoi.getConfiguration(); var config = cornerstoneTools.ellipticalRoi.getConfiguration();
var context = eventData.canvasContext.canvas.getContext('2d'); var context = eventData.canvasContext.canvas.getContext('2d');
var seriesModule = cornerstone.metaData.get('generalSeriesModule', image.imageId); var seriesModule = cornerstone.metaData.get('generalSeriesModule', image.imageId);
var modality = seriesModule.modality; var modality;
if (seriesModule) {
modality = seriesModule.modality;
}
context.setTransform(1, 0, 0, 1, 0, 0); context.setTransform(1, 0, 0, 1, 0, 0);
@ -3282,10 +3250,10 @@ if (typeof cornerstoneTools === 'undefined') {
// Retrieve the bounds of the ellipse in image coordinates // Retrieve the bounds of the ellipse in image coordinates
var ellipse = { var ellipse = {
left: Math.min(data.handles.start.x, data.handles.end.x), left: Math.round(Math.min(data.handles.start.x, data.handles.end.x)),
top: Math.min(data.handles.start.y, data.handles.end.y), top: Math.round(Math.min(data.handles.start.y, data.handles.end.y)),
width: Math.abs(data.handles.start.x - data.handles.end.x), width: Math.round(Math.abs(data.handles.start.x - data.handles.end.x)),
height: Math.abs(data.handles.start.y - data.handles.end.y) height: Math.round(Math.abs(data.handles.start.y - data.handles.end.y))
}; };
// First, make sure this is not a color image, since no mean / standard // First, make sure this is not a color image, since no mean / standard
@ -3295,7 +3263,7 @@ if (typeof cornerstoneTools === 'undefined') {
var pixels = cornerstone.getPixels(element, ellipse.left, ellipse.top, ellipse.width, ellipse.height); 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 // Calculate the mean & standard deviation from the pixels and the ellipse details
meanStdDev = calculateMeanStdDev(pixels, ellipse); meanStdDev = cornerstoneTools.calculateEllipseStatistics(pixels, ellipse);
if (modality === 'PT') { if (modality === 'PT') {
// If the image is from a PET scan, use the DICOM tags to // If the image is from a PET scan, use the DICOM tags to
@ -3339,7 +3307,7 @@ if (typeof cornerstoneTools === 'undefined') {
var textLines = []; var textLines = [];
// If the mean and standard deviation values are present, display them // If the mean and standard deviation values are present, display them
if (meanStdDev && meanStdDev.mean) { if (meanStdDev && meanStdDev.mean !== undefined) {
// If the modality is CT, add HU to denote Hounsfield Units // If the modality is CT, add HU to denote Hounsfield Units
var moSuffix = ''; var moSuffix = '';
if (modality === 'CT') { if (modality === 'CT') {
@ -3716,7 +3684,11 @@ if (typeof cornerstoneTools === 'undefined') {
// Connect the end of the drawing to the handle nearest to the click // Connect the end of the drawing to the handle nearest to the click
if (handleNearby !== undefined){ if (handleNearby !== undefined){
data.handles[config.currentHandle - 1].lines.push(data.handles[handleNearby]); // only save x,y params from nearby handle to prevent circular reference
data.handles[config.currentHandle - 1].lines.push({
x: data.handles[handleNearby].x,
y: data.handles[handleNearby].y
});
} }
if (config.modifying) { if (config.modifying) {
@ -5692,9 +5664,9 @@ if (typeof cornerstoneTools === 'undefined') {
if (data.handles.textBox.hasMoved) { if (data.handles.textBox.hasMoved) {
// Draw dashed link line between tool and text // Draw dashed link line between tool and text
var link = { var link = {
start: {}, start: {},
end: {} end: {}
}; };
link.end.x = textCoords.x; link.end.x = textCoords.x;
link.end.y = textCoords.y; link.end.y = textCoords.y;
@ -5703,22 +5675,22 @@ if (typeof cornerstoneTools === 'undefined') {
var boundingBoxPoints = [ var boundingBoxPoints = [
{ {
// Top middle point of bounding box // Top middle point of bounding box
x: boundingBox.left + boundingBox.width / 2, x: boundingBox.left + boundingBox.width / 2,
y: boundingBox.top y: boundingBox.top
}, { }, {
// Left middle point of bounding box // Left middle point of bounding box
x: boundingBox.left, x: boundingBox.left,
y: boundingBox.top + boundingBox.height / 2 y: boundingBox.top + boundingBox.height / 2
}, { }, {
// Bottom middle point of bounding box // Bottom middle point of bounding box
x: boundingBox.left + boundingBox.width / 2, x: boundingBox.left + boundingBox.width / 2,
y: boundingBox.top + boundingBox.height y: boundingBox.top + boundingBox.height
}, { }, {
// Right middle point of bounding box // Right middle point of bounding box
x: boundingBox.left + boundingBox.width, x: boundingBox.left + boundingBox.width,
y: boundingBox.top + boundingBox.height / 2 y: boundingBox.top + boundingBox.height / 2
}, },
]; ];
link.end = cornerstoneMath.point.findClosestPoint(boundingBoxPoints, link.start); link.end = cornerstoneMath.point.findClosestPoint(boundingBoxPoints, link.start);
@ -6504,7 +6476,7 @@ if (typeof cornerstoneTools === 'undefined') {
// now check to see if there is a handle we can move // now check to see if there is a handle we can move
if (!toolData) { if (!toolData) {
return; return false;
} }
if (eventData.handlePressed) { if (eventData.handlePressed) {
@ -7045,6 +7017,15 @@ if (typeof cornerstoneTools === 'undefined') {
} }
function correctShift(shift, viewport) { function correctShift(shift, viewport) {
// Apply Flips
if (viewport.hflip) {
shift.x *= -1;
}
if (viewport.vflip) {
shift.y *= -1;
}
// Apply rotations // Apply rotations
if (viewport.rotation !== 0) { if (viewport.rotation !== 0) {
var angle = viewport.rotation * Math.PI / 180; var angle = viewport.rotation * Math.PI / 180;
@ -7059,15 +7040,6 @@ if (typeof cornerstoneTools === 'undefined') {
shift.y = newY; shift.y = newY;
} }
// Apply Flips
if (viewport.hflip) {
shift.x *= -1;
}
if (viewport.vflip) {
shift.y *= -1;
}
return shift; return shift;
} }
@ -8443,7 +8415,14 @@ if (typeof cornerstoneTools === 'undefined') {
var referenceImagePlane = cornerstoneTools.metaData.get('imagePlane', referenceImage.imageId); var referenceImagePlane = cornerstoneTools.metaData.get('imagePlane', referenceImage.imageId);
// Make sure the target and reference actually have image plane metadata // Make sure the target and reference actually have image plane metadata
if (!targetImagePlane || !referenceImagePlane) { if (!targetImagePlane ||
!referenceImagePlane ||
!targetImagePlane.rowCosines ||
!targetImagePlane.columnCosines ||
!targetImagePlane.imagePositionPatient ||
!referenceImagePlane.rowCosines ||
!referenceImagePlane.columnCosines ||
!referenceImagePlane.imagePositionPatient) {
return; return;
} }
@ -8726,6 +8705,88 @@ if (typeof cornerstoneTools === 'undefined') {
var toolType = 'playClip'; var toolType = 'playClip';
/**
* [private] Turns a Frame Time Vector (0018,1065) array into a normalized array of timeouts. Each element
* ... of the resulting array represents the amount of time each frame will remain on the screen.
* @param {Array} vector A Frame Time Vector (0018,1065) as specified in section C.7.6.5.1.2 of DICOM standard.
* @param {Number} speed A speed factor which will be applied to each element of the resulting array.
* @return {Array} An array with timeouts for each animation frame.
*/
function getPlayClipTimeouts(vector, speed) {
var i,
sample,
delay,
sum = 0,
limit = vector.length,
timeouts = [];
// initialize time varying to false
timeouts.isTimeVarying = false;
if (typeof speed !== 'number' || speed <= 0) {
speed = 1;
}
// first element of a frame time vector must be discarded
for (i = 1; i < limit; i++) {
delay = (+vector[i] / speed) | 0; // integral part only
timeouts.push(delay);
if (i === 1) { // use first item as a sample for comparison
sample = delay;
} else if (delay !== sample) {
timeouts.isTimeVarying = true;
}
sum += delay;
}
if (timeouts.length > 0) {
if (timeouts.isTimeVarying) {
// if it's a time varying vector, make the last item an average...
delay = (sum / timeouts.length) | 0;
} else {
delay = timeouts[0];
}
timeouts.push(delay);
}
return timeouts;
}
/**
* [private] Performs the heavy lifting of stopping an ongoing animation.
* @param {Object} playClipData The data from playClip that needs to be stopped.
* @return void
*/
function stopClipWithData(playClipData) {
var id = playClipData.intervalId;
if (typeof id !== 'undefined') {
playClipData.intervalId = undefined;
if (playClipData.usingFrameTimeVector) {
clearTimeout(id);
} else {
clearInterval(id);
}
}
}
/**
* [private] Trigger playClip tool stop event.
* @param element
* @return void
*/
function triggerStopEvent(element) {
var event,
eventDetail = {
element: element
};
event = $.Event('CornerstoneToolsClipStopped', eventDetail);
$(element).trigger(event, eventDetail);
}
/** /**
* Starts playing a clip or adjusts the frame rate of an already playing clip. framesPerSecond is * 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. * optional and defaults to 30 if not specified. A negative framesPerSecond will play the clip in reverse.
@ -8734,86 +8795,109 @@ if (typeof cornerstoneTools === 'undefined') {
* @param framesPerSecond * @param framesPerSecond
*/ */
function playClip(element, framesPerSecond) { function playClip(element, framesPerSecond) {
// hoisting of context variables
var stackToolData,
stackData,
playClipToolData,
playClipData,
playClipTimeouts,
playClipAction;
if (element === undefined) { if (element === undefined) {
throw 'playClip: element must not be undefined'; throw 'playClip: element must not be undefined';
} }
var stackToolData = cornerstoneTools.getToolState(element, 'stack'); stackToolData = cornerstoneTools.getToolState(element, 'stack');
if (!stackToolData || !stackToolData.data || !stackToolData.data.length) { if (!stackToolData || !stackToolData.data || !stackToolData.data.length) {
return; return;
} }
var stackData = stackToolData.data[0]; stackData = stackToolData.data[0];
var playClipToolData = cornerstoneTools.getToolState(element, toolType);
var playClipData;
playClipToolData = cornerstoneTools.getToolState(element, toolType);
if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) { if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) {
playClipData = { playClipData = {
intervalId: undefined, intervalId: undefined,
framesPerSecond: 30, framesPerSecond: 30,
lastFrameTimeStamp: undefined, lastFrameTimeStamp: undefined,
frameRate: 0, frameRate: 0,
frameTimeVector: undefined,
ignoreFrameTimeVector: false,
usingFrameTimeVector: false,
speed: 1,
reverse: false,
loop: true loop: true
}; };
cornerstoneTools.addToolState(element, toolType, playClipData); cornerstoneTools.addToolState(element, toolType, playClipData);
} else { } else {
playClipData = playClipToolData.data[0]; playClipData = playClipToolData.data[0];
// make sure the specified clip is not running before any property update
stopClipWithData(playClipData);
} }
// If a framerate is specified, update the playClipData now // If a framesPerSecond is specified and is valid, update the playClipData now
if (framesPerSecond) { if (framesPerSecond < 0 || framesPerSecond > 0) {
playClipData.framesPerSecond = framesPerSecond; playClipData.framesPerSecond = +framesPerSecond;
playClipData.reverse = playClipData.framesPerSecond < 0;
// if framesPerSecond is given, frameTimeVector will be ignored...
playClipData.ignoreFrameTimeVector = true;
} }
// if already playing, do not set a new interval // determine if frame time vector should be used instead of a fixed frame rate...
if (playClipData.intervalId !== undefined) { if (
return; playClipData.ignoreFrameTimeVector !== true &&
playClipData.frameTimeVector &&
playClipData.frameTimeVector.length === stackData.imageIds.length
) {
playClipTimeouts = getPlayClipTimeouts(playClipData.frameTimeVector, playClipData.speed);
} }
playClipData.intervalId = setInterval(function() { // this function encapsulates the frame rendering logic...
var newImageIdIndex = stackData.currentImageIdIndex; playClipAction = function playClipAction() {
if (playClipData.framesPerSecond > 0) { // hoisting of context variables
newImageIdIndex++; var loader,
} else { viewport,
startLoadingHandler,
endLoadingHandler,
errorLoadingHandler,
newImageIdIndex = stackData.currentImageIdIndex,
imageCount = stackData.imageIds.length;
if (playClipData.reverse) {
newImageIdIndex--; newImageIdIndex--;
} else {
newImageIdIndex++;
} }
if (!playClipData.loop && (newImageIdIndex >= stackData.imageIds.length || newImageIdIndex < 0)) { if (!playClipData.loop && (newImageIdIndex < 0 || newImageIdIndex >= imageCount)) {
var eventDetail = { stopClipWithData(playClipData);
element: element triggerStopEvent(element);
};
var event = $.Event('CornerstoneToolsClipStopped', eventDetail);
$(element).trigger(event, eventDetail);
clearInterval(playClipData.intervalId);
playClipData.intervalId = undefined;
return; return;
} }
// loop around if we go outside the stack // loop around if we go outside the stack
if (newImageIdIndex >= stackData.imageIds.length) { if (newImageIdIndex >= imageCount) {
newImageIdIndex = 0; newImageIdIndex = 0;
} }
if (newImageIdIndex < 0) { if (newImageIdIndex < 0) {
newImageIdIndex = stackData.imageIds.length - 1; newImageIdIndex = imageCount - 1;
} }
if (newImageIdIndex !== stackData.currentImageIdIndex) { if (newImageIdIndex !== stackData.currentImageIdIndex) {
var startLoadingHandler = cornerstoneTools.loadHandlerManager.getStartLoadHandler();
var endLoadingHandler = cornerstoneTools.loadHandlerManager.getEndLoadHandler(); startLoadingHandler = cornerstoneTools.loadHandlerManager.getStartLoadHandler();
var errorLoadingHandler = cornerstoneTools.loadHandlerManager.getErrorLoadingHandler(); endLoadingHandler = cornerstoneTools.loadHandlerManager.getEndLoadHandler();
errorLoadingHandler = cornerstoneTools.loadHandlerManager.getErrorLoadingHandler();
if (startLoadingHandler) { if (startLoadingHandler) {
startLoadingHandler(element); startLoadingHandler(element);
} }
var viewport = cornerstone.getViewport(element); viewport = cornerstone.getViewport(element);
var loader;
if (stackData.preventCache === true) { if (stackData.preventCache === true) {
loader = cornerstone.loadImage(stackData.imageIds[newImageIdIndex]); loader = cornerstone.loadImage(stackData.imageIds[newImageIdIndex]);
} else { } else {
@ -8832,8 +8916,25 @@ if (typeof cornerstoneTools === 'undefined') {
errorLoadingHandler(element, imageId, error); errorLoadingHandler(element, imageId, error);
} }
}); });
} }
}, 1000 / Math.abs(playClipData.framesPerSecond));
};
// if playClipTimeouts array is available, not empty and its elements are NOT uniform ...
// ... (at least one timeout is different from the others), use alternate setTimeout implementation
if (playClipTimeouts && playClipTimeouts.length > 0 && playClipTimeouts.isTimeVarying) {
playClipData.usingFrameTimeVector = true;
playClipData.intervalId = setTimeout(function playClipTimeoutHandler() {
playClipData.intervalId = setTimeout(playClipTimeoutHandler, playClipTimeouts[stackData.currentImageIdIndex]);
playClipAction();
}, 0);
} else {
// ... otherwise user setInterval implementation which is much more efficient.
playClipData.usingFrameTimeVector = false;
playClipData.intervalId = setInterval(playClipAction, 1000 / Math.abs(playClipData.framesPerSecond));
}
} }
/** /**
@ -8841,15 +8942,15 @@ if (typeof cornerstoneTools === 'undefined') {
* * @param element * * @param element
*/ */
function stopClip(element) { function stopClip(element) {
var playClipToolData = cornerstoneTools.getToolState(element, toolType); var playClipToolData = cornerstoneTools.getToolState(element, toolType);
if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) { if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) {
return; return;
} }
var playClipData = playClipToolData.data[0]; stopClipWithData(playClipToolData.data[0]);
clearInterval(playClipData.intervalId);
playClipData.intervalId = undefined;
} }
// module/private exports // module/private exports
@ -8872,7 +8973,8 @@ Display scroll progress bar across bottom of image.
var configuration = { var configuration = {
backgroundColor: 'rgb(19, 63, 141)', backgroundColor: 'rgb(19, 63, 141)',
fillColor: 'white' fillColor: 'white',
orientation: 'horizontal'
}; };
function onImageRendered(e, eventData){ function onImageRendered(e, eventData){
@ -8892,7 +8994,11 @@ Display scroll progress bar across bottom of image.
// draw indicator background // draw indicator background
context.fillStyle = config.backgroundColor; context.fillStyle = config.backgroundColor;
context.fillRect(0, height - scrollBarHeight, width, scrollBarHeight); if (config.orientation === 'horizontal') {
context.fillRect(0, height - scrollBarHeight, width, scrollBarHeight);
} else {
context.fillRect(0, 0, scrollBarHeight, height);
}
// get current image index // get current image index
var stackData = cornerstoneTools.getToolState(element, 'stack'); var stackData = cornerstoneTools.getToolState(element, 'stack');
@ -8905,10 +9011,16 @@ Display scroll progress bar across bottom of image.
// draw current image cursor // draw current image cursor
var cursorWidth = width / imageIds.length; var cursorWidth = width / imageIds.length;
var cursorHeight = height / imageIds.length;
var xPosition = cursorWidth * currentImageIdIndex; var xPosition = cursorWidth * currentImageIdIndex;
var yPosition = cursorHeight * currentImageIdIndex;
context.fillStyle = config.fillColor; context.fillStyle = config.fillColor;
context.fillRect(xPosition, height - scrollBarHeight, cursorWidth, scrollBarHeight); if (config.orientation === 'horizontal') {
context.fillRect(xPosition, height - scrollBarHeight, cursorWidth, scrollBarHeight);
} else {
context.fillRect(0, yPosition, scrollBarHeight, cursorHeight);
}
context.restore(); context.restore();
} }
@ -9095,29 +9207,20 @@ Display scroll progress bar across bottom of image.
requestPoolManager.startGrabbing(); requestPoolManager.startGrabbing();
} }
function handleCacheFull(e) {
// Stop prefetching if the ImageCacheFull event is fired from cornerstone
// console.log('CornerstoneImageCacheFull full, stopping');
var element = e.data.element;
var stackPrefetchData = cornerstoneTools.getToolState(element, toolType);
if (!stackPrefetchData || !stackPrefetchData.data || !stackPrefetchData.data.length) {
return;
}
// Disable the stackPrefetch tool
// stackPrefetchData.data[0].enabled = false;
// Clear current prefetch requests from the requestPool
cornerstoneTools.requestPoolManager.clearRequestStack(requestType);
}
function promiseRemovedHandler(e, eventData) { function promiseRemovedHandler(e, eventData) {
// When an imagePromise has been pushed out of the cache, re-add its index // When an imagePromise has been pushed out of the cache, re-add its index
// it to the indicesToRequest list so that it will be retrieved later if the // it to the indicesToRequest list so that it will be retrieved later if the
// currentImageIdIndex is changed to an image nearby // currentImageIdIndex is changed to an image nearby
var element = e.data.element; var element = e.data.element;
var stackData = cornerstoneTools.getToolState(element, 'stack'); var stackData;
try {
// It will throw an exception in some cases (eg: thumbnails)
stackData = cornerstoneTools.getToolState(element, 'stack');
} catch(error) {
return;
}
if (!stackData || !stackData.data || !stackData.data.length) { if (!stackData || !stackData.data || !stackData.data.length) {
return; return;
} }
@ -9194,11 +9297,6 @@ Display scroll progress bar across bottom of image.
$(element).off('CornerstoneNewImage', onImageUpdated); $(element).off('CornerstoneNewImage', onImageUpdated);
$(element).on('CornerstoneNewImage', onImageUpdated); $(element).on('CornerstoneNewImage', onImageUpdated);
$(cornerstone).off('CornerstoneImageCacheFull', handleCacheFull);
$(cornerstone).on('CornerstoneImageCacheFull', {
element: element
}, handleCacheFull);
$(cornerstone).off('CornerstoneImageCachePromiseRemoved', promiseRemovedHandler); $(cornerstone).off('CornerstoneImageCachePromiseRemoved', promiseRemovedHandler);
$(cornerstone).on('CornerstoneImageCachePromiseRemoved', { $(cornerstone).on('CornerstoneImageCachePromiseRemoved', {
element: element element: element
@ -9209,7 +9307,6 @@ Display scroll progress bar across bottom of image.
clearTimeout(resetPrefetchTimeout); clearTimeout(resetPrefetchTimeout);
$(element).off('CornerstoneNewImage', onImageUpdated); $(element).off('CornerstoneNewImage', onImageUpdated);
$(cornerstone).off('CornerstoneImageCacheFull', handleCacheFull);
$(cornerstone).off('CornerstoneImageCachePromiseRemoved', promiseRemovedHandler); $(cornerstone).off('CornerstoneImageCachePromiseRemoved', promiseRemovedHandler);
var stackPrefetchData = cornerstoneTools.getToolState(element, toolType); var stackPrefetchData = cornerstoneTools.getToolState(element, toolType);
@ -11184,6 +11281,79 @@ Display scroll progress bar across bottom of image.
// End Source; src/timeSeriesTools/timeSeriesScroll.js // End Source; src/timeSeriesTools/timeSeriesScroll.js
// Begin Source: src/util/RoundToDecimal.js
(function($, cornerstone, cornerstoneTools) {
'use strict';
function roundToDecimal(value, precision) {
var multiplier = Math.pow(10, precision);
return (Math.round(value * multiplier) / multiplier);
}
// module exports
cornerstoneTools.roundToDecimal = roundToDecimal;
})($, cornerstone, cornerstoneTools);
// End Source; src/util/RoundToDecimal.js
// Begin Source: src/util/calculateEllipseStatistics.js
(function(cornerstoneTools) {
'use strict';
function calculateEllipseStatistics(sp, ellipse) {
// TODO: Get a real statistics library here that supports large counts
var sum = 0;
var sumSquared = 0;
var count = 0;
var index = 0;
for (var y = ellipse.top; y < ellipse.top + ellipse.height; y++) {
for (var x = ellipse.left; x < ellipse.left + ellipse.width; x++) {
var point = {
x: x,
y: y
};
if (cornerstoneTools.pointInEllipse(ellipse, point)) {
sum += sp[index];
sumSquared += sp[index] * sp[index];
count++;
}
index++;
}
}
if (count === 0) {
return {
count: count,
mean: 0.0,
variance: 0.0,
stdDev: 0.0
};
}
var mean = sum / count;
var variance = sumSquared / count - mean * mean;
return {
count: count,
mean: mean,
variance: variance,
stdDev: Math.sqrt(variance)
};
}
cornerstoneTools.calculateEllipseStatistics = calculateEllipseStatistics;
})(cornerstoneTools);
// End Source; src/util/calculateEllipseStatistics.js
// Begin Source: src/util/calculateSUV.js // Begin Source: src/util/calculateSUV.js
(function(cornerstoneTools) { (function(cornerstoneTools) {
@ -11201,6 +11371,10 @@ Display scroll progress bar across bottom of image.
var patientStudyModule = cornerstone.metaData.get('patientStudyModule', image.imageId); var patientStudyModule = cornerstone.metaData.get('patientStudyModule', image.imageId);
var seriesModule = cornerstone.metaData.get('generalSeriesModule', image.imageId); var seriesModule = cornerstone.metaData.get('generalSeriesModule', image.imageId);
if (!patientStudyModule || !seriesModule) {
return;
}
var modality = seriesModule.modality; var modality = seriesModule.modality;
// image must be PET // image must be PET
@ -11211,12 +11385,12 @@ Display scroll progress bar across bottom of image.
var modalityPixelValue = storedPixelValue * image.slope + image.intercept; var modalityPixelValue = storedPixelValue * image.slope + image.intercept;
var patientWeight = patientStudyModule.patientWeight; // in kg var patientWeight = patientStudyModule.patientWeight; // in kg
if (patientWeight === undefined) { if (!patientWeight) {
return; return;
} }
var petSequenceModule = cornerstone.metaData.get('petIsotopeModule', image.imageId); var petSequenceModule = cornerstone.metaData.get('petIsotopeModule', image.imageId);
if (petSequenceModule === undefined) { if (!petSequenceModule) {
return; return;
} }
@ -11701,6 +11875,44 @@ Display scroll progress bar across bottom of image.
// End Source; src/util/pauseEvent.js // End Source; src/util/pauseEvent.js
// Begin Source: src/util/pointInEllipse.js
(function(cornerstoneTools) {
'use strict';
function pointInEllipse(ellipse, location) {
var xRadius = ellipse.width / 2;
var yRadius = ellipse.height / 2;
if (xRadius <= 0.0 || yRadius <= 0.0) {
return false;
}
var center = {
x: ellipse.left + xRadius,
y: ellipse.top + yRadius
};
/* This is a more general form of the circle equation
*
* X^2/a^2 + Y^2/b^2 <= 1
*/
var normalized = {
x: location.x - center.x,
y: location.y - center.y
};
var inEllipse = ((normalized.x * normalized.x) / (xRadius * xRadius)) + ((normalized.y * normalized.y) / (yRadius * yRadius)) <= 1.0;
return inEllipse;
}
cornerstoneTools.pointInEllipse = pointInEllipse;
})(cornerstoneTools);
// End Source; src/util/pointInEllipse.js
// Begin Source: src/util/pointInsideBoundingBox.js // Begin Source: src/util/pointInsideBoundingBox.js
(function(cornerstoneMath, cornerstoneTools) { (function(cornerstoneMath, cornerstoneTools) {
@ -11844,41 +12056,6 @@ Display scroll progress bar across bottom of image.
// End Source; src/util/pointProjector.js // End Source; src/util/pointProjector.js
// Begin Source: src/util/requestAnimFrame.js
(function(cornerstoneTools) {
'use strict';
function requestAnimFrame(callback) {
// This functionality was moved to cornerstone.
console.warn('cornerstoneTools.requestAnimFrame() is deprecated, consider using cornerstone.requestAnimationFrame()');
cornerstone.requestAnimationFrame(callback);
}
// Module exports
cornerstoneTools.requestAnimFrame = requestAnimFrame;
})(cornerstoneTools);
// End Source; src/util/requestAnimFrame.js
// Begin Source: src/util/RoundToDecimal.js
(function($, cornerstone, cornerstoneTools) {
'use strict';
function roundToDecimal(value, precision) {
var multiplier = Math.pow(10, precision);
return (Math.round(value * multiplier) / multiplier);
}
// module exports
cornerstoneTools.roundToDecimal = roundToDecimal;
})($, cornerstone, cornerstoneTools);
// End Source; src/util/RoundToDecimal.js
// Begin Source: src/util/scroll.js // Begin Source: src/util/scroll.js
(function(cornerstone, cornerstoneTools) { (function(cornerstone, cornerstoneTools) {

View File

@ -383,7 +383,7 @@ if(typeof cornerstoneWADOImageLoader === 'undefined'){
// JPEGBaseline (8 bits) is already returning the pixel data in the right format (rgba) // JPEGBaseline (8 bits) is already returning the pixel data in the right format (rgba)
// because it's using a canvas to load and decode images. // because it's using a canvas to load and decode images.
if(!cornerstoneWADOImageLoader.isJPEGBaseline8Bit(imageFrame, transferSyntax)) { if(!cornerstoneWADOImageLoader.isJPEGBaseline8BitColor(imageFrame, transferSyntax)) {
setPixelDataType(imageFrame); setPixelDataType(imageFrame);
// convert color space // convert color space
@ -447,10 +447,12 @@ if(typeof cornerstoneWADOImageLoader === 'undefined'){
image.render = cornerstone.renderGrayscaleImage; image.render = cornerstone.renderGrayscaleImage;
} }
// Calculate min/max pixel values (do not trust DICOM Headers) // calculate min/max if not supplied
var minMax = cornerstoneWADOImageLoader.getMinMax(imageFrame.pixelData); if(image.minPixelValue === undefined || image.maxPixelValue === undefined) {
image.minPixelValue = minMax.min; var minMax = cornerstoneWADOImageLoader.getMinMax(imageFrame.pixelData);
image.maxPixelValue = minMax.max; image.minPixelValue = minMax.min;
image.maxPixelValue = minMax.max;
}
// Modality LUT // Modality LUT
if(modalityLutModule.modalityLUTSequence && if(modalityLutModule.modalityLUTSequence &&
@ -531,9 +533,12 @@ if(typeof cornerstoneWADOImageLoader === 'undefined'){
// JPEG Baseline lossy process 1 (8 bit) // JPEG Baseline lossy process 1 (8 bit)
else if (transferSyntax === "1.2.840.10008.1.2.4.50") else if (transferSyntax === "1.2.840.10008.1.2.4.50")
{ {
if(imageFrame.bitsAllocated === 8) // Handle 8-bit JPEG Baseline color images using the browser's built-in
// JPEG decoding
if(imageFrame.bitsAllocated === 8 &&
(imageFrame.samplesPerPixel === 3 || imageFrame.samplesPerPixel === 4))
{ {
return cornerstoneWADOImageLoader.decodeJPEGBaseline8Bit(imageFrame, pixelData, canvas); return cornerstoneWADOImageLoader.decodeJPEGBaseline8BitColor(imageFrame, pixelData, canvas);
} else { } else {
return addDecodeTask(imageFrame, transferSyntax, pixelData, options); return addDecodeTask(imageFrame, transferSyntax, pixelData, options);
} }
@ -622,7 +627,7 @@ if(typeof cornerstoneWADOImageLoader === 'undefined'){
} }
} }
function decodeJPEGBaseline8Bit(imageFrame, pixelData, canvas) { function decodeJPEGBaseline8BitColor(imageFrame, pixelData, canvas) {
var start = new Date().getTime(); var start = new Date().getTime();
var deferred = $.Deferred(); var deferred = $.Deferred();
@ -666,18 +671,20 @@ if(typeof cornerstoneWADOImageLoader === 'undefined'){
return deferred.promise(); return deferred.promise();
} }
function isJPEGBaseline8Bit(imageFrame, transferSyntax) { function isJPEGBaseline8BitColor(imageFrame, transferSyntax) {
transferSyntax = transferSyntax || imageFrame.transferSyntax; transferSyntax = transferSyntax || imageFrame.transferSyntax;
if((imageFrame.bitsAllocated === 8) && (transferSyntax === "1.2.840.10008.1.2.4.50")) { if(imageFrame.bitsAllocated === 8 &&
transferSyntax === "1.2.840.10008.1.2.4.50" &&
(imageFrame.samplesPerPixel === 3 || imageFrame.samplesPerPixel === 4)) {
return true; return true;
} }
} }
// module exports // module exports
cornerstoneWADOImageLoader.decodeJPEGBaseline8Bit = decodeJPEGBaseline8Bit; cornerstoneWADOImageLoader.decodeJPEGBaseline8BitColor = decodeJPEGBaseline8BitColor;
cornerstoneWADOImageLoader.isJPEGBaseline8Bit = isJPEGBaseline8Bit; cornerstoneWADOImageLoader.isJPEGBaseline8BitColor = isJPEGBaseline8BitColor;
}($, cornerstoneWADOImageLoader)); }($, cornerstoneWADOImageLoader));
/** /**

File diff suppressed because one or more lines are too long