Draw precise circle through two points - python

I am programming an application for high precision measurements. Now I have a problem.
User can select a segment on axis X that would represent diameter of a circle.
How do I draw the circle in question through two selected end points if the selected diameter has even pixel length
In this case origin of the circle would be between two pixels, which would make me choose one of them, thus shifting the circle one pixel left or right.
It is true, the look wouldn't change because one pixel will be included and another added on the other side of selected segment, but what if I need to draw a square based on the segment's length around the area first.
Then circle and square would touch in wrong places.
What is usually done in this circumstances?

ImageDraw.Draw.ellipse takes a bounding box as a parameter. It doesn't care where the center of the circle goes.
The circle outline includes all 4 edges of the bounding box in the tests I performed. I suggest you do your own tests too.
Calculating the bounding box from two arbitrary points is easy, once you have the diameter and center of the circle.
diameter = ((x2 - x1)**2 + (y2 - y1)**2) ** 0.5
radius = diameter / 2
xC, yC = (x1 + x2) / 2.0, (y1 + y2) / 2.0
bbox = [round(xC - radius), round(yC - radius), round(xC + radius), round(yC + radius)]

Related

PyCairo : how to resize & rotate an image by its center to a final canvas

Using PyCairo, I want to be able to have a method that can put, resize & rotate a given ImageSurface on a context, but rotating by the center of the image (not the top-left)
Okay I've tried the examples I've found here but without any success.
Let's introduce the "context" in details.
I've got a "finale" ImageSurface (say A) on which some other images & texts are written.
I want to put on it another ImageSurface (say B), at a specified position where this position is the top-left where to put B on A. Then I need to resize B (reduce its size) and to rotate it by its center instead of by its top-left corner.
Here is an illustration of the wanted result :
I've tried the following but without success:
def draw_rotated_image(ctx, image_surface, left, top, width, height, angle):
ctx.save()
w = image_surface.get_width()
h = image_surface.get_height()
cl = left / (width/w)
ct = top / (height/h)
ctx.rotate(angle*3.1415927/180)
ctx.scale(width/w, height/h)
ctx.translate(cl + (-0.5*w),ct + (-0.5*h) )
ctx.set_source_surface(image_surface, 0, 0)
ctx.paint()
ctx.restore()
return
Thanks a lot for your help :)
Well, I finally made it ! (thanks to my 14 year old son who made me revise my trigonometry)
I'm trying to explain here my solution.
First, I AM NOT A MATHEMATICIAN. So there is probably a best way, and surely my explanation have errors, but I'm just explaining the logical way I have used to get the good result.
The best idea for that is to first draw a circle around the rectangle, because we need to move the top-left corner of this rectangle, around its own circle, according to the desired angle.
So to get the radius of the rectangle circle, we need to compute its hypothenuse, then to divide by 2 :
hypothenuse = math.hypot(layerWidth,layerHeight)
radius = hypothenuse / 2
Then we will be able to draw a circle around the rectangle.
Second, we need to know at which angle, on this circle, is the actual top-left corner of the rectangle.
So for that, we need compute the invert tangent of the rectangle, which is arc-tan(height/width).
But because we want to know how many degrees are we far from 0°, we need to compute the opposite so arc-tan(width/height).
Finally, another singularity is that Cairo 0° is in fact at 90°, so we will have to rotate again.
This can be shown by this simple graphic :
So finally, what is necessary to understand ?
If you want to draw a layer, with an angle, rotated by its center, the top-left point will move around the circle according to the desired angle.
The top-left position with a given angle of 0 needs to be "the reference".
So we need to get the new X-Y position where to start putting the layer to be able to rotate it :
Now, we can write a function that will return the X-Y pos of the top left rectangle where to draw it with a given angle :
def getTopLeftForRectangleAtAngle(layerLeft,layerTop,layerWidth,layerHeight,angleInDegrees):
# now we need to know the angle of the top-left corner
# for that, we need to compute the arc tangent of the triangle-rectangle:
layerAngleRad = math.atan((layerWidth / layerHeight))
layerAngle = math.degrees(layerAngleRad)
# 0° is 3 o'clock. So we need to rotate left to 90° first
# Then we want that 0° will be the top left corner which is "layerAngle" far from 0
if (angleInDegrees >= (90 + layerAngle)):
angleInDegrees -= (90 + layerAngle)
else:
angleInDegrees = 360 - ((90 + layerAngle) - angleInDegrees)
angle = (angleInDegrees * math.pi / 180.0)
centerLeft = layerLeft + (layerWidth / 2)
centerTop = layerTop + (layerHeight / 2)
# hypothenuse will help us knowing the circle radius
hypothenuse = math.hypot(layerWidth,layerHeight)
radius = hypothenuse / 2
pointX = centerLeft + radius * math.cos(angle)
pointY = centerTop + radius * math.sin(angle)
return (pointX,pointY)
And finally, here is how to use it with an image we want to resize, rotate and write on a context:
def draw_rotated_image(ctx, image_surface, left, top, width, height, angle=0.0, alpha=1.0):
ctx.save()
w = image_surface.get_width()
h = image_surface.get_height()
# get the new top-left position according to the given angle
newTopLeft = getTopLeftForRectangleAtAngle(left, top, width, height, angle)
# translate
ctx.translate(newTopLeft[0], newTopLeft[1])
# rotate
ctx.rotate(angle * math.pi / 180)
# scale & write
ctx.scale(width/w, height/h)
ctx.set_source_surface(image_surface, 0, 0)
ctx.paint_with_alpha(alpha)
ctx.restore()
return

