Restructuring bidirectionalTool to improve maintainability
This commit is contained in:
parent
4aa0204119
commit
0acdff0bc5
@ -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);
|
||||
});
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
const toolType = 'bidirectional';
|
||||
const distanceThreshold = 6;
|
||||
|
||||
export {
|
||||
toolType,
|
||||
distanceThreshold
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
import './tool.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
import moveHandle from './moveHandle';
|
||||
export default moveHandle;
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
import onImageRendered from './onImageRendered';
|
||||
export default onImageRendered;
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
});
|
||||
@ -1,4 +1,4 @@
|
||||
import './bidirectionalTool.js';
|
||||
import './bidirectionalTool';
|
||||
import './nonTargetTool.js';
|
||||
import './scaleOverlayTool.js';
|
||||
import './deleteLesionKeyboardTool.js';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user