-
\ No newline at end of file
diff --git a/OHIFViewer/client/components/viewer/viewerMain/viewerMain.js b/OHIFViewer/client/components/viewer/viewerMain/viewerMain.js
deleted file mode 100644
index 260bf1cc9..000000000
--- a/OHIFViewer/client/components/viewer/viewerMain/viewerMain.js
+++ /dev/null
@@ -1,5 +0,0 @@
-Template.viewerMain.helpers({
- studies : function() {
- return this.studies;
- }
-});
\ No newline at end of file
diff --git a/OHIFViewer/client/components/viewer/viewerMain/viewerMain.styl b/OHIFViewer/client/components/viewer/viewerMain/viewerMain.styl
deleted file mode 100644
index 7b269b4cd..000000000
--- a/OHIFViewer/client/components/viewer/viewerMain/viewerMain.styl
+++ /dev/null
@@ -1,5 +0,0 @@
-.viewerMain
- float: left
- background-color: gray
- height: 100%
- width: calc(100% - 120px)
\ No newline at end of file
diff --git a/OHIFViewer/client/head.html b/OHIFViewer/client/head.html
index 979732482..b3bffd530 100644
--- a/OHIFViewer/client/head.html
+++ b/OHIFViewer/client/head.html
@@ -2,7 +2,7 @@
OHIF Viewer
-
+
diff --git a/OHIFViewer/client/log.js b/OHIFViewer/client/log.js
new file mode 100644
index 000000000..1f567f0c5
--- /dev/null
+++ b/OHIFViewer/client/log.js
@@ -0,0 +1,3 @@
+// Create application logger using loglevel
+// https://atmospherejs.com/spacejamio/loglevel
+log = loglevel.createAppLogger('OHIFViewer', defaultLevel = 'info');
diff --git a/OHIFViewer/client/routes.js b/OHIFViewer/client/routes.js
index 84ae25bfe..c05cebf2c 100644
--- a/OHIFViewer/client/routes.js
+++ b/OHIFViewer/client/routes.js
@@ -1,24 +1,25 @@
-Router.configure({
- layoutTemplate: 'layout',
- //loadingTemplate: '',
- notFoundTemplate: 'notFound'
-});
-
-Router.route('/', function () {
- this.render('worklist', {});
-});
-
-tabs = new Meteor.Collection(null);
Session.setDefault('ViewerData', {});
-Session.setDefault('ViewerDataUpdated', Random.id());
+// Re-add any tab data saved in the Session
Object.keys(ViewerData).forEach(function(contentId) {
var tabData = ViewerData[contentId];
var data = {
title: tabData.title,
- contentid: tabData.contentId,
+ contentid: tabData.contentid,
};
- tabs.insert(data);
+ WorklistTabs.insert(data);
+});
+
+Router.configure({
+ layoutTemplate: 'layout',
+ loadingTemplate: 'layout',
+ notFoundTemplate: 'notFound'
+});
+
+Router.onBeforeAction('loading');
+
+Router.route('/', function () {
+ this.render('worklist');
});
@@ -26,24 +27,18 @@ Router.route('/viewer/:_id', {
layoutTemplate: 'layout',
name: 'viewer',
onBeforeAction: function() {
- var self = this;
+ log.info('Router GetStudyMetadata');
- Meteor.call('GetStudyMetadata', this.params._id, function(error, study) {
- sortStudy(study);
+ var studyInstanceUid = this.params._id;
+
+ // Check if this study is already loaded in a tab
+ // If it is, stop here so we don't keep adding tabs on hot-code reloads
+ var tab = WorklistTabs.find({studyInstanceUid: studyInstanceUid}).fetch();
+ if (tab) {
+ return;
+ }
- var title = study.seriesList[0].instances[0].patientName;
- var contentid = generateUUID();
-
- var data = {
- title: title,
- contentid: contentid,
- };
- tabs.insert(data);
-
- data.studies = [study];
-
- self.render('worklist');
- Session.set('OpenNewTabEvent', data);
- });
+ this.render('worklist');
+ openNewTab(studyInstanceUid);
}
});
\ No newline at end of file
diff --git a/Packages/cornerstone/client/cornerstone.js b/Packages/cornerstone/client/cornerstone.js
index b68821d71..f2cca6c2d 100644
--- a/Packages/cornerstone/client/cornerstone.js
+++ b/Packages/cornerstone/client/cornerstone.js
@@ -2148,7 +2148,7 @@ if(typeof cornerstone === 'undefined'){
function initShaders() {
for (var id in cornerstone.webGL.shaders) {
- console.log("WEBGL: Loading shader", id);
+ //console.log("WEBGL: Loading shader", id);
var shader = cornerstone.webGL.shaders[ id ];
shader.attributes = {};
shader.uniforms = {};
@@ -2168,13 +2168,13 @@ if(typeof cornerstone === 'undefined'){
function initRenderer() {
if (cornerstone.webGL.isWebGLInitialized === true) {
- console.log("WEBGL Renderer already initialized");
+ //console.log("WEBGL Renderer already initialized");
return;
}
if ( initWebGL( renderCanvas ) ) {
initBuffers();
initShaders();
- console.log("WEBGL Renderer initialized!");
+ //console.log("WEBGL Renderer initialized!");
cornerstone.webGL.isWebGLInitialized = true;
}
}
diff --git a/Packages/cornerstone/client/cornerstoneTools.js b/Packages/cornerstone/client/cornerstoneTools.js
index 5b45ea67e..7aa176511 100644
--- a/Packages/cornerstone/client/cornerstoneTools.js
+++ b/Packages/cornerstone/client/cornerstoneTools.js
@@ -1,4 +1,4 @@
-/*! cornerstoneTools - v0.7.6 - 2015-10-08 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
+/*! cornerstoneTools - v0.7.7 - 2015-11-11 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */
// Begin Source: src/header.js
if (typeof cornerstone === 'undefined') {
cornerstone = {};
@@ -40,7 +40,14 @@ if (typeof cornerstoneTools === 'undefined') {
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: e.pageX || e.originalEvent.pageX, pageY: e.pageY || e.originalEvent.pageY, imageX: startingCoords.x, imageY: startingCoords.y
+ element: element,
+ 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);
@@ -58,7 +65,8 @@ if (typeof cornerstoneTools === 'undefined') {
// module exports
cornerstoneTools.mouseWheelInput = {
- enable: enable, disable: disable
+ enable: enable,
+ disable: disable
};
})($, cornerstone, cornerstoneTools);
@@ -87,8 +95,11 @@ if (typeof cornerstoneTools === 'undefined') {
var eventType = 'CornerstoneToolsMouseDoubleClick';
var startPoints = {
- page: cornerstoneMath.point.pageToPoint(e), image: cornerstone.pageToPixel(element, e.pageX, e.pageY), client: {
- x: e.clientX, y: e.clientY
+ page: cornerstoneMath.point.pageToPoint(e),
+ image: cornerstone.pageToPixel(element, e.pageX, e.pageY),
+ client: {
+ x: e.clientX,
+ y: e.clientY
}
};
startPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image);
@@ -104,7 +115,8 @@ if (typeof cornerstoneTools === 'undefined') {
lastPoints: lastPoints,
currentPoints: startPoints,
deltaPoints: {
- x: 0, y: 0
+ x: 0,
+ y: 0
},
type: eventType
};
@@ -123,8 +135,11 @@ if (typeof cornerstoneTools === 'undefined') {
$(element).off('mousemove', mouseMove);
var startPoints = {
- page: cornerstoneMath.point.pageToPoint(e), image: cornerstone.pageToPixel(element, e.pageX, e.pageY), client: {
- x: e.clientX, y: e.clientY
+ page: cornerstoneMath.point.pageToPoint(e),
+ image: cornerstone.pageToPixel(element, e.pageX, e.pageY),
+ client: {
+ x: e.clientX,
+ y: e.clientY
}
};
startPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image);
@@ -140,7 +155,8 @@ if (typeof cornerstoneTools === 'undefined') {
lastPoints: lastPoints,
currentPoints: startPoints,
deltaPoints: {
- x: 0, y: 0
+ x: 0,
+ y: 0
},
type: eventType
};
@@ -161,15 +177,21 @@ if (typeof cornerstoneTools === 'undefined') {
// calculate our current points in page and image coordinates
var eventType = 'CornerstoneToolsMouseDrag';
var currentPoints = {
- page: cornerstoneMath.point.pageToPoint(e), image: cornerstone.pageToPixel(element, e.pageX, e.pageY), client: {
- x: e.clientX, y: e.clientY
+ page: cornerstoneMath.point.pageToPoint(e),
+ image: cornerstone.pageToPixel(element, e.pageX, e.pageY),
+ client: {
+ x: e.clientX,
+ y: e.clientY
}
};
currentPoints.canvas = cornerstone.pixelToCanvas(element, currentPoints.image);
// Calculate delta values in page and image coordinates
var deltaPoints = {
- page: cornerstoneMath.point.subtract(currentPoints.page, lastPoints.page), image: cornerstoneMath.point.subtract(currentPoints.image, lastPoints.image), client: cornerstoneMath.point.subtract(currentPoints.client, lastPoints.client), canvas: cornerstoneMath.point.subtract(currentPoints.canvas, lastPoints.canvas)
+ page: cornerstoneMath.point.subtract(currentPoints.page, lastPoints.page),
+ image: cornerstoneMath.point.subtract(currentPoints.image, lastPoints.image),
+ client: cornerstoneMath.point.subtract(currentPoints.client, lastPoints.client),
+ canvas: cornerstoneMath.point.subtract(currentPoints.canvas, lastPoints.canvas)
};
var eventData = {
@@ -228,7 +250,8 @@ if (typeof cornerstoneTools === 'undefined') {
which: whichMouseButton,
viewport: cornerstone.getViewport(element),
image: cornerstone.getEnabledElement(element).image,
- element: element, startPoints: startPoints,
+ element: element,
+ startPoints: startPoints,
lastPoints: lastPoints,
currentPoints: currentPoints,
deltaPoints: deltaPoints,
@@ -257,8 +280,11 @@ if (typeof cornerstoneTools === 'undefined') {
var eventType = 'CornerstoneToolsMouseMove';
var startPoints = {
- page: cornerstoneMath.point.pageToPoint(e), image: cornerstone.pageToPixel(element, e.pageX, e.pageY), client: {
- x: e.clientX, y: e.clientY
+ page: cornerstoneMath.point.pageToPoint(e),
+ image: cornerstone.pageToPixel(element, e.pageX, e.pageY),
+ client: {
+ x: e.clientX,
+ y: e.clientY
}
};
startPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image);
@@ -269,15 +295,21 @@ if (typeof cornerstoneTools === 'undefined') {
// calculate our current points in page and image coordinates
var currentPoints = {
- page: cornerstoneMath.point.pageToPoint(e), image: cornerstone.pageToPixel(element, e.pageX, e.pageY), client: {
- x: e.clientX, y: e.clientY
+ page: cornerstoneMath.point.pageToPoint(e),
+ image: cornerstone.pageToPixel(element, e.pageX, e.pageY),
+ client: {
+ x: e.clientX,
+ y: e.clientY
}
};
currentPoints.canvas = cornerstone.pixelToCanvas(element, currentPoints.image);
// Calculate delta values in page and image coordinates
var deltaPoints = {
- page: cornerstoneMath.point.subtract(currentPoints.page, lastPoints.page), image: cornerstoneMath.point.subtract(currentPoints.image, lastPoints.image), client: cornerstoneMath.point.subtract(currentPoints.client, lastPoints.client), canvas: cornerstoneMath.point.subtract(currentPoints.canvas, lastPoints.canvas)
+ page: cornerstoneMath.point.subtract(currentPoints.page, lastPoints.page),
+ image: cornerstoneMath.point.subtract(currentPoints.image, lastPoints.image),
+ client: cornerstoneMath.point.subtract(currentPoints.client, lastPoints.client),
+ canvas: cornerstoneMath.point.subtract(currentPoints.canvas, lastPoints.canvas)
};
var eventData = {
@@ -329,16 +361,23 @@ if (typeof cornerstoneTools === 'undefined') {
/*jshint newcap: false */
- var initScale = 1.0,
+ var lastScale = 1.0,
lastRotation = 0.0,
startPoints,
currentPoints,
lastPoints,
deltaPoints,
- eventData;
+ eventData,
+ touchStartDelay,
+ pressDelay = 700,
+ pressTimeout,
+ isPress = false,
+ pressMaxDistance = 5,
+ pageDistanceMoved,
+ preventNextPinch = false;
function onTouch(e) {
- console.log(e.type);
+ ///console.log(e.type);
var element = e.target.parentNode,
event,
eventType;
@@ -346,8 +385,18 @@ if (typeof cornerstoneTools === 'undefined') {
// Prevent mouse events from occurring alongside touch events
e.preventDefault();
+ // If more than one finger is placed on the element, stop the press timeout
+ if ((e.pointers && e.pointers.length > 1) ||
+ (e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length > 1)) {
+ isPress = false;
+ clearTimeout(pressTimeout);
+ }
+
switch (e.type) {
case 'tap':
+ isPress = false;
+ clearTimeout(pressTimeout);
+
// calculate our current points in page and image coordinates
currentPoints = {
page: cornerstoneMath.point.pageToPoint(e.pointers[0]),
@@ -375,6 +424,9 @@ if (typeof cornerstoneTools === 'undefined') {
break;
case 'doubletap':
+ isPress = false;
+ clearTimeout(pressTimeout);
+
// calculate our current points in page and image coordinates
currentPoints = {
page: cornerstoneMath.point.pageToPoint(e.pointers[0]),
@@ -401,41 +453,25 @@ if (typeof cornerstoneTools === 'undefined') {
$(element).trigger(event, eventData);
break;
- case 'press':
- // calculate our current points in page and image coordinates
- currentPoints = {
- page: cornerstoneMath.point.pageToPoint(e.pointers[0]),
- image: cornerstone.pageToPixel(element, e.pointers[0].pageX, e.pointers[0].pageY),
- client: {
- x: e.pointers[0].clientX,
- y: e.pointers[0].clientY
- }
- };
- currentPoints.canvas = cornerstone.pixelToCanvas(element, currentPoints.image);
-
- eventType = 'CornerstoneToolsTouchPress';
- eventData = {
- event: e,
- viewport: cornerstone.getViewport(element),
- image: cornerstone.getEnabledElement(element).image,
- element: element,
- currentPoints: currentPoints,
- type: eventType,
- isTouchEvent: true
- };
-
- event = $.Event(eventType, eventData);
- $(element).trigger(event, eventData);
- break;
-
case 'pinchstart':
- var viewport = cornerstone.getViewport(element);
- initScale = viewport.scale || 1;
+ isPress = false;
+ clearTimeout(pressTimeout);
+
+ lastScale = 1.0;
break;
case 'pinchmove':
- var scaleChange = initScale * e.scale;
-
+ isPress = false;
+ clearTimeout(pressTimeout);
+
+ if (preventNextPinch === true) {
+ lastScale = e.scale;
+ preventNextPinch = false;
+ break;
+ }
+
+ var scaleChange = (e.scale - lastScale) / lastScale;
+
startPoints = {
page: e.center,
image: cornerstone.pageToPixel(element, e.center.x, e.center.y),
@@ -457,46 +493,106 @@ if (typeof cornerstoneTools === 'undefined') {
event = $.Event(eventType, eventData);
$(element).trigger(event, eventData);
+
+ lastScale = e.scale;
break;
case 'touchstart':
- startPoints = {
- page: cornerstoneMath.point.pageToPoint(e.originalEvent.touches[0]),
- image: cornerstone.pageToPixel(element, e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY),
- client: {
- x: e.originalEvent.touches[0].clientX,
- y: e.originalEvent.touches[0].clientY
+ lastScale = 1.0;
+
+ clearTimeout(pressTimeout);
+
+ clearTimeout(touchStartDelay);
+ touchStartDelay = setTimeout(function() {
+ startPoints = {
+ page: cornerstoneMath.point.pageToPoint(e.originalEvent.touches[0]),
+ image: cornerstone.pageToPixel(element, e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY),
+ client: {
+ x: e.originalEvent.touches[0].clientX,
+ y: e.originalEvent.touches[0].clientY
+ }
+ };
+ startPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image);
+
+ eventType = 'CornerstoneToolsTouchStart';
+ if (e.originalEvent.touches.length > 1) {
+ eventType = 'CornerstoneToolsMultiTouchStart';
}
- };
- startPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image);
- eventType = 'CornerstoneToolsTouchStart';
+ eventData = {
+ event: e,
+ viewport: cornerstone.getViewport(element),
+ image: cornerstone.getEnabledElement(element).image,
+ element: element,
+ startPoints: startPoints,
+ currentPoints: startPoints,
+ type: eventType,
+ isTouchEvent: true
+ };
- eventData = {
- event: e.srcEvent,
- viewport: cornerstone.getViewport(element),
- image: cornerstone.getEnabledElement(element).image,
- element: element,
- startPoints: startPoints,
- currentPoints: startPoints,
- type: eventType,
- isTouchEvent: true
- };
+ event = $.Event(eventType, eventData);
+ $(element).trigger(event, eventData);
- event = $.Event(eventType, eventData);
- $(eventData.element).trigger(event, eventData);
+ if (event.isImmediatePropagationStopped() === false) {
+ //isPress = false;
+ //clearTimeout(pressTimeout);
- if (event.isImmediatePropagationStopped() === false) {
- // No current tools responded to the drag action.
- // Create new tool measurement
- eventType = 'CornerstoneToolsTouchStartActive';
- eventData.type = eventType;
- $(element).trigger(eventType, eventData);
- }
- lastPoints = cornerstoneTools.copyPoints(startPoints);
+ // No current tools responded to the drag action.
+ // Create new tool measurement
+ eventType = 'CornerstoneToolsTouchStartActive';
+ if (e.originalEvent.touches.length > 1) {
+ eventType = 'CornerstoneToolsMultiTouchStartActive';
+ }
+
+ eventData.type = eventType;
+ $(element).trigger(eventType, eventData);
+ }
+
+ //console.log(eventType);
+ lastPoints = cornerstoneTools.copyPoints(startPoints);
+ }, 50);
+
+ isPress = true;
+ pageDistanceMoved = 0;
+ pressTimeout = setTimeout(function() {
+ if (!isPress) {
+ return;
+ }
+
+ currentPoints = {
+ page: cornerstoneMath.point.pageToPoint(e.originalEvent.touches[0]),
+ image: cornerstone.pageToPixel(element, e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY),
+ client: {
+ x: e.originalEvent.touches[0].clientX,
+ y: e.originalEvent.touches[0].clientY
+ }
+ };
+ currentPoints.canvas = cornerstone.pixelToCanvas(element, startPoints.image);
+
+ eventType = 'CornerstoneToolsTouchPress';
+ eventData = {
+ event: e,
+ viewport: cornerstone.getViewport(element),
+ image: cornerstone.getEnabledElement(element).image,
+ element: element,
+ currentPoints: currentPoints,
+ type: eventType,
+ isTouchEvent: true
+ };
+
+ event = $.Event(eventType, eventData);
+ $(element).trigger(event, eventData);
+
+ //console.log(eventType);
+ }, pressDelay);
break;
case 'touchend':
+ lastScale = 1.0;
+
+ isPress = false;
+ clearTimeout(pressTimeout);
+
startPoints = {
page: cornerstoneMath.point.pageToPoint(e.originalEvent.changedTouches[0]),
image: cornerstone.pageToPixel(element, e.originalEvent.changedTouches[0].pageX, e.originalEvent.changedTouches[0].pageY),
@@ -510,7 +606,7 @@ if (typeof cornerstoneTools === 'undefined') {
eventType = 'CornerstoneToolsTouchEnd';
eventData = {
- event: e.srcEvent,
+ event: e,
viewport: cornerstone.getViewport(element),
image: cornerstone.getEnabledElement(element).image,
element: element,
@@ -521,7 +617,7 @@ if (typeof cornerstoneTools === 'undefined') {
};
event = $.Event(eventType, eventData);
- $(eventData.element).trigger(event, eventData);
+ $(element).trigger(event, eventData);
break;
case 'panmove':
@@ -543,6 +639,14 @@ if (typeof cornerstoneTools === 'undefined') {
client: cornerstoneMath.point.subtract(currentPoints.client, lastPoints.client),
canvas: cornerstoneMath.point.subtract(currentPoints.canvas, lastPoints.canvas)
};
+
+ pageDistanceMoved += Math.sqrt(deltaPoints.page.x * deltaPoints.page.x + deltaPoints.page.y * deltaPoints.page.y);
+ //console.log("pageDistanceMoved: " + pageDistanceMoved);
+ if (pageDistanceMoved > pressMaxDistance) {
+ //console.log('Press event aborted due to movement');
+ isPress = false;
+ clearTimeout(pressTimeout);
+ }
eventType = 'CornerstoneToolsTouchDrag';
if (e.pointers.length > 1) {
@@ -568,7 +672,23 @@ if (typeof cornerstoneTools === 'undefined') {
lastPoints = cornerstoneTools.copyPoints(currentPoints);
break;
+ case 'panstart':
+ currentPoints = {
+ page: cornerstoneMath.point.pageToPoint(e.pointers[0]),
+ image: cornerstone.pageToPixel(element, e.pointers[0].pageX, e.pointers[0].pageY),
+ client: {
+ x: e.pointers[0].clientX,
+ y: e.pointers[0].clientY
+ }
+ };
+ currentPoints.canvas = cornerstone.pixelToCanvas(element, currentPoints.image);
+ lastPoints = cornerstoneTools.copyPoints(currentPoints);
+ break;
+
case 'panend':
+ isPress = false;
+ clearTimeout(pressTimeout);
+
// If lastPoints is not yet set, it means panend fired without panstart or pan,
// so we can ignore this event
if (!lastPoints) {
@@ -610,9 +730,18 @@ if (typeof cornerstoneTools === 'undefined') {
event = $.Event(eventType, eventData);
$(element).trigger(event, eventData);
+
+ var remainingPointers = e.pointers.length - e.changedPointers.length;
+ if (remainingPointers === 2) {
+ preventNextPinch = true;
+ }
+
return cornerstoneTools.pauseEvent(e);
case 'rotatemove':
+ isPress = false;
+ clearTimeout(pressTimeout);
+
var rotation = e.rotation - lastRotation;
lastRotation = e.rotation;
@@ -629,11 +758,14 @@ if (typeof cornerstoneTools === 'undefined') {
$(element).trigger(event, eventData);
break;
}
+
+ //console.log(eventType);
return false;
}
function enable(element) {
disable(element);
+
var hammerOptions = {
inputClass: Hammer.SUPPORT_POINTER_EVENTS ? Hammer.PointerEventInput : Hammer.TouchInput
};
@@ -653,29 +785,30 @@ if (typeof cornerstoneTools === 'undefined') {
var rotate = new Hammer.Rotate({
threshold: 0
});
- var press = new Hammer.Press({
- threshold: 10
- });
// we want to detect both the same time
pinch.recognizeWith(pan);
pinch.recognizeWith(rotate);
// add to the Manager
- mc.add([ pan, press, rotate, pinch ]);
- mc.on('press tap doubletap panstart panmove panend pinchstart pinchmove rotatemove', onTouch);
+ mc.add([ pan, rotate, pinch ]);
+ mc.on('tap doubletap panstart panmove panend pinchstart pinchmove rotatemove', onTouch);
- $(element).data('hammer', mc);
+ cornerstoneTools.preventGhostClick.enable(element);
$(element).on('touchstart touchend', onTouch);
- cornerstoneTools.preventGhostClick(element);
+ $(element).data('hammer', mc);
+ //console.log('touchInput enabled');
}
function disable(element) {
+ cornerstoneTools.preventGhostClick.disable(element);
$(element).off('touchstart touchend', onTouch);
var mc = $(element).data('hammer');
if (mc) {
- mc.off('press tap doubletap panstart panmove panend pinchmove rotatemove', onTouch);
+ mc.off('tap doubletap panstart panmove panend pinchmove rotatemove', onTouch);
}
+
+ //console.log('touchInput disabled');
}
// module exports
@@ -700,7 +833,8 @@ if (typeof cornerstoneTools === 'undefined') {
activate: function(element, mouseButtonMask, options) {
$(element).off('CornerstoneToolsMouseDownActivate', mouseDownCallback);
var eventData = {
- mouseButtonMask: mouseButtonMask, options: options
+ mouseButtonMask: mouseButtonMask,
+ options: options
};
$(element).on('CornerstoneToolsMouseDownActivate', eventData, mouseDownCallback);
},
@@ -828,14 +962,6 @@ if (typeof cornerstoneTools === 'undefined') {
data.active = !data.active;
imageNeedsUpdate = true;
}
-
- //TODO: ayselafsar
- //Check hover event for lesion tool text
- if (mouseToolInterface.toolType === "lesion") {
- if ( mouseToolInterface.pointNearToolForText(eventData.element, data, coords) && !data.active) {
- data.active = !data.active;
- }
- }
}
// Handle activation status changed, redraw the image
@@ -871,7 +997,7 @@ if (typeof cornerstoneTools === 'undefined') {
for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i];
var distanceSq = 25;
- var handle = cornerstoneTools.getHandleNearImagePoint(element, data, coords, distanceSq);
+ var handle = cornerstoneTools.getHandleNearImagePoint(element, data.handles, coords, distanceSq);
if (handle) {
$(element).off('CornerstoneToolsMouseMove', mouseToolInterface.mouseMoveCallback || mouseMoveCallback);
data.active = true;
@@ -900,19 +1026,6 @@ if (typeof cornerstoneTools === 'undefined') {
}
}
}
-
- //TODO: ayselafsar: Add check statement if mouse down location is on text callout. If so, call moveTextCallout function
- if(toolData !== undefined && mouseToolInterface.pointNearToolForText !== undefined) {
- for(i=0; i < toolData.data.length; i++) {
- data = toolData.data[i];
- if(mouseToolInterface.pointNearToolForText(eventData.element, data, coords)) {
- $(eventData.element).off('CornerstoneToolsMouseMove', mouseMoveCallback);
- cornerstoneTools.moveTextHandles(e, data, toolData, true);
- e.stopImmediatePropagation();
- return false;
- }
- }
- }
}
}
///////// END DEACTIVE TOOL ///////
@@ -1137,10 +1250,12 @@ if (typeof cornerstoneTools === 'undefined') {
var i;
// now check to see if there is a handle we can move
+ var distanceSq = 25;
+
if (toolData !== undefined) {
for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i];
- var handle = cornerstoneTools.getHandleNearImagePoint(eventData.element, data, coords);
+ var handle = cornerstoneTools.getHandleNearImagePoint(eventData.element, data.handles, coords, distanceSq);
if (handle !== undefined) {
$(eventData.element).off('CornerstoneToolsMouseMove', mouseMoveCallback);
data.active = true;
@@ -1153,12 +1268,17 @@ if (typeof cornerstoneTools === 'undefined') {
// Now check to see if there is a line we can move
// now check to see if we have a tool that we can move
+ var options = {
+ deleteIfHandleOutsideImage: true,
+ preventHandleOutsideImage: preventHandleOutsideImage
+ };
+
if (toolData !== undefined && mouseToolInterface.pointInsideRect !== undefined) {
for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i];
if (mouseToolInterface.pointInsideRect(eventData.element, data, coords)) {
$(eventData.element).off('CornerstoneToolsMouseMove', mouseMoveCallback);
- cornerstoneTools.moveAllHandles(e, data, toolData, false, preventHandleOutsideImage);
+ cornerstoneTools.moveAllHandles(e, data, toolData, options);
$(eventData.element).on('CornerstoneToolsMouseMove', mouseMoveCallback);
e.stopImmediatePropagation();
return false;
@@ -1296,17 +1416,20 @@ if (typeof cornerstoneTools === 'undefined') {
if (options && options.activateCallback) {
options.activateCallback(element);
}
- }, disable: function(element) {
+ },
+ disable: function(element) {
$(element).off(events, touchDragCallback);
if (options && options.disableCallback) {
options.disableCallback(element);
}
- }, enable: function(element) {
+ },
+ enable: function(element) {
$(element).off(events, touchDragCallback);
if (options && options.enableCallback) {
options.enableCallback(element);
}
- }, deactivate: function(element) {
+ },
+ deactivate: function(element) {
$(element).off(events, touchDragCallback);
if (options && options.deactivateCallback) {
options.deactivateCallback(element);
@@ -1384,6 +1507,7 @@ if (typeof cornerstoneTools === 'undefined') {
///////// BEGIN ACTIVE TOOL ///////
function addNewMeasurement(touchEventData) {
+ //console.log('touchTool addNewMeasurement');
var element = touchEventData.element;
var measurementData = touchToolInterface.createNewMeasurement(touchEventData);
@@ -1408,7 +1532,6 @@ if (typeof cornerstoneTools === 'undefined') {
}
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
cornerstone.updateImage(element);
@@ -1421,13 +1544,13 @@ if (typeof cornerstoneTools === 'undefined') {
}
$(element).on('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
- $(element).on('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
cornerstone.updateImage(element);
});
}
function touchDownActivateCallback(e, eventData) {
+ //console.log('touchTool touchDownActivateCallback');
if (touchToolInterface.addNewMeasurement) {
touchToolInterface.addNewMeasurement(eventData);
} else {
@@ -1439,38 +1562,8 @@ if (typeof cornerstoneTools === 'undefined') {
///////// END ACTIVE TOOL ///////
///////// BEGIN INACTIVE TOOL ///////
- function touchMoveCallback(e, eventData) {
- cornerstoneTools.toolCoordinates.setCoords(eventData);
-
- // if we have no tool data for this element, do nothing
- var toolData = cornerstoneTools.getToolState(eventData.element, touchToolInterface.toolType);
- if (!toolData) {
- return;
- }
-
- // We have tool data, search through all data
- // and see if we can activate a handle
- var imageNeedsUpdate = false;
- var distanceThreshold = 50;
- for (var i = 0; i < toolData.data.length; i++) {
- // get the touch position in canvas coordinates
- var coords = eventData.currentPoints.canvas;
-
- var data = toolData.data[i];
- if (cornerstoneTools.handleActivator(eventData.element, data.handles, coords, distanceThreshold) === true) {
- imageNeedsUpdate = true;
- }
- }
-
- // Handle activation status changed, redraw the image
- if (imageNeedsUpdate === true) {
- cornerstone.updateImage(eventData.element);
- }
-
- return false;
- }
-
function tapCallback(e, eventData) {
+ //console.log('touchTool tapCallback');
var element = eventData.element;
var coords = eventData.currentPoints.canvas;
var toolData = cornerstoneTools.getToolState(e.currentTarget, touchToolInterface.toolType);
@@ -1481,6 +1574,7 @@ if (typeof cornerstoneTools === 'undefined') {
deactivateAllToolInstances(toolData);
function doneMovingCallback() {
+ console.log('touchTool tapCallback doneMovingCallback');
deactivateAllToolInstances(toolData);
if (cornerstoneTools.anyHandlesOutsideImage(eventData, data.handles)) {
// delete the measurement
@@ -1488,7 +1582,6 @@ if (typeof cornerstoneTools === 'undefined') {
}
cornerstone.updateImage(element);
- $(element).on('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).on('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
}
@@ -1498,9 +1591,8 @@ if (typeof cornerstoneTools === 'undefined') {
for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i];
var distanceSq = 75; // Should probably make this a settable property later
- var handle = cornerstoneTools.getHandleNearImagePoint(element, data, coords, distanceSq);
+ var handle = cornerstoneTools.getHandleNearImagePoint(element, data.handles, coords, distanceSq);
if (handle) {
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
data.active = true;
@@ -1518,7 +1610,6 @@ if (typeof cornerstoneTools === 'undefined') {
for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i];
if (touchToolInterface.pointNearTool(element, data, coords)) {
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
data.active = true;
@@ -1542,6 +1633,7 @@ if (typeof cornerstoneTools === 'undefined') {
}
function touchStartCallback(e, eventData) {
+ //console.log('touchTool touchStartCallback');
var element = eventData.element;
var coords = eventData.startPoints.canvas;
var data;
@@ -1549,6 +1641,7 @@ if (typeof cornerstoneTools === 'undefined') {
var i;
function doneMovingCallback() {
+ //console.log('touchTool touchStartCallback doneMovingCallback');
data.active = false;
data.invalidated = true;
if (cornerstoneTools.anyHandlesOutsideImage(eventData, data.handles)) {
@@ -1557,7 +1650,6 @@ if (typeof cornerstoneTools === 'undefined') {
}
cornerstone.updateImage(eventData.element);
- $(element).on('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).on('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
}
@@ -1569,9 +1661,8 @@ if (typeof cornerstoneTools === 'undefined') {
for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i];
- var handle = cornerstoneTools.getHandleNearImagePoint(eventData.element, data, coords, distanceSq);
+ var handle = cornerstoneTools.getHandleNearImagePoint(eventData.element, data.handles, coords, distanceSq);
if (handle) {
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
data.active = true;
@@ -1587,11 +1678,9 @@ if (typeof cornerstoneTools === 'undefined') {
for (i = 0; i < toolData.data.length; i++) {
data = toolData.data[i];
if (touchToolInterface.pointNearTool(eventData.element, data, coords)) {
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
cornerstoneTools.touchMoveAllHandles(e, data, toolData, true, doneMovingCallback);
- $(element).on('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
e.stopImmediatePropagation();
return false; // false = causes jquery to preventDefault() and stopPropagation() this event
}
@@ -1603,7 +1692,6 @@ if (typeof cornerstoneTools === 'undefined') {
// not visible, not interactive
function disable(element) {
$(element).off('CornerstoneImageRendered', touchToolInterface.onImageRendered);
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
@@ -1622,7 +1710,6 @@ if (typeof cornerstoneTools === 'undefined') {
// visible but not interactive
function enable(element) {
$(element).off('CornerstoneImageRendered', touchToolInterface.onImageRendered);
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
@@ -1642,14 +1729,14 @@ if (typeof cornerstoneTools === 'undefined') {
// visible, interactive and can create
function activate(element) {
+ //console.log('activate touchTool');
+
$(element).off('CornerstoneImageRendered', touchToolInterface.onImageRendered);
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
$(element).on('CornerstoneImageRendered', touchToolInterface.onImageRendered);
- $(element).on('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).on('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).on('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
@@ -1669,14 +1756,14 @@ if (typeof cornerstoneTools === 'undefined') {
// visible, interactive
function deactivate(element) {
+ //console.log('deactivate touchTool');
+
$(element).off('CornerstoneImageRendered', touchToolInterface.onImageRendered);
- $(element).off('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
$(element).off('CornerstoneToolsTouchStart', touchToolInterface.touchStartCallback || touchStartCallback);
$(element).off('CornerstoneToolsTouchStartActive', touchToolInterface.touchDownActivateCallback || touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
$(element).on('CornerstoneImageRendered', touchToolInterface.onImageRendered);
- $(element).on('CornerstoneToolsTouchDrag', touchToolInterface.touchMoveCallback || touchMoveCallback);
//$(element).on('CornerstoneToolsTap', touchToolInterface.tapCallback || tapCallback);
if (touchToolInterface.doubleTapCallback) {
@@ -1698,11 +1785,15 @@ if (typeof cornerstoneTools === 'undefined') {
activate: activate,
deactivate: deactivate,
touchStartCallback: touchToolInterface.touchStartCallback || touchStartCallback,
- touchMoveCallback: touchToolInterface.touchMoveCallback || touchMoveCallback,
touchDownActivateCallback: touchToolInterface.touchDownActivateCallback || touchDownActivateCallback,
tapCallback: touchToolInterface.tapCallback || tapCallback
};
+ // Expose pointNearTool if available
+ if (touchToolInterface.pointNearTool) {
+ toolInterface.pointNearTool = touchToolInterface.pointNearTool;
+ }
+
if (touchToolInterface.doubleTapCallback) {
toolInterface.doubleTapCallback = touchToolInterface.doubleTapCallback;
}
@@ -1812,9 +1903,9 @@ if (typeof cornerstoneTools === 'undefined') {
// configurable shadow
if (config && config.shadow) {
- context.shadowColor = '#000000';
- context.shadowOffsetX = +1;
- context.shadowOffsetY = +1;
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
}
var data = toolData.data[i];
@@ -2000,7 +2091,24 @@ if (typeof cornerstoneTools === 'undefined') {
};
var distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
- return (distanceToPoint < 25);
+ if (distanceToPoint < 25) {
+ return true;
+ }
+
+ if (data.textCoords) {
+ var padding = 5;
+ var fontSize = cornerstoneTools.textStyle.getFontSize();
+
+ var rect = {
+ left: data.textCoords.x,
+ top: data.textCoords.y,
+ width: data.textWidth + (padding * 2),
+ height: fontSize + (padding * 2)
+ };
+
+ var distanceToTextRect = cornerstoneMath.rect.distanceToPoint(rect, coords);
+ return (distanceToTextRect < 25);
+ }
}
function drawArrow(context, start, end, color, lineWidth) {
@@ -2057,6 +2165,12 @@ if (typeof cornerstoneTools === 'undefined') {
for (var i = 0; i < toolData.data.length; i++) {
context.save();
+ if (config && config.shadow) {
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
+ }
+
var data = toolData.data[i];
if (data.active) {
@@ -2102,11 +2216,13 @@ if (typeof cornerstoneTools === 'undefined') {
y: handleEndCanvas.y - handleStartCanvas.y
};
+ var textWidth = context.measureText(data.text).width;
+
var textCoords;
if (config.arrowFirst) {
// Fix text placement if arrow faces right
if (vector.x < 0) {
- displacement.x = -displacement.x - context.measureText(data.text).width;
+ displacement.x = -displacement.x - textWidth;
}
textCoords = {
@@ -2116,7 +2232,7 @@ if (typeof cornerstoneTools === 'undefined') {
} else {
// Fix text placement if arrow faces right
if (vector.x > 0) {
- displacement.x = -displacement.x - context.measureText(data.text).width;
+ displacement.x = -displacement.x - textWidth;
}
textCoords = {
@@ -2125,6 +2241,8 @@ if (typeof cornerstoneTools === 'undefined') {
};
}
+ data.textCoords = textCoords;
+ data.textWidth = textWidth;
cornerstoneTools.drawTextBox(context, data.text, textCoords.x, textCoords.y, color);
}
@@ -2150,8 +2268,7 @@ if (typeof cornerstoneTools === 'undefined') {
var measurementData = createNewMeasurement(touchEventData);
cornerstoneTools.addToolState(element, toolType, measurementData);
- $(element).off('CornerstoneToolsTouchDrag', cornerstoneTools.arrowAnnotateTouch.touchMoveCallback);
- $(element).off('CornerstoneToolsDragStartActive', cornerstoneTools.arrowAnnotateTouch.touchDownActivateCallback);
+ $(element).off('CornerstoneToolsTouchStartActive', cornerstoneTools.arrowAnnotateTouch.touchDownActivateCallback);
$(element).off('CornerstoneToolsTap', cornerstoneTools.arrowAnnotateTouch.tapCallback);
cornerstone.updateImage(element);
@@ -2168,8 +2285,7 @@ if (typeof cornerstoneTools === 'undefined') {
config.getTextCallback(doneChangingTextCallback);
}
- $(element).on('CornerstoneToolsTouchDrag', cornerstoneTools.arrowAnnotateTouch.touchMoveCallback);
- $(element).on('CornerstoneToolsDragStartActive', cornerstoneTools.arrowAnnotateTouch.touchDownActivateCallback);
+ $(element).on('CornerstoneToolsTouchStartActive', cornerstoneTools.arrowAnnotateTouch.touchDownActivateCallback);
$(element).on('CornerstoneToolsTap', cornerstoneTools.arrowAnnotateTouch.tapCallback);
});
}
@@ -2219,6 +2335,55 @@ if (typeof cornerstoneTools === 'undefined') {
return false; // false = causes jquery to preventDefault() and stopPropagation() this event
}
+ function pressCallback(e, eventData) {
+ var element = eventData.element;
+ var data;
+
+ function doneChangingTextCallback(data, updatedText, deleteTool) {
+ if (deleteTool === true) {
+ cornerstoneTools.removeToolState(element, toolType, data);
+ } else {
+ data.text = updatedText;
+ }
+
+ data.active = false;
+ cornerstone.updateImage(element);
+ $(element).on('CornerstoneToolsTouchStart', cornerstoneTools.arrowAnnotateTouch.touchStartCallback);
+ $(element).on('CornerstoneToolsTouchStartActive', cornerstoneTools.arrowAnnotateTouch.touchDownActivateCallback);
+ }
+
+ if (e.data && e.data.mouseButtonMask && !cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
+ return false;
+ }
+
+ var config = cornerstoneTools.arrowAnnotate.getConfiguration();
+
+ var coords = eventData.currentPoints.canvas;
+ var toolData = cornerstoneTools.getToolState(element, toolType);
+
+ // now check to see if there is a handle we can move
+ if (!toolData) {
+ return false;
+ }
+
+ for (var i = 0; i < toolData.data.length; i++) {
+ data = toolData.data[i];
+ if (pointNearTool(element, data, coords)) {
+ data.active = true;
+ cornerstone.updateImage(element);
+ // Allow relabelling via a callback
+ $(element).off('CornerstoneToolsTouchStart', cornerstoneTools.arrowAnnotateTouch.touchStartCallback);
+ $(element).off('CornerstoneToolsTouchStartActive', cornerstoneTools.arrowAnnotateTouch.touchDownActivateCallback);
+ config.changeTextCallback(data, doneChangingTextCallback);
+
+ e.stopImmediatePropagation();
+ return false;
+ }
+ }
+
+ return false; // false = causes jquery to preventDefault() and stopPropagation() this event
+ }
+
cornerstoneTools.arrowAnnotate = cornerstoneTools.mouseButtonTool({
addNewMeasurement: addNewMeasurement,
createNewMeasurement: createNewMeasurement,
@@ -2236,7 +2401,7 @@ if (typeof cornerstoneTools === 'undefined') {
onImageRendered: onImageRendered,
pointNearTool: pointNearTool,
toolType: toolType,
- pressCallback: doubleClickCallback
+ pressCallback: pressCallback
});
})($, cornerstone, cornerstoneMath, cornerstoneTools);
@@ -2251,6 +2416,8 @@ if (typeof cornerstoneTools === 'undefined') {
var toolType = 'crosshairs';
function chooseLocation(e, eventData) {
+ e.stopImmediatePropagation(); // Prevent CornerstoneToolsTouchStartActive from killing any press events
+
// if we have no toolData for this element, return immediately as there is nothing to do
var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
if (!toolData) {
@@ -2478,9 +2645,16 @@ if (typeof cornerstoneTools === 'undefined') {
var color = cornerstoneTools.toolColors.getActiveColor();
var font = cornerstoneTools.textStyle.getFont();
var fontHeight = cornerstoneTools.textStyle.getFontSize();
+ var config = cornerstoneTools.dragProbe.getConfiguration();
context.save();
+ if (config && config.shadow) {
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
+ }
+
var x = Math.round(eventData.currentPoints.image.x);
var y = Math.round(eventData.currentPoints.image.y);
@@ -2538,17 +2712,20 @@ if (typeof cornerstoneTools === 'undefined') {
var color = cornerstoneTools.toolColors.getActiveColor();
var font = cornerstoneTools.textStyle.getFont();
-
+ var config = cornerstoneTools.dragProbe.getConfiguration();
+
context.save();
+ if (config && config.shadow) {
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
+ }
+
var toolCoords;
if (eventData.isTouchEvent === true) {
- var ratio = image.height / enabledElement.canvas.height;
- console.log ('Image to canvas height ratio: ' + ratio);
- toolCoords = {
- x: eventData.currentPoints.image.x,
- y: eventData.currentPoints.image.y - cornerstoneTools.textStyle.getFontSize() * 4 * ratio
- };
+ toolCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x,
+ eventData.currentPoints.page.y - cornerstoneTools.textStyle.getFontSize() * 4);
} else {
toolCoords = eventData.currentPoints.image;
}
@@ -2796,9 +2973,16 @@ if (typeof cornerstoneTools === 'undefined') {
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
var font = cornerstoneTools.textStyle.getFont();
var fontHeight = cornerstoneTools.textStyle.getFontSize();
+ var config = cornerstoneTools.ellipticalRoi.getConfiguration();
for (var i = 0; i < toolData.data.length; i++) {
context.save();
+
+ if (config && config.shadow) {
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
+ }
var data = toolData.data[i];
@@ -3599,11 +3783,12 @@ if (typeof cornerstoneTools === 'undefined') {
for (var i = 0; i < toolData.data.length; i++) {
context.save();
+
// configurable shadow
if (config && config.shadow) {
- context.shadowColor = '#000000';
- context.shadowOffsetX = +1;
- context.shadowOffsetY = +1;
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
}
var data = toolData.data[i];
@@ -3777,9 +3962,15 @@ if (typeof cornerstoneTools === 'undefined') {
var zoomCtx = magnify.getContext('2d');
zoomCtx.setTransform(1, 0, 0, 1, 0, 0);
+ var getSize = magnifySize / magnificationLevel;
+
// Calculate the on-canvas location of the mouse pointer / touch
var canvasLocation = cornerstone.pixelToCanvas(eventData.element, eventData.currentPoints.image);
+ if (eventData.isTouchEvent === true) {
+ canvasLocation.y -= 1.25 * getSize;
+ }
+
canvasLocation.x = Math.max(canvasLocation.x, 0);
canvasLocation.x = Math.min(canvasLocation.x, canvas.width);
@@ -3793,7 +3984,6 @@ if (typeof cornerstoneTools === 'undefined') {
// Fill it with the pixels that the mouse is clicking on
zoomCtx.fillRect(0, 0, magnifySize, magnifySize);
- var getSize = magnifySize / magnificationLevel;
var copyFrom = {
x: canvasLocation.x - 0.5 * getSize,
y: canvasLocation.y - 0.5 * getSize
@@ -3815,14 +4005,9 @@ if (typeof cornerstoneTools === 'undefined') {
};
zoomCtx.drawImage(canvas, copyFrom.x, copyFrom.y, canvas.width - copyFrom.x, canvas.height - copyFrom.y, 0, 0, scaledMagnify.x, scaledMagnify.y);
- if (eventData.isTouchEvent === true) {
- magnify.style.top = canvasLocation.y - 1 * magnifySize + 'px';
- magnify.style.left = canvasLocation.x - 0.5 * magnifySize + 'px';
- } else {
- // Place the magnification tool at the same location as the pointer
- magnify.style.top = canvasLocation.y - 0.5 * magnifySize + 'px';
- magnify.style.left = canvasLocation.x - 0.5 * magnifySize + 'px';
- }
+ // Place the magnification tool at the same location as the pointer
+ magnify.style.top = canvasLocation.y - 0.5 * magnifySize + 'px';
+ magnify.style.left = canvasLocation.x - 0.5 * magnifySize + 'px';
magnify.style.display = 'block';
@@ -4445,7 +4630,8 @@ if (typeof cornerstoneTools === 'undefined') {
var rect = eventData.element.getBoundingClientRect(eventData.element);
var points = {
- x: eventData.currentPoints.client.x, y: eventData.currentPoints.client.y
+ x: eventData.currentPoints.client.x,
+ y: eventData.currentPoints.client.y
};
var width = eventData.element.clientWidth;
@@ -4530,7 +4716,8 @@ if (typeof cornerstoneTools === 'undefined') {
}
cornerstoneTools.rotateTouch = {
- activate: activate, disable: disable
+ activate: activate,
+ disable: disable
};
})($, cornerstone, cornerstoneTools);
@@ -4588,13 +4775,26 @@ if (typeof cornerstoneTools === 'undefined') {
function createNewMeasurement(mouseEventData) {
// create the measurement data for this tool with the end handle activated
var angleData = {
- visible: true, active: true, handles: {
+ visible: true,
+ active: true,
+ handles: {
start: {
- x: mouseEventData.currentPoints.image.x, y: mouseEventData.currentPoints.image.y, highlight: true, active: false
- }, middle: {
- x: mouseEventData.currentPoints.image.x, y: mouseEventData.currentPoints.image.y, highlight: true, active: true
- }, end: {
- x: mouseEventData.currentPoints.image.x, y: mouseEventData.currentPoints.image.y, highlight: true, active: false
+ x: mouseEventData.currentPoints.image.x,
+ y: mouseEventData.currentPoints.image.y,
+ highlight: true,
+ active: false
+ },
+ middle: {
+ x: mouseEventData.currentPoints.image.x,
+ y: mouseEventData.currentPoints.image.y,
+ highlight: true,
+ active: true
+ },
+ end: {
+ x: mouseEventData.currentPoints.image.x,
+ y: mouseEventData.currentPoints.image.y,
+ highlight: true,
+ active: false
}
}
};
@@ -4605,11 +4805,12 @@ if (typeof cornerstoneTools === 'undefined') {
function pointNearTool(element, data, coords) {
var lineSegment = {
- start: cornerstone.pixelToCanvas(element, data.handles.start), end: cornerstone.pixelToCanvas(element, data.handles.middle)
+ start: cornerstone.pixelToCanvas(element, data.handles.start),
+ end: cornerstone.pixelToCanvas(element, data.handles.middle)
};
var distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
- if (distanceToPoint < 5) {
+ if (distanceToPoint < 25) {
return true;
}
@@ -4617,7 +4818,7 @@ if (typeof cornerstoneTools === 'undefined') {
lineSegment.end = cornerstone.pixelToCanvas(element, data.handles.end);
distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
- return (distanceToPoint < 5);
+ return (distanceToPoint < 25);
}
function length(vector) {
@@ -4641,10 +4842,17 @@ if (typeof cornerstoneTools === 'undefined') {
var color;
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
var font = cornerstoneTools.textStyle.getFont();
+ var config = cornerstoneTools.simpleAngle.getConfiguration();
for (var i = 0; i < toolData.data.length; i++) {
context.save();
+ if (config && config.shadow) {
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
+ }
+
var data = toolData.data[i];
//differentiate the color of activation tool
@@ -4685,15 +4893,18 @@ if (typeof cornerstoneTools === 'undefined') {
}
var sideA = {
- x: (Math.ceil(data.handles.middle.x) - Math.ceil(data.handles.start.x)) * columnPixelSpacing, y: (Math.ceil(data.handles.middle.y) - Math.ceil(data.handles.start.y)) * rowPixelSpacing
+ x: (Math.ceil(data.handles.middle.x) - Math.ceil(data.handles.start.x)) * columnPixelSpacing,
+ y: (Math.ceil(data.handles.middle.y) - Math.ceil(data.handles.start.y)) * rowPixelSpacing
};
var sideB = {
- x: (Math.ceil(data.handles.end.x) - Math.ceil(data.handles.middle.x)) * columnPixelSpacing, y: (Math.ceil(data.handles.end.y) - Math.ceil(data.handles.middle.y)) * rowPixelSpacing
+ x: (Math.ceil(data.handles.end.x) - Math.ceil(data.handles.middle.x)) * columnPixelSpacing,
+ y: (Math.ceil(data.handles.end.y) - Math.ceil(data.handles.middle.y)) * rowPixelSpacing
};
var sideC = {
- x: (Math.ceil(data.handles.end.x) - Math.ceil(data.handles.start.x)) * columnPixelSpacing, y: (Math.ceil(data.handles.end.y) - Math.ceil(data.handles.start.y)) * rowPixelSpacing
+ x: (Math.ceil(data.handles.end.x) - Math.ceil(data.handles.start.x)) * columnPixelSpacing,
+ y: (Math.ceil(data.handles.end.y) - Math.ceil(data.handles.start.y)) * rowPixelSpacing
};
var sideALength = length(sideA);
@@ -4789,7 +5000,8 @@ if (typeof cornerstoneTools === 'undefined') {
// since we are dragging to another place to drop the end point, we can just activate
// the end point and let the moveHandle move it for us.
$(element).off('CornerstoneToolsTouchDrag', cornerstoneTools.simpleAngleTouch.touchMoveCallback);
- $(element).off('CornerstoneToolsDragStartActive', cornerstoneTools.simpleAngleTouch.touchDownActivateCallback);
+ $(element).off('CornerstoneToolsTouchStartActive', cornerstoneTools.simpleAngleTouch.touchDownActivateCallback);
+ $(element).off('CornerstoneToolsTouchStart', cornerstoneTools.simpleAngleTouch.touchStartCallback);
$(element).off('CornerstoneToolsTap', cornerstoneTools.simpleAngleTouch.tapCallback);
cornerstone.updateImage(element);
@@ -4811,7 +5023,8 @@ if (typeof cornerstoneTools === 'undefined') {
}
$(element).on('CornerstoneToolsTouchDrag', cornerstoneTools.simpleAngleTouch.touchMoveCallback);
- $(element).on('CornerstoneToolsDragStartActive', cornerstoneTools.simpleAngleTouch.touchDownActivateCallback);
+ $(element).on('CornerstoneToolsTouchStart', cornerstoneTools.simpleAngleTouch.touchStartCallback);
+ $(element).on('CornerstoneToolsTouchStartActive', cornerstoneTools.simpleAngleTouch.touchDownActivateCallback);
$(element).on('CornerstoneToolsTap', cornerstoneTools.simpleAngleTouch.tapCallback);
cornerstone.updateImage(element);
});
@@ -4855,7 +5068,10 @@ if (typeof cornerstoneTools === 'undefined') {
// create the measurement data for this tool with the end handle activated
var measurementData = {
- visible: true, active: true, text: config.current, handles: {
+ visible: true,
+ active: true,
+ text: config.current,
+ handles: {
end: {
x: mouseEventData.currentPoints.image.x,
y: mouseEventData.currentPoints.image.y,
@@ -4921,6 +5137,7 @@ if (typeof cornerstoneTools === 'undefined') {
var font = cornerstoneTools.textStyle.getFont();
var fontSize = cornerstoneTools.textStyle.getFontSize();
+ var config = cornerstoneTools.textMarker.getConfiguration();
for (var i = 0; i < toolData.data.length; i++) {
var data = toolData.data[i];
@@ -4932,6 +5149,12 @@ if (typeof cornerstoneTools === 'undefined') {
context.save();
+ if (config && config.shadow) {
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
+ }
+
// Draw text
context.font = font;
context.fillStyle = color;
@@ -4966,9 +5189,10 @@ if (typeof cornerstoneTools === 'undefined') {
data.active = false;
cornerstone.updateImage(element);
- $(element).off('CornerstoneToolsMouseMove', cornerstoneTools.textMarker.mouseMoveCallback);
- $(element).off('CornerstoneToolsMouseDown', cornerstoneTools.textMarker.mouseDownCallback);
- $(element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.textMarker.mouseDownActivateCallback);
+ $(element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.textMarker.mouseMoveCallback);
+ $(element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.textMarker.mouseDownCallback);
+ $(element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.textMarker.mouseDownActivateCallback);
+ $(element).on('CornerstoneToolsMouseDoubleClick', eventData, cornerstoneTools.textMarker.mouseDoubleClickCallback);
}
if (e.data && e.data.mouseButtonMask && !cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
@@ -4994,6 +5218,7 @@ if (typeof cornerstoneTools === 'undefined') {
$(element).off('CornerstoneToolsMouseMove', cornerstoneTools.textMarker.mouseMoveCallback);
$(element).off('CornerstoneToolsMouseDown', cornerstoneTools.textMarker.mouseDownCallback);
$(element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.textMarker.mouseDownActivateCallback);
+ $(element).off('CornerstoneToolsMouseDoubleClick', cornerstoneTools.textMarker.mouseDoubleClickCallback);
// Allow relabelling via a callback
config.changeTextCallback(data, doneChangingTextCallback);
@@ -5018,6 +5243,12 @@ if (typeof cornerstoneTools === 'undefined') {
data.active = false;
cornerstone.updateImage(element);
+ $(element).off('CornerstoneToolsTouchDrag', cornerstoneTools.textMarkerTouch.touchMoveCallback);
+ $(element).off('CornerstoneToolsTouchStartActive', cornerstoneTools.textMarkerTouch.touchDownActivateCallback);
+ $(element).off('CornerstoneToolsTouchStart', cornerstoneTools.textMarkerTouch.touchStartCallback);
+ $(element).off('CornerstoneToolsTap', cornerstoneTools.textMarkerTouch.tapCallback);
+ $(element).off('CornerstoneToolsTouchPress', cornerstoneTools.textMarkerTouch.pressCallback);
+
$(element).on('CornerstoneToolsTouchDrag', cornerstoneTools.textMarkerTouch.touchMoveCallback);
$(element).on('CornerstoneToolsTouchStartActive', cornerstoneTools.textMarkerTouch.touchDownActivateCallback);
$(element).on('CornerstoneToolsTouchStart', cornerstoneTools.textMarkerTouch.touchStartCallback);
@@ -5125,6 +5356,7 @@ if (typeof cornerstoneTools === 'undefined') {
}
function touchDragCallback(e, eventData) {
+ e.stopImmediatePropagation(); // Prevent CornerstoneToolsTouchStartActive from killing any press events
var dragData = eventData;
var maxVOI = dragData.image.maxPixelValue * dragData.image.slope + dragData.image.intercept;
@@ -5239,7 +5471,7 @@ if (typeof cornerstoneTools === 'undefined') {
/** Calculates the minimum and maximum value in the given pixel array */
function applyWWWCRegion(eventData) {
var toolData = cornerstoneTools.getToolState(eventData.element, toolType);
- if (toolData === undefined) {
+ if (!toolData) {
return;
}
@@ -5274,6 +5506,10 @@ if (typeof cornerstoneTools === 'undefined') {
// Adjust the viewport window width and center based on the calculated values
var config = cornerstoneTools.wwwcRegion.getConfiguration();
var viewport = cornerstone.getViewport(eventData.element);
+ if (config.minWindowWidth === undefined) {
+ config.minWindowWidth = 10;
+ }
+
viewport.voi.windowWidth = Math.max(Math.abs(minMaxMean.max - minMaxMean.min), config.minWindowWidth);
viewport.voi.windowCenter = minMaxMean.mean;
cornerstone.setViewport(eventData.element, viewport);
@@ -5353,7 +5589,7 @@ if (typeof cornerstoneTools === 'undefined') {
function onImageRendered(e, eventData) {
var toolData = cornerstoneTools.getToolState(eventData.element, toolType);
- if (toolData === undefined) {
+ if (!toolData) {
return;
}
@@ -5386,10 +5622,17 @@ if (typeof cornerstoneTools === 'undefined') {
var height = Math.abs(startPointCanvas.y - endPointCanvas.y);
var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
+ var config = cornerstoneTools.wwwcRegion.getConfiguration();
// Draw the rectangle
context.save();
+ if (config && config.shadow) {
+ context.shadowColor = config.shadowColor || '#000000';
+ context.shadowOffsetX = config.shadowOffsetX || 1;
+ context.shadowOffsetY = config.shadowOffsetY || 1;
+ }
+
context.beginPath();
context.strokeStyle = color;
context.lineWidth = lineWidth;
@@ -5677,15 +5920,13 @@ if (typeof cornerstoneTools === 'undefined') {
var config = cornerstoneTools.zoom.getConfiguration();
var viewport = eventData.viewport;
var element = eventData.element;
- var scale = eventData.scaleChange;
// Change the scale based on the pinch gesture's scale change
- if (config.maxScale && scale > config.maxScale) {
+ viewport.scale += eventData.scaleChange * viewport.scale;
+ if (config.maxScale && viewport.scale > config.maxScale) {
viewport.scale = config.maxScale;
- } else if (config.minScale && scale < config.minScale) {
+ } else if (config.minScale && viewport.scale < config.minScale) {
viewport.scale = config.minScale;
- } else {
- viewport.scale = scale;
}
cornerstone.setViewport(element, viewport);
@@ -5733,7 +5974,15 @@ if (typeof cornerstoneTools === 'undefined') {
e = window.event || e; // old IE support
var keyPressData = {
- element: element, viewport: cornerstone.getViewport(element), image: cornerstone.getEnabledElement(element).image, pageX: mouseX, pageY: mouseY, imageX: startingCoords.x, imageY: startingCoords.y, keyCode: e.keyCode, which: e.which
+ element: element,
+ viewport: cornerstone.getViewport(element),
+ image: cornerstone.getEnabledElement(element).image,
+ pageX: mouseX,
+ pageY: mouseY,
+ imageX: startingCoords.x,
+ imageY: startingCoords.y,
+ keyCode: e.keyCode,
+ which: e.which
};
if (e.type === 'keydown') {
@@ -5763,7 +6012,8 @@ if (typeof cornerstoneTools === 'undefined') {
// module exports
cornerstoneTools.keyboardInput = {
- enable: enable, disable: disable
+ enable: enable,
+ disable: disable
};
})($, cornerstone, cornerstoneTools);
@@ -5779,47 +6029,68 @@ if (typeof cornerstoneTools === 'undefined') {
// All credit to @kosich
// https://gist.github.com/kosich/23188dd86633b6c2efb7
- var antiGhostDelay = 2000;
+ var antiGhostDelay = 2000,
+ pointerType = {
+ mouse: 0,
+ touch: 1
+ },
+ lastInteractionType,
+ lastInteractionTime;
- var pointerType = {
- mouse: 0,
- touch: 1
- };
-
- function preventGhostClick(element) {
- var lastInteractionType,
- lastInteractionTime;
-
- function handleTap(type, e) {
- var now = Date.now();
- if (type !== lastInteractionType) {
- if (now - lastInteractionTime <= antiGhostDelay) {
- e.preventDefault();
- e.stopPropagation();
- e.stopImmediatePropagation();
- return false;
- }
-
- lastInteractionType = type;
+ function handleTap(type, e) {
+ var now = Date.now();
+ if (type !== lastInteractionType) {
+ if (now - lastInteractionTime <= antiGhostDelay) {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ return false;
}
- lastInteractionTime = now;
+ lastInteractionType = type;
}
- function attachEvents(eventList, interactionType) {
- eventList.forEach(function(eventName) {
- element.addEventListener(eventName, handleTap.bind(null, interactionType), true);
- });
- }
-
- var mouseEvents = [ 'mousedown', 'mouseup', 'mousemove' ];
- var touchEvents = [ 'touchstart', 'touchend' ];
-
- attachEvents(mouseEvents, pointerType.mouse);
- attachEvents(touchEvents, pointerType.touch);
+ lastInteractionTime = now;
}
- cornerstoneTools.preventGhostClick = preventGhostClick;
+ // Cacheing the function references
+ // Necessary because a new function reference is created after .bind() is called
+ // http://stackoverflow.com/questions/11565471/removing-event-listener-which-was-added-with-bind
+ var handleTapMouse = handleTap.bind(null, pointerType.mouse);
+ var handleTapTouch = handleTap.bind(null, pointerType.touch);
+
+ function attachEvents(element, eventList, interactionType) {
+ var tapHandler = interactionType ? handleTapMouse : handleTapTouch;
+ eventList.forEach(function(eventName) {
+ element.addEventListener(eventName, tapHandler, true);
+ });
+ }
+
+ function removeEvents(element, eventList, interactionType) {
+ var tapHandler = interactionType ? handleTapMouse : handleTapTouch;
+ eventList.forEach(function(eventName) {
+ element.removeEventListener(eventName, tapHandler, true);
+ });
+ }
+
+ var mouseEvents = [ 'mousedown', 'mouseup' ];
+ var touchEvents = [ 'touchstart', 'touchend' ];
+
+ function disable(element) {
+ removeEvents(element, mouseEvents, pointerType.mouse);
+ removeEvents(element, touchEvents, pointerType.touch);
+ }
+
+ function enable(element) {
+ disable(element);
+ attachEvents(element, mouseEvents, pointerType.mouse);
+ attachEvents(element, touchEvents, pointerType.touch);
+ }
+
+ cornerstoneTools.preventGhostClick = {
+ enable: enable,
+ disable: disable
+ };
})(cornerstoneTools);
@@ -5870,6 +6141,10 @@ if (typeof cornerstoneTools === 'undefined') {
Object.keys(handles).forEach(function(name) {
var handle = handles[name];
+ if (handle.drawnIndependently === true) {
+ return;
+ }
+
context.beginPath();
if (handle.active) {
@@ -5902,20 +6177,27 @@ if (typeof cornerstoneTools === 'undefined') {
'use strict';
- function getHandleNearImagePoint(element, data, coords, distanceSq) {
+ function getHandleNearImagePoint(element, handles, coords, distanceThreshold) {
var nearbyHandle;
- if (!data.handles) {
+ if (!handles) {
return;
}
- Object.keys(data.handles).forEach(function(name) {
- var handle = data.handles[name];
- var handleCanvas = cornerstone.pixelToCanvas(element, handle);
- var distanceSquared = cornerstoneMath.point.distanceSquared(handleCanvas, coords);
- if (distanceSquared < distanceSq) {
- nearbyHandle = handle;
- return;
+ Object.keys(handles).forEach(function(name) {
+ var handle = handles[name];
+ if (handle.hasOwnProperty('pointNearHandle')) {
+ if (handle.pointNearHandle(element, handle, coords)) {
+ nearbyHandle = handle;
+ return;
+ }
+ } else {
+ var handleCanvas = cornerstone.pixelToCanvas(element, handle);
+ var distance = cornerstoneMath.point.distance(handleCanvas, coords);
+ if (distance <= distanceThreshold) {
+ nearbyHandle = handle;
+ return;
+ }
}
});
@@ -5934,22 +6216,6 @@ if (typeof cornerstoneTools === 'undefined') {
'use strict';
- function findHandleNear(element, handles, canvasPoint, distanceThreshold) {
- var nearbyHandle;
-
- Object.keys(handles).forEach(function(name) {
- var handle = handles[name];
- var handleCanvas = cornerstone.pixelToCanvas(element, handle);
- var distance = cornerstoneMath.point.distance(handleCanvas, canvasPoint);
- if (distance <= distanceThreshold) {
- nearbyHandle = handle;
- return;
- }
- });
-
- return nearbyHandle;
- }
-
function getActiveHandle(handles) {
var activeHandle;
@@ -5970,7 +6236,7 @@ if (typeof cornerstoneTools === 'undefined') {
}
var activeHandle = getActiveHandle(handles);
- var nearbyHandle = findHandleNear(element, handles, canvasPoint, distanceThreshold);
+ var nearbyHandle = cornerstoneTools.getHandleNearImagePoint(element, handles, canvasPoint, distanceThreshold);
if (activeHandle !== nearbyHandle) {
if (nearbyHandle !== undefined) {
nearbyHandle.active = true;
@@ -6006,6 +6272,10 @@ if (typeof cornerstoneTools === 'undefined') {
Object.keys(data.handles).forEach(function(name) {
var handle = data.handles[name];
+ if (handle.movesIndependently === true) {
+ return;
+ }
+
handle.x += eventData.deltaPoints.image.x;
handle.y += eventData.deltaPoints.image.y;
@@ -6036,7 +6306,10 @@ if (typeof cornerstoneTools === 'undefined') {
var image = eventData.image;
var handleOutsideImage = false;
var rect = {
- top: 0, left: 0, width: image.width, height: image.height
+ top: 0,
+ left: 0,
+ width: image.width,
+ height: image.height
};
Object.keys(data.handles).forEach(function(name) {
@@ -6065,7 +6338,10 @@ if (typeof cornerstoneTools === 'undefined') {
}
cornerstone.updateImage(element);
- doneMovingCallback();
+
+ if (typeof doneMovingCallback === 'function') {
+ doneMovingCallback();
+ }
}
$(element).on('CornerstoneToolsMouseUp', mouseUpCallback);
@@ -6073,94 +6349,8 @@ if (typeof cornerstoneTools === 'undefined') {
return true;
}
- //TODO:moveTextHandles-ayselafsar
- function moveTextHandles(e, data, toolData, deleteIfHandleOutsideImage, preventHandleOutsideImage)
- {
- var mouseEventData = e;
- var element = mouseEventData.element;
-
- function mouseDragCallback(e, eventData)
- {
- for(var property in data.handles) {
- var handle = data.linkedTextCoords[property];
- handle.x += eventData.deltaPoints.image.x;
- handle.y += eventData.deltaPoints.image.y;
- if (preventHandleOutsideImage)
- {
- if (handle.x < 0)
- {
- handle.x = 0;
- }
- if (handle.x > eventData.image.width)
- {
- handle.x = eventData.image.width;
- }
- if (handle.y < 0)
- {
- handle.y = 0;
- }
- if (handle.y > eventData.image.height)
- {
- handle.y = eventData.image.height;
- }
- }
- }
- cornerstone.updateImage(element);
- return false; // false = cases jquery to preventDefault() and stopPropagation() this event
- }
-
- $(element).on("CornerstoneToolsMouseDrag", mouseDragCallback);
-
- function mouseUpCallback(e, eventData) {
- data.moving = false;
-
- $(element).off('CornerstoneToolsMouseDrag', mouseDragCallback);
- $(element).off('CornerstoneToolsMouseUp', mouseUpCallback);
-
- // If any handle is outside the image, delete the tool data
-
- if(deleteIfHandleOutsideImage === true) {
- var image = eventData.image;//.getEnabledElement(element).image;
- var handleOutsideImage = false;
- var rect = {
- top: 0,
- left: 0,
- width: image.width,
- height: image.height
- };
- for(var property in data.handles) {
- var handle = data.handles[property];
- if(cornerstoneMath.point.insideRect(handle, rect) === false)
- {
- handleOutsideImage = true;
- }
- }
-
- if(handleOutsideImage)
- {
- // find this tool data
- var indexOfData = -1;
- for(var i = 0; i < toolData.data.length; i++) {
- if(toolData.data[i] === data)
- {
- indexOfData = i;
- }
- }
- if(indexOfData !== -1) {
- toolData.data.splice(indexOfData, 1);
- }
- }
- }
- cornerstone.updateImage(element);
- }
- $(element).on("CornerstoneToolsMouseUp",mouseUpCallback);
- return true;
- }
-
-
// module/private exports
cornerstoneTools.moveAllHandles = moveAllHandles;
- cornerstoneTools.moveTextHandles = moveTextHandles;
})($, cornerstone, cornerstoneMath, cornerstoneTools);
@@ -6173,11 +6363,16 @@ if (typeof cornerstoneTools === 'undefined') {
function moveHandle(mouseEventData, handle, doneMovingCallback, preventHandleOutsideImage) {
var element = mouseEventData.element;
+ var distanceFromTool = {
+ x: handle.x - mouseEventData.currentPoints.image.x,
+ y: handle.y - mouseEventData.currentPoints.image.y
+ };
function mouseDragCallback(e, eventData) {
handle.active = true;
- handle.x = eventData.currentPoints.image.x;
- handle.y = eventData.currentPoints.image.y;
+ handle.x = eventData.currentPoints.image.x + distanceFromTool.x;
+ handle.y = eventData.currentPoints.image.y + distanceFromTool.y;
+
if (preventHandleOutsideImage) {
handle.x = Math.max(handle.x, 0);
handle.x = Math.min(handle.x, eventData.image.width);
@@ -6198,7 +6393,9 @@ if (typeof cornerstoneTools === 'undefined') {
$(element).off('CornerstoneToolsMouseClick', mouseUpCallback);
cornerstone.updateImage(element);
- doneMovingCallback();
+ if (typeof doneMovingCallback === 'function') {
+ doneMovingCallback();
+ }
}
$(element).on('CornerstoneToolsMouseUp', mouseUpCallback);
@@ -6217,7 +6414,7 @@ if (typeof cornerstoneTools === 'undefined') {
'use strict';
- function moveNewHandle(mouseEventData, handle, doneMoveCallback, preventHandleOutsideImage) {
+ function moveNewHandle(mouseEventData, handle, doneMovingCallback, preventHandleOutsideImage) {
var element = mouseEventData.element;
function moveCallback(e, eventData) {
@@ -6261,7 +6458,9 @@ if (typeof cornerstoneTools === 'undefined') {
handle.active = false;
cornerstone.updateImage(element);
- doneMoveCallback();
+ if (typeof doneMovingCallback === 'function') {
+ doneMovingCallback();
+ }
}
}
@@ -6277,11 +6476,12 @@ if (typeof cornerstoneTools === 'undefined') {
'use strict';
- function moveNewHandleTouch(eventData, handle, doneMoveCallback, preventHandleOutsideImage) {
+ function moveNewHandleTouch(eventData, handle, doneMovingCallback, preventHandleOutsideImage) {
var element = eventData.element;
+ var imageCoords = cornerstone.pageToPixel(element, eventData.currentPoints.page.x, eventData.currentPoints.page.y + 50);
var distanceFromTouch = {
- x: handle.x - eventData.currentPoints.image.x,
- y: handle.y - eventData.currentPoints.image.y
+ x: handle.x - imageCoords.x,
+ y: handle.y - imageCoords.y
};
function moveCallback(e, eventData) {
@@ -6302,10 +6502,19 @@ if (typeof cornerstoneTools === 'undefined') {
function moveEndCallback(e, eventData) {
$(element).off('CornerstoneToolsTouchDrag', moveCallback);
+ $(element).off('CornerstoneToolsTouchPinch', moveEndCallback);
+ $(element).off('CornerstoneToolsTouchPress', moveEndCallback);
$(element).off('CornerstoneToolsTouchEnd', moveEndCallback);
$(element).off('CornerstoneToolsDragEnd', moveEndCallback);
$(element).off('CornerstoneToolsTap', moveEndCallback);
+ if (e.type === 'CornerstoneToolsTouchPinch' || e.type === 'CornerstoneToolsTouchPress') {
+ handle.active = false;
+ cornerstone.updateImage(element);
+ doneMovingCallback();
+ return;
+ }
+
handle.active = false;
handle.x = eventData.currentPoints.image.x + distanceFromTouch.x;
handle.y = eventData.currentPoints.image.y + distanceFromTouch.y;
@@ -6320,10 +6529,14 @@ if (typeof cornerstoneTools === 'undefined') {
cornerstone.updateImage(element);
- doneMoveCallback();
+ if (typeof doneMovingCallback === 'function') {
+ doneMovingCallback();
+ }
}
$(element).on('CornerstoneToolsTouchDrag', moveCallback);
+ $(element).on('CornerstoneToolsTouchPinch', moveEndCallback);
+ $(element).on('CornerstoneToolsTouchPress', moveEndCallback);
$(element).on('CornerstoneToolsTouchEnd', moveEndCallback);
$(element).on('CornerstoneToolsDragEnd', moveEndCallback);
$(element).on('CornerstoneToolsTap', moveEndCallback);
@@ -6342,13 +6555,19 @@ if (typeof cornerstoneTools === 'undefined') {
'use strict';
function touchMoveAllHandles(touchEventData, data, toolData, deleteIfHandleOutsideImage, doneMovingCallback) {
+ //console.log('touchMoveAllHandles');
var element = touchEventData.element;
function touchDragCallback(e, eventData) {
+ //console.log('touchMoveAllHandles touchDragCallback');
data.active = true;
Object.keys(data.handles).forEach(function(name) {
var handle = data.handles[name];
+ if (handle.movesIndependently === true) {
+ return;
+ }
+
handle.x += eventData.deltaPoints.image.x;
handle.y += eventData.deltaPoints.image.y;
});
@@ -6359,10 +6578,14 @@ if (typeof cornerstoneTools === 'undefined') {
$(element).on('CornerstoneToolsTouchDrag', touchDragCallback);
function touchEndCallback(e, eventData) {
+ //console.log('touchMoveAllHandles touchEndCallback');
data.active = false;
data.invalidated = false;
$(element).off('CornerstoneToolsTouchDrag', touchDragCallback);
+ $(element).off('CornerstoneToolsTouchPinch', touchEndCallback);
+ $(element).off('CornerstoneToolsTouchPress', touchEndCallback);
+ $(element).off('CornerstoneToolsTouchEnd', touchEndCallback);
$(element).off('CornerstoneToolsDragEnd', touchEndCallback);
$(element).off('CornerstoneToolsTap', touchEndCallback);
@@ -6371,7 +6594,10 @@ if (typeof cornerstoneTools === 'undefined') {
var image = eventData.image;
var handleOutsideImage = false;
var rect = {
- top: 0, left: 0, width: image.width, height: image.height
+ top: 0,
+ left: 0,
+ width: image.width,
+ height: image.height
};
Object.keys(data.handles).forEach(function(name) {
@@ -6397,10 +6623,16 @@ if (typeof cornerstoneTools === 'undefined') {
}
}
- doneMovingCallback();
+ if (typeof doneMovingCallback === 'function') {
+ doneMovingCallback();
+ }
+
cornerstone.updateImage(element);
}
+ $(element).on('CornerstoneToolsTouchPinch', touchEndCallback);
+ $(element).on('CornerstoneToolsTouchPress', touchEndCallback);
+ $(element).on('CornerstoneToolsTouchEnd', touchEndCallback);
$(element).on('CornerstoneToolsDragEnd', touchEndCallback);
$(element).on('CornerstoneToolsTap', touchEndCallback);
return true;
@@ -6419,6 +6651,7 @@ if (typeof cornerstoneTools === 'undefined') {
'use strict';
function touchMoveHandle(touchEventData, handle, doneMovingCallback) {
+ //console.log('touchMoveHandle');
var element = touchEventData.element;
var distanceFromTouch = {
x: handle.x - touchEventData.currentPoints.image.x,
@@ -6438,14 +6671,20 @@ if (typeof cornerstoneTools === 'undefined') {
function touchEndCallback() {
handle.active = false;
$(element).off('CornerstoneToolsTouchDrag', touchDragCallback);
+ $(element).off('CornerstoneToolsTouchPinch', touchEndCallback);
+ $(element).off('CornerstoneToolsTouchPress', touchEndCallback);
$(element).off('CornerstoneToolsTouchEnd', touchEndCallback);
$(element).off('CornerstoneToolsDragEnd', touchEndCallback);
$(element).off('CornerstoneToolsTap', touchEndCallback);
cornerstone.updateImage(element);
- doneMovingCallback();
+ if (typeof doneMovingCallback === 'function') {
+ doneMovingCallback();
+ }
}
+ $(element).on('CornerstoneToolsTouchPinch', touchEndCallback);
+ $(element).on('CornerstoneToolsTouchPress', touchEndCallback);
$(element).on('CornerstoneToolsTouchEnd', touchEndCallback);
$(element).on('CornerstoneToolsDragEnd', touchEndCallback);
$(element).on('CornerstoneToolsTap', touchEndCallback);
@@ -6500,7 +6739,8 @@ if (typeof cornerstoneTools === 'undefined') {
var index = that.measurements.push(measurement);
// fire event
var eventDetail = {
- index: index, measurement: measurement
+ index: index,
+ measurement: measurement
};
$(that).trigger('CornerstoneMeasurementAdded', eventDetail);
};
@@ -6510,7 +6750,8 @@ if (typeof cornerstoneTools === 'undefined') {
that.measurements.splice(index, 1);
// fire event
var eventDetail = {
- index: index, measurement: measurement
+ index: index,
+ measurement: measurement
};
$(that).trigger('CornerstoneMeasurementRemoved', eventDetail);
};
@@ -6705,7 +6946,8 @@ if (typeof cornerstoneTools === 'undefined') {
renderer = renderer || cornerstoneTools.referenceLines.renderActiveReferenceLine;
cornerstoneTools.addToolState(element, toolType, {
- synchronizationContext: synchronizationContext, renderer: renderer
+ synchronizationContext: synchronizationContext,
+ renderer: renderer
});
$(element).on('CornerstoneImageRendered', onImageRendered);
cornerstone.updateImage(element);
@@ -6719,7 +6961,8 @@ if (typeof cornerstoneTools === 'undefined') {
// module/private exports
cornerstoneTools.referenceLines.tool = {
- enable: enable, disable: disable
+ enable: enable,
+ disable: disable
};
@@ -6745,6 +6988,11 @@ if (typeof cornerstoneTools === 'undefined') {
var targetImagePlane = cornerstoneTools.metaData.get('imagePlane', targetImage.imageId);
var referenceImagePlane = cornerstoneTools.metaData.get('imagePlane', referenceImage.imageId);
+ // Make sure the target and reference actually have image plane metadata
+ if (!targetImagePlane || !referenceImagePlane) {
+ return;
+ }
+
// the image planes must be in the same frame of reference
if (targetImagePlane.frameOfReferenceUID !== referenceImagePlane.frameOfReferenceUID) {
return;
@@ -7030,7 +7278,11 @@ if (typeof cornerstoneTools === 'undefined') {
if (playClipToolData === undefined || playClipToolData.data.length === 0) {
playClipData = {
- intervalId: undefined, framesPerSecond: framesPerSecond, lastFrameTimeStamp: undefined, frameRate: 0, loop: true
+ intervalId: undefined,
+ framesPerSecond: framesPerSecond,
+ lastFrameTimeStamp: undefined,
+ frameRate: 0,
+ loop: true
};
cornerstoneTools.addToolState(element, toolType, playClipData);
} else {
@@ -7451,6 +7703,7 @@ Display scroll progress bar across bottom of image.
}
function disable(element) {
+ clearTimeout(resetPrefetchTimeout);
$(element).off('CornerstoneNewImage', onImageUpdated);
$(cornerstone).off('CornerstoneImageCacheFull', handleCacheFull);
@@ -7565,7 +7818,8 @@ Display scroll progress bar across bottom of image.
'use strict';
var keys = {
- UP: 38, DOWN: 40
+ UP: 38,
+ DOWN: 40
};
function keyDownCallback(e, eventData) {
@@ -8128,7 +8382,8 @@ Display scroll progress bar across bottom of image.
}
var imageIdToolStateManager = {
- get: getStackSpecificToolState, add: addStackSpecificToolState
+ get: getStackSpecificToolState,
+ add: addStackSpecificToolState
};
return imageIdToolStateManager;
}
@@ -8225,7 +8480,8 @@ Display scroll progress bar across bottom of image.
}
var toolCoords = {
- setCoords: setActiveToolCoords, getCoords: getActiveToolCoords
+ setCoords: setActiveToolCoords,
+ getCoords: getActiveToolCoords
};
return toolCoords;
@@ -8364,7 +8620,7 @@ Display scroll progress bar across bottom of image.
function touchSettingsManager() {
var defaultDistanceFromTouch = {
x: 0,
- y: -45
+ y: -55
};
function setToolDistanceFromTouch(distance){
@@ -8734,8 +8990,16 @@ Display scroll progress bar across bottom of image.
sourceElements.forEach(function(sourceElement) {
var sourceEnabledElement = cornerstone.getEnabledElement(sourceElement);
+ if (!sourceEnabledElement || !sourceEnabledElement.image) {
+ return;
+ }
+
var sourceImageId = sourceEnabledElement.image.imageId;
var sourceImagePlane = cornerstoneTools.metaData.get('imagePlane', sourceImageId);
+ if (!sourceImagePlane || !sourceImagePlane.imagePositionPatient) {
+ return;
+ }
+
var sourceImagePosition = sourceImagePlane.imagePositionPatient;
if (initialData.hasOwnProperty(sourceEnabledElement)) {
@@ -8748,6 +9012,10 @@ Display scroll progress bar across bottom of image.
targetElements.forEach(function(targetElement) {
var targetEnabledElement = cornerstone.getEnabledElement(targetElement);
+ if (!targetEnabledElement || !targetEnabledElement.image) {
+ return;
+ }
+
var targetImageId = targetEnabledElement.image.imageId;
initialData.imageIds.targetElements.push(targetImageId);
@@ -8765,6 +9033,10 @@ Display scroll progress bar across bottom of image.
}
var targetImagePlane = cornerstoneTools.metaData.get('imagePlane', targetImageId);
+ if (!targetImagePlane || !targetImagePlane.imagePositionPatient) {
+ return;
+ }
+
var targetImagePosition = targetImagePlane.imagePositionPatient;
initialData.distances[sourceImageId][targetImageId] = targetImagePosition.clone().sub(sourceImagePosition);
@@ -9050,7 +9322,11 @@ Display scroll progress bar across bottom of image.
// create the measurement data for this tool with the end handle activated
var measurementData = {
- timeSeries: timeSeries, lineSample: new cornerstoneTools.LineSampleMeasurement(), imageIdIndex: timeSeries.stacks[timeSeries.currentStackIndex].currentImageIdIndex, visible: true, handles: {
+ timeSeries: timeSeries,
+ lineSample: new cornerstoneTools.LineSampleMeasurement(),
+ imageIdIndex: timeSeries.stacks[timeSeries.currentStackIndex].currentImageIdIndex,
+ visible: true,
+ handles: {
end: {
x: mouseEventData.currentPoints.image.x,
y: mouseEventData.currentPoints.image.y,
@@ -9110,7 +9386,9 @@ Display scroll progress bar across bottom of image.
// module exports
cornerstoneTools.probeTool4D = cornerstoneTools.mouseButtonTool({
- createNewMeasurement: createNewMeasurement, onImageRendered: onImageRendered, toolType: toolType
+ createNewMeasurement: createNewMeasurement,
+ onImageRendered: onImageRendered,
+ toolType: toolType
});
})($, cornerstone, cornerstoneTools);
@@ -9216,7 +9494,10 @@ Display scroll progress bar across bottom of image.
var playClipData;
if (playClipToolData === undefined || playClipToolData.data.length === 0) {
playClipData = {
- intervalId: undefined, framesPerSecond: framesPerSecond, lastFrameTimeStamp: undefined, frameRate: 0
+ intervalId: undefined,
+ framesPerSecond: framesPerSecond,
+ lastFrameTimeStamp: undefined,
+ frameRate: 0
};
cornerstoneTools.addToolState(element, toolType, playClipData);
} else {
@@ -9257,7 +9538,8 @@ Display scroll progress bar across bottom of image.
// module/private exports
cornerstoneTools.timeSeriesPlayer = {
- start: playClip, stop: stopClip
+ start: playClip,
+ stop: stopClip
};
})($, cornerstone, cornerstoneTools);
@@ -9431,7 +9713,10 @@ Display scroll progress bar across bottom of image.
var client = cornerstoneMath.point.copy(points.client);
var canvas = cornerstoneMath.point.copy(points.canvas);
return {
- page: page, image: image, client: client, canvas: canvas
+ page: page,
+ image: image,
+ client: client,
+ canvas: canvas
};
}
@@ -9479,28 +9764,54 @@ Display scroll progress bar across bottom of image.
'use strict';
- function drawTextBox(context, text, x, y, color) {
+ function drawTextBox(context, textLines, x, y, color) {
+ if (Object.prototype.toString.call(textLines) !== '[object Array]') {
+ textLines = [ textLines ];
+ }
+
var padding = 5,
font = cornerstoneTools.textStyle.getFont(),
fontSize = cornerstoneTools.textStyle.getFontSize(),
backgroundColor = cornerstoneTools.textStyle.getBackgroundColor();
context.save();
-
- // Get the text width in the current font
context.font = font;
- var width = context.measureText(text).width;
+ context.textBaseline = 'top';
+ context.strokeStyle = color;
+
+ // Find the longest text width in the array of text data
+ var maxWidth = 0;
+ textLines.forEach(function(text) {
+ // Get the text width in the current font
+ var width = context.measureText(text).width;
+
+ // Find the maximum with for all the text rows;
+ maxWidth = Math.max(maxWidth, width);
+ });
// Draw the background box with padding
- context.textBaseline = 'top';
context.fillStyle = backgroundColor;
- context.fillRect(x, y - fontSize, width + (padding * 2), fontSize + (padding * 2));
-
- // Draw the text
- context.fillStyle = color;
- context.fillText(text, x + padding, y - fontSize + padding);
-
+
+ // Calculate the bounding box for this text box
+ var boundingBox = {
+ left: x,
+ top: y,
+ width: maxWidth + (padding * 2),
+ height: textLines.length * (fontSize + (padding * 2))
+ };
+ context.fillRect(boundingBox.left, boundingBox.top, boundingBox.width, boundingBox.height);
+
+ // Draw each of the text lines on top of the background box
+ textLines.forEach(function(text, index) {
+ context.fillStyle = color;
+ context.fillText(text, x + padding, y + fontSize * index + padding);
+
+ });
+
context.restore();
+
+ // Return the bounding box so it can be used for pointNearHandle
+ return boundingBox;
}
// module exports
@@ -9757,7 +10068,8 @@ Display scroll progress bar across bottom of image.
var x = imagePlane.rowCosines.dot(point) / imagePlane.columnPixelSpacing;
var y = imagePlane.columnCosines.dot(point) / imagePlane.rowPixelSpacing;
return {
- x: x, y: y
+ x: x,
+ y: y
};
}
@@ -10008,7 +10320,9 @@ Display scroll progress bar across bottom of image.
// TODO: actually calculate this?
var lineHeight = fontSize / ee.viewport.scale / fontScale;
return {
- fontSize: scaledFontSize, lineHeight: lineHeight, fontScale: fontScale
+ fontSize: scaledFontSize,
+ lineHeight: lineHeight,
+ fontScale: fontScale
};
}
diff --git a/Packages/cornerstone/client/dicomParser.js b/Packages/cornerstone/client/dicomParser.js
index bf6230245..67f934eb0 100644
--- a/Packages/cornerstone/client/dicomParser.js
+++ b/Packages/cornerstone/client/dicomParser.js
@@ -1,183 +1,160 @@
-/*! dicom-parser - v1.1.5 - 2015-08-17 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
+/*! dicom-parser - v1.2.0 - 2015-11-02 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */
(function (root, factory) {
- // node.js
- if (typeof module !== 'undefined' && module.exports) {
- module.exports = factory();
- }
- else if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define([], factory);
- } else {
- // Browser globals
- if(typeof cornerstone === 'undefined'){
- dicomParser = {};
-
- // meteor
- if (typeof Package !== 'undefined') {
- root.dicomParser = dicomParser;
- }
+ // node.js
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = factory();
+ }
+ else if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define([], factory);
+ } else {
+ // Browser globals
+ if(typeof cornerstone === 'undefined'){
+ dicomParser = {};
+
+ // meteor
+ if (typeof Package !== 'undefined') {
+ root.dicomParser = dicomParser;
+ }
+ }
+ dicomParser = factory();
}
- dicomParser = factory();
- }
}(this, function () {
- /**
- * 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
- * tag is encoutered. This can be used to parse partial byte streams.
- *
- * @param byteArray the byte array
- * @param options object to control parsing behavior (optional)
- * @returns {DataSet}
- * @throws error if an error occurs while parsing. The exception object will contain a property dataSet with the
- * elements successfully parsed before the error.
- */
- var dicomParser = (function(dicomParser) {
+ /**
+ * 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
+ * tag is encoutered. This can be used to parse partial byte streams.
+ *
+ * @param byteArray the byte array
+ * @param options object to control parsing behavior (optional)
+ * @returns {DataSet}
+ * @throws error if an error occurs while parsing. The exception object will contain a property dataSet with the
+ * elements successfully parsed before the error.
+ */
+var dicomParser = (function(dicomParser) {
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
dicomParser.parseDicom = function(byteArray, options) {
- if(byteArray === undefined)
- {
- throw "dicomParser.parseDicom: missing required parameter 'byteArray'";
- }
-
- var littleEndianByteStream = new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray);
-
- function readPrefix()
- {
- littleEndianByteStream.seek(128);
- var prefix = littleEndianByteStream.readFixedString(4);
- if(prefix !== "DICM")
+ if(byteArray === undefined)
{
- throw "dicomParser.parseDicom: DICM prefix not found at location 132";
+ throw "dicomParser.parseDicom: missing required parameter 'byteArray'";
}
- }
- function readPart10Header()
- {
- // Per the DICOM standard, the header is always encoded in Explicit VR Little Endian (see PS3.10, section 7.1)
- // so use littleEndianByteStream throughout this method regardless of the transfer syntax
- readPrefix();
-
- var warnings = [];
- var elements = {};
- while(littleEndianByteStream.position < littleEndianByteStream.byteArray.length) {
- var position = littleEndianByteStream.position;
- var element = dicomParser.readDicomElementExplicit(littleEndianByteStream, warnings);
- if(element.tag > 'x0002ffff') {
- littleEndianByteStream.position = position;
- break;
- }
- // Cache the littleEndianByteArrayParser for meta header elements, since the rest of the data set may be big endian
- // and this parser will be needed later if the meta header values are to be read.
- element.parser = dicomParser.littleEndianByteArrayParser;
- elements[element.tag] = element;
+ function readTransferSyntax(metaHeaderDataSet) {
+ if(metaHeaderDataSet.elements.x00020010 === undefined) {
+ throw 'dicomParser.parseDicom: missing required meta header attribute 0002,0010';
+ }
+ var transferSyntaxElement = metaHeaderDataSet.elements.x00020010;
+ return dicomParser.readFixedString(byteArray, transferSyntaxElement.dataOffset, transferSyntaxElement.length);
}
- var metaHeaderDataSet = new dicomParser.DataSet(littleEndianByteStream.byteArrayParser, littleEndianByteStream.byteArray, elements);
- metaHeaderDataSet.warnings = littleEndianByteStream.warnings;
- return metaHeaderDataSet;
- }
- function readTransferSyntax(metaHeaderDataSet) {
- if(metaHeaderDataSet.elements.x00020010 === undefined) {
- throw 'dicomParser.parseDicom: missing required meta header attribute 0002,0010';
+ function isExplicit(transferSyntax) {
+ if(transferSyntax === '1.2.840.10008.1.2') // implicit little endian
+ {
+ return false;
+ }
+ // all other transfer syntaxes should be explicit
+ return true;
}
- var transferSyntaxElement = metaHeaderDataSet.elements.x00020010;
- return dicomParser.readFixedString(littleEndianByteStream.byteArray, transferSyntaxElement.dataOffset, transferSyntaxElement.length);
- }
- function isExplicit(transferSyntax) {
- if(transferSyntax === '1.2.840.10008.1.2') // implicit little endian
+ function getDataSetByteStream(transferSyntax, position) {
+ if(transferSyntax === '1.2.840.10008.1.2.1.99')
+ {
+ if(typeof(pako) === "undefined") {
+ throw 'dicomParser.parseDicom: deflated transfer syntax encountered but pako not loaded';
+ }
+ 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 = new Uint8Array(inflated.length + position);
+ fullByteArray.set(byteArray.slice(0, position), 0);
+ fullByteArray.set(inflated, position);
+ return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArray, 0);
+ }
+ if(transferSyntax === '1.2.840.10008.1.2.2') // explicit big endian
+ {
+ return new dicomParser.ByteStream(dicomParser.bigEndianByteArrayParser, byteArray, position);
+ }
+ else
+ {
+ // all other transfer syntaxes are little endian; only the pixel encoding differs
+ // make a new stream so the metaheader warnings don't come along for the ride
+ return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray, position);
+ }
+ }
+
+ function mergeDataSets(metaHeaderDataSet, instanceDataSet)
{
- return false;
+ for (var propertyName in metaHeaderDataSet.elements)
+ {
+ if(metaHeaderDataSet.elements.hasOwnProperty(propertyName))
+ {
+ instanceDataSet.elements[propertyName] = metaHeaderDataSet.elements[propertyName];
+ }
+ }
+ if (metaHeaderDataSet.warnings !== undefined) {
+ instanceDataSet.warnings = metaHeaderDataSet.warnings.concat(instanceDataSet.warnings);
+ }
+ return instanceDataSet;
}
- // all other transfer syntaxes should be explicit
- return true;
- }
- function getDataSetByteStream(transferSyntax) {
- if(transferSyntax === '1.2.840.10008.1.2.2') // explicit big endian
+ function readDataSet(metaHeaderDataSet)
{
- return new dicomParser.ByteStream(dicomParser.bigEndianByteArrayParser, byteArray, littleEndianByteStream.position);
+ var transferSyntax = readTransferSyntax(metaHeaderDataSet);
+ var explicit = isExplicit(transferSyntax);
+ var dataSetByteStream = getDataSetByteStream(transferSyntax, metaHeaderDataSet.position);
+
+ var elements = {};
+ var dataSet = new dicomParser.DataSet(dataSetByteStream.byteArrayParser, dataSetByteStream.byteArray, elements);
+ dataSet.warnings = dataSetByteStream.warnings;
+
+ try{
+ if(explicit) {
+ dicomParser.parseDicomDataSetExplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options);
+ }
+ else
+ {
+ dicomParser.parseDicomDataSetImplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options);
+ }
+ }
+ catch(e) {
+ var ex = {
+ exception: e,
+ dataSet: dataSet
+ };
+ throw ex;
+ }
+ return dataSet;
}
- else
- {
- // all other transfer syntaxes are little endian; only the pixel encoding differs
- // make a new stream so the metaheader warnings don't come along for the ride
- return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray, littleEndianByteStream.position);
+
+ // main function here
+ function parseTheByteStream() {
+ var metaHeaderDataSet = dicomParser.readPart10Header(byteArray, options);
+
+ var dataSet = readDataSet(metaHeaderDataSet);
+
+ return mergeDataSets(metaHeaderDataSet, dataSet);
}
- }
- function mergeDataSets(metaHeaderDataSet, instanceDataSet)
- {
- for (var propertyName in metaHeaderDataSet.elements)
- {
- if(metaHeaderDataSet.elements.hasOwnProperty(propertyName))
- {
- instanceDataSet.elements[propertyName] = metaHeaderDataSet.elements[propertyName];
- }
- }
- if (metaHeaderDataSet.warnings !== undefined) {
- instanceDataSet.warnings = metaHeaderDataSet.warnings.concat(instanceDataSet.warnings);
- }
- return instanceDataSet;
- }
-
- function readDataSet(metaHeaderDataSet)
- {
- var transferSyntax = readTransferSyntax(metaHeaderDataSet);
- var explicit = isExplicit(transferSyntax);
- var dataSetByteStream = getDataSetByteStream(transferSyntax);
-
- var elements = {};
- var dataSet = new dicomParser.DataSet(dataSetByteStream.byteArrayParser, dataSetByteStream.byteArray, elements);
- dataSet.warnings = dataSetByteStream.warnings;
-
- try{
- if(explicit) {
- dicomParser.parseDicomDataSetExplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options);
- }
- else
- {
- dicomParser.parseDicomDataSetImplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options);
- }
- }
- catch(e) {
- var ex = {
- exception: e,
- dataSet: dataSet
- };
- throw ex;
- }
- return dataSet;
- }
-
- // main function here
- function parseTheByteStream() {
- var metaHeaderDataSet = readPart10Header();
-
- var dataSet = readDataSet(metaHeaderDataSet);
-
- return mergeDataSets(metaHeaderDataSet, dataSet);
- }
-
- // This is where we actually start parsing
- return parseTheByteStream();
+ // This is where we actually start parsing
+ return parseTheByteStream();
};
return dicomParser;
- })(dicomParser);
+})(dicomParser);
- var dicomParser = (function (dicomParser) {
+var dicomParser = (function (dicomParser) {
"use strict";
if (dicomParser === undefined) {
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -187,64 +164,64 @@
*/
dicomParser.explicitDataSetToJS = function (dataSet, options) {
- if(dataSet === undefined) {
- throw 'dicomParser.explicitDataSetToJS: missing required parameter dataSet';
- }
-
- options = options || {
- omitPrivateAttibutes: true, // true if private elements should be omitted
- maxElementLength : 128 // maximum element length to try and convert to string format
- };
-
- var result = {
-
- };
-
- for(var tag in dataSet.elements) {
- var element = dataSet.elements[tag];
-
- // skip this element if it a private element and our options specify that we should
- if(options.omitPrivateAttibutes === true && dicomParser.isPrivateTag(tag))
- {
- continue;
+ if(dataSet === undefined) {
+ throw 'dicomParser.explicitDataSetToJS: missing required parameter dataSet';
}
- if(element.items) {
- // handle sequences
- var sequenceItems = [];
- for(var i=0; i < element.items.length; i++) {
- sequenceItems.push(dicomParser.explicitDataSetToJS(element.items[i].dataSet, options));
- }
- result[tag] = sequenceItems;
- } else {
- var asString;
- asString = undefined;
- if(element.length < options.maxElementLength) {
- asString = dicomParser.explicitElementToString(dataSet, element);
- }
+ options = options || {
+ omitPrivateAttibutes: true, // true if private elements should be omitted
+ maxElementLength : 128 // maximum element length to try and convert to string format
+ };
- if(asString !== undefined) {
- result[tag] = asString;
- } else {
- result[tag] = {
- dataOffset: element.dataOffset,
- length : element.length
- };
- }
+ var result = {
+
+ };
+
+ for(var tag in dataSet.elements) {
+ var element = dataSet.elements[tag];
+
+ // skip this element if it a private element and our options specify that we should
+ if(options.omitPrivateAttibutes === true && dicomParser.isPrivateTag(tag))
+ {
+ continue;
+ }
+
+ if(element.items) {
+ // handle sequences
+ var sequenceItems = [];
+ for(var i=0; i < element.items.length; i++) {
+ sequenceItems.push(dicomParser.explicitDataSetToJS(element.items[i].dataSet, options));
+ }
+ result[tag] = sequenceItems;
+ } else {
+ var asString;
+ asString = undefined;
+ if(element.length < options.maxElementLength) {
+ asString = dicomParser.explicitElementToString(dataSet, element);
+ }
+
+ if(asString !== undefined) {
+ result[tag] = asString;
+ } else {
+ result[tag] = {
+ dataOffset: element.dataOffset,
+ length : element.length
+ };
+ }
+ }
}
- }
- return result;
+ return result;
};
return dicomParser;
- }(dicomParser));
- var dicomParser = (function (dicomParser) {
+}(dicomParser));
+var dicomParser = (function (dicomParser) {
"use strict";
if (dicomParser === undefined) {
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -256,244 +233,244 @@
*/
dicomParser.explicitElementToString = function(dataSet, element)
{
- if(dataSet === undefined || element === undefined) {
- throw 'dicomParser.explicitElementToString: missing required parameters';
- }
- if(element.vr === undefined) {
- throw 'dicomParser.explicitElementToString: cannot convert implicit element to string';
- }
- var vr = element.vr;
- var tag = element.tag;
-
- var textResult;
-
- function multiElementToString(numItems, func) {
- var result = "";
- for(var i=0; i < numItems; i++) {
- if(i !== 0) {
- result += '/';
- }
- result += func.call(dataSet, tag).toString();
+ if(dataSet === undefined || element === undefined) {
+ throw 'dicomParser.explicitElementToString: missing required parameters';
}
- return result;
- }
-
- if(dicomParser.isStringVr(vr) === true)
- {
- textResult = dataSet.string(tag);
- }
- else if (vr == 'AT') {
- var num = dataSet.uint32(tag);
- if(num === undefined) {
- return undefined;
+ if(element.vr === undefined) {
+ throw 'dicomParser.explicitElementToString: cannot convert implicit element to string';
}
- if (num < 0)
+ var vr = element.vr;
+ var tag = element.tag;
+
+ var textResult;
+
+ function multiElementToString(numItems, func) {
+ var result = "";
+ for(var i=0; i < numItems; i++) {
+ if(i !== 0) {
+ result += '/';
+ }
+ result += func.call(dataSet, tag).toString();
+ }
+ return result;
+ }
+
+ if(dicomParser.isStringVr(vr) === true)
{
- num = 0xFFFFFFFF + num + 1;
+ textResult = dataSet.string(tag);
+ }
+ else if (vr == 'AT') {
+ var num = dataSet.uint32(tag);
+ if(num === undefined) {
+ return undefined;
+ }
+ if (num < 0)
+ {
+ num = 0xFFFFFFFF + num + 1;
+ }
+
+ return 'x' + num.toString(16).toUpperCase();
+ }
+ else if (vr == 'US')
+ {
+ textResult = multiElementToString(element.length / 2, dataSet.uint16);
+ }
+ else if(vr === 'SS')
+ {
+ textResult = multiElementToString(element.length / 2, dataSet.int16);
+ }
+ else if (vr == 'UL')
+ {
+ textResult = multiElementToString(element.length / 4, dataSet.uint32);
+ }
+ else if(vr === 'SL')
+ {
+ textResult = multiElementToString(element.length / 4, dataSet.int32);
+ }
+ else if(vr == 'FD')
+ {
+ textResult = multiElementToString(element.length / 8, dataSet.double);
+ }
+ else if(vr == 'FL')
+ {
+ textResult = multiElementToString(element.length / 4, dataSet.float);
}
- return 'x' + num.toString(16).toUpperCase();
- }
- else if (vr == 'US')
- {
- textResult = multiElementToString(element.length / 2, dataSet.uint16);
- }
- else if(vr === 'SS')
- {
- textResult = multiElementToString(element.length / 2, dataSet.int16);
- }
- else if (vr == 'UL')
- {
- textResult = multiElementToString(element.length / 4, dataSet.uint32);
- }
- else if(vr === 'SL')
- {
- textResult = multiElementToString(element.length / 4, dataSet.int32);
- }
- else if(vr == 'FD')
- {
- textResult = multiElementToString(element.length / 8, dataSet.double);
- }
- else if(vr == 'FL')
- {
- textResult = multiElementToString(element.length / 4, dataSet.float);
- }
-
- return textResult;
+ return textResult;
};
return dicomParser;
- }(dicomParser));
- /**
- * Utility functions for dealing with DICOM
- */
+}(dicomParser));
+/**
+ * Utility functions for dealing with DICOM
+ */
- var dicomParser = (function (dicomParser)
+var dicomParser = (function (dicomParser)
+{
+ "use strict";
+
+ if(dicomParser === undefined)
{
- "use strict";
+ dicomParser = {};
+ }
- if(dicomParser === undefined)
- {
- dicomParser = {};
+ // algorithm based on http://stackoverflow.com/questions/1433030/validate-number-of-days-in-a-given-month
+ function daysInMonth(m, y) { // m is 0 indexed: 0-11
+ switch (m) {
+ case 2 :
+ return (y % 4 == 0 && y % 100) || y % 400 == 0 ? 29 : 28;
+ case 9 : case 4 : case 6 : case 11 :
+ return 30;
+ default :
+ return 31
}
+ }
- // algorithm based on http://stackoverflow.com/questions/1433030/validate-number-of-days-in-a-given-month
- function daysInMonth(m, y) { // m is 0 indexed: 0-11
- switch (m) {
- case 2 :
- return (y % 4 == 0 && y % 100) || y % 400 == 0 ? 29 : 28;
- case 9 : case 4 : case 6 : case 11 :
- return 30;
- default :
- return 31
- }
- }
-
- function isValidDate(d, m, y) {
- // make year is a number
- if(isNaN(y)) {
- return false;
- }
- return m > 0 && m <= 12 && d > 0 && d <= daysInMonth(m, y);
+ function isValidDate(d, m, y) {
+ // make year is a number
+ if(isNaN(y)) {
+ return false;
}
+ return m > 0 && m <= 12 && d > 0 && d <= daysInMonth(m, y);
+ }
- /**
- * Parses a DA formatted string into a Javascript object
- * @param {string} date a string in the DA VR format
- * @param {boolean} [validate] - true if an exception should be thrown if the date is invalid
- * @returns {*} Javascript object with properties year, month and day or undefined if not present or not 8 bytes long
- */
- dicomParser.parseDA = function(date, validate)
- {
- if(date && date.length === 8)
- {
- var yyyy = parseInt(date.substring(0, 4), 10);
- var mm = parseInt(date.substring(4, 6), 10);
- var dd = parseInt(date.substring(6, 8), 10);
-
- if(validate) {
- if (isValidDate(dd, mm, yyyy) !== true) {
- throw "invalid DA '" + date + "'";
- }
- }
- return {
- year: yyyy,
- month: mm,
- day: dd
- };
- }
- if(validate) {
- throw "invalid DA '" + date + "'";
- }
- return undefined;
- };
-
- return dicomParser;
- }(dicomParser));
/**
- * Utility functions for dealing with DICOM
+ * Parses a DA formatted string into a Javascript object
+ * @param {string} date a string in the DA VR format
+ * @param {boolean} [validate] - true if an exception should be thrown if the date is invalid
+ * @returns {*} Javascript object with properties year, month and day or undefined if not present or not 8 bytes long
*/
-
- var dicomParser = (function (dicomParser)
+ dicomParser.parseDA = function(date, validate)
{
- "use strict";
-
- if(dicomParser === undefined)
+ if(date && date.length === 8)
{
- dicomParser = {};
- }
-
- /**
- * Parses a TM formatted string into a javascript object with properties for hours, minutes, seconds and fractionalSeconds
- * @param {string} time - a string in the TM VR format
- * @param {boolean} [validate] - true if an exception should be thrown if the date is invalid
- * @returns {*} javascript object with properties for hours, minutes, seconds and fractionalSeconds or undefined if no element or data. Missing fields are set to undefined
- */
- dicomParser.parseTM = function(time, validate) {
-
- if (time.length >= 2) // must at least have HH
- {
- // 0123456789
- // HHMMSS.FFFFFF
- var hh = parseInt(time.substring(0, 2), 10);
- var mm = time.length >= 4 ? parseInt(time.substring(2, 4), 10) : undefined;
- var ss = time.length >= 6 ? parseInt(time.substring(4, 6), 10) : undefined;
- var ffffff = time.length >= 8 ? parseInt(time.substring(7, 13), 10) : undefined;
-
- if(validate) {
- if((isNaN(hh)) ||
- (mm !== undefined && isNaN(mm)) ||
- (ss !== undefined && isNaN(ss)) ||
- (ffffff !== undefined && isNaN(ffffff)) ||
- (hh < 0 || hh > 23) ||
- (mm && (mm <0 || mm > 59)) ||
- (ss && (ss <0 || ss > 59)) ||
- (ffffff && (ffffff <0 || ffffff > 999999)))
- {
- throw "invalid TM '" + time + "'";
- }
- }
-
- return {
- hours: hh,
- minutes: mm,
- seconds: ss,
- fractionalSeconds: ffffff
- };
- }
+ var yyyy = parseInt(date.substring(0, 4), 10);
+ var mm = parseInt(date.substring(4, 6), 10);
+ var dd = parseInt(date.substring(6, 8), 10);
if(validate) {
- throw "invalid TM '" + time + "'";
+ if (isValidDate(dd, mm, yyyy) !== true) {
+ throw "invalid DA '" + date + "'";
+ }
+ }
+ return {
+ year: yyyy,
+ month: mm,
+ day: dd
+ };
+ }
+ if(validate) {
+ throw "invalid DA '" + date + "'";
+ }
+ return undefined;
+ };
+
+ return dicomParser;
+}(dicomParser));
+/**
+ * Utility functions for dealing with DICOM
+ */
+
+var dicomParser = (function (dicomParser)
+{
+ "use strict";
+
+ if(dicomParser === undefined)
+ {
+ dicomParser = {};
+ }
+
+ /**
+ * Parses a TM formatted string into a javascript object with properties for hours, minutes, seconds and fractionalSeconds
+ * @param {string} time - a string in the TM VR format
+ * @param {boolean} [validate] - true if an exception should be thrown if the date is invalid
+ * @returns {*} javascript object with properties for hours, minutes, seconds and fractionalSeconds or undefined if no element or data. Missing fields are set to undefined
+ */
+ dicomParser.parseTM = function(time, validate) {
+
+ if (time.length >= 2) // must at least have HH
+ {
+ // 0123456789
+ // HHMMSS.FFFFFF
+ var hh = parseInt(time.substring(0, 2), 10);
+ var mm = time.length >= 4 ? parseInt(time.substring(2, 4), 10) : undefined;
+ var ss = time.length >= 6 ? parseInt(time.substring(4, 6), 10) : undefined;
+ var ffffff = time.length >= 8 ? parseInt(time.substring(7, 13), 10) : undefined;
+
+ if(validate) {
+ if((isNaN(hh)) ||
+ (mm !== undefined && isNaN(mm)) ||
+ (ss !== undefined && isNaN(ss)) ||
+ (ffffff !== undefined && isNaN(ffffff)) ||
+ (hh < 0 || hh > 23) ||
+ (mm && (mm <0 || mm > 59)) ||
+ (ss && (ss <0 || ss > 59)) ||
+ (ffffff && (ffffff <0 || ffffff > 999999)))
+ {
+ throw "invalid TM '" + time + "'";
+ }
}
- return undefined;
- };
+ return {
+ hours: hh,
+ minutes: mm,
+ seconds: ss,
+ fractionalSeconds: ffffff
+ };
+ }
- return dicomParser;
- }(dicomParser));
- /**
- * Utility functions for dealing with DICOM
- */
+ if(validate) {
+ throw "invalid TM '" + time + "'";
+ }
- var dicomParser = (function (dicomParser)
- {
+ return undefined;
+ };
+
+ return dicomParser;
+}(dicomParser));
+/**
+ * Utility functions for dealing with DICOM
+ */
+
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
var stringVrs = {
- AE: true,
- AS: true,
- AT: false,
- CS: true,
- DA: true,
- DS: true,
- DT: true,
- FL: false,
- FD: false,
- IS: true,
- LO: true,
- LT: true,
- OB: false,
- OD: false,
- OF: false,
- OW: false,
- PN: true,
- SH: true,
- SL: false,
- SQ: false,
- SS: false,
- ST: true,
- TM: true,
- UI: true,
- UL: false,
- UN: undefined, // dunno
- UR: true,
- US: false,
- UT: true
+ AE: true,
+ AS: true,
+ AT: false,
+ CS: true,
+ DA: true,
+ DS: true,
+ DT: true,
+ FL: false,
+ FD: false,
+ IS: true,
+ LO: true,
+ LT: true,
+ OB: false,
+ OD: false,
+ OF: false,
+ OW: false,
+ PN: true,
+ SH: true,
+ SL: false,
+ SQ: false,
+ SS: false,
+ ST: true,
+ TM: true,
+ UI: true,
+ UL: false,
+ UN: undefined, // dunno
+ UR: true,
+ US: false,
+ UT: true
};
/**
@@ -503,7 +480,7 @@
*/
dicomParser.isStringVr = function(vr)
{
- return stringVrs[vr];
+ return stringVrs[vr];
};
/**
@@ -513,9 +490,9 @@
*/
dicomParser.isPrivateTag = function(tag)
{
- var lastGroupDigit = parseInt(tag[4]);
- var groupIsOdd = (lastGroupDigit % 2) === 1;
- return groupIsOdd;
+ var lastGroupDigit = parseInt(tag[4]);
+ var groupIsOdd = (lastGroupDigit % 2) === 1;
+ return groupIsOdd;
};
/**
@@ -525,109 +502,112 @@
* @returns {*} javascript object with properties for givenName, familyName, middleName, prefix and suffix or undefined if no element or data
*/
dicomParser.parsePN = function(personName) {
- if(personName === undefined) {
- return undefined;
- }
- var stringValues = personName.split('^');
- return {
- familyName: stringValues[0],
- givenName: stringValues[1],
- middleName: stringValues[2],
- prefix: stringValues[3],
- suffix: stringValues[4]
- };
+ if(personName === undefined) {
+ return undefined;
+ }
+ var stringValues = personName.split('^');
+ return {
+ familyName: stringValues[0],
+ givenName: stringValues[1],
+ middleName: stringValues[2],
+ prefix: stringValues[3],
+ suffix: stringValues[4]
+ };
};
return dicomParser;
- }(dicomParser));
- /**
- * Functionality for extracting encapsulated pixel data
- */
+}(dicomParser));
+/**
+ * Functionality for extracting encapsulated pixel data
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
function getPixelDataFromFragments(byteStream, fragments, bufferSize)
{
- // 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++];
+ // 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);
}
- }
- return pixelData;
+ // 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;
+ // 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;
}
- 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;
+ // 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";
- }
+ // 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];
- byteStream.seek(frameOffset);
+ // 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;
- }
+ // 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;
+ // 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';
- }
+ // 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;
+ // read this frame
+ var endOfFrame = byteStream.position + pixelDataElement.length;
+ var pixelData = readFragmentsUntil(byteStream, endOfFrame);
+ return pixelData;
}
/**
@@ -640,243 +620,243 @@
*/
dicomParser.readEncapsulatedPixelData = function(dataSet, pixelDataElement, frame)
{
- if(dataSet === undefined) {
- throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'dataSet'";
- }
- if(pixelDataElement === undefined) {
- throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'element'";
- }
- if(frame === undefined) {
- throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'frame'";
- }
- if(pixelDataElement.tag !== 'x7fe00010') {
- throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to non pixel data tag (expected tag = x7fe00010'";
- }
- if(pixelDataElement.encapsulatedPixelData !== true) {
- throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
- }
- if(pixelDataElement.hadUndefinedLength !== true) {
- throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
- }
- if(pixelDataElement.basicOffsetTable === undefined) {
- throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
- }
- if(pixelDataElement.fragments === undefined) {
- throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
- }
- if(frame < 0) {
- throw "dicomParser.readEncapsulatedPixelData: parameter 'frame' must be >= 0";
- }
+ if(dataSet === undefined) {
+ throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'dataSet'";
+ }
+ if(pixelDataElement === undefined) {
+ throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'element'";
+ }
+ if(frame === undefined) {
+ throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'frame'";
+ }
+ if(pixelDataElement.tag !== 'x7fe00010') {
+ throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to non pixel data tag (expected tag = x7fe00010'";
+ }
+ if(pixelDataElement.encapsulatedPixelData !== true) {
+ throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
+ }
+ if(pixelDataElement.hadUndefinedLength !== true) {
+ throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
+ }
+ if(pixelDataElement.basicOffsetTable === undefined) {
+ throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
+ }
+ if(pixelDataElement.fragments === undefined) {
+ throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data";
+ }
+ if(frame < 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)
- 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);
+ // seek past the basic offset table (no need to parse it again since we already have)
+ 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)
- {
- return readEncapsulatedPixelDataWithBasicOffsetTable(pixelDataElement, byteStream, frame);
- }
- else
- {
- return readEncapsulatedDataNoBasicOffsetTable(pixelDataElement, byteStream, frame);
- }
+ // 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)
+ {
+ return readEncapsulatedPixelDataWithBasicOffsetTable(pixelDataElement, byteStream, frame);
+ }
+ else
+ {
+ return readEncapsulatedDataNoBasicOffsetTable(pixelDataElement, byteStream, frame);
+ }
};
return dicomParser;
- }(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
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
dicomParser.bigEndianByteArrayParser = {
- /**
- *
- * Parses an unsigned int 16 from a big-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed unsigned int 16
- * @throws error if buffer overread would occur
- * @access private
- */
- readUint16: function (byteArray, position) {
- if (position < 0) {
- throw 'bigEndianByteArrayParser.readUint16: position cannot be less than 0';
+ /**
+ *
+ * Parses an unsigned int 16 from a big-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed unsigned int 16
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readUint16: function (byteArray, position) {
+ if (position < 0) {
+ throw 'bigEndianByteArrayParser.readUint16: position cannot be less than 0';
+ }
+ if (position + 2 > byteArray.length) {
+ throw 'bigEndianByteArrayParser.readUint16: attempt to read past end of buffer';
+ }
+ return (byteArray[position] << 8) + byteArray[position + 1];
+ },
+
+ /**
+ *
+ * Parses a signed int 16 from a big-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed signed int 16
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readInt16: function (byteArray, position) {
+ if (position < 0) {
+ throw 'bigEndianByteArrayParser.readInt16: position cannot be less than 0';
+ }
+ if (position + 2 > byteArray.length) {
+ throw 'bigEndianByteArrayParser.readInt16: attempt to read past end of buffer';
+ }
+ var int16 = (byteArray[position] << 8) + byteArray[position + 1];
+ // fix sign
+ if (int16 & 0x8000) {
+ int16 = int16 - 0xFFFF - 1;
+ }
+ return int16;
+ },
+
+ /**
+ * Parses an unsigned int 32 from a big-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed unsigned int 32
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readUint32: function (byteArray, position) {
+ if (position < 0) {
+ throw 'bigEndianByteArrayParser.readUint32: position cannot be less than 0';
+ }
+
+ if (position + 4 > byteArray.length) {
+ throw 'bigEndianByteArrayParser.readUint32: attempt to read past end of buffer';
+ }
+
+ var uint32 = (256 * (256 * (256 * byteArray[position] +
+ byteArray[position + 1]) +
+ byteArray[position + 2]) +
+ byteArray[position + 3]);
+
+ return uint32;
+ },
+
+ /**
+ * Parses a signed int 32 from a big-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed signed int 32
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readInt32: function (byteArray, position) {
+ if (position < 0) {
+ throw 'bigEndianByteArrayParser.readInt32: position cannot be less than 0';
+ }
+
+ if (position + 4 > byteArray.length) {
+ throw 'bigEndianByteArrayParser.readInt32: attempt to read past end of buffer';
+ }
+
+ var int32 = ((byteArray[position] << 24) +
+ (byteArray[position + 1] << 16) +
+ (byteArray[position + 2] << 8) +
+ byteArray[position + 3]);
+
+ return int32;
+ },
+
+ /**
+ * Parses 32-bit float from a big-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed 32-bit float
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readFloat: function (byteArray, position) {
+ if (position < 0) {
+ throw 'bigEndianByteArrayParser.readFloat: position cannot be less than 0';
+ }
+
+ if (position + 4 > byteArray.length) {
+ throw 'bigEndianByteArrayParser.readFloat: attempt to read past end of buffer';
+ }
+
+ // I am sure there is a better way than this but this should be safe
+ var byteArrayForParsingFloat = new Uint8Array(4);
+ byteArrayForParsingFloat[3] = byteArray[position];
+ byteArrayForParsingFloat[2] = byteArray[position + 1];
+ byteArrayForParsingFloat[1] = byteArray[position + 2];
+ byteArrayForParsingFloat[0] = byteArray[position + 3];
+ var floatArray = new Float32Array(byteArrayForParsingFloat.buffer);
+ return floatArray[0];
+ },
+
+ /**
+ * Parses 64-bit float from a big-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed 64-bit float
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readDouble: function (byteArray, position) {
+ if (position < 0) {
+ throw 'bigEndianByteArrayParser.readDouble: position cannot be less than 0';
+ }
+
+ if (position + 8 > byteArray.length) {
+ throw 'bigEndianByteArrayParser.readDouble: attempt to read past end of buffer';
+ }
+
+ // I am sure there is a better way than this but this should be safe
+ var byteArrayForParsingFloat = new Uint8Array(8);
+ byteArrayForParsingFloat[7] = byteArray[position];
+ byteArrayForParsingFloat[6] = byteArray[position + 1];
+ byteArrayForParsingFloat[5] = byteArray[position + 2];
+ byteArrayForParsingFloat[4] = byteArray[position + 3];
+ byteArrayForParsingFloat[3] = byteArray[position + 4];
+ byteArrayForParsingFloat[2] = byteArray[position + 5];
+ byteArrayForParsingFloat[1] = byteArray[position + 6];
+ byteArrayForParsingFloat[0] = byteArray[position + 7];
+ var floatArray = new Float64Array(byteArrayForParsingFloat.buffer);
+ return floatArray[0];
}
- if (position + 2 > byteArray.length) {
- throw 'bigEndianByteArrayParser.readUint16: attempt to read past end of buffer';
- }
- return (byteArray[position] << 8) + byteArray[position + 1];
- },
-
- /**
- *
- * Parses a signed int 16 from a big-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed signed int 16
- * @throws error if buffer overread would occur
- * @access private
- */
- readInt16: function (byteArray, position) {
- if (position < 0) {
- throw 'bigEndianByteArrayParser.readInt16: position cannot be less than 0';
- }
- if (position + 2 > byteArray.length) {
- throw 'bigEndianByteArrayParser.readInt16: attempt to read past end of buffer';
- }
- var int16 = (byteArray[position] << 8) + byteArray[position + 1];
- // fix sign
- if (int16 & 0x8000) {
- int16 = int16 - 0xFFFF - 1;
- }
- return int16;
- },
-
- /**
- * Parses an unsigned int 32 from a big-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed unsigned int 32
- * @throws error if buffer overread would occur
- * @access private
- */
- readUint32: function (byteArray, position) {
- if (position < 0) {
- throw 'bigEndianByteArrayParser.readUint32: position cannot be less than 0';
- }
-
- if (position + 4 > byteArray.length) {
- throw 'bigEndianByteArrayParser.readUint32: attempt to read past end of buffer';
- }
-
- var uint32 = (256 * (256 * (256 * byteArray[position] +
- byteArray[position + 1]) +
- byteArray[position + 2]) +
- byteArray[position + 3]);
-
- return uint32;
- },
-
- /**
- * Parses a signed int 32 from a big-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed signed int 32
- * @throws error if buffer overread would occur
- * @access private
- */
- readInt32: function (byteArray, position) {
- if (position < 0) {
- throw 'bigEndianByteArrayParser.readInt32: position cannot be less than 0';
- }
-
- if (position + 4 > byteArray.length) {
- throw 'bigEndianByteArrayParser.readInt32: attempt to read past end of buffer';
- }
-
- var int32 = ((byteArray[position] << 24) +
- (byteArray[position + 1] << 16) +
- (byteArray[position + 2] << 8) +
- byteArray[position + 3]);
-
- return int32;
- },
-
- /**
- * Parses 32-bit float from a big-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed 32-bit float
- * @throws error if buffer overread would occur
- * @access private
- */
- readFloat: function (byteArray, position) {
- if (position < 0) {
- throw 'bigEndianByteArrayParser.readFloat: position cannot be less than 0';
- }
-
- if (position + 4 > byteArray.length) {
- throw 'bigEndianByteArrayParser.readFloat: attempt to read past end of buffer';
- }
-
- // I am sure there is a better way than this but this should be safe
- var byteArrayForParsingFloat = new Uint8Array(4);
- byteArrayForParsingFloat[3] = byteArray[position];
- byteArrayForParsingFloat[2] = byteArray[position + 1];
- byteArrayForParsingFloat[1] = byteArray[position + 2];
- byteArrayForParsingFloat[0] = byteArray[position + 3];
- var floatArray = new Float32Array(byteArrayForParsingFloat.buffer);
- return floatArray[0];
- },
-
- /**
- * Parses 64-bit float from a big-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed 64-bit float
- * @throws error if buffer overread would occur
- * @access private
- */
- readDouble: function (byteArray, position) {
- if (position < 0) {
- throw 'bigEndianByteArrayParser.readDouble: position cannot be less than 0';
- }
-
- if (position + 8 > byteArray.length) {
- throw 'bigEndianByteArrayParser.readDouble: attempt to read past end of buffer';
- }
-
- // I am sure there is a better way than this but this should be safe
- var byteArrayForParsingFloat = new Uint8Array(8);
- byteArrayForParsingFloat[7] = byteArray[position];
- byteArrayForParsingFloat[6] = byteArray[position + 1];
- byteArrayForParsingFloat[5] = byteArray[position + 2];
- byteArrayForParsingFloat[4] = byteArray[position + 3];
- byteArrayForParsingFloat[3] = byteArray[position + 4];
- byteArrayForParsingFloat[2] = byteArray[position + 5];
- byteArrayForParsingFloat[1] = byteArray[position + 6];
- byteArrayForParsingFloat[0] = byteArray[position + 7];
- var floatArray = new Float64Array(byteArrayForParsingFloat.buffer);
- return floatArray[0];
- }
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions common to parsing byte arrays of any type
- */
+}(dicomParser));
+/**
+ * Internal helper functions common to parsing byte arrays of any type
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -893,48 +873,48 @@
*/
dicomParser.readFixedString = function(byteArray, position, length)
{
- if(length < 0)
- {
- throw 'readFixedString - length cannot be less than 0';
- }
-
- if(position + length > byteArray.length) {
- throw 'dicomParser.readFixedString: attempt to read past end of buffer';
- }
-
- var result = "";
- for(var i=0; i < length; i++)
- {
- var byte = byteArray[position + i];
- if(byte === 0) {
- position += length;
- return result;
+ if(length < 0)
+ {
+ throw 'readFixedString - length cannot be less than 0';
}
- result += String.fromCharCode(byte);
- }
- return result;
+ if(position + length > byteArray.length) {
+ throw 'dicomParser.readFixedString: attempt to read past end of buffer';
+ }
+
+ var result = "";
+ for(var i=0; i < length; i++)
+ {
+ var byte = byteArray[position + i];
+ if(byte === 0) {
+ position += length;
+ return result;
+ }
+ result += String.fromCharCode(byte);
+ }
+
+ return result;
};
return dicomParser;
- }(dicomParser));
- /**
- *
- * Internal helper class to assist with parsing. Supports reading from a byte
- * stream contained in a Uint8Array. Example usage:
- *
- * var byteArray = new Uint8Array(32);
- * var byteStream = new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray);
- *
- * */
- var dicomParser = (function (dicomParser)
- {
+}(dicomParser));
+/**
+ *
+ * Internal helper class to assist with parsing. Supports reading from a byte
+ * stream contained in a Uint8Array. Example usage:
+ *
+ * var byteArray = new Uint8Array(32);
+ * var byteStream = new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray);
+ *
+ * */
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -948,30 +928,30 @@
* @throws will throw an error if the position parameter is not inside the byte array
*/
dicomParser.ByteStream = function(byteArrayParser, byteArray, position) {
- if(byteArrayParser === undefined)
- {
- throw "dicomParser.ByteStream: missing required parameter 'byteArrayParser'";
- }
- if(byteArray === undefined)
- {
- throw "dicomParser.ByteStream: missing required parameter 'byteArray'";
- }
- if((byteArray instanceof Uint8Array) === false) {
- throw 'dicomParser.ByteStream: parameter byteArray is not of type Uint8Array';
- }
- if(position < 0)
- {
- throw "dicomParser.ByteStream: parameter 'position' cannot be less than 0";
- }
- if(position >= byteArray.length)
- {
- throw "dicomParser.ByteStream: parameter 'position' cannot be greater than or equal to 'byteArray' length";
+ if(byteArrayParser === undefined)
+ {
+ throw "dicomParser.ByteStream: missing required parameter 'byteArrayParser'";
+ }
+ if(byteArray === undefined)
+ {
+ throw "dicomParser.ByteStream: missing required parameter 'byteArray'";
+ }
+ if((byteArray instanceof Uint8Array) === false) {
+ throw 'dicomParser.ByteStream: parameter byteArray is not of type Uint8Array';
+ }
+ if(position < 0)
+ {
+ throw "dicomParser.ByteStream: parameter 'position' cannot be less than 0";
+ }
+ if(position >= byteArray.length)
+ {
+ throw "dicomParser.ByteStream: parameter 'position' cannot be greater than or equal to 'byteArray' length";
- }
- this.byteArrayParser = byteArrayParser;
- this.byteArray = byteArray;
- this.position = position ? position : 0;
- this.warnings = []; // array of string warnings encountered while parsing
+ }
+ this.byteArrayParser = byteArrayParser;
+ this.byteArray = byteArray;
+ this.position = position ? position : 0;
+ this.warnings = []; // array of string warnings encountered while parsing
};
/**
@@ -982,11 +962,11 @@
*/
dicomParser.ByteStream.prototype.seek = function(offset)
{
- if(this.position + offset < 0)
- {
- throw "cannot seek to position < 0";
- }
- this.position += offset;
+ if(this.position + offset < 0)
+ {
+ throw "cannot seek to position < 0";
+ }
+ this.position += offset;
};
/**
@@ -997,12 +977,12 @@
*/
dicomParser.ByteStream.prototype.readByteStream = function(numBytes)
{
- if(this.position + numBytes > this.byteArray.length) {
- throw 'readByteStream - buffer overread';
- }
- var byteArrayView = new Uint8Array(this.byteArray.buffer, this.position, numBytes);
- this.position += numBytes;
- return new dicomParser.ByteStream(this.byteArrayParser, byteArrayView);
+ if(this.position + numBytes > this.byteArray.length) {
+ throw 'readByteStream - buffer overread';
+ }
+ var byteArrayView = new Uint8Array(this.byteArray.buffer, this.position, numBytes);
+ this.position += numBytes;
+ return new dicomParser.ByteStream(this.byteArrayParser, byteArrayView);
};
/**
@@ -1015,9 +995,9 @@
*/
dicomParser.ByteStream.prototype.readUint16 = function()
{
- var result = this.byteArrayParser.readUint16(this.byteArray, this.position);
- this.position += 2;
- return result;
+ var result = this.byteArrayParser.readUint16(this.byteArray, this.position);
+ this.position += 2;
+ return result;
};
/**
@@ -1029,9 +1009,9 @@
*/
dicomParser.ByteStream.prototype.readUint32 = function()
{
- var result = this.byteArrayParser.readUint32(this.byteArray, this.position);
- this.position += 4;
- return result;
+ var result = this.byteArrayParser.readUint32(this.byteArray, this.position);
+ this.position += 4;
+ return result;
};
/**
@@ -1044,45 +1024,45 @@
*/
dicomParser.ByteStream.prototype.readFixedString = function(length)
{
- var result = dicomParser.readFixedString(this.byteArray, this.position, length);
- this.position += length;
- return result;
+ var result = dicomParser.readFixedString(this.byteArray, this.position, length);
+ this.position += length;
+ return result;
};
return dicomParser;
- }(dicomParser));
- /**
- *
- * The DataSet class encapsulates a collection of DICOM Elements and provides various functions
- * to access the data in those elements
- *
- * Rules for handling padded spaces:
- * DS = Strip leading and trailing spaces
- * DT = Strip trailing spaces
- * IS = Strip leading and trailing spaces
- * PN = Strip trailing spaces
- * TM = Strip trailing spaces
- * AE = Strip leading and trailing spaces
- * CS = Strip leading and trailing spaces
- * SH = Strip leading and trailing spaces
- * LO = Strip leading and trailing spaces
- * LT = Strip trailing spaces
- * ST = Strip trailing spaces
- * UT = Strip trailing spaces
- *
- */
- var dicomParser = (function (dicomParser)
- {
+}(dicomParser));
+/**
+ *
+ * The DataSet class encapsulates a collection of DICOM Elements and provides various functions
+ * to access the data in those elements
+ *
+ * Rules for handling padded spaces:
+ * DS = Strip leading and trailing spaces
+ * DT = Strip trailing spaces
+ * IS = Strip leading and trailing spaces
+ * PN = Strip trailing spaces
+ * TM = Strip trailing spaces
+ * AE = Strip leading and trailing spaces
+ * CS = Strip leading and trailing spaces
+ * SH = Strip leading and trailing spaces
+ * LO = Strip leading and trailing spaces
+ * LT = Strip trailing spaces
+ * ST = Strip trailing spaces
+ * UT = Strip trailing spaces
+ *
+ */
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
function getByteArrayParser(element, defaultParser)
{
- return (element.parser !== undefined ? element.parser : defaultParser);
+ return (element.parser !== undefined ? element.parser : defaultParser);
}
/**
@@ -1094,9 +1074,9 @@
*/
dicomParser.DataSet = function(byteArrayParser, byteArray, elements)
{
- this.byteArrayParser = byteArrayParser;
- this.byteArray = byteArray;
- this.elements = elements;
+ this.byteArrayParser = byteArrayParser;
+ this.byteArray = byteArray;
+ this.elements = elements;
};
/**
@@ -1107,13 +1087,13 @@
*/
dicomParser.DataSet.prototype.uint16 = function(tag, index)
{
- var element = this.elements[tag];
- index = (index !== undefined) ? index : 0;
- if(element && element.length !== 0)
- {
- return getByteArrayParser(element, this.byteArrayParser).readUint16(this.byteArray, element.dataOffset + (index *2));
- }
- return undefined;
+ var element = this.elements[tag];
+ index = (index !== undefined) ? index : 0;
+ if(element && element.length !== 0)
+ {
+ return getByteArrayParser(element, this.byteArrayParser).readUint16(this.byteArray, element.dataOffset + (index *2));
+ }
+ return undefined;
};
/**
@@ -1124,13 +1104,13 @@
*/
dicomParser.DataSet.prototype.int16 = function(tag, index)
{
- var element = this.elements[tag];
- index = (index !== undefined) ? index : 0;
- if(element && element.length !== 0)
- {
- return getByteArrayParser(element, this.byteArrayParser).readInt16(this.byteArray, element.dataOffset + (index * 2));
- }
- return undefined;
+ var element = this.elements[tag];
+ index = (index !== undefined) ? index : 0;
+ if(element && element.length !== 0)
+ {
+ return getByteArrayParser(element, this.byteArrayParser).readInt16(this.byteArray, element.dataOffset + (index * 2));
+ }
+ return undefined;
};
/**
@@ -1141,13 +1121,13 @@
*/
dicomParser.DataSet.prototype.uint32 = function(tag, index)
{
- var element = this.elements[tag];
- index = (index !== undefined) ? index : 0;
- if(element && element.length !== 0)
- {
- return getByteArrayParser(element, this.byteArrayParser).readUint32(this.byteArray, element.dataOffset + (index * 4));
- }
- return undefined;
+ var element = this.elements[tag];
+ index = (index !== undefined) ? index : 0;
+ if(element && element.length !== 0)
+ {
+ return getByteArrayParser(element, this.byteArrayParser).readUint32(this.byteArray, element.dataOffset + (index * 4));
+ }
+ return undefined;
};
/**
@@ -1158,13 +1138,13 @@
*/
dicomParser.DataSet.prototype.int32 = function(tag, index)
{
- var element = this.elements[tag];
- index = (index !== undefined) ? index : 0;
- if(element && element.length !== 0)
- {
- return getByteArrayParser(element, this.byteArrayParser).readInt32(this.byteArray, element.dataOffset + (index * 4));
- }
- return undefined;
+ var element = this.elements[tag];
+ index = (index !== undefined) ? index : 0;
+ if(element && element.length !== 0)
+ {
+ return getByteArrayParser(element, this.byteArrayParser).readInt32(this.byteArray, element.dataOffset + (index * 4));
+ }
+ return undefined;
};
/**
@@ -1175,13 +1155,13 @@
*/
dicomParser.DataSet.prototype.float = function(tag, index)
{
- var element = this.elements[tag];
- index = (index !== undefined) ? index : 0;
- if(element && element.length !== 0)
- {
- return getByteArrayParser(element, this.byteArrayParser).readFloat(this.byteArray, element.dataOffset + (index * 4));
- }
- return undefined;
+ var element = this.elements[tag];
+ index = (index !== undefined) ? index : 0;
+ if(element && element.length !== 0)
+ {
+ return getByteArrayParser(element, this.byteArrayParser).readFloat(this.byteArray, element.dataOffset + (index * 4));
+ }
+ return undefined;
};
/**
@@ -1192,13 +1172,13 @@
*/
dicomParser.DataSet.prototype.double = function(tag, index)
{
- var element = this.elements[tag];
- index = (index !== undefined) ? index : 0;
- if(element && element.length !== 0)
- {
- return getByteArrayParser(element, this.byteArrayParser).readDouble(this.byteArray, element.dataOffset + (index * 8));
- }
- return undefined;
+ var element = this.elements[tag];
+ index = (index !== undefined) ? index : 0;
+ if(element && element.length !== 0)
+ {
+ return getByteArrayParser(element, this.byteArrayParser).readDouble(this.byteArray, element.dataOffset + (index * 8));
+ }
+ return undefined;
};
/**
@@ -1208,18 +1188,18 @@
*/
dicomParser.DataSet.prototype.numStringValues = function(tag)
{
- var element = this.elements[tag];
- if(element && element.length > 0)
- {
- var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length);
- var numMatching = fixedString.match(/\\/g);
- if(numMatching === null)
+ var element = this.elements[tag];
+ if(element && element.length > 0)
{
- return 1;
+ var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length);
+ var numMatching = fixedString.match(/\\/g);
+ if(numMatching === null)
+ {
+ return 1;
+ }
+ return numMatching.length + 1;
}
- return numMatching.length + 1;
- }
- return undefined;
+ return undefined;
};
/**
@@ -1235,23 +1215,23 @@
*/
dicomParser.DataSet.prototype.string = function(tag, index)
{
- var element = this.elements[tag];
- if(element && element.length > 0)
- {
- var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length);
- if(index >= 0)
+ var element = this.elements[tag];
+ if(element && element.length > 0)
{
- var values = fixedString.split('\\');
- // trim trailing spaces
- return values[index].trim();
+ var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length);
+ if(index >= 0)
+ {
+ var values = fixedString.split('\\');
+ // trim trailing spaces
+ return values[index].trim();
+ }
+ else
+ {
+ // trim trailing spaces
+ return fixedString.trim();
+ }
}
- else
- {
- // trim trailing spaces
- return fixedString.trim();
- }
- }
- return undefined;
+ return undefined;
};
/**
@@ -1265,21 +1245,21 @@
*/
dicomParser.DataSet.prototype.text = function(tag, index)
{
- var element = this.elements[tag];
- if(element && element.length > 0)
- {
- var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length);
- if(index >= 0)
+ var element = this.elements[tag];
+ if(element && element.length > 0)
{
- var values = fixedString.split('\\');
- return values[index].replace(/ +$/, '');
+ var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length);
+ if(index >= 0)
+ {
+ var values = fixedString.split('\\');
+ return values[index].replace(/ +$/, '');
+ }
+ else
+ {
+ return fixedString.replace(/ +$/, '');
+ }
}
- else
- {
- return fixedString.replace(/ +$/, '');
- }
- }
- return undefined;
+ return undefined;
};
/**
@@ -1291,16 +1271,16 @@
*/
dicomParser.DataSet.prototype.floatString = function(tag, index)
{
- var element = this.elements[tag];
- if(element && element.length > 0)
- {
- index = (index !== undefined) ? index : 0;
- var value = this.string(tag, index);
- if(value !== undefined) {
- return parseFloat(value);
+ var element = this.elements[tag];
+ if(element && element.length > 0)
+ {
+ index = (index !== undefined) ? index : 0;
+ var value = this.string(tag, index);
+ if(value !== undefined) {
+ return parseFloat(value);
+ }
}
- }
- return undefined;
+ return undefined;
};
/**
@@ -1312,32 +1292,32 @@
*/
dicomParser.DataSet.prototype.intString = function(tag, index)
{
- var element = this.elements[tag];
- if(element && element.length > 0) {
- index = (index !== undefined) ? index : 0;
- var value = this.string(tag, index);
- if(value !== undefined) {
- return parseInt(value);
+ var element = this.elements[tag];
+ if(element && element.length > 0) {
+ index = (index !== undefined) ? index : 0;
+ var value = this.string(tag, index);
+ if(value !== undefined) {
+ return parseInt(value);
+ }
}
- }
- return undefined;
+ return undefined;
};
//dicomParser.DataSet = DataSet;
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing DICOM elements
- */
+}(dicomParser));
+/**
+ * Internal helper functions for parsing DICOM elements
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -1349,89 +1329,89 @@
*/
dicomParser.findEndOfEncapsulatedElement = function(byteStream, element, warnings)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.findEndOfEncapsulatedElement: missing required parameter 'byteStream'";
- }
- if(element === undefined)
- {
- throw "dicomParser.findEndOfEncapsulatedElement: missing required parameter 'element'";
- }
-
- element.encapsulatedPixelData = true;
- element.basicOffsetTable = [];
- element.fragments = [];
- var basicOffsetTableItemTag = dicomParser.readTag(byteStream);
- if(basicOffsetTableItemTag !== 'xfffee000') {
- throw "dicomParser.findEndOfEncapsulatedElement: basic offset table not found";
- }
- var basicOffsetTableItemlength = byteStream.readUint32();
- var numFragments = basicOffsetTableItemlength / 4;
- for(var i =0; i < numFragments; i++) {
- var offset = byteStream.readUint32();
- element.basicOffsetTable.push(offset);
- }
- var baseOffset = byteStream.position;
-
- while(byteStream.position < byteStream.byteArray.length)
- {
- var tag = dicomParser.readTag(byteStream);
- var length = byteStream.readUint32();
- if(tag === 'xfffee0dd')
+ if(byteStream === undefined)
{
- byteStream.seek(length);
- element.length = byteStream.position - element.dataOffset;
- return;
+ throw "dicomParser.findEndOfEncapsulatedElement: missing required parameter 'byteStream'";
}
- else if(tag === 'xfffee000')
+ if(element === undefined)
{
- element.fragments.push({
- offset: byteStream.position - baseOffset - 8,
- position : byteStream.position,
- length : length
- });
- }
- else {
- if(warnings) {
- warnings.push('unexpected tag ' + tag + ' while searching for end of pixel data element with undefined length');
- }
- if(length > byteStream.byteArray.length - byteStream.position)
- {
- // fix length
- length = byteStream.byteArray.length - byteStream.position;
- }
- element.fragments.push({
- offset: byteStream.position - baseOffset - 8,
- position : byteStream.position,
- length : length
- });
- byteStream.seek(length);
- element.length = byteStream.position - element.dataOffset;
- return;
+ throw "dicomParser.findEndOfEncapsulatedElement: missing required parameter 'element'";
}
- byteStream.seek(length);
- }
+ element.encapsulatedPixelData = true;
+ element.basicOffsetTable = [];
+ element.fragments = [];
+ var basicOffsetTableItemTag = dicomParser.readTag(byteStream);
+ if(basicOffsetTableItemTag !== 'xfffee000') {
+ throw "dicomParser.findEndOfEncapsulatedElement: basic offset table not found";
+ }
+ var basicOffsetTableItemlength = byteStream.readUint32();
+ var numFragments = basicOffsetTableItemlength / 4;
+ for(var i =0; i < numFragments; i++) {
+ var offset = byteStream.readUint32();
+ element.basicOffsetTable.push(offset);
+ }
+ var baseOffset = byteStream.position;
- if(warnings) {
- warnings.push("pixel data element " + element.tag + " missing sequence delimiter tag xfffee0dd");
- }
+ while(byteStream.position < byteStream.byteArray.length)
+ {
+ var tag = dicomParser.readTag(byteStream);
+ var length = byteStream.readUint32();
+ if(tag === 'xfffee0dd')
+ {
+ byteStream.seek(length);
+ element.length = byteStream.position - element.dataOffset;
+ return;
+ }
+ else if(tag === 'xfffee000')
+ {
+ element.fragments.push({
+ offset: byteStream.position - baseOffset - 8,
+ position : byteStream.position,
+ length : length
+ });
+ }
+ else {
+ if(warnings) {
+ warnings.push('unexpected tag ' + tag + ' while searching for end of pixel data element with undefined length');
+ }
+ if(length > byteStream.byteArray.length - byteStream.position)
+ {
+ // fix length
+ length = byteStream.byteArray.length - byteStream.position;
+ }
+ element.fragments.push({
+ offset: byteStream.position - baseOffset - 8,
+ position : byteStream.position,
+ length : length
+ });
+ byteStream.seek(length);
+ element.length = byteStream.position - element.dataOffset;
+ return;
+ }
+
+ byteStream.seek(length);
+ }
+
+ if(warnings) {
+ warnings.push("pixel data element " + element.tag + " missing sequence delimiter tag xfffee0dd");
+ }
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing DICOM elements
- */
+}(dicomParser));
+/**
+ * Internal helper functions for parsing DICOM elements
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -1442,228 +1422,228 @@
*/
dicomParser.findItemDelimitationItemAndSetElementLength = function(byteStream, element)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.readDicomElementImplicit: missing required parameter 'byteStream'";
- }
-
- var itemDelimitationItemLength = 8; // group, element, length
- var maxPosition = byteStream.byteArray.length - itemDelimitationItemLength;
- while(byteStream.position <= maxPosition)
- {
- var groupNumber = byteStream.readUint16();
- if(groupNumber === 0xfffe)
+ if(byteStream === undefined)
{
- var elementNumber = byteStream.readUint16();
- if(elementNumber === 0xe00d)
- {
- // NOTE: It would be better to also check for the length to be 0 as part of the check above
- // but we will just log a warning for now
- var itemDelimiterLength = byteStream.readUint32(); // the length
- if(itemDelimiterLength !== 0) {
- byteStream.warnings('encountered non zero length following item delimiter at position' + byteStream.position - 4 + " while reading element of undefined length with tag ' + element.tag");
- }
- element.length = byteStream.position - element.dataOffset;
- return;
- }
+ throw "dicomParser.readDicomElementImplicit: missing required parameter 'byteStream'";
}
- }
- // No item delimitation item - silently set the length to the end of the buffer and set the position past the end of the buffer
- element.length = byteStream.byteArray.length - element.dataOffset;
- byteStream.seek(byteStream.byteArray.length - byteStream.position);
+ var itemDelimitationItemLength = 8; // group, element, length
+ var maxPosition = byteStream.byteArray.length - itemDelimitationItemLength;
+ while(byteStream.position <= maxPosition)
+ {
+ var groupNumber = byteStream.readUint16();
+ if(groupNumber === 0xfffe)
+ {
+ var elementNumber = byteStream.readUint16();
+ if(elementNumber === 0xe00d)
+ {
+ // NOTE: It would be better to also check for the length to be 0 as part of the check above
+ // but we will just log a warning for now
+ var itemDelimiterLength = byteStream.readUint32(); // the length
+ if(itemDelimiterLength !== 0) {
+ byteStream.warnings('encountered non zero length following item delimiter at position' + byteStream.position - 4 + " while reading element of undefined length with tag ' + element.tag");
+ }
+ element.length = byteStream.position - element.dataOffset;
+ return;
+ }
+ }
+ }
+
+ // No item delimitation item - silently set the length to the end of the buffer and set the position past the end of the buffer
+ element.length = byteStream.byteArray.length - element.dataOffset;
+ byteStream.seek(byteStream.byteArray.length - byteStream.position);
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing different types from a little-endian byte array
- */
+}(dicomParser));
+/**
+ * Internal helper functions for parsing different types from a little-endian byte array
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
dicomParser.littleEndianByteArrayParser = {
- /**
- *
- * Parses an unsigned int 16 from a little-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed unsigned int 16
- * @throws error if buffer overread would occur
- * @access private
- */
- readUint16: function (byteArray, position) {
- if (position < 0) {
- throw 'littleEndianByteArrayParser.readUint16: position cannot be less than 0';
+ /**
+ *
+ * Parses an unsigned int 16 from a little-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed unsigned int 16
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readUint16: function (byteArray, position) {
+ if (position < 0) {
+ throw 'littleEndianByteArrayParser.readUint16: position cannot be less than 0';
+ }
+ if (position + 2 > byteArray.length) {
+ throw 'littleEndianByteArrayParser.readUint16: attempt to read past end of buffer';
+ }
+ return byteArray[position] + (byteArray[position + 1] * 256);
+ },
+
+ /**
+ *
+ * Parses a signed int 16 from a little-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed signed int 16
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readInt16: function (byteArray, position) {
+ if (position < 0) {
+ throw 'littleEndianByteArrayParser.readInt16: position cannot be less than 0';
+ }
+ if (position + 2 > byteArray.length) {
+ throw 'littleEndianByteArrayParser.readInt16: attempt to read past end of buffer';
+ }
+ var int16 = byteArray[position] + (byteArray[position + 1] << 8);
+ // fix sign
+ if (int16 & 0x8000) {
+ int16 = int16 - 0xFFFF - 1;
+ }
+ return int16;
+ },
+
+
+ /**
+ * Parses an unsigned int 32 from a little-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed unsigned int 32
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readUint32: function (byteArray, position) {
+ if (position < 0) {
+ throw 'littleEndianByteArrayParser.readUint32: position cannot be less than 0';
+ }
+
+ if (position + 4 > byteArray.length) {
+ throw 'littleEndianByteArrayParser.readUint32: attempt to read past end of buffer';
+ }
+
+ var uint32 = (byteArray[position] +
+ (byteArray[position + 1] * 256) +
+ (byteArray[position + 2] * 256 * 256) +
+ (byteArray[position + 3] * 256 * 256 * 256 ));
+
+ return uint32;
+ },
+
+ /**
+ * Parses a signed int 32 from a little-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed unsigned int 32
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readInt32: function (byteArray, position) {
+ if (position < 0) {
+ throw 'littleEndianByteArrayParser.readInt32: position cannot be less than 0';
+ }
+
+ if (position + 4 > byteArray.length) {
+ throw 'littleEndianByteArrayParser.readInt32: attempt to read past end of buffer';
+ }
+
+ var int32 = (byteArray[position] +
+ (byteArray[position + 1] << 8) +
+ (byteArray[position + 2] << 16) +
+ (byteArray[position + 3] << 24));
+
+ return int32;
+
+ },
+
+ /**
+ * Parses 32-bit float from a little-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed 32-bit float
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readFloat: function (byteArray, position) {
+ if (position < 0) {
+ throw 'littleEndianByteArrayParser.readFloat: position cannot be less than 0';
+ }
+
+ if (position + 4 > byteArray.length) {
+ throw 'littleEndianByteArrayParser.readFloat: attempt to read past end of buffer';
+ }
+
+ // I am sure there is a better way than this but this should be safe
+ var byteArrayForParsingFloat = new Uint8Array(4);
+ byteArrayForParsingFloat[0] = byteArray[position];
+ byteArrayForParsingFloat[1] = byteArray[position + 1];
+ byteArrayForParsingFloat[2] = byteArray[position + 2];
+ byteArrayForParsingFloat[3] = byteArray[position + 3];
+ var floatArray = new Float32Array(byteArrayForParsingFloat.buffer);
+ return floatArray[0];
+ },
+
+ /**
+ * Parses 64-bit float from a little-endian byte array
+ *
+ * @param byteArray the byte array to read from
+ * @param position the position in the byte array to read from
+ * @returns {*} the parsed 64-bit float
+ * @throws error if buffer overread would occur
+ * @access private
+ */
+ readDouble: function (byteArray, position) {
+ if (position < 0) {
+ throw 'littleEndianByteArrayParser.readDouble: position cannot be less than 0';
+ }
+
+ if (position + 8 > byteArray.length) {
+ throw 'littleEndianByteArrayParser.readDouble: attempt to read past end of buffer';
+ }
+
+ // I am sure there is a better way than this but this should be safe
+ var byteArrayForParsingFloat = new Uint8Array(8);
+ byteArrayForParsingFloat[0] = byteArray[position];
+ byteArrayForParsingFloat[1] = byteArray[position + 1];
+ byteArrayForParsingFloat[2] = byteArray[position + 2];
+ byteArrayForParsingFloat[3] = byteArray[position + 3];
+ byteArrayForParsingFloat[4] = byteArray[position + 4];
+ byteArrayForParsingFloat[5] = byteArray[position + 5];
+ byteArrayForParsingFloat[6] = byteArray[position + 6];
+ byteArrayForParsingFloat[7] = byteArray[position + 7];
+ var floatArray = new Float64Array(byteArrayForParsingFloat.buffer);
+ return floatArray[0];
}
- if (position + 2 > byteArray.length) {
- throw 'littleEndianByteArrayParser.readUint16: attempt to read past end of buffer';
- }
- return byteArray[position] + (byteArray[position + 1] * 256);
- },
-
- /**
- *
- * Parses a signed int 16 from a little-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed signed int 16
- * @throws error if buffer overread would occur
- * @access private
- */
- readInt16: function (byteArray, position) {
- if (position < 0) {
- throw 'littleEndianByteArrayParser.readInt16: position cannot be less than 0';
- }
- if (position + 2 > byteArray.length) {
- throw 'littleEndianByteArrayParser.readInt16: attempt to read past end of buffer';
- }
- var int16 = byteArray[position] + (byteArray[position + 1] << 8);
- // fix sign
- if (int16 & 0x8000) {
- int16 = int16 - 0xFFFF - 1;
- }
- return int16;
- },
-
-
- /**
- * Parses an unsigned int 32 from a little-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed unsigned int 32
- * @throws error if buffer overread would occur
- * @access private
- */
- readUint32: function (byteArray, position) {
- if (position < 0) {
- throw 'littleEndianByteArrayParser.readUint32: position cannot be less than 0';
- }
-
- if (position + 4 > byteArray.length) {
- throw 'littleEndianByteArrayParser.readUint32: attempt to read past end of buffer';
- }
-
- var uint32 = (byteArray[position] +
- (byteArray[position + 1] * 256) +
- (byteArray[position + 2] * 256 * 256) +
- (byteArray[position + 3] * 256 * 256 * 256 ));
-
- return uint32;
- },
-
- /**
- * Parses a signed int 32 from a little-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed unsigned int 32
- * @throws error if buffer overread would occur
- * @access private
- */
- readInt32: function (byteArray, position) {
- if (position < 0) {
- throw 'littleEndianByteArrayParser.readInt32: position cannot be less than 0';
- }
-
- if (position + 4 > byteArray.length) {
- throw 'littleEndianByteArrayParser.readInt32: attempt to read past end of buffer';
- }
-
- var int32 = (byteArray[position] +
- (byteArray[position + 1] << 8) +
- (byteArray[position + 2] << 16) +
- (byteArray[position + 3] << 24));
-
- return int32;
-
- },
-
- /**
- * Parses 32-bit float from a little-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed 32-bit float
- * @throws error if buffer overread would occur
- * @access private
- */
- readFloat: function (byteArray, position) {
- if (position < 0) {
- throw 'littleEndianByteArrayParser.readFloat: position cannot be less than 0';
- }
-
- if (position + 4 > byteArray.length) {
- throw 'littleEndianByteArrayParser.readFloat: attempt to read past end of buffer';
- }
-
- // I am sure there is a better way than this but this should be safe
- var byteArrayForParsingFloat = new Uint8Array(4);
- byteArrayForParsingFloat[0] = byteArray[position];
- byteArrayForParsingFloat[1] = byteArray[position + 1];
- byteArrayForParsingFloat[2] = byteArray[position + 2];
- byteArrayForParsingFloat[3] = byteArray[position + 3];
- var floatArray = new Float32Array(byteArrayForParsingFloat.buffer);
- return floatArray[0];
- },
-
- /**
- * Parses 64-bit float from a little-endian byte array
- *
- * @param byteArray the byte array to read from
- * @param position the position in the byte array to read from
- * @returns {*} the parsed 64-bit float
- * @throws error if buffer overread would occur
- * @access private
- */
- readDouble: function (byteArray, position) {
- if (position < 0) {
- throw 'littleEndianByteArrayParser.readDouble: position cannot be less than 0';
- }
-
- if (position + 8 > byteArray.length) {
- throw 'littleEndianByteArrayParser.readDouble: attempt to read past end of buffer';
- }
-
- // I am sure there is a better way than this but this should be safe
- var byteArrayForParsingFloat = new Uint8Array(8);
- byteArrayForParsingFloat[0] = byteArray[position];
- byteArrayForParsingFloat[1] = byteArray[position + 1];
- byteArrayForParsingFloat[2] = byteArray[position + 2];
- byteArrayForParsingFloat[3] = byteArray[position + 3];
- byteArrayForParsingFloat[4] = byteArray[position + 4];
- byteArrayForParsingFloat[5] = byteArray[position + 5];
- byteArrayForParsingFloat[6] = byteArray[position + 6];
- byteArrayForParsingFloat[7] = byteArray[position + 7];
- var floatArray = new Float64Array(byteArrayForParsingFloat.buffer);
- return floatArray[0];
- }
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing implicit and explicit DICOM data sets
- */
+}(dicomParser));
+/**
+ * Internal helper functions for parsing implicit and explicit DICOM data sets
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -1673,30 +1653,30 @@
*/
dicomParser.parseDicomDataSetExplicit = function (dataSet, byteStream, maxPosition, options) {
- maxPosition = (maxPosition === undefined) ? byteStream.byteArray.length : maxPosition ;
- options = options || {};
+ maxPosition = (maxPosition === undefined) ? byteStream.byteArray.length : maxPosition ;
+ options = options || {};
- if(byteStream === undefined)
- {
- throw "dicomParser.parseDicomDataSetExplicit: missing required parameter 'byteStream'";
- }
- if(maxPosition < byteStream.position || maxPosition > byteStream.byteArray.length)
- {
- throw "dicomParser.parseDicomDataSetExplicit: invalid value for parameter 'maxPosition'";
- }
- var elements = dataSet.elements;
-
- while(byteStream.position < maxPosition)
- {
- var element = dicomParser.readDicomElementExplicit(byteStream, dataSet.warnings, options.untilTag);
- elements[element.tag] = element;
- if(element.tag === options.untilTag) {
- return;
+ if(byteStream === undefined)
+ {
+ throw "dicomParser.parseDicomDataSetExplicit: missing required parameter 'byteStream'";
+ }
+ if(maxPosition < byteStream.position || maxPosition > byteStream.byteArray.length)
+ {
+ throw "dicomParser.parseDicomDataSetExplicit: invalid value for parameter 'maxPosition'";
+ }
+ var elements = dataSet.elements;
+
+ while(byteStream.position < maxPosition)
+ {
+ var element = dicomParser.readDicomElementExplicit(byteStream, dataSet.warnings, options.untilTag);
+ elements[element.tag] = element;
+ if(element.tag === options.untilTag) {
+ return;
+ }
+ }
+ if(byteStream.position > maxPosition) {
+ throw "dicomParser:parseDicomDataSetExplicit: buffer overrun";
}
- }
- if(byteStream.position > maxPosition) {
- throw "dicomParser:parseDicomDataSetExplicit: buffer overrun";
- }
};
/**
@@ -1706,385 +1686,454 @@
*/
dicomParser.parseDicomDataSetImplicit = function(dataSet, byteStream, maxPosition, options)
{
- maxPosition = (maxPosition === undefined) ? dataSet.byteArray.length : maxPosition ;
- options = options || {};
+ maxPosition = (maxPosition === undefined) ? dataSet.byteArray.length : maxPosition ;
+ options = options || {};
- if(byteStream === undefined)
- {
- throw "dicomParser.parseDicomDataSetImplicit: missing required parameter 'byteStream'";
- }
- if(maxPosition < byteStream.position || maxPosition > byteStream.byteArray.length)
- {
- throw "dicomParser.parseDicomDataSetImplicit: invalid value for parameter 'maxPosition'";
- }
-
- var elements = dataSet.elements;
-
- while(byteStream.position < maxPosition)
- {
- var element = dicomParser.readDicomElementImplicit(byteStream, options.untilTag);
- elements[element.tag] = element;
- if(element.tag === options.untilTag) {
- return;
+ if(byteStream === undefined)
+ {
+ throw "dicomParser.parseDicomDataSetImplicit: missing required parameter 'byteStream'";
+ }
+ if(maxPosition < byteStream.position || maxPosition > byteStream.byteArray.length)
+ {
+ throw "dicomParser.parseDicomDataSetImplicit: invalid value for parameter 'maxPosition'";
+ }
+
+ var elements = dataSet.elements;
+
+ while(byteStream.position < maxPosition)
+ {
+ var element = dicomParser.readDicomElementImplicit(byteStream, options.untilTag);
+ elements[element.tag] = element;
+ if(element.tag === options.untilTag) {
+ return;
+ }
}
- }
};
return dicomParser;
- }(dicomParser));
+}(dicomParser));
- /**
- * Internal helper functions for for parsing DICOM elements
- */
+/**
+ * Internal helper functions for for parsing DICOM elements
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
function getDataLengthSizeInBytesForVR(vr)
{
- if( vr === 'OB' ||
- vr === 'OW' ||
- vr === 'SQ' ||
- vr === 'OF' ||
- vr === 'UT' ||
- vr === 'UN')
- {
- return 4;
- }
- else
- {
- return 2;
- }
+ if( vr === 'OB' ||
+ vr === 'OW' ||
+ vr === 'SQ' ||
+ vr === 'OF' ||
+ vr === 'UT' ||
+ vr === 'UN')
+ {
+ return 4;
+ }
+ else
+ {
+ return 2;
+ }
}
dicomParser.readDicomElementExplicit = function(byteStream, warnings, untilTag)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.readDicomElementExplicit: missing required parameter 'byteStream'";
- }
-
- var element = {
- tag : dicomParser.readTag(byteStream),
- vr : byteStream.readFixedString(2)
- // length set below based on VR
- // dataOffset set below based on VR and size of length
- };
-
- var dataLengthSizeBytes = getDataLengthSizeInBytesForVR(element.vr);
- if(dataLengthSizeBytes === 2)
- {
- element.length = byteStream.readUint16();
- element.dataOffset = byteStream.position;
- }
- else
- {
- byteStream.seek(2);
- element.length = byteStream.readUint32();
- element.dataOffset = byteStream.position;
- }
-
- if(element.length === 4294967295)
- {
- element.hadUndefinedLength = true;
- }
-
- if(element.tag === untilTag) {
- return element;
- }
-
- // if VR is SQ, parse the sequence items
- if(element.vr === 'SQ')
- {
- dicomParser.readSequenceItemsExplicit(byteStream, element, warnings);
- return element;
- }
- if(element.length === 4294967295)
- {
- if(element.tag === 'x7fe00010') {
- dicomParser.findEndOfEncapsulatedElement(byteStream, element, warnings);
- return element;
- } else {
- dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element);
- return element;
+ if(byteStream === undefined)
+ {
+ throw "dicomParser.readDicomElementExplicit: missing required parameter 'byteStream'";
}
- }
- byteStream.seek(element.length);
- return element;
+ var element = {
+ tag : dicomParser.readTag(byteStream),
+ vr : byteStream.readFixedString(2)
+ // length set below based on VR
+ // dataOffset set below based on VR and size of length
+ };
+
+ var dataLengthSizeBytes = getDataLengthSizeInBytesForVR(element.vr);
+ if(dataLengthSizeBytes === 2)
+ {
+ element.length = byteStream.readUint16();
+ element.dataOffset = byteStream.position;
+ }
+ else
+ {
+ byteStream.seek(2);
+ element.length = byteStream.readUint32();
+ element.dataOffset = byteStream.position;
+ }
+
+ if(element.length === 4294967295)
+ {
+ element.hadUndefinedLength = true;
+ }
+
+ if(element.tag === untilTag) {
+ return element;
+ }
+
+ // if VR is SQ, parse the sequence items
+ if(element.vr === 'SQ')
+ {
+ dicomParser.readSequenceItemsExplicit(byteStream, element, warnings);
+ return element;
+ }
+ if(element.length === 4294967295)
+ {
+ if(element.tag === 'x7fe00010') {
+ dicomParser.findEndOfEncapsulatedElement(byteStream, element, warnings);
+ return element;
+ } else {
+ dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element);
+ return element;
+ }
+ }
+
+ byteStream.seek(element.length);
+ return element;
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for for parsing DICOM elements
- */
+}(dicomParser));
+/**
+ * Internal helper functions for for parsing DICOM elements
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
dicomParser.readDicomElementImplicit = function(byteStream, untilTag)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.readDicomElementImplicit: missing required parameter 'byteStream'";
- }
-
- var element = {
- tag : dicomParser.readTag(byteStream),
- length: byteStream.readUint32(),
- dataOffset : byteStream.position
- };
-
- if(element.length === 4294967295)
- {
- element.hadUndefinedLength = true;
- }
-
- if(element.tag === untilTag) {
- return element;
- }
-
- // peek ahead at the next tag to see if it looks like a 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.
- if ((byteStream.position + 4) <= byteStream.byteArray.length) {
- 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(byteStream === undefined)
+ {
+ throw "dicomParser.readDicomElementImplicit: missing required parameter 'byteStream'";
}
- if (nextTag === 'xfffee000') {
- // parse the sequence
- dicomParser.readSequenceItemsImplicit(byteStream, element);
- //element.length = byteStream.byteArray.length - element.dataOffset;
- return element;
+ var element = {
+ tag : dicomParser.readTag(byteStream),
+ length: byteStream.readUint32(),
+ dataOffset : byteStream.position
+ };
+
+ if(element.length === 4294967295)
+ {
+ element.hadUndefinedLength = true;
}
- }
- // 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.
- if(element.length === 4294967295)
- {
- dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element);
+ if(element.tag === untilTag) {
+ return element;
+ }
+
+ // peek ahead at the next tag to see if it looks like a 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.
+ if ((byteStream.position + 4) <= byteStream.byteArray.length) {
+ 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
+ // scan the data for a magic number to figure out when it ends.
+ if(element.length === 4294967295)
+ {
+ dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element);
+ return element;
+ }
+
+ // non sequence element with known length, skip over the data part
+ byteStream.seek(element.length);
return element;
- }
-
- // non sequence element with known length, skip over the data part
- byteStream.seek(element.length);
- return element;
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing DICOM elements
- */
-
- var dicomParser = (function (dicomParser)
+}(dicomParser));
+/**
+ * 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
+ * tag is encoutered. This can be used to parse partial byte streams.
+ *
+ * @param byteArray the byte array
+ * @param options object to control parsing behavior (optional)
+ * @returns {DataSet}
+ * @throws error if an error occurs while parsing. The exception object will contain a property dataSet with the
+ * elements successfully parsed before the error.
+ */
+var dicomParser = (function(dicomParser) {
+ if(dicomParser === undefined)
{
+ dicomParser = {};
+ }
+
+ dicomParser.readPart10Header = function(byteArray, options) {
+
+ if(byteArray === undefined)
+ {
+ throw "dicomParser.readPart10Header: missing required parameter 'byteArray'";
+ }
+
+ var littleEndianByteStream = new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray);
+
+ function readPrefix()
+ {
+ littleEndianByteStream.seek(128);
+ var prefix = littleEndianByteStream.readFixedString(4);
+ if(prefix !== "DICM")
+ {
+ throw "dicomParser.readPart10Header: DICM prefix not found at location 132";
+ }
+ }
+
+ // main function here
+ function readTheHeader() {
+ // Per the DICOM standard, the header is always encoded in Explicit VR Little Endian (see PS3.10, section 7.1)
+ // so use littleEndianByteStream throughout this method regardless of the transfer syntax
+ readPrefix();
+
+ var warnings = [];
+ var elements = {};
+ while(littleEndianByteStream.position < littleEndianByteStream.byteArray.length) {
+ var position = littleEndianByteStream.position;
+ var element = dicomParser.readDicomElementExplicit(littleEndianByteStream, warnings);
+ if(element.tag > 'x0002ffff') {
+ littleEndianByteStream.position = position;
+ break;
+ }
+ // Cache the littleEndianByteArrayParser for meta header elements, since the rest of the data set may be big endian
+ // and this parser will be needed later if the meta header values are to be read.
+ element.parser = dicomParser.littleEndianByteArrayParser;
+ elements[element.tag] = element;
+ }
+ var metaHeaderDataSet = new dicomParser.DataSet(littleEndianByteStream.byteArrayParser, littleEndianByteStream.byteArray, elements);
+ metaHeaderDataSet.warnings = littleEndianByteStream.warnings;
+ metaHeaderDataSet.position = littleEndianByteStream.position;
+ return metaHeaderDataSet;
+ }
+
+ // This is where we actually start parsing
+ return readTheHeader();
+ };
+
+ return dicomParser;
+})(dicomParser);
+
+/**
+ * Internal helper functions for parsing DICOM elements
+ */
+
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
function readDicomDataSetExplicitUndefinedLength(byteStream, warnings)
{
- var elements = {};
+ var elements = {};
- while(byteStream.position < byteStream.byteArray.length)
- {
- var element = dicomParser.readDicomElementExplicit(byteStream, warnings);
- elements[element.tag] = element;
-
- // we hit an item delimiter tag, return the current offset to mark
- // the end of this sequence item
- if(element.tag === 'xfffee00d')
+ while(byteStream.position < byteStream.byteArray.length)
{
- return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
+ var element = dicomParser.readDicomElementExplicit(byteStream, warnings);
+ elements[element.tag] = element;
+
+ // we hit an item delimiter tag, return the current offset to mark
+ // the end of this sequence item
+ if(element.tag === 'xfffee00d')
+ {
+ return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
+ }
+
}
- }
-
- // 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');
- return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
+ // 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');
+ return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
}
function readSequenceItemExplicit(byteStream, warnings)
{
- var item = dicomParser.readSequenceItem(byteStream);
+ var item = dicomParser.readSequenceItem(byteStream);
- if(item.length === 4294967295)
- {
- item.hadUndefinedLength = true;
- item.dataSet = readDicomDataSetExplicitUndefinedLength(byteStream, warnings);
- item.length = byteStream.position - item.dataOffset;
- }
- else
- {
- item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {});
- dicomParser.parseDicomDataSetExplicit(item.dataSet, byteStream, byteStream.position + item.length);
- }
- return item;
+ if(item.length === 4294967295)
+ {
+ item.hadUndefinedLength = true;
+ item.dataSet = readDicomDataSetExplicitUndefinedLength(byteStream, warnings);
+ item.length = byteStream.position - item.dataOffset;
+ }
+ else
+ {
+ item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {});
+ dicomParser.parseDicomDataSetExplicit(item.dataSet, byteStream, byteStream.position + item.length);
+ }
+ return item;
}
function readSQElementUndefinedLengthExplicit(byteStream, element, warnings)
{
- while(byteStream.position < byteStream.byteArray.length)
- {
- // end reading this sequence if the next tag is the sequence delimitation item
- var nextTag = dicomParser.readTag(byteStream);
- byteStream.seek(-4);
- if (nextTag === 'xfffee0dd') {
- // set the correct length
- element.length = byteStream.position - element.dataOffset;
- byteStream.seek(8);
- return element;
- }
+ while(byteStream.position < byteStream.byteArray.length)
+ {
+ // end reading this sequence if the next tag is the sequence delimitation item
+ var nextTag = dicomParser.readTag(byteStream);
+ byteStream.seek(-4);
+ if (nextTag === 'xfffee0dd') {
+ // set the correct length
+ element.length = byteStream.position - element.dataOffset;
+ byteStream.seek(8);
+ return element;
+ }
- var item = readSequenceItemExplicit(byteStream, warnings);
- element.items.push(item);
- }
- element.length = byteStream.position - element.dataOffset;
+ var item = readSequenceItemExplicit(byteStream, warnings);
+ element.items.push(item);
+ }
+ element.length = byteStream.position - element.dataOffset;
}
function readSQElementKnownLengthExplicit(byteStream, element, warnings)
{
- var maxPosition = element.dataOffset + element.length;
- while(byteStream.position < maxPosition)
- {
- var item = readSequenceItemExplicit(byteStream, warnings);
- element.items.push(item);
- }
+ var maxPosition = element.dataOffset + element.length;
+ while(byteStream.position < maxPosition)
+ {
+ var item = readSequenceItemExplicit(byteStream, warnings);
+ element.items.push(item);
+ }
}
dicomParser.readSequenceItemsExplicit = function(byteStream, element, warnings)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.readSequenceItemsExplicit: missing required parameter 'byteStream'";
- }
- if(element === undefined)
- {
- throw "dicomParser.readSequenceItemsExplicit: missing required parameter 'element'";
- }
+ if(byteStream === undefined)
+ {
+ throw "dicomParser.readSequenceItemsExplicit: missing required parameter 'byteStream'";
+ }
+ if(element === undefined)
+ {
+ throw "dicomParser.readSequenceItemsExplicit: missing required parameter 'element'";
+ }
- element.items = [];
+ element.items = [];
- if(element.length === 4294967295)
- {
- readSQElementUndefinedLengthExplicit(byteStream, element);
- }
- else
- {
- readSQElementKnownLengthExplicit(byteStream, element, warnings);
- }
+ if(element.length === 4294967295)
+ {
+ readSQElementUndefinedLengthExplicit(byteStream, element);
+ }
+ else
+ {
+ readSQElementKnownLengthExplicit(byteStream, element, warnings);
+ }
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing DICOM elements
- */
+}(dicomParser));
+/**
+ * Internal helper functions for parsing DICOM elements
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
function readDicomDataSetImplicitUndefinedLength(byteStream)
{
- var elements = {};
+ var elements = {};
- while(byteStream.position < byteStream.byteArray.length)
- {
- var element = dicomParser.readDicomElementImplicit(byteStream);
- elements[element.tag] = element;
-
- // we hit an item delimiter tag, return the current offset to mark
- // the end of this sequence item
- if(element.tag === 'xfffee00d')
+ while(byteStream.position < byteStream.byteArray.length)
{
- return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
+ var element = dicomParser.readDicomElementImplicit(byteStream);
+ elements[element.tag] = element;
+
+ // we hit an item delimiter tag, return the current offset to mark
+ // the end of this sequence item
+ if(element.tag === 'xfffee00d')
+ {
+ return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
+ }
}
- }
- // eof encountered - log a warning and return what we have for the element
- byteStream.warnings.push('eof encountered before finding sequence item delimiter in sequence item of undefined length');
- return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
+ // eof encountered - log a warning and return what we have for the element
+ byteStream.warnings.push('eof encountered before finding sequence item delimiter in sequence item of undefined length');
+ return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements);
}
function readSequenceItemImplicit(byteStream)
{
- var item = dicomParser.readSequenceItem(byteStream);
+ var item = dicomParser.readSequenceItem(byteStream);
- if(item.length === 4294967295)
- {
- item.hadUndefinedLength = true;
- item.dataSet = readDicomDataSetImplicitUndefinedLength(byteStream);
- item.length = byteStream.position - item.dataOffset;
- }
- else
- {
- item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {});
- dicomParser.parseDicomDataSetImplicit(item.dataSet, byteStream, byteStream.position + item.length);
- }
- return item;
+ if(item.length === 4294967295)
+ {
+ item.hadUndefinedLength = true;
+ item.dataSet = readDicomDataSetImplicitUndefinedLength(byteStream);
+ item.length = byteStream.position - item.dataOffset;
+ }
+ else
+ {
+ item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {});
+ dicomParser.parseDicomDataSetImplicit(item.dataSet, byteStream, byteStream.position + item.length);
+ }
+ return item;
}
function readSQElementUndefinedLengthImplicit(byteStream, element)
{
- while(byteStream.position < byteStream.byteArray.length)
- {
- // end reading this sequence if the next tag is the sequence delimitation item
- var nextTag = dicomParser.readTag(byteStream);
- byteStream.seek(-4);
- if (nextTag === 'xfffee0dd') {
- // set the correct length
- element.length = byteStream.position - element.dataOffset;
- byteStream.seek(8);
- return element;
- }
+ while(byteStream.position < byteStream.byteArray.length)
+ {
+ // end reading this sequence if the next tag is the sequence delimitation item
+ var nextTag = dicomParser.readTag(byteStream);
+ byteStream.seek(-4);
+ if (nextTag === 'xfffee0dd') {
+ // set the correct length
+ element.length = byteStream.position - element.dataOffset;
+ byteStream.seek(8);
+ return element;
+ }
- var item = readSequenceItemImplicit(byteStream);
- element.items.push(item);
- }
- element.length = byteStream.byteArray.length - element.dataOffset;
+ var item = readSequenceItemImplicit(byteStream);
+ element.items.push(item);
+ }
+ element.length = byteStream.byteArray.length - element.dataOffset;
}
function readSQElementKnownLengthImplicit(byteStream, element)
{
- var maxPosition = element.dataOffset + element.length;
- while(byteStream.position < maxPosition)
- {
- var item = readSequenceItemImplicit(byteStream);
- element.items.push(item);
- }
+ var maxPosition = element.dataOffset + element.length;
+ while(byteStream.position < maxPosition)
+ {
+ var item = readSequenceItemImplicit(byteStream);
+ element.items.push(item);
+ }
}
/**
@@ -2094,40 +2143,40 @@
*/
dicomParser.readSequenceItemsImplicit = function(byteStream, element)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.readSequenceItemsImplicit: missing required parameter 'byteStream'";
- }
- if(element === undefined)
- {
- throw "dicomParser.readSequenceItemsImplicit: missing required parameter 'element'";
- }
+ if(byteStream === undefined)
+ {
+ throw "dicomParser.readSequenceItemsImplicit: missing required parameter 'byteStream'";
+ }
+ if(element === undefined)
+ {
+ throw "dicomParser.readSequenceItemsImplicit: missing required parameter 'element'";
+ }
- element.items = [];
+ element.items = [];
- if(element.length === 4294967295)
- {
- readSQElementUndefinedLengthImplicit(byteStream, element);
- }
- else
- {
- readSQElementKnownLengthImplicit(byteStream, element);
- }
+ if(element.length === 4294967295)
+ {
+ readSQElementUndefinedLengthImplicit(byteStream, element);
+ }
+ else
+ {
+ readSQElementKnownLengthImplicit(byteStream, element);
+ }
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing DICOM elements
- */
+}(dicomParser));
+/**
+ * Internal helper functions for parsing DICOM elements
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -2140,34 +2189,34 @@
*/
dicomParser.readSequenceItem = function(byteStream)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.readSequenceItem: missing required parameter 'byteStream'";
- }
+ if(byteStream === undefined)
+ {
+ throw "dicomParser.readSequenceItem: missing required parameter 'byteStream'";
+ }
- var element = {
- tag : dicomParser.readTag(byteStream),
- length : byteStream.readUint32(),
- dataOffset : byteStream.position
- };
+ var element = {
+ tag : dicomParser.readTag(byteStream),
+ length : byteStream.readUint32(),
+ dataOffset : byteStream.position
+ };
- return element;
+ return element;
};
return dicomParser;
- }(dicomParser));
- /**
- * Internal helper functions for parsing DICOM elements
- */
+}(dicomParser));
+/**
+ * Internal helper functions for parsing DICOM elements
+ */
- var dicomParser = (function (dicomParser)
- {
+var dicomParser = (function (dicomParser)
+{
"use strict";
if(dicomParser === undefined)
{
- dicomParser = {};
+ dicomParser = {};
}
/**
@@ -2178,35 +2227,35 @@
*/
dicomParser.readTag = function(byteStream)
{
- if(byteStream === undefined)
- {
- throw "dicomParser.readTag: missing required parameter 'byteStream'";
- }
+ if(byteStream === undefined)
+ {
+ throw "dicomParser.readTag: missing required parameter 'byteStream'";
+ }
- var groupNumber = byteStream.readUint16() * 256 * 256;
- var elementNumber = byteStream.readUint16();
- var tag = "x" + ('00000000' + (groupNumber + elementNumber).toString(16)).substr(-8);
- return tag;
+ var groupNumber = byteStream.readUint16() * 256 * 256;
+ var elementNumber = byteStream.readUint16();
+ var tag = "x" + ('00000000' + (groupNumber + elementNumber).toString(16)).substr(-8);
+ return tag;
};
return dicomParser;
- }(dicomParser));
- /**
- * Version
- */
+}(dicomParser));
+/**
+ * Version
+ */
- var dicomParser = (function (dicomParser)
+var dicomParser = (function (dicomParser)
+{
+ "use strict";
+
+ if(dicomParser === undefined)
{
- "use strict";
+ dicomParser = {};
+ }
- if(dicomParser === undefined)
- {
- dicomParser = {};
- }
+ dicomParser.version = "1.2.0";
- dicomParser.version = "1.1.5";
-
- return dicomParser;
- }(dicomParser));
return dicomParser;
-}));
\ No newline at end of file
+}(dicomParser));
+ return dicomParser;
+}));
diff --git a/Packages/cornerstone/client/hangingProtocol.js b/Packages/cornerstone/client/hangingProtocol.js
index 591481964..6f5195c87 100644
--- a/Packages/cornerstone/client/hangingProtocol.js
+++ b/Packages/cornerstone/client/hangingProtocol.js
@@ -1,14 +1,38 @@
var hangingProtocol;
+/**
+ * This is an example of a hanging protocol
+ * It takes in a set of studies, as well as the
+ * number of rows and columns in the layout.
+ *
+ * It returns an array of objects, one for each viewport, detailing
+ * which series should be loaded in the viewport.
+ *
+ * @param inputData
+ * @returns {Array}
+ */
function defaultHangingProtocol(inputData) {
var studies = inputData.studies;
var viewportRows = inputData.viewportRows;
var viewportColumns = inputData.viewportColumns;
+ if (!studies.length) {
+ log.warn("No studies provided to Hanging Protocol");
+ return;
+ }
+
// This is the most basic hanging protocol.
var stacks = [];
studies.forEach(function(study) {
study.seriesList.forEach(function(series) {
+
+ // Ensure that the series has image data
+ // (All images have rows)
+ var anInstance = series.instances[0];
+ if (!anInstance || !anInstance.rows) {
+ return;
+ }
+
var stack = {
series: series,
study: study
diff --git a/Packages/dicomweb/lib/findIndexOfString.js b/Packages/dicomweb/lib/findIndexOfString.js
new file mode 100644
index 000000000..37eaf61f3
--- /dev/null
+++ b/Packages/dicomweb/lib/findIndexOfString.js
@@ -0,0 +1,49 @@
+/**
+ * Converts a String to a UInt8 array of character codes
+ * @param {String} str Input string
+ * @returns {Uint8Array} Uint8 Array of character codes
+ */
+function stringToUint8Array(str) {
+ var uint=new Uint8Array(str.length);
+ for(var i=0,j=str.length;i data.length) {
+ //console.log('dataOffset >> ', dataOffset);
+ return false;
+ }
+
+ for(var i = 0; i < token.length; i++) {
+ if(token[i] !== data[endIndex++]) {
+ if(endIndex > 520000) {
+ console.log('token=',uint8ArrayToString(token));
+ console.log('data=', uint8ArrayToString(data, dataOffset, endIndex-dataOffset));
+ console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(data[endIndex]), endIndex);
+ console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(token[endIndex]), endIndex);
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+
+findIndexOfString = function(data, str, offset) {
+ offset = offset || 0;
+
+ var token = stringToUint8Array(str);
+
+ for(var i=offset; i < data.length; i++) {
+ if(data[i] === token[0]) {
+ //console.log('match @', i);
+ if(checkToken(token, data, i)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+};
\ No newline at end of file
diff --git a/Packages/dicomweb/lib/uint8ArrayToString.js b/Packages/dicomweb/lib/uint8ArrayToString.js
new file mode 100644
index 000000000..35b85655d
--- /dev/null
+++ b/Packages/dicomweb/lib/uint8ArrayToString.js
@@ -0,0 +1,17 @@
+/**
+ * Converts a Uint8 array to a string
+ * @param data
+ * @param offset
+ * @param length
+ * @returns {string}
+ */
+uint8ArrayToString = function(data, offset, length) {
+ offset = offset || 0;
+ length = length || data.length - offset;
+ var str = "";
+
+ for(var i=offset; i < offset + length; i++) {
+ str += String.fromCharCode(data[i]);
+ }
+ return str;
+};
diff --git a/Packages/dicomweb/package.js b/Packages/dicomweb/package.js
index a96d9fea4..eb8b4f101 100644
--- a/Packages/dicomweb/package.js
+++ b/Packages/dicomweb/package.js
@@ -5,6 +5,9 @@ Package.describe({
});
Package.onUse(function (api) {
+ api.use('http');
+
+ // DICOMWeb API functions
api.addFiles('server/namespace.js', 'server');
api.addFiles('server/getImageFrame.js', 'server');
api.addFiles('server/getJSON.js', 'server');
@@ -12,6 +15,10 @@ Package.onUse(function (api) {
api.addFiles('server/getNumber.js', 'server');
api.addFiles('server/getString.js', 'server');
+ // Helper functions
+ api.addFiles('lib/findIndexOfString.js', 'server');
+ api.addFiles('lib/uint8ArrayToString.js', 'server');
+
api.export("DICOMWeb", 'server');
});
diff --git a/Packages/lesiontracker/compatibility/lesionTool.js b/Packages/lesiontracker/compatibility/lesionTool.js
index 8b6b3b62b..fe7e91e63 100644
--- a/Packages/lesiontracker/compatibility/lesionTool.js
+++ b/Packages/lesiontracker/compatibility/lesionTool.js
@@ -1,298 +1,408 @@
-var lineIndex = 0; //This holds drawn line index
-var cornerstoneTools = (function ($, cornerstone, cornerstoneMath, cornerstoneTools) {
+var activeLesionMeasurementData;
+var timepointID;
+var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneTools) {
"use strict";
- if(cornerstoneTools === undefined) {
+ if (cornerstoneTools === undefined) {
cornerstoneTools = {};
}
+ var configuration = {
+ getLesionLocationCallback: getLesionLocationCallback,
+ changeLesionLocationCallback: changeLesionLocationCallback,
+ };
+
+ // Define a callback to get your text annotation
+ // This could be used, e.g. to open a modal
+ function getLesionLocationCallback(measurementData, eventData, doneCallback) {
+ doneCallback(prompt('Enter your lesion location:'));
+ }
+
+ function changeLesionLocationCallback(measurementData, eventData, doneCallback) {
+ doneCallback(prompt('Change your lesion location:'));
+ }
+
var toolType = "lesion";
///////// BEGIN ACTIVE TOOL ///////
- function createNewMeasurement(mouseEventData)
- {
+
+ function addNewMeasurement(mouseEventData) {
var element = mouseEventData.element;
- var lesionCounter = "";
- // Subscribe CornerstoneMouseup event, when mouse is up, call lesionDialog
- $(element).on("CornerstoneToolsMouseUp", function (e) {
+ function doneCallback(lesionNumber) {
+ measurementData.lesionName = "Target " + lesionNumber;
+ measurementData.lesionNumber = lesionNumber;
+ measurementData.active = false;
+ cornerstone.updateImage(element);
+ }
- // Unsubscribe CornerstoneToolsMouseUp event
- $(element).off("CornerstoneToolsMouseUp");
+ var measurementData = createNewMeasurement(mouseEventData);
- // Show LesionDialog
- $(document).trigger("ShowLesionDialog", e);
+ var eventData = {
+ mouseButtonMask: mouseEventData.which,
+ };
+
+ // associate this data with this imageId so we can render it and manipulate it
+ cornerstoneTools.addToolState(element, toolType, measurementData);
+
+ // since we are dragging to another place to drop the end point, we can just activate
+ // the end point and let the moveHandle move it for us.
+ $(element).off('CornerstoneToolsMouseMove', cornerstoneTools.lesion.mouseMoveCallback);
+ $(element).off('CornerstoneToolsMouseDown', cornerstoneTools.lesion.mouseDownCallback);
+ $(element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.lesion.mouseDownActivateCallback);
+ cornerstone.updateImage(element);
+ cornerstoneTools.moveNewHandle(mouseEventData, measurementData.handles.end, function() {
+ if (cornerstoneTools.anyHandlesOutsideImage(mouseEventData, measurementData.handles)) {
+ // delete the measurement
+ cornerstoneTools.removeToolState(element, toolType, measurementData);
+ } else {
+ // Set lesionMeasurementData Session
+ var config = cornerstoneTools.lesion.getConfiguration();
+ config.getLesionLocationCallback(measurementData, mouseEventData, doneCallback);
+ }
+
+ $(element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.lesion.mouseMoveCallback);
+ $(element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.lesion.mouseDownCallback);
+ $(element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.lesion.mouseDownActivateCallback);
+ cornerstone.updateImage(element);
});
+ }
- // Set Lesion Name
- $(element).on("LesionNameSet", function(e,lesionName){
- lesionCounter = lesionName;
+ function addNewMeasurementTouch(touchEventData) {
+ var element = touchEventData.element;
+
+ function doneCallback(lesionNumber) {
+ measurementData.lesionName = "Target " + lesionNumber;
+ measurementData.lesionNumber = lesionNumber;
+ measurementData.active = false;
+ cornerstone.updateImage(element);
+ }
+
+ var measurementData = createNewMeasurement(touchEventData);
+
+ // associate this data with this imageId so we can render it and manipulate it
+ cornerstoneTools.addToolState(element, toolType, measurementData);
+
+ // since we are dragging to another place to drop the end point, we can just activate
+ // the end point and let the moveHandle move it for us.
+ $(element).off('CornerstoneToolsTouchDrag', cornerstoneTools.lesion.touchMoveHandle);
+ $(element).off('CornerstoneToolsTap', cornerstoneTools.lesion.tapCallback);
+ $(element).off('CornerstoneToolsDragStartActive', cornerstoneTools.lesion.touchDownActivateCallback);
+
+ cornerstone.updateImage(element);
+ cornerstoneTools.moveNewHandleTouch(touchEventData, measurementData.handles.end, function() {
+ if (cornerstoneTools.anyHandlesOutsideImage(touchEventData, measurementData.handles)) {
+ // delete the measurement
+ cornerstoneTools.removeToolState(element, toolType, measurementData);
+ } else {
+ // Set lesionMeasurementData Session
+ var config = cornerstoneTools.lesion.getConfiguration();
+ config.getLesionLocationCallback(measurementData, touchEventData, doneCallback);
+ }
+
+ $(element).on('CornerstoneToolsTouchDrag', cornerstoneTools.lesion.touchMoveHandle);
+ $(element).on('CornerstoneToolsTap', cornerstoneTools.lesion.tapCallback);
+ $(element).on('CornerstoneToolsDragStartActive', cornerstoneTools.lesion.touchDownActivateCallback);
+ cornerstone.updateImage(element);
});
+ }
- // Subscribe LesionToolModified and calls measurementModified function when lesion measurement is changed or updated.
- $(element).on("LesionToolModified", measurementModified);
-
- // Subscribe LesionMeasurementCreated
- $(element).trigger("LesionMeasurementCreated");
-
- // create the measurement data for this tool with the end handle activated
+ function createNewMeasurement(mouseEventData) {
+ // Create the measurement data for this tool with the end handle activated
var measurementData = {
- visible : true,
- active : true,
- handles : {
- start : {
- x : mouseEventData.currentPoints.image.x,
- y : mouseEventData.currentPoints.image.y,
+ visible: true,
+ active: true,
+ handles: {
+ start: {
+ x: mouseEventData.currentPoints.image.x,
+ y: mouseEventData.currentPoints.image.y,
highlight: true,
active: false
},
end: {
- x : mouseEventData.currentPoints.image.x,
- y : mouseEventData.currentPoints.image.y,
+ x: mouseEventData.currentPoints.image.x,
+ y: mouseEventData.currentPoints.image.y,
highlight: true,
active: true
+ },
+ textBox: {
+ x: mouseEventData.currentPoints.image.x - 50,
+ y: mouseEventData.currentPoints.image.y - 50,
+ pointNearHandle: pointNearTextBox,
+ active: false,
+ movesIndependently: true,
+ drawnIndependently: true
}
},
- index: lineIndex,
- measurementText: "",
- linkedTextCoords: {
- start : {
- x : mouseEventData.currentPoints.image.x,
- y : mouseEventData.currentPoints.image.y,
- highlight: true,
- active: false
- },
- end: {
- x : mouseEventData.currentPoints.image.x,
- y : mouseEventData.currentPoints.image.y,
- highlight: true,
- active: true
- },
- init: false
- },
- lesionName: "Target "+lesionCounter,
+ imageId: mouseEventData.image.imageId,
+ measurementText: 0,
+ lesionName: '',
isDeleted: false,
- lineNumber:"" //Indicates line number on image
+ isTarget: true,
+ uid: uuid.v4()
};
- lineIndex++;
return measurementData;
}
///////// END ACTIVE TOOL ///////
- function pointNearTool(element, data, coords)
- {
+ function pointNearTool(element, data, coords) {
var lineSegment = {
- start: cornerstone.pixelToCanvas(element, data.handles.start), end: cornerstone.pixelToCanvas(element, data.handles.end)
+ start: cornerstone.pixelToCanvas(element, data.handles.start),
+ end: cornerstone.pixelToCanvas(element, data.handles.end)
};
var distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
+
+ if (pointNearTextBox(element, data.handles.textBox, coords)) {
+ return true;
+ }
+
return (distanceToPoint < 25);
}
- function pointNearToolForText(element, data, coords) {
- var lineSegment = {
- start: cornerstone.pixelToCanvas(element, data.linkedTextCoords.start), end: cornerstone.pixelToCanvas(element, data.linkedTextCoords.end)
- };
-
- var distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
- return (distanceToPoint < 30);
-
+ function pointNearTextBox(element, handle, coords) {
+ if (!handle.boundingBox) {
+ return;
+ }
+ return cornerstoneMath.point.insideRect(coords, handle.boundingBox);
}
+ function suscribeLesionToolSelectedEvent(element) {
+ var elementEvents = $._data(element, "events");
+ var index = Object.keys(elementEvents).indexOf("LesionToolSelected");
+ if (index < 0) {
+ // Subscribe LesionToolSelected and calls measurementModified function when lesion measurement is changed or updated.
+ $(element).on("LesionToolSelected", measurementModified);
+ }
+ }
///////// BEGIN IMAGE RENDERING ///////
function onImageRendered(e, eventData) {
+ suscribeLesionToolSelectedEvent(e.currentTarget);
+
// if we have no toolData for this element, return immediately as there is nothing to do
var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
- if (toolData === undefined) {
+ if (!toolData) {
return;
}
+ updateLesions(toolData, eventData);
+ }
+
+ function updateLesions(toolData, eventData) {
// we have tool data for this element - iterate over each one and draw it
var context = eventData.canvasContext.canvas.getContext('2d');
context.setTransform(1, 0, 0, 1, 0, 0);
- var color;
- var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
- var font = cornerstoneTools.textStyle.getFont();
- var config = cornerstoneTools.length.getConfiguration();
-
for (var i = 0; i < toolData.data.length; i++) {
- context.save();
- // configurable shadow
- if (config && config.shadow) {
- context.shadowColor = '#000000';
- context.shadowOffsetX = +1;
- context.shadowOffsetY = +1;
- }
-
- var data = toolData.data[i];
- if (data.active) {
- color = cornerstoneTools.toolColors.getActiveColor();
- } else {
- color = cornerstoneTools.toolColors.getToolColor();
- }
-
- // draw the line
- var handleStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.start);
- var handleEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.end);
-
- context.beginPath();
- context.strokeStyle = color;
- context.lineWidth = lineWidth;
- context.moveTo(handleStartCanvas.x, handleStartCanvas.y);
- context.lineTo(handleEndCanvas.x, handleEndCanvas.y);
- context.stroke();
-
- // draw the handles
- cornerstoneTools.drawHandles(context, eventData, data.handles, color);
-
- //Set coordinates of text
- var linkedTextStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.start);
- var linkedTextEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.end);
- if(!data.linkedTextCoords.init){
- data.linkedTextCoords.start.x = data.handles.start.x + 50;
- data.linkedTextCoords.start.y = data.handles.start.y + 40;
- linkedTextStartCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.start);
-
- //Set end point of linkedTextCoords
- data.linkedTextCoords.end.x = data.linkedTextCoords.start.x + 50;
- data.linkedTextCoords.end.y = data.linkedTextCoords.start.y + 30;
- linkedTextEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.linkedTextCoords.end);
-
- //initialized coordinates of text
- data.linkedTextCoords.init = true;
- }
-
- //Draw linked line as dashed
- context.setLineDash([2,3]);
- context.beginPath();
- context.strokeStyle = color;
- context.lineWidth = 1 / eventData.viewport.scale;
- var mid = {
- x:(handleStartCanvas.x + handleEndCanvas.x) / 2, y:(handleStartCanvas.y + handleEndCanvas.y) / 2
- };
-
- context.moveTo(mid.x, mid.y);
- context.lineTo(linkedTextStartCanvas.x + 20, linkedTextStartCanvas.y + 20);
- context.stroke();
-
- // Draw the text
- context.fillStyle = color;
- context.font = font;
- var dx = (data.handles.start.x - data.handles.end.x) * (eventData.image.columnPixelSpacing || 1);
- var dy = (data.handles.start.y - data.handles.end.y) * (eventData.image.rowPixelSpacing || 1);
- var length = Math.sqrt(dx * dx + dy * dy);
-
- var suffix = ' mm';
- if (!eventData.image.rowPixelSpacing || !eventData.image.columnPixelSpacing) {
- suffix = ' pixels';
- }
-
- var text = '' + length.toFixed(2) + suffix;
- var textCoords = {
- x: linkedTextStartCanvas.x, y: linkedTextStartCanvas.y
- };
-
- cornerstoneTools.drawTextBox(context, data.lesionName, textCoords.x, textCoords.y, color);
- cornerstoneTools.drawTextBox(context, text, textCoords.x, textCoords.y + 20, color);
-
- //Set measurement text to show lesion table
- data.measurementText = length.toFixed(2);
- $(eventData.enabledElement.element).trigger("LesionTextChanged",data);
-
- context.restore();
+ renderLesion(toolData.data[i], context, eventData);
+ updateLesionCollection(toolData.data[i]);
}
-
}
- ///////// END IMAGE RENDERING ///////
- //This function is called from cornerstone-viewport.html and updates lesion measurement and makes the lesion active
- function measurementModified(e, eventObject){
- var start = new Date();
-
- var enabledElement = eventObject.enabledElement;
- var lineIndex = eventObject.lineIndex;
- var type = eventObject.type;
-
- // if we have no toolData for this element, return immediately as there is nothing to do
- var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
- if (toolData === undefined) {
+ function updateLesionCollection(lesionData) {
+ if (!lesionData.active) {
return;
}
- //If type is delete, remove measurement
- if(type === "delete") {
- var deletedDataIndex = -1;
-
- for (var i = 0; i < toolData.data.length; i++) {
- var data = toolData.data[i];
-
- //When click a row of table measurements, measurement will be active and color will be green
- if (data.index === eventObject.lineIndex && eventObject.type !== "active") {
- data.visible = false;
- deletedDataIndex = i;
- }
- }
-
- if (deletedDataIndex >= 0 && deletedDataIndex < toolData.data.length) {
- toolData.data.splice(deletedDataIndex, 1);
- }
+ if (lesionData.timepointID && lesionData.timepointID !== "") {
+ // Update Measurements Collection
+ measurementManagerDAL.updateTimepointData(lesionData);
} else {
- for (var i = 0; i < toolData.data.length; i++) {
- var data = toolData.data[i];
+ activeLesionMeasurementData = lesionData;
+ }
+ }
- //When click a row of table measurements, measurement will be active and color will be green
- if (data.index === eventObject.lineIndex && eventObject.type === "active") {
- data.active = true;
- } else{
- data.active = false;
- }
- }
+ function renderLesion(data, context, eventData) {
+ context.save();
+
+ var color;
+ var element = eventData.element;
+ var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
+ var font = cornerstoneTools.textStyle.getFont();
+ var config = cornerstoneTools.lesion.getConfiguration();
+
+ // configurable shadow from CornerstoneTools
+ if (config && config.shadow) {
+ context.shadowColor = '#000000';
+ context.shadowOffsetX = +1;
+ context.shadowOffsetY = +1;
}
+ if (data.active) {
+ color = cornerstoneTools.toolColors.getActiveColor();
+ } else {
+ color = cornerstoneTools.toolColors.getToolColor();
+ }
- enabledElement.image.render(enabledElement, true);
+ // draw the line
+ var handleStartCanvas = cornerstone.pixelToCanvas(element, data.handles.start);
+ var handleEndCanvas = cornerstone.pixelToCanvas(element, data.handles.end);
+ var canvasTextLocation = cornerstone.pixelToCanvas(element, data.handles.textBox);
- var context = enabledElement.canvas.getContext('2d');
+ context.beginPath();
+ context.strokeStyle = color;
+ context.lineWidth = lineWidth;
+ context.moveTo(handleStartCanvas.x, handleStartCanvas.y);
+ context.lineTo(handleEndCanvas.x, handleEndCanvas.y);
+ context.stroke();
- var end = new Date();
- var diff = end - start;
- //console.log(diff + ' ms');
+ // draw the handles
+ cornerstoneTools.drawHandles(context, eventData, data.handles, color);
- var eventData = {
- viewport : enabledElement.viewport,
- element : enabledElement.element,
- image : enabledElement.image,
- enabledElement : enabledElement,
- canvasContext: context,
- measurementText: "",
- renderTimeInMs : diff,
- lineIndex : lineIndex,
- type : type //Holds image will be deleted or active
+ //Draw linked line as dashed
+ context.setLineDash([2, 3]);
+ context.beginPath();
+ context.strokeStyle = color;
+ context.lineWidth = 1 / eventData.viewport.scale;
+ var mid = {
+ x: (handleStartCanvas.x + handleEndCanvas.x) / 2,
+ y: (handleStartCanvas.y + handleEndCanvas.y) / 2
};
- enabledElement.invalid = false;
+ context.moveTo(mid.x, mid.y);
+ context.lineTo(canvasTextLocation.x + 20, canvasTextLocation.y + 20);
+ context.stroke();
- onImageRendered(e, eventData);
+ // Draw the text
+ var dx = (data.handles.start.x - data.handles.end.x) * (eventData.image.columnPixelSpacing || 1);
+ var dy = (data.handles.start.y - data.handles.end.y) * (eventData.image.rowPixelSpacing || 1);
+ var length = Math.sqrt(dx * dx + dy * dy);
+
+ var suffix = ' mm';
+ if (!eventData.image.rowPixelSpacing || !eventData.image.columnPixelSpacing) {
+ suffix = ' pixels';
+ }
+
+ var text = '' + length.toFixed(2) + suffix;
+ var textLines = [data.lesionName, text];
+
+ var boundingBox = cornerstoneTools.drawTextBox(context,
+ textLines,
+ canvasTextLocation.x, canvasTextLocation.y, color);
+
+ data.handles.textBox.boundingBox = boundingBox;
+
+ // Set measurement text to show lesion table
+ data.measurementText = length.toFixed(1);
+
+ // Lesion Measurement is changed
+ $(element).trigger("LesionTextChanged", data);
+
+ context.restore();
+ }
+
+ ///////// END IMAGE RENDERING ///////
+
+ function updateLesion(e, eventData) {
+ var enabledElement = eventData.enabledElement;
+ var element = eventData.enabledElement.element;
+ var isTarget = eventData.lesionData.isTarget;
+ var lesionNumber = eventData.lesionData.lesionNumber;
+ var type = eventData.type;
+
+ // if we have no toolData for this element, return immediately as there is nothing to do
+ var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
+ if (!toolData) {
+ return;
+ }
+
+ var data,
+ i;
+
+ switch (type) {
+ case "delete":
+ //If type is delete, remove measurement
+ var deletedDataIndex = -1;
+
+ for (i = 0; i < toolData.data.length; i++) {
+ data = toolData.data[i];
+
+ //When click a row of table measurements, measurement will be active and color will be green
+ if (data.lesionNumber === lesionNumber && eventData.type !== "active" && isTarget) {
+ data.visible = false;
+ deletedDataIndex = i;
+ }
+ }
+
+ if (deletedDataIndex >= 0 && deletedDataIndex < toolData.data.length) {
+ toolData.data.splice(deletedDataIndex, 1);
+ }
+ break;
+
+ case "active":
+ for (i = 0; i < toolData.data.length; i++) {
+ data = toolData.data[i];
+ //When click a row of table measurements, measurement will be active and color will be green
+ if (data.lesionNumber === lesionNumber && eventData.type === "active" && isTarget) {
+ data.active = true;
+ } else {
+ data.active = false;
+ }
+ }
+ break;
+
+ case "inactive":
+ for (i = 0; i < toolData.data.length; i++) {
+ data = toolData.data[i];
+ // Make inactive all lesions for the timepoint
+ data.active = false;
+ }
+ break;
+ }
+
+ cornerstone.updateImage(element);
+ }
+
+
+ function loadImage(e, eventData) {
+ // If type is active, load image and activate lesion
+ // If type is inactive, update lesions of enabledElement as inactive
+
+ if (eventData.type === "active") {
+ var stackToolDataSource = cornerstoneTools.getToolState(e.currentTarget, 'stack');
+ var stackData = stackToolDataSource.data[0];
+ var imageIds = stackData.imageIds;
+ var imageIdIndex = imageIds.indexOf(eventData.lesionData.imageId);
+ if (imageIdIndex < 0) {
+ return;
+ }
+
+ cornerstone.loadAndCacheImage(imageIds[imageIdIndex]).then(function(image) {
+ cornerstone.displayImage(eventData.enabledElement.element, image);
+ updateLesion(e, eventData);
+ });
+ } else if (eventData.type === "inactive") {
+ updateLesion(e, eventData);
+ }
+ }
+
+ //This function is called from cornerstone-viewport.html and updates lesion measurement and makes the lesion active
+ function measurementModified(e, eventData) {
+ loadImage(e, eventData);
}
// module exports
cornerstoneTools.lesion = cornerstoneTools.mouseButtonTool({
- createNewMeasurement : createNewMeasurement,
- onImageRendered: onImageRendered,
- pointNearTool : pointNearTool,
- pointNearToolForText: pointNearToolForText,
- toolType : toolType
- });
- cornerstoneTools.lesionTouch = cornerstoneTools.touchTool({
createNewMeasurement: createNewMeasurement,
+ addNewMeasurement: addNewMeasurement,
onImageRendered: onImageRendered,
pointNearTool: pointNearTool,
- pointNearToolForText: pointNearToolForText,
toolType: toolType
});
+
+ cornerstoneTools.lesionTouch = cornerstoneTools.touchTool({
+ createNewMeasurement: createNewMeasurement,
+ addNewMeasurement: addNewMeasurementTouch,
+ onImageRendered: onImageRendered,
+ pointNearTool: pointNearTool,
+ toolType: toolType
+ });
+
+ cornerstoneTools.lesion.setConfiguration(configuration);
+
return cornerstoneTools;
+
}($, cornerstone, cornerstoneMath, cornerstoneTools));
\ No newline at end of file
diff --git a/Packages/lesiontracker/compatibility/measurementManagerDAL.js b/Packages/lesiontracker/compatibility/measurementManagerDAL.js
new file mode 100644
index 000000000..b438bbf21
--- /dev/null
+++ b/Packages/lesiontracker/compatibility/measurementManagerDAL.js
@@ -0,0 +1,148 @@
+var measurementManagerDAL = (function() {
+ PatientLocations = new Meteor.Collection(null);
+
+ function getLocationName(id) {
+ var locationObject = PatientLocations.findOne(id);
+ return locationObject.location || "";
+ }
+
+ // Add timepoint data to Measurements collection
+ function addTimepointData(lesionData) {
+ var timepoints = Timepoints.find().fetch();
+ var timepointsObject = {};
+
+ for (var i = 0; i < timepoints.length; i++) {
+ var timepointId = timepoints[i].timepointID;
+ var lesionTimepointId = lesionData.timepointID;
+
+ var timepointObject;
+ if (timepointId === lesionTimepointId) {
+ // Add real mesurement
+ timepointObject = {
+ longestDiameter: lesionData.measurementText,
+ imageId: lesionData.imageId,
+ };
+ } else {
+ // Add null measurement
+ timepointObject = {
+ longestDiameter: "",
+ imageId: ""
+ };
+ }
+ timepointsObject[timepointId] = timepointObject;
+ }
+
+ var lesionDataObject = {
+ lesionUID: uuid.v4(),
+ number: Measurements.find().count() + 1,
+ lesionNumber: lesionData.lesionNumber,
+ isTarget: lesionData.isTarget,
+ locationUID: lesionData.locationUID,
+ location: getLocationName(lesionData.locationUID),
+ timepoints: timepointsObject
+ };
+ Measurements.insert(lesionDataObject);
+ }
+
+ // Update timepoint data in Measurements collection
+ function updateTimepointData(lesionData) {
+ // Find the specific lesion to be updated
+ var measurement = Measurements.find({
+ lesionNumber: lesionData.lesionNumber,
+ isTarget: lesionData.isTarget
+ }).fetch()[0];
+
+ // If no such lesion exists, stop here
+ if (!measurement) {
+ return;
+ }
+
+ // Update this specific lesion at the given timepoint
+ var timepointID = lesionData.timepointID;
+
+ // Update timepoints from lesion data
+ var timepoints = measurement.timepoints;
+ timepoints[timepointID].longestDiameter = lesionData.measurementText;
+ timepoints[timepointID].imageId = lesionData.imageId;
+
+ Measurements.update({
+ lesionNumber: lesionData.lesionNumber,
+ isTarget: lesionData.isTarget
+ }, {
+ $set: {
+ timepoints: timepoints
+ }
+ });
+ }
+
+ // Check timepointData is found in Measurements collection
+ function hasTimepointData(lesionData) {
+ var timepointData = Measurements.findOne({
+ lesionNumber: lesionData.lesionNumber,
+ isTarget: lesionData.isTarget
+ });
+ if (timepointData) {
+ return true;
+ }
+ return false;
+ }
+
+ // Adds new timepoint item to tiemepoints array
+ function addLesionData(lesionData) {
+ if (hasTimepointData(lesionData)) {
+ // Update data
+ updateTimepointData(lesionData);
+ } else {
+ // Insert data
+ addTimepointData(lesionData);
+ }
+ }
+
+ // Returns new lesion number according to timepointID
+ function getNewLesionNumber(timepointID, isTarget) {
+ // Get all current lesion measurements
+ var measurements = Measurements.find({isTarget: isTarget}).fetch();
+
+ // If no measurements exist yet, start at 1
+ if (!measurements.length) {
+ return 1;
+ }
+
+ // If measurements exist, find the last lesion number
+ // from the given timepoint
+ var lesionNumberCounter = 0;
+ for (var i = 0; i < measurements.length; i++) {
+ var measurement = measurements[i];
+ var timepoints = measurement.timepoints;
+
+ if (timepoints[timepointID].longestDiameter === '') {
+ return measurement.lesionNumber;
+ } else {
+ lesionNumberCounter = lesionNumberCounter + 1;
+ }
+ }
+ return lesionNumberCounter + 1;
+ }
+
+ // If lesion number is added for any timepoint, returns lesion locationUID
+ function lesionNumberExists(lesionData) {
+ if (!hasTimepointData(lesionData)) {
+ return;
+ }
+
+ var measurement = Measurements.find({
+ lesionNumber: lesionData.lesionNumber,
+ isTarget: lesionData.isTarget
+ }).fetch()[0];
+
+ return measurement.locationUID;
+ }
+
+ return {
+ addLesionData: addLesionData,
+ getNewLesionNumber: getNewLesionNumber,
+ lesionNumberExists: lesionNumberExists,
+ updateTimepointData: updateTimepointData,
+ getLocationName: getLocationName
+ };
+})();
\ No newline at end of file
diff --git a/Packages/lesiontracker/compatibility/nonTargetTool.js b/Packages/lesiontracker/compatibility/nonTargetTool.js
new file mode 100644
index 000000000..b609b6a22
--- /dev/null
+++ b/Packages/lesiontracker/compatibility/nonTargetTool.js
@@ -0,0 +1,490 @@
+(function($, cornerstone, cornerstoneMath, cornerstoneTools) {
+
+ 'use strict';
+
+ var toolType = 'nonTarget';
+
+ var configuration = {
+ setLesionNumberCallback: setLesionNumberCallback,
+ getNonTargetLesionLocationCallback: getNonTargetLesionLocationCallback,
+ changeNonTargetLesionLocationCallback: changeNonTargetLesionLocationCallback,
+ drawHandles: false,
+ drawHandlesOnHover: true,
+ arrowFirst: true
+ };
+
+ // Set lesion number
+ // Get Non-Target lesions on image
+ function setLesionNumberCallback(measurementData, eventData, doneCallback) {
+ var lesionNumber = 1;
+ doneCallback(lesionNumber);
+ }
+ // Define a callback to get your text annotation
+ // This could be used, e.g. to open a modal
+ function getNonTargetLesionLocationCallback(measurementData, eventData, doneCallback) {
+ doneCallback(prompt('Enter your lesion location:'));
+ }
+
+ function changeNonTargetLesionLocationCallback(measurementData, eventData, doneCallback) {
+ doneCallback(prompt('Change your lesion location:'));
+ }
+
+ /// --- Mouse Tool --- ///
+ ///////// BEGIN ACTIVE TOOL ///////
+ function addNewMeasurement(mouseEventData) {
+
+ var element = mouseEventData.element;
+
+ function doneCallback(lesionNumber) {
+ measurementData.lesionName = "Non-Target "+lesionNumber;
+ measurementData.lesionNumber = lesionNumber;
+ measurementData.active = true;
+ cornerstone.updateImage(element);
+ }
+
+ var measurementData = createNewMeasurement(mouseEventData);
+
+ var eventData = {
+ mouseButtonMask: mouseEventData.which
+ };
+
+ // associate this data with this imageId so we can render it and manipulate it
+ cornerstoneTools.addToolState(mouseEventData.element, toolType, measurementData);
+
+ // since we are dragging to another place to drop the end point, we can just activate
+ // the end point and let the moveHandle move it for us.
+ $(mouseEventData.element).off('CornerstoneToolsMouseMove', cornerstoneTools.nonTarget.mouseMoveCallback);
+ $(mouseEventData.element).off('CornerstoneToolsMouseDown', cornerstoneTools.nonTarget.mouseDownCallback);
+ $(mouseEventData.element).off('CornerstoneToolsMouseDownActivate', cornerstoneTools.nonTarget.mouseDownActivateCallback);
+
+ var config = cornerstoneTools.nonTarget.getConfiguration();
+
+ // Set lesion number and lesion name
+ if (measurementData.lesionName === undefined) {
+ config.setLesionNumberCallback(measurementData, mouseEventData, doneCallback);
+ }
+
+ cornerstone.updateImage(element);
+
+ cornerstoneTools.moveNewHandle(mouseEventData, measurementData.handles.end, function() {
+ if (cornerstoneTools.anyHandlesOutsideImage(mouseEventData, measurementData.handles)) {
+ // delete the measurement
+ cornerstoneTools.removeToolState(mouseEventData.element, toolType, measurementData);
+ }
+
+ config.getNonTargetLesionLocationCallback(measurementData, mouseEventData, doneCallback);
+
+ $(mouseEventData.element).on('CornerstoneToolsMouseMove', eventData, cornerstoneTools.nonTarget.mouseMoveCallback);
+ $(mouseEventData.element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.nonTarget.mouseDownCallback);
+ $(mouseEventData.element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.nonTarget.mouseDownActivateCallback);
+
+ cornerstone.updateImage(mouseEventData.element);
+ });
+ }
+
+ function createNewMeasurement(mouseEventData) {
+ // create the measurement data for this tool with the end handle activated
+ var measurementData = {
+ visible: true,
+ active: true,
+ handles: {
+ start: {
+ x: mouseEventData.currentPoints.image.x,
+ y: mouseEventData.currentPoints.image.y,
+ highlight: true,
+ active: false
+ },
+ end: {
+ x: mouseEventData.currentPoints.image.x,
+ y: mouseEventData.currentPoints.image.y,
+ highlight: true,
+ active: false
+ },
+ textBox: {
+ x: mouseEventData.currentPoints.image.x - 50,
+ y: mouseEventData.currentPoints.image.y - 50,
+ pointNearHandle: pointNearTextBox,
+ active: false,
+ movesIndependently: true,
+ drawnIndependently: true
+ }
+ },
+ imageId: mouseEventData.image.imageId,
+ measurementText: '',
+ isTarget: false,
+ uid: uuid.v4()
+ };
+
+ return measurementData;
+ }
+ ///////// END ACTIVE TOOL ///////
+
+ function pointNearTool(element, data, coords) {
+ var lineSegment = {
+ start: cornerstone.pixelToCanvas(element, data.handles.start),
+ end: cornerstone.pixelToCanvas(element, data.handles.end)
+ };
+ var distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords);
+
+ if (pointNearTextBox(element, data.handles.textBox, coords)) {
+ return true;
+ }
+
+ return distanceToPoint < 25;
+ }
+
+ function pointNearTextBox(element, handle, coords) {
+ if (!handle.boundingBox) {
+ return;
+ }
+ return cornerstoneMath.point.insideRect(coords, handle.boundingBox);
+ }
+
+ function drawArrow(context, start, end, color, lineWidth) {
+ //variables to be used when creating the arrow
+ var headLength = 10;
+
+ var angle = Math.atan2(end.y - start.y, end.x - start.x);
+
+ //starting path of the arrow from the start square to the end square and drawing the stroke
+ context.beginPath();
+ context.moveTo(start.x, start.y);
+ context.lineTo(end.x, end.y);
+ context.strokeStyle = color;
+ context.lineWidth = lineWidth;
+ context.stroke();
+
+ //starting a new path from the head of the arrow to one of the sides of the point
+ context.beginPath();
+ context.moveTo(end.x, end.y);
+ context.lineTo(end.x - headLength * Math.cos(angle - Math.PI / 7), end.y - headLength * Math.sin(angle - Math.PI / 7));
+
+ //path from the side point of the arrow, to the other side point
+ context.lineTo(end.x - headLength * Math.cos(angle + Math.PI / 7), end.y - headLength * Math.sin(angle + Math.PI / 7));
+
+ //path from the side point back to the tip of the arrow, and then again to the opposite side point
+ context.lineTo(end.x, end.y);
+ context.lineTo(end.x - headLength * Math.cos(angle - Math.PI / 7), end.y - headLength * Math.sin(angle - Math.PI / 7));
+
+ //draws the paths created above
+ context.strokeStyle = color;
+ context.lineWidth = lineWidth;
+ context.stroke();
+ context.fillStyle = color;
+ context.fill();
+ }
+
+ function suscribeNonTargetToolModifiedEvent(element) {
+ var elementEvents = $._data(element, "events");
+ var index = Object.keys(elementEvents).indexOf("NonTargetToolSelected");
+ if (index < 0) {
+ // Subscribe LesionToolModified and calls measurementModified function when lesion measurement is changed or updated.
+ $(element).on("NonTargetToolSelected", measurementModified);
+ }
+
+ }
+ ///////// BEGIN IMAGE RENDERING ///////
+ function onImageRendered(e, eventData) {
+
+ suscribeNonTargetToolModifiedEvent(e.currentTarget);
+
+ // if we have no toolData for this element, return immediately as there is nothing to do
+ var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
+ if (toolData === undefined) {
+ return;
+ }
+
+ updateLesions(toolData, eventData);
+ }
+
+ function updateLesions(toolData, eventData) {
+ // we have tool data for this element - iterate over each one and draw it
+ var context = eventData.canvasContext.canvas.getContext('2d');
+ context.setTransform(1, 0, 0, 1, 0, 0);
+
+ for (var i = 0; i < toolData.data.length; i++) {
+ renderLesion(toolData.data[i], context, eventData);
+ }
+ }
+
+ function renderLesion(lesion, context, eventData) {
+ context.save();
+
+ var color;
+ var lineWidth = cornerstoneTools.toolStyle.getToolWidth();
+ var font = cornerstoneTools.textStyle.getFont();
+ var config = cornerstoneTools.nonTarget.getConfiguration();
+
+ // configurable shadow from CornerstoneTools
+ if (config && config.shadow) {
+ context.shadowColor = '#000000';
+ context.shadowOffsetX = +1;
+ context.shadowOffsetY = +1;
+ }
+
+ if (lesion.active) {
+ color = cornerstoneTools.toolColors.getActiveColor();
+ } else {
+ color = cornerstoneTools.toolColors.getToolColor();
+ }
+
+ // Draw the arrow
+ var handleStartCanvas = cornerstone.pixelToCanvas(eventData.element, lesion.handles.start);
+ var handleEndCanvas = cornerstone.pixelToCanvas(eventData.element, lesion.handles.end);
+ var canvasTextLocation = cornerstone.pixelToCanvas(eventData.element, lesion.handles.textBox);
+
+ if (config.arrowFirst) {
+ drawArrow(context, handleEndCanvas, handleStartCanvas, color, lineWidth);
+ } else {
+ drawArrow(context, handleStartCanvas, handleEndCanvas, color, lineWidth);
+ }
+
+ if (config.drawHandles) {
+ cornerstoneTools.drawHandles(context, eventData, lesion.handles, color);
+ } else if (config.drawHandlesOnHover && lesion.handles.start.active) {
+ cornerstoneTools.drawHandles(context, eventData, [ lesion.handles.start ], color);
+ } else if (config.drawHandlesOnHover && lesion.handles.end.active) {
+ cornerstoneTools.drawHandles(context, eventData, [ lesion.handles.end ], color);
+ }
+
+ //Draw linked line as dashed
+ var mid = {
+ x: (handleStartCanvas.x + handleEndCanvas.x) / 2,
+ y: (handleStartCanvas.y + handleEndCanvas.y) / 2
+ };
+ context.setLineDash([2, 3]);
+ context.beginPath();
+ context.strokeStyle = color;
+ context.lineWidth = 1 / eventData.viewport.scale;
+ var mid = {
+ x: mid.x,
+ y: mid.y
+ };
+
+ context.moveTo(mid.x, mid.y);
+ context.lineTo(canvasTextLocation.x + 20, canvasTextLocation.y + 20);
+ context.stroke();
+
+
+ // Draw the text
+ if (lesion.lesionName && lesion.lesionName !== '') {
+ context.font = font;
+ var boundingBox = cornerstoneTools.drawTextBox(context, lesion.lesionName, canvasTextLocation.x, canvasTextLocation.y, color);
+ lesion.handles.textBox.boundingBox = boundingBox;
+ }
+
+
+ context.restore();
+
+ }
+ // ---- Touch tool ----
+
+ ///////// BEGIN ACTIVE TOOL ///////
+ function addNewMeasurementTouch(touchEventData) {
+ var element = touchEventData.element;
+
+ function doneCallback(lesionNumber) {
+ measurementData.lesionName = "Non-Target "+lesionNumber;
+ measurementData.lesionNumber = lesionNumber;
+ measurementData.active = true;
+ cornerstone.updateImage(element);
+ }
+
+ var measurementData = createNewMeasurement(touchEventData);
+ cornerstoneTools.addToolState(element, toolType, measurementData);
+ $(element).off('CornerstoneToolsTouchDrag', cornerstoneTools.nonTargetTouch.touchMoveHandle);
+ $(element).off('CornerstoneToolsDragStartActive', cornerstoneTools.nonTargetTouch.touchDownActivateCallback);
+ $(element).off('CornerstoneToolsTap', cornerstoneTools.nonTargetTouch.tapCallback);
+ var config = cornerstoneTools.nonTarget.getConfiguration();
+
+ // Set lesion number and lesion name
+ if (measurementData.lesionName === undefined) {
+ config.setLesionNumberCallback(measurementData, touchEventData, doneCallback);
+ }
+ cornerstone.updateImage(element);
+
+ cornerstoneTools.moveNewHandleTouch(touchEventData, measurementData.handles.end, function() {
+ cornerstone.updateImage(element);
+
+ if (cornerstoneTools.anyHandlesOutsideImage(touchEventData, measurementData.handles)) {
+ // delete the measurement
+ cornerstoneTools.removeToolState(element, toolType, measurementData);
+ }
+
+ config.getNonTargetLesionLocationCallback(measurementData, touchEventData, doneCallback);
+
+
+ $(element).on('CornerstoneToolsTouchDrag', cornerstoneTools.nonTargetTouch.touchMoveHandle);
+ $(element).on('CornerstoneToolsDragStartActive', cornerstoneTools.nonTargetTouch.touchDownActivateCallback);
+ $(element).on('CornerstoneToolsTap', cornerstoneTools.nonTargetTouch.tapCallback);
+ });
+ }
+
+
+ function doubleClickCallback(e, eventData) {
+ var element = eventData.element;
+ var data;
+
+ function doneChangingTextCallback(data, updatedText, deleteTool) {
+ if (deleteTool === true) {
+ cornerstoneTools.removeToolState(element, toolType, data);
+ } else {
+ data.text = updatedText;
+ }
+
+ data.active = false;
+ cornerstone.updateImage(element);
+ }
+
+ if (e.data && e.data.mouseButtonMask && !cornerstoneTools.isMouseButtonEnabled(eventData.which, e.data.mouseButtonMask)) {
+ return false;
+ }
+
+ var config = cornerstoneTools.nonTarget.getConfiguration();
+
+ var coords = eventData.currentPoints.canvas;
+ var toolData = cornerstoneTools.getToolState(element, toolType);
+
+ // now check to see if there is a handle we can move
+ if (!toolData) {
+ return false;
+ }
+
+ for (var i = 0; i < toolData.data.length; i++) {
+ data = toolData.data[i];
+ if (pointNearTool(element, data, coords)) {
+ data.active = true;
+ cornerstone.updateImage(element);
+ // Allow relabelling via a callback
+ config.changeTextCallback(data, doneChangingTextCallback);
+
+ e.stopImmediatePropagation();
+ return false;
+ }
+ }
+
+ return false; // false = causes jquery to preventDefault() and stopPropagation() this event
+ }
+
+ function updateLesion(e, eventObject) {
+ var start = new Date();
+
+ var enabledElement = eventObject.enabledElement;
+ var isTarget = eventObject.lesionData.isTarget;
+ var lesionNumber = eventObject.lesionData.lesionNumber;
+ var type = eventObject.type;
+
+ // if we have no toolData for this element, return immediately as there is nothing to do
+ var toolData = cornerstoneTools.getToolState(e.currentTarget, toolType);
+ if (toolData === undefined) {
+ return;
+ }
+
+ //If type is delete, remove measurement
+ if (type === "delete") {
+ var deletedDataIndex = -1;
+
+ for (var i = 0; i < toolData.data.length; i++) {
+ var data = toolData.data[i];
+
+ //When click a row of table measurements, measurement will be active and color will be green
+ if (data.lesionNumber === eventObject.lesionNumber && eventObject.type !== "active" && !isTarget) {
+ data.visible = false;
+ deletedDataIndex = i;
+ }
+ }
+
+ if (deletedDataIndex >= 0 && deletedDataIndex < toolData.data.length) {
+ toolData.data.splice(deletedDataIndex, 1);
+ }
+ } else if (type === "active") {
+ for (var i = 0; i < toolData.data.length; i++) {
+ var data = toolData.data[i];
+ //When click a row of table measurements, measurement will be active and color will be green
+ if (data.lesionNumber === eventObject.lesionData.lesionNumber && eventObject.type === "active" && !isTarget) {
+ data.active = true;
+ } else {
+ data.active = false;
+ }
+ }
+ } else if (type === "inactive") {
+ for (var i = 0; i < toolData.data.length; i++) {
+ var data = toolData.data[i];
+ // Make inactive all lesions for the timepoint
+ data.active = false;
+ }
+ }
+
+ var context = enabledElement.canvas.getContext('2d');
+
+ var end = new Date();
+ var diff = end - start;
+ //console.log(diff + ' ms');
+
+ var eventData = {
+ viewport: enabledElement.viewport,
+ element: enabledElement.element,
+ image: enabledElement.image,
+ enabledElement: enabledElement,
+ canvasContext: context,
+ measurementText: "",
+ renderTimeInMs: diff,
+ lesionNumber: lesionNumber,
+ isTarget: isTarget,
+ type: type //Holds image will be deleted or active
+ };
+
+ enabledElement.invalid = false;
+
+ onImageRendered(e, eventData);
+ }
+
+ function loadImage(e, eventObject) {
+ // If type is active, load image and activate lesion
+ // If type is inactive, update lesions of enabledElement as inactive
+
+ if (eventObject.type === "active") {
+ var stackToolDataSource = cornerstoneTools.getToolState(e.currentTarget, 'stack');
+ var stackData = stackToolDataSource.data[0];
+ var imageIdsArr = stackData.imageIds;
+ var indexOfImage = imageIdsArr.indexOf(eventObject.lesionData.imageId);
+ if (indexOfImage > -1) {
+ cornerstone.loadAndCacheImage(stackData.imageIds[indexOfImage]).then(function(image) {
+ cornerstone.displayImage(eventObject.enabledElement.element, image);
+ updateLesion(e, eventObject);
+ });
+ }
+ } else if (eventObject.type === "inactive") {
+ updateLesion(e, eventObject);
+ }
+
+
+ }
+
+ //This function is called from cornerstone-viewport.html and updates lesion measurement and makes the lesion active
+ function measurementModified(e, eventObject) {
+ loadImage(e, eventObject);
+ }
+
+ cornerstoneTools.nonTarget = cornerstoneTools.mouseButtonTool({
+ addNewMeasurement: addNewMeasurement,
+ createNewMeasurement: createNewMeasurement,
+ onImageRendered: onImageRendered,
+ pointNearTool: pointNearTool,
+ toolType: toolType
+ //mouseDoubleClickCallback: doubleClickCallback
+ });
+
+ cornerstoneTools.nonTarget.setConfiguration(configuration);
+
+ cornerstoneTools.nonTargetTouch = cornerstoneTools.touchTool({
+ addNewMeasurement: addNewMeasurementTouch,
+ createNewMeasurement: createNewMeasurement,
+ onImageRendered: onImageRendered,
+ pointNearTool: pointNearTool,
+ toolType: toolType
+ // pressCallback: doubleClickCallback
+ });
+
+
+})($, cornerstone, cornerstoneMath, cornerstoneTools);
diff --git a/Packages/lesiontracker/components/lesionDialog/lesionDialog.html b/Packages/lesiontracker/components/lesionDialog/lesionDialog.html
deleted file mode 100644
index 369c7cd5a..000000000
--- a/Packages/lesiontracker/components/lesionDialog/lesionDialog.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
- {{> lesionLocationDialog}}
-
\ No newline at end of file
diff --git a/Packages/lesiontracker/components/lesionDialog/lesionDialog.js b/Packages/lesiontracker/components/lesionDialog/lesionDialog.js
deleted file mode 100644
index 0d16ac861..000000000
--- a/Packages/lesiontracker/components/lesionDialog/lesionDialog.js
+++ /dev/null
@@ -1,46 +0,0 @@
-Template.lesionDialog.onRendered(function () {
- // Show Lesion Dialog
- $(document).on("ShowLesionDialog", function (e, eventData) {
- var dialogPointsOnPage = eventData.currentPoints.page;
- $("#modal-dialog-container").css({
- "top": dialogPointsOnPage.y,
- "left": dialogPointsOnPage.x
- });
-
- $("#lesionDialog").modal("show");
- });
-});
-
-
-
-// Listen lesionMeaurementCreated Event and when this event is triggered, show lesionLocationDialog Box
-/*
- $(document).on("lesionMeasurementCreated",function(e,eventCaller,data){
- console.log("Lesion measurement xreated");
- var element = data.element;
- var tabId = data.tabId;
- var viewports = $("#content"+tabId).find(".imageViewerViewport");
- var viewportIndex = $(viewports).index($(element));
- data.viewportIndex = viewportIndex;
- data.totalViewportCount = viewports.length;
-
- //Set Lesion Table Data to insert into lesion table
- setLesionTableData(data);
-
- // Check popup will shown or not
- var returnVal = lesionNumberIsAdded(data);
- if(!returnVal.isFound) {
- if( !Session.get("lesionLocationSelected") ) {
- $("#modal-dialog-container").css({
- "top": eventCaller.pageY,
- "left": eventCaller.pageX
- });
-
- $("#lesionDialog").modal("show");
- }
- } else {
- // If lesionNumber is added before:
- data.rowIndex = returnVal.rowIndex;
- $(document).trigger("addLesionToExistedRow",data);
- }
- });*/
diff --git a/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.html b/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.html
index 1f25b7950..b71e636f9 100644
--- a/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.html
+++ b/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.html
@@ -1,19 +1,17 @@
-
-
-
-
-
Select Lesion Location
-
-
-
-
-
-
-
-
+
+
+
Select Lesion Location
+
+
+
+
+
diff --git a/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.js b/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.js
index 1d33b0fe0..9997b87af 100644
--- a/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.js
+++ b/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.js
@@ -1,49 +1,172 @@
-//fill selectLesionLocation element
-var lesionLocationsArray = [
- {location:"Brain Brainstem",hasDescription:false, description:""},
- {location:"Brain Cerebellum Left",hasDescription:false, description:""},
- {location:"Brain Cerebrum Left",hasDescription:false, description:""},
- {location:"Brain Cerebrum Right",hasDescription:false, description:""},
- {location:"Brain Multiple Sites",hasDescription:false, description:""}
-];
+// This event determines whether or not to show the lesion dialog
+// If there already exists a lesion with this specific lesion number,
+// related to the chosen location.
+function getLesionLocationCallback(measurementData, eventData, doneCallback) {
+ // Get the lesion location dialog
+ var lesionDialog = $("#lesionLocationDialog");
-Template.lesionLocationDialog.onRendered(function () {
+ // Find the select option box
+ var selector = lesionDialog.find("select#selectLesionLocation");
- function fillSelectLesionLocation () {
- var el = $("#selectLesionLocation");
- el.find('option:not(:first)').remove();
- $.each(lesionLocationsArray, function(key, value) {
- el.append("");
- });
+ // Get the current element's timepointID
+ // TODO: Change this when we are no longer storing timepointID in the viewport element DOM data
+ measurementData.timepointID = $(eventData.element).data('timepointID');
+
+ // Get a lesion number for this lesion, depending on whether or not the same lesion previously
+ // exists at a different timepoint
+ var lesionNumber = measurementManagerDAL.getNewLesionNumber(measurementData.timepointID, isTarget=true);
+ measurementData.lesionNumber = lesionNumber;
+
+ // Find out if this lesion number is already added in the lesion manager for another timepoint
+ // If it is, stop here because we don't need the dialog.
+ var locationUID = measurementManagerDAL.lesionNumberExists(measurementData);
+ if (locationUID) {
+ measurementData.locationUID = locationUID;
+ measurementManagerDAL.updateTimepointData(measurementData);
+ closeHandler();
+ return;
+ }
+ // If it isn't, continue to open the dialog and have the user choose a lesion location
+
+ // Attach close handler if the user clicks the close button
+ var close = lesionDialog.find("#btnCloseLesionPopup");
+ close.off('click');
+ close.on('click', function() {
+ closeHandler();
+ });
+
+ function closeHandler() {
+ // Hide the lesion dialog
+ lesionDialog.css('display', 'none');
+
+ // Fire the doneCallback with the lesion number
+ doneCallback(lesionNumber);
+
+ // Get the current value of the select option box
+ var currentValue = selector.find("option:selected").val();
+
+ // Select the first option for the next time the dialog is opened
+ selector.val(currentValue);
}
- // Fill dropdown
- fillSelectLesionLocation();
-});
+ // Attach keypress handlers so the user can close with the Enter button
+ lesionDialog.off("keypress");
+ lesionDialog.on('keypress', keyPressHandler);
-Template.lesionLocationDialog.events({
- 'click button#btnCloseLesionPopup': function (e) {
- $("#lesionDialog").modal("hide");
- },
-
- 'change select#selectLesionLocation': function (e) {
- var el = $(e.target);
- var selectedLocationIndex = el.val()
- if(selectedLocationIndex !== "-1"){
-
- var locationObj = lesionLocationsArray[selectedLocationIndex];
-
- // Trigger location selected event
- $(document).trigger("lesionLocationSelected",locationObj);
-
- // Set activeModule parameters in index.html
- $("#lesionDialog").modal("hide");
-
- // Select first option
- el.val($("#selectLesionLocation option:first").val());
-
- // Set lesion location selected session to prevent open
- Session.set("lesionLocationSelected", true);
+ // This is the keypress callback function
+ function keyPressHandler(e) {
+ // If Enter is pressed, close the dialog
+ if (e.which === 13) {
+ closeHandler();
}
}
+
+ // Show the lesion location dialog above
+ lesionDialog.css({
+ top: eventData.currentPoints.page.y - lesionDialog.outerHeight() - 40,
+ left: eventData.currentPoints.page.x - lesionDialog.outerWidth() / 2,
+ display: 'block'
+ });
+
+ // Attach a callback for the select box
+ selector.off('change');
+ selector.on('change', function(e) {
+ // Get the current value of the selector
+ var selectedOptionId = this.value;
+
+ // If the selected option is still the default (-1)
+ // then stop here
+ if (selectedOptionId < 0) {
+ return;
+ }
+
+ // Get selected location data
+ var locationObj = LesionLocations.findOne({_id: selectedOptionId});
+
+
+ var id;
+ var existingLocation = PatientLocations.findOne({location: locationObj.location});
+ if (existingLocation) {
+ id = existingLocation._id;
+ } else {
+ // Adds location data to PatientLocation and retrieve the location ID
+ id = PatientLocations.insert({location: locationObj.location});
+ }
+
+ // Link locationUID with active lesion measurementData
+ measurementData.locationUID = id;
+
+ /// Set the isTarget value to true, since this is the target-lesion dialog callback
+ measurementData.isTarget = true;
+
+ // Adds lesion data to timepoints array
+ measurementManagerDAL.addLesionData(measurementData);
+
+ // Close the dialog
+ closeHandler();
+ });
+}
+
+function changeLesionLocationCallback(measurementData, eventData, doneCallback) {
+ doneCallback(prompt('Change your lesion location:'));
+}
+
+var config = {
+ getLesionLocationCallback: getLesionLocationCallback,
+ changeLesionLocationCallback: changeLesionLocationCallback
+};
+
+cornerstoneTools.lesion.setConfiguration(config);
+
+
+LesionLocations = new Meteor.Collection(null);
+
+LesionLocations.insert({
+ location: "Brain Brainstem",
+ hasDescription: false,
+ description: ""
+});
+
+LesionLocations.insert({
+ location: "Brain Cerebellum Left",
+ hasDescription: false,
+ description: ""
+});
+
+LesionLocations.insert({
+ location: "Brain Cerebrum Left",
+ hasDescription: false,
+ description: ""
+});
+
+LesionLocations.insert({
+ location: "Brain Cerebrum Right",
+ hasDescription: false,
+ description: ""
+});
+
+LesionLocations.insert({
+ location: "Brain Multiple Sites",
+ hasDescription: false,
+ description: ""
+});
+
+var lastAddedLesionData;
+
+Template.lesionLocationDialog.onRendered(function() {
+ // Observe Measurements Collection Changes
+ Measurements.find().observe({
+ added: function(lesionData) {
+ lastAddedLesionData = lesionData;
+ },
+ changed: function(lesionData) {
+ console.log("lesionData has changed!");
+ }
+ });
+});
+
+Template.lesionLocationDialog.helpers({
+ 'lesionLocations': function() {
+ return LesionLocations.find();
+ }
});
\ No newline at end of file
diff --git a/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.styl b/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.styl
new file mode 100644
index 000000000..65a0ce31e
--- /dev/null
+++ b/Packages/lesiontracker/components/lesionLocationDialog/lesionLocationDialog.styl
@@ -0,0 +1,19 @@
+#lesionLocationDialog
+ display: none
+ position: absolute
+ z-index: 100
+ background: white
+ padding: 10px
+ border-radius: 10px
+
+#closeLesionPopup
+ outline: 0
+ border: none
+ background-color: transparent
+
+#selectLesionLocation
+ margin-top: 5px
+
+.dialogContent
+ margin-bottom: 20px
+
diff --git a/Packages/lesiontracker/components/lesionTable/lesionTable.html b/Packages/lesiontracker/components/lesionTable/lesionTable.html
index b50c19bef..1942d8881 100644
--- a/Packages/lesiontracker/components/lesionTable/lesionTable.html
+++ b/Packages/lesiontracker/components/lesionTable/lesionTable.html
@@ -1,23 +1,24 @@
+
+
\ No newline at end of file
diff --git a/Packages/lesiontracker/components/nonTargetLesionDialog/nonTargetLesionDialog.js b/Packages/lesiontracker/components/nonTargetLesionDialog/nonTargetLesionDialog.js
new file mode 100644
index 000000000..cb2d5677e
--- /dev/null
+++ b/Packages/lesiontracker/components/nonTargetLesionDialog/nonTargetLesionDialog.js
@@ -0,0 +1,231 @@
+// This event sets lesion number for new lesion
+
+function setLesionNumberCallback(measurementData, eventData, doneCallback) {
+
+ measurementData.timepointID = $(eventData.element).data('timepointID');
+ // Get a lesion number for this lesion, depending on whether or not the same lesion previously
+ // exists at a different timepoint
+ var lesionNumber = measurementManagerDAL.getNewLesionNumber(measurementData.timepointID, isTarget=false);
+ measurementData.lesionNumber = lesionNumber;
+
+ // Set lesion number
+ doneCallback(lesionNumber);
+}
+
+// This event determines whether or not to show the Non-Target lesion dialog
+// If there already exists a lesion with this specific lesion number,
+// related to the chosen location.
+
+function getNonTargetLesionLocationCallback(measurementData, eventData, doneCallback) {
+
+ // Get the non-target lesion location dialog
+ var nonTargetlesionDialog = $("#nonTargetLesionLocationDialog");
+
+ // Find the select option box
+ var selectorLocation = nonTargetlesionDialog.find("select#selectNonTargetLesionLocation");
+ var selectorResponse = nonTargetlesionDialog.find("select#selectNonTargetLesionLocationResponse");
+
+ // OK button
+ var btnOK = nonTargetlesionDialog.find("button#nonTargetLesionOK");
+
+
+ // If selector location is disabled, make it enable
+ enableLocationSelection();
+
+ // Find out if this lesion number is already added in the lesion manager for another timepoint
+ // If it is, disable selector location
+ var locationUID = measurementManagerDAL.lesionNumberExists(measurementData);
+ if (locationUID) {
+ measurementData.locationUID = locationUID;
+ // Disable the selection of a new location
+ disableLocationSelection(measurementData.locationUID);
+ }
+
+ // Disable selector location to prevent selecting a new location
+ function disableLocationSelection(locationUID) {
+ var locationName = measurementManagerDAL.getLocationName(locationUID);
+ selectorLocation.find('option').each(function()
+ {
+ if ($(this).text() === locationName) {
+ // Select location in locations dropdown list
+ selectorLocation.find('option').eq($(this).index()).prop("selected", true);
+ return;
+ }
+ });
+
+ selectorLocation.prop("disabled", true);
+ }
+
+ // Enable selector location
+ function enableLocationSelection() {
+ selectorLocation.removeAttr("disabled");
+ }
+
+ function closeHandler() {
+
+ // Hide the lesion dialog
+ nonTargetlesionDialog.css('display', 'none');
+ // Enable selector location
+ enableLocationSelection();
+
+ // Get the current value of the select option box
+ selectorLocation.find("option:first").prop("selected", "selected");
+ selectorResponse.find("option:first").prop("selected", "selected");
+
+ }
+
+ // Attach keypress handlers so the user can close with the Enter button
+ nonTargetlesionDialog.off("keypress");
+ nonTargetlesionDialog.on('keypress', keyPressHandler);
+
+ // This is the keypress callback function
+ function keyPressHandler(e) {
+ // If Enter is pressed, close the dialog
+ if (e.which === 13) {
+ closeHandler();
+ }
+ }
+
+ // Show the lesion location dialog above
+ nonTargetlesionDialog.css({
+ top: eventData.currentPoints.page.y,
+ left: eventData.currentPoints.page.x,
+ display: 'block'
+ });
+
+ // Click OK button
+ // Add lesion to lesion table
+ btnOK.off('click');
+ btnOK.on('click', function(e) {
+ // Get the current value of the selector
+ var selectedOptionId = selectorLocation.find("option:selected").val();
+ var responseOptionCode = nonTargetlesionDialog.find("select#selectNonTargetLesionLocationResponse option:selected").val();
+
+ // If the selected option is still the default (-1)
+ // then stop here
+ if (selectedOptionId < 0) {
+ return;
+ }
+
+ // If the selected response option is still the default (-1)
+ // then stop here
+ if (responseOptionCode < 0) {
+ return;
+ }
+
+ // Get selected location data
+ var locationObj = LesionLocations.findOne({_id: selectedOptionId});
+
+ var id;
+ var existingLocation = PatientLocations.findOne({location: locationObj.location});
+ if (existingLocation) {
+ id = existingLocation._id;
+ } else {
+ // Adds location data to PatientLocation and retrieve the location ID
+ id = PatientLocations.insert({location: locationObj.location});
+ }
+
+ // Link locationUID with active lesion measurementData
+ measurementData.locationUID = id;
+
+ /// Set the isTarget value to true, since this is the target-lesion dialog callback
+ measurementData.isTarget = false;
+
+ // measurementText is set from location response list
+ measurementData.measurementText = responseOptionCode;
+
+ // Adds lesion data to timepoints array
+ measurementManagerDAL.addLesionData(measurementData);
+
+ // Close the dialog
+ closeHandler();
+ });
+}
+
+function changeNonTargetLesionLocationCallback(measurementData, eventData, doneCallback) {
+ doneCallback(prompt('Change your lesion location:'));
+}
+
+var config = {
+ setLesionNumberCallback: setLesionNumberCallback,
+ getNonTargetLesionLocationCallback: getNonTargetLesionLocationCallback,
+ changeNonTargetLesionLocationCallback: changeNonTargetLesionLocationCallback
+};
+
+cornerstoneTools.nonTarget.setConfiguration(config);
+
+
+LocationResponses = new Meteor.Collection(null);
+
+LocationResponses.insert(
+ {
+ text: "Complete response",
+ code: "CR",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Progressive disease",
+ code: "PD",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Complete response",
+ code: "CR",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Stable disease",
+ code: "SD",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Non-measurable",
+ code: "NM",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Unknown",
+ code: "UN",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Not Evaluable",
+ code: "NE",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Non-CR/Non-PD",
+ code: "NN",
+ description: ""
+ }
+);
+LocationResponses.insert(
+ {
+ text: "Excluded",
+ code: "EX",
+ description: ""
+ }
+);
+
+Template.nonTargetLesionDialog.helpers({
+ 'lesionLocations': function() {
+ return LesionLocations.find();
+ },
+ 'locationResponses': function() {
+ return LocationResponses.find();
+ }
+});
\ No newline at end of file
diff --git a/Packages/lesiontracker/lib/uuid.js b/Packages/lesiontracker/lib/uuid.js
new file mode 100755
index 000000000..0b596fc3e
--- /dev/null
+++ b/Packages/lesiontracker/lib/uuid.js
@@ -0,0 +1,250 @@
+// uuid.js
+//
+// Copyright (c) 2010-2012 Robert Kieffer
+// MIT License - http://opensource.org/licenses/mit-license.php
+
+(function() {
+ var _global = this;
+
+ // Unique ID creation requires a high quality random # generator. We feature
+ // detect to determine the best RNG source, normalizing to a function that
+ // returns 128-bits of randomness, since that's what's usually required
+ var _rng;
+
+ // Allow for MSIE11 msCrypto
+ var _crypto = _global.crypto || _global.msCrypto;
+
+ // Node.js crypto-based RNG - http://nodejs.org/docs/v0.6.2/api/crypto.html
+ //
+ // Moderately fast, high quality
+ if (typeof(_global.require) == 'function') {
+ try {
+ var _rb = _global.require('crypto').randomBytes;
+ _rng = _rb && function() {return _rb(16);};
+ } catch(e) {}
+ }
+
+ if (!_rng && _crypto && _crypto.getRandomValues) {
+ // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto
+ //
+ // Moderately fast, high quality
+ var _rnds8 = new Uint8Array(16);
+ _rng = function whatwgRNG() {
+ _crypto.getRandomValues(_rnds8);
+ return _rnds8;
+ };
+ }
+
+ if (!_rng) {
+ // Math.random()-based (RNG)
+ //
+ // If all else fails, use Math.random(). It's fast, but is of unspecified
+ // quality.
+ var _rnds = new Array(16);
+ _rng = function() {
+ for (var i = 0, r; i < 16; i++) {
+ if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
+ _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
+ }
+
+ return _rnds;
+ };
+ }
+
+ // Buffer class to use
+ var BufferClass = typeof(_global.Buffer) == 'function' ? _global.Buffer : Array;
+
+ // Maps for number <-> hex string conversion
+ var _byteToHex = [];
+ var _hexToByte = {};
+ for (var i = 0; i < 256; i++) {
+ _byteToHex[i] = (i + 0x100).toString(16).substr(1);
+ _hexToByte[_byteToHex[i]] = i;
+ }
+
+ // **`parse()` - Parse a UUID into it's component bytes**
+ function parse(s, buf, offset) {
+ var i = (buf && offset) || 0, ii = 0;
+
+ buf = buf || [];
+ s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) {
+ if (ii < 16) { // Don't overflow!
+ buf[i + ii++] = _hexToByte[oct];
+ }
+ });
+
+ // Zero out remaining bytes if string was short
+ while (ii < 16) {
+ buf[i + ii++] = 0;
+ }
+
+ return buf;
+ }
+
+ // **`unparse()` - Convert UUID byte array (ala parse()) into a string**
+ function unparse(buf, offset) {
+ var i = offset || 0, bth = _byteToHex;
+ return bth[buf[i++]] + bth[buf[i++]] +
+ bth[buf[i++]] + bth[buf[i++]] + '-' +
+ bth[buf[i++]] + bth[buf[i++]] + '-' +
+ bth[buf[i++]] + bth[buf[i++]] + '-' +
+ bth[buf[i++]] + bth[buf[i++]] + '-' +
+ bth[buf[i++]] + bth[buf[i++]] +
+ bth[buf[i++]] + bth[buf[i++]] +
+ bth[buf[i++]] + bth[buf[i++]];
+ }
+
+ // **`v1()` - Generate time-based UUID**
+ //
+ // Inspired by https://github.com/LiosK/UUID.js
+ // and http://docs.python.org/library/uuid.html
+
+ // random #'s we need to init node and clockseq
+ var _seedBytes = _rng();
+
+ // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
+ var _nodeId = [
+ _seedBytes[0] | 0x01,
+ _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5]
+ ];
+
+ // Per 4.2.2, randomize (14 bit) clockseq
+ var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff;
+
+ // Previous uuid creation time
+ var _lastMSecs = 0, _lastNSecs = 0;
+
+ // See https://github.com/broofa/node-uuid for API details
+ function v1(options, buf, offset) {
+ var i = buf && offset || 0;
+ var b = buf || [];
+
+ options = options || {};
+
+ var clockseq = options.clockseq != null ? options.clockseq : _clockseq;
+
+ // UUID timestamps are 100 nano-second units since the Gregorian epoch,
+ // (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
+ // time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
+ // (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
+ var msecs = options.msecs != null ? options.msecs : new Date().getTime();
+
+ // Per 4.2.1.2, use count of uuid's generated during the current clock
+ // cycle to simulate higher resolution clock
+ var nsecs = options.nsecs != null ? options.nsecs : _lastNSecs + 1;
+
+ // Time since last uuid creation (in msecs)
+ var dt = (msecs - _lastMSecs) + (nsecs - _lastNSecs)/10000;
+
+ // Per 4.2.1.2, Bump clockseq on clock regression
+ if (dt < 0 && options.clockseq == null) {
+ clockseq = clockseq + 1 & 0x3fff;
+ }
+
+ // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
+ // time interval
+ if ((dt < 0 || msecs > _lastMSecs) && options.nsecs == null) {
+ nsecs = 0;
+ }
+
+ // Per 4.2.1.2 Throw error if too many uuids are requested
+ if (nsecs >= 10000) {
+ throw new Error('uuid.v1(): Can\'t create more than 10M uuids/sec');
+ }
+
+ _lastMSecs = msecs;
+ _lastNSecs = nsecs;
+ _clockseq = clockseq;
+
+ // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
+ msecs += 12219292800000;
+
+ // `time_low`
+ var tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
+ b[i++] = tl >>> 24 & 0xff;
+ b[i++] = tl >>> 16 & 0xff;
+ b[i++] = tl >>> 8 & 0xff;
+ b[i++] = tl & 0xff;
+
+ // `time_mid`
+ var tmh = (msecs / 0x100000000 * 10000) & 0xfffffff;
+ b[i++] = tmh >>> 8 & 0xff;
+ b[i++] = tmh & 0xff;
+
+ // `time_high_and_version`
+ b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
+ b[i++] = tmh >>> 16 & 0xff;
+
+ // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
+ b[i++] = clockseq >>> 8 | 0x80;
+
+ // `clock_seq_low`
+ b[i++] = clockseq & 0xff;
+
+ // `node`
+ var node = options.node || _nodeId;
+ for (var n = 0; n < 6; n++) {
+ b[i + n] = node[n];
+ }
+
+ return buf ? buf : unparse(b);
+ }
+
+ // **`v4()` - Generate random UUID**
+
+ // See https://github.com/broofa/node-uuid for API details
+ function v4(options, buf, offset) {
+ // Deprecated - 'format' argument, as supported in v1.2
+ var i = buf && offset || 0;
+
+ if (typeof(options) == 'string') {
+ buf = options == 'binary' ? new BufferClass(16) : null;
+ options = null;
+ }
+ options = options || {};
+
+ var rnds = options.random || (options.rng || _rng)();
+
+ // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
+ rnds[6] = (rnds[6] & 0x0f) | 0x40;
+ rnds[8] = (rnds[8] & 0x3f) | 0x80;
+
+ // Copy bytes to buffer, if provided
+ if (buf) {
+ for (var ii = 0; ii < 16; ii++) {
+ buf[i + ii] = rnds[ii];
+ }
+ }
+
+ return buf || unparse(rnds);
+ }
+
+ // Export public API
+ var uuid = v4;
+ uuid.v1 = v1;
+ uuid.v4 = v4;
+ uuid.parse = parse;
+ uuid.unparse = unparse;
+ uuid.BufferClass = BufferClass;
+
+ if (typeof(module) != 'undefined' && module.exports) {
+ // Publish as node.js module
+ module.exports = uuid;
+ } else if (typeof define === 'function' && define.amd) {
+ // Publish as AMD module
+ define(function() {return uuid;});
+
+
+ } else {
+ // Publish as global (in browsers)
+ var _previousRoot = _global.uuid;
+
+ // **`noConflict()` - (browser only) to reset global 'uuid' var**
+ uuid.noConflict = function() {
+ _global.uuid = _previousRoot;
+ return uuid;
+ };
+
+ _global.uuid = uuid;
+ }
+}).call(this);
diff --git a/Packages/lesiontracker/package.js b/Packages/lesiontracker/package.js
index c4ed6f673..7dbac85e1 100644
--- a/Packages/lesiontracker/package.js
+++ b/Packages/lesiontracker/package.js
@@ -15,12 +15,12 @@ Package.onUse(function (api) {
api.use('cornerstone');
api.addFiles('compatibility/lesionTool.js', 'client', {bare: true});
-
- api.addFiles('components/lesionDialog/lesionDialog.html', 'client');
- api.addFiles('components/lesionDialog/lesionDialog.js', 'client');
+ api.addFiles('compatibility/nonTargetTool.js', 'client', {bare: true});
+ api.addFiles('compatibility/measurementManagerDAL.js', 'client', {bare: true});
api.addFiles('components/lesionLocationDialog/lesionLocationDialog.html', 'client');
api.addFiles('components/lesionLocationDialog/lesionLocationDialog.js', 'client');
+ api.addFiles('components/lesionLocationDialog/lesionLocationDialog.styl', 'client');
api.addFiles('components/lesionTable/lesionTable.html', 'client');
api.addFiles('components/lesionTable/lesionTable.styl', 'client');
@@ -30,6 +30,20 @@ Package.onUse(function (api) {
api.addFiles('components/lesionTableRow/lesionTableRow.js', 'client');
api.addFiles('components/lesionTableTimepointCell/lesionTableTimepointCell.html', 'client');
+ api.addFiles('components/lesionTableTimepointCell/lesionTableTimepointCell.js', 'client');
api.addFiles('components/lesionTableTimepointHeader/lesionTableTimepointHeader.html', 'client');
+ api.addFiles('components/lesionTableTimepointHeader/lesionTableTimepointHeader.js', 'client');
+
+ api.addFiles('components/nonTargetLesionDialog/nonTargetLesionDialog.html', 'client');
+ api.addFiles('components/nonTargetLesionDialog/nonTargetLesionDialog.css', 'client');
+ api.addFiles('components/nonTargetLesionDialog/nonTargetLesionDialog.js', 'client');
+
+
+ // Library functions
+ api.addFiles('lib/uuid.js', 'client');
+
+ api.export('Measurements', 'client');
+ api.export('Timepoints', 'client');
+
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.html b/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.html
index 30dc3777a..f39e4fd27 100644
--- a/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.html
+++ b/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.html
@@ -1,5 +1,12 @@
- {{> studyBrowser}}
+
+
+
+
+ {{> studyBrowser}}
+
\ No newline at end of file
diff --git a/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.js b/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.js
new file mode 100644
index 000000000..36e5c8a2b
--- /dev/null
+++ b/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.js
@@ -0,0 +1,45 @@
+Template.hidingPanel.events({
+ 'click button#btnCollapse': function(e,template) {
+
+ // Check hidingPanel is open or not
+ var hidingPanelOpen = template.hidingPanelOpen.get();
+ hidingPanelOpen = !hidingPanelOpen;
+ template.hidingPanelOpen.set(hidingPanelOpen);
+
+ // Get hidingPanel width from hidingPanelWidth reactiveVar
+ var contentId = this.contentId;
+ var hidingPanelElement = $("#"+contentId).find(".hidingPanel");
+ var hidingPanelWidth = template.hidingPanelWidth.get();
+ if(hidingPanelWidth == 0) {
+ hidingPanelWidth = $(hidingPanelElement).width();
+ template.hidingPanelWidth.set(parseInt(hidingPanelWidth));
+ }
+
+ $(hidingPanelElement).toggleClass("hidingPanelCollapse");
+
+ var studyBrowserContainerElement = $("#"+contentId).find(".studyBrowserContainer");
+ $(studyBrowserContainerElement).toggleClass("studyBrowserContainerCollapse");
+
+ var btnCollapseIconElement = $("#"+contentId).find("#btnCollapseIcon");
+ $(btnCollapseIconElement).toggleClass("btnCollapseIcon-collapse");
+
+ // Set viewportAndLesionTable width according to hiding panel status
+ if (hidingPanelOpen) {
+ var viewportAndLesionTableElement = $("#"+contentId).find("#viewportAndLesionTable");
+ var viewportAndLesionTableElementWidth = $(viewportAndLesionTableElement).width();
+ $(viewportAndLesionTableElement).width(viewportAndLesionTableElementWidth - hidingPanelWidth +"px");
+
+ } else {
+ var viewportAndLesionTableElement = $("#"+contentId).find("#viewportAndLesionTable");
+ var viewportAndLesionTableElementWidth = $(viewportAndLesionTableElement).width();
+ $(viewportAndLesionTableElement).width(viewportAndLesionTableElementWidth + hidingPanelWidth +"px");
+ }
+
+
+ }
+});
+
+Template.hidingPanel.onCreated(function() {
+ this.hidingPanelOpen = new ReactiveVar(true);
+ this.hidingPanelWidth = new ReactiveVar(0);
+});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.styl b/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.styl
index 8f0c0ce70..afdbe7f55 100644
--- a/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.styl
+++ b/Packages/viewerbase/components/basic/hidingPanel/hidingPanel.styl
@@ -1,6 +1,70 @@
-.hidingPanel
- width: 120px
- height: 100%
- padding-top: 35px
- position: relative
- float: left
\ No newline at end of file
+
+.hidingPanel,.hidingPanelCollapse, .toggleBtnContainer, .studyBrowserContainer, .studyBrowserContainerCollapse, #btnCollapse,.btnCollapseIcon, .btnCollapseIcon-collapse, .viewportStyle {
+
+ -o-transition: all 0.3s ease-out;
+ -ms-transition: all 0.3s ease-out;
+ -moz-transition: all 0.3s ease-out;
+ -webkit-transition: all 0.3s ease-out;
+ transition: all 0.3s ease-out;
+}
+
+.hidingPanel {
+ width: 120px;
+ height: 100%;
+ position: relative;
+ float: left;
+}
+
+.hidingPanelCollapse{
+ width: 0px;
+}
+
+.toggleBtnContainer{
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 25px;
+ height: 25px;
+}
+
+.studyBrowserContainer {
+ position: absolute;
+ display: block;
+ margin-top: 30px;
+ height: calc(100% - 30px);
+ width: 100%;
+ opacity: 1;
+}
+
+.studyBrowserContainerCollapse {
+ opacity: 0;
+ width: 0px;
+
+}
+.studyBrowserContainerCollapseDisplay {
+ display: none;
+}
+
+#btnCollapse{
+ position: absolute;
+ display: block;
+ width: 25px;
+ height: 25px;
+ line-height: 25px;
+ padding: 0;
+ border: 0;
+ border-top: 1px solid #e6e6e6;
+ background: #f6f6f6;
+ color: #666;
+ font-size: 0.875rem;
+ text-align: center;
+ cursor: pointer;
+}
+
+.btnCollapseIcon-collapse{
+ -moz-transform: rotate(180deg);
+ -webkit-transform: rotate(180deg);
+ transform: rotate(180deg);
+}
+
+
diff --git a/Packages/viewerbase/components/basic/loadingText/loadingText.html b/Packages/viewerbase/components/basic/loadingText/loadingText.html
new file mode 100644
index 000000000..73237dfdd
--- /dev/null
+++ b/Packages/viewerbase/components/basic/loadingText/loadingText.html
@@ -0,0 +1,5 @@
+
+
+
Loading...
+
+
\ No newline at end of file
diff --git a/Packages/viewerbase/components/basic/loadingText/loadingText.styl b/Packages/viewerbase/components/basic/loadingText/loadingText.styl
new file mode 100644
index 000000000..62728d08b
--- /dev/null
+++ b/Packages/viewerbase/components/basic/loadingText/loadingText.styl
@@ -0,0 +1,5 @@
+.loadingTextDiv
+ margin: 30px
+
+ h5
+ text-align: center
\ No newline at end of file
diff --git a/Packages/viewerbase/components/basic/removableBackdrop/removableBackdrop.html b/Packages/viewerbase/components/basic/removableBackdrop/removableBackdrop.html
new file mode 100644
index 000000000..4a02a21f0
--- /dev/null
+++ b/Packages/viewerbase/components/basic/removableBackdrop/removableBackdrop.html
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/Packages/viewerbase/components/basic/removableBackdrop/removableBackdrop.styl b/Packages/viewerbase/components/basic/removableBackdrop/removableBackdrop.styl
new file mode 100644
index 000000000..06a5d3a7b
--- /dev/null
+++ b/Packages/viewerbase/components/basic/removableBackdrop/removableBackdrop.styl
@@ -0,0 +1,9 @@
+.removableBackdrop
+ position: fixed
+ top: 0
+ right: 0
+ bottom: 0
+ left: 0
+ z-index: 50
+ background-color: transparent
+
\ No newline at end of file
diff --git a/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.html b/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.html
index 7e2a05d2b..ae4044fb8 100644
--- a/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.html
+++ b/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.html
@@ -1,4 +1,10 @@
+
+
Loading {{percentComplete}}
+
+
+
Error
+
\ No newline at end of file
diff --git a/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.js b/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.js
index b0fc8b0ba..2a8e175e9 100644
--- a/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.js
+++ b/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.js
@@ -1,11 +1,33 @@
Template.imageThumbnail.onRendered(function() {
- var instance = this.data.instances[0];
- var element = this.find('.imageThumbnail');
- cornerstone.enable(element);
+ var instance = this.data.stack.instances[0];
+ var element = this.find('.imageThumbnail');
+ cornerstone.enable(element);
- var imageId = getImageId(instance);
+ var imageId = getImageId(instance);
- cornerstone.loadAndCacheImage(imageId).then(function(image) {
- cornerstone.displayImage(element, image);
- });
+ var elem = $(element);
+ elem.find('.imageThumbnailLoadingIndicator').css('display', 'block');
+
+ var thumbnailIndex = $('.imageThumbnail').index(element);
+ ThumbnailLoading[thumbnailIndex] = imageId;
+
+ this.data.thumbnailIndex = thumbnailIndex;
+
+ cornerstone.loadAndCacheImage(imageId).then(function(image) {
+ cornerstone.displayImage(element, image);
+
+ delete ThumbnailLoading[thumbnailIndex];
+ elem.find('.imageThumbnailLoadingIndicator').css('display', 'none');
+ }, function(error) {
+ elem.find('.imageThumbnailErrorLoadingIndicator').css('display', 'block');
+ });
+});
+
+Template.imageThumbnail.helpers({
+ 'percentComplete': function() {
+ var percentComplete = Session.get('CornerstoneThumbnailLoadProgress' + this.thumbnailIndex);
+ if (percentComplete && percentComplete !== 100) {
+ return percentComplete + '%';
+ }
+ }
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.styl b/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.styl
index 342eeeef9..cf1750626 100644
--- a/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.styl
+++ b/Packages/viewerbase/components/studyBrowser/imageThumbnail/imageThumbnail.styl
@@ -2,4 +2,22 @@
width: 90px
height: 90px
background-color: black
- margin: 0 auto
\ No newline at end of file
+ margin: 0 auto
+ position: relative
+
+.thumbnailLoadingIndicator
+ display: none
+ pointer-events: none
+ color: #f8a13f
+ height: 20px
+ width: 100%
+ top: 0
+ left: 0
+ right: 0
+ bottom: 0
+ margin: auto
+ position: absolute
+
+ p
+ text-align: center
+ font-size: 10pt
\ No newline at end of file
diff --git a/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.js b/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.js
index 512dd6076..6fc907987 100644
--- a/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.js
+++ b/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.js
@@ -1,5 +1,14 @@
Template.studyBrowser.helpers({
studies : function() {
- return this.studies;
+ var studies = Session.get('studies');
+
+ var array = [];
+ studies.forEach(function(study, index) {
+ array.push({
+ studyIndex: index,
+ study: study
+ });
+ });
+ return array;
}
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.styl b/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.styl
index 1135a00fa..aec804342 100644
--- a/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.styl
+++ b/Packages/viewerbase/components/studyBrowser/studyBrowser/studyBrowser.styl
@@ -6,4 +6,4 @@
overflow-y: auto
overflow-x: hidden
background-color: black
- padding-bottom: 20px
\ No newline at end of file
+ padding-bottom: 20px
diff --git a/Packages/viewerbase/components/studyBrowser/thumbnailEntry/thumbnailEntry.js b/Packages/viewerbase/components/studyBrowser/thumbnailEntry/thumbnailEntry.js
index d2320e174..52927b6ad 100644
--- a/Packages/viewerbase/components/studyBrowser/thumbnailEntry/thumbnailEntry.js
+++ b/Packages/viewerbase/components/studyBrowser/thumbnailEntry/thumbnailEntry.js
@@ -26,13 +26,8 @@ function thumbnailDragStartHandler(e, data) {
var imageThumbnail = $(targetThumbnail);
// Store this data for use during drag and drop
- OHIF.viewer.dragAndDropData = {
- studyInstanceUid: imageThumbnail.data('studyInstanceUid'),
- seriesInstanceUid: imageThumbnail.data('seriesInstanceUid')
- };
+ OHIF.viewer.dragAndDropData = data;
- $.extend(OHIF.viewer.dragAndDropData, data);
-
// Clone the image thumbnail
var targetId = targetThumbnail.id + 'DragClone';
var clone = cloneElement(targetThumbnail, targetId);
@@ -204,6 +199,8 @@ Template.thumbnailEntry.events({
// Touch drag/drop events
'touchstart .thumbnailEntry, mousedown .thumbnailEntry': function(e) {
var data = Template.parentData(2);
+ data.studyInstanceUid = this.stack.studyInstanceUid;
+ data.seriesInstanceUid = this.stack.seriesInstanceUid;
thumbnailDragStartHandler(e, data);
},
'touchmove .thumbnailEntry': function(e) {
@@ -216,9 +213,6 @@ Template.thumbnailEntry.events({
Template.thumbnailEntry.helpers({
seriesDescription : function() {
- if (!this.seriesDescription) {
- return '';
- }
- return this.seriesDescription;
+ return this.stack.seriesDescription || '';
}
});
diff --git a/Packages/viewerbase/components/studyBrowser/thumbnails/thumbnails.js b/Packages/viewerbase/components/studyBrowser/thumbnails/thumbnails.js
index a3849adee..dc9150b6f 100644
--- a/Packages/viewerbase/components/studyBrowser/thumbnails/thumbnails.js
+++ b/Packages/viewerbase/components/studyBrowser/thumbnails/thumbnails.js
@@ -1,6 +1,15 @@
Template.thumbnails.helpers({
- thumbnails: function() {
- var stacks = createStacks(this);
- return stacks;
- }
+ thumbnails: function() {
+ var stacks = createStacks(this.study);
+ var studyIndex = this.studyIndex;
+
+ var array = [];
+ stacks.forEach(function(stack, index) {
+ array.push({
+ thumbnailIndex: index * (studyIndex + 1),
+ stack: stack
+ });
+ });
+ return array;
+ }
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js
index fd722f35f..23826a2e8 100644
--- a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js
+++ b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js
@@ -1,13 +1,11 @@
/**
* This function enables stack prefetching for a specified element (viewport)
* It first disables any prefetching currently occurring on any other viewports.
- * Prefetching will not be enabled if the series is a clip.
*
- * @param element
+ * @param element {node} DOM Node representing the viewport element
*/
-function enablePrefetchOnElement(viewportIndex) {
- var element = $('.imageViewerViewport').get(viewportIndex);
- //console.log('Enabling prefetch on new element');
+function enablePrefetchOnElement(element) {
+ log.info("imageViewerViewport enablePrefetchOnElement");
// Loop through all viewports and disable stackPrefetch
$('.imageViewerViewport').each(function() {
@@ -24,10 +22,21 @@ function enablePrefetchOnElement(viewportIndex) {
}
}
-function displayReferenceLines(viewportIndex) {
- var element = $('.imageViewerViewport').get(viewportIndex);
+/**
+ * This function disables reference lines for a specific viewport element.
+ * It also enables reference lines for all other viewports with the
+ * class .imageViewerViewport.
+ *
+ * @param element {node} DOM Node representing the viewport element
+ */
+function displayReferenceLines(element) {
+ log.info("imageViewerViewport displayReferenceLines");
- $('.imageViewerViewport').each(function(index, element) {
+ // Disable reference lines for the current element
+ cornerstoneTools.referenceLines.tool.disable(element);
+
+ // Loop through all other viewport elements and enable reference lines
+ $('.imageViewerViewport').not(element).each(function(index, element) {
var imageId;
try {
var enabledElement = cornerstone.getEnabledElement(element);
@@ -40,193 +49,363 @@ function displayReferenceLines(viewportIndex) {
return;
}
- if (index === viewportIndex) {
- cornerstoneTools.referenceLines.tool.disable(element);
- return;
- }
cornerstoneTools.referenceLines.tool.enable(element, OHIF.viewer.updateImageSynchronizer);
});
}
+
+var allCornerstoneEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
+ 'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' +
+ 'CornerstoneToolsMouseWheel CornerstoneToolsTap CornerstoneToolsTouchPress ' +
+ 'CornerstoneToolsTouchStart CornerstoneToolsTouchStartActive ' +
+ 'CornerstoneToolsMultiTouchDragStart';
+
+/**
+ * This function loads a study series into a viewport element.
+ *
+ * @param data {object} Object containing the study, series, and viewport element to be used
+ */
function loadSeriesIntoViewport(data) {
- if (!data.series || !data.element) {
+ log.info("imageViewerViewport loadSeriesIntoViewport");
+
+ // Make sure we have all the data required to render the series
+ if (!data.study || !data.series || !data.element) {
return;
}
- var contentId = $("#viewer").parents('.tab-pane.active').attr('id');
-
- var study = data.study;
- var series = data.series;
+ // Get the current element and it's index in the list of all viewports
+ // The viewport index is often used to store information about a viewport element
var element = data.element;
var viewportIndex = $(".imageViewerViewport").index(element);
- var allEvents = 'CornerstoneToolsMouseDown CornerstoneToolsMouseDownActivate ' +
- 'CornerstoneToolsMouseClick CornerstoneToolsMouseDrag CornerstoneToolsMouseUp ' +
- 'CornerstoneToolsMouseWheel CornerstoneToolsTap CornerstoneToolsTouchPress ' +
- 'CornerstoneToolsTouchStart CornerstoneToolsTouchStartActive ' +
- 'CornerstoneToolsDragStartActive CornerstoneToolsMultiTouchDragStart';
+ // Get the contentID of the current worklist tab, if the viewport is running
+ // alongside the worklist package
+ var contentId = Session.get('activeContentId');
+
+ // If the viewer is inside a tab, create an object related to the specified viewport
+ // This data will be saved so that the tab can be reloaded to the same state after tabs
+ // are switched
+ if (contentId) {
+ ViewerData[contentId].loadedSeriesData[viewportIndex] = {};
+ }
+ // Create an empty array to populate with image IDs
var imageIds = [];
+
+ // Loop through the current series and add metadata to the
+ // Cornerstone meta data provider. This will be used to fill information
+ // into the viewport overlays, and to calculate reference lines and orientation markers
+ var series = data.series;
var numImages = series.instances.length;
series.instances.forEach(function(instance, imageIndex) {
var imageId = getImageId(instance);
imageIds.push(imageId);
- var data = {
+ var metaData = {
instance: instance,
series: series,
- study: study,
+ study: data.study,
numImages: numImages,
imageIndex: imageIndex + 1
};
- addMetaData(imageId, data);
+
+ addMetaData(imageId, metaData);
});
+ // Define the current image stack using the newly created image IDs
var stack = {
currentImageIdIndex: data.currentImageIdIndex || 0,
imageIds: imageIds
};
+ // Get the current image ID for the stack that will be rendered
var imageId = imageIds[stack.currentImageIdIndex];
+ // Save the current image ID inside the template data so it can be
+ // retrieved from the template helpers
var templateData = Template.currentData();
templateData.imageId = imageId;
+ // Save the current image ID inside the ViewportLoading object.
+ //
+ // The ViewportLoading object relates the viewport elements with whichever
+ // image is currently being loaded into them. This is useful so that we can
+ // place progress (download %) for each image inside the proper viewports.
+ ViewportLoading[viewportIndex] = imageId;
+
+ // Enable Cornerstone for the viewport element
+ //
// NOTE: This uses the experimental WebGL renderer for Cornerstone!
- cornerstone.enable(element, cornerstone.webGL.renderer.render);
// If you have problems, replace it with this line instead:
// cornerstone.enable(element);
+ cornerstone.enable(element, cornerstone.webGL.renderer.render);
- var startLoadingHandler = cornerstoneTools.loadHandlerManager.getStartLoadHandler();
+ // Get the handler functions that will run when loading has finished or thrown
+ // an error. These are used to show/hide loading / error text boxes on each viewport.
var endLoadingHandler = cornerstoneTools.loadHandlerManager.getEndLoadHandler();
var errorLoadingHandler = cornerstoneTools.loadHandlerManager.getErrorLoadingHandler();
- if (startLoadingHandler) {
- startLoadingHandler(element);
- }
-
+ // Start loading the image.
cornerstone.loadAndCacheImage(imageId).then(function(image) {
+ // Fire an event to give notice that a new series was loaded in the viewport
+ var eventType = "ViewerBaseSeriesLoaded";
+ var eventData = {
+ element: element,
+ stack: stack,
+ study: data.study,
+ series: data.series
+ };
+ $(document).trigger(eventType, eventData);
+
+ // If there is a saved object containing Cornerstone viewport data
+ // (e.g. scale, invert, window settings) in the input data, apply it now.
+ //
+ // Otherwise, display the loaded image in the viewport element with the
+ // default viewport settings.
if (data.viewport) {
cornerstone.displayImage(element, image, data.viewport);
} else {
cornerstone.displayImage(element, image);
}
-
- if (endLoadingHandler) {
- endLoadingHandler(element);
- }
+ // Remove the data for this viewport from the ViewportLoading object
+ // This will stop the loading percentage complete from being displayed.
+ delete ViewportLoading[viewportIndex];
+
+ // Call the handler function that represents the end of the image loading phase
+ // (e.g. hide the progress text box)
+ endLoadingHandler(element);
+
+ // Resize the canvas to fit the current viewport element size. Fit the displayed
+ // image to the canvas dimensions.
+ cornerstone.resize(element, true);
+
+ // Remove the 'empty' class from the viewport to hide any instruction text
element.classList.remove('empty');
+
+ // Hide the viewport instructions (i.e. 'Drag a stack here') and show
+ // the viewport overlay data.
$(element).siblings('.viewportInstructions').hide();
$(element).siblings('.imageViewerViewportOverlay').show();
- var imagePlane = cornerstoneTools.metaData.get('imagePlane', image.imageId);
-
+ // Add stack state managers for the stack tool, CINE tool, and reference lines
cornerstoneTools.addStackStateManager(element, [ 'stack', 'playClip', 'referenceLines' ]);
- // Enable orientation markers, if applicable
+ // Get the current viewport settings
var viewport = cornerstone.getViewport(element);
- updateOrientationMarkers(element, viewport);
+
+ // Enable orientation markers, if applicable
+ updateOrientationMarkers(element);
// Clear any old stack data
cornerstoneTools.clearToolState(element, 'stack');
cornerstoneTools.addToolState(element, 'stack', stack);
- // Enable mouse input
+ // Enable mouse, mouseWheel, and touch input on the element
cornerstoneTools.mouseInput.enable(element);
cornerstoneTools.touchInput.enable(element);
cornerstoneTools.mouseWheelInput.enable(element);
+ // Use the tool manager to enable the currently active tool for this
+ // newly rendered element
var activeTool = toolManager.getActiveTool();
toolManager.setActiveTool(activeTool);
- cornerstoneTools.magnify.enable(element);
-
+ // Define a function to run whenever the Cornerstone viewport is rendered
+ // (e.g. following a change of window or zoom)
function onImageRendered(e, eventData) {
+ log.info('imageViewerViewport onImageRendered');
+
+ // Use Session to trigger reactive updates in the viewportOverlay helper functions
+ // This lets the viewport overlay always display correct window / zoom values
Session.set('CornerstoneImageRendered' + viewportIndex, Random.id());
+
+ // Save the current viewport into the ViewerData global variable, as well as the
+ // Meteor Session. This lets the viewport be saved/reloaded on a hot-code reload
var viewport = cornerstone.getViewport(element);
- ViewerData[contentId].viewer.imageViewerLoadedSeriesDictionary[viewportIndex].viewport = viewport;
+ ViewerData[contentId].loadedSeriesData[viewportIndex].viewport = viewport;
Session.set('ViewerData', ViewerData);
}
+ // Attach the onImageRendered callback to the CornerstoneImageRendered event
$(element).off('CornerstoneImageRendered', onImageRendered);
$(element).on('CornerstoneImageRendered', onImageRendered);
+
+ // Set a random value for the Session variable in order to trigger an overlay update
Session.set('CornerstoneImageRendered' + viewportIndex, Random.id());
+ // Define a function to run whenever the Cornerstone viewport changes images
+ // (e.g. during scrolling)
function onNewImage(e, eventData) {
+ log.info('imageViewerViewport onNewImage');
+
// Update the templateData with the new imageId
// This allows the template helpers to update reactively
templateData.imageId = eventData.enabledElement.image.imageId;
Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
+ // If this viewport is displaying a stack of images, save the current image
+ // index in the stack to the global ViewerData object, as well as the Meteor Session.
var stack = cornerstoneTools.getToolState(element, 'stack');
if (stack && stack.data.length && stack.data[0].imageIds.length > 1) {
var imageIdIndex = stack.data[0].imageIds.indexOf(templateData.imageId);
- ViewerData[contentId].viewer.imageViewerLoadedSeriesDictionary[viewportIndex].currentImageIdIndex = imageIdIndex;
+ ViewerData[contentId].loadedSeriesData[viewportIndex].currentImageIdIndex = imageIdIndex;
Session.set('ViewerData', ViewerData);
}
}
+ // Attach the onNewImage callback to the CornerstoneNewImage event
$(element).off('CornerstoneNewImage', onNewImage);
$(element).on('CornerstoneNewImage', onNewImage);
+ // Set a random value for the Session variable in order to trigger an overlay update
+ Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
+
+ // Define a function to trigger an event whenever a new viewport is being used
+ // This is used to update the value of the "active viewport", when the user interacts
+ // with a new viewport element
function sendActivationTrigger(e, eventData) {
- var activeViewportIndex = data.activeViewport.curValue;
+ log.info('imageViewerViewport sendActivationTrigger');
+
+ // Check if the current active viewport in the Meteor Session
+ // Is the same as the viewport in which the activation event was fired.
+ // If it was, no changes are necessary, so stop here.
+ var activeViewportIndex = Session.get('activeViewport');
var viewportIndex = $(".imageViewerViewport").index(eventData.element);
if (viewportIndex === activeViewportIndex) {
return;
}
+
+ // Otherwise, trigger an 'ActivateViewport' event to be handled by the Template event
+ // handler
eventData.viewportIndex = viewportIndex;
- var customEvent = jQuery.Event('ActivateViewport', eventData);
- customEvent.type = 'ActivateViewport'; // Need to overwrite the type set in the touch tools
+ var customEvent = $.Event('ActivateViewport', eventData);
+
+ // Need to overwrite the type set in the original event
+ customEvent.type = 'ActivateViewport';
$(e.target).trigger(customEvent, eventData);
}
- $(element).off(allEvents, sendActivationTrigger);
- $(element).on(allEvents, sendActivationTrigger);
+ // Attach the sendActivationTrigger function to all of the Cornerstone interaction events
+ $(element).off(allCornerstoneEvents, sendActivationTrigger);
+ $(element).on(allCornerstoneEvents, sendActivationTrigger);
- Session.set('CornerstoneNewImage' + viewportIndex, Random.id());
-
- OHIF.viewer.imageViewerLoadedSeriesDictionary[viewportIndex] = {
+ // Store the current series data inside a global variable
+ OHIF.viewer.loadedSeriesData[viewportIndex] = {
studyInstanceUid: data.studyInstanceUid,
seriesInstanceUid: data.seriesInstanceUid,
currentImageIdIndex: data.currentImageIdIndex,
viewport: viewport
};
+ ViewerData[contentId].loadedSeriesData = OHIF.viewer.loadedSeriesData;
+ // Check if image plane (orientation / loction) data is present for the current image
+ var imagePlane = cornerstoneTools.metaData.get('imagePlane', image.imageId);
+
+ // If it is, and reference lines are enabled, add this element to the global synchronizer
+ // that is used for updating reference lines, and enable reference lines for this viewport.
if (OHIF.viewer.refLinesEnabled && imagePlane && imagePlane.frameOfReferenceUID) {
OHIF.viewer.updateImageSynchronizer.add(element);
- displayReferenceLines(viewportIndex);
+ displayReferenceLines(element);
}
- }, function(error) {
- if (errorLoadingHandler) {
- errorLoadingHandler(element, imageId, error);
+ if (viewportIndex === Session.get('activeViewport')) {
+ enablePrefetchOnElement(element);
}
+ }, function(error) {
+ // If something goes wrong while loading the image, fire the error handler.
+ errorLoadingHandler(element, imageId, error);
});
}
+/**
+ * This function searches an object to return the keys that contain a specific value
+ *
+ * @param object {object} The object to be searched
+ * @param value The value to be found
+ *
+ * @returns {array} The keys for which the object has the specified value
+ */
+function getKeysByValue(object, value) {
+ // http://stackoverflow.com/questions/9907419/javascript-object-get-key-by-value
+ return Object.keys(object).filter(function(key) {
+ return object[key] === value;
+ });
+}
+
+
+Meteor.startup(function() {
+ // On Meteor startup, define the global objects used to store loading imageIds
+ // by viewport / thumbnail element
+ ViewportLoading = {};
+ ThumbnailLoading = {};
+
+ // Whenever the CornerstoneImageLoadProgress is fired, identify which viewports
+ // the "in-progress" image is to be displayed in. Then pass the percent complete
+ // via the Meteor Session to the other templates to be displayed in the relevant viewports.
+ $(cornerstone).on('CornerstoneImageLoadProgress', function(e, eventData) {
+ viewportIndices = getKeysByValue(ViewportLoading, eventData.imageId);
+ viewportIndices.forEach(function(viewportIndex) {
+ Session.set('CornerstoneLoadProgress' + viewportIndex, eventData.percentComplete);
+ });
+
+ thumbnailIndices = getKeysByValue(ThumbnailLoading, eventData.imageId);
+ thumbnailIndices.forEach(function(thumbnailIndex) {
+ Session.set('CornerstoneThumbnailLoadProgress' + thumbnailIndex, eventData.percentComplete);
+ });
+ });
+
+ var config = {
+ magnifySize: 300,
+ magnificationLevel: 3
+ };
+
+ cornerstoneTools.magnify.setConfiguration(config);
+});
+
Template.imageViewerViewport.onRendered(function() {
- var studies = this.data.studies;
+ log.info("imageViewerViewport onRendered");
+
+ // When the imageViewerViewport template is rendered
var element = this.find(".imageViewerViewport");
+
+ // Display the loading indicator for this element
+ $(element).siblings('.imageViewerLoadingIndicator').css('display', 'block');
+
+ // Get the current active viewport index, if this viewport has the same index,
+ // add the CSS 'active' class to highlight this viewport.
+ var activeViewport = Session.get('activeViewport');
+ if (activeViewport === this.data.viewportIndex) {
+ $('#imageViewerViewports .viewportContainer').removeClass('active');
+ $(element).parents('.viewportContainer').addClass('active');
+ }
+
+ // Create a data object to pass to the series loading function (loadSeriesIntoViewport)
var data = {
element: element,
viewport: this.data.viewport,
currentImageIdIndex: this.data.currentImageIdIndex,
- activeViewport: this.data.activeViewport,
studyInstanceUid: this.data.studyInstanceUid,
- seriesInstanceUid: this.data.seriesInstanceUid
+ seriesInstanceUid: this.data.seriesInstanceUid,
+ activeViewport: activeViewport
};
+ // If no seriesInstanceUid or studyInstanceUid were supplied, display the drag/drop
+ // instructions and then stop here since we don't know what to display in the viewport.
if (this.data.seriesInstanceUid === undefined || this.data.studyInstanceUid === undefined) {
element.classList.add('empty');
+ $(element).siblings('.imageViewerLoadingIndicator').css('display', 'none');
$(element).siblings('.viewportInstructions').show();
return;
}
+ // Retrieve the current set of studies from the Meteor Session
+ var studies = Session.get('studies');
+
+ // Look through every study and their series' until we find the
+ // series that matches the seriesInstanceUid and studyInstanceUid
var studyInstanceUid = this.data.studyInstanceUid;
var seriesInstanceUid = this.data.seriesInstanceUid;
-
studies.every(function(study) {
if (study.studyInstanceUid === studyInstanceUid) {
data.study = study;
@@ -242,22 +421,51 @@ Template.imageViewerViewport.onRendered(function() {
return true;
});
+ // If we didn't find anything, stop here
+ if (!data.study || !data.series) {
+ return;
+ }
+
+ // Otherwise, load pass the data object into loadSeriesIntoViewport
loadSeriesIntoViewport(data);
});
Template.imageViewerViewport.onDestroyed(function() {
+ log.info("imageViewerViewport onDestroyed");
+
+ // When a viewport element is being destroyed
var element = this.find(".imageViewerViewport");
+
+ // Try to stop any currently playing clips
+ // Otherwise the interval will continuously throw errors
+ try {
+ cornerstoneTools.stopClip(element);
+ } catch(error) {
+ log.warn(error);
+ }
+
+ // Disable the viewport element with Cornerstone
+ // This also triggers the removal of the element from all available
+ // synchronizers, such as the one used for reference lines.
cornerstone.disable(element);
});
Template.imageViewerViewport.events({
'ActivateViewport .imageViewerViewport': function(e) {
- if (this.viewportIndex === this.activeViewport.curValue) {
- return;
- }
- console.log('ActivateViewport index: ' + this.viewportIndex);
- this.activeViewport.curValue = this.viewportIndex;
- enablePrefetchOnElement(this.viewportIndex);
- displayReferenceLines(this.viewportIndex);
- },
+ log.info("imageViewerViewport ActivateViewport");
+
+ // When an ActivateViewport event is fired, update the Meteor Session
+ // with the viewport index that it was fired from.
+ Session.set("activeViewport", e.viewportIndex);
+
+ // Add the 'active' class to the parent container to highlight the active viewport
+ $('#imageViewerViewports .viewportContainer').removeClass('active');
+ $(e.currentTarget).parents('.viewportContainer').addClass('active');
+
+ // Finally, enable stack prefetching and hide the reference lines from
+ // the newly activated viewport
+ var element = e.currentTarget;
+ enablePrefetchOnElement(element);
+ displayReferenceLines(element);
+ }
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.styl b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.styl
index 6f3c17da0..2182b6540 100644
--- a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.styl
+++ b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.styl
@@ -20,4 +20,22 @@
right: 0
left: 0
margin: auto
- height: 20px
\ No newline at end of file
+ height: 20px
+
+/* These are some good CSS settings for a circular magnifying glass
+ Try removing the border-radius to make a square magnifying glass */
+.magnifyTool{
+ border: 4px white solid;
+ box-shadow: 2px 2px 10px #1e1e1e;
+ border-radius: 50%;
+ display: none;
+ cursor: none;
+}
+/* prevents selection when left click dragging */
+.disable-selection {
+ -moz-user-select: none; -webkit-user-select: none; -ms-user-select:none; user-select:none;
+}
+/* prevents cursor from changing to the i bar on the overlays*/
+.noIbar {
+ cursor:default;
+}
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.html b/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.html
index aced504b7..3a22cc13d 100644
--- a/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.html
+++ b/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.html
@@ -3,7 +3,7 @@
{{ #each viewportArray }}
{{ /each }}
diff --git a/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.js b/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.js
index 1cf4ec99b..48e2bf207 100644
--- a/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.js
+++ b/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.js
@@ -1,49 +1,55 @@
+Template.imageViewerViewports.onRendered(function() {
+ this.autorun(function() {
+ var studies = Session.get('studies');
+ var eventType = "ViewerBaseStudyLoaded";
+ var eventData = {
+ studies: studies
+ };
+ $(document).trigger(eventType, eventData);
+ });
+});
+
Template.imageViewerViewports.helpers({
height: function() {
- var viewportRows;
- if (!Template.parentData(1).viewportRows) {
- viewportRows = 1;
- } else {
- viewportRows = Template.parentData(1).viewportRows.curValue; //Having issues with .get(), not sure why?
- }
+ var viewportRows = this.viewportRows || 1;
return 100 / viewportRows;
},
width: function() {
- var viewportColumns;
- if (!Template.parentData(1).viewportColumns) {
- viewportColumns = 1;
- } else {
- viewportColumns = Template.parentData(1).viewportColumns.curValue; //Having issues with .get(), not sure why?
- }
+ var viewportColumns = this.viewportColumns || 1;
return 100 / viewportColumns;
},
viewportArray: function() {
- // This is a really annoying thing to have to do, but Meteor
- // doesn't want to let me use another type of helper.
- var viewportRows;
- if (!this.viewportRows) {
- viewportRows = 1;
- } else {
- viewportRows = this.viewportRows.curValue; //Having issues with .get(), not sure why?
+ log.info("imageViewerViewports viewportArray");
+
+ var studies = Session.get('studies');
+
+ var viewportRows = this.viewportRows || 1;
+ var viewportColumns = this.viewportColumns || 1;
+
+ var contentId = this.contentId || $("#viewer").parents(".tab-pane.active").attr('id');
+ if (this.viewportRows && this.viewportColumns) {
+ viewportRows = this.viewportRows || 1;
+ viewportColumns = this.viewportColumns || 1;
+ } else if (ViewerData[contentId].viewportRows && ViewerData[contentId].viewportColumns) {
+ viewportRows = ViewerData[contentId].viewportRows;
+ viewportColumns = ViewerData[contentId].viewportColumns;
}
- var viewportColumns;
- if (!this.viewportColumns) {
- viewportColumns = 1;
- } else {
- viewportColumns = this.viewportColumns.curValue; //Having issues with .get(), not sure why?
- }
-
+ // Update viewerData
+ ViewerData[contentId].viewportRows = viewportRows;
+ ViewerData[contentId].viewportColumns = viewportColumns;
+ Session.set("ViewerData", ViewerData);
+
var viewportData;
- if (OHIF && OHIF.viewer && !$.isEmptyObject(OHIF.viewer.imageViewerLoadedSeriesDictionary)) {
- viewportData = OHIF.viewer.imageViewerLoadedSeriesDictionary;
+ if (!$.isEmptyObject(ViewerData[contentId].loadedSeriesData)) {
+ viewportData = ViewerData[contentId].loadedSeriesData;
}
var hangingProtocol = getHangingProtocol();
var inputData = {
viewportColumns: viewportColumns,
viewportRows: viewportRows,
- studies: this.studies
+ studies: studies
};
var hangingProtocolViewportData = hangingProtocol(inputData);
@@ -52,8 +58,10 @@ Template.imageViewerViewports.helpers({
for (var i=0; i < numViewports; ++i) {
var data = {
viewportIndex: i,
- studies: this.studies,
- activeViewport: this.activeViewport
+ // These two are necessary because otherwise the width and height helpers
+ // don't get the right data context. Seems to be related to the "each" loop.
+ viewportColumns: viewportColumns,
+ viewportRows: viewportRows
};
if (viewportData && viewportData[i]) {
data.seriesInstanceUid = viewportData[i].seriesInstanceUid;
@@ -70,4 +78,75 @@ Template.imageViewerViewports.helpers({
}
return array;
},
+});
+
+var savedSeriesData,
+ savedViewportRows,
+ savedViewportColumns;
+
+Template.imageViewerViewports.events({
+ 'dblclick .imageViewerViewport': function(e) {
+ var container = $(".viewerMain").get(0);
+ var data;
+ var contentId = this.contentId || $("#viewer").parents(".tab-pane.active").attr('id');
+
+ // If there is more than one viewport on screen
+ // And one of them is double-clicked, it should be rendered alone
+ // If it is double-clicked again, the viewer should revert to the previous layout
+ if ($(e.currentTarget).hasClass('zoomed')) {
+ // Revert to saved settings
+ ViewerData[contentId].loadedSeriesData = $.extend(true, {}, savedSeriesData);
+ ViewerData[contentId].viewportRows = savedViewportRows;
+ ViewerData[contentId].viewportColumns = savedViewportColumns;
+
+ savedViewportRows = 0;
+ savedViewportColumns = 0;
+
+ data = {
+ viewportRows: ViewerData[contentId].viewportRows,
+ viewportColumns: ViewerData[contentId].viewportColumns,
+ };
+
+ // Render the imageViewerViewports template with these settings
+ $('#imageViewerViewports').remove();
+ UI.renderWithData(Template.imageViewerViewports, data, container);
+
+ // Remove the 'zoomed' class from any viewports
+ $('.imageViewerViewport').removeClass('zoomed');
+ } else {
+ // Zoom to single viewport
+
+ // If only one viewport is on-screen, stop here
+ if (ViewerData[contentId].viewportRows === 1 &&
+ ViewerData[contentId].viewportColumns === 1) {
+ return;
+ }
+
+ // Save the current settings
+ savedSeriesData = $.extend(true, {}, ViewerData[contentId].loadedSeriesData);
+ savedViewportRows = ViewerData[contentId].viewportRows;
+ savedViewportColumns = ViewerData[contentId].viewportColumns;
+
+ // Get the clicked-on viewport's index
+ var viewportIndex = this.viewportIndex;
+
+ // Set the first viewport's data to be the same as the currently clicked-on viewport
+ ViewerData[contentId].loadedSeriesData[0] = ViewerData[contentId].loadedSeriesData[viewportIndex];
+
+ // Set the basic template data
+ data = {
+ viewportRows: 1,
+ viewportColumns: 1,
+ };
+
+ // Render the imageViewerViewports template with these settings
+ $('#imageViewerViewports').remove();
+ UI.renderWithData(Template.imageViewerViewports, data, container);
+
+ // Add the 'zoomed' class to the lone remaining viewport
+ $('.imageViewerViewport').eq(0).addClass('zoomed');
+
+ }
+ log.info('dblclick');
+ }
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.styl b/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.styl
index dc1244d31..65693b725 100644
--- a/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.styl
+++ b/Packages/viewerbase/components/viewer/imageViewerViewports/imageViewerViewports.styl
@@ -6,6 +6,12 @@
float: left
border: 2px solid #777
+ &.active, &:hover.active
+ border: 2px solid #9A6617
+
+ &:hover
+ border: 2px solid white
+
.removable
width: 100%
height: 100%
diff --git a/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js b/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js
index 4d0cfac35..88a9e00dd 100644
--- a/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js
+++ b/Packages/viewerbase/components/viewer/layoutChooser/layoutChooser.js
@@ -1,3 +1,10 @@
+/**
+ * Adds the 'hover' class to cells above and to the left of the current cell
+ * This is used to "fill in" the grid that the user will change the layout to,
+ * if they click on a specific table cell.
+ *
+ * @param currentCell
+ */
function highlightCells(currentCell) {
var cells = $('.layoutChooser table td');
cells.removeClass('hover');
@@ -27,31 +34,18 @@ Template.layoutChooser.events({
},
'click .layoutChooser table td': function(evt) {
$('#imageViewerViewports').remove();
- var container = $(".viewerMain").get(0);
var currentCell = $(evt.currentTarget);
var rowIndex = currentCell.closest('tr').index();
var columnIndex = currentCell.index();
- var data = {};
-
// Add 1 because the indices start from zero
- if (this.viewportRows) {
- this.viewportRows.curValue = rowIndex + 1;
- data.viewportRows = this.viewportRows;
- } else {
- data.viewportRows = 1;
- }
-
- if (this.viewportColumns) {
- this.viewportColumns.curValue = columnIndex + 1;
- data.viewportColumns = this.viewportColumns;
- } else {
- data.viewportColumns = 1;
- }
-
- data.studies = Template.parentData(2).studies;
- data.activeViewport = Template.parentData(2).activeViewport;
+ var data = {
+ viewportRows: rowIndex + 1,
+ viewportColumns: columnIndex + 1,
+ };
+
+ var container = $(".viewerMain").get(0);
UI.renderWithData(Template.imageViewerViewports, data, container);
}
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.html b/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.html
index f840e6372..c6855124e 100644
--- a/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.html
+++ b/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.html
@@ -1,7 +1,7 @@
-
Loading...
+
Loading... {{percentComplete}}
diff --git a/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.js b/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.js
index eedae9c7b..b45330d50 100644
--- a/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.js
+++ b/Packages/viewerbase/components/viewer/loadingIndicator/loadingIndicator.js
@@ -58,4 +58,13 @@ errorLoadingHandler = function(element, imageId, error, source) {
if (error) {
errorLoadingIndicator.find('.details').text("Details: " + error);
}
-};
\ No newline at end of file
+};
+
+Template.loadingIndicator.helpers({
+ 'percentComplete': function(e) {
+ var percentComplete = Session.get('CornerstoneLoadProgress' + this.viewportIndex);
+ if (percentComplete && percentComplete !== 100) {
+ return percentComplete + '%';
+ }
+ }
+});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/playClipButton/playClipButton.js b/Packages/viewerbase/components/viewer/playClipButton/playClipButton.js
index 75d8e7947..ec35a6d47 100644
--- a/Packages/viewerbase/components/viewer/playClipButton/playClipButton.js
+++ b/Packages/viewerbase/components/viewer/playClipButton/playClipButton.js
@@ -1,7 +1,7 @@
Template.playClipButton.helpers({
'isPlaying': function() {
Session.get('UpdateCINE');
- var viewportIndex = this.activeViewport.curValue;
- return !!OHIF.viewer.isPlaying[viewportIndex];
+ var activeViewport = Session.get('activeViewport');
+ return !!OHIF.viewer.isPlaying[activeViewport];
}
});
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/toolbar/toolbar.html b/Packages/viewerbase/components/viewer/toolbar/toolbar.html
index 12c4ac125..b4616916a 100644
--- a/Packages/viewerbase/components/viewer/toolbar/toolbar.html
+++ b/Packages/viewerbase/components/viewer/toolbar/toolbar.html
@@ -6,11 +6,11 @@
{{ /each }}
{{ #if includePlayClipButton }}
- {{ >playClipButton activeViewport=activeViewport}}
+ {{ >playClipButton }}
{{ /if }}
{{ #if includeLayoutButton }}
- {{ >layoutButton viewportRows=viewportRows viewportColumns=viewportColumns}}
+ {{ >layoutButton }}
{{ /if }}
diff --git a/Packages/viewerbase/components/viewer/toolbar/toolbar.js b/Packages/viewerbase/components/viewer/toolbar/toolbar.js
index 77799e35e..00c8faba9 100644
--- a/Packages/viewerbase/components/viewer/toolbar/toolbar.js
+++ b/Packages/viewerbase/components/viewer/toolbar/toolbar.js
@@ -8,6 +8,27 @@ function getDefaultButtonData() {
iconClasses: 'fa fa-sun-o'
});
+ buttonData.push({
+ id: 'wwwcRegion',
+ title: 'Window by Region',
+ classes: 'imageViewerTool',
+ iconClasses: 'fa fa-square'
+ });
+
+ buttonData.push({
+ id: 'magnify',
+ title: 'Magnify',
+ classes: 'imageViewerTool',
+ iconClasses: 'fa fa-circle'
+ });
+
+ buttonData.push({
+ id: 'annotate',
+ title: 'Annotation',
+ classes: 'imageViewerTool',
+ iconClasses: 'fa fa-arrows-h'
+ });
+
buttonData.push({
id: 'invert',
title: 'Invert',
@@ -70,6 +91,20 @@ function getDefaultButtonData() {
classes: 'imageViewerTool',
iconClasses: 'fa fa-square-o'
});
+
+ buttonData.push({
+ id: 'resetViewport',
+ title: 'Reset Viewport',
+ classes: 'imageViewerCommand',
+ iconClasses: 'fa fa-undo'
+ });
+
+ buttonData.push({
+ id: 'clearTools',
+ title: 'Clear tools',
+ classes: 'imageViewerCommand',
+ iconClasses: 'fa fa-trash'
+ });
return buttonData;
}
@@ -84,8 +119,8 @@ Template.toolbar.events({
if (!OHIF.viewer.functionList.hasOwnProperty(command)) {
return;
}
- var viewportIndex = Template.parentData(0).activeViewport.get();
- var element = $('.imageViewerViewport').get(viewportIndex);
+ var activeViewport = Session.get('activeViewport');
+ var element = $('.imageViewerViewport').get(activeViewport);
OHIF.viewer.functionList[command](element);
}
});
diff --git a/Packages/viewerbase/components/viewer/toolbar/toolbar.styl b/Packages/viewerbase/components/viewer/toolbar/toolbar.styl
index 774a71141..49fabeab2 100644
--- a/Packages/viewerbase/components/viewer/toolbar/toolbar.styl
+++ b/Packages/viewerbase/components/viewer/toolbar/toolbar.styl
@@ -10,6 +10,7 @@
color: #ffffff
background-color: #424242
border-color: #424242
+ outline: none
&.active, &:active
background-color: #3B678E
\ No newline at end of file
diff --git a/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js b/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js
index c7de59c96..c64676f4a 100644
--- a/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js
+++ b/Packages/viewerbase/components/viewer/viewportOrientationMarkers/viewportOrientationMarkers.js
@@ -1,5 +1,13 @@
-updateOrientationMarkers = function(element, viewport) {
- // Updates the orientation labels on the viewport
+/**
+ * Updates the orientation labels on a Cornerstone-enabled Viewport element
+ * when the viewport settings change (e.g. when a horizontal flip or a rotation occurs)
+ *
+ * @param element The DOM element of the Cornerstone viewport
+ */
+updateOrientationMarkers = function(element) {
+ // Get the current viewport settings
+ var viewport = cornerstone.getViewport(element);
+
var enabledElement = cornerstone.getEnabledElement(element);
var imagePlane = cornerstoneTools.metaData.get('imagePlane', enabledElement.image.imageId);
diff --git a/Packages/viewerbase/components/viewer/viewportOverlay/viewportOverlay.html b/Packages/viewerbase/components/viewer/viewportOverlay/viewportOverlay.html
index 6f80f9a30..68d7c092a 100644
--- a/Packages/viewerbase/components/viewer/viewportOverlay/viewportOverlay.html
+++ b/Packages/viewerbase/components/viewer/viewportOverlay/viewportOverlay.html
@@ -1,13 +1,13 @@
-
{{patientName}}
+
{{formatPN patientName}}
{{patientId}}
{{studyDescription}}
-
{{studyDate}} {{studyTime}}
+
{{formatDA studyDate}} {{formatTM studyTime}}
Zoom: {{formatNumberPrecision zoom 2}}%
diff --git a/Packages/viewerbase/lib/createStacks.js b/Packages/viewerbase/lib/createStacks.js
index 8bb695b98..2b5073803 100644
--- a/Packages/viewerbase/lib/createStacks.js
+++ b/Packages/viewerbase/lib/createStacks.js
@@ -1,7 +1,22 @@
+/**
+ * Creates a set of series to be placed in the Study Browser
+ * The series that appear in the Study Browser must represent
+ * imaging modalities.
+ *
+ * Furthermore, for drag/drop functionality,
+ * it is easiest if the stack objects also contain information about
+ * which study they are linked to.
+ *
+ * @param study The study instance to be used
+ * @returns {Array} An array of series to be placed in the Study Browser
+ */
createStacks = function(study) {
+ // Define an empty array of stacks
var stacks = [];
+
// TODO: Split by multi-frame, modality, image size, etc
study.seriesList.forEach(function(series) {
+ // If the series has no instanced, skip it
if (!series.instances) {
return;
}
@@ -12,6 +27,11 @@ createStacks = function(study) {
if (!anInstance || !anInstance.rows) {
return;
}
+
+ // Include the study instance Uid for drag/drop purposes
+ series.studyInstanceUid = study.studyInstanceUid;
+
+ // Add this series to the list of stacks
stacks.push(series);
});
return stacks;
diff --git a/Packages/viewerbase/lib/docking-container.js b/Packages/viewerbase/lib/docking-container.js
index 5b7a1b4d3..9d4e6628b 100644
--- a/Packages/viewerbase/lib/docking-container.js
+++ b/Packages/viewerbase/lib/docking-container.js
@@ -1,6 +1,4 @@
(function($) {
-
-
//Collapse the container
function collapseContainer ( container ) {
container.toggleClass("collapseHorizontal");
diff --git a/Packages/viewerbase/lib/encodeQueryData.js b/Packages/viewerbase/lib/encodeQueryData.js
index 10355e65a..a97d5a6cf 100644
--- a/Packages/viewerbase/lib/encodeQueryData.js
+++ b/Packages/viewerbase/lib/encodeQueryData.js
@@ -8,10 +8,11 @@
encodeQueryData = function(data)
{
var ret = [];
+
for (var d in data) {
- if(data[d]) {
- ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
- }
+ if(data[d]) {
+ ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d]));
+ }
}
return ret.join("&");
};
diff --git a/Packages/viewerbase/lib/findIndexOfString.js b/Packages/viewerbase/lib/findIndexOfString.js
deleted file mode 100644
index 4b0746dd2..000000000
--- a/Packages/viewerbase/lib/findIndexOfString.js
+++ /dev/null
@@ -1,39 +0,0 @@
-function checkToken(token, data, dataOffset) {
-
- if(dataOffset + token.length > data.length) {
- //console.log('dataOffset >> ', dataOffset);
- return false;
- }
-
- var endIndex = dataOffset;
-
- for(var i = 0; i < token.length; i++) {
- if(token[i] !== data[endIndex++]) {
- if(endIndex > 520000) {
- console.log('token=',uint8ArrayToString(token));
- console.log('data=', uint8ArrayToString(data, dataOffset, endIndex-dataOffset));
- console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(data[endIndex]), endIndex);
- console.log('miss at %d %s dataOffset=%d', i, String.fromCharCode(token[endIndex]), endIndex);
- }
- return false;
- }
- }
- return true;
-}
-
-findIndexOfString = function(data, str, offset) {
-
- offset = offset || 0;
-
- var token = stringToUint8Array(str);
-
- for(var i=offset; i < data.length; i++) {
- if(token[0] === data[i]) {
- //console.log('match @', i);
- if(checkToken(token, data, i)) {
- return i;
- }
- }
- }
- return -1;
-}
\ No newline at end of file
diff --git a/Packages/viewerbase/lib/getActiveViewportElement.js b/Packages/viewerbase/lib/getActiveViewportElement.js
deleted file mode 100644
index 1f71ff1db..000000000
--- a/Packages/viewerbase/lib/getActiveViewportElement.js
+++ /dev/null
@@ -1,5 +0,0 @@
-getActiveViewportElement = function() {
- // TODO: Fix this, just hardcoded for now
- var element = $('.imageViewerViewport').get(0);
- return element;
-};
\ No newline at end of file
diff --git a/Packages/viewerbase/lib/getImageId.js b/Packages/viewerbase/lib/getImageId.js
index bd75fe237..5c0122fc7 100644
--- a/Packages/viewerbase/lib/getImageId.js
+++ b/Packages/viewerbase/lib/getImageId.js
@@ -1,3 +1,9 @@
+/**
+ * Obtain an imageId for Cornerstone from an image instance
+ *
+ * @param instance
+ * @returns {string} The imageId to be used by Cornerstone
+ */
getImageId = function(instance) {
if (!instance) {
return;
diff --git a/Packages/viewerbase/lib/getWADORSImageId.js b/Packages/viewerbase/lib/getWADORSImageId.js
index 21dd3cc7f..df7049267 100644
--- a/Packages/viewerbase/lib/getWADORSImageId.js
+++ b/Packages/viewerbase/lib/getWADORSImageId.js
@@ -1,3 +1,10 @@
+/**
+ * Obtain an imageId for Cornerstone based on the WADO-RS scheme
+ *
+ * @param instance
+ * @returns {string} The imageId to be used by Cornerstone
+ */
+
getWADORSImageId = function(instance) {
var columnPixelSpacing = 1.0;
var rowPixelSpacing = 1.0;
diff --git a/Packages/viewerbase/lib/helpers/formatDA.js b/Packages/viewerbase/lib/helpers/formatDA.js
index 420064e36..8c549d4f4 100644
--- a/Packages/viewerbase/lib/helpers/formatDA.js
+++ b/Packages/viewerbase/lib/helpers/formatDA.js
@@ -1,13 +1,11 @@
+/**
+ * A global Blaze UI helper function to format DICOM Dates using the Moment library
+ */
UI.registerHelper('formatDA', function (context, format, options) {
if (!context) {
return undefined;
}
- /*var date = DICOMUtilities.toDate(context, '', false);
- if(!date) {
- return context;
- }*/
- var date = context;
- var dateAsMoment = moment(date);
+ var dateAsMoment = moment(context, "YYYYMMDD");
var strFormat = "MMM D, YYYY";
if (options) {
strFormat = format;
diff --git a/Packages/viewerbase/lib/helpers/formatNumberPrecision.js b/Packages/viewerbase/lib/helpers/formatNumberPrecision.js
index e575df50f..069718c09 100644
--- a/Packages/viewerbase/lib/helpers/formatNumberPrecision.js
+++ b/Packages/viewerbase/lib/helpers/formatNumberPrecision.js
@@ -1,5 +1,8 @@
-UI.registerHelper('formatNumberPrecision', function(context, precision, options) {
- if(context) {
+/**
+ * A global Blaze UI helper to format a float value to a specified precision
+ */
+UI.registerHelper('formatNumberPrecision', function(context, precision) {
+ if (context) {
return parseFloat(context).toFixed(precision);
}
});
\ No newline at end of file
diff --git a/Packages/viewerbase/lib/helpers/formatPN.js b/Packages/viewerbase/lib/helpers/formatPN.js
index d1b762591..c2dc9db65 100644
--- a/Packages/viewerbase/lib/helpers/formatPN.js
+++ b/Packages/viewerbase/lib/helpers/formatPN.js
@@ -1,20 +1,9 @@
-UI.registerHelper('formatDA', function (context, format, options) {
+/**
+ * A global Blaze UI helper to format a patient name for display purposes
+ */
+UI.registerHelper('formatPN', function (context) {
if (!context) {
return undefined;
}
- var pn = context;
- var str = '';
- if(pn.prefix) {
- str = pn.prefix + ' ';
- }
- str += (pn.familyName || '') + ', ';
- str += (pn.givenName || '');
- if(pn.middleName) {
- str += ' ' + pn.middleName;
- }
- if(pn.suffix) {
- str += ' ' + pn.suffix;
- }
-
- return str;
+ return context.replace('^', ', ');
});
\ No newline at end of file
diff --git a/Packages/viewerbase/lib/helpers/formatTM.js b/Packages/viewerbase/lib/helpers/formatTM.js
index 842571724..01a113326 100644
--- a/Packages/viewerbase/lib/helpers/formatTM.js
+++ b/Packages/viewerbase/lib/helpers/formatTM.js
@@ -1,13 +1,14 @@
-UI.registerHelper('formatTM', function(context, format, options) {
+/**
+ * A global Blaze UI helper to format a DICOM Time for display using the Moment library
+ */
+UI.registerHelper('formatTM', function(context, format) {
if (!context) {
return undefined;
}
- var date = context;
- var dateAsMoment = moment(date);
+ var dateAsMoment = moment(context);
var strFormat = "HH:mm:ss";
if (options) {
strFormat = format;
}
-
return dateAsMoment.format(strFormat);
});
diff --git a/Packages/viewerbase/lib/isTouchDevice.js b/Packages/viewerbase/lib/isTouchDevice.js
index 6a1fe84e9..b5db9cddd 100644
--- a/Packages/viewerbase/lib/isTouchDevice.js
+++ b/Packages/viewerbase/lib/isTouchDevice.js
@@ -1,3 +1,11 @@
+/**
+ * Helper function to determine if the current client devices
+ * is touch-capable. This can be used to modify certain aspects of the UI.
+ *
+ * The check may not work on all devices!
+ *
+ * @returns {boolean} true if the client device is touch-capable, false otherwise
+ */
isTouchDevice = function() {
return (('ontouchstart' in window) ||
(navigator.MaxTouchPoints > 0) ||
diff --git a/Packages/viewerbase/lib/metaDataProvider.js b/Packages/viewerbase/lib/metaDataProvider.js
index b38f995e0..3a8171c4b 100644
--- a/Packages/viewerbase/lib/metaDataProvider.js
+++ b/Packages/viewerbase/lib/metaDataProvider.js
@@ -1,5 +1,17 @@
var metaDataLookup = {};
+/**
+ * Cornerstone MetaData provider to store image meta data
+ * Data from instances, series, and studies are associated with
+ * imageIds to facilitate usage of this information by Cornerstone's Tools
+ *
+ * e.g. the imagePlane metaData object contains instance information about
+ * row/column pixel spacing, patient position, and patient orientation. It
+ * is used in CornerstoneTools to position reference lines and orientation markers.
+ *
+ * @param {String} imageId The Cornerstone ImageId
+ * @param {Object} data An object containing instance, series, and study metaData
+ */
addMetaData = function(imageId, data) {
var instanceMetaData = data.instance;
var seriesMetaData = data.series;
@@ -10,27 +22,33 @@ addMetaData = function(imageId, data) {
var metaData = {};
metaData.study = {
- date: instanceMetaData.studyDate,
- time: instanceMetaData.studyTime,
- description: instanceMetaData.studyDescription
+ instanceUid: studyMetaData.studyInstanceUid,
+ date: studyMetaData.studyDate,
+ time: studyMetaData.studyTime,
+ description: studyMetaData.studyDescription
};
metaData.series = {
description: seriesMetaData.seriesDescription,
number: seriesMetaData.seriesNumber,
modality: seriesMetaData.modality,
- instanceUid: seriesMetaData.instanceUid,
+ instanceUid: seriesMetaData.seriesInstanceUid,
numImages: numImages
};
metaData.instance = {
+ wadouri: instanceMetaData.wadouri,
+ imageType: instanceMetaData.imageType,
+ photometricInterpretation: instanceMetaData.photometricInterpretation,
+ sopInstanceUid: instanceMetaData.sopInstanceUid,
+ sopClassUid: instanceMetaData.sopClassUid,
number: instanceMetaData.instanceNumber,
index: imageIndex
};
metaData.patient = {
- name: instanceMetaData.patientName,
- id: instanceMetaData.patientId
+ name: studyMetaData.patientName,
+ id: studyMetaData.patientId
};
// If there is sufficient information, populate
@@ -66,6 +84,15 @@ addMetaData = function(imageId, data) {
metaDataLookup[imageId] = metaData;
};
+/**
+ * Looks up metaData for Cornerstone Tools given a specified type and imageId
+ * A type may be, e.g. 'study', or 'patient', or 'imagePlane'. These types
+ * are keys in the stored metaData objects.
+ *
+ * @param type
+ * @param imageId
+ * @returns {Object} Relevant metaData of the specified type
+ */
function provider(type, imageId) {
var imageMetaData = metaDataLookup[imageId];
if (!imageMetaData) {
diff --git a/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js b/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js
index a8dae13e9..6efbde61f 100644
--- a/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js
+++ b/Packages/viewerbase/lib/rerenderViewportWithNewSeries.js
@@ -1,5 +1,5 @@
/**
- * This function destroys and rerenders the imageViewerViewport template.
+ * This function destroys and re-renders the imageViewerViewport template.
* It uses the data provided (object containing seriesIndex and studyIndex) to
* load a new series into the produced viewport.
*
@@ -7,12 +7,16 @@
* @param data An object containing a seriesIndex and studyIndex for a study to load into this viewport
*/
rerenderViewportWithNewSeries = function(element, data) {
+ // Get the container and index of the current viewport.
+ // The parent container is identified because it is later removed from the DOM
var container = $(element).parents('.viewportContainer').get(0);
var viewportIndex = $(container).index();
+
+ // Record the current viewportIndex so this can be passed into the re-rendering call
data.viewportIndex = viewportIndex;
// Update the dictionary of loaded series for the specified viewport
- OHIF.viewer.imageViewerLoadedSeriesDictionary[viewportIndex] = {
+ OHIF.viewer.loadedSeriesData[viewportIndex] = {
seriesInstanceUid: data.seriesInstanceUid,
studyInstanceUid: data.studyInstanceUid,
currentImageIdIndex: 0
diff --git a/Packages/viewerbase/lib/sortStudy.js b/Packages/viewerbase/lib/sortStudy.js
index adad39941..fae02eacc 100644
--- a/Packages/viewerbase/lib/sortStudy.js
+++ b/Packages/viewerbase/lib/sortStudy.js
@@ -1,3 +1,9 @@
+/**
+ * Sorts the series and instances inside a study instance by their series
+ * and instance numbers in ascending order.
+ *
+ * @param {Object} study The study instance
+ */
sortStudy = function(study) {
if (!study) {
return;
diff --git a/Packages/viewerbase/lib/stringToUint8Array.js b/Packages/viewerbase/lib/stringToUint8Array.js
index 484bbc82a..8b1378917 100644
--- a/Packages/viewerbase/lib/stringToUint8Array.js
+++ b/Packages/viewerbase/lib/stringToUint8Array.js
@@ -1,7 +1 @@
-stringToUint8Array = function(str) {
- var uint=new Uint8Array(str.length);
- for(var i=0,j=str.length;i 0) {
// First, deactivate the current active tool
tools[activeTool].mouse.deactivate(element, 1);
- tools[activeTool].touch.deactivate(element);
+
+ if (tools[activeTool].touch) {
+ tools[activeTool].touch.deactivate(element);
+ }
// First, get the stack toolData
var toolData = cornerstoneTools.getToolState(element, 'stack');
- if (toolData === undefined || toolData.data === undefined || toolData.data.length === 0) {
+ if (!toolData || !toolData.data || !toolData.data.length) {
return;
}
@@ -127,7 +130,10 @@ toolManager = {
// Activate the chosen tool
tools[tool].mouse.activate(element, 1);
}
- tools[tool].touch.activate(element);
+
+ if (tools[tool].touch) {
+ tools[tool].touch.activate(element);
+ }
cornerstoneTools.zoomTouchPinch.activate(element);
cornerstoneTools.panMultiTouch.activate(element);
diff --git a/Packages/viewerbase/lib/uint8ArrayToString.js b/Packages/viewerbase/lib/uint8ArrayToString.js
deleted file mode 100644
index ba8369b63..000000000
--- a/Packages/viewerbase/lib/uint8ArrayToString.js
+++ /dev/null
@@ -1,9 +0,0 @@
-uint8ArrayToString = function(data, offset, length) {
- offset = offset || 0;
- length = length || data.length - offset;
- var str = "";
- for(var i=offset; i < offset + length; i++) {
- str += String.fromCharCode(data[i]);
- }
- return str;
-};
\ No newline at end of file
diff --git a/Packages/viewerbase/lib/updateOrientationMarkers.js b/Packages/viewerbase/lib/updateOrientationMarkers.js
index c7de59c96..0bf56b2dc 100644
--- a/Packages/viewerbase/lib/updateOrientationMarkers.js
+++ b/Packages/viewerbase/lib/updateOrientationMarkers.js
@@ -1,5 +1,13 @@
-updateOrientationMarkers = function(element, viewport) {
- // Updates the orientation labels on the viewport
+/**
+ * Updates the orientation labels on a Cornerstone-enabled Viewport element
+ * when the viewport settings change (e.g. when a horizontal flip or a rotation occurs)
+ *
+ * @param element The DOM element of the Cornerstone viewport
+ */
+updateOrientationMarkers = function(element) {
+ // Get the current viewport settings
+ var viewport = cornerstone.getViewport(element);
+
var enabledElement = cornerstone.getEnabledElement(element);
var imagePlane = cornerstoneTools.metaData.get('imagePlane', enabledElement.image.imageId);
@@ -14,7 +22,7 @@ updateOrientationMarkers = function(element, viewport) {
var markers = {
top: oppositeColumnString,
- left: oppositeRowString,
+ left: oppositeRowString
};
// If any vertical or horizontal flips are applied, change the orientation strings ahead of
diff --git a/Packages/viewerbase/log.js b/Packages/viewerbase/log.js
new file mode 100644
index 000000000..bb7ba9220
--- /dev/null
+++ b/Packages/viewerbase/log.js
@@ -0,0 +1,3 @@
+// Create package logger using loglevel
+// https://atmospherejs.com/spacejamio/loglevel
+log = loglevel.createPackageLogger('viewerbase', defaultLevel = 'info');
\ No newline at end of file
diff --git a/Packages/viewerbase/package.js b/Packages/viewerbase/package.js
index 15f3418a4..cc29a76d0 100644
--- a/Packages/viewerbase/package.js
+++ b/Packages/viewerbase/package.js
@@ -10,10 +10,17 @@ Package.onUse(function (api) {
api.use('standard-app-packages');
api.use('jquery');
api.use('stylus');
+ api.use('accounts-password');
+ api.use('ian:accounts-ui-bootstrap-3');
+ api.use('http');
+ api.use('practicalmeteor:loglevel');
+ api.use('momentjs:moment');
// Our custom package
api.use('cornerstone');
+ api.addFiles('log.js', ['client', 'server']);
+
// ---------- Components ----------
// Basic components
@@ -22,7 +29,14 @@ Package.onUse(function (api) {
api.addFiles('components/basic/login/login.html', 'client');
api.addFiles('components/basic/notFound/notFound.html', 'client');
+ api.addFiles('components/basic/loadingText/loadingText.html', 'client');
+ api.addFiles('components/basic/loadingText/loadingText.styl', 'client');
+
+ api.addFiles('components/basic/removableBackdrop/removableBackdrop.html', 'client');
+ api.addFiles('components/basic/removableBackdrop/removableBackdrop.styl', 'client');
+
api.addFiles('components/basic/hidingPanel/hidingPanel.html', 'client');
+ api.addFiles('components/basic/hidingPanel/hidingPanel.js', 'client');
api.addFiles('components/basic/hidingPanel/hidingPanel.styl', 'client');
// Study Browser components
@@ -81,33 +95,24 @@ Package.onUse(function (api) {
api.addFiles('lib/accountsConfig.js', 'client');
api.addFiles('lib/createStacks.js', 'client');
api.addFiles('lib/encodeQueryData.js', ['client', 'server']);
- api.addFiles('lib/findIndexOfString.js', 'client');
- api.addFiles('lib/getActiveViewportElement.js', 'client');
api.addFiles('lib/getImageId.js', 'client');
api.addFiles('lib/getWADORSImageId.js', 'client');
api.addFiles('lib/isTouchDevice.js', 'client');
api.addFiles('lib/metaDataProvider.js', 'client');
api.addFiles('lib/rerenderViewportWithNewSeries.js', 'client');
api.addFiles('lib/sortStudy.js', 'client');
- api.addFiles('lib/stringToUint8Array.js', 'client');
api.addFiles('lib/toolManager.js', 'client');
- api.addFiles('lib/uint8ArrayToString.js', 'client');
- api.addFiles('lib/updateOrientationMarkers.js', 'client');
//api.export('accountsConfig', 'client');
api.export('createStacks', 'client');
api.export('encodeQueryData', ['client', 'server']);
- api.export('findIndexOfString', 'client');
- api.export('getActiveViewportElement', 'client');
api.export('getImageId', 'client');
api.export('getWADORSImageId', 'client');
api.export('isTouchDevice', 'client');
api.export('metaDataProvider', 'client');
api.export('rerenderViewportWithNewSeries', 'client');
api.export('sortStudy', 'client');
- api.export('stringToUint8Array', 'client');
api.export('toolManager', 'client');
- api.export('uint8ArrayToString', 'client');
api.export('updateOrientationMarkers', 'client');
// UI Helpers
diff --git a/Packages/viewerbase/server/methods/getStudyMetadata.js b/Packages/viewerbase/server/methods/getStudyMetadata.js
index f1eeccdfe..b4fe2697a 100644
--- a/Packages/viewerbase/server/methods/getStudyMetadata.js
+++ b/Packages/viewerbase/server/methods/getStudyMetadata.js
@@ -1,10 +1,15 @@
Meteor.methods({
- 'GetStudyMetadata' : function(studyInstanceUid) {
- console.log('GetStudyMetadata(%s)', studyInstanceUid);
+ /**
+ * Retrieves Study metadata given a Study Instance UID
+ * This Meteor method is available from both the client and the server
+ */
+ 'GetStudyMetadata' : function(studyInstanceUid) {
+ log.info('GetStudyMetadata(%s)', studyInstanceUid);
- var server = Meteor.settings.dicomWeb.endpoints[0];
+ // Get the server data. This is user-defined in the
+ // config.json files used to run the Meteor server
+ var server = Meteor.settings.dicomWeb.endpoints[0];
- //return Services.QIDO.Instances(server, studyInstanceUid);
- return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
- }
+ return Services.WADO.RetrieveMetadata(server, studyInstanceUid);
+ }
});
\ No newline at end of file
diff --git a/Packages/viewerbase/server/methods/worklistSearch.js b/Packages/viewerbase/server/methods/worklistSearch.js
index b41ffe987..d22cd8a3c 100644
--- a/Packages/viewerbase/server/methods/worklistSearch.js
+++ b/Packages/viewerbase/server/methods/worklistSearch.js
@@ -1,6 +1,13 @@
Meteor.methods({
- 'WorklistSearch' : function(filter) {
- var server = Meteor.settings.dicomWeb.endpoints[0];
- return Services.QIDO.Studies(server, filter);
- }
+ /**
+ * Use the specified filter to conduct a search from the DICOM server
+ * @param filter
+ */
+ 'WorklistSearch': function(filter) {
+ // Get the server data. This is user-defined in the
+ // config.json files used to run the Meteor server
+ var server = Meteor.settings.dicomWeb.endpoints[0];
+
+ return Services.QIDO.Studies(server, filter);
+ }
});
\ No newline at end of file
diff --git a/Packages/viewerbase/server/services/qido/instances.js b/Packages/viewerbase/server/services/qido/instances.js
index 27634ea39..6c16b2f4d 100644
--- a/Packages/viewerbase/server/services/qido/instances.js
+++ b/Packages/viewerbase/server/services/qido/instances.js
@@ -1,59 +1,83 @@
+/**
+ * Creates a QIDO URL given the server settings and a study instance UID
+ * @param server
+ * @param studyInstanceUid
+ * @returns {string} URL to be used for QIDO calls
+ */
function buildUrl(server, studyInstanceUid) {
-
- var url = server.qidoRoot + '/studies/' + studyInstanceUid + '/instances';
-
- return url;
+ return server.qidoRoot + '/studies/' + studyInstanceUid + '/instances';
}
+/**
+ * Parses data returned from a QIDO search and transforms it into
+ * an array of series that are present in the study
+ *
+ * @param server The DICOM server
+ * @param studyInstanceUid
+ * @param resultData
+ * @returns {Array} Series List
+ */
function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
- var seriesMap = {};
- var seriesList = [];
- resultData.forEach(function(instance) {
- var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
- var series = seriesMap[seriesInstanceUid];
- if(!series) {
- series = {
- seriesInstanceUid : seriesInstanceUid,
- seriesNumber : DICOMWeb.getString(instance['00200011']),
- instances: []
- };
- seriesMap[seriesInstanceUid] = series;
- seriesList.push(series);
- }
+ var seriesMap = {};
+ var seriesList = [];
- // The uri for the dicomweb
- // NOTE: DCM4CHEE seems to return the data zipped
- // NOTE: Orthanc returns the data with multi-part mime which cornerstoneWADOImageLoader doesn't
- // know how to parse yet
- //var uri = DICOMWeb.getString(instance['00081190']);
- //uri = uri.replace('wado-rs', 'dicom-web');
+ resultData.forEach(function(instance) {
+ // Use seriesMap to cache series data
+ // If the series instance UID has already been used to
+ // process series data, continue using that series
+ var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
+ var series = seriesMap[seriesInstanceUid];
- // manually create a WADO-URI from the UID's
- // NOTE: Haven't been able to get Orthanc's WADO-URI to work yet - maybe its not configured?
- var sopInstanceUid = DICOMWeb.getString(instance['00080018']);
- var uri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom";
+ // If no series data exists in the seriesMap cache variable,
+ // process any available series data
+ if(!series) {
+ series = {
+ seriesInstanceUid : seriesInstanceUid,
+ seriesNumber : DICOMWeb.getString(instance['00200011']),
+ instances: []
+ };
- series.instances.push({
- sopClassUid: DICOMWeb.getString(instance['00080016']),
- sopInstanceUid: sopInstanceUid,
- uri: uri,
- instanceNumber: DICOMWeb.getString(instance['00200013'])
- });
+ // Save this data in the seriesMap cache variable
+ seriesMap[seriesInstanceUid] = series;
+ seriesList.push(series);
+ }
+
+ // The uri for the dicomweb
+ // NOTE: DCM4CHEE seems to return the data zipped
+ // NOTE: Orthanc returns the data with multi-part mime which cornerstoneWADOImageLoader doesn't
+ // know how to parse yet
+ //var uri = DICOMWeb.getString(instance['00081190']);
+ //uri = uri.replace('wado-rs', 'dicom-web');
+
+ // manually create a WADO-URI from the UIDs
+ // NOTE: Haven't been able to get Orthanc's WADO-URI to work yet - maybe its not configured?
+ var sopInstanceUid = DICOMWeb.getString(instance['00080018']);
+ var uri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom";
+
+ // Add this instance to the current series
+ series.instances.push({
+ sopClassUid: DICOMWeb.getString(instance['00080016']),
+ sopInstanceUid: sopInstanceUid,
+ uri: uri,
+ instanceNumber: DICOMWeb.getString(instance['00200013'])
+ });
});
return seriesList;
}
+/**
+ * Retrieve a set of instances using a QIDO call
+ * @param server
+ * @param studyInstanceUid
+ * @returns {{wadoUriRoot: String, studyInstanceUid: String, seriesList: Array}}
+ */
Services.QIDO.Instances = function(server, studyInstanceUid) {
+ var url = buildUrl(server, studyInstanceUid);
+ var result = DICOMWeb.getJSON(url, server.requestOptions);
- var url = buildUrl(server, studyInstanceUid);
-
- var result = DICOMWeb.getJSON(url, server.requestOptions);
-
- var study = {
- wadoUriRoot: server.wadoUriRoot,
- studyInstanceUid: studyInstanceUid,
- seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
- };
-
- return study;
+ return {
+ wadoUriRoot: server.wadoUriRoot,
+ studyInstanceUid: studyInstanceUid,
+ seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
+ };
};
\ No newline at end of file
diff --git a/Packages/viewerbase/server/services/qido/studies.js b/Packages/viewerbase/server/services/qido/studies.js
index 9feb9ede0..cfcfd3c87 100644
--- a/Packages/viewerbase/server/services/qido/studies.js
+++ b/Packages/viewerbase/server/services/qido/studies.js
@@ -1,38 +1,56 @@
+/**
+ * Produces a QIDO URL given server details and a set of specified search filter
+ * items
+ *
+ * @param server
+ * @param filter
+ * @returns {string} The URL with encoded filter query data
+ */
function filterToQIDOURL(server, filter) {
- var parameters = {
- PatientName : filter.patientName,
- PatientID: filter.patientId,
- AccessionNumber : filter.accessionNumber,
- limit : filter.limit || 20,
- includefield : server.qidoSupportsIncludeField ? 'all' : undefined
- };
- var url = server.qidoRoot + '/studies?' + encodeQueryData(parameters);
- return url;
+ var parameters = {
+ PatientName : filter.patientName,
+ PatientID: filter.patientId,
+ AccessionNumber : filter.accessionNumber,
+ limit : filter.limit || 20,
+ includefield : server.qidoSupportsIncludeField ? 'all' : undefined
+ };
+ return server.qidoRoot + '/studies?' + encodeQueryData(parameters);
}
+/**
+ * Parses resulting data from a QIDO call into a set of Study MetaData
+ *
+ * @param resultData
+ * @returns {Array} An array of Study MetaData objects
+ */
function resultDataToStudies(resultData) {
- var studies = [];
- resultData.forEach(function(study) {
- studies.push({
- patientName: DICOMWeb.getName(study['00100010']),
- patientId: DICOMWeb.getString(study['00100020']),
- accessionNumber : DICOMWeb.getString(study['00080050']),
- studyDate: DICOMWeb.getString(study['00080020']),
- modalities: DICOMWeb.getString(study['00080061']),
- studyDescription: DICOMWeb.getString(study['00081030']),
- imageCount: DICOMWeb.getString(study['00201208']),
- studyInstanceUid: DICOMWeb.getString(study['0020000D'])
- //url : server.wadoRoot + '/studies/' + DICOMWeb.getString(study['0020000D']) + '/metadata'
+ var studies = [];
+
+ resultData.forEach(function(study) {
+ studies.push({
+ studyInstanceUid: DICOMWeb.getString(study['0020000D']),
+ // 00080005 = SpecificCharacterSet
+ studyDate: DICOMWeb.getString(study['00080020']),
+ studyTime: DICOMWeb.getString(study['00080030']),
+ accessionNumber: DICOMWeb.getString(study['00080050']),
+ referringPhysicianName: DICOMWeb.getString(study['00080090']),
+ // 00081190 = URL
+ patientName: DICOMWeb.getName(study['00100010']),
+ patientId: DICOMWeb.getString(study['00100020']),
+ patientBirthdate: DICOMWeb.getString(study['00100030']),
+ patientSex: DICOMWeb.getString(study['00100040']),
+ imageCount: DICOMWeb.getString(study['00201208']),
+ studyId: DICOMWeb.getString(study['00200010']),
+ studyDescription: DICOMWeb.getString(study['00081030']),
+ modalities: DICOMWeb.getString(study['00080061'])
+ });
});
- });
- return studies;
+ return studies;
}
+
Services.QIDO.Studies = function(server, filter) {
-
- var url = filterToQIDOURL(server, filter);
-
- var result = DICOMWeb.getJSON(url, server.requestOptions);
-
- return resultDataToStudies(result.data);
+ var url = filterToQIDOURL(server, filter);
+ var result = DICOMWeb.getJSON(url, server.requestOptions);
+ return resultDataToStudies(result.data);
};
\ No newline at end of file
diff --git a/Packages/viewerbase/server/services/wado/retrieveMetadata.js b/Packages/viewerbase/server/services/wado/retrieveMetadata.js
index 7bfebbc95..9f6734003 100644
--- a/Packages/viewerbase/server/services/wado/retrieveMetadata.js
+++ b/Packages/viewerbase/server/services/wado/retrieveMetadata.js
@@ -1,88 +1,139 @@
+/**
+ * Creates a URL for a WADO search
+ *
+ * @param server
+ * @param studyInstanceUid
+ * @returns {string}
+ */
function buildUrl(server, studyInstanceUid) {
-
- var url = server.wadoRoot + '/studies/' + studyInstanceUid + '/metadata';
-
- return url;
+ return server.wadoRoot + '/studies/' + studyInstanceUid + '/metadata';
}
+/**
+ * Parses the SourceImageSequence, if it exists, in order
+ * to return a ReferenceSOPInstanceUID. The ReferenceSOPInstanceUID
+ * is used to refer to this image in any accompanying DICOM-SR documents.
+ *
+ * @param instance
+ * @returns {String} The ReferenceSOPInstanceUID
+ */
+function getSourceImageInstanceUid(instance) {
+ // TODO= Parse the whole Source Image Sequence
+ // This is a really poor workaround for now.
+ // Later we should probably parse the whole sequence.
+ var SourceImageSequence = instance['00082112'];
+ if (SourceImageSequence && SourceImageSequence.Value && SourceImageSequence.Value.length) {
+ return SourceImageSequence.Value[0]['00081155'].Value[0];
+ }
+}
+
+/**
+ * Parses result data from a WADO search into Study MetaData
+ * Returns an object populated with study metadata, including the
+ * series list.
+ *
+ * @param server
+ * @param studyInstanceUid
+ * @param resultData
+ * @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
+ */
function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
- var seriesMap = {};
- var seriesList = [];
- resultData.forEach(function(instance) {
- var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
- var series = seriesMap[seriesInstanceUid];
- if(!series) {
- series = {
- seriesDescription: DICOMWeb.getString(instance['0008103E']),
- modality: DICOMWeb.getString(instance['00080060']),
- seriesInstanceUid : seriesInstanceUid,
- seriesNumber : DICOMWeb.getNumber(instance['00200011']),
- instances: []
- };
- seriesMap[seriesInstanceUid] = series;
- seriesList.push(series);
+ var seriesMap = {};
+ var seriesList = [];
+
+ if (!resultData.length) {
+ return;
}
- var sopInstanceUid = DICOMWeb.getString(instance['00080018']);
+ var anInstance = resultData[0];
+ if (!anInstance) {
+ return;
+ }
- var instanceSummary = {
- // -----------
- // TODO = Fix this
- // This doesn't seem like the best place to put this, but otherwise we have no study info
- patientName: DICOMWeb.getName(instance['00100010']),
- patientId: DICOMWeb.getString(instance['00100020']),
- accessionNumber : DICOMWeb.getString(instance['00080050']),
- studyDate: DICOMWeb.getString(instance['00080020']),
- modalities: DICOMWeb.getString(instance['00080061']),
- studyDescription: DICOMWeb.getString(instance['00081030']),
- imageCount: DICOMWeb.getString(instance['00201208']),
- studyInstanceUid: DICOMWeb.getString(instance['0020000D']),
- // -----------
- imageType: DICOMWeb.getString(instance['00080008']),
- sopClassUid: DICOMWeb.getString(instance['00080016']),
- sopInstanceUid: sopInstanceUid,
- instanceNumber: DICOMWeb.getNumber(instance['00200013']),
- imagePositionPatient: DICOMWeb.getString(instance['00200032']),
- imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
- frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
- sliceLocation: DICOMWeb.getNumber(instance['00201041']),
- samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
- photometricInterpretation: DICOMWeb.getString(instance['00280004']),
- rows: DICOMWeb.getNumber(instance['00280010']),
- columns: DICOMWeb.getNumber(instance['00280011']),
- pixelSpacing: DICOMWeb.getString(instance['00280030']),
- bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
- bitsStored: DICOMWeb.getNumber(instance['00280101']),
- highBit: DICOMWeb.getNumber(instance['00280102']),
- pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
- windowCenter: DICOMWeb.getString(instance['00281050']),
- windowWidth: DICOMWeb.getString(instance['00281051']),
- rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
- rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
+ var studyData = {
+ seriesList: seriesList,
+ patientName: DICOMWeb.getName(anInstance['00100010']),
+ patientId: DICOMWeb.getString(anInstance['00100020']),
+ accessionNumber: DICOMWeb.getString(anInstance['00080050']),
+ studyDate: DICOMWeb.getString(anInstance['00080020']),
+ modalities: DICOMWeb.getString(anInstance['00080061']),
+ studyDescription: DICOMWeb.getString(anInstance['00081030']),
+ imageCount: DICOMWeb.getString(anInstance['00201208']),
+ studyInstanceUid: DICOMWeb.getString(anInstance['0020000D']),
};
- if(server.imageRendering === 'wadouri') {
- instanceSummary.wadouri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom";
- } else {
- instanceSummary.wadorsuri = server.wadoRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
- }
+ resultData.forEach(function(instance) {
+ var seriesInstanceUid = DICOMWeb.getString(instance['0020000E']);
+ var series = seriesMap[seriesInstanceUid];
+ if (!series) {
+ series = {
+ seriesDescription: DICOMWeb.getString(instance['0008103E']),
+ modality: DICOMWeb.getString(instance['00080060']),
+ seriesInstanceUid: seriesInstanceUid,
+ seriesNumber: DICOMWeb.getNumber(instance['00200011']),
+ instances: []
+ };
+ seriesMap[seriesInstanceUid] = series;
+ seriesList.push(series);
+ }
- series.instances.push(instanceSummary);
- });
- return seriesList;
+ var sopInstanceUid = DICOMWeb.getString(instance['00080018']);
+
+ var instanceSummary = {
+ imageType: DICOMWeb.getString(instance['00080008']),
+ sopClassUid: DICOMWeb.getString(instance['00080016']),
+ sopInstanceUid: sopInstanceUid,
+ instanceNumber: DICOMWeb.getNumber(instance['00200013']),
+ imagePositionPatient: DICOMWeb.getString(instance['00200032']),
+ imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
+ frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
+ sliceLocation: DICOMWeb.getNumber(instance['00201041']),
+ samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
+ photometricInterpretation: DICOMWeb.getString(instance['00280004']),
+ rows: DICOMWeb.getNumber(instance['00280010']),
+ columns: DICOMWeb.getNumber(instance['00280011']),
+ pixelSpacing: DICOMWeb.getString(instance['00280030']),
+ bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
+ bitsStored: DICOMWeb.getNumber(instance['00280101']),
+ highBit: DICOMWeb.getNumber(instance['00280102']),
+ pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
+ windowCenter: DICOMWeb.getString(instance['00281050']),
+ windowWidth: DICOMWeb.getString(instance['00281051']),
+ rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
+ rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
+ sourceImageInstanceUid: getSourceImageInstanceUid(instance)
+ };
+
+ if (server.imageRendering === 'wadouri') {
+ instanceSummary.wadouri = server.wadoUriRoot + '?requestType=WADO&studyUID=' + studyInstanceUid + '&seriesUID=' + seriesInstanceUid + '&objectUID=' + sopInstanceUid + "&contentType=application%2Fdicom";
+ } else {
+ instanceSummary.wadorsuri = server.wadoRoot + '/studies/' + studyInstanceUid + '/series/' + seriesInstanceUid + '/instances/' + sopInstanceUid + '/frames/1';
+ }
+
+ series.instances.push(instanceSummary);
+ });
+
+ return studyData;
}
+/**
+ * Retrieved Study MetaData from a DICOM server using a WADO call
+ * @param server
+ * @param studyInstanceUid
+ * @returns {{seriesList: Array, patientName: *, patientId: *, accessionNumber: *, studyDate: *, modalities: *, studyDescription: *, imageCount: *, studyInstanceUid: *}}
+ */
Services.WADO.RetrieveMetadata = function(server, studyInstanceUid) {
+ var url = buildUrl(server, studyInstanceUid);
- var url = buildUrl(server, studyInstanceUid);
+ var result = DICOMWeb.getJSON(url, server.requestOptions);
- var result = DICOMWeb.getJSON(url, server.requestOptions);
+ var study = resultDataToStudyMetadata(server, studyInstanceUid, result.data);
+ if (!study) {
+ study = {};
+ }
- var study = {
- wadoUriRoot: server.wadoUriRoot,
- studyInstanceUid: studyInstanceUid,
- seriesList: resultDataToStudyMetadata(server, studyInstanceUid, result.data)
- };
+ study.wadoUriRoot = server.wadoUriRoot;
+ study.studyInstanceUid = studyInstanceUid;
- return study;
+ return study;
};
\ No newline at end of file
diff --git a/Packages/worklist/components/tabContent/tabContent.html b/Packages/worklist/components/tabContent/tabContent.html
index efe106f26..390d5f74a 100644
--- a/Packages/worklist/components/tabContent/tabContent.html
+++ b/Packages/worklist/components/tabContent/tabContent.html
@@ -1,4 +1,5 @@
+ {{ >loadingText }}
\ No newline at end of file
diff --git a/Packages/worklist/components/tabContent/tabContent.styl b/Packages/worklist/components/tabContent/tabContent.styl
new file mode 100644
index 000000000..f7820215b
--- /dev/null
+++ b/Packages/worklist/components/tabContent/tabContent.styl
@@ -0,0 +1,4 @@
+.tab-pane
+ .loadingTextDiv
+ h5
+ color: #777
\ No newline at end of file
diff --git a/Packages/worklist/components/tabTitle/tabTitle.js b/Packages/worklist/components/tabTitle/tabTitle.js
index 662aa8fd7..10b4ac3d9 100644
--- a/Packages/worklist/components/tabTitle/tabTitle.js
+++ b/Packages/worklist/components/tabTitle/tabTitle.js
@@ -1,19 +1,46 @@
Template.tabTitle.events({
+ /**
+ * Closes a tab when the close button is pressed in the title
+ * The next tab to the left is loaded if the current tab is closed
+ *
+ * @param e The click event used to close the tab
+ */
'click .close': function(e) {
+ // Identify the tab title DOM node
var tab = $(e.currentTarget).parents('a[data-toggle="tab"]').eq(0);
+
+ // Get the relevent contentId that this tab title represents
+ // Replace any hash marks (#) that were required by Bootstrap's tab switching
var contentId = tab.data("target").replace("#", "");
+
+ // Check if we are closing the active tab. If we are, prepare to switch
+ // to the next tab to the left.
+ var activeContentId = Session.get('activeContentId');
+ if (activeContentId === contentId) {
+ // Find the index of the tab that is being closed
+ var tabIndex = tab.parent('li').index();
- var tabIndex = tab.parent('li').index();
- var newActiveTabIndex = Math.max(tabIndex - 1, 0);
- var newActiveTab = $(".tabTitle").eq(newActiveTabIndex);
- var newActiveTabLink = newActiveTab.find("a[data-toggle=tab]");
- var newContentId = newActiveTabLink.data("target").replace("#", "");
+ // Find the index the tab to its left
+ var newActiveTabIndex = Math.max(tabIndex - 1, 0);
- switchToTab(newContentId);
-
- var tabObjectId = tabs.find({contentid: contentId}).fetch()[0]._id;
- tabs.remove(tabObjectId);
+ // Find the DOM node of the tab that will be activated
+ var newActiveTab = $(".tabTitle").eq(newActiveTabIndex);
+ // Find the content ID of the tab that will be switched to
+ var newActiveTabLink = newActiveTab.find("a[data-toggle=tab]");
+ var newContentId = newActiveTabLink.data("target").replace("#", "");
+
+ // Switch to this tab
+ switchToTab(newContentId);
+ }
+
+ // Find the tab to be closed in the Tabs collection
+ var tabObjectId = WorklistTabs.findOne({contentid: contentId})._id;
+
+ // Remove this tab from the Tabs collection so it is no longer rendered
+ WorklistTabs.remove(tabObjectId);
+
+ // Remove any stored data related to this tab from the global ViewerData structure
delete ViewerData[contentId];
}
});
\ No newline at end of file
diff --git a/Packages/worklist/components/tabTitle/tabTitle.styl b/Packages/worklist/components/tabTitle/tabTitle.styl
index 0519ecba6..1c9b8bbbb 100644
--- a/Packages/worklist/components/tabTitle/tabTitle.styl
+++ b/Packages/worklist/components/tabTitle/tabTitle.styl
@@ -1 +1,4 @@
-
\ No newline at end of file
+.tabTitle
+ button.close
+ // Prevent blue glow around button in Chrome
+ outline: none
\ No newline at end of file
diff --git a/Packages/worklist/components/worklist.html b/Packages/worklist/components/worklist.html
index e890fb4de..b7207fba3 100644
--- a/Packages/worklist/components/worklist.html
+++ b/Packages/worklist/components/worklist.html
@@ -3,7 +3,7 @@
- {{ #each tabs }}
+ {{ #each worklistTabs }}
{{ >tabContent }}
{{ /each }}
diff --git a/Packages/worklist/components/worklist.js b/Packages/worklist/components/worklist.js
index 097a9ea7a..267921307 100644
--- a/Packages/worklist/components/worklist.js
+++ b/Packages/worklist/components/worklist.js
@@ -1,41 +1,124 @@
+/**
+ * Template: Worklist
+ *
+ * This is the main component of the Worklist package
+ */
+
+// Define the ViewerData global object
+// If there is currently any Session data for this object,
+// use this to repopulate the variable
ViewerData = Session.get('ViewerData') || {};
-Template.worklist.helpers({
- 'tabs': function() {
- console.log('Updating tabs');
- return tabs.find();
- }
-});
+// Define the StudyMetaData object. This is used as a cache
+// to store study meta data information to prevent unnecessary
+// calls to the server
+var StudyMetaData = {};
-Template.worklist.events({
- 'click a[data-toggle="tab"]': function(e) {
- var contentId = $(e.currentTarget).data('target').replace("#", "");
- switchToTab(contentId);
- }
-});
+// Create the WorklistTabs collection
+WorklistTabs = new Meteor.Collection(null);
-switchToTab = function(contentId) {
- var data = ViewerData[contentId];
+/**
+* Retrieves study metadata using a server call, and fires a callback
+* when completed.
+*
+* @params {string} studyInstanceUid The UID of the Study to be retrieved
+* @params {function} doneCallback The callback function to be executed when the study retrieval has finished
+*/
+getStudyMetadata = function(studyInstanceUid, doneCallback) {
+ log.info('worklistStudy getStudyMetadata');
- console.log("Switching to tab: " + contentId);
- $('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
-
- $("#viewer").remove();
-
- var container = $('.tab-content').find("#" + contentId).get(0);
- if (!container) {
+ // If the StudyMetaData cache already has data related to this
+ // studyInstanceUid, then we should fire the doneCallback with this data
+ // and stop here.
+ var study = StudyMetaData[studyInstanceUid];
+ if (study) {
+ doneCallback(study);
return;
}
+ // If no study metadata is in the cache variable, we need to retrieve it from
+ // the server with a call.
+ Meteor.call('GetStudyMetadata', studyInstanceUid, function(error, study) {
+ // Once we have retrieved the data, we sort the series' by series
+ // and instance number in ascending order
+ sortStudy(study);
+
+ // Then we store this data in the cache variable
+ StudyMetaData[studyInstanceUid] = study;
+
+ // Finally, we fire the doneCallback with this study meta data
+ doneCallback(study);
+ });
+};
+
+/**
+ * Switches to a new tab in the tabbed worklist container
+ * This function renders either the Worklist or the Viewer template with new data.
+ *
+ * @param contentId The unique ID of the tab to be switched to
+ */
+switchToTab = function(contentId) {
+ log.info("Switching to tab: " + contentId);
+
+ // Use Bootstrap's Tab JavaScript to show the contents of the current tab
+ // Unless it is the worklist, it is currently an empty div
+ $('.tabTitle a[data-target="#' + contentId + '"]').tab('show');
+
+ // Remove any previous Viewers from the DOM
+ $("#viewer").remove();
+
+ // Update the 'activeContentId' variable in Session
+ Session.set("activeContentId", contentId);
+
+ // If we are switching to the Worklist tab, reset any CSS styles
+ // that have been applied to prevent scrolling in the Viewer.
+ // Then stop here, since nothing needs to be re-rendered.
if (contentId === 'worklistTab') {
- console.log('Switching to worklist');
document.body.style.overflow = null;
document.body.style.height = null;
document.body.style.minWidth = null;
document.body.style.position = null;
- } else {
+ return;
+ }
+
+ // Get tab content container given the contentId string
+ // If no such container exists, stop here because something is wrong
+ var container = $('.tab-content').find("#" + contentId).get(0);
+ if (!container) {
+ log.warn('No container present with the contentId: ' + contentId);
+ return;
+ }
+
+ // Use the stored ViewerData global object to retrieve the studyInstanceUid
+ // related to this tab
+ var studyInstanceUid = ViewerData[contentId].studyInstanceUid;
+
+ // Attempt to retrieve the meta data (it might be cached)
+ getStudyMetadata(studyInstanceUid, function(study) {
+
+ // Once we have the study data, store it in a structure with
+ // any other saved data about this tab (e.g. layout structure)
+ var data = {
+ viewportRows: ViewerData[contentId].viewportRows,
+ viewportColumns: ViewerData[contentId].viewportColumns,
+ contentId: contentId,
+ studies: [study]
+ };
+
+ // Update the Session variable with the loaded studies
+ Session.set('studies', data.studies);
+
+ // Remove the loading text template that is inside the tab container by default
+ container.innerHTML = "";
+
+ // Use Blaze to render the Viewer Template into the container
UI.renderWithData(Template.viewer, data, container);
+
+ // Retrieve the DOM element of the viewer
var imageViewer = $("#viewer");
+
+ // If it is present in the DOM (it should be), then apply
+ // styles to prevent page scrolling and overscrolling on mobile devices
if (imageViewer) {
document.body.style.overflow = "hidden";
document.body.style.height = '100%';
@@ -43,33 +126,71 @@ switchToTab = function(contentId) {
document.body.style.minWidth = 0;
document.body.style.position = 'fixed'; // Prevent overscroll on mobile devices
}
- }
+ });
+};
+
+/**
+ * Opens a new tab in the tabbed worklist environment using
+ * a given study and new tab title.
+ *
+ * @param studyInstanceUid The UID of the Study to be opened
+ * @param title The title to be used for the tab heading
+ */
+openNewTab = function(studyInstanceUid, title) {
+ // Generate a unique ID to represent this tab
+ // We can't just use the Mongo entry ID because
+ // then it will change after hot-reloading.
+ var contentid = generateUUID();
+
+ // Create a new entry in the WorklistTabs Collection
+ WorklistTabs.insert({
+ title: title,
+ contentid: contentid,
+ active: false
+ });
+
+ // Update the ViewerData global object
+ ViewerData[contentid] = {
+ title: title,
+ contentid: contentid,
+ studyInstanceUid: studyInstanceUid
+ };
+
+ // Switch to the new tab
+ switchToTab(contentid);
};
Template.worklist.onRendered(function() {
- this.autorun(function() {
- var data = Session.get('OpenNewTabEvent');
-
- // If we have no new tab data, stop here
- // (e.g. if we are rendering the worklist)
- if (!data) {
- return;
- }
-
- var contentId = data.contentid;
- if (ViewerData.hasOwnProperty(contentId)) {
- console.warn('Contentid already exists?');
- return;
- }
-
- // Update the viewer data object
- ViewerData[contentId] = {
- contentId: contentId,
- studies: data.studies,
- title: data.title
- };
- Session.set('ViewerData', ViewerData);
-
+ // If there is a tab set as active in the Session,
+ // switch to that now.
+ var contentId = Session.get("activeContentId");
+ if (contentId) {
switchToTab(contentId);
- });
+ }
+});
+
+
+Template.worklist.helpers({
+ /**
+ * Returns the current set of Worklist Tabs
+ * @returns Meteor.Collection The current state of the WorklistTabs Collection
+ */
+ 'worklistTabs': function() {
+ return WorklistTabs.find();
+ }
+});
+
+Template.worklist.events({
+ 'click #tablist a[data-toggle="tab"]': function(e) {
+ // If this tab is already active, do nothing
+ var tabButton = $(e.currentTarget);
+ var tabTitle = tabButton.parents('.tabTitle');
+ if (tabTitle.hasClass("active")) {
+ return;
+ }
+
+ // Otherwise, switch to the tab
+ var contentId = tabButton.data('target').replace("#", "");
+ switchToTab(contentId);
+ }
});
\ No newline at end of file
diff --git a/Packages/worklist/components/worklist.styl b/Packages/worklist/components/worklist.styl
index fb8aa3514..140eef433 100644
--- a/Packages/worklist/components/worklist.styl
+++ b/Packages/worklist/components/worklist.styl
@@ -46,6 +46,7 @@ body
&:hover
background-color: #424242
+ cursor: none
a
color: white
diff --git a/Packages/worklist/components/worklistResult/worklistResult.html b/Packages/worklist/components/worklistResult/worklistResult.html
index 2233871c4..ecab9fda3 100644
--- a/Packages/worklist/components/worklistResult/worklistResult.html
+++ b/Packages/worklist/components/worklistResult/worklistResult.html
@@ -1,10 +1,11 @@
-
+ {{ else }}
+ {{ >loadingText }}
+ {{ /if }}
\ No newline at end of file
diff --git a/Packages/worklist/components/worklistResult/worklistResult.js b/Packages/worklist/components/worklistResult/worklistResult.js
index 56a198edc..ab75de242 100644
--- a/Packages/worklist/components/worklistResult/worklistResult.js
+++ b/Packages/worklist/components/worklistResult/worklistResult.js
@@ -1,7 +1,14 @@
+// Define the Studies Collection
+// This is a client-side only Collection which
+// Stores the list of studies in the Worklist
Studies = new Mongo.Collection(null);
Template.worklistResult.helpers({
+ /**
+ * Returns a sorted instance of the Studies Collection
+ * by Patient name and Study Date in Ascending order.
+ */
studies : function() {
- return Studies.find();
+ return Studies.find({}, {sort: {patientName : 1, studyDate : 1}});
}
-});
\ No newline at end of file
+});
diff --git a/Packages/worklist/components/worklistSearch/worklistSearch.js b/Packages/worklist/components/worklistSearch/worklistSearch.js
index af31b5f18..6f7c09514 100644
--- a/Packages/worklist/components/worklistSearch/worklistSearch.js
+++ b/Packages/worklist/components/worklistSearch/worklistSearch.js
@@ -1,4 +1,4 @@
-//Get all studies
+// Retrieve all studies
search();
var studyDateFrom;
@@ -6,6 +6,13 @@ var studyDateTo;
var checkFrom = false;
var checkTo = false;
+/**
+ * Transforms an input string into a search filter for
+ * the Worklist Search call
+ *
+ * @param filter The input string to be searched for
+ * @returns {*}
+ */
function getFilter(filter) {
if(filter && filter.length && filter.substr(filter.length - 1) !== '*') {
filter += '*';
@@ -13,16 +20,19 @@ function getFilter(filter) {
return filter;
}
-//Search value
+/**
+ * Search for a value in a string
+ */
function isIndexOf(mainVal, searchVal) {
- if(mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
+ if (mainVal === undefined || mainVal === '' || mainVal.indexOf(searchVal) > -1){
return true;
}
-
return false;
}
-//Replace object is undefined
+/**
+ * Replace object if undefined
+ */
function replaceUndefinedColumnValue (text) {
if (text == undefined || text === "undefined") {
return "";
@@ -31,94 +41,96 @@ function replaceUndefinedColumnValue (text) {
}
}
-//Convert string to study date
+/**
+ * Convert string to study date
+ */
function convertStringToStudyDate (dateStr) {
var y = dateStr.substring(0,4);
var m = dateStr.substring(4,6);
var d = dateStr.substring(6,8);
var newDateStr = y+"/"+m+"/"+d;
- var date_ = new Date(newDateStr);
- return date_;
+ return new Date(newDateStr);
}
+/**
+ * Runs a search for studies matching the worklist query parameters
+ * Inserts the identified studies into the Studies Collection
+ */
function search() {
+ // Create the filters to be used for the Worklist Search
var filter = {
patientName: getFilter($('#patientName').val()),
patientId: getFilter($('#patientId').val()),
accessionNumber: getFilter($('#patientAccessionNumber').val())
};
+ // Make sure that modality has a reasonable value, since it is occasionally
+ // returned as 'undefined'
var modality = replaceUndefinedColumnValue($('#modality').val());
+ // Clear all current studies
Studies.remove({});
+
Meteor.call('WorklistSearch', filter, function(error, studies) {
- if (studies) {
- studies.forEach(function(study) {
-
- if(isIndexOf(study.modalities, modality) &&
- (new Date(studyDateFrom).setHours(0,0,0,0) <= convertStringToStudyDate(study.studyDate) || !checkFrom) &&
- (convertStringToStudyDate(study.studyDate) <= new Date(studyDateTo).setHours(0,0,0,0) || !checkTo)) {
-
- Studies.insert(study);
- }
- });
+ if (!studies) {
+ return;
}
+
+ // Loop through all identified studies
+ studies.forEach(function(study) {
+
+ // Search the rest of the parameters that aren't done via the server call
+ if(isIndexOf(study.modalities, modality) &&
+ (new Date(studyDateFrom).setHours(0,0,0,0) <= convertStringToStudyDate(study.studyDate) || !checkFrom) &&
+ (convertStringToStudyDate(study.studyDate) <= new Date(studyDateTo).setHours(0,0,0,0) || !checkTo)) {
+
+ // Insert any matching studies into the Studies Collection
+ Studies.insert(study);
+ }
+ });
});
}
Template.worklistSearch.events({
-
- //Search
+ /**
+ * Searches for studies given the current state of the form controls
+ */
'click button#btnSearch' :function(event) {
studyDateFrom = $('#studyDateFrom').val();
studyDateTo = $('#studyDateTo').val();
checkFrom = $('#checkFrom').is(':checked');
checkTo = $('#checkTo').is(':checked');
search();
-
- return false;
},
-
- //Today
+ /**
+ * Searches for studies with a study date equal to Today
+ */
'click button#btnToday' :function(event) {
studyDateTo = new Date();
studyDateFrom = studyDateTo;
checkFrom = true;
checkTo = true;
-
search();
-
- return false;
},
- //Clear
- 'click button#btnClear' :function(event) {
- $("#patientId").val("");
- $("#patientName").val("");
- $("#patientAccessionNumber").val("");
- $("#studyDescription").val("");
- $("#referringPhysician").val("");
- $("#studyDateFrom").val("");
- $("#studyDateTo").val("");
- $("#modality").val("");
- $('#checkFrom').prop('checked', false);
- $('#checkTo').prop('checked', false);
-
- },
-
- //Last 7 Days
- 'click button#btnLastSevenDays' :function(event) {
+ /**
+ * Searches for studies in the Last 7 Days
+ */
+ 'click button#btnLastSevenDays' :function() {
studyDateTo = new Date();
studyDateFrom = new Date();
studyDateFrom.setDate(studyDateFrom.getDate()-7);
checkFrom = true;
checkTo = true;
-
search();
-
- return false;
+ },
+ /**
+ * Clears all input data in the study search panel
+ */
+ 'click button#btnClear' :function(event) {
+ $("#searchPanel .form-control").val("");
+ $('#checkFrom').prop('checked', false);
+ $('#checkTo').prop('checked', false);
}
-
-
});
\ No newline at end of file
diff --git a/Packages/worklist/components/worklistStudy/worklistStudy.html b/Packages/worklist/components/worklistStudy/worklistStudy.html
index b82ba87c6..fd244e6c9 100644
--- a/Packages/worklist/components/worklistStudy/worklistStudy.html
+++ b/Packages/worklist/components/worklistStudy/worklistStudy.html
@@ -1,7 +1,7 @@
-