Skip to content

Commit

Permalink
Merge pull request #916 from daniel2101/fix-issue-579
Browse files Browse the repository at this point in the history
Fix issue 579
  • Loading branch information
JamesAPetts authored Apr 30, 2019
2 parents 1c347ab + b1fb8e7 commit 07fa8df
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 29 deletions.
46 changes: 30 additions & 16 deletions src/drawing/drawEllipse.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import external from './../externalModules.js';
import path from './path.js';
import { rotatePoint } from '../util/pointProjector.js';

/**
* Draw an ellipse within the bounding box defined by `corner1` and `corner2`.
Expand All @@ -15,6 +16,7 @@ import path from './path.js';
* @param {String} [coordSystem='pixel'] - Can be "pixel" (default) or "canvas". The coordinate
* system of the points passed in to the function. If "pixel" then cornerstone.pixelToCanvas
* is used to transform the points from pixel to canvas coordinates.
* @param {Number} initialRotation - Ellipse initial rotation
* @returns {undefined}
*/
export default function(
Expand All @@ -23,32 +25,44 @@ export default function(
corner1,
corner2,
options,
coordSystem = 'pixel'
coordSystem = 'pixel',
initialRotation = 0.0
) {
// http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
if (coordSystem === 'pixel') {
corner1 = external.cornerstone.pixelToCanvas(element, corner1);
corner2 = external.cornerstone.pixelToCanvas(element, corner2);
}
const x = Math.min(corner1.x, corner2.x);
const y = Math.min(corner1.y, corner2.y);

const viewport = external.cornerstone.getViewport(element);

// Calculate the center of the image
const { clientWidth: width, clientHeight: height } = element;
const { scale, translation } = viewport;
const rotation = viewport.rotation - initialRotation;

const centerPoint = {
x: width / 2 + translation.x * scale,
y: height / 2 + translation.y * scale,
};

if (Math.abs(rotation) > 0.05) {
corner1 = rotatePoint(corner1, centerPoint, -rotation);
corner2 = rotatePoint(corner2, centerPoint, -rotation);
}
const w = Math.abs(corner1.x - corner2.x);
const h = Math.abs(corner1.y - corner2.y);
const xMin = Math.min(corner1.x, corner2.x);
const yMin = Math.min(corner1.y, corner2.y);

let center = { x: xMin + w / 2, y: yMin + h / 2 };

const kappa = 0.5522848,
ox = (w / 2) * kappa, // Control point offset horizontal
oy = (h / 2) * kappa, // Control point offset vertical
xe = x + w, // X-end
ye = y + h, // Y-end
xm = x + w / 2, // X-middle
ym = y + h / 2; // Y-middle
if (Math.abs(rotation) > 0.05) {
center = rotatePoint(center, centerPoint, rotation);
}
const angle = (rotation * Math.PI) / 180;

path(context, options, context => {
context.moveTo(x, ym);
context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
context.ellipse(center.x, center.y, w / 2, h / 2, angle, 0, 2 * Math.PI);
context.closePath();
});
}
64 changes: 58 additions & 6 deletions src/drawing/drawRect.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import external from './../externalModules.js';
import path from './path.js';
import { rotatePoint } from '../util/pointProjector.js';

/**
* Draw a rectangle defined by `corner1` and `corner2`.
Expand All @@ -15,6 +16,7 @@ import path from './path.js';
* @param {String} [coordSystem='pixel'] - Can be "pixel" (default) or "canvas". The coordinate
* system of the points passed in to the function. If "pixel" then cornerstone.pixelToCanvas
* is used to transform the points from pixel to canvas coordinates.
* @param {Number} initialRotation - Rectangle initial rotation
* @returns {undefined}
*/
export default function(
Expand All @@ -23,7 +25,8 @@ export default function(
corner1,
corner2,
options,
coordSystem = 'pixel'
coordSystem = 'pixel',
initialRotation = 0.0
) {
if (coordSystem === 'pixel') {
const cornerstone = external.cornerstone;
Expand All @@ -32,12 +35,61 @@ export default function(
corner2 = cornerstone.pixelToCanvas(element, corner2);
}

const left = Math.min(corner1.x, corner2.x);
const top = Math.min(corner1.y, corner2.y);
const width = Math.abs(corner1.x - corner2.x);
const height = Math.abs(corner1.y - corner2.y);
const viewport = external.cornerstone.getViewport(element);

// Calculate the center of the image
const { clientWidth: width, clientHeight: height } = element;
const { scale, translation } = viewport;
const rotation = viewport.rotation - initialRotation;

const centerPoint = {
x: width / 2 + translation.x * scale,
y: height / 2 + translation.y * scale,
};

if (Math.abs(rotation) > 0.05) {
corner1 = rotatePoint(corner1, centerPoint, -rotation);
corner2 = rotatePoint(corner2, centerPoint, -rotation);
}

const w = Math.abs(corner1.x - corner2.x);
const h = Math.abs(corner1.y - corner2.y);

corner1 = {
x: Math.min(corner1.x, corner2.x),
y: Math.min(corner1.y, corner2.y),
};

corner2 = {
x: corner1.x + w,
y: corner1.y + h,
};

let corner3 = {
x: corner1.x + w,
y: corner1.y,
};

let corner4 = {
x: corner1.x,
y: corner1.y + h,
};

if (Math.abs(rotation) > 0.05) {
corner1 = rotatePoint(corner1, centerPoint, rotation);
corner2 = rotatePoint(corner2, centerPoint, rotation);
corner3 = rotatePoint(corner3, centerPoint, rotation);
corner4 = rotatePoint(corner4, centerPoint, rotation);
}

path(context, options, context => {
context.rect(left, top, width, height);
context.moveTo(corner1.x, corner1.y);
context.lineTo(corner3.x, corner3.y);
context.moveTo(corner3.x, corner3.y);
context.lineTo(corner2.x, corner2.y);
context.moveTo(corner2.x, corner2.y);
context.lineTo(corner4.x, corner4.y);
context.moveTo(corner4.x, corner4.y);
context.lineTo(corner1.x, corner1.y);
});
}
17 changes: 13 additions & 4 deletions src/tools/annotation/EllipticalRoiTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default class EllipticalRoiTool extends BaseAnnotationTool {
highlight: true,
active: true,
},
initialRotation: eventData.viewport.rotation,
textBox: {
active: false,
hasMoved: false,
Expand Down Expand Up @@ -200,9 +201,17 @@ export default class EllipticalRoiTool extends BaseAnnotationTool {
setShadow(context, this.configuration);

// Draw
drawEllipse(context, element, data.handles.start, data.handles.end, {
color,
});
drawEllipse(
context,
element,
data.handles.start,
data.handles.end,
{
color,
},
'pixel',
data.handles.initialRotation
);
drawHandles(context, eventData, data.handles, handleOptions);

// Update textbox stats
Expand Down Expand Up @@ -446,7 +455,7 @@ function _calculateStats(image, element, handles, modality, pixelSpacing) {
ellipseCoordinates.height
);

// Calculate the mean & standard deviation from the pixels and the ellipse details
// Calculate the mean & standard deviation from the pixels and the ellipse details.
const ellipseMeanStdDev = calculateEllipseStatistics(
pixels,
ellipseCoordinates
Expand Down
15 changes: 15 additions & 0 deletions src/tools/annotation/EllipticalRoiTool.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const goodMouseEventData = {
y: 0,
},
},
viewport: {
rotation: 0,
},
};

describe('EllipticalRoiTool.js', () => {
Expand Down Expand Up @@ -83,6 +86,18 @@ describe('EllipticalRoiTool.js', () => {
expect(endHandle.y).toBe(goodMouseEventData.currentPoints.image.y);
});

it('returns a measurement with a initial rotation', () => {
const instantiatedTool = new EllipticalRoiTool();

const toolMeasurement = instantiatedTool.createNewMeasurement(
goodMouseEventData
);

const initialRotation = toolMeasurement.handles.initialRotation;

expect(initialRotation).toBe(goodMouseEventData.viewport.rotation);
});

it('returns a measurement with a textBox handle', () => {
const instantiatedTool = new EllipticalRoiTool();

Expand Down
15 changes: 12 additions & 3 deletions src/tools/annotation/RectangleRoiTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export default class RectangleRoiTool extends BaseAnnotationTool {
highlight: true,
active: true,
},
initialRotation: eventData.viewport.rotation,
textBox: {
active: false,
hasMoved: false,
Expand Down Expand Up @@ -187,9 +188,17 @@ export default class RectangleRoiTool extends BaseAnnotationTool {
setShadow(context, this.configuration);

// Draw
drawRect(context, element, data.handles.start, data.handles.end, {
color,
});
drawRect(
context,
element,
data.handles.start,
data.handles.end,
{
color,
},
'pixel',
data.handles.initialRotation
);
drawHandles(context, eventData, data.handles, handleOptions);

// Update textbox stats
Expand Down
15 changes: 15 additions & 0 deletions src/tools/annotation/RectangleRoiTool.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const goodMouseEventData = {
y: 0,
},
},
viewport: {
rotation: 0,
},
};

describe('RectangleRoiTool.js', () => {
Expand Down Expand Up @@ -83,6 +86,18 @@ describe('RectangleRoiTool.js', () => {
expect(endHandle.y).toBe(goodMouseEventData.currentPoints.image.y);
});

it('returns a measurement with a initial rotation', () => {
const instantiatedTool = new RectangleRoiTool();

const toolMeasurement = instantiatedTool.createNewMeasurement(
goodMouseEventData
);

const initialRotation = toolMeasurement.handles.initialRotation;

expect(initialRotation).toBe(goodMouseEventData.viewport.rotation);
});

it('returns a measurement with a textBox handle', () => {
const instantiatedTool = new RectangleRoiTool();

Expand Down
29 changes: 29 additions & 0 deletions src/util/pointProjector.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,32 @@ export function planePlaneIntersection(targetImagePlane, referenceImagePlane) {
end: intersections[1],
};
}

/**
* Translate a point using a rotation angle.
* @export @public @method
* @name rotatePoint
*
* @param {Object} point - `{ x, y }` in either pixel or canvas coordinates.
* @param {Object} center - `{ x, y }` in either pixel or canvas coordinates.
* @param {Number} angle - angle in degrees
* @returns {Object} - `{ x, y }` new point translated
*/
export function rotatePoint(point, center, angle) {
const angleRadians = angle * (Math.PI / 180); // Convert to radians

const rotatedX =
Math.cos(angleRadians) * (point.x - center.x) -
Math.sin(angleRadians) * (point.y - center.y) +
center.x;

const rotatedY =
Math.sin(angleRadians) * (point.x - center.x) +
Math.cos(angleRadians) * (point.y - center.y) +
center.y;

return {
x: rotatedX,
y: rotatedY,
};
}

0 comments on commit 07fa8df

Please sign in to comment.