diff --git a/LesionTracker/client/components/viewer.js b/LesionTracker/client/components/viewer.js index 94f5e26f6..29dc1b671 100644 --- a/LesionTracker/client/components/viewer.js +++ b/LesionTracker/client/components/viewer.js @@ -69,7 +69,7 @@ Template.viewer.onCreated(function() { } var contentId = this.data.contentId; - + if (ViewerData[contentId].loadedSeriesData) { log.info('Reloading previous loadedSeriesData'); @@ -78,7 +78,7 @@ Template.viewer.onCreated(function() { } else { log.info('Setting default ViewerData'); OHIF.viewer.loadedSeriesData = {}; - + ViewerData[contentId].loadedSeriesData = OHIF.viewer.loadedSeriesData; // Update the viewer data object @@ -90,7 +90,6 @@ Template.viewer.onCreated(function() { Session.set('activeViewport', ViewerData[contentId].activeViewport || 0); - // Update the ViewerStudies collection with the loaded studies ViewerStudies = new Meteor.Collection(null); this.data.studies.forEach(function(study) { @@ -101,7 +100,7 @@ Template.viewer.onCreated(function() { var patientId = this.data.studies[0].patientId; Session.set('patientId', patientId); - timepointsAdded = false; + initialized = false; self.autorun(function() { var patientId = Session.get('patientId'); self.subscribe('timepoints', patientId); @@ -111,7 +110,7 @@ Template.viewer.onCreated(function() { return; } - if (timepointsAdded === true) { + if (initialized === true) { return; } @@ -138,12 +137,65 @@ Template.viewer.onCreated(function() { }); }); - timepointsAdded = true; + // This is used to re-add tools from the database into the + // Cornerstone ToolData structure + Measurements.find().observe({ + added: function (data) { + if (data.toolDataInsertedManually === true) { + return; + } + + log.info('Measurement added'); + addMeasurementAsToolData(data); + } + }); + + initialized = true; }); OHIF.viewer.updateImageSynchronizer = new cornerstoneTools.Synchronizer("CornerstoneNewImage", cornerstoneTools.updateImageSynchronizer); }); +function addMeasurementAsToolData(data) { + // Check what toolType we should be adding this to, based on the isTarget value + // of the stored Measurement + var toolType = data.isTarget ? 'lesion' : 'nonTarget'; + var toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.toolState; + + // Loop through the timepoint data for this measurement + Object.keys(data.timepoints).forEach(function(key) { + var storedData = data.timepoints[key]; + var imageId = storedData.imageId; + + if (!toolState[imageId]) { + toolState[imageId] = {}; + } + + if (!toolState[imageId][toolType]) { + toolState[imageId][toolType] = { + data: [] + }; + } + + // Create measurementData structure based on the lesion data at this timepoint + // We will add this into the toolData for this imageId + var measurementData = storedData; + measurementData.isTarget = data.isTarget; + measurementData.lesionNumber = data.lesionNumber; + measurementData.measurementText = data.measurementText; + measurementData.lesionName = data.lesionName; + measurementData.isDeleted = data.isDeleted; + measurementData.location = data.location; + measurementData.locationUID = data.locationUID; + measurementData.patientId = patientId; + measurementData.visible = data.visible; + measurementData.active = data.active; + measurementData.uid = data.uid; + + toolState[imageId][toolType].data.push(measurementData); + }); +} + Template.viewer.onDestroyed(function() { log.info("onDestroyed"); diff --git a/Packages/cornerstone/client/cornerstoneTools.js b/Packages/cornerstone/client/cornerstoneTools.js index 1fa1667e1..38ec70036 100644 --- a/Packages/cornerstone/client/cornerstoneTools.js +++ b/Packages/cornerstone/client/cornerstoneTools.js @@ -1,4 +1,4 @@ -/*! cornerstoneTools - v0.7.7 - 2015-11-21 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */ +/*! cornerstoneTools - v0.7.7 - 2015-11-22 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstoneTools */ // Begin Source: src/header.js if (typeof cornerstone === 'undefined') { cornerstone = {}; @@ -2111,40 +2111,6 @@ if (typeof cornerstoneTools === 'undefined') { } } - 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(); - } - ///////// BEGIN IMAGE RENDERING /////// function onImageRendered(e, eventData) { // if we have no toolData for this element, return immediately as there is nothing to do @@ -2184,9 +2150,9 @@ if (typeof cornerstoneTools === 'undefined') { var handleEndCanvas = cornerstone.pixelToCanvas(eventData.element, data.handles.end); if (config.arrowFirst) { - drawArrow(context, handleEndCanvas, handleStartCanvas, color, lineWidth); + cornerstoneTools.drawArrow(context, handleEndCanvas, handleStartCanvas, color, lineWidth); } else { - drawArrow(context, handleStartCanvas, handleEndCanvas, color, lineWidth); + cornerstoneTools.drawArrow(context, handleStartCanvas, handleEndCanvas, color, lineWidth); } if (config.drawHandles) { @@ -9792,6 +9758,52 @@ Display scroll progress bar across bottom of image. // End Source; src/util/copyPoints.js +// Begin Source: src/util/drawArrow.js +(function(cornerstoneTools) { + + 'use strict'; + + 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(); + } + + // Module exports + cornerstoneTools.drawArrow = drawArrow; + +})(cornerstoneTools); + +// End Source; src/util/drawArrow.js + // Begin Source: src/util/drawEllipse.js (function(cornerstoneTools) { diff --git a/Packages/lesiontracker/compatibility/lesionTool.js b/Packages/lesiontracker/compatibility/lesionTool.js index a5e0e0553..45a6fec5f 100644 --- a/Packages/lesiontracker/compatibility/lesionTool.js +++ b/Packages/lesiontracker/compatibility/lesionTool.js @@ -1,4 +1,3 @@ -var activeLesionMeasurementData; var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneTools) { "use strict"; @@ -66,6 +65,8 @@ var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneToo $(element).on('CornerstoneToolsMouseDown', eventData, cornerstoneTools.lesion.mouseDownCallback); $(element).on('CornerstoneToolsMouseDownActivate', eventData, cornerstoneTools.lesion.mouseDownActivateCallback); cornerstone.updateImage(element); + + updateLesionCollection(measurementData); }); } @@ -179,20 +180,8 @@ var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneToo return cornerstoneMath.point.insideRect(coords, handle.boundingBox); } - function subscribeLesionToolSelectedEvent(element) { - var elementEvents = $._data(element, "events"); - var index = Object.keys(elementEvents).indexOf("LesionToolSelected"); - if (index < 0) { - // Subscribe LesionToolSelected and calls loadImage when lesion measurement is changed or updated. - $(element).on("LesionToolSelected", loadImage); - } - - } ///////// BEGIN IMAGE RENDERING /////// function onImageRendered(e, eventData) { - - subscribeLesionToolSelectedEvent(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) { @@ -203,13 +192,91 @@ var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneToo var context = eventData.canvasContext.canvas.getContext('2d'); context.setTransform(1, 0, 0, 1, 0, 0); + var color; + var element = eventData.element; + var lineWidth = cornerstoneTools.toolStyle.getToolWidth(); + var config = cornerstoneTools.lesion.getConfiguration(); + for (var i = 0; i < toolData.data.length; i++) { - renderLesion(toolData.data[i], context, eventData); + var data = toolData.data[i]; + + context.save(); + + // 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(); + } + + // 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); + + 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); + + if (data.lesionName) { + //Draw linked line as dashed + context.beginPath(); + context.strokeStyle = color; + context.lineWidth = lineWidth; + context.setLineDash([2, 3]); + + var mid = { + x: (handleStartCanvas.x + handleEndCanvas.x) / 2, + y: (handleStartCanvas.y + handleEndCanvas.y) / 2 + }; + + context.moveTo(mid.x, mid.y); + context.lineTo(canvasTextLocation.x + 20, canvasTextLocation.y + 20); + context.stroke(); + + // 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); + } + + context.restore(); + updateLesionCollection(toolData.data[i]); } } function updateLesionCollection(lesionData) { + // TODO = Remove this in favour of measurement events if (!lesionData.active) { return; } @@ -217,176 +284,11 @@ var cornerstoneTools = (function($, cornerstone, cornerstoneMath, cornerstoneToo if (lesionData.timepointID && lesionData.timepointID !== "") { // Update Measurements Collection measurementManagerDAL.updateTimepointData(lesionData); - } else { - activeLesionMeasurementData = lesionData; } } - function renderLesion(data, context, eventData) { - context.save(); - - var color; - var element = eventData.element; - var lineWidth = cornerstoneTools.toolStyle.getToolWidth(); - 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(); - } - - // 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); - - 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); - - //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(canvasTextLocation.x + 20, canvasTextLocation.y + 20); - context.stroke(); - - // 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); - } - - - // TODO=Refactor because this function is repeated in nonTargetTool! - function loadImage(e, eventData) { - // If type is active, load image and activate lesion - // If type is inactive, update lesions of enabledElement as inactive - log.info('lesionTool loadImage'); - var element = eventData.enabledElement.element; - var imageId = eventData.lesionData.imageId; - if (eventData.type === "active") { - var stackToolDataSource = cornerstoneTools.getToolState(element, 'stack'); - var stackData = stackToolDataSource.data[0]; - var imageIdIndex = stackData.imageIds.indexOf(imageId); - if (imageIdIndex < 0) { - return; - } - - cornerstone.loadAndCacheImage(imageIds[imageIdIndex]).then(function(image) { - cornerstone.displayImage(element, image); - updateLesion(e, eventData); - }); - } else if (eventData.type === "inactive") { - updateLesion(e, eventData); - } - } - // module exports cornerstoneTools.lesion = cornerstoneTools.mouseButtonTool({ createNewMeasurement: createNewMeasurement, diff --git a/Packages/lesiontracker/compatibility/measurementManagerDAL.js b/Packages/lesiontracker/compatibility/measurementManagerDAL.js index bd3ecec6a..cf546f2ac 100644 --- a/Packages/lesiontracker/compatibility/measurementManagerDAL.js +++ b/Packages/lesiontracker/compatibility/measurementManagerDAL.js @@ -23,8 +23,8 @@ var measurementManagerDAL = (function() { longestDiameter: lesionData.measurementText, imageId: lesionData.imageId, seriesInstanceUid: lesionData.seriesInstanceUid, - studyInstanceUid: lesionData.studyInstanceUid - + studyInstanceUid: lesionData.studyInstanceUid, + handles: lesionData.handles }; } else { // Add null measurement @@ -32,22 +32,25 @@ var measurementManagerDAL = (function() { longestDiameter: "", imageId: "", seriesInstanceUid: "", - studyInstanceUid: "" + studyInstanceUid: "", + handles: undefined }; } timepointsObject[timepointId] = timepointObject; } - var lesionDataObject = { - patientId: timepoints[0].patientId, - lesionUID: uuid.v4(), - number: Measurements.find().count() + 1, - lesionNumber: lesionData.lesionNumber, - isTarget: lesionData.isTarget, - locationUID: lesionData.locationUID, - location: getLocationName(lesionData.locationUID), - timepoints: timepointsObject - }; + var lesionDataObject = lesionData; + + lesionDataObject.patientId = timepoints[0].patientId; + lesionDataObject.location = getLocationName(lesionData.locationUID); + lesionDataObject.timepoints = timepointsObject; + + // Is there a use for this? + lesionDataObject.number = Measurements.find().count() + 1; + + // TODO=Fix this workaround to prevent the observe hook from adding another set of toolData + lesionDataObject.toolDataInsertedManually = true; + Measurements.insert(lesionDataObject); } diff --git a/Packages/lesiontracker/compatibility/nonTargetTool.js b/Packages/lesiontracker/compatibility/nonTargetTool.js index e9f48b170..157430035 100644 --- a/Packages/lesiontracker/compatibility/nonTargetTool.js +++ b/Packages/lesiontracker/compatibility/nonTargetTool.js @@ -36,7 +36,7 @@ var element = mouseEventData.element; function doneCallback(lesionNumber) { - measurementData.lesionName = "Non-Target "+lesionNumber; + measurementData.lesionName = "Non-Target " + lesionNumber; measurementData.lesionNumber = lesionNumber; measurementData.active = true; cornerstone.updateImage(element); @@ -152,142 +152,88 @@ 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 subscribeNonTargetToolModifiedEvent(element) { - var elementEvents = $._data(element, "events"); - var index = Object.keys(elementEvents).indexOf("NonTargetToolSelected"); - if (index < 0) { - // Subscribe LesionToolModified and calls loadImage function when lesion measurement is changed or updated. - $(element).on("NonTargetToolSelected", loadImage); - } - - } ///////// BEGIN IMAGE RENDERING /////// function onImageRendered(e, eventData) { - subscribeNonTargetToolModifiedEvent(e.currentTarget); + var element = eventData.element; // 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) { + var toolData = cornerstoneTools.getToolState(element, toolType); + 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); - 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; + context.font = font; + for (var i = 0; i < toolData.data.length; i++) { + var data = toolData.data[i]; + + context.save(); + + // 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(); + } + + // Draw the arrow + var handleStartCanvas = cornerstone.pixelToCanvas(element, data.handles.start); + var handleEndCanvas = cornerstone.pixelToCanvas(element, data.handles.end); + var canvasTextLocation = cornerstone.pixelToCanvas(element, data.handles.textBox); + + if (config.arrowFirst) { + cornerstoneTools.drawArrow(context, handleEndCanvas, handleStartCanvas, color, lineWidth); + } else { + cornerstoneTools.drawArrow(context, handleStartCanvas, handleEndCanvas, color, lineWidth); + } + + if (config.drawHandles) { + cornerstoneTools.drawHandles(context, eventData, data.handles, color); + } else if (config.drawHandlesOnHover && data.handles.start.active) { + cornerstoneTools.drawHandles(context, eventData, [ lesion.handles.start ], color); + } else if (config.drawHandlesOnHover && data.handles.end.active) { + cornerstoneTools.drawHandles(context, eventData, [ lesion.handles.end ], color); + } + + // Draw the text + if (data.lesionName && data.lesionName !== '') { + //Draw linked line as dashed + var mid = { + x: (handleStartCanvas.x + handleEndCanvas.x) / 2, + y: (handleStartCanvas.y + handleEndCanvas.y) / 2 + }; + + context.beginPath(); + context.strokeStyle = color; + context.lineWidth = lineWidth; + context.setLineDash([2, 3]); + + context.moveTo(mid.x, mid.y); + context.lineTo(canvasTextLocation.x + 20, canvasTextLocation.y + 20); + context.stroke(); + + var boundingBox = cornerstoneTools.drawTextBox(context, data.lesionName, canvasTextLocation.x, canvasTextLocation.y, color); + data.handles.textBox.boundingBox = boundingBox; + } + + context.restore(); } - - 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; - 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 /////// @@ -295,7 +241,7 @@ var element = touchEventData.element; function doneCallback(lesionNumber) { - measurementData.lesionName = "Non-Target "+lesionNumber; + measurementData.lesionName = "Non-Target " + lesionNumber; measurementData.lesionNumber = lesionNumber; measurementData.active = true; cornerstone.updateImage(element); @@ -377,114 +323,6 @@ 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 seriesInstanceUid = eventObject.lesionData.seriesInstanceUid; - var studyInstanceUid = eventObject.lesionData.studyInstanceUid; - 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) { - 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 === lesionNumber - && eventObject.type !== "active" - && !isTarget - && data.seriesInstanceUid === seriesInstanceUid - && data.studyInstanceUid === studyInstanceUid) { - - 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.seriesInstanceUid === seriesInstanceUid - && data.studyInstanceUid === studyInstanceUid) { - - 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, eventData) { - // If type is active, load image and activate lesion - // If type is inactive, update lesions of enabledElement as inactive - log.info('nonTargetTool loadImage'); - var element = eventData.enabledElement.element; - var imageId = eventData.lesionData.imageId; - if (eventData.type === "active") { - var stackToolDataSource = cornerstoneTools.getToolState(element, 'stack'); - var stackData = stackToolDataSource.data[0]; - var imageIdIndex = stackData.imageIds.indexOf(imageId); - if (imageIdIndex < 0) { - return; - } - - cornerstone.loadAndCacheImage(stackData.imageIds[imageIdIndex]).then(function(image) { - cornerstone.displayImage(element, image); - updateLesion(e, eventData); - }); - } else if (eventData.type === "inactive") { - updateLesion(e, eventData); - } - } - cornerstoneTools.nonTarget = cornerstoneTools.mouseButtonTool({ addNewMeasurement: addNewMeasurement, createNewMeasurement: createNewMeasurement, diff --git a/Packages/lesiontracker/components/lesionTable/lesionTable.js b/Packages/lesiontracker/components/lesionTable/lesionTable.js index d7190bd04..cf9c454cd 100644 --- a/Packages/lesiontracker/components/lesionTable/lesionTable.js +++ b/Packages/lesiontracker/components/lesionTable/lesionTable.js @@ -1,74 +1,3 @@ -/** - * Returns timepoint object based on timepoint id of the enabled element - * - * @param timepoints - * @param enabledElement - * @returns {*|{}} Timepoint object based on timepoint id of the enabled element (or an empty Object) - */ -function getTimepointObject(imageId) { - var study = cornerstoneTools.metaData.get('study', imageId); - - var timepoint = Timepoints.findOne({timepointName: study.studyDate}); - return timepoint; -} - -/** - * Switch to the image of the correct image index - * Activate the selected measurement on the switched image (color to be green) - * Deactivate all other measurements on the switched image (color to be white) - */ -function activateMeasurements(element, measurementId) { - // TODO=Switch this to use the new CornerstoneToolMeasurementModified event, - // Once it has 'modified on activation' set up - - var enabledElement = cornerstone.getEnabledElement(element); - var imageId = enabledElement.image.imageId; - var timepointData = getTimepointObject(imageId); - var measurementData = Measurements.findOne(measurementId); - - var measurementAtTimepoint = measurementData.timepoints[timepointData.timepointID]; - if (!measurementAtTimepoint) { - return; - } - - // Defines event data - var eventData = { - enabledElement: enabledElement, - lesionData: { - id: measurementId, - isTarget: measurementData.isTarget, - lesionNumber: measurementData.lesionNumber, - imageId: imageId, - seriesInstanceUid: measurementAtTimepoint.seriesInstanceUid, - studyInstanceUid: measurementAtTimepoint.studyInstanceUid - }, - type: "active" - }; - - // If isTarget = false, this measurement is nonTarget measurement - // Activate related nonTarget measurement - // Deactivate all target measurements to activate only nonTarget measurement - if (!isTarget) { - $(element).trigger("NonTargetToolSelected", eventData); - - // Deactivate lesion tool measurements - eventData.type = "inactive"; - - $(element).trigger("LesionToolSelected", eventData); - - } else { - // Trigger event for target measurements - $(element).trigger("LesionToolSelected", eventData); - - // Deactivate nonTarget tool measurements - eventData.type = "inactive"; - - // Trigger event for nonTarget measurements - // Inactivate all nonTarget measurements if any measurement is active - $(element).trigger("NonTargetToolSelected", eventData); - } -} - /** * Activates a set of lesions when lesion table row is clicked * @@ -87,25 +16,45 @@ function activateLesion(measurementId) { // Get the timepoint data from this Measurement var timepoints = measurementData.timepoints; + // Get all non-dummy timepoint entries in the Measurement + // TODO=Re-evaluate this approach to populating viewports with timepoints + // What is the desired behaviour here? + var timepointsWithEntries = []; + Object.keys(timepoints).forEach(function(key) { + var timepoint = timepoints[key]; + + if (timepoint.imageId === "" || + timepoint.studyInstanceUid === "" || + timepoint.seriesInstanceUid === "") { + return; + } + + timepointsWithEntries.push(timepoint); + }); + + // If there are no non-dummy timepoint entries, stop here + if (!timepointsWithEntries.length) { + return; + } + // Loop through the viewports and display each timepoint $(".imageViewerViewport").each(function(viewportIndex, element) { // Stop if we run out of timepoints before viewports - if (viewportIndex >= Object.keys(timepoints).length) { + if (viewportIndex >= timepointsWithEntries.length) { return false; } + // Find measurements related to the Nth timepoint + // TODO=Re-evaluate this approach to populating viewports with timepoints + // What is the desired behaviour here? + var measurementAtTimepoint = timepointsWithEntries[viewportIndex]; + // Find the image that is currently in this viewport var enabledElement = cornerstone.getEnabledElement(element); if (!enabledElement || !enabledElement.image) { return; } - // Find measurements related to the Nth timepoint - // TODO=Re-evaluate this approach to populating viewports with timepoints - // What is the desired behaviour here? - var key = Object.keys(measurementData.timepoints)[viewportIndex]; - var measurementAtTimepoint = measurementData.timepoints[key]; - // If there is no measurement data to display, stop here if (!measurementAtTimepoint) { return; @@ -134,6 +83,115 @@ function activateLesion(measurementId) { }); } +/** + * Returns timepoint object based on timepoint id of the enabled element + * + * @param timepoints + * @param enabledElement + * @returns {*|{}} Timepoint object based on timepoint id of the enabled element (or an empty Object) + */ +function getTimepointObject(imageId) { + var study = cornerstoneTools.metaData.get('study', imageId); + return Timepoints.findOne({timepointName: study.studyDate}); +} + +/** + * Switch to the image of the correct image index + * Activate the selected measurement on the switched image (color to be green) + * Deactivate all other measurements on the switched image (color to be white) + */ +function activateMeasurements(element, measurementId) { + // TODO=Switch this to use the new CornerstoneToolMeasurementModified event, + // Once it has 'modified on activation' set up + + var enabledElement = cornerstone.getEnabledElement(element); + var imageId = enabledElement.image.imageId; + var timepointData = getTimepointObject(imageId); + var measurementData = Measurements.findOne(measurementId); + + var measurementAtTimepoint = measurementData.timepoints[timepointData.timepointID]; + if (!measurementAtTimepoint) { + return; + } + + // If type is active, load image and activate lesion + // If type is inactive, update lesions of enabledElement as inactive + var stackToolDataSource = cornerstoneTools.getToolState(element, 'stack'); + var stackData = stackToolDataSource.data[0]; + var imageIds = stackData.imageIds; + var imageIdIndex = imageIds.indexOf(measurementAtTimepoint.imageId); + + if (imageIdIndex < 0) { + return; + } + + if (imageIdIndex === stackData.currentImageIdIndex){ + activateTool(element, measurementData, timepointData.timepointID); + } else { + cornerstone.loadAndCacheImage(imageIds[imageIdIndex]).then(function(image) { + cornerstone.displayImage(element, image); + activateTool(element, measurementData, timepointData.timepointID); + }); + } +} + +/** + * Activates a specific tool data instance and deactivates all other + * target and non-target measurement data + * + * @param element + * @param measurementData + * @param timepointID + */ +function activateTool(element, measurementData, timepointID) { + deactivateAllToolData(element, 'lesion'); + deactivateAllToolData(element, 'nonTarget'); + + var toolType = measurementData.isTarget ? 'lesion' : 'nonTarget'; + var toolData = cornerstoneTools.getToolState(element, toolType); + if (!toolData) { + return; + } + + var measurementAtTimepoint = measurementData.timepoints[timepointID]; + + for (var 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 + // TODO= Remove this with the measurementId once it is in the tool data + if (data.seriesInstanceUid === measurementAtTimepoint.seriesInstanceUid && + data.studyInstanceUid === measurementAtTimepoint.studyInstanceUid && + data.lesionNumber === measurementData.lesionNumber && + data.isTarget == measurementData.isTarget) { + + data.active = true; + break; + } + } + + cornerstone.updateImage(element); +} + +/** + * Sets all tool data entries value for 'active' to false + * This is used to remove the active color on entire sets of tools + * + * @param element The Cornerstone element that is being used + * @param toolType The tooltype of the tools that will be deactivated + */ +function deactivateAllToolData(element, toolType) { + var toolData = cornerstoneTools.getToolState(element, toolType); + if (!toolData) { + return; + } + + for (var i = 0; i < toolData.data.length; i++) { + var data = toolData.data[i]; + data.active = false; + } +} + Template.lesionTable.helpers({ 'measurement': function() { return Measurements.find({}, {sort: {number: 1}}); diff --git a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js index 9006dc2ad..a117c851c 100644 --- a/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js +++ b/Packages/viewerbase/components/viewer/imageViewerViewport/imageViewerViewport.js @@ -198,7 +198,7 @@ function loadSeriesIntoViewport(data) { // Use the tool manager to enable the currently active tool for this // newly rendered element var activeTool = toolManager.getActiveTool(); - toolManager.setActiveTool(activeTool); + toolManager.setActiveTool(activeTool, element); // Define a function to run whenever the Cornerstone viewport is rendered // (e.g. following a change of window or zoom) @@ -382,6 +382,7 @@ Template.imageViewerViewport.onRendered(function() { currentImageIdIndex: this.data.currentImageIdIndex, studyInstanceUid: this.data.studyInstanceUid, seriesInstanceUid: this.data.seriesInstanceUid, + renderedCallback: this.data.renderedCallback, activeViewport: activeViewport }; diff --git a/Packages/viewerbase/components/viewer/toolbar/toolbar.js b/Packages/viewerbase/components/viewer/toolbar/toolbar.js index 36bd50314..af43eb145 100644 --- a/Packages/viewerbase/components/viewer/toolbar/toolbar.js +++ b/Packages/viewerbase/components/viewer/toolbar/toolbar.js @@ -112,7 +112,9 @@ Template.toolbar.events({ 'click .imageViewerTool': function(e) { var tool = e.currentTarget.id; console.log('Setting active tool to: ' + tool); - toolManager.setActiveTool(tool); + + var elements = $('.imageViewerViewport'); + toolManager.setActiveTool(tool, elements); }, 'click .imageViewerCommand': function(e) { var command = e.currentTarget.id; diff --git a/Packages/viewerbase/lib/toolManager.js b/Packages/viewerbase/lib/toolManager.js index 2a5ce0596..f98107bc5 100644 --- a/Packages/viewerbase/lib/toolManager.js +++ b/Packages/viewerbase/lib/toolManager.js @@ -1,6 +1,6 @@ var activeTool; -var alwaysEnabledTools; +var alwaysEnabledTools = []; var tools = {}; @@ -77,7 +77,72 @@ toolManager = { setAlwaysEnabledTools: function(tools) { alwaysEnabledTools = tools; }, - setActiveTool: function(tool) { + setActiveToolForElement: function(tool, element) { + var canvases = $(element).find('canvas'); + if (element.classList.contains('empty') || !canvases.length) { + return; + } + + // First, deactivate the current active tool + tools[activeTool].mouse.deactivate(element, 1); + + if (tools[activeTool].touch) { + tools[activeTool].touch.deactivate(element); + } + + // Enable any tools set as 'Always enabled' + alwaysEnabledTools.forEach(function(toolType) { + cornerstoneTools[toolType].enable(element); + }); + + // Get the stack toolData + var toolData = cornerstoneTools.getToolState(element, 'stack'); + if (!toolData || !toolData.data || !toolData.data.length) { + return; + } + + // Get the imageIds for this element + var imageIds = toolData.data[0].imageIds; + + // Deactivate all the middle mouse, right click, and scroll wheel tools + cornerstoneTools.pan.deactivate(element); + cornerstoneTools.zoom.deactivate(element); + cornerstoneTools.zoomWheel.deactivate(element); + cornerstoneTools.stackScrollWheel.deactivate(element); + + // Reactivate the relevant scrollwheel tool for this element + if (imageIds.length > 1) { + // scroll is the default tool for middle mouse wheel for stacks + cornerstoneTools.stackScrollWheel.activate(element); + } else { + // zoom is the default tool for middle mouse wheel for single images (non stacks) + cornerstoneTools.zoomWheel.activate(element); + } + + // This block ensures that the middle mouse and scroll tools keep working + if (tool === 'pan') { + cornerstoneTools.pan.activate(element, 3); // 3 means left mouse button and middle mouse button + cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button + } else if (tool === 'zoom') { + cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button + cornerstoneTools.zoom.activate(element, 5); // 5 means left mouse button and right mouse button + } else { + // Reactivate the middle mouse and right click tools + cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button + cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button + + // Activate the chosen tool + tools[tool].mouse.activate(element, 1); + } + + if (tools[tool].touch) { + tools[tool].touch.activate(element); + } + + cornerstoneTools.zoomTouchPinch.activate(element); + cornerstoneTools.panMultiTouch.activate(element); + }, + setActiveTool: function(tool, elements) { if (!initialized) { toolManager.init(); } @@ -86,70 +151,9 @@ toolManager = { var toolButton = document.getElementById(tool); toolButton.classList.add('active'); - $('.imageViewerViewport').each(function(index, element) { - var canvases = $(element).find('canvas'); - if (element.classList.contains('empty') || !canvases.length) { - return; - } - - // First, deactivate the current active tool - tools[activeTool].mouse.deactivate(element, 1); - - if (tools[activeTool].touch) { - tools[activeTool].touch.deactivate(element); - } - - // Enable any tools set as 'Always enabled' - alwaysEnabledTools.forEach(function(toolType) { - cornerstoneTools[toolType].enable(element); - }); - - // Get the stack toolData - var toolData = cornerstoneTools.getToolState(element, 'stack'); - if (!toolData || !toolData.data || !toolData.data.length) { - return; - } - - // Get the imageIds for this element - var imageIds = toolData.data[0].imageIds; - - // Deactivate all the middle mouse, right click, and scroll wheel tools - cornerstoneTools.pan.deactivate(element); - cornerstoneTools.zoom.deactivate(element); - cornerstoneTools.zoomWheel.deactivate(element); - cornerstoneTools.stackScrollWheel.deactivate(element); - - // Reactivate the relevant scrollwheel tool for this element - if (imageIds.length > 1) { - // scroll is the default tool for middle mouse wheel for stacks - cornerstoneTools.stackScrollWheel.activate(element); - } else { - // zoom is the default tool for middle mouse wheel for single images (non stacks) - cornerstoneTools.zoomWheel.activate(element); - } - - // This block ensures that the middle mouse and scroll tools keep working - if (tool === 'pan') { - cornerstoneTools.pan.activate(element, 3); // 3 means left mouse button and middle mouse button - cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button - } else if (tool === 'zoom') { - cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button - cornerstoneTools.zoom.activate(element, 5); // 5 means left mouse button and right mouse button - } else { - // Reactivate the middle mouse and right click tools - cornerstoneTools.pan.activate(element, 2); // pan is the default tool for middle mouse button - cornerstoneTools.zoom.activate(element, 4); // zoom is the default tool for right mouse button - - // Activate the chosen tool - tools[tool].mouse.activate(element, 1); - } - - if (tools[tool].touch) { - tools[tool].touch.activate(element); - } - - cornerstoneTools.zoomTouchPinch.activate(element); - cornerstoneTools.panMultiTouch.activate(element); + // Otherwise, set the active tool for all viewport elements + $(elements).each(function(index, element) { + toolManager.setActiveToolForElement(tool, element); }); activeTool = tool; },