- Update cornerstoneTools from "v0.7.8 - 2016-03-17" to "v0.7.8 - 2016-05-17"
- Update cornerstoneWADOImageLoader from "v0.9.1 - 2016-02-09" to "v0.13.3 - 2016-06-02" - Update dicomParser from "v1.2.1 - 2016-02-07" to "v1.7.1 - 2016-06-09"
This commit is contained in:
parent
7ffe59655c
commit
8e8654bec1
@ -1,9 +1,13 @@
|
|||||||
/*! cornerstoneTools - v0.7.8 - 2016-03-17 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
|
/*! cornerstoneTools - v0.7.8 - 2016-05-17 | (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 dicomParser === 'undefined') {
|
||||||
|
dicomParser = {};
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof cornerstoneTools === 'undefined') {
|
if (typeof cornerstoneTools === 'undefined') {
|
||||||
cornerstoneTools = {
|
cornerstoneTools = {
|
||||||
referenceLines: {},
|
referenceLines: {},
|
||||||
@ -18,39 +22,64 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var scrollTimeout;
|
||||||
|
var scrollTimeoutDelay = 1;
|
||||||
|
|
||||||
function mouseWheel(e) {
|
function mouseWheel(e) {
|
||||||
// !!!HACK/NOTE/WARNING!!!
|
clearTimeout(scrollTimeout);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var element = e.currentTarget;
|
scrollTimeout = setTimeout(function() {
|
||||||
var startingCoords = cornerstone.pageToPixel(element, e.pageX || e.originalEvent.pageX, e.pageY || e.originalEvent.pageY);
|
// !!!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;
|
||||||
|
}
|
||||||
|
|
||||||
e = window.event || e; // old IE support
|
var element = e.currentTarget;
|
||||||
var wheelDelta = e.wheelDelta || -e.detail || -e.originalEvent.detail;
|
|
||||||
var direction = Math.max(-1, Math.min(1, (wheelDelta)));
|
|
||||||
|
|
||||||
var mouseWheelData = {
|
var x;
|
||||||
element: element,
|
var y;
|
||||||
viewport: cornerstone.getViewport(element),
|
|
||||||
image: cornerstone.getEnabledElement(element).image,
|
|
||||||
direction: direction,
|
|
||||||
pageX: e.pageX || e.originalEvent.pageX,
|
|
||||||
pageY: e.pageY || e.originalEvent.pageY,
|
|
||||||
imageX: startingCoords.x,
|
|
||||||
imageY: startingCoords.y
|
|
||||||
};
|
|
||||||
|
|
||||||
$(element).trigger('CornerstoneToolsMouseWheel', mouseWheelData);
|
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);
|
||||||
|
|
||||||
|
e = window.event || e; // old IE support
|
||||||
|
var wheelDelta = e.wheelDelta || -e.detail || -e.originalEvent.detail || -e.originalEvent.deltaY;
|
||||||
|
var direction = Math.max(-1, Math.min(1, (wheelDelta)));
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
var mouseWheelEvents = 'mousewheel DOMMouseScroll';
|
var mouseWheelEvents = 'mousewheel DOMMouseScroll';
|
||||||
@ -2109,6 +2138,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
$(mouseEventData.element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
$(mouseEventData.element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
||||||
$(mouseEventData.element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
$(mouseEventData.element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
||||||
$(mouseEventData.element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
$(mouseEventData.element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
||||||
|
$(mouseEventData.element).on('CornerstoneToolsMouseDoubleClick', eventData, cornerstoneTools.arrowAnnotate.mouseDoubleClickCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// associate this data with this imageId so we can render it and manipulate it
|
// associate this data with this imageId so we can render it and manipulate it
|
||||||
@ -2119,6 +2149,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
$(mouseEventData.element).off('CornerstoneToolsMouseMove', cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
$(mouseEventData.element).off('CornerstoneToolsMouseMove', cornerstoneTools.arrowAnnotate.mouseMoveCallback);
|
||||||
$(mouseEventData.element).off('CornerstoneToolsMouseDown', cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
$(mouseEventData.element).off('CornerstoneToolsMouseDown', cornerstoneTools.arrowAnnotate.mouseDownCallback);
|
||||||
$(mouseEventData.element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
$(mouseEventData.element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.arrowAnnotate.mouseDownActivateCallback);
|
||||||
|
$(mouseEventData.element).off('CornerstoneToolsMouseDoubleClick', cornerstoneTools.arrowAnnotate.mouseDoubleClickCallback);
|
||||||
|
|
||||||
cornerstone.updateImage(mouseEventData.element);
|
cornerstone.updateImage(mouseEventData.element);
|
||||||
cornerstoneTools.moveNewHandle(mouseEventData, toolType, measurementData, measurementData.handles.end, function() {
|
cornerstoneTools.moveNewHandle(mouseEventData, toolType, measurementData, measurementData.handles.end, function() {
|
||||||
@ -2886,11 +2917,12 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
toolCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x,
|
toolCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x,
|
||||||
eventData.currentPoints.page.y - cornerstoneTools.textStyle.getFontSize() * 4);
|
eventData.currentPoints.page.y - cornerstoneTools.textStyle.getFontSize() * 4);
|
||||||
} else {
|
} else {
|
||||||
toolCoords = eventData.currentPoints.image;
|
toolCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x,
|
||||||
|
eventData.currentPoints.page.y - cornerstoneTools.textStyle.getFontSize() / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
var storedPixels;
|
var storedPixels;
|
||||||
var text;
|
var text = '';
|
||||||
|
|
||||||
if (toolCoords.x < 0 || toolCoords.y < 0 ||
|
if (toolCoords.x < 0 || toolCoords.y < 0 ||
|
||||||
toolCoords.x >= image.columns || toolCoords.y >= image.rows) {
|
toolCoords.x >= image.columns || toolCoords.y >= image.rows) {
|
||||||
@ -2898,12 +2930,29 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (image.color) {
|
if (image.color) {
|
||||||
storedPixels = cornerstone.getStoredPixels(element, toolCoords.x, toolCoords.y, 3, 1);
|
storedPixels = cornerstoneTools.getRGBPixels(element, toolCoords.x, toolCoords.y, 1, 1);
|
||||||
text = 'R: ' + storedPixels[0] + ' G: ' + storedPixels[1] + ' B: ' + storedPixels[2];
|
text = 'R: ' + storedPixels[0] + ' G: ' + storedPixels[1] + ' B: ' + storedPixels[2];
|
||||||
} else {
|
} else {
|
||||||
storedPixels = cornerstone.getStoredPixels(element, toolCoords.x, toolCoords.y, 1, 1);
|
storedPixels = cornerstone.getStoredPixels(element, toolCoords.x, toolCoords.y, 1, 1);
|
||||||
var huValue = storedPixels[0] * image.slope + image.intercept;
|
var sp = storedPixels[0];
|
||||||
text = parseFloat(huValue.toFixed(3));
|
var mo = sp * eventData.image.slope + eventData.image.intercept;
|
||||||
|
var suv = cornerstoneTools.calculateSUV(eventData.image, sp);
|
||||||
|
|
||||||
|
var modalityTag = 'x00080060';
|
||||||
|
var modality;
|
||||||
|
if (eventData.image.data) {
|
||||||
|
modality = eventData.image.data.string(modalityTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modality === 'CT') {
|
||||||
|
text += 'HU: ';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw text
|
||||||
|
text += parseFloat(mo.toFixed(2));
|
||||||
|
if (suv) {
|
||||||
|
text += ' SUV: ' + parseFloat(suv.toFixed(2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare text
|
// Prepare text
|
||||||
@ -2913,26 +2962,26 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
// Translate the x/y away from the cursor
|
// Translate the x/y away from the cursor
|
||||||
var translation;
|
var translation;
|
||||||
|
var handleRadius = 6;
|
||||||
|
var width = context.measureText(text).width;
|
||||||
|
|
||||||
if (eventData.isTouchEvent === true) {
|
if (eventData.isTouchEvent === true) {
|
||||||
var handleRadius = 6;
|
|
||||||
var width = context.measureText(text).width;
|
|
||||||
|
|
||||||
translation = {
|
translation = {
|
||||||
x: -width / 2 - 5,
|
x: -width / 2 - 5,
|
||||||
y: -cornerstoneTools.textStyle.getFontSize() - 10 - 2 * handleRadius
|
y: -cornerstoneTools.textStyle.getFontSize() - 10 - 2 * handleRadius
|
||||||
};
|
};
|
||||||
|
|
||||||
context.beginPath();
|
|
||||||
context.strokeStyle = color;
|
|
||||||
context.arc(textCoords.x, textCoords.y, handleRadius, 0, 2 * Math.PI);
|
|
||||||
context.stroke();
|
|
||||||
} else {
|
} else {
|
||||||
translation = {
|
translation = {
|
||||||
x: 4,
|
x: 12,
|
||||||
y: -4
|
y: -(cornerstoneTools.textStyle.getFontSize() + 10) / 2
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.beginPath();
|
||||||
|
context.strokeStyle = color;
|
||||||
|
context.arc(textCoords.x, textCoords.y, handleRadius, 0, 2 * Math.PI);
|
||||||
|
context.stroke();
|
||||||
|
|
||||||
cornerstoneTools.drawTextBox(context, text, textCoords.x + translation.x, textCoords.y + translation.y, color);
|
cornerstoneTools.drawTextBox(context, text, textCoords.x + translation.x, textCoords.y + translation.y, color);
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
@ -3133,118 +3182,208 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onImageRendered(e, eventData) {
|
function onImageRendered(e, eventData) {
|
||||||
|
// If we have no toolData for this element, return immediately as there is nothing to do
|
||||||
// if we have no toolData for this element, return immediately as there is nothing to do
|
|
||||||
var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
|
var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
|
||||||
if (!toolData) {
|
if (!toolData) {
|
||||||
return;
|
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.ellipticalRoi.getConfiguration();
|
||||||
var context = eventData.canvasContext.canvas.getContext('2d');
|
var context = eventData.canvasContext.canvas.getContext('2d');
|
||||||
context.setTransform(1, 0, 0, 1, 0, 0);
|
context.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
|
||||||
//activation color
|
// Retrieve the image modality from its metadata, if available
|
||||||
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
|
var modalityTag = 'x00080060';
|
||||||
var config = cornerstoneTools.ellipticalRoi.getConfiguration();
|
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++) {
|
for (var i = 0; i < toolData.data.length; i++) {
|
||||||
context.save();
|
context.save();
|
||||||
|
|
||||||
|
var data = toolData.data[i];
|
||||||
|
|
||||||
|
// Apply any shadow settings defined in the tool configuration
|
||||||
if (config && config.shadow) {
|
if (config && config.shadow) {
|
||||||
context.shadowColor = config.shadowColor || '#000000';
|
context.shadowColor = config.shadowColor || '#000000';
|
||||||
context.shadowOffsetX = config.shadowOffsetX || 1;
|
context.shadowOffsetX = config.shadowOffsetX || 1;
|
||||||
context.shadowOffsetY = config.shadowOffsetY || 1;
|
context.shadowOffsetY = config.shadowOffsetY || 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = toolData.data[i];
|
// Check which color the rendered tool should be
|
||||||
|
|
||||||
//differentiate the color of activation tool
|
|
||||||
var color = cornerstoneTools.toolColors.getColorIfActive(data.active);
|
var color = cornerstoneTools.toolColors.getColorIfActive(data.active);
|
||||||
|
|
||||||
// draw the ellipse
|
// Convert Image coordinates to Canvas coordinates given the element
|
||||||
var handleStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.start);
|
var handleStartCanvas = cornerstone.pixelToCanvas(element, data.handles.start);
|
||||||
var handleEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.end);
|
var handleEndCanvas = cornerstone.pixelToCanvas(element, data.handles.end);
|
||||||
|
|
||||||
var widthCanvas = Math.abs(handleStartCanvas.x - handleEndCanvas.x);
|
// Retrieve the bounds of the ellipse (left, top, width, and height)
|
||||||
var heightCanvas = Math.abs(handleStartCanvas.y - handleEndCanvas.y);
|
// in Canvas coordinates
|
||||||
var leftCanvas = Math.min(handleStartCanvas.x, handleEndCanvas.x);
|
var leftCanvas = Math.min(handleStartCanvas.x, handleEndCanvas.x);
|
||||||
var topCanvas = Math.min(handleStartCanvas.y, handleEndCanvas.y);
|
var topCanvas = Math.min(handleStartCanvas.y, handleEndCanvas.y);
|
||||||
|
var widthCanvas = Math.abs(handleStartCanvas.x - handleEndCanvas.x);
|
||||||
|
var heightCanvas = Math.abs(handleStartCanvas.y - handleEndCanvas.y);
|
||||||
|
|
||||||
|
// Draw the ellipse on the canvas
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.strokeStyle = color;
|
context.strokeStyle = color;
|
||||||
context.lineWidth = lineWidth;
|
context.lineWidth = lineWidth;
|
||||||
cornerstoneTools.drawEllipse(context, leftCanvas, topCanvas, widthCanvas, heightCanvas);
|
cornerstoneTools.drawEllipse(context, leftCanvas, topCanvas, widthCanvas, heightCanvas);
|
||||||
context.closePath();
|
context.closePath();
|
||||||
|
|
||||||
// draw the handles
|
// If the tool configuration specifies to only draw the handles on hover / active,
|
||||||
var handleOptions = {
|
// follow this logic
|
||||||
drawHandlesIfActive: (config && config.drawHandlesOnHover)
|
if (config && config.drawHandlesOnHover) {
|
||||||
};
|
// Draw the handles if the tool is active
|
||||||
|
if (data.active === true) {
|
||||||
cornerstoneTools.drawHandles(context, eventData, data.handles, color, handleOptions);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define variables for the area and mean/standard deviation
|
||||||
var area,
|
var area,
|
||||||
meanStdDev;
|
meanStdDev,
|
||||||
|
meanStdDevSUV;
|
||||||
|
|
||||||
|
// 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 (!data.invalidated) {
|
||||||
|
// If the data is not invalidated, retrieve it from the toolData
|
||||||
meanStdDev = data.meanStdDev;
|
meanStdDev = data.meanStdDev;
|
||||||
|
meanStdDevSUV = data.meanStdDevSUV;
|
||||||
area = data.area;
|
area = data.area;
|
||||||
} else {
|
} else {
|
||||||
// TODO: calculate this in web worker for large pixel counts...
|
// If the data has been invalidated, we need to calculate it again
|
||||||
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);
|
|
||||||
|
|
||||||
|
// Retrieve the bounds of the ellipse in image coordinates
|
||||||
var ellipse = {
|
var ellipse = {
|
||||||
left: left,
|
left: Math.min(data.handles.start.x, data.handles.end.x),
|
||||||
top: top,
|
top: Math.min(data.handles.start.y, data.handles.end.y),
|
||||||
width: width,
|
width: Math.abs(data.handles.start.x - data.handles.end.x),
|
||||||
height: height
|
height: Math.abs(data.handles.start.y - data.handles.end.y)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate the mean, stddev, and area
|
// First, make sure this is not a color image, since no mean / standard
|
||||||
meanStdDev = calculateMeanStdDev(pixels, ellipse);
|
// deviation will be calculated for color images.
|
||||||
area = Math.PI * (width * eventData.image.columnPixelSpacing / 2) * (height * eventData.image.rowPixelSpacing / 2);
|
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);
|
||||||
|
|
||||||
data.invalidated = false;
|
// 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 = Math.PI * (ellipse.width * columnPixelSpacing / 2) * (ellipse.height * rowPixelSpacing / 2);
|
||||||
|
|
||||||
|
// If the area value is sane, store it for later retrieval
|
||||||
if (!isNaN(area)) {
|
if (!isNaN(area)) {
|
||||||
data.area = area;
|
data.area = area;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNaN(meanStdDev.mean) && !isNaN(meanStdDev.stdDev)) {
|
// Set the invalidated flag to false so that this data won't automatically be recalculated
|
||||||
data.meanStdDev = meanStdDev;
|
data.invalidated = false;
|
||||||
data.mean = meanStdDev.mean;
|
|
||||||
data.stdev = meanStdDev.stdev;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define an array to store the rows of text for the textbox
|
||||||
var textLines = [];
|
var textLines = [];
|
||||||
if (meanStdDev) {
|
|
||||||
var meanText = 'Mean: ' + numberWithCommas(meanStdDev.mean.toFixed(2));
|
|
||||||
textLines.push(meanText);
|
|
||||||
|
|
||||||
var stdDevText = 'StdDev: ' + numberWithCommas(meanStdDev.stdDev.toFixed(2));
|
// 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);
|
textLines.push(stdDevText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (area !== undefined && !isNaN(area)) {
|
// If the area is a sane value, display it
|
||||||
// Char code 178 is a superscript 2 for mm^2
|
if (area) {
|
||||||
var areaText = 'Area: ' + numberWithCommas(area.toFixed(2)) + ' mm' + String.fromCharCode(178);
|
// 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);
|
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) {
|
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.x = Math.max(data.handles.start.x, data.handles.end.x);
|
||||||
data.handles.textBox.y = (data.handles.start.y + data.handles.end.y) / 2;
|
data.handles.textBox.y = (data.handles.start.y + data.handles.end.y) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
var textCoords = cornerstone.pixelToCanvas(eventData.element, data.handles.textBox);
|
// Convert the textbox Image coordinates into Canvas coordinates
|
||||||
|
var textCoords = cornerstone.pixelToCanvas(element, data.handles.textBox);
|
||||||
|
|
||||||
// Draw text
|
// Set options for the textbox drawing function
|
||||||
var options = {
|
var options = {
|
||||||
centering: {
|
centering: {
|
||||||
x: false,
|
x: false,
|
||||||
@ -3252,18 +3391,30 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Draw the textbox and retrieves it's bounding box for mouse-dragging and highlighting
|
||||||
var boundingBox = cornerstoneTools.drawTextBox(context, textLines, textCoords.x,
|
var boundingBox = cornerstoneTools.drawTextBox(context, textLines, textCoords.x,
|
||||||
textCoords.y, color, options);
|
textCoords.y, color, options);
|
||||||
|
|
||||||
|
// Store the bounding box data in the handle for mouse-dragging and highlighting
|
||||||
data.handles.textBox.boundingBox = boundingBox;
|
data.handles.textBox.boundingBox = boundingBox;
|
||||||
|
|
||||||
|
// 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) {
|
if (data.handles.textBox.hasMoved) {
|
||||||
// Draw dashed link line between tool and text
|
// Draw dashed link line between tool and text
|
||||||
|
|
||||||
|
// The initial link position is at the center of the
|
||||||
|
// textbox.
|
||||||
var link = {
|
var link = {
|
||||||
start: {},
|
start: {},
|
||||||
end: {}
|
end: {
|
||||||
|
x: textCoords.x,
|
||||||
|
y: textCoords.y
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// First we calculate the ellipse points (top, left, right, and bottom)
|
||||||
var ellipsePoints = [ {
|
var ellipsePoints = [ {
|
||||||
// Top middle point of ellipse
|
// Top middle point of ellipse
|
||||||
x: leftCanvas + widthCanvas / 2,
|
x: leftCanvas + widthCanvas / 2,
|
||||||
@ -3280,14 +3431,13 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
// Right middle point of ellipse
|
// Right middle point of ellipse
|
||||||
x: leftCanvas + widthCanvas,
|
x: leftCanvas + widthCanvas,
|
||||||
y: topCanvas + heightCanvas / 2
|
y: topCanvas + heightCanvas / 2
|
||||||
},
|
} ];
|
||||||
];
|
|
||||||
|
|
||||||
link.end.x = textCoords.x;
|
|
||||||
link.end.y = textCoords.y;
|
|
||||||
|
|
||||||
|
// 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);
|
link.start = cornerstoneMath.point.findClosestPoint(ellipsePoints, link.end);
|
||||||
|
|
||||||
|
// Next we calculate the corners of the textbox bounding box
|
||||||
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,
|
||||||
@ -3304,11 +3454,13 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
// 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
|
||||||
},
|
}, ];
|
||||||
];
|
|
||||||
|
|
||||||
|
// 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);
|
link.end = cornerstoneMath.point.findClosestPoint(boundingBoxPoints, link.start);
|
||||||
|
|
||||||
|
// Finally we draw the dashed linking line
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.strokeStyle = color;
|
context.strokeStyle = color;
|
||||||
context.lineWidth = lineWidth;
|
context.lineWidth = lineWidth;
|
||||||
@ -5484,11 +5636,26 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
x: mouseEventData.currentPoints.image.x,
|
x: mouseEventData.currentPoints.image.x,
|
||||||
y: mouseEventData.currentPoints.image.y,
|
y: mouseEventData.currentPoints.image.y,
|
||||||
highlight: true,
|
highlight: true,
|
||||||
active: true
|
active: true,
|
||||||
|
hasBoundingBox: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create a rectangle representing the image
|
||||||
|
var imageRect = {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: mouseEventData.image.width,
|
||||||
|
height: mouseEventData.image.height
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the current handle is outside the image,
|
||||||
|
// If it is, prevent the handle creation
|
||||||
|
if (!cornerstoneMath.point.insideRect(measurementData.handles.end, imageRect)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the current marker for the next marker
|
// Update the current marker for the next marker
|
||||||
var currentIndex = config.markers.indexOf(config.current);
|
var currentIndex = config.markers.indexOf(config.current);
|
||||||
if (config.ascending) {
|
if (config.ascending) {
|
||||||
@ -5519,12 +5686,13 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
///////// BEGIN IMAGE RENDERING ///////
|
///////// BEGIN IMAGE RENDERING ///////
|
||||||
function pointNearTool(element, data, coords) {
|
function pointNearTool(element, data, coords) {
|
||||||
if (!data.textBoundingBox) {
|
if (!data.handles.end.boundingBox) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var distanceToPoint = cornerstoneMath.rect.distanceToPoint(data.textBoundingBox, coords);
|
var distanceToPoint = cornerstoneMath.rect.distanceToPoint(data.handles.end.boundingBox, coords);
|
||||||
return (distanceToPoint < 10);
|
var insideBoundingBox = cornerstoneTools.pointInsideBoundingBox(data.handles.end, coords);
|
||||||
|
return (distanceToPoint < 10) || insideBoundingBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onImageRendered(e, eventData) {
|
function onImageRendered(e, eventData) {
|
||||||
@ -5563,8 +5731,15 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
var textCoords = cornerstone.pixelToCanvas(eventData.element, data.handles.end);
|
var textCoords = cornerstone.pixelToCanvas(eventData.element, data.handles.end);
|
||||||
|
|
||||||
var boundingBox = cornerstoneTools.drawTextBox(context, data.text, textCoords.x - data.textWidth / 2, textCoords.y, color);
|
var options = {
|
||||||
data.textBoundingBox = boundingBox;
|
centering: {
|
||||||
|
x: true,
|
||||||
|
y: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var boundingBox = cornerstoneTools.drawTextBox(context, data.text, textCoords.x, textCoords.y - 10, color, options);
|
||||||
|
data.handles.end.boundingBox = boundingBox;
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
@ -5583,10 +5758,15 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
data.active = false;
|
data.active = false;
|
||||||
cornerstone.updateImage(element);
|
cornerstone.updateImage(element);
|
||||||
$(element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.textMarker.mouseMoveCallback);
|
|
||||||
$(element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.textMarker.mouseDownCallback);
|
var mouseButtonData = {
|
||||||
$(element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.textMarker.mouseDownActivateCallback);
|
mouseButtonMask: e.data.mouseButtonMask
|
||||||
$(element).on('CornerstoneToolsMouseDoubleClick', eventData, cornerstoneTools.textMarker.mouseDoubleClickCallback);
|
};
|
||||||
|
|
||||||
|
$(element).on('CornerstoneToolsMouseMove', mouseButtonData, cornerstoneTools.textMarker.mouseMoveCallback);
|
||||||
|
$(element).on('CornerstoneToolsMouseDown', mouseButtonData, cornerstoneTools.textMarker.mouseDownCallback);
|
||||||
|
$(element).on('CornerstoneToolsMouseDownActivate', mouseButtonData, cornerstoneTools.textMarker.mouseDownActivateCallback);
|
||||||
|
$(element).on('CornerstoneToolsMouseDoubleClick', mouseButtonData, cornerstoneTools.textMarker.mouseDoubleClickCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.data && e.data.mouseButtonMask && !cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
|
if (e.data && e.data.mouseButtonMask && !cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
|
||||||
@ -5645,10 +5825,6 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
$(element).on('CornerstoneToolsTouchPress', cornerstoneTools.textMarkerTouch.pressCallback);
|
$(element).on('CornerstoneToolsTouchPress', cornerstoneTools.textMarkerTouch.pressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.data && e.data.mouseButtonMask && !cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = cornerstoneTools.textMarker.getConfiguration();
|
var config = cornerstoneTools.textMarker.getConfiguration();
|
||||||
|
|
||||||
var coords = eventData.currentPoints.canvas;
|
var coords = eventData.currentPoints.canvas;
|
||||||
@ -6322,6 +6498,13 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
|
|
||||||
function mouseWheelCallback(e, eventData) {
|
function mouseWheelCallback(e, eventData) {
|
||||||
var ticks = -eventData.direction / 4;
|
var ticks = -eventData.direction / 4;
|
||||||
|
|
||||||
|
// Allow inversion of the mouse wheel scroll via a configuration option
|
||||||
|
var config = cornerstoneTools.zoom.getConfiguration();
|
||||||
|
if (config && config.invert) {
|
||||||
|
ticks *= -1;
|
||||||
|
}
|
||||||
|
|
||||||
var viewport = changeViewportScale(eventData.viewport, ticks);
|
var viewport = changeViewportScale(eventData.viewport, ticks);
|
||||||
cornerstone.setViewport(eventData.element, viewport);
|
cornerstone.setViewport(eventData.element, viewport);
|
||||||
}
|
}
|
||||||
@ -7729,6 +7912,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
numRequests[type]--;
|
numRequests[type]--;
|
||||||
// console.log(numRequests);
|
// console.log(numRequests);
|
||||||
failCallback(error);
|
failCallback(error);
|
||||||
|
startAgain();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -7750,6 +7934,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
numRequests[type]--;
|
numRequests[type]--;
|
||||||
// console.log(numRequests);
|
// console.log(numRequests);
|
||||||
failCallback(error);
|
failCallback(error);
|
||||||
|
startAgain();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7853,7 +8038,7 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) {
|
if (!playClipToolData || !playClipToolData.data || !playClipToolData.data.length) {
|
||||||
playClipData = {
|
playClipData = {
|
||||||
intervalId: undefined,
|
intervalId: undefined,
|
||||||
framesPerSecond: framesPerSecond || 30,
|
framesPerSecond: 30,
|
||||||
lastFrameTimeStamp: undefined,
|
lastFrameTimeStamp: undefined,
|
||||||
frameRate: 0,
|
frameRate: 0,
|
||||||
loop: true
|
loop: true
|
||||||
@ -7863,6 +8048,11 @@ if (typeof cornerstoneTools === 'undefined') {
|
|||||||
playClipData = playClipToolData.data[0];
|
playClipData = playClipToolData.data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a framerate is specified, update the playClipData now
|
||||||
|
if (framesPerSecond) {
|
||||||
|
playClipData.framesPerSecond = framesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
// if already playing, do not set a new interval
|
// if already playing, do not set a new interval
|
||||||
if (playClipData.intervalId !== undefined) {
|
if (playClipData.intervalId !== undefined) {
|
||||||
return;
|
return;
|
||||||
@ -8143,16 +8333,6 @@ Display scroll progress bar across bottom of image.
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function doneCallback(image) {
|
|
||||||
//console.log('prefetch done: ' + image.imageId);
|
|
||||||
var imageIdIndex = stack.imageIds.indexOf(image.imageId);
|
|
||||||
removeFromList(imageIdIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
function failCallback(error) {
|
|
||||||
console.log('prefetch errored: ' + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the requestPool of prefetch requests
|
// Clear the requestPool of prefetch requests
|
||||||
var requestPoolManager = cornerstoneTools.requestPoolManager;
|
var requestPoolManager = cornerstoneTools.requestPoolManager;
|
||||||
requestPoolManager.clearRequestStack(requestType);
|
requestPoolManager.clearRequestStack(requestType);
|
||||||
@ -8164,6 +8344,22 @@ Display scroll progress bar across bottom of image.
|
|||||||
nextImageIdIndex,
|
nextImageIdIndex,
|
||||||
preventCache = false;
|
preventCache = false;
|
||||||
|
|
||||||
|
function doneCallback(image) {
|
||||||
|
//console.log('prefetch done: ' + image.imageId);
|
||||||
|
var imageIdIndex = stack.imageIds.indexOf(image.imageId);
|
||||||
|
removeFromList(imageIdIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the errorLoadingHandler if one exists
|
||||||
|
var errorLoadingHandler = cornerstoneTools.loadHandlerManager.getErrorLoadingHandler();
|
||||||
|
|
||||||
|
function failCallback(error) {
|
||||||
|
console.log('prefetch errored: ' + error);
|
||||||
|
if (errorLoadingHandler) {
|
||||||
|
errorLoadingHandler(element, imageId, error, 'stackPrefetch');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Prefetch images around the current image (before and after)
|
// Prefetch images around the current image (before and after)
|
||||||
var lowerIndex = nearest.low;
|
var lowerIndex = nearest.low;
|
||||||
var higherIndex = nearest.high;
|
var higherIndex = nearest.high;
|
||||||
@ -10288,41 +10484,50 @@ Display scroll progress bar across bottom of image.
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// Returns a decimal value given a fractional value
|
||||||
|
function fracToDec(fractionalValue) {
|
||||||
|
return parseFloat('.' + fractionalValue);
|
||||||
|
}
|
||||||
|
|
||||||
function calculateSUV(image, storedPixelValue) {
|
function calculateSUV(image, storedPixelValue) {
|
||||||
// if no dicom data set, return undefined
|
if (!dicomParser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no dicom data set, return
|
||||||
if (image.data === undefined) {
|
if (image.data === undefined) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// image must be PET
|
// image must be PET
|
||||||
if (image.data.string('x00080060') !== 'PT') {
|
if (image.data.string('x00080060') !== 'PT') {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var modalityPixelValue = storedPixelValue * image.slope + image.intercept;
|
var modalityPixelValue = storedPixelValue * image.slope + image.intercept;
|
||||||
|
|
||||||
var patientWeight = image.data.floatString('x00101030'); // in kg
|
var patientWeight = image.data.floatString('x00101030'); // in kg
|
||||||
if (patientWeight === undefined) {
|
if (patientWeight === undefined) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var petSequence = image.data.elements.x00540016;
|
var petSequence = image.data.elements.x00540016;
|
||||||
if (petSequence === undefined) {
|
if (petSequence === undefined) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
petSequence = petSequence.items[0].dataSet;
|
petSequence = petSequence.items[0].dataSet;
|
||||||
var startTime = petSequence.time('x00181072');
|
var startTime = dicomParser.parseTM(petSequence.string('x00181072'));
|
||||||
var totalDose = petSequence.floatString('x00181074');
|
var totalDose = petSequence.floatString('x00181074');
|
||||||
var halfLife = petSequence.floatString('x00181075');
|
var halfLife = petSequence.floatString('x00181075');
|
||||||
var acquisitionTime = image.data.time('x00080032');
|
var seriesAcquisitionTime = dicomParser.parseTM(image.data.string('x00080031'));
|
||||||
|
|
||||||
if (!startTime || !totalDose || !halfLife || !acquisitionTime) {
|
if (!startTime || !totalDose || !halfLife || !seriesAcquisitionTime) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var acquisitionTimeInSeconds = acquisitionTime.fractionalSeconds + acquisitionTime.seconds + acquisitionTime.minutes * 60 + acquisitionTime.hours * 60 * 60;
|
var acquisitionTimeInSeconds = fracToDec(seriesAcquisitionTime.fractionalSeconds) + seriesAcquisitionTime.seconds + seriesAcquisitionTime.minutes * 60 + seriesAcquisitionTime.hours * 60 * 60;
|
||||||
var injectionStartTimeInSeconds = startTime.fractionalSeconds + startTime.seconds + startTime.minutes * 60 + startTime.hours * 60 * 60;
|
var injectionStartTimeInSeconds = fracToDec(startTime.fractionalSeconds) + startTime.seconds + startTime.minutes * 60 + startTime.hours * 60 * 60;
|
||||||
var durationInSeconds = acquisitionTimeInSeconds - injectionStartTimeInSeconds;
|
var durationInSeconds = acquisitionTimeInSeconds - injectionStartTimeInSeconds;
|
||||||
var correctedDose = totalDose * Math.exp(-durationInSeconds * Math.log(2) / halfLife);
|
var correctedDose = totalDose * Math.exp(-durationInSeconds * Math.log(2) / halfLife);
|
||||||
var suv = modalityPixelValue * patientWeight / correctedDose * 1000;
|
var suv = modalityPixelValue * patientWeight / correctedDose * 1000;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
/*! dicom-parser - v1.2.1 - 2016-02-07 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
|
/*! dicom-parser - v1.7.1 - 2016-06-09 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
|
|
||||||
// node.js
|
// node.js
|
||||||
@ -22,17 +22,17 @@
|
|||||||
}
|
}
|
||||||
}(this, function () {
|
}(this, function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a DICOM P10 byte array and returns a DataSet object with the parsed elements. If the options
|
* Parses a DICOM P10 byte array and returns a DataSet object with the parsed elements. If the options
|
||||||
* argument is supplied and it contains the untilTag property, parsing will stop once that
|
* argument is supplied and it contains the untilTag property, parsing will stop once that
|
||||||
* tag is encoutered. This can be used to parse partial byte streams.
|
* tag is encoutered. This can be used to parse partial byte streams.
|
||||||
*
|
*
|
||||||
* @param byteArray the byte array
|
* @param byteArray the byte array
|
||||||
* @param options object to control parsing behavior (optional)
|
* @param options object to control parsing behavior (optional)
|
||||||
* @returns {DataSet}
|
* @returns {DataSet}
|
||||||
* @throws error if an error occurs while parsing. The exception object will contain a property dataSet with the
|
* @throws error if an error occurs while parsing. The exception object will contain a property dataSet with the
|
||||||
* elements successfully parsed before the error.
|
* elements successfully parsed before the error.
|
||||||
*/
|
*/
|
||||||
var dicomParser = (function(dicomParser) {
|
var dicomParser = (function(dicomParser) {
|
||||||
if(dicomParser === undefined)
|
if(dicomParser === undefined)
|
||||||
{
|
{
|
||||||
@ -66,18 +66,42 @@ var dicomParser = (function(dicomParser) {
|
|||||||
function getDataSetByteStream(transferSyntax, position) {
|
function getDataSetByteStream(transferSyntax, position) {
|
||||||
if(transferSyntax === '1.2.840.10008.1.2.1.99')
|
if(transferSyntax === '1.2.840.10008.1.2.1.99')
|
||||||
{
|
{
|
||||||
// https://github.com/nodeca/pako
|
// if an infalter callback is registered, use it
|
||||||
if(typeof(pako) === "undefined") {
|
if (options && options.inflater) {
|
||||||
throw 'dicomParser.parseDicom: deflated transfer syntax encountered but pako not loaded';
|
var fullByteArrayCallback = options.inflater(byteArray, position);
|
||||||
}
|
return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArrayCallback, 0);
|
||||||
var deflated = byteArray.slice(position);
|
}
|
||||||
var inflated = pako.inflateRaw(deflated);
|
// if running on node, use the zlib library to inflate
|
||||||
|
// http://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js
|
||||||
|
else if (typeof module !== 'undefined' && this.module !== module) {
|
||||||
|
// inflate it
|
||||||
|
var zlib = require('zlib');
|
||||||
|
var deflatedBuffer = dicomParser.sharedCopy(byteArray, position, byteArray.length - position);
|
||||||
|
var inflatedBuffer = zlib.inflateRawSync(deflatedBuffer);
|
||||||
|
|
||||||
// create a single byte array with the full header bytes and the inflated bytes
|
// create a single byte array with the full header bytes and the inflated bytes
|
||||||
var fullByteArray = new Uint8Array(inflated.length + position);
|
var fullByteArrayBuffer = dicomParser.alloc(byteArray, inflatedBuffer.length + position);
|
||||||
fullByteArray.set(byteArray.slice(0, position), 0);
|
byteArray.copy(fullByteArrayBuffer, 0, 0, position);
|
||||||
fullByteArray.set(inflated, position);
|
inflatedBuffer.copy(fullByteArrayBuffer, position);
|
||||||
return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArray, 0);
|
return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArrayBuffer, 0);
|
||||||
|
}
|
||||||
|
// if pako is defined - use it. This is the web browser path
|
||||||
|
// https://github.com/nodeca/pako
|
||||||
|
else if(typeof pako !== "undefined") {
|
||||||
|
// inflate it
|
||||||
|
var deflated = byteArray.slice(position);
|
||||||
|
var inflated = pako.inflateRaw(deflated);
|
||||||
|
|
||||||
|
// create a single byte array with the full header bytes and the inflated bytes
|
||||||
|
var fullByteArray = dicomParser.alloc(byteArray, inflated.length + position);
|
||||||
|
fullByteArray.set(byteArray.slice(0, position), 0);
|
||||||
|
fullByteArray.set(inflated, position);
|
||||||
|
return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArray, 0);
|
||||||
|
}
|
||||||
|
// throw exception since no inflater is available
|
||||||
|
else {
|
||||||
|
throw 'dicomParser.parseDicom: no inflater available to handle deflate transfer syntax';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(transferSyntax === '1.2.840.10008.1.2.2') // explicit big endian
|
if(transferSyntax === '1.2.840.10008.1.2.2') // explicit big endian
|
||||||
{
|
{
|
||||||
@ -151,6 +175,124 @@ var dicomParser = (function(dicomParser) {
|
|||||||
return dicomParser;
|
return dicomParser;
|
||||||
})(dicomParser);
|
})(dicomParser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function for creating a basic offset table for JPEG transfer syntaxes
|
||||||
|
*/
|
||||||
|
|
||||||
|
var dicomParser = (function (dicomParser)
|
||||||
|
{
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if(dicomParser === undefined)
|
||||||
|
{
|
||||||
|
dicomParser = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each JPEG image has an end of image marker 0xFFD9
|
||||||
|
function isEndOfImageMarker(dataSet, position) {
|
||||||
|
return (dataSet.byteArray[position] === 0xFF &&
|
||||||
|
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
|
||||||
|
// fragments are zero padded
|
||||||
|
if(isEndOfImageMarker(dataSet, fragment.position + fragment.length - 2) ||
|
||||||
|
isEndOfImageMarker(dataSet, fragment.position + fragment.length - 3)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a basic offset table by scanning fragments for JPEG start of image and end Of Image markers
|
||||||
|
* @param {object} dataSet - the parsed dicom dataset
|
||||||
|
* @param {object} pixelDataElement - the pixel data element
|
||||||
|
* @param [fragments] - optional array of objects describing each fragment (offset, position, length)
|
||||||
|
* @returns {Array} basic offset table (array of offsets to beginning of each frame)
|
||||||
|
*/
|
||||||
|
dicomParser.createJPEGBasicOffsetTable = function(dataSet, pixelDataElement, fragments) {
|
||||||
|
// Validate parameters
|
||||||
|
if(dataSet === undefined) {
|
||||||
|
throw 'dicomParser.createJPEGBasicOffsetTable: missing required parameter dataSet';
|
||||||
|
}
|
||||||
|
if(pixelDataElement === undefined) {
|
||||||
|
throw 'dicomParser.createJPEGBasicOffsetTable: missing required parameter pixelDataElement';
|
||||||
|
}
|
||||||
|
if(pixelDataElement.tag !== 'x7fe00010') {
|
||||||
|
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to non pixel data tag (expected tag = x7fe00010'";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.encapsulatedPixelData !== true) {
|
||||||
|
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.hadUndefinedLength !== true) {
|
||||||
|
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.basicOffsetTable === undefined) {
|
||||||
|
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.fragments === undefined) {
|
||||||
|
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.fragments.length <= 0) {
|
||||||
|
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(fragments && fragments.length <=0) {
|
||||||
|
throw "dicomParser.createJPEGBasicOffsetTable: parameter 'fragments' must not be zero length";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default values
|
||||||
|
fragments = fragments || pixelDataElement.fragments;
|
||||||
|
|
||||||
|
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
|
||||||
|
basicOffsetTable.push(pixelDataElement.fragments[startFragmentIndex].offset);
|
||||||
|
var endFragmentIndex = findLastImageFrameFragmentIndex(dataSet, pixelDataElement, startFragmentIndex);
|
||||||
|
if(endFragmentIndex === undefined || endFragmentIndex === pixelDataElement.fragments.length -1) {
|
||||||
|
return basicOffsetTable;
|
||||||
|
}
|
||||||
|
startFragmentIndex = endFragmentIndex + 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return dicomParser;
|
||||||
|
}(dicomParser));
|
||||||
var dicomParser = (function (dicomParser) {
|
var dicomParser = (function (dicomParser) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
@ -533,94 +675,31 @@ var dicomParser = (function (dicomParser)
|
|||||||
dicomParser = {};
|
dicomParser = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPixelDataFromFragments(byteStream, fragments, bufferSize)
|
var deprecatedNoticeLogged = false;
|
||||||
{
|
|
||||||
// if there is only one fragment, return a view on this array to avoid copying
|
|
||||||
if(fragments.length === 1) {
|
|
||||||
return new Uint8Array(byteStream.byteArray.buffer, fragments[0].dataOffset, fragments[0].length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// more than one fragment, combine all of the fragments into one buffer
|
|
||||||
var pixelData = new Uint8Array(bufferSize);
|
|
||||||
var pixelDataIndex = 0;
|
|
||||||
for(var i=0; i < fragments.length; i++) {
|
|
||||||
var fragmentOffset = fragments[i].dataOffset;
|
|
||||||
for(var j=0; j < fragments[i].length; j++) {
|
|
||||||
pixelData[pixelDataIndex++] = byteStream.byteArray[fragmentOffset++];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pixelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readFragmentsUntil(byteStream, endOfFrame) {
|
|
||||||
// Read fragments until we reach endOfFrame
|
|
||||||
var fragments = [];
|
|
||||||
var bufferSize = 0;
|
|
||||||
while(byteStream.position < endOfFrame && byteStream.position < byteStream.byteArray.length) {
|
|
||||||
var fragment = dicomParser.readSequenceItem(byteStream);
|
|
||||||
// NOTE: we only encounter this for the sequence delimiter tag when extracting the last frame
|
|
||||||
if(fragment.tag === 'xfffee0dd') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fragments.push(fragment);
|
|
||||||
byteStream.seek(fragment.length);
|
|
||||||
bufferSize += fragment.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the fragments into a single pixelData buffer
|
|
||||||
var pixelData = getPixelDataFromFragments(byteStream, fragments, bufferSize);
|
|
||||||
return pixelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readEncapsulatedPixelDataWithBasicOffsetTable(pixelDataElement, byteStream, frame) {
|
|
||||||
// validate that we have an offset for this frame
|
|
||||||
var numFrames = pixelDataElement.basicOffsetTable.length;
|
|
||||||
if(frame > numFrames) {
|
|
||||||
throw "dicomParser.readEncapsulatedPixelData: parameter frame exceeds number of frames in basic offset table";
|
|
||||||
}
|
|
||||||
|
|
||||||
// move to the start of this frame
|
|
||||||
var frameOffset = pixelDataElement.basicOffsetTable[frame];
|
|
||||||
var firstFragment = byteStream.position;
|
|
||||||
byteStream.seek(frameOffset);
|
|
||||||
|
|
||||||
// Find the end of this frame
|
|
||||||
var endOfFrameOffset = pixelDataElement.basicOffsetTable[frame + 1];
|
|
||||||
if(endOfFrameOffset === undefined) { // special case for last frame
|
|
||||||
endOfFrameOffset = byteStream.position + pixelDataElement.length;
|
|
||||||
} else {
|
|
||||||
endOfFrameOffset += firstFragment;
|
|
||||||
}
|
|
||||||
|
|
||||||
// read this frame
|
|
||||||
var pixelData = readFragmentsUntil(byteStream, endOfFrameOffset);
|
|
||||||
return pixelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readEncapsulatedDataNoBasicOffsetTable(pixelDataElement, byteStream, frame) {
|
|
||||||
// if the basic offset table is empty, this is a single frame so make sure the requested
|
|
||||||
// frame is 0
|
|
||||||
if(frame !== 0) {
|
|
||||||
throw 'dicomParser.readEncapsulatedPixelData: non zero frame specified for single frame encapsulated pixel data';
|
|
||||||
}
|
|
||||||
|
|
||||||
// read this frame
|
|
||||||
var endOfFrame = byteStream.position + pixelDataElement.length;
|
|
||||||
var pixelData = readFragmentsUntil(byteStream, endOfFrame);
|
|
||||||
return pixelData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the pixel data for the specified frame in an encapsulated pixel data element
|
* Returns the pixel data for the specified frame in an encapsulated pixel data element. If no basic offset
|
||||||
|
* table is present, it assumes that all fragments are for one frame. Note that this assumption/logic is not
|
||||||
|
* valid for multi-frame instances so this function has been deprecated and will eventually be removed. Code
|
||||||
|
* should be updated to use readEncapsulatedPixelDataFromFragments() or readEncapsulatedImageFrame()
|
||||||
*
|
*
|
||||||
|
* @deprecated since version 1.6 - use readEncapsulatedPixelDataFromFragments() or readEncapsulatedImageFrame()
|
||||||
* @param dataSet - the dataSet containing the encapsulated pixel data
|
* @param dataSet - the dataSet containing the encapsulated pixel data
|
||||||
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the frame from
|
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the frame from
|
||||||
* @param frame - the zero based frame index
|
* @param frame - the zero based frame index
|
||||||
* @returns Uint8Array with the encapsulated pixel data
|
* @returns {object} with the encapsulated pixel data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
dicomParser.readEncapsulatedPixelData = function(dataSet, pixelDataElement, frame)
|
dicomParser.readEncapsulatedPixelData = function(dataSet, pixelDataElement, frame)
|
||||||
{
|
{
|
||||||
|
if(!deprecatedNoticeLogged) {
|
||||||
|
deprecatedNoticeLogged = true;
|
||||||
|
if(console && console.log) {
|
||||||
|
console.log("WARNING: dicomParser.readEncapsulatedPixelData() has been deprecated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(dataSet === undefined) {
|
if(dataSet === undefined) {
|
||||||
throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'dataSet'";
|
throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'dataSet'";
|
||||||
}
|
}
|
||||||
@ -649,30 +728,54 @@ var dicomParser = (function (dicomParser)
|
|||||||
throw "dicomParser.readEncapsulatedPixelData: parameter 'frame' must be >= 0";
|
throw "dicomParser.readEncapsulatedPixelData: parameter 'frame' must be >= 0";
|
||||||
}
|
}
|
||||||
|
|
||||||
// seek past the basic offset table (no need to parse it again since we already have)
|
// If the basic offset table is not empty, we can extract the frame
|
||||||
var byteStream = new dicomParser.ByteStream(dataSet.byteArrayParser, dataSet.byteArray, pixelDataElement.dataOffset);
|
|
||||||
var basicOffsetTable = dicomParser.readSequenceItem(byteStream);
|
|
||||||
if(basicOffsetTable.tag !== 'xfffee000')
|
|
||||||
{
|
|
||||||
throw "dicomParser.readEncapsulatedPixelData: missing basic offset table xfffee000";
|
|
||||||
}
|
|
||||||
byteStream.seek(basicOffsetTable.length);
|
|
||||||
|
|
||||||
// If the basic offset table is empty (no entries), it is a single frame. If it is not empty,
|
|
||||||
// it has at least one frame so use the basic offset table to find the bytes
|
|
||||||
if(pixelDataElement.basicOffsetTable.length !== 0)
|
if(pixelDataElement.basicOffsetTable.length !== 0)
|
||||||
{
|
{
|
||||||
return readEncapsulatedPixelDataWithBasicOffsetTable(pixelDataElement, byteStream, frame);
|
return dicomParser.readEncapsulatedImageFrame(dataSet, pixelDataElement, frame);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return readEncapsulatedDataNoBasicOffsetTable(pixelDataElement, byteStream, frame);
|
// No basic offset table, assume all fragments are for one frame - NOTE that this is NOT a valid
|
||||||
|
// assumption but is the original behavior so we are keeping it for now
|
||||||
|
return dicomParser.readEncapsulatedPixelDataFromFragments(dataSet, pixelDataElement, 0, pixelDataElement.fragments.length);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return dicomParser;
|
return dicomParser;
|
||||||
}(dicomParser));
|
}(dicomParser));
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Internal helper function to allocate new byteArray buffers
|
||||||
|
*/
|
||||||
|
var dicomParser = (function (dicomParser)
|
||||||
|
{
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if(dicomParser === undefined)
|
||||||
|
{
|
||||||
|
dicomParser = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new byteArray of the same type (Uint8Array or Buffer) of the specified length.
|
||||||
|
* @param byteArray the underlying byteArray (either Uint8Array or Buffer)
|
||||||
|
* @param length number of bytes of the Byte Array
|
||||||
|
* @returns {object} Uint8Array or Buffer depending on the type of byteArray
|
||||||
|
*/
|
||||||
|
dicomParser.alloc = function(byteArray, length) {
|
||||||
|
if (typeof Buffer !== 'undefined' && byteArray instanceof Buffer) {
|
||||||
|
return Buffer.alloc(length);
|
||||||
|
}
|
||||||
|
else if(byteArray instanceof Uint8Array) {
|
||||||
|
return new Uint8Array(length);
|
||||||
|
} else {
|
||||||
|
throw 'dicomParser.alloc: unknown type for byteArray';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return dicomParser;
|
||||||
|
}(dicomParser));
|
||||||
/**
|
/**
|
||||||
* Internal helper functions for parsing different types from a big-endian byte array
|
* Internal helper functions for parsing different types from a big-endian byte array
|
||||||
*/
|
*/
|
||||||
@ -876,7 +979,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
{
|
{
|
||||||
if(length < 0)
|
if(length < 0)
|
||||||
{
|
{
|
||||||
throw 'readFixedString - length cannot be less than 0';
|
throw 'dicomParser.readFixedString - length cannot be less than 0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(position + length > byteArray.length) {
|
if(position + length > byteArray.length) {
|
||||||
@ -884,9 +987,10 @@ var dicomParser = (function (dicomParser)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result = "";
|
var result = "";
|
||||||
|
var byte;
|
||||||
for(var i=0; i < length; i++)
|
for(var i=0; i < length; i++)
|
||||||
{
|
{
|
||||||
var byte = byteArray[position + i];
|
byte = byteArray[position + i];
|
||||||
if(byte === 0) {
|
if(byte === 0) {
|
||||||
position += length;
|
position += length;
|
||||||
return result;
|
return result;
|
||||||
@ -966,7 +1070,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
{
|
{
|
||||||
if(this.position + offset < 0)
|
if(this.position + offset < 0)
|
||||||
{
|
{
|
||||||
throw "cannot seek to position < 0";
|
throw "dicomParser.ByteStream.prototype.seek: cannot seek to position < 0";
|
||||||
}
|
}
|
||||||
this.position += offset;
|
this.position += offset;
|
||||||
};
|
};
|
||||||
@ -980,9 +1084,9 @@ var dicomParser = (function (dicomParser)
|
|||||||
dicomParser.ByteStream.prototype.readByteStream = function(numBytes)
|
dicomParser.ByteStream.prototype.readByteStream = function(numBytes)
|
||||||
{
|
{
|
||||||
if(this.position + numBytes > this.byteArray.length) {
|
if(this.position + numBytes > this.byteArray.length) {
|
||||||
throw 'readByteStream - buffer overread';
|
throw 'dicomParser.ByteStream.prototype.readByteStream: readByteStream - buffer overread';
|
||||||
}
|
}
|
||||||
var byteArrayView = new Uint8Array(this.byteArray.buffer, this.position, numBytes);
|
var byteArrayView = dicomParser.sharedCopy(this.byteArray, this.position, numBytes);
|
||||||
this.position += numBytes;
|
this.position += numBytes;
|
||||||
return new dicomParser.ByteStream(this.byteArrayParser, byteArrayView);
|
return new dicomParser.ByteStream(this.byteArrayParser, byteArrayView);
|
||||||
};
|
};
|
||||||
@ -1704,7 +1808,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
|
|
||||||
while(byteStream.position < maxPosition)
|
while(byteStream.position < maxPosition)
|
||||||
{
|
{
|
||||||
var element = dicomParser.readDicomElementImplicit(byteStream, options.untilTag);
|
var element = dicomParser.readDicomElementImplicit(byteStream, options.untilTag, options.vrCallback);
|
||||||
elements[element.tag] = element;
|
elements[element.tag] = element;
|
||||||
if(element.tag === options.untilTag) {
|
if(element.tag === options.untilTag) {
|
||||||
return;
|
return;
|
||||||
@ -1817,7 +1921,25 @@ var dicomParser = (function (dicomParser)
|
|||||||
dicomParser = {};
|
dicomParser = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
dicomParser.readDicomElementImplicit = function(byteStream, untilTag)
|
function isSequence(element, byteStream, vrCallback) {
|
||||||
|
// if a data dictionary callback was provided, use that to verify that the element is a sequence.
|
||||||
|
if (typeof vrCallback !== 'undefined') {
|
||||||
|
return (vrCallback(element.tag) === 'SQ');
|
||||||
|
}
|
||||||
|
if ((byteStream.position + 4) <= byteStream.byteArray.length) {
|
||||||
|
var nextTag = dicomParser.readTag(byteStream);
|
||||||
|
byteStream.seek(-4);
|
||||||
|
// Item start tag (fffe,e000) or sequence delimiter (i.e. end of sequence) tag (0fffe,e0dd)
|
||||||
|
// These are the tags that could potentially be found directly after a sequence start tag (the delimiter
|
||||||
|
// is found in the case of an empty sequence). This is not 100% safe because a non-sequence item
|
||||||
|
// could have data that has these bytes, but this is how to do it without a data dictionary.
|
||||||
|
return (nextTag === 'xfffee000') || (nextTag === 'xfffee0dd');
|
||||||
|
}
|
||||||
|
byteStream.warnings.push('eof encountered before finding sequence item tag or sequence delimiter tag in peeking to determine VR');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dicomParser.readDicomElementImplicit = function(byteStream, untilTag, vrCallback)
|
||||||
{
|
{
|
||||||
if(byteStream === undefined)
|
if(byteStream === undefined)
|
||||||
{
|
{
|
||||||
@ -1830,8 +1952,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
dataOffset : byteStream.position
|
dataOffset : byteStream.position
|
||||||
};
|
};
|
||||||
|
|
||||||
if(element.length === 4294967295)
|
if(element.length === 4294967295) {
|
||||||
{
|
|
||||||
element.hadUndefinedLength = true;
|
element.hadUndefinedLength = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1839,31 +1960,15 @@ var dicomParser = (function (dicomParser)
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
// peek ahead at the next tag to see if it looks like a sequence. This is not 100%
|
if (isSequence(element, byteStream, vrCallback)) {
|
||||||
// safe because a non sequence item could have data that has these bytes, but this
|
// parse the sequence
|
||||||
// is how to do it without a data dictionary.
|
dicomParser.readSequenceItemsImplicit(byteStream, element);
|
||||||
if ((byteStream.position + 4) <= byteStream.byteArray.length) {
|
return element;
|
||||||
var nextTag = dicomParser.readTag(byteStream);
|
|
||||||
byteStream.seek(-4);
|
|
||||||
|
|
||||||
// zero length sequence
|
|
||||||
if (element.hadUndefinedLength && nextTag === 'xfffee0dd') {
|
|
||||||
element.length = 0;
|
|
||||||
byteStream.seek(8);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextTag === 'xfffee000') {
|
|
||||||
// parse the sequence
|
|
||||||
dicomParser.readSequenceItemsImplicit(byteStream, element);
|
|
||||||
//element.length = byteStream.byteArray.length - element.dataOffset;
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if element is not a sequence and has undefined length, we have to
|
// if element is not a sequence and has undefined length, we have to
|
||||||
// scan the data for a magic number to figure out when it ends.
|
// scan the data for a magic number to figure out when it ends.
|
||||||
if(element.length === 4294967295)
|
if(element.hadUndefinedLength)
|
||||||
{
|
{
|
||||||
dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element);
|
dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element);
|
||||||
return element;
|
return element;
|
||||||
@ -1877,6 +1982,236 @@ var dicomParser = (function (dicomParser)
|
|||||||
|
|
||||||
return dicomParser;
|
return dicomParser;
|
||||||
}(dicomParser));
|
}(dicomParser));
|
||||||
|
/**
|
||||||
|
* Functionality for extracting encapsulated pixel data
|
||||||
|
*/
|
||||||
|
|
||||||
|
var dicomParser = (function (dicomParser)
|
||||||
|
{
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if(dicomParser === undefined)
|
||||||
|
{
|
||||||
|
dicomParser = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findFragmentIndexWithOffset(fragments, offset) {
|
||||||
|
for(var i=0; i < fragments.length; i++) {
|
||||||
|
if(fragments[i].offset === offset) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateNumberOfFragmentsForFrame(frameIndex, basicOffsetTable, fragments, startFragmentIndex) {
|
||||||
|
// special case for last frame
|
||||||
|
if(frameIndex === basicOffsetTable.length -1) {
|
||||||
|
return fragments.length - startFragmentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate through each fragment looking for the one matching the offset for the next frame
|
||||||
|
var nextFrameOffset = basicOffsetTable[frameIndex + 1];
|
||||||
|
for(var i=startFragmentIndex + 1; i < fragments.length; i++) {
|
||||||
|
if(fragments[i].offset === nextFrameOffset) {
|
||||||
|
return i - startFragmentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw "dicomParser.calculateNumberOfFragmentsForFrame: could not find fragment with offset matching basic offset table";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pixel data for the specified frame in an encapsulated pixel data element that has a non
|
||||||
|
* empty basic offset table. Note that this function will fail if the basic offset table is empty - in that
|
||||||
|
* case you need to determine which fragments map to which frames and read them using
|
||||||
|
* readEncapsulatedPixelDataFromFragments(). Also see the function createJEPGBasicOffsetTable() to see
|
||||||
|
* how a basic offset table can be created for JPEG images
|
||||||
|
*
|
||||||
|
* @param dataSet - the dataSet containing the encapsulated pixel data
|
||||||
|
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the frame from
|
||||||
|
* @param frameIndex - the zero based frame index
|
||||||
|
* @param [basicOffsetTable] - optional array of starting offsets for frames
|
||||||
|
* @param [fragments] - optional array of objects describing each fragment (offset, position, length)
|
||||||
|
* @returns {object} with the encapsulated pixel data
|
||||||
|
*/
|
||||||
|
dicomParser.readEncapsulatedImageFrame = function(dataSet, pixelDataElement, frameIndex, basicOffsetTable, fragments)
|
||||||
|
{
|
||||||
|
// default parameters
|
||||||
|
basicOffsetTable = basicOffsetTable || pixelDataElement.basicOffsetTable;
|
||||||
|
fragments = fragments || pixelDataElement.fragments;
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if(dataSet === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: missing required parameter 'dataSet'";
|
||||||
|
}
|
||||||
|
if(pixelDataElement === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: missing required parameter 'pixelDataElement'";
|
||||||
|
}
|
||||||
|
if(frameIndex === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: missing required parameter 'frameIndex'";
|
||||||
|
}
|
||||||
|
if(basicOffsetTable === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: parameter 'pixelDataElement' does not have basicOffsetTable";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.tag !== 'x7fe00010') {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: parameter 'pixelDataElement' refers to non pixel data tag (expected tag = x7fe00010'";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.encapsulatedPixelData !== true) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.hadUndefinedLength !== true) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: parameter 'pixelDataElement' refers to pixel data element that does not have undefined length";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.fragments === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: parameter 'pixelDataElement' refers to pixel data element that does not have fragments";
|
||||||
|
}
|
||||||
|
if(basicOffsetTable.length === 0) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: basicOffsetTable has zero entries";
|
||||||
|
}
|
||||||
|
if(frameIndex < 0) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: parameter 'frameIndex' must be >= 0";
|
||||||
|
}
|
||||||
|
if(frameIndex >= basicOffsetTable.length) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: parameter 'frameIndex' must be < basicOffsetTable.length";
|
||||||
|
}
|
||||||
|
|
||||||
|
// find starting fragment based on the offset for the frame in the basic offset table
|
||||||
|
var offset = basicOffsetTable[frameIndex];
|
||||||
|
var startFragmentIndex = findFragmentIndexWithOffset(fragments, offset);
|
||||||
|
if(startFragmentIndex === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedImageFrame: unable to find fragment that matches basic offset table entry";
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the number of fragments for this frame
|
||||||
|
var numFragments = calculateNumberOfFragmentsForFrame(frameIndex, basicOffsetTable, fragments, startFragmentIndex);
|
||||||
|
|
||||||
|
// now extract the frame from the fragments
|
||||||
|
return dicomParser.readEncapsulatedPixelDataFromFragments(dataSet, pixelDataElement, startFragmentIndex, numFragments, fragments);
|
||||||
|
};
|
||||||
|
|
||||||
|
return dicomParser;
|
||||||
|
}(dicomParser));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functionality for extracting encapsulated pixel data
|
||||||
|
*/
|
||||||
|
|
||||||
|
var dicomParser = (function (dicomParser)
|
||||||
|
{
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if(dicomParser === undefined)
|
||||||
|
{
|
||||||
|
dicomParser = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateBufferSize(fragments, startFragment, numFragments) {
|
||||||
|
var bufferSize = 0;
|
||||||
|
for(var i=startFragment; i < startFragment + numFragments; i++) {
|
||||||
|
bufferSize += fragments[i].length;
|
||||||
|
}
|
||||||
|
return bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encapsulated pixel data from the specified fragments. Use this function when you know
|
||||||
|
* the fragments you want to extract data from. See
|
||||||
|
*
|
||||||
|
* @param dataSet - the dataSet containing the encapsulated pixel data
|
||||||
|
* @param pixelDataElement - the pixel data element (x7fe00010) to extract the fragment data from
|
||||||
|
* @param startFragmentIndex - zero based index of the first fragment to extract from
|
||||||
|
* @param [numFragments] - the number of fragments to extract from, default is 1
|
||||||
|
* @param [fragments] - optional array of objects describing each fragment (offset, position, length)
|
||||||
|
* @returns {object} byte array with the encapsulated pixel data
|
||||||
|
*/
|
||||||
|
dicomParser.readEncapsulatedPixelDataFromFragments = function(dataSet, pixelDataElement, startFragmentIndex, numFragments, fragments)
|
||||||
|
{
|
||||||
|
// default values
|
||||||
|
numFragments = numFragments || 1;
|
||||||
|
fragments = fragments || pixelDataElement.fragments;
|
||||||
|
|
||||||
|
// check parameters
|
||||||
|
if(dataSet === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: missing required parameter 'dataSet'";
|
||||||
|
}
|
||||||
|
if(pixelDataElement === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: missing required parameter 'pixelDataElement'";
|
||||||
|
}
|
||||||
|
if(startFragmentIndex === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: missing required parameter 'startFragmentIndex'";
|
||||||
|
}
|
||||||
|
if(numFragments === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: missing required parameter 'numFragments'";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.tag !== 'x7fe00010') {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'pixelDataElement' refers to non pixel data tag (expected tag = x7fe00010'";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.encapsulatedPixelData !== true) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.hadUndefinedLength !== true) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.basicOffsetTable === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.fragments === undefined) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(pixelDataElement.fragments.length <= 0) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'pixelDataElement' refers to pixel data element that does not have encapsulated pixel data";
|
||||||
|
}
|
||||||
|
if(startFragmentIndex < 0) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'startFragmentIndex' must be >= 0";
|
||||||
|
}
|
||||||
|
if(startFragmentIndex >= pixelDataElement.fragments.length) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'startFragmentIndex' must be < number of fragments";
|
||||||
|
}
|
||||||
|
if(numFragments < 1) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'numFragments' must be > 0";
|
||||||
|
}
|
||||||
|
if(startFragmentIndex + numFragments > pixelDataElement.fragments.length) {
|
||||||
|
throw "dicomParser.readEncapsulatedPixelDataFromFragments: parameter 'startFragment' + 'numFragments' < number of fragments";
|
||||||
|
}
|
||||||
|
|
||||||
|
// create byte stream on the data for this pixel data element
|
||||||
|
var byteStream = new dicomParser.ByteStream(dataSet.byteArrayParser, dataSet.byteArray, pixelDataElement.dataOffset);
|
||||||
|
|
||||||
|
// seek past the basic offset table (no need to parse it again since we already have)
|
||||||
|
var basicOffsetTable = dicomParser.readSequenceItem(byteStream);
|
||||||
|
if(basicOffsetTable.tag !== 'xfffee000')
|
||||||
|
{
|
||||||
|
throw "dicomParser.readEncapsulatedPixelData: missing basic offset table xfffee000";
|
||||||
|
}
|
||||||
|
byteStream.seek(basicOffsetTable.length);
|
||||||
|
|
||||||
|
var fragmentZeroPosition = byteStream.position;
|
||||||
|
var fragmentHeaderSize = 8; // tag + length
|
||||||
|
|
||||||
|
// if there is only one fragment, return a view on this array to avoid copying
|
||||||
|
if(numFragments === 1) {
|
||||||
|
return dicomParser.sharedCopy(byteStream.byteArray, fragmentZeroPosition + fragments[startFragmentIndex].offset + fragmentHeaderSize, fragments[startFragmentIndex].length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// more than one fragment, combine all of the fragments into one buffer
|
||||||
|
var bufferSize = calculateBufferSize(fragments, startFragmentIndex, numFragments);
|
||||||
|
|
||||||
|
var pixelData = dicomParser.alloc(byteStream.byteArray, bufferSize);
|
||||||
|
|
||||||
|
var pixelDataIndex = 0;
|
||||||
|
for(var i=startFragmentIndex; i < startFragmentIndex + numFragments; i++) {
|
||||||
|
var fragmentOffset = fragmentZeroPosition + fragments[i].offset + fragmentHeaderSize;
|
||||||
|
for(var j=0; j < fragments[i].length; j++) {
|
||||||
|
pixelData[pixelDataIndex++] = byteStream.byteArray[fragmentOffset++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixelData;
|
||||||
|
};
|
||||||
|
|
||||||
|
return dicomParser;
|
||||||
|
}(dicomParser));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a DICOM P10 byte array and returns a DataSet object with the parsed elements. If the options
|
* Parses a DICOM P10 byte array and returns a DataSet object with the parsed elements. If the options
|
||||||
* argument is supplied and it contains the untilTag property, parsing will stop once that
|
* argument is supplied and it contains the untilTag property, parsing will stop once that
|
||||||
@ -1909,7 +2244,7 @@ var dicomParser = (function(dicomParser) {
|
|||||||
var prefix = littleEndianByteStream.readFixedString(4);
|
var prefix = littleEndianByteStream.readFixedString(4);
|
||||||
if(prefix !== "DICM")
|
if(prefix !== "DICM")
|
||||||
{
|
{
|
||||||
throw "dicomParser.readPart10Header: DICM prefix not found at location 132";
|
throw "dicomParser.readPart10Header: DICM prefix not found at location 132 - this is not a valid DICOM P10 file.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1978,7 +2313,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eof encountered - log a warning and return what we have for the element
|
// eof encountered - log a warning and return what we have for the element
|
||||||
byteStream.warnings.push('eof encountered before finding sequence delimitation item while reading sequence item of undefined length');
|
warnings.push('eof encountered before finding item delimiter tag while reading sequence item of undefined length');
|
||||||
return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
|
return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2002,7 +2337,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
|
|
||||||
function readSQElementUndefinedLengthExplicit(byteStream, element, warnings)
|
function readSQElementUndefinedLengthExplicit(byteStream, element, warnings)
|
||||||
{
|
{
|
||||||
while(byteStream.position < byteStream.byteArray.length)
|
while((byteStream.position + 4) <= byteStream.byteArray.length)
|
||||||
{
|
{
|
||||||
// end reading this sequence if the next tag is the sequence delimitation item
|
// end reading this sequence if the next tag is the sequence delimitation item
|
||||||
var nextTag = dicomParser.readTag(byteStream);
|
var nextTag = dicomParser.readTag(byteStream);
|
||||||
@ -2017,6 +2352,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
var item = readSequenceItemExplicit(byteStream, warnings);
|
var item = readSequenceItemExplicit(byteStream, warnings);
|
||||||
element.items.push(item);
|
element.items.push(item);
|
||||||
}
|
}
|
||||||
|
warnings.push('eof encountered before finding sequence delimitation tag while reading sequence of undefined length');
|
||||||
element.length = byteStream.position - element.dataOffset;
|
element.length = byteStream.position - element.dataOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2045,7 +2381,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
|
|
||||||
if(element.length === 4294967295)
|
if(element.length === 4294967295)
|
||||||
{
|
{
|
||||||
readSQElementUndefinedLengthExplicit(byteStream, element);
|
readSQElementUndefinedLengthExplicit(byteStream, element, warnings);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2069,13 +2405,13 @@ var dicomParser = (function (dicomParser)
|
|||||||
dicomParser = {};
|
dicomParser = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function readDicomDataSetImplicitUndefinedLength(byteStream)
|
function readDicomDataSetImplicitUndefinedLength(byteStream, vrCallback)
|
||||||
{
|
{
|
||||||
var elements = {};
|
var elements = {};
|
||||||
|
|
||||||
while(byteStream.position < byteStream.byteArray.length)
|
while(byteStream.position < byteStream.byteArray.length)
|
||||||
{
|
{
|
||||||
var element = dicomParser.readDicomElementImplicit(byteStream);
|
var element = dicomParser.readDicomElementImplicit(byteStream, undefined, vrCallback);
|
||||||
elements[element.tag] = element;
|
elements[element.tag] = element;
|
||||||
|
|
||||||
// we hit an item delimiter tag, return the current offset to mark
|
// we hit an item delimiter tag, return the current offset to mark
|
||||||
@ -2090,27 +2426,27 @@ var dicomParser = (function (dicomParser)
|
|||||||
return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
|
return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
function readSequenceItemImplicit(byteStream)
|
function readSequenceItemImplicit(byteStream, vrCallback)
|
||||||
{
|
{
|
||||||
var item = dicomParser.readSequenceItem(byteStream);
|
var item = dicomParser.readSequenceItem(byteStream);
|
||||||
|
|
||||||
if(item.length === 4294967295)
|
if(item.length === 4294967295)
|
||||||
{
|
{
|
||||||
item.hadUndefinedLength = true;
|
item.hadUndefinedLength = true;
|
||||||
item.dataSet = readDicomDataSetImplicitUndefinedLength(byteStream);
|
item.dataSet = readDicomDataSetImplicitUndefinedLength(byteStream, vrCallback);
|
||||||
item.length = byteStream.position - item.dataOffset;
|
item.length = byteStream.position - item.dataOffset;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {});
|
item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {});
|
||||||
dicomParser.parseDicomDataSetImplicit(item.dataSet, byteStream, byteStream.position + item.length);
|
dicomParser.parseDicomDataSetImplicit(item.dataSet, byteStream, byteStream.position + item.length, {vrCallback: vrCallback});
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readSQElementUndefinedLengthImplicit(byteStream, element)
|
function readSQElementUndefinedLengthImplicit(byteStream, element, vrCallback)
|
||||||
{
|
{
|
||||||
while(byteStream.position < byteStream.byteArray.length)
|
while((byteStream.position + 4) <= byteStream.byteArray.length)
|
||||||
{
|
{
|
||||||
// end reading this sequence if the next tag is the sequence delimitation item
|
// end reading this sequence if the next tag is the sequence delimitation item
|
||||||
var nextTag = dicomParser.readTag(byteStream);
|
var nextTag = dicomParser.readTag(byteStream);
|
||||||
@ -2122,18 +2458,19 @@ var dicomParser = (function (dicomParser)
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = readSequenceItemImplicit(byteStream);
|
var item = readSequenceItemImplicit(byteStream, vrCallback);
|
||||||
element.items.push(item);
|
element.items.push(item);
|
||||||
}
|
}
|
||||||
|
byteStream.warnings.push('eof encountered before finding sequence delimiter in sequence of undefined length');
|
||||||
element.length = byteStream.byteArray.length - element.dataOffset;
|
element.length = byteStream.byteArray.length - element.dataOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readSQElementKnownLengthImplicit(byteStream, element)
|
function readSQElementKnownLengthImplicit(byteStream, element, vrCallback)
|
||||||
{
|
{
|
||||||
var maxPosition = element.dataOffset + element.length;
|
var maxPosition = element.dataOffset + element.length;
|
||||||
while(byteStream.position < maxPosition)
|
while(byteStream.position < maxPosition)
|
||||||
{
|
{
|
||||||
var item = readSequenceItemImplicit(byteStream);
|
var item = readSequenceItemImplicit(byteStream, vrCallback);
|
||||||
element.items.push(item);
|
element.items.push(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2142,8 +2479,9 @@ var dicomParser = (function (dicomParser)
|
|||||||
* Reads sequence items for an element in an implicit little endian byte stream
|
* Reads sequence items for an element in an implicit little endian byte stream
|
||||||
* @param byteStream the implicit little endian byte stream
|
* @param byteStream the implicit little endian byte stream
|
||||||
* @param element the element to read the sequence items for
|
* @param element the element to read the sequence items for
|
||||||
|
* @param vrCallback an optional method that returns a VR string given a tag
|
||||||
*/
|
*/
|
||||||
dicomParser.readSequenceItemsImplicit = function(byteStream, element)
|
dicomParser.readSequenceItemsImplicit = function(byteStream, element, vrCallback)
|
||||||
{
|
{
|
||||||
if(byteStream === undefined)
|
if(byteStream === undefined)
|
||||||
{
|
{
|
||||||
@ -2158,11 +2496,11 @@ var dicomParser = (function (dicomParser)
|
|||||||
|
|
||||||
if(element.length === 4294967295)
|
if(element.length === 4294967295)
|
||||||
{
|
{
|
||||||
readSQElementUndefinedLengthImplicit(byteStream, element);
|
readSQElementUndefinedLengthImplicit(byteStream, element, vrCallback);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
readSQElementKnownLengthImplicit(byteStream, element);
|
readSQElementKnownLengthImplicit(byteStream, element, vrCallback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2202,6 +2540,11 @@ var dicomParser = (function (dicomParser)
|
|||||||
dataOffset : byteStream.position
|
dataOffset : byteStream.position
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (element.tag !== 'xfffee000') {
|
||||||
|
var startPosition = byteStream.position;
|
||||||
|
throw "dicomParser.readSequenceItem: item tag (FFFE,E000) not found at offset " + startPosition;
|
||||||
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2242,6 +2585,41 @@ var dicomParser = (function (dicomParser)
|
|||||||
|
|
||||||
return dicomParser;
|
return dicomParser;
|
||||||
}(dicomParser));
|
}(dicomParser));
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Internal helper function to create a shared copy of a byteArray
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
var dicomParser = (function (dicomParser)
|
||||||
|
{
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if(dicomParser === undefined)
|
||||||
|
{
|
||||||
|
dicomParser = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a view of the underlying byteArray. The view is of the same type as the byteArray (e.g.
|
||||||
|
* Uint8Array or Buffer) and shares the same underlying memory (changing one changes the other)
|
||||||
|
* @param byteArray the underlying byteArray (either Uint8Array or Buffer)
|
||||||
|
* @param byteOffset offset into the underlying byteArray to create the view of
|
||||||
|
* @param length number of bytes in the view
|
||||||
|
* @returns {object} Uint8Array or Buffer depending on the type of byteArray
|
||||||
|
*/
|
||||||
|
dicomParser.sharedCopy = function(byteArray, byteOffset, length) {
|
||||||
|
if (typeof Buffer !== 'undefined' && byteArray instanceof Buffer) {
|
||||||
|
return byteArray.slice(byteOffset, byteOffset + length);
|
||||||
|
}
|
||||||
|
else if(byteArray instanceof Uint8Array) {
|
||||||
|
return new Uint8Array(byteArray.buffer, byteArray.byteOffset + byteOffset, length);
|
||||||
|
} else {
|
||||||
|
throw 'dicomParser.from: unknown type for byteArray';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return dicomParser;
|
||||||
|
}(dicomParser));
|
||||||
/**
|
/**
|
||||||
* Version
|
* Version
|
||||||
*/
|
*/
|
||||||
@ -2255,7 +2633,7 @@ var dicomParser = (function (dicomParser)
|
|||||||
dicomParser = {};
|
dicomParser = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
dicomParser.version = "1.2.1";
|
dicomParser.version = "1.7.1";
|
||||||
|
|
||||||
return dicomParser;
|
return dicomParser;
|
||||||
}(dicomParser));
|
}(dicomParser));
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user