diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/getBaseData.js b/src/tools/annotation/bidirectionalTool/moveHandle/getBaseData.js new file mode 100644 index 000000000..26ea1043b --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/getBaseData.js @@ -0,0 +1,54 @@ +import external from '../../../../externalModules.js'; +import getDistanceWithPixelSpacing from '../utils/getDistanceWithPixelSpacing.js'; + +function createLine(startPoint, endPoint) { + return { + start: startPoint, + end: endPoint, + }; +} + +/** + * Extract and group the base data to be used on bidirectional tool lines + * moving. + * + * @param {*} measurementData Data from current bidirectional tool measurement + * @param {*} eventData Data object associated with the event + * @param {*} fixedPoint Point that is not being moved in line + * + * @returns {*} Grouped that needed for lines moving + */ +export default function getBaseData(measurementData, eventData, fixedPoint) { + const { lineSegment } = external.cornerstoneMath; + const { + start, + end, + perpendicularStart, + perpendicularEnd, + } = measurementData.handles; + const { columnPixelSpacing = 1, rowPixelSpacing = 1 } = eventData.image; + + const longLine = createLine(start, end); + const perpendicularLine = createLine(perpendicularStart, perpendicularEnd); + const intersection = lineSegment.intersectLine(longLine, perpendicularLine); + + const distanceToFixed = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + fixedPoint, + intersection + ); + + return { + columnPixelSpacing, // Width that a pixel represents in mm + rowPixelSpacing, // Height that a pixel represents in mm + start, // Start point of the long line + end, // End point of the long line + perpendicularStart, // Start point of the perpendicular line + perpendicularEnd, // End point of the perpendicular line + longLine, // Long line object containing the start and end points + intersection, // Intersection point between long and perpendicular lines + distanceToFixed, // Distance from intersection to the fixed point + fixedPoint, // Opposite point from the handle that is being moved + }; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/getBaseData.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/getBaseData.test.js new file mode 100644 index 000000000..71f0224e5 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/getBaseData.test.js @@ -0,0 +1,102 @@ +import getBaseData from './getBaseData'; + +jest.mock('./../../../../externalModules.js', () => { + const intersectLine = () => ({ + x: 4, + y: 4, + }); + + return { + cornerstoneMath: { + lineSegment: { + intersectLine: jest.fn(intersectLine), + }, + }, + }; +}); + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('getBaseData.js', () => { + it('returns a baseData object with the correct values', () => { + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 3, + rowPixelSpacing: 2, + }, + }; + + const fixedPoint = measurementData.handles.end; + + const result = getBaseData(measurementData, eventData, fixedPoint); + + expect(result.columnPixelSpacing).toEqual(3); + expect(result.rowPixelSpacing).toEqual(2); + expect(result.start).toMatchObject(start); + expect(result.end).toMatchObject(end); + expect(result.perpendicularStart).toMatchObject(perpendicularStart); + expect(result.perpendicularEnd).toMatchObject(perpendicularEnd); + expect(result.longLine.start).toMatchObject(start); + expect(result.longLine.end).toMatchObject(end); + expect(result.intersection.x).toEqual(4); + expect(result.intersection.y).toEqual(4); + expect(result.distanceToFixed).toEqual(2); + expect(result.fixedPoint).toMatchObject(fixedPoint); + }); + + it('ensure that baseData object is returned with default row and column pixel spacing', () => { + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: {}, + }; + + const fixedPoint = measurementData.handles.end; + + const result = getBaseData(measurementData, eventData, fixedPoint); + + expect(result.columnPixelSpacing).toEqual(1); + expect(result.rowPixelSpacing).toEqual(1); + expect(result.start).toMatchObject(start); + expect(result.end).toMatchObject(end); + expect(result.perpendicularStart).toMatchObject(perpendicularStart); + expect(result.perpendicularEnd).toMatchObject(perpendicularEnd); + expect(result.longLine.start).toMatchObject(start); + expect(result.longLine.end).toMatchObject(end); + expect(result.intersection.x).toEqual(4); + expect(result.intersection.y).toEqual(4); + expect(result.distanceToFixed).toEqual(4); + expect(result.fixedPoint).toMatchObject(fixedPoint); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/moveLongLine.js b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/moveLongLine.js new file mode 100644 index 000000000..6534e82c3 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/moveLongLine.js @@ -0,0 +1,54 @@ +import getDistanceWithPixelSpacing from '../../utils/getDistanceWithPixelSpacing.js'; +import getBaseData from '../getBaseData.js'; +import updatePerpendicularLine from './updatePerpendicularLine.js'; + +/** + * Move the long line updating the perpendicular line handles position. + * + * @param {*} proposedPoint Point that was moved in bidirectional tool + * @param {*} measurementData Data from current bidirectional tool measurement + * @param {*} eventData Data object associated with the event + * @param {*} fixedPoint Point that is not being moved in long line + * + * @returns {boolean} True if perpendicular handles were updated, false if not + */ +export default function moveLongLine( + proposedPoint, + measurementData, + eventData, + fixedPoint +) { + const baseData = getBaseData(measurementData, eventData, fixedPoint); + const { columnPixelSpacing, rowPixelSpacing, distanceToFixed } = baseData; + + // Calculate the length of the new line, considering the proposed point + const newLineLength = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + fixedPoint, + proposedPoint + ); + + // Stop here if the handle tries to move before the intersection point + if (newLineLength <= distanceToFixed) { + return false; + } + + // Calculate the new intersection point + const distanceRatio = distanceToFixed / newLineLength; + const newIntersection = { + x: fixedPoint.x + (proposedPoint.x - fixedPoint.x) * distanceRatio, + y: fixedPoint.y + (proposedPoint.y - fixedPoint.y) * distanceRatio, + }; + + // Calculate and the new position of the perpendicular handles + const newLine = updatePerpendicularLine(baseData, newIntersection); + + // Update the perpendicular line handles + measurementData.handles.perpendicularStart.x = newLine.start.x; + measurementData.handles.perpendicularStart.y = newLine.start.y; + measurementData.handles.perpendicularEnd.x = newLine.end.x; + measurementData.handles.perpendicularEnd.y = newLine.end.y; + + return true; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/moveLongLine.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/moveLongLine.test.js new file mode 100644 index 000000000..2587b4efb --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/moveLongLine.test.js @@ -0,0 +1,148 @@ +import moveLongLine from './moveLongLine'; + +jest.mock('./../../../../../externalModules.js', () => { + const intersectLine = () => ({ + x: 4, + y: 4, + }); + + return { + cornerstoneMath: { + lineSegment: { + intersectLine: jest.fn(intersectLine), + }, + }, + }; +}); + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('moveLongLine.js', () => { + it('long line is moved and perpendicular line position is updated', () => { + const proposedPoint = createPoint(12, 6); + + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 0.5, + }, + }; + + const fixedPoint = measurementData.handles.start; + + const result = moveLongLine( + proposedPoint, + measurementData, + eventData, + fixedPoint + ); + + // Expect line to be moved successfully + expect(result).toEqual(true); + + // Expect perpendicular lines position to be updated + expect(perpendicularStart.x).toBeCloseTo(3.9031375531257786); + expect(perpendicularStart.y).toBeCloseTo(6.657455355319679); + expect(perpendicularEnd.x).toBeCloseTo(4.069228512833258); + expect(perpendicularEnd.y).toBeCloseTo(2.671272322340161); + }); + + it('long line is moved and perpendicular line position stays the same', () => { + const proposedPoint = createPoint(-4, 4); + + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 2, + }, + }; + + const fixedPoint = measurementData.handles.end; + + const result = moveLongLine( + proposedPoint, + measurementData, + eventData, + fixedPoint + ); + + // Expect line to be moved successfully + expect(result).toEqual(true); + + // Expect perpendicular lines position to be updated + expect(perpendicularStart.x).toEqual(4); + expect(perpendicularStart.y).toEqual(6); + expect(perpendicularEnd.x).toEqual(4); + expect(perpendicularEnd.y).toEqual(2); + }); + + it('long line is not moved (proposed point is before intersection point)', () => { + const proposedPoint = createPoint(3, 6); + + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 0.5, + }, + }; + + const fixedPoint = measurementData.handles.start; + + const result = moveLongLine( + proposedPoint, + measurementData, + eventData, + fixedPoint + ); + + // Expect line to not be moved + expect(result).toEqual(false); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/updatePerpendicularLine.js b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/updatePerpendicularLine.js new file mode 100644 index 000000000..41b1126cb --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/updatePerpendicularLine.js @@ -0,0 +1,64 @@ +import getLineVector from '../../utils/getLineVector'; +import getDistanceWithPixelSpacing from '../../utils/getDistanceWithPixelSpacing'; + +/** + * Returns the updated line object that will be used to change the position of + * the perpendicular line handles. + * + * @param {*} baseData Base data for bidirectional line moving + * @param {*} mid Middle point considering the proposed point + * + * @returns {*} Returns a line object with the updated handles position + */ +export default function updatePerpendicularLine(baseData, mid) { + const { + columnPixelSpacing, + rowPixelSpacing, + start, + perpendicularStart, + perpendicularEnd, + intersection, + fixedPoint, + } = baseData; + + // Get the original distance from perpendicular start handle to intersection + const distancePS = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + perpendicularStart, + intersection + ); + + // Get the original distance from perpendicular end handle to intersection + const distancePE = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + perpendicularEnd, + intersection + ); + + // Inclination of the perpendicular line + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + fixedPoint, + mid + ); + + // Define the multiplier + const multiplier = fixedPoint === start ? 1 : -1; + const rowMultiplier = multiplier * rowPixelSpacing; + const columnMultiplier = multiplier * columnPixelSpacing; + + // Calculate and return the new position of the perpendicular handles + return { + start: { + x: mid.x + vector.y * distancePS * rowMultiplier, + y: mid.y + vector.x * distancePS * columnMultiplier * -1, + }, + end: { + x: mid.x + vector.y * distancePE * rowMultiplier * -1, + y: mid.y + vector.x * distancePE * columnMultiplier, + }, + }; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/updatePerpendicularLine.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/updatePerpendicularLine.test.js new file mode 100644 index 000000000..0664086b6 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/moveLongLine/updatePerpendicularLine.test.js @@ -0,0 +1,38 @@ +import updatePerpendicularLine from './updatePerpendicularLine'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('updatePerpendicularLine.js', () => { + it('line has points in expected position', () => { + const columnPixelSpacing = 1; + const rowPixelSpacing = 0.5; + const start = createPoint(2, 3); + const perpendicularStart = createPoint(1, 0); + const perpendicularEnd = createPoint(2, 8); + const fixedPoint = perpendicularStart; + const intersection = createPoint(4, 4); + const mid = createPoint(5, 5); + + const baseData = { + columnPixelSpacing, + rowPixelSpacing, + start, + perpendicularStart, + perpendicularEnd, + intersection, + fixedPoint, + }; + + const newLine = updatePerpendicularLine(baseData, mid); + + expect(newLine.start.x).toBeCloseTo(6.910938354123028); + expect(newLine.start.y).toBeCloseTo(-1.1150027331936894); + expect(newLine.end.x).toBeCloseTo(3.500936622008277); + expect(newLine.end.y).toBeCloseTo(9.797002809573513); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getDirectionMultiplier.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getDirectionMultiplier.js new file mode 100644 index 000000000..72b7166b7 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getDirectionMultiplier.js @@ -0,0 +1,14 @@ +import isPerpendicularEndFixed from './isPerpendicularEndFixed.js'; + +/** + * Return the direction multiplier based on the perpendicular fixed point and + * the end point. + * + * @param {*} fixedPoint Point that is not being moved in perpendicular line + * @param {*} perpendicularEnd The end point of the perpencular line + * + * @returns {number} Returns -1 if end point is not being moved or 1 if it is + */ +export default function getDirectionMultiplier(fixedPoint, perpendicularEnd) { + return isPerpendicularEndFixed(fixedPoint, perpendicularEnd) ? -1 : 1; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getDirectionMultiplier.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getDirectionMultiplier.test.js new file mode 100644 index 000000000..e9971404e --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getDirectionMultiplier.test.js @@ -0,0 +1,26 @@ +import getDirectionMultiplier from './getDirectionMultiplier'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('getDirectionMultiplier.js', () => { + it('has positive direction multiplier', () => { + const fixedPoint = createPoint(0, 0); + const perpendicularEnd = createPoint(1, 1); + const multiplier = getDirectionMultiplier(fixedPoint, perpendicularEnd); + + expect(multiplier).toEqual(1); + }); + + it('has negative direction multiplier', () => { + const fixedPoint = createPoint(0, 0); + const perpendicularEnd = fixedPoint; + const multiplier = getDirectionMultiplier(fixedPoint, perpendicularEnd); + + expect(multiplier).toEqual(-1); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getHelperLine.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getHelperLine.js new file mode 100644 index 000000000..7eb1cb5cd --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getHelperLine.js @@ -0,0 +1,38 @@ +import getDirectionMultiplier from './getDirectionMultiplier'; + +/** + * Creates a helper line with the same inclination as the perpendicular line + * but having the start point as the proposed point. + * This line will start in the proposed point and will grow in the long line + * direction trying to cross it to enable finding the intersection point + * between the long line and this new perpendicular line. + * + * @param {*} baseData Base data for bidirectional line moving + * @param {*} proposedPoint Point that was moved in bidirectional tool + * @param {*} vector Vector with the perpendicular line inclination + * + * @returns {*} Returns the helper line containing the start and end points + */ +export default function getHelperLine(baseData, proposedPoint, vector) { + const { + columnPixelSpacing, + rowPixelSpacing, + perpendicularEnd, + fixedPoint, + } = baseData; + + // Create a helper line to find the intesection point in the long line + const highNumber = Number.MAX_SAFE_INTEGER; + + // Get the multiplier + const multiplier = + getDirectionMultiplier(fixedPoint, perpendicularEnd) * highNumber; + + return { + start: proposedPoint, + end: { + x: proposedPoint.x + vector.y * rowPixelSpacing * multiplier, + y: proposedPoint.y + vector.x * columnPixelSpacing * multiplier * -1, + }, + }; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getHelperLine.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getHelperLine.test.js new file mode 100644 index 000000000..eb5fdc9b4 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getHelperLine.test.js @@ -0,0 +1,77 @@ +import getHelperLine from './getHelperLine'; +import getLineVector from '../../utils/getLineVector'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('getHelperLine.js', () => { + it('helper line for fixed start point', () => { + const columnPixelSpacing = 1; + const rowPixelSpacing = 0.5; + const perpendicularStart = createPoint(0, 0); + const perpendicularEnd = createPoint(10, 10); + const proposedPoint = createPoint(20, 20); + const fixedPoint = perpendicularStart; + + const baseData = { + columnPixelSpacing, + rowPixelSpacing, + perpendicularEnd, + fixedPoint, + }; + + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + perpendicularStart, + perpendicularEnd + ); + + const helperLine = getHelperLine(baseData, proposedPoint, vector); + + const expectedEndX = -2014070982048610; + const expectedEndY = 8056283928194540; + + expect(helperLine.start.x).toEqual(20); + expect(helperLine.start.y).toEqual(20); + expect(helperLine.end.x).toEqual(expectedEndX); + expect(helperLine.end.y).toEqual(expectedEndY); + }); + + it('helper line for fixed end point', () => { + const columnPixelSpacing = 1; + const rowPixelSpacing = 0.5; + const perpendicularStart = createPoint(10, 20); + const perpendicularEnd = createPoint(40, 50); + const proposedPoint = createPoint(60, 80); + const fixedPoint = perpendicularEnd; + + const baseData = { + columnPixelSpacing, + rowPixelSpacing, + perpendicularEnd, + fixedPoint, + }; + + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + perpendicularStart, + perpendicularEnd + ); + + const helperLine = getHelperLine(baseData, proposedPoint, vector); + + const expectedEndX = 2014070982048690.2; + const expectedEndY = -8056283928194441; + + expect(helperLine.start.x).toEqual(60); + expect(helperLine.start.y).toEqual(80); + expect(helperLine.end.x).toEqual(expectedEndX); + expect(helperLine.end.y).toEqual(expectedEndY); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getMovingPoint.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getMovingPoint.js new file mode 100644 index 000000000..6b7ebba1f --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getMovingPoint.js @@ -0,0 +1,24 @@ +import isPerpendicularEndFixed from './isPerpendicularEndFixed.js'; + +/** + * Utility function to return the point that is the opposite of the fixed + * point (the point not being moved in the bidirectional tool's perpendicular + * line). + * + * @param {*} fixedPoint Point that is not being moved in perpendicular line + * @param {*} perpendicularStart The start point of the perpencular line + * @param {*} perpendicularEnd The end point of the perpencular line + * + * @returns {*} Point that is being moved in perpendicular line + */ +export default function getMovingPoint( + fixedPoint, + perpendicularStart, + perpendicularEnd +) { + if (isPerpendicularEndFixed(fixedPoint, perpendicularEnd)) { + return perpendicularStart; + } + + return perpendicularEnd; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getMovingPoint.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getMovingPoint.test.js new file mode 100644 index 000000000..6d2d31799 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/getMovingPoint.test.js @@ -0,0 +1,38 @@ +import getMovingPoint from './getMovingPoint'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('getMovingPoint.js', () => { + it('moving point is end point', () => { + const perpendicularStart = createPoint(0, 0); + const perpendicularEnd = createPoint(1, 1); + const fixedPoint = perpendicularStart; + + const movingPoint = getMovingPoint( + fixedPoint, + perpendicularStart, + perpendicularEnd + ); + + expect(movingPoint).toEqual(perpendicularEnd); + }); + + it('moving point is start point', () => { + const perpendicularStart = createPoint(0, 0); + const perpendicularEnd = createPoint(1, 1); + const fixedPoint = perpendicularEnd; + + const movingPoint = getMovingPoint( + fixedPoint, + perpendicularStart, + perpendicularEnd + ); + + expect(movingPoint).toEqual(perpendicularStart); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/isPerpendicularEndFixed.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/isPerpendicularEndFixed.js new file mode 100644 index 000000000..472ae17ac --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/isPerpendicularEndFixed.js @@ -0,0 +1,12 @@ +/** + * Returns true if the end point is the point that is not being moved in the + * perpendicular line. + * + * @param {*} fixedPoint Point that is not being moved in perpendicular line + * @param {*} perpendicularEnd The end point of the perpencular line + * + * @returns {boolean} Returns true if the fixed point is the end point + */ +export default function isPerpendicularEndFixed(fixedPoint, perpendicularEnd) { + return fixedPoint === perpendicularEnd; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/isPerpendicularEndFixed.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/isPerpendicularEndFixed.test.js new file mode 100644 index 000000000..7bc92f286 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/isPerpendicularEndFixed.test.js @@ -0,0 +1,28 @@ +import isPerpendicularEndFixed from './isPerpendicularEndFixed'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('isPerpendicularEndFixed.js', () => { + it('perpendicular end point is the fixed point', () => { + const perpendicularEnd = createPoint(1, 1); + const fixedPoint = perpendicularEnd; + + const isFixed = isPerpendicularEndFixed(fixedPoint, perpendicularEnd); + + expect(isFixed).toEqual(true); + }); + + it('perpendicular end point is not the fixed point', () => { + const perpendicularEnd = createPoint(1, 1); + const fixedPoint = createPoint(2, 2); + + const isFixed = isPerpendicularEndFixed(fixedPoint, perpendicularEnd); + + expect(isFixed).toEqual(false); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/lineHasLength.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/lineHasLength.js new file mode 100644 index 000000000..f1fa15464 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/lineHasLength.js @@ -0,0 +1,26 @@ +import getDistanceWithPixelSpacing from '../../utils/getDistanceWithPixelSpacing'; + +/** + * Returns true if the given line object has its length different from zero, + * considering the column and row pixel spacings. + * + * @param {number} columnPixelSpacing Width that a pixel represents in mm + * @param {number} rowPixelSpacing Height that a pixel represents in mm + * @param {*} line Line object that will have its length calculated + * + * @returns {boolean} Returns true if line has any length + */ +export default function lineHasLength( + columnPixelSpacing, + rowPixelSpacing, + line +) { + const lineLength = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + line.start, + line.end + ); + + return lineLength !== 0; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/lineHasLength.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/lineHasLength.test.js new file mode 100644 index 000000000..f03a338a9 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/lineHasLength.test.js @@ -0,0 +1,53 @@ +import lineHasLength from './lineHasLength'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +function createLine(start, end) { + return { + start, + end, + }; +} + +describe('lineHasLength.js', () => { + it('positive line has length', () => { + const columnPixelSpacing = 1; + const rowPixelSpacing = 0.5; + const start = createPoint(1, 1); + const end = createPoint(5, 5); + const line = createLine(start, end); + + const hasLength = lineHasLength(columnPixelSpacing, rowPixelSpacing, line); + + expect(hasLength).toEqual(true); + }); + + it('negative line has length', () => { + const columnPixelSpacing = 0.2; + const rowPixelSpacing = 0.8; + const start = createPoint(5, 8); + const end = createPoint(3, 1); + const line = createLine(start, end); + + const hasLength = lineHasLength(columnPixelSpacing, rowPixelSpacing, line); + + expect(hasLength).toEqual(true); + }); + + it('line with same points coordinates has not length', () => { + const columnPixelSpacing = 2; + const rowPixelSpacing = 0.1; + const start = createPoint(3, 9); + const end = createPoint(3, 9); + const line = createLine(start, end); + + const hasLength = lineHasLength(columnPixelSpacing, rowPixelSpacing, line); + + expect(hasLength).toEqual(false); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/movePerpendicularLine.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/movePerpendicularLine.js new file mode 100644 index 000000000..bbdb4d341 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/movePerpendicularLine.js @@ -0,0 +1,73 @@ +import external from './../../../../../externalModules.js'; +import getLineVector from '../../utils/getLineVector.js'; +import getBaseData from '../getBaseData.js'; +import lineHasLength from './lineHasLength.js'; +import getHelperLine from './getHelperLine.js'; +import updatePerpendicularLine from './updatePerpendicularLine.js'; + +/** + * Move the perpendicular line updating the opposite handle position. + * + * @param {*} proposedPoint Point that was moved in bidirectional tool + * @param {*} measurementData Data from current bidirectional tool measurement + * @param {*} eventData Data object associated with the event + * @param {*} fixedPoint Point that is not being moved in long line + * + * @returns {boolean} True if perpendicular handles were updated, false if not + */ +export default function movePerpendicularLine( + proposedPoint, + measurementData, + eventData, + fixedPoint +) { + const { lineSegment } = external.cornerstoneMath; + const baseData = getBaseData(measurementData, eventData, fixedPoint); + const { + columnPixelSpacing, + rowPixelSpacing, + start, + longLine, + intersection, + } = baseData; + + // Stop here if the long line has no length + if (!lineHasLength(columnPixelSpacing, rowPixelSpacing, longLine)) { + return false; + } + + // Inclination of the perpendicular line + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + start, + intersection + ); + + // Get a helper line to calculate the intersection + const helperLine = getHelperLine(baseData, proposedPoint, vector); + + // Find the new intersection in the long line + const newIntersection = lineSegment.intersectLine(longLine, helperLine); + + // Stop the flow here if there's no intersection point between lines + if (!newIntersection) { + return false; + } + + // Calculate and the new position of the perpendicular handles + const newLine = updatePerpendicularLine( + baseData, + newIntersection, + helperLine, + vector + ); + + // Change the position of the perpendicular line handles + measurementData.handles.perpendicularStart.x = newLine.start.x; + measurementData.handles.perpendicularStart.y = newLine.start.y; + measurementData.handles.perpendicularEnd.x = newLine.end.x; + measurementData.handles.perpendicularEnd.y = newLine.end.y; + + return true; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/movePerpendicularLine.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/movePerpendicularLine.test.js new file mode 100644 index 000000000..5684168ff --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/movePerpendicularLine.test.js @@ -0,0 +1,201 @@ +import movePerpendicularLine from './movePerpendicularLine'; + +jest.mock('./../../../../../externalModules.js', () => { + const intersectLine = (a, b) => { + if (b.start.x === 3) { + return { + x: 3, + y: 4, + }; + } else if (b.start.y === 5) { + return undefined; + } else if (b.end.y === 4) { + return { + x: NaN, + y: NaN, + }; + } + + return { + x: 4, + y: 4, + }; + }; + + return { + cornerstoneMath: { + lineSegment: { + intersectLine: jest.fn(intersectLine), + }, + }, + }; +}); + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('movePerpendicularLine.js', () => { + it('perpendicular line is moved and the opposite handle position is updated', () => { + const proposedPoint = createPoint(3, 1); + + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 0.5, + }, + }; + + const fixedPoint = measurementData.handles.perpendicularStart; + + const result = movePerpendicularLine( + proposedPoint, + measurementData, + eventData, + fixedPoint + ); + + // Expect line to be moved successfully + expect(result).toEqual(true); + + // Expect perpendicular lines position to be updated + expect(perpendicularStart.x).toEqual(3); + expect(perpendicularStart.y).toEqual(6); + expect(perpendicularEnd.x).toEqual(3); + expect(perpendicularEnd.y).toEqual(1); + }); + + it('perpendicular line is moved and the opposite handle position stays the same', () => { + const proposedPoint = createPoint(4, 7); + + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 2, + }, + }; + + const fixedPoint = measurementData.handles.perpendicularEnd; + + const result = movePerpendicularLine( + proposedPoint, + measurementData, + eventData, + fixedPoint + ); + + // Expect line to be moved successfully + expect(result).toEqual(true); + + // Expect perpendicular lines position to be updated + expect(perpendicularStart.x).toEqual(4); + expect(perpendicularStart.y).toEqual(7); + expect(perpendicularEnd.x).toEqual(4); + expect(perpendicularEnd.y).toEqual(2); + }); + + it('perpendicular line is not moved (proposed point is before intersection point)', () => { + const proposedPoint = createPoint(4, 5); + + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 0.5, + }, + }; + + const fixedPoint = measurementData.handles.perpendicularStart; + + const result = movePerpendicularLine( + proposedPoint, + measurementData, + eventData, + fixedPoint + ); + + // Expect line to not be moved + expect(result).toEqual(false); + }); + + it('perpendicular line is not moved (long line has no length)', () => { + const proposedPoint = createPoint(4, 5); + + const start = createPoint(4, 4); + const end = createPoint(4, 4); + const perpendicularStart = createPoint(4, 4); + const perpendicularEnd = createPoint(4, 4); + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 4, + rowPixelSpacing: 3, + }, + }; + + const fixedPoint = measurementData.handles.perpendicularStart; + + const result = movePerpendicularLine( + proposedPoint, + measurementData, + eventData, + fixedPoint + ); + + // Expect line to not be moved + expect(result).toEqual(false); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/updatePerpendicularLine.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/updatePerpendicularLine.js new file mode 100644 index 000000000..a54501f13 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/updatePerpendicularLine.js @@ -0,0 +1,57 @@ +import getDirectionMultiplier from './getDirectionMultiplier'; +import getMovingPoint from './getMovingPoint'; + +/** + * Returns the updated line object that will be used to change the position of + * the perpendicular line handles. + * + * @param {*} baseData Base data for bidirectional line moving + * @param {*} mid Middle point considering the proposed point + * @param {*} helperLine Line based on proposed point that crosses long line + * @param {*} vector Vector with the perpendicular line inclination + * + * @returns {*} Returns a line object with the updated handles position + */ +export default function updatePerpendicularLine( + baseData, + mid, + helperLine, + vector +) { + const { + columnPixelSpacing, + rowPixelSpacing, + fixedPoint, + perpendicularStart, + perpendicularEnd, + distanceToFixed, + } = baseData; + + // Get the multiplier + const multiplier = + getDirectionMultiplier(fixedPoint, perpendicularEnd) * distanceToFixed; + + // Define the moving point + const movingPoint = getMovingPoint( + fixedPoint, + perpendicularStart, + perpendicularEnd + ); + + // Get the object keys for moving and fixed points + const isMovingStart = movingPoint === perpendicularStart; + const movingKey = isMovingStart ? 'start' : 'end'; + const fixedKey = isMovingStart ? 'end' : 'start'; + + // Calculate and return the new position of the perpendicular handles + return { + [movingKey]: { + x: helperLine.start.x, + y: helperLine.start.y, + }, + [fixedKey]: { + x: mid.x + vector.y * rowPixelSpacing * multiplier, + y: mid.y + vector.x * columnPixelSpacing * multiplier * -1, + }, + }; +} diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/updatePerpendicularLine.test.js b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/updatePerpendicularLine.test.js new file mode 100644 index 000000000..2769be6b1 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/moveHandle/movePerpendicularLine/updatePerpendicularLine.test.js @@ -0,0 +1,86 @@ +import external from './../../../../../externalModules.js'; +import updatePerpendicularLine from './updatePerpendicularLine'; +import getHelperLine from './getHelperLine'; +import getLineVector from '../../utils/getLineVector'; +import getDistanceWithPixelSpacing from '../../utils/getDistanceWithPixelSpacing.js'; + +jest.mock('./../../../../../externalModules.js', () => { + const intersectLine = () => ({ + x: 4, + y: 4, + }); + + return { + cornerstoneMath: { + lineSegment: { + intersectLine: jest.fn(intersectLine), + }, + }, + }; +}); + +function createPoint(x, y) { + return { + x, + y, + }; +} + +function createLine(start, end) { + return { + start, + end, + }; +} + +describe('updatePerpendicularLine.js', () => { + it('line has points in expected position', () => { + const { lineSegment } = external.cornerstoneMath; + const columnPixelSpacing = 1; + const rowPixelSpacing = 0.5; + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(4, 6); + const perpendicularEnd = createPoint(4, 2); + const longLine = createLine(start, end); + const fixedPoint = perpendicularStart; + const intersection = createPoint(4, 4); + const proposedPoint = createPoint(4, 1); + const distanceToFixed = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + fixedPoint, + intersection + ); + + const baseData = { + columnPixelSpacing, + rowPixelSpacing, + start, + perpendicularStart, + perpendicularEnd, + intersection, + fixedPoint, + distanceToFixed, + }; + + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + start, + intersection + ); + + const helperLine = getHelperLine(baseData, proposedPoint, vector); + const mid = lineSegment.intersectLine(longLine, helperLine); + + const newLine = updatePerpendicularLine(baseData, mid, helperLine, vector); + + console.log(mid, vector); + + expect(newLine.start.x).toEqual(4); + expect(newLine.start.y).toEqual(6); + expect(newLine.end.x).toEqual(4); + expect(newLine.end.y).toEqual(1); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularBothFixedLeft.js b/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularBothFixedLeft.js deleted file mode 100644 index b58bc97d7..000000000 --- a/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularBothFixedLeft.js +++ /dev/null @@ -1,65 +0,0 @@ -import external from './../../../../externalModules.js'; - -// Move long-axis start point -export default function(proposedPoint, data) { - const { distance } = external.cornerstoneMath.point; - const { start, end, perpendicularStart, perpendicularEnd } = data.handles; - - 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 = external.cornerstoneMath.lineSegment.intersectLine( - longLine, - perpendicularLine - ); - - const distanceFromPerpendicularP1 = distance( - perpendicularStart, - intersection - ); - const distanceFromPerpendicularP2 = distance(perpendicularEnd, intersection); - - const distanceToLineP2 = distance(end, intersection); - const newLineLength = distance(end, proposedPoint); - - if (newLineLength <= distanceToLineP2) { - return false; - } - - const dx = (end.x - proposedPoint.x) / newLineLength; - const dy = (end.y - proposedPoint.y) / newLineLength; - - const k = distanceToLineP2 / newLineLength; - - const newIntersection = { - x: end.x + (proposedPoint.x - end.x) * k, - y: end.y + (proposedPoint.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/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularBothFixedRight.js b/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularBothFixedRight.js deleted file mode 100644 index eb1fb4e4a..000000000 --- a/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularBothFixedRight.js +++ /dev/null @@ -1,65 +0,0 @@ -import external from './../../../../externalModules.js'; - -// Move long-axis end point -export default function(proposedPoint, data) { - const { distance } = external.cornerstoneMath.point; - const { start, end, perpendicularStart, perpendicularEnd } = data.handles; - - 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 = external.cornerstoneMath.lineSegment.intersectLine( - longLine, - perpendicularLine - ); - - const distanceFromPerpendicularP1 = distance( - perpendicularStart, - intersection - ); - const distanceFromPerpendicularP2 = distance(perpendicularEnd, intersection); - - const distanceToLineP2 = distance(start, intersection); - const newLineLength = distance(start, proposedPoint); - - if (newLineLength <= distanceToLineP2) { - return false; - } - - const dx = (start.x - proposedPoint.x) / newLineLength; - const dy = (start.y - proposedPoint.y) / newLineLength; - - const k = distanceToLineP2 / newLineLength; - - const newIntersection = { - x: start.x + (proposedPoint.x - start.x) * k, - y: start.y + (proposedPoint.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/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularLeftFixedPoint.js b/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularLeftFixedPoint.js deleted file mode 100644 index ea9711787..000000000 --- a/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularLeftFixedPoint.js +++ /dev/null @@ -1,93 +0,0 @@ -import external from './../../../../externalModules.js'; - -// Move perpendicular line start point -export default function(movedPoint, data) { - const { distance } = external.cornerstoneMath.point; - const { start, end, perpendicularStart, perpendicularEnd } = data.handles; - - const fudgeFactor = 1; - const fixedPoint = perpendicularEnd; - - const distanceFromFixed = external.cornerstoneMath.lineSegment.distanceToPoint( - data.handles, - fixedPoint - ); - const distanceFromMoved = external.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 = external.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/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularRightFixedPoint.js b/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularRightFixedPoint.js deleted file mode 100644 index 95039912e..000000000 --- a/src/tools/annotation/bidirectionalTool/moveHandle/perpendicularRightFixedPoint.js +++ /dev/null @@ -1,91 +0,0 @@ -import external from './../../../../externalModules.js'; - -// Move perpendicular line end point -export default function(movedPoint, data) { - const { distance } = external.cornerstoneMath.point; - const { start, end, perpendicularStart, perpendicularEnd } = data.handles; - - const fudgeFactor = 1; - - const fixedPoint = perpendicularStart; - - const distanceFromFixed = external.cornerstoneMath.lineSegment.distanceToPoint( - data.handles, - fixedPoint - ); - const distanceFromMoved = external.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 = external.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/src/tools/annotation/bidirectionalTool/moveHandle/setHandlesPosition.js b/src/tools/annotation/bidirectionalTool/moveHandle/setHandlesPosition.js index af7fbd664..a96e417b3 100644 --- a/src/tools/annotation/bidirectionalTool/moveHandle/setHandlesPosition.js +++ b/src/tools/annotation/bidirectionalTool/moveHandle/setHandlesPosition.js @@ -1,8 +1,6 @@ import external from './../../../../externalModules.js'; -import perpendicularBothFixedLeft from './perpendicularBothFixedLeft.js'; -import perpendicularBothFixedRight from './perpendicularBothFixedRight.js'; -import perpendicularLeftFixedPoint from './perpendicularLeftFixedPoint.js'; -import perpendicularRightFixedPoint from './perpendicularRightFixedPoint.js'; +import moveLongLine from './moveLongLine/moveLongLine.js'; +import movePerpendicularLine from './movePerpendicularLine/movePerpendicularLine.js'; // Sets position of handles(start, end, perpendicularStart, perpendicularEnd) export default function(handle, eventData, data, distanceFromTool) { @@ -22,7 +20,7 @@ export default function(handle, eventData, data, distanceFromTool) { if (handle.index === 0) { // If long-axis start point is moved - result = perpendicularBothFixedLeft(proposedPoint, data); + result = moveLongLine(proposedPoint, data, eventData, data.handles.end); if (result) { handle.x = proposedPoint.x; handle.y = proposedPoint.y; @@ -32,7 +30,7 @@ export default function(handle, eventData, data, distanceFromTool) { } } else if (handle.index === 1) { // If long-axis end point is moved - result = perpendicularBothFixedRight(proposedPoint, data); + result = moveLongLine(proposedPoint, data, eventData, data.handles.start); if (result) { handle.x = proposedPoint.x; handle.y = proposedPoint.y; @@ -93,7 +91,12 @@ export default function(handle, eventData, data, distanceFromTool) { movedPoint = false; if (!outOfBounds) { - movedPoint = perpendicularLeftFixedPoint(proposedPoint, data); + movedPoint = movePerpendicularLine( + proposedPoint, + data, + eventData, + data.handles.perpendicularEnd + ); if (!movedPoint) { eventData.currentPoints.image.x = data.handles.perpendicularStart.x; @@ -154,7 +157,12 @@ export default function(handle, eventData, data, distanceFromTool) { movedPoint = false; if (!outOfBounds) { - movedPoint = perpendicularRightFixedPoint(proposedPoint, data); + movedPoint = movePerpendicularLine( + proposedPoint, + data, + eventData, + data.handles.perpendicularStart + ); if (!movedPoint) { eventData.currentPoints.image.x = data.handles.perpendicularEnd.x; diff --git a/src/tools/annotation/bidirectionalTool/utils/getDistanceWithPixelSpacing.js b/src/tools/annotation/bidirectionalTool/utils/getDistanceWithPixelSpacing.js new file mode 100644 index 000000000..fe6411600 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/utils/getDistanceWithPixelSpacing.js @@ -0,0 +1,21 @@ +/** + * Return the distance between 2 points considering the pixel spacing + * + * @param {number} columnPixelSpacing Width that a pixel represents in mm + * @param {number} rowPixelSpacing Height that a pixel represents in mm + * @param {*} startPoint Start point of the line + * @param {*} endPoint End point of the line + * + * @returns {number} Distance between the 2 given points considering the pixel spacing + */ +export default function getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint +) { + const calcX = (startPoint.x - endPoint.x) / rowPixelSpacing; + const calcY = (startPoint.y - endPoint.y) / columnPixelSpacing; + + return Math.sqrt(calcX * calcX + calcY * calcY); +} diff --git a/src/tools/annotation/bidirectionalTool/utils/getDistanceWithPixelSpacing.test.js b/src/tools/annotation/bidirectionalTool/utils/getDistanceWithPixelSpacing.test.js new file mode 100644 index 000000000..bc128909f --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/utils/getDistanceWithPixelSpacing.test.js @@ -0,0 +1,61 @@ +import getDistanceWithPixelSpacing from './getDistanceWithPixelSpacing'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('getDistanceWithPixelSpacing.js', () => { + it('distance for positive line', () => { + const columnPixelSpacing = 1; + const rowPixelSpacing = 0.5; + + const startPoint = createPoint(2, 4); + const endPoint = createPoint(4, 8); + + const distance = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint + ); + + expect(distance).toEqual(5.656854249492381); + }); + + it('distance for negative line', () => { + const columnPixelSpacing = 1; + const rowPixelSpacing = 0.5; + + const startPoint = createPoint(10, 10); + const endPoint = createPoint(0, 0); + + const distance = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint + ); + + expect(distance).toEqual(22.360679774997898); + }); + + it('distance for line with no length', () => { + const columnPixelSpacing = 2; + const rowPixelSpacing = 0.5; + + const startPoint = createPoint(5, 7); + const endPoint = createPoint(5, 7); + + const distance = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint + ); + + expect(distance).toEqual(0); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/utils/getLineVector.js b/src/tools/annotation/bidirectionalTool/utils/getLineVector.js new file mode 100644 index 000000000..3ff93ace2 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/utils/getLineVector.js @@ -0,0 +1,28 @@ +/** + * Return the Vector of a line which determines its inclination and length + * + * @param {number} columnPixelSpacing Width that a pixel represents in mm + * @param {number} rowPixelSpacing Height that a pixel represents in mm + * @param {*} startPoint Start point of the line + * @param {*} endPoint End point of the line + * + * @returns {*} Resulting line inclination vector + */ +export default function getLineVector( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint +) { + const dx = (startPoint.x - endPoint.x) * columnPixelSpacing; + const dy = (startPoint.y - endPoint.y) * rowPixelSpacing; + const length = Math.sqrt(dx * dx + dy * dy); + const vectorX = dx / length; + const vectorY = dy / length; + + return { + x: vectorX, + y: vectorY, + length, + }; +} diff --git a/src/tools/annotation/bidirectionalTool/utils/getLineVector.test.js b/src/tools/annotation/bidirectionalTool/utils/getLineVector.test.js new file mode 100644 index 000000000..b7af704e5 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/utils/getLineVector.test.js @@ -0,0 +1,67 @@ +import getLineVector from './getLineVector'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('getLineVector.js', () => { + it('line vector for positive line', () => { + const columnPixelSpacing = 1.4; + const rowPixelSpacing = 0.2; + + const startPoint = createPoint(2, 4); + const endPoint = createPoint(4, 8); + + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint + ); + + expect(vector.x).toBeCloseTo(-0.9615239476408233); + expect(vector.y).toBeCloseTo(-0.2747211278973781); + expect(vector.length).toBeCloseTo(2.912043955712207); + }); + + it('line vector for negative line', () => { + const columnPixelSpacing = 1.4; + const rowPixelSpacing = 0.2; + + const startPoint = createPoint(5, 5); + const endPoint = createPoint(1, 1); + + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint + ); + + expect(vector.x).toBeCloseTo(0.9899494936611666); + expect(vector.y).toBeCloseTo(0.14142135623730953); + expect(vector.length).toBeCloseTo(5.65685424949238); + }); + + it('line vector for line with same point coordinates', () => { + const columnPixelSpacing = 1; + const rowPixelSpacing = 1; + + const startPoint = createPoint(4, 6); + const endPoint = createPoint(4, 6); + + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + startPoint, + endPoint + ); + + expect(vector.x).toEqual(NaN); + expect(vector.y).toEqual(NaN); + expect(vector.length).toEqual(0); + }); +}); diff --git a/src/tools/annotation/bidirectionalTool/utils/updatePerpendicularLineHandles.js b/src/tools/annotation/bidirectionalTool/utils/updatePerpendicularLineHandles.js index b945a4537..8996a66e8 100644 --- a/src/tools/annotation/bidirectionalTool/utils/updatePerpendicularLineHandles.js +++ b/src/tools/annotation/bidirectionalTool/utils/updatePerpendicularLineHandles.js @@ -1,12 +1,27 @@ -// Update the perpendicular line handles -export default function(eventData, data) { - if (!data.handles.perpendicularStart.locked) { - return; +import getLineVector from './getLineVector'; + +/** + * Update the perpendicular line handles when the measurement is being created. + * This method will make the perpendicular line intersect in the middle of the + * long line and assume half the size of the long line. + * + * @param {*} eventData Data object associated with the event + * @param {*} measurementData Data from current bidirectional tool measurement + * + * @returns {boolean} False in case the handle is not locked or true when moved + */ +export default function updatePerpendicularLineHandles( + eventData, + measurementData +) { + if (!measurementData.handles.perpendicularStart.locked) { + return false; } let startX, startY, endX, endY; - const { start, end } = data.handles; + const { start, end } = measurementData.handles; + const { columnPixelSpacing = 1, rowPixelSpacing = 1 } = eventData.image; if (start.x === end.x && start.y === end.y) { startX = start.x; @@ -20,24 +35,28 @@ export default function(eventData, data) { y: (start.y + end.y) / 2, }; - // Length of long-axis - const dx = (start.x - end.x) * (eventData.image.columnPixelSpacing || 1); - const dy = (start.y - end.y) * (eventData.image.rowPixelSpacing || 1); - const length = Math.sqrt(dx * dx + dy * dy); - - const vectorX = (start.x - end.x) / length; - const vectorY = (start.y - 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; + // Inclination of the perpendicular line + const vector = getLineVector( + columnPixelSpacing, + rowPixelSpacing, + start, + end + ); + + const perpendicularLineLength = vector.length / 2; + const rowMultiplier = perpendicularLineLength / (2 * rowPixelSpacing); + const columnMultiplier = perpendicularLineLength / (2 * columnPixelSpacing); + + startX = mid.x + columnMultiplier * vector.y; + startY = mid.y - rowMultiplier * vector.x; + endX = mid.x - columnMultiplier * vector.y; + endY = mid.y + rowMultiplier * vector.x; } - data.handles.perpendicularStart.x = startX; - data.handles.perpendicularStart.y = startY; - data.handles.perpendicularEnd.x = endX; - data.handles.perpendicularEnd.y = endY; + measurementData.handles.perpendicularStart.x = startX; + measurementData.handles.perpendicularStart.y = startY; + measurementData.handles.perpendicularEnd.x = endX; + measurementData.handles.perpendicularEnd.y = endY; + + return true; } diff --git a/src/tools/annotation/bidirectionalTool/utils/updatePerpendicularLineHandles.test.js b/src/tools/annotation/bidirectionalTool/utils/updatePerpendicularLineHandles.test.js new file mode 100644 index 000000000..cb2775f74 --- /dev/null +++ b/src/tools/annotation/bidirectionalTool/utils/updatePerpendicularLineHandles.test.js @@ -0,0 +1,96 @@ +import updatePerpendicularLineHandles from './updatePerpendicularLineHandles'; +import getDistanceWithPixelSpacing from './getDistanceWithPixelSpacing'; + +function createPoint(x, y) { + return { + x, + y, + }; +} + +describe('updatePerpendicularLineHandles.js', () => { + it('perpendicular line has half the size of the long line', () => { + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(8, 4); + const perpendicularEnd = createPoint(8, 4); + + perpendicularStart.locked = true; + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 1, + }, + }; + + const result = updatePerpendicularLineHandles(eventData, measurementData); + + // Expect line to have been moved successfully + expect(result).toEqual(true); + + // Expect perpendicular line to be in the correct position + expect(perpendicularStart.x).toEqual(4); + expect(perpendicularStart.y).toEqual(6); + expect(perpendicularEnd.x).toEqual(4); + expect(perpendicularEnd.y).toEqual(2); + + const { columnPixelSpacing, rowPixelSpacing } = eventData.image; + + const distanceLongLine = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + start, + end + ); + + const distancePerpendicularLine = getDistanceWithPixelSpacing( + columnPixelSpacing, + rowPixelSpacing, + perpendicularStart, + perpendicularEnd + ); + + // Expect perpendicular line to has half the size of the long line + expect(distancePerpendicularLine).toEqual(distanceLongLine / 2); + }); + + it('line is not updated because start point is not locked', () => { + const start = createPoint(0, 4); + const end = createPoint(8, 4); + const perpendicularStart = createPoint(8, 4); + const perpendicularEnd = createPoint(8, 4); + + perpendicularStart.locked = false; + + const measurementData = { + handles: { + start, + end, + perpendicularStart, + perpendicularEnd, + }, + }; + + const eventData = { + image: { + columnPixelSpacing: 1, + rowPixelSpacing: 1, + }, + }; + + const result = updatePerpendicularLineHandles(eventData, measurementData); + + // Expect line to not have been moved + expect(result).toEqual(false); + }); +});