From 0acdff0bc57071f1204d19f51a86c1a6ae29712a Mon Sep 17 00:00:00 2001 From: Bruno Alves de Faria Date: Tue, 14 Nov 2017 14:43:46 -0200 Subject: [PATCH] Restructuring bidirectionalTool to improve maintainability --- .../bidirectionalTool/addNewMeasurement.js | 121 +++++++++++++ .../addNewMeasurementTouch.js | 50 ++++++ .../bidirectionalTool/createNewMeasurement.js | 40 +++++ .../bidirectionalTool/definitions.js | 7 + .../compatibility/bidirectionalTool/index.js | 1 + .../bidirectionalTool/mouseDownCallback.js | 141 +++++++++++++++ .../bidirectionalTool/mouseMoveCallback.js | 64 +++++++ .../bidirectionalTool/moveHandle/index.js | 2 + .../moveHandle/moveHandle.js | 70 ++++++++ .../moveHandle/perpendicularBothFixedLeft.js | 60 +++++++ .../moveHandle/perpendicularBothFixedRight.js | 60 +++++++ .../moveHandle/perpendicularLeftFixedPoint.js | 84 +++++++++ .../perpendicularRightFixedPoint.js | 82 +++++++++ .../moveHandle/setHandlesPosition.js | 140 +++++++++++++++ .../onImageRendered/drawHandles.js | 22 +++ .../onImageRendered/drawPerpendicularLine.js | 54 ++++++ .../onImageRendered/drawSelectedMarker.js | 39 ++++ .../onImageRendered/index.js | 2 + .../onImageRendered/onImageRendered.js | 170 ++++++++++++++++++ .../bidirectionalTool/pointNearTool.js | 33 ++++ .../compatibility/bidirectionalTool/tool.js | 47 +++++ .../client/compatibility/index.js | 2 +- 22 files changed, 1290 insertions(+), 1 deletion(-) create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurement.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurementTouch.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/createNewMeasurement.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/definitions.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/index.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseDownCallback.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseMoveCallback.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/index.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/moveHandle.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedLeft.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedRight.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularLeftFixedPoint.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularRightFixedPoint.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/setHandlesPosition.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawHandles.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawPerpendicularLine.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawSelectedMarker.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/index.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/onImageRendered.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/pointNearTool.js create mode 100644 Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/tool.js diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurement.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurement.js new file mode 100644 index 000000000..e6047452e --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurement.js @@ -0,0 +1,121 @@ +import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; +import { toolType } from './definitions'; +import createNewMeasurement from './createNewMeasurement'; +import mouseMoveCallback from './mouseMoveCallback'; +import mouseDownCallback from './mouseDownCallback'; + +export default function(mouseEventData) { + const element = mouseEventData.element; + const $element = $(element); + + // LT-29 Disable Target Measurements when pixel spacing is not available + if (!mouseEventData.image.rowPixelSpacing || !mouseEventData.image.columnPixelSpacing) { + return; + } + + function doneCallback() { + measurementData.active = false; + cornerstone.updateImage(element); + } + + const measurementData = createNewMeasurement(mouseEventData); + measurementData.viewport = cornerstone.getViewport(element); + + const eventData = { + mouseButtonMask: mouseEventData.which + }; + + const tool = cornerstoneTools[toolType]; + const config = tool.getConfiguration(); + const { mouseDownActivateCallback } = tool; + + // 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', mouseMoveCallback); + $element.off('CornerstoneToolsMouseDown', mouseDownCallback); + $element.off('CornerstoneToolsMouseDownActivate', mouseDownActivateCallback); + + let cancelled = false; + const cancelAction = () => { + cancelled = true; + cornerstoneTools.removeToolState(element, toolType, measurementData); + }; + + // Add a flag for using Esc to cancel tool placement + const keyDownHandler = event => { + // If the Esc key was pressed, set the flag to true + if (event.which === 27) { + cancelAction(); + } + + // Don't propagate this keydown event so it can't interfere + // with anything outside of this tool + return false; + }; + + // Bind a one-time event listener for the Esc key + $element.one('keydown', keyDownHandler); + + // Keep the current image and create a handler for new rendered images + const currentImage = cornerstone.getImage(element); + const currentViewport = cornerstone.getViewport(element); + const imageRenderedHandler = () => { + const newImage = cornerstone.getImage(element); + + // Check if the rendered image changed during measurement creation and delete it if so + if (newImage.imageId !== currentImage.imageId) { + cornerstone.displayImage(element, currentImage, currentViewport); + cancelAction(); + cornerstone.displayImage(element, newImage, currentViewport); + } + }; + + // Bind the event listener for image rendering + $element.on('CornerstoneImageRendered', imageRenderedHandler); + + // Bind the tool deactivation and enlargement handlers + $element.on('CornerstoneToolsToolDeactivated', cancelAction); + $element.one('ohif.viewer.viewport.toggleEnlargement', cancelAction); + + cornerstone.updateImage(element); + + const timestamp = new Date().getTime(); + const { end, perpendicularStart } = measurementData.handles; + cornerstoneTools.moveNewHandle(mouseEventData, toolType, measurementData, end, () => { + const { handles, longestDiameter, shortestDiameter } = measurementData; + const hasHandlesOutside = cornerstoneTools.anyHandlesOutsideImage(mouseEventData, handles); + const longestDiameterSize = parseFloat(longestDiameter) || 0; + const shortestDiameterSize = parseFloat(shortestDiameter) || 0; + const isTooSmal = (longestDiameterSize < 1) || (shortestDiameterSize < 1); + const isTooFast = (new Date().getTime() - timestamp) < 150; + if (cancelled || hasHandlesOutside || isTooSmal || isTooFast) { + // delete the measurement + measurementData.cancelled = true; + cornerstoneTools.removeToolState(element, toolType, measurementData); + } else { + // Set lesionMeasurementData Session + config.getMeasurementLocationCallback(measurementData, mouseEventData, doneCallback); + } + + // Unbind the Esc keydown hook + $element.off('keydown', keyDownHandler); + + // Unbind the event listener for image rendering + $element.off('CornerstoneImageRendered', imageRenderedHandler); + + // Unbind the tool deactivation and enlargement handlers + $element.off('CornerstoneToolsToolDeactivated', cancelAction); + $element.off('ohif.viewer.viewport.toggleEnlargement', cancelAction); + + // perpendicular line is not connected to long-line + perpendicularStart.locked = false; + + $element.on('CornerstoneToolsMouseMove', eventData, mouseMoveCallback); + $element.on('CornerstoneToolsMouseDown', eventData, mouseDownCallback); + $element.on('CornerstoneToolsMouseDownActivate', eventData, mouseDownActivateCallback); + cornerstone.updateImage(element); + }); +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurementTouch.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurementTouch.js new file mode 100644 index 000000000..f7f235cd1 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/addNewMeasurementTouch.js @@ -0,0 +1,50 @@ +import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; +import { toolType } from './definitions'; +import createNewMeasurement from './createNewMeasurement'; + +export default function(touchEventData) { + const element = { touchEventData }; + const $element = $(element); + + // LT-29 Disable Target Measurements when pixel spacing is not available + if (!touchEventData.image.rowPixelSpacing || !touchEventData.image.columnPixelSpacing) return; + + const doneCallback = () => { + measurementData.active = false; + cornerstone.updateImage(element); + }; + + const measurementData = createNewMeasurement(touchEventData); + const { cancelled, handles } = measurementData; + const config = cornerstoneTools[toolType].getConfiguration(); + + // 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. + const { touchMoveHandle, tapCallback, touchDownActivateCallback } = cornerstoneTools[toolType]; + $element.off('CornerstoneToolsTouchDrag', touchMoveHandle); + $element.off('CornerstoneToolsTap', tapCallback); + $element.off('CornerstoneToolsDragStartActive', touchDownActivateCallback); + + cornerstone.updateImage(element); + const { end, perpendicularStart } = handles; + cornerstoneTools.moveNewHandleTouch(touchEventData, toolType, measurementData, end, () => { + if (cancelled || cornerstoneTools.anyHandlesOutsideImage(touchEventData, handles)) { + // delete the measurement + cornerstoneTools.removeToolState(element, toolType, measurementData); + } else { + // Set lesionMeasurementData Session + config.getMeasurementLocationCallback(measurementData, touchEventData, doneCallback); + } + + // perpendicular line is not connected to long-line + perpendicularStart.locked = false; + + $element.on('CornerstoneToolsTouchDrag', touchMoveHandle); + $element.on('CornerstoneToolsTap', tapCallback); + $element.on('CornerstoneToolsDragStartActive', touchDownActivateCallback); + cornerstone.updateImage(element); + }); +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/createNewMeasurement.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/createNewMeasurement.js new file mode 100644 index 000000000..83f6543fd --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/createNewMeasurement.js @@ -0,0 +1,40 @@ +import { toolType } from './definitions'; + +const getHandle = (x, y, index, extraAttributes={}) => { + return Object.assign({ + x, + y, + index, + drawnIndependently: false, + allowedOutsideImage: false, + highlight: true, + active: false + }, extraAttributes); +}; + +export default function(mouseEventData) { + const { x, y } = mouseEventData.currentPoints.image; + // Create the measurement data for this tool with the end handle activated + const measurementData = { + toolType, + isCreating: true, + visible: true, + active: true, + handles: { + start: getHandle(x, y, 0), + end: getHandle(x, y, 1, { active: true }), + perpendicularStart: getHandle(x, y, 2, { locked: true }), + perpendicularEnd: getHandle(x, y, 3), + textBox: getHandle(x - 50, y - 70, 4, { + movesIndependently: false, + drawnIndependently: true, + allowedOutsideImage: true, + hasBoundingBox: true + }) + }, + longestDiameter: 0, + shortestDiameter: 0 + }; + + return measurementData; +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/definitions.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/definitions.js new file mode 100644 index 000000000..7d49efbdd --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/definitions.js @@ -0,0 +1,7 @@ +const toolType = 'bidirectional'; +const distanceThreshold = 6; + +export { + toolType, + distanceThreshold +}; diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/index.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/index.js new file mode 100644 index 000000000..1d10a74ed --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/index.js @@ -0,0 +1 @@ +import './tool.js'; diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseDownCallback.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseDownCallback.js new file mode 100644 index 000000000..d654052dc --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseDownCallback.js @@ -0,0 +1,141 @@ +import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; +import { toolType, distanceThreshold } from './definitions'; +import mouseMoveCallback from './mouseMoveCallback'; +import pointNearTool from './pointNearTool'; +import moveHandle from './moveHandle'; + +// Clear the selected state for the given handles object +const unselectAllHandles = handles => { + let imageNeedsUpdate = false; + Object.keys(handles).forEach(handleKey => { + if (handleKey === 'textBox') return; + handles[handleKey].selected = false; + imageNeedsUpdate = handles[handleKey].active || imageNeedsUpdate; + handles[handleKey].active = false; + }); + return imageNeedsUpdate; +}; + +// Clear the bidirectional tool's selection for all tool handles +const clearBidirectionalSelection = event => { + let imageNeedsUpdate = false; + const toolData = cornerstoneTools.getToolState(event.currentTarget, 'bidirectional'); + if (!toolData) return; + toolData.data.forEach(data => { + const unselectResult = unselectAllHandles(data.handles); + imageNeedsUpdate = imageNeedsUpdate || unselectResult; + }); + return imageNeedsUpdate; +}; + +const setHandlesMovingState = (handles, state) => { + Object.keys(handles).forEach(handleKey => { + if (handleKey === 'textBox') return; + handles[handleKey].moving = state; + }); +}; + +// mouseDowCallback is used to restrict behaviour of perpendicular-line +export default function(event, eventData) { + let data; + const element = eventData.element; + const $element = $(element); + + // Add an event listener to clear the selected state when a measurement is activated + const activateEventKey = 'ViewerMeasurementsActivated'; + $element.off(activateEventKey).on(activateEventKey, () => clearBidirectionalSelection(event)); + + // Clear selection on left mouse button click + if (eventData.which === 1) { + const imageNeedsUpdate = clearBidirectionalSelection(event); + if (imageNeedsUpdate) { + cornerstone.updateImage(element); + } + } + + function handleDoneMove(handle) { + // Set the cursor back to its default + $element.css('cursor', ''); + + data.invalidated = true; + if (cornerstoneTools.anyHandlesOutsideImage(eventData, data.handles)) { + // delete the measurement + cornerstoneTools.removeToolState(element, toolType, data); + } + + // Update the handles to keep selected state + if (handle) { + handle.moving = false; + handle.selected = true; + } + + cornerstone.updateImage(element); + $element.on('CornerstoneToolsMouseMove', eventData, mouseMoveCallback); + } + + if (cornerstoneTools.isMouseButtonEnabled(eventData.which, event.data.mouseButtonMask)) { + const coords = eventData.startPoints.canvas; + const toolData = cornerstoneTools.getToolState(event.currentTarget, toolType); + + // now check to see if there is a handle we can move + if (toolData) { + for (let i = 0; i < toolData.data.length; i++) { + data = toolData.data[i]; + const handle = cornerstoneTools.getHandleNearImagePoint( + element, + data.handles, + coords, + distanceThreshold + ); + + if (handle) { + // Hide the cursor to improve precision while resizing the line or set to move + // if dragging text box + $element.css('cursor', handle.hasBoundingBox ? 'move' : 'none'); + + $element.off('CornerstoneToolsMouseMove', mouseMoveCallback); + data.active = true; + + unselectAllHandles(data.handles); + handle.moving = true; + moveHandle(eventData, toolType, data, handle, handleDoneMove); + event.stopImmediatePropagation(); + return false; + } + } + } + + // 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 + if (toolData) { + const options = { + deleteIfHandleOutsideImage: true, + preventHandleOutsideImage: false + }; + + const getDoneMovingCallback = handles => () => { + setHandlesMovingState(handles, false); + handleDoneMove(); + }; + + for (let i = 0; i < toolData.data.length; i++) { + data = toolData.data[i]; + if (pointNearTool(element, data, coords)) { + // Set the cursor to move + $element.css('cursor', 'move'); + + $element.off('CornerstoneToolsMouseMove', mouseMoveCallback); + data.active = true; + + unselectAllHandles(data.handles); + setHandlesMovingState(data.handles, true); + + const doneMovingCallback = getDoneMovingCallback(data.handles); + cornerstoneTools.moveAllHandles(event, data, toolData, toolType, options, doneMovingCallback); + event.stopImmediatePropagation(); + return false; + } + } + } + } +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseMoveCallback.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseMoveCallback.js new file mode 100644 index 000000000..b3ab9bdb4 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/mouseMoveCallback.js @@ -0,0 +1,64 @@ +import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; +import { toolType } from './definitions'; +import pointNearTool from './pointNearTool'; + +// Replaces the cornerstoneTools.handleActivator function by skiping the active handle comparison +const handleActivator = (element, handles, canvasPoint, distanceThreshold=6) => { + const nearbyHandle = cornerstoneTools.getHandleNearImagePoint(element, handles, canvasPoint, distanceThreshold); + + let handleActivatorChanged = false; + Object.keys(handles).forEach(handleKey => { + if (handleKey === 'textBox') return; + const handle = handles[handleKey]; + const newActiveState = handle === nearbyHandle; + if (handle.active !== newActiveState) { + handleActivatorChanged = true; + } + + handle.active = newActiveState; + }); + + return handleActivatorChanged; +}; + +// mouseMoveCallback is used to hide handles when mouse is away +export default function (event, eventData) { + cornerstoneTools.toolCoordinates.setCoords(eventData); + // if a mouse button is down, do nothing + if (eventData.which !== 0) { + return; + } + + // if we have no tool data for this element, do nothing + const toolData = cornerstoneTools.getToolState(eventData.element, toolType); + if (!toolData) return; + + // We have tool data, search through all data and see if we can activate a handle + let imageNeedsUpdate = false; + for (let i = 0; i < toolData.data.length; i++) { + // get the cursor position in canvas coordinates + const coords = eventData.currentPoints.canvas; + + const data = toolData.data[i]; + const handleActivatorChanged = handleActivator(eventData.element, data.handles, coords); + Object.keys(data.handles).forEach(handleKey => { + if (handleKey === 'textBox') return; + const handle = data.handles[handleKey]; + handle.hover = handle.active; + }); + + if (handleActivatorChanged) { + imageNeedsUpdate = true; + } + + if ((pointNearTool(eventData.element, data, coords) && !data.active) || (!pointNearTool(eventData.element, data, coords) && data.active)) { + data.active = !data.active; + imageNeedsUpdate = true; + } + } + + // Handle activation status changed, redraw the image + if (imageNeedsUpdate === true) { + cornerstone.updateImage(eventData.element); + } +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/index.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/index.js new file mode 100644 index 000000000..2f3b8f0ba --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/index.js @@ -0,0 +1,2 @@ +import moveHandle from './moveHandle'; +export default moveHandle; diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/moveHandle.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/moveHandle.js new file mode 100644 index 000000000..233466dd7 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/moveHandle.js @@ -0,0 +1,70 @@ +import { cornerstone } from 'meteor/ohif:cornerstone'; +import setHandlesPosition from './setHandlesPosition'; + +export default function (mouseEventData, toolType, data, handle, doneMovingCallback, preventHandleOutsideImage) { + const element = mouseEventData.element; + const $element = $(element); + const distanceFromTool = { + x: handle.x - mouseEventData.currentPoints.image.x, + y: handle.y - mouseEventData.currentPoints.image.y + }; + + const mouseDragCallback = (event, eventData) => { + handle.active = true; + + if (handle.index === undefined || handle.index === null) { + handle.x = eventData.currentPoints.image.x + distanceFromTool.x; + handle.y = eventData.currentPoints.image.y + distanceFromTool.y; + } else { + setHandlesPosition(handle, eventData, data); + } + + if (preventHandleOutsideImage) { + handle.x = Math.max(handle.x, 0); + handle.x = Math.min(handle.x, eventData.image.width); + + handle.y = Math.max(handle.y, 0); + handle.y = Math.min(handle.y, eventData.image.height); + } + + cornerstone.updateImage(element); + + const eventType = 'CornerstoneToolsMeasurementModified'; + const modifiedEventData = { + toolType: toolType, + element: element, + measurementData: data + }; + $element.trigger(eventType, modifiedEventData); + }; + + $element.on('CornerstoneToolsMouseDrag', mouseDragCallback); + + const currentImage = cornerstone.getImage(element); + const imageRenderedHandler = () => { + const newImage = cornerstone.getImage(element); + + // Check if the rendered image changed during measurement modifying and stop it if so + if (newImage.imageId !== currentImage.imageId) { + mouseUpCallback(); + } + }; + + // Bind the event listener for image rendering + $element.on('CornerstoneImageRendered', imageRenderedHandler); + + const mouseUpCallback = () => { + $element.off('CornerstoneToolsMouseDrag', mouseDragCallback); + $element.off('CornerstoneToolsMouseUp', mouseUpCallback); + $element.off('CornerstoneToolsMouseClick', mouseUpCallback); + $element.off('CornerstoneImageRendered', imageRenderedHandler); + cornerstone.updateImage(element); + + if (typeof doneMovingCallback === 'function') { + doneMovingCallback(); + } + }; + + $element.on('CornerstoneToolsMouseUp', mouseUpCallback); + $element.on('CornerstoneToolsMouseClick', mouseUpCallback); +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedLeft.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedLeft.js new file mode 100644 index 000000000..c25500199 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedLeft.js @@ -0,0 +1,60 @@ +import { cornerstoneMath } from 'meteor/ohif:cornerstone'; + +// Move long-axis start point +export default function(eventData, data) { + const { distance } = cornerstoneMath.point; + const { start, end, perpendicularStart, perpendicularEnd } = data.handles; + const { image } = eventData.currentPoints; + + const longLine = { + start: { + x: start.x, + y: start.y + }, + end: { + x: end.x, + y: end.y + } + }; + + const perpendicularLine = { + start: { + x: perpendicularStart.x, + y: perpendicularStart.y + }, + end: { + x: perpendicularEnd.x, + y: perpendicularEnd.y + } + }; + + const intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + + const distanceFromPerpendicularP1 = distance(perpendicularStart, intersection); + const distanceFromPerpendicularP2 = distance(perpendicularEnd, intersection); + + const distanceToLineP2 = distance(end, intersection); + const newLineLength = distance(end, image); + + if (newLineLength <= distanceToLineP2) { + return false; + } + + const dx = (end.x - image.x) / newLineLength; + const dy = (end.y - image.y) / newLineLength; + + const k = distanceToLineP2 / newLineLength; + + const newIntersection = { + x: end.x + ((image.x - end.x) * k), + y: end.y + ((image.y - end.y) * k) + }; + + perpendicularStart.x = newIntersection.x - distanceFromPerpendicularP1 * dy; + perpendicularStart.y = newIntersection.y + distanceFromPerpendicularP1 * dx; + + perpendicularEnd.x = newIntersection.x + distanceFromPerpendicularP2 * dy; + perpendicularEnd.y = newIntersection.y - distanceFromPerpendicularP2 * dx; + + return true; +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedRight.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedRight.js new file mode 100644 index 000000000..36fe8d561 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularBothFixedRight.js @@ -0,0 +1,60 @@ +import { cornerstoneMath } from 'meteor/ohif:cornerstone'; + +// Move long-axis end point +export default function(eventData, data) { + const { distance } = cornerstoneMath.point; + const { start, end, perpendicularStart, perpendicularEnd } = data.handles; + const { image } = eventData.currentPoints; + + const longLine = { + start: { + x: start.x, + y: start.y + }, + end: { + x: end.x, + y: end.y + } + }; + + const perpendicularLine = { + start: { + x: perpendicularStart.x, + y: perpendicularStart.y + }, + end: { + x: perpendicularEnd.x, + y: perpendicularEnd.y + } + }; + + const intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + + const distanceFromPerpendicularP1 = distance(perpendicularStart, intersection); + const distanceFromPerpendicularP2 = distance(perpendicularEnd, intersection); + + const distanceToLineP2 = distance(start, intersection); + const newLineLength = distance(start, image); + + if (newLineLength <= distanceToLineP2) { + return false; + } + + const dx = (start.x - image.x) / newLineLength; + const dy = (start.y - image.y) / newLineLength; + + const k = distanceToLineP2 / newLineLength; + + const newIntersection = { + x: start.x + ((image.x - start.x) * k), + y: start.y + ((image.y - start.y) * k) + }; + + perpendicularStart.x = newIntersection.x + distanceFromPerpendicularP1 * dy; + perpendicularStart.y = newIntersection.y - distanceFromPerpendicularP1 * dx; + + perpendicularEnd.x = newIntersection.x - distanceFromPerpendicularP2 * dy; + perpendicularEnd.y = newIntersection.y + distanceFromPerpendicularP2 * dx; + + return true; +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularLeftFixedPoint.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularLeftFixedPoint.js new file mode 100644 index 000000000..1e6603eaa --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularLeftFixedPoint.js @@ -0,0 +1,84 @@ +import { cornerstoneMath } from 'meteor/ohif:cornerstone'; + +// Move perpendicular line start point +export default function(eventData, data) { + const { distance } = cornerstoneMath.point; + const { start, end, perpendicularStart, perpendicularEnd } = data.handles; + + const fudgeFactor = 1; + + const fixedPoint = perpendicularEnd; + const movedPoint = eventData.currentPoints.image; + + const distanceFromFixed = cornerstoneMath.lineSegment.distanceToPoint(data.handles, fixedPoint); + const distanceFromMoved = cornerstoneMath.lineSegment.distanceToPoint(data.handles, movedPoint); + + const distanceBetweenPoints = distance(fixedPoint, movedPoint); + + const total = distanceFromFixed + distanceFromMoved; + + if (distanceBetweenPoints <= distanceFromFixed) { + return false; + } + + const length = distance(start, end); + if (length === 0) { + return false; + } + + const dx = (start.x - end.x) / length; + const dy = (start.y - end.y) / length; + + const adjustedLineP1 = { + x: start.x - fudgeFactor * dx, + y: start.y - fudgeFactor * dy + }; + const adjustedLineP2 = { + x: end.x + fudgeFactor * dx, + y: end.y + fudgeFactor * dy + }; + + perpendicularStart.x = movedPoint.x; + perpendicularStart.y = movedPoint.y; + perpendicularEnd.x = movedPoint.x - total * dy; + perpendicularEnd.y = movedPoint.y + total * dx; + + const longLine = { + start: { + x: start.x, + y: start.y + }, + end: { + x: end.x, + y: end.y + } + }; + + const perpendicularLine = { + start: { + x: perpendicularStart.x, + y: perpendicularStart.y + }, + end: { + x: perpendicularEnd.x, + y: perpendicularEnd.y + } + }; + + const intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + if (!intersection) { + if (distance(movedPoint, start) > distance(movedPoint, end)) { + perpendicularStart.x = adjustedLineP2.x + distanceFromMoved * dy; + perpendicularStart.y = adjustedLineP2.y - distanceFromMoved * dx; + perpendicularEnd.x = perpendicularStart.x - total * dy; + perpendicularEnd.y = perpendicularStart.y + total * dx; + } else { + perpendicularStart.x = adjustedLineP1.x + distanceFromMoved * dy; + perpendicularStart.y = adjustedLineP1.y - distanceFromMoved * dx; + perpendicularEnd.x = perpendicularStart.x - total * dy; + perpendicularEnd.y = perpendicularStart.y + total * dx; + } + } + + return true; +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularRightFixedPoint.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularRightFixedPoint.js new file mode 100644 index 000000000..ced494fc9 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/perpendicularRightFixedPoint.js @@ -0,0 +1,82 @@ +import { cornerstoneMath } from 'meteor/ohif:cornerstone'; + +// Move perpendicular line end point +export default function(eventData, data) { + const { distance } = cornerstoneMath.point; + const { start, end, perpendicularStart, perpendicularEnd } = data.handles; + + const fudgeFactor = 1; + + const fixedPoint = perpendicularStart; + const movedPoint = eventData.currentPoints.image; + + const distanceFromFixed = cornerstoneMath.lineSegment.distanceToPoint(data.handles, fixedPoint); + const distanceFromMoved = cornerstoneMath.lineSegment.distanceToPoint(data.handles, movedPoint); + + const distanceBetweenPoints = distance(fixedPoint, movedPoint); + + const total = distanceFromFixed + distanceFromMoved; + + if (distanceBetweenPoints <= distanceFromFixed) { + return false; + } + + const length = distance(start, end); + const dx = (start.x - end.x) / length; + const dy = (start.y - end.y) / length; + + const adjustedLineP1 = { + x: start.x - fudgeFactor * dx, + y: start.y - fudgeFactor * dy + }; + const adjustedLineP2 = { + x: end.x + fudgeFactor * dx, + y: end.y + fudgeFactor * dy + }; + + perpendicularStart.x = movedPoint.x + total * dy; + perpendicularStart.y = movedPoint.y - total * dx; + perpendicularEnd.x = movedPoint.x; + perpendicularEnd.y = movedPoint.y; + perpendicularEnd.locked = false; + perpendicularStart.locked = false; + + const longLine = { + start: { + x: start.x, + y: start.y + }, + end: { + x: end.x, + y: end.y + } + }; + + const perpendicularLine = { + start: { + x: perpendicularStart.x, + y: perpendicularStart.y + }, + end: { + x: perpendicularEnd.x, + y: perpendicularEnd.y + } + }; + + const intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + if (!intersection) { + if (distance(movedPoint, start) > distance(movedPoint, end)) { + perpendicularEnd.x = adjustedLineP2.x - distanceFromMoved * dy; + perpendicularEnd.y = adjustedLineP2.y + distanceFromMoved * dx; + perpendicularStart.x = perpendicularEnd.x + total * dy; + perpendicularStart.y = perpendicularEnd.y - total * dx; + } else { + perpendicularEnd.x = adjustedLineP1.x - distanceFromMoved * dy; + perpendicularEnd.y = adjustedLineP1.y + distanceFromMoved * dx; + perpendicularStart.x = perpendicularEnd.x + total * dy; + perpendicularStart.y = perpendicularEnd.y - total * dx; + } + } + + return true; +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/setHandlesPosition.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/setHandlesPosition.js new file mode 100644 index 000000000..622d594be --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/moveHandle/setHandlesPosition.js @@ -0,0 +1,140 @@ +import { cornerstoneMath } from 'meteor/ohif:cornerstone'; +import perpendicularBothFixedLeft from './perpendicularBothFixedLeft'; +import perpendicularBothFixedRight from './perpendicularBothFixedRight'; +import perpendicularLeftFixedPoint from './perpendicularLeftFixedPoint'; +import perpendicularRightFixedPoint from './perpendicularRightFixedPoint'; + +// Sets position of handles(start, end, perpendicularStart, perpendicularEnd) +export default function(handle, eventData, data) { + let movedPoint, + outOfBounds, + result, + intersection, + d1, + d2; + + let longLine = {}, + perpendicularLine = {}; + + if (handle.index === 0) { + // if long-axis start point is moved + result = perpendicularBothFixedLeft(eventData, data); + if (result) { + handle.x = eventData.currentPoints.image.x; + handle.y = eventData.currentPoints.image.y; + } else { + eventData.currentPoints.image.x = handle.x; + eventData.currentPoints.image.y = handle.y; + } + + } else if (handle.index === 1) { + // if long-axis end point is moved + result = perpendicularBothFixedRight(eventData, data); + if (result) { + handle.x = eventData.currentPoints.image.x; + handle.y = eventData.currentPoints.image.y; + } else { + eventData.currentPoints.image.x = handle.x; + eventData.currentPoints.image.y = handle.y; + } + + } else if (handle.index === 2) { + outOfBounds = false; + // if perpendicular start point is moved + longLine.start = { + x: data.handles.start.x, + y: data.handles.start.y + }; + longLine.end = { + x: data.handles.end.x, + y: data.handles.end.y + }; + + perpendicularLine.start = { + x: data.handles.perpendicularEnd.x, + y: data.handles.perpendicularEnd.y + }; + perpendicularLine.end = { + x: eventData.currentPoints.image.x, + y: eventData.currentPoints.image.y + }; + + intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + if (!intersection) { + perpendicularLine.end = { + x: data.handles.perpendicularStart.x, + y: data.handles.perpendicularStart.y + }; + + intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + + d1 = cornerstoneMath.point.distance(intersection, data.handles.start); + d2 = cornerstoneMath.point.distance(intersection, data.handles.end); + + if (!intersection || d1 < 3 || d2 < 3) { + outOfBounds = true; + } + } + + movedPoint = false; + + if (!outOfBounds) { + movedPoint = perpendicularLeftFixedPoint(eventData, data); + + if (!movedPoint) { + eventData.currentPoints.image.x = data.handles.perpendicularStart.x; + eventData.currentPoints.image.y = data.handles.perpendicularStart.y; + } + } + + } else if (handle.index === 3) { + outOfBounds = false; + + // if perpendicular end point is moved + longLine.start = { + x: data.handles.start.x, + y: data.handles.start.y + }; + longLine.end = { + x: data.handles.end.x, + y: data.handles.end.y + }; + + perpendicularLine.start = { + x: data.handles.perpendicularStart.x, + y: data.handles.perpendicularStart.y + }; + perpendicularLine.end = { + x: eventData.currentPoints.image.x, + y: eventData.currentPoints.image.y + }; + + intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + if (!intersection) { + perpendicularLine.end = { + x: data.handles.perpendicularEnd.x, + y: data.handles.perpendicularEnd.y + }; + + intersection = cornerstoneMath.lineSegment.intersectLine(longLine, perpendicularLine); + + d1 = cornerstoneMath.point.distance(intersection, data.handles.start); + d2 = cornerstoneMath.point.distance(intersection, data.handles.end); + + if (!intersection || d1 < 3 || d2 < 3) { + outOfBounds = true; + } + } + + movedPoint = false; + + if (!outOfBounds) { + movedPoint = perpendicularRightFixedPoint(eventData, data); + + if (!movedPoint) { + eventData.currentPoints.image.x = data.handles.perpendicularEnd.x; + eventData.currentPoints.image.y = data.handles.perpendicularEnd.y; + } + } + } +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawHandles.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawHandles.js new file mode 100644 index 000000000..35333b279 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawHandles.js @@ -0,0 +1,22 @@ +import { cornerstoneTools } from 'meteor/ohif:cornerstone'; + +// Add a proxy to cornerstoneTools.drawHandles function to change the handles' active state base on +// the hover, moving and selected states +export default function(context, eventData, handles, color, options) { + Object.keys(handles).forEach(handleKey => { + if (handleKey === 'textBox') return; + const handle = handles[handleKey]; + handle.drawnIndependently = handle.moving; + if (handle.selected) { + handle.active = handle.hover; + } else { + if (handle.hover) { + handle.active = true; + } else { + handle.active = false; + } + } + }); + + cornerstoneTools.drawHandles(context, eventData, handles, color, options); +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawPerpendicularLine.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawPerpendicularLine.js new file mode 100644 index 000000000..16c4e003c --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawPerpendicularLine.js @@ -0,0 +1,54 @@ +import { cornerstone } from 'meteor/ohif:cornerstone'; + +// draw perpendicular line +export default function(context, eventData, element, data, color, lineWidth) { + let startX, startY, endX, endY; + + if (data.handles.start.x === data.handles.end.x && + data.handles.start.y === data.handles.end.y) { + startX = data.handles.start.x; + startY = data.handles.start.y; + endX = data.handles.end.x; + endY = data.handles.end.y; + } else { + // mid point of long-axis line + const mid = { + x: (data.handles.start.x + data.handles.end.x) / 2, + y: (data.handles.start.y + data.handles.end.y) / 2 + }; + + // Length of long-axis + const dx = (data.handles.start.x - data.handles.end.x) * (eventData.image.columnPixelSpacing || 1); + const dy = (data.handles.start.y - data.handles.end.y) * (eventData.image.rowPixelSpacing || 1); + const length = Math.sqrt(dx * dx + dy * dy); + + const vectorX = (data.handles.start.x - data.handles.end.x) / length; + const vectorY = (data.handles.start.y - data.handles.end.y) / length; + + const perpendicularLineLength = length / 2; + + startX = mid.x + (perpendicularLineLength / 2) * vectorY; + startY = mid.y - (perpendicularLineLength / 2) * vectorX; + endX = mid.x - (perpendicularLineLength / 2) * vectorY; + endY = mid.y + (perpendicularLineLength / 2) * vectorX; + } + + if (data.handles.perpendicularStart.locked) { + data.handles.perpendicularStart.x = startX; + data.handles.perpendicularStart.y = startY; + data.handles.perpendicularEnd.x = endX; + data.handles.perpendicularEnd.y = endY; + } + + // Draw perpendicular line + const perpendicularStartCanvas = cornerstone.pixelToCanvas(element, data.handles.perpendicularStart); + const perpendicularEndCanvas = cornerstone.pixelToCanvas(element, data.handles.perpendicularEnd); + + context.beginPath(); + context.strokeStyle = color; + context.lineWidth = lineWidth; + context.moveTo(perpendicularStartCanvas.x, perpendicularStartCanvas.y); + context.lineTo(perpendicularEndCanvas.x, perpendicularEndCanvas.y); + context.stroke(); + +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawSelectedMarker.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawSelectedMarker.js new file mode 100644 index 000000000..753a3120a --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/drawSelectedMarker.js @@ -0,0 +1,39 @@ +import { _ } from 'meteor/underscore'; +import { OHIF } from 'meteor/ohif:core'; +import { cornerstone, cornerstoneTools } from 'meteor/ohif:cornerstone'; + +// Draw a line marker over the selected arm +export default function(eventData, handles, color) { + const lib = OHIF.lesiontracker.bidirectional; + const { canvasContext, element } = eventData; + + const handleKey = lib.getSelectedHandleKey(handles); + if (!handleKey) return; + const handle = handles[handleKey]; + + // Used a big distance (1km) to fill the entire line + const mmStep = -1000000; + + // Get the line's start and end points + const fakeImage = { + columnPixelSpacing: eventData.viewport.scale, + rowPixelSpacing: eventData.viewport.scale + }; + const pointA = lib.repositionBidirectionalArmHandle(fakeImage, handles, handleKey, mmStep, 0); + const pointB = _.pick(handle, ['x', 'y']); + + // Stop here if pointA is not present + if (!pointA) return; + + // Get the canvas coordinates for the line var perpendicularStartCanvas = cornerstone.pixelToCanvas(element, data.handles.perpendicularStart); + const canvasPointA = cornerstone.pixelToCanvas(element, pointA); + const canvasPointB = cornerstone.pixelToCanvas(element, pointB); + + // Draw the line marker + canvasContext.beginPath(); + canvasContext.strokeStyle = color; + canvasContext.lineWidth = cornerstoneTools.toolStyle.getToolWidth(); + canvasContext.moveTo(canvasPointA.x, canvasPointA.y); + canvasContext.lineTo(canvasPointB.x, canvasPointB.y); + canvasContext.stroke(); +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/index.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/index.js new file mode 100644 index 000000000..5fee5bd66 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/index.js @@ -0,0 +1,2 @@ +import onImageRendered from './onImageRendered'; +export default onImageRendered; diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/onImageRendered.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/onImageRendered.js new file mode 100644 index 000000000..1492ff102 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/onImageRendered/onImageRendered.js @@ -0,0 +1,170 @@ +import { cornerstone, cornerstoneMath, cornerstoneTools } from 'meteor/ohif:cornerstone'; +import { OHIF } from 'meteor/ohif:core'; +import { toolType } from '../definitions'; +import drawHandles from './drawHandles'; +import drawPerpendicularLine from './drawPerpendicularLine'; +import drawSelectedMarker from './drawSelectedMarker'; + +export default function(event, eventData) { + const { element, canvasContext } = eventData; + + // if we have no toolData for this element, return immediately as there is nothing to do + const toolData = cornerstoneTools.getToolState(event.currentTarget, toolType); + if (!toolData) return; + + // LT-29 Disable Target Measurements when pixel spacing is not available + const { rowPixelSpacing, columnPixelSpacing } = eventData.image; + if (!rowPixelSpacing || !columnPixelSpacing) return; + + // we have tool data for this element - iterate over each one and draw it + const context = canvasContext.canvas.getContext('2d'); + context.setTransform(1, 0, 0, 1, 0, 0); + + let color; + const lineWidth = cornerstoneTools.toolStyle.getToolWidth(); + const config = cornerstoneTools[toolType].getConfiguration(); + + for (let i = 0; i < toolData.data.length; i++) { + const data = toolData.data[i]; + const { start, end, perpendicularStart, perpendicularEnd, textBox } = data.handles; + const strokeWidth = lineWidth; + + context.save(); + + // configurable shadow from CornerstoneTools + const { shadow } = config; + if (shadow && shadow.shadow) { + context.shadowColor = shadow.shadowColor || '#000000'; + context.shadowOffsetX = shadow.shadowOffsetX || 1; + context.shadowOffsetY = shadow.shadowOffsetY || 1; + } + + const activeColor = cornerstoneTools.toolColors.getActiveColor(); + if (data.active) { + color = activeColor; + } else { + color = cornerstoneTools.toolColors.getToolColor(); + } + + // draw the line + const handleStartCanvas = cornerstone.pixelToCanvas(element, start); + const handleEndCanvas = cornerstone.pixelToCanvas(element, end); + const canvasTextLocation = cornerstone.pixelToCanvas(element, textBox); + + context.beginPath(); + context.strokeStyle = color; + context.lineWidth = strokeWidth; + context.moveTo(handleStartCanvas.x, handleStartCanvas.y); + context.lineTo(handleEndCanvas.x, handleEndCanvas.y); + context.stroke(); + + // Draw perpendicular line + drawPerpendicularLine(context, eventData, element, data, color, strokeWidth); + + // Draw the handles + const handlesColor = color; + drawHandles(context, eventData, data.handles, handlesColor, { drawHandlesIfActive: true }); + + // Draw the selected marker + drawSelectedMarker(eventData, data.handles, '#FF9999'); + + // Calculate the long axis length + const dx = (start.x - end.x) * (columnPixelSpacing || 1); + const dy = (start.y - end.y) * (rowPixelSpacing || 1); + let length = Math.sqrt(dx * dx + dy * dy); + + // Calculate the short axis length + const wx = (perpendicularStart.x - perpendicularEnd.x) * (columnPixelSpacing || 1); + const wy = (perpendicularStart.y - perpendicularEnd.y) * (rowPixelSpacing || 1); + let width = Math.sqrt(wx * wx + wy * wy); + if (!width) { + width = 0; + } + + // Length is always longer than width + if (width > length) { + const tempW = width; + const tempL = length; + length = tempW; + width = tempL; + } + + if (data.measurementNumber) { + // Draw the textbox + let suffix = ' mm'; + if (!rowPixelSpacing || !columnPixelSpacing) { + suffix = ' pixels'; + } + + const lengthText = ' L ' + length.toFixed(1) + suffix; + const widthText = ' W ' + width.toFixed(1) + suffix; + const textLines = [`Target ${data.measurementNumber}`, lengthText, widthText]; + + const boundingBox = cornerstoneTools.drawTextBox( + context, + textLines, + canvasTextLocation.x, + canvasTextLocation.y, + color, + config.textBox + ); + + textBox.boundingBox = boundingBox; + + OHIF.cornerstone.repositionTextBox(eventData, data, config.textBox); + + // Draw linked line as dashed + const link = { + start: {}, + end: {} + }; + + const midpointCanvas = { + x: (handleStartCanvas.x + handleEndCanvas.x) / 2, + y: (handleStartCanvas.y + handleEndCanvas.y) / 2, + }; + + const points = [ handleStartCanvas, handleEndCanvas, midpointCanvas ]; + + link.end.x = canvasTextLocation.x; + link.end.y = canvasTextLocation.y; + + link.start = cornerstoneMath.point.findClosestPoint(points, link.end); + + const boundingBoxPoints = [ { + // Top middle point of bounding box + x: boundingBox.left + boundingBox.width / 2, + y: boundingBox.top + }, { + // Left middle point of bounding box + x: boundingBox.left, + y: boundingBox.top + boundingBox.height / 2 + }, { + // Bottom middle point of bounding box + x: boundingBox.left + boundingBox.width / 2, + y: boundingBox.top + boundingBox.height + }, { + // Right middle point of bounding box + x: boundingBox.left + boundingBox.width, + y: boundingBox.top + boundingBox.height / 2 + }, + ]; + + link.end = cornerstoneMath.point.findClosestPoint(boundingBoxPoints, link.start); + context.beginPath(); + context.strokeStyle = color; + context.lineWidth = lineWidth; + context.setLineDash([ 2, 3 ]); + + context.moveTo(link.start.x, link.start.y); + context.lineTo(link.end.x, link.end.y); + context.stroke(); + } + + // Set measurement text to show lesion table + data.longestDiameter = length.toFixed(1); + data.shortestDiameter = width.toFixed(1); + + context.restore(); + } +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/pointNearTool.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/pointNearTool.js new file mode 100644 index 000000000..10e91a597 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/pointNearTool.js @@ -0,0 +1,33 @@ +import { cornerstone, cornerstoneMath, cornerstoneTools } from 'meteor/ohif:cornerstone'; +import { distanceThreshold } from './definitions'; + +const pointNearPerpendicular = (element, handles, coords) => { + const lineSegment = { + start: cornerstone.pixelToCanvas(element, handles.perpendicularStart), + end: cornerstone.pixelToCanvas(element, handles.perpendicularEnd) + }; + + const distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords); + + return (distanceToPoint < distanceThreshold); +}; + +export default function(element, data, coords) { + const { handles } = data; + const lineSegment = { + start: cornerstone.pixelToCanvas(element, handles.start), + end: cornerstone.pixelToCanvas(element, handles.end) + }; + + const distanceToPoint = cornerstoneMath.lineSegment.distanceToPoint(lineSegment, coords); + + if (cornerstoneTools.pointInsideBoundingBox(handles.textBox, coords)) { + return true; + } + + if (pointNearPerpendicular(element, handles, coords)) { + return true; + } + + return (distanceToPoint < distanceThreshold); +} diff --git a/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/tool.js b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/tool.js new file mode 100644 index 000000000..55b6789b5 --- /dev/null +++ b/Packages/ohif-lesiontracker/client/compatibility/bidirectionalTool/tool.js @@ -0,0 +1,47 @@ +import { Viewerbase } from 'meteor/ohif:viewerbase'; +import { cornerstoneTools } from 'meteor/ohif:cornerstone'; +import { toolType } from './definitions'; +import createNewMeasurement from './createNewMeasurement'; +import addNewMeasurement from './addNewMeasurement'; +import addNewMeasurementTouch from './addNewMeasurementTouch'; +import onImageRendered from './onImageRendered'; +import pointNearTool from './pointNearTool'; +import mouseDownCallback from './mouseDownCallback'; +import mouseMoveCallback from './mouseMoveCallback'; + +function createToolInterface() { + const toolInterface = { toolType }; + + const baseInterface = { + createNewMeasurement, + onImageRendered, + pointNearTool, + toolType + }; + + toolInterface.mouse = cornerstoneTools.mouseButtonTool(Object.assign({ + addNewMeasurement, + mouseDownCallback, + mouseMoveCallback + }, baseInterface)); + + toolInterface.touch = cornerstoneTools.touchTool(Object.assign({ + addNewMeasurement: addNewMeasurementTouch + }, baseInterface)); + + return toolInterface; +} + +const toolInterface = createToolInterface(); +cornerstoneTools[toolType] = toolInterface.mouse; +cornerstoneTools[toolType + 'Touch'] = toolInterface.touch; + +// Define an empty location callback +const emptyLocationCallback = (measurementData, eventData, doneCallback) => doneCallback(); +const { shadowConfig, textBoxConfig } = Viewerbase.toolManager.getToolDefaultStates(); +cornerstoneTools[toolType].setConfiguration({ + getMeasurementLocationCallback: emptyLocationCallback, + changeMeasurementLocationCallback: emptyLocationCallback, + textBox: textBoxConfig, + shadow: shadowConfig +}); diff --git a/Packages/ohif-lesiontracker/client/compatibility/index.js b/Packages/ohif-lesiontracker/client/compatibility/index.js index abbd6373d..f1fc36855 100644 --- a/Packages/ohif-lesiontracker/client/compatibility/index.js +++ b/Packages/ohif-lesiontracker/client/compatibility/index.js @@ -1,4 +1,4 @@ -import './bidirectionalTool.js'; +import './bidirectionalTool'; import './nonTargetTool.js'; import './scaleOverlayTool.js'; import './deleteLesionKeyboardTool.js';