Determine if points are within ellipse (Python and Pillow)

Using PIL, I've drawn an ellipse using the square coordinates around the circle (ie, the X and Y coordinates of the shape if it were a square).
I want to determine if coordinates I pass in are within the range of the coordinates of the ellipse. If this were a square, I imagine this would be relatively easy, as I could simply ask the script to determine if the coordinates (x and y) are between the range of x and y coordinates of the square.
However, because it's an ellipse, I do not know the coordinates of the lines produced by the arc. For this reason, I believe I need a PIL solution to give me the range of the ellipse line first. Does this exist natively in PIL?
Full handwork solution incoming
Since an ellipse is just a "stuffed circle", one could resize the ellipse to be a circle and move on from there.
You have the coordinates, so the first step is to resize.
x_diff = abs(x_max - x_min)
x = (x - x_min ) / x_diff
y_diff = abs(y_max - y_min)
y = (y - y_min ) / y_diff
This resizes the ellipse to a circle with radius .5.
Using Pythagora's theorem, the distance to (0, 0) should be lower than the distances to the x and y axis, each squared. So a² + b² = c² becomes c <= sqrt(a² + b²). And since we have scaled the ellipse to a circle with radius 0.5, we can assume, that c has to be smaller or equal to 0.5.
import math
c = math.sqrt(x**2 + y**2)
if c <= .5:
print("Coordinate lies within ellipse!")
else:
print("Try again :(")

Distance between two Aruco Markers in Python?

I am trying to calculate the distance between two Aruco markers in Python. I have code that can calculate the pose of one marker but I am not sure how to move from there. Is there anyone that has done something similar or can point me in the right direction?
Thank you!
You can find the distance between the markers by calculating the distance between the corners of the detected markers.
The following will give you the corners and the co-ordinates of that corner.
corners, ids, rejectedImgPoints = aruco.detectMarkers(gray, aruco_dict, parameters=arucoParameters)
x1 = int (corners[0][0][0][0])
y1 = int (corners[0][0][0][1])
Similarly you can find the co-ordinates of the corner of the other marker (x2,y2).
import math
def calculateDistance(x1,y1,x2,y2):
dist = math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return dist
print calculateDistance(x1, y1, x2, y2)
This code will give the distance between the two corners

How can I retrieve the center of a rotated ellipse from a pyqtgraph EllipseROI?

I would like to find the center of an ellipse created using the pyqtgraph EllipseROI, generated using the following command:
import pyqtgraph as pg
...
ellipse = pg.EllipseROI(pos = [200, 200], size=125, pen='r', movable=True)
Once a user moves and rotates the ellipse, some information can be retrieved from the handles to determine the new position:
x0=ellipse.pos().x()
y0=ellipse.pos().y()
width=ellipse.size()[0]
height=ellipse.size()[1]
angle=ellipse.angle()
It looks like the width and height values correspond to the rectangular region surrounding the non-rotated ellipse:
In the above images, the black pixels represent the following points (from lower left, proceeding counter-clockwise):
[x0, y0]
[x0+width, y0]
[x0+width, y0+height]
[x0, y0+height]
The center for the ellipse with angle=0 is simply [x0+width/2, y0+height/2]. For a rotated ellipse, I can find the center by rotating the center of the ellipse corresponding to angle=0 about the point [x0, y0]:
import numpy as np
...
center_x0 = x0+width/2.0
center_y0 = y0+height/2.0
center_x = x0 + np.cos(np.radians(angle)) * (center_x0 - x0) - np.sin(np.radians(angle)) * (x0 - center_x0)
center_y = y0 + np.sin(np.radians(angle)) * (center_x0 - x0) + np.cos(np.radians(angle)) * (center_y - y0)
Am I missing a simple solution to quickly retrieve the center position from the EllipseROI (or any pyqtgraph ROI) that does not first require retrieving the bounding box points of an non-rotated ROI and then rotating their center? I would like to minimize the delay between when the user adjusts the ROI and subsequent displays in the viewbox (that rely on the center point), and the method I am currently using seems like an inefficient way to calculate the center.

Drawing Arcs in Pyside

I'm trying to do draw widgets on the circle, for this I need to paint the widgets as a arc. I know the number of widgets (let's say), then each widget is at 36 degrees from the origin to the circumference. The information I have is the center of circle, radius and I know the starting and end point on the circumference for each such widget.
This is computed by doing
dx = int(round(400 + 300 * np.cos(angle)))
dy = int(round(400 + 300 * np.sin(angle)))
where angle = 2 * np.pi / 15
I go over a for loop computing the new value for angle which is basically angle * i where i = (1, 10)
I don't understand the start angle and span angle for the arcs function in QPainter.QPainter Arc. I googled and not many terms came up. Maybe there is a different term for them.
So the problem is I have a starting point and ending point on the circumference and center and radius, how do I use them to draw Arcs such that I get something that looks like :
circos
What I have tried is, I can compute the center point (cx) of the two end points, if I draw a line from the center of the circle to this point cx, then I can compute how far this point circumference which essentially is my width, but how to get the orientation correct to represent them as circles.
Instead of circular I do have a layout with just lines like this, but would like to be like the circos one.
My image
I don't understand the start angle and span angle for the arcs function in QPainter.QPainter Arc.
Why? The documentation is IMHO very clear:
The startAngle and spanAngle must be specified in 1/16th of a degree, i.e. a full circle equals 5760 (16 * 360).
This means that your units are 1/16º. E.g. 45º is 45*16 units.
Positive values for the angles mean counter-clockwise while negative values mean the clockwise direction. Zero degrees is at the 3 o’clock position."
This means that 90*16 points at 12 o'clock (goes 90º counter-clockwise from 3 o'clock), and -90*16 points at 6 o'clock.
Of course the "zero" degrees only has sense for the start angle. The span angle states how much further does the arc go, and in which direction.
For example, to draw an arc from 3 o'clock to 12 o'clock, you'd do
painter.drawArc(rect, 0, 90*16)
*or*
painter.drawArc(rect, 90*16, -90*16)
But to draw an arc from 3 o'clock to 6 o'clock, you'd do
painter.drawArc(rect, 0, -90*16)
*or*
painter.drawArc(rect, -90*16, 90*16)
The arcs are not specified using center and radius, but rather using a bounding rectangle. If the arc was a full ellipse, it would be inscribed in the rectangle - the arcs are implicitly elliptical arcs.
So, given x and y centerpoint, and r for circular radius, the bounding rectangle is
rect = QRect(x-r, y-r, 2*r, 2*r)

Categories