python arrange images on canvas in a circle - python

I have bunch of images (say 10) I have generated both as array or PIL object.
I need to integrate them into a circular fashion to display them and it should adjust itself to the resolution of the screen, is there anything in python that can do this?
I have tried using paste, but figuring out the resolution canvas and positions to paste is painful, wondering if there is an easier solution?

We can say that points are arranged evenly in a circle when there is a constant angle theta between neighboring points. theta can be calculated as 2*pi radians divided by the number of points. The first point is at angle 0 with respect to the x axis, the second point at angle theta*1, the third point at angle theta*2, etc.
Using simple trigonometry, you can find the X and Y coordinates of any point that lies on the edge of a circle. For a point at angle ohm lying on a circle with radius r:
xFromCenter = r*cos(ohm)
yFromCenter = r*sin(ohm)
Using this math, it is possible to arrange your images evenly on a circle:
import math
from PIL import Image
def arrangeImagesInCircle(masterImage, imagesToArrange):
imgWidth, imgHeight = masterImage.size
#we want the circle to be as large as possible.
#but the circle shouldn't extend all the way to the edge of the image.
#If we do that, then when we paste images onto the circle, those images will partially fall over the edge.
#so we reduce the diameter of the circle by the width/height of the widest/tallest image.
diameter = min(
imgWidth - max(img.size[0] for img in imagesToArrange),
imgHeight - max(img.size[1] for img in imagesToArrange)
)
radius = diameter / 2
circleCenterX = imgWidth / 2
circleCenterY = imgHeight / 2
theta = 2*math.pi / len(imagesToArrange)
for i, curImg in enumerate(imagesToArrange):
angle = i * theta
dx = int(radius * math.cos(angle))
dy = int(radius * math.sin(angle))
#dx and dy give the coordinates of where the center of our images would go.
#so we must subtract half the height/width of the image to find where their top-left corners should be.
pos = (
circleCenterX + dx - curImg.size[0]/2,
circleCenterY + dy - curImg.size[1]/2
)
masterImage.paste(curImg, pos)
img = Image.new("RGB", (500,500), (255,255,255))
#red.png, blue.png, green.png are simple 50x50 pngs of solid color
imageFilenames = ["red.png", "blue.png", "green.png"] * 5
images = [Image.open(filename) for filename in imageFilenames]
arrangeImagesInCircle(img, images)
img.save("output.png")
Result:

Related

Get points of any regular polygon that can be also rotated

I'm trying to draw any regular polygon, so from triangles to polygons with so many corners, it looks like a circle. to make it easier, they must be regular, so a normal pentagon/hexagon/octagon etc. I want to be able to rotate them. What ive tried is to draw a circle and divide 360 by the amount of points i want then, create a point every nth degrees around the circle, putting these points in pygame.draw.polygon() then creates the shape i want, the problem is it isn't the right size, i also want to be able to have stretch the shapes, so have a different width and height.
def regular_polygon(hapi, x, y, w, h, n, rotation, angle_offset = 0):
#angle_offset is the starting angle in the circle where rotation is rotating the circle
#so when its an oval, rotation rotates the oval and angle_offset is where on the oval to start from
if n < 3:
n = 3
midpoint = pygame.Vector2(x + w//2, y + h//2)
r = sqrt(w**2 + h**2)
#if angle_offset != 0:
#w = (w//2)//cos(angle_offset)
#if angle_offset != 90:
#h = (h//2)//sin(angle_offset)
w,h = r,r
points = []
for angle in range(0, 360, 360//n):
angle = radians(angle + angle_offset)
d = pygame.Vector2(-sin(angle)*w//2, -cos(angle)*h//2).rotate(rotation) #the negative sign is because it was drawing upside down
points.append(midpoint + d)
#draws the circle for debugging
for angle in range(0, 360, 1):
angle = radians(angle + angle_offset)
d = pygame.Vector2(-sin(angle)*w//2, -cos(angle)*h//2).rotate(rotation)
pygame.draw.rect(screen, (0,255,0), (midpoint[0] + d[0], midpoint[1] + d[1], 5, 5))
pygame.draw.polygon(screen,(255,0,0),points)
the red square is the the function above is making, the blue one behind is what it should be
as you can see the circle does line up with the edges of the rect, but because the angles of the circle are not even, the rectangle the func makes is not right.
i think i need to change the circle to an oval but cannot find out how to find the width radius and height radius of it. currently ive found the radius by using pythag.
this is what happens when i dont change the width or height
I found the solution
doing w *= math.sqrt(2) and h *= math.sqrt(2) works perfectly. dont quite understand the math, but after trial and error, this works. You can probable find the maths here but i just multiplied the width and height by a number and printed that number when it lined up which was very close to the sqrt(2)

How to increase elliptical arc resolution for high radius values in opencv?

I've been trying to draw an elliptical arc in openCV using the ellipse function (https://docs.opencv.org/3.0-beta/modules/imgproc/doc/drawing_functions.html), however, for high radius values the arcs seem segmented.
Do you know how can I sort of increase the arc's resolution to appear better for high radius values?
I tried to draw an arc with a small radius and it looked smooth and I also tried increasing image resolution but no difference was noticed.
My code is as follows:
A[0] = round(A[0]*dpm - Xmin + margin) #Normalize CenterX
A[1] = round(A[1]*dpm - Ymin + margin) #Normalize CenterY
A[2] = round(A[2]*dpm) #Normalize Radius
startAng = A[3]
endAng = A[4]
A=A.astype(int)
cv2.ellipse(Blank,(A[0],A[1]),(A[2],A[2]), 0, startAng, endAng, 0 ,1)
while:
Blank is the image I want to draw the arc on (np array, size= (398, 847)
(A[0],A[1]) is the center point
(A[2],A[2]) ellipse axes
0 is the angle
startAng is the starting angle of the arc
endAng is the ending angle of the arc
0 is the line color (Black)
1 is the line thickess
The code should produce a smooth arc but it looks segmented as if it is made of 4 lines.
I ended up writing a function to plot an arc on an input image:
import numpy as np
import cv2
blank = np.ones((500,500))
def DrawArc(image, center, radius, startAng, endAng, color,resolution):
'''Draws an arc with specific reslution and color on an input image
Args:
image - The input image to draw the arc on
center - Arc's center
radius - Arc's radius
startAng - the starting angle of the arc
engAng - the ending angle of the arc
color - Arc's color on the input image
resolution - Number of points for calculation
output:
image - updated image with plotted arc'''
startAng += 90
endAng += 90
theta = np.linspace(startAng,endAng,resolution)
x = np.round(radius*np.cos(np.deg2rad(theta))) + center[0]
y = np.round(radius*np.sin(np.deg2rad(theta))) + center[1]
x=x.astype(int)
y=y.astype(int)
for k in range(np.size(theta)):
image[x[k]][y[k]] = color
return image
image = DrawArc(blank,(250,250),200,0,90,0,1000)
cv2.imshow("Arc",image)
cv2.waitKey()
The output image is
Output

Crop area from image using Pillow in Python

I want to crop a rectangle shape area from an image using Pillow in python. The problem is that the rectangle is not necessary parallel with the image margins so I cannot use the .crop((left, top, right, bottom)) function.
Is there a way to achieve this with Pillow? (assuming we know the coordinates of all 4 points of rectangle)
If not, how it can be done using a different Python library?
You can use min rotated rectangle in OpenCV:
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
As a result You have: center coordinates (x,y), width, height, angle of rotation of rectangle. You can rotate whole image with angle from this rectangle. You image now will be rotated:
You can calculate new coordinates of four rectangle vertices (you got angle). Then just calculate normal rectangle for this points (normal rectangle = not minimal, without any rotation). With this rect You can crop Your rotated image. In this crop image will be what You want if I understand You correctly. Something like that:
So You only need Opencv. Maybe there is some library with which You can do it easier.
Here's a solution based on scikit-image (not Pillow) that you might find useful.
You could pass the vertices of the region you wish to crop to the function skimage.draw.polygon and then use the retrieved pixel coordinates to mask the original image (for example, through the alpha channel).
import numpy as np
from skimage import io, draw
img = io.imread('https://i.stack.imgur.com/x5Ym4.png')
vertices = np.asarray([[150, 140],
[300, 240],
[210, 420],
[90, 320],
[150, 150]])
rows, cols = draw.polygon(vertices[:, 0], vertices[:, 1])
crop = img.copy()
crop[:, :, -1] = 0
crop[rows, cols, -1] = 255
io.imshow(crop)
I adapted this opencv-based solution (sub_image) for use with PIL. It takes a (center, size, theta) rect which I'm getting from cv2.minAreaRect, but could be constructed mathmatically from points, etc.
I've seen a few other solutions but they left some weird artifacts.
def crop_tilted_rect(image, rect):
""" crop rect out of image, handing rotation
rect in this case is a tuple of ((center_x, center_y), (width, height), theta),
which I get from opencv's cv2.minAreaRect(contour)
"""
# Get center, size, and angle from rect
center, size, theta = rect
width, height = [int(d) for d in size]
if 45 < theta <= 90:
theta = theta - 90
width, height = height, width
theta *= math.pi / 180 # convert to rad
v_x = (math.cos(theta), math.sin(theta))
v_y = (-math.sin(theta), math.cos(theta))
s_x = center[0] - v_x[0] * (width / 2) - v_y[0] * (height / 2)
s_y = center[1] - v_x[1] * (width / 2) - v_y[1] * (height / 2)
mapping = np.array([v_x[0],v_y[0], s_x, v_x[1],v_y[1], s_y])
return image.transform((width, height), Image.AFFINE, data=mapping, resample=0, fill=1, fillcolor=(255,255,255))

Efficient way to find the pixel coordinates in image for circle circumference intersection

I would like to efficiently find the coordinates of the line described by the intersection between the circumference of a circle and an image (origin of circle is outside the image). Right now I'm using a loop in python to start at one edge of the image and move through the image a step at a time. Each step moves a certain distance (say 0.01 inches). I calculate the angle needed to move that distance and then use polar geometry formulas to define the next pixel coordinate. This all works just fine, however, it takes a long time. I'm creating many of these lines through the image as the radius of the circle increases.
Is there a way to use a built in function or an array based formula so that I don't have to have so many steps in my algorithm? Basically, what is the most efficient way to accomplish this in python 2?
Thanks,
rb3
# circle parameters
x0 = -5
y0 = -5
R = 25
# image size
max_x = 100
max_y = 100
# sample points
theta = np.linspace(0, 2*np.pi, 2048) # make bigger if you have huge images
# the pixels that get hit
xy = list(set([xy for xy in zip( (R * cos(theta) - x0).astype(int), (R * sin(theta) - y0).astype(int)) if
xy[0] >= 0 and xy[0] < max_x and xy[1] >= 0 and xy[1] < max_y]))

Creating an arc with a given thickness using PIL's Imagedraw

I am trying to create a segmented arc using PIL and Imagedraw. The arc function allows me to draw an arc easily, but it is just a line. I need to be able to place an arc of given radius and thickness(ID to OD), but AI cannot find any type of thickness or width setting. Is there a way to do this? If not, is there some other way to do this using PIL?
Snippet:
import Image
import ImageDraw
conv = 0.1
ID = 15
OD = 20
image = Image.new('1',(int(ceil(OD/conv))+2,int(ceil(OD/conv))+1), 1)
draw = ImageDraw.Draw(image)
diam = OD-ID
box=(1, 1, int(ceil(diam/conv)), int(ceil(diam/conv))) #create bounding box
draw.arc(box, 0, 90, 0) #draw circle in black
I created the following arc replacement function based on Mark's suggestion:
https://gist.github.com/skion/9259926
Probably not pixel perfect (nor fast), but seems to come close for what I need it for. If you have a better version please comment in the Gist.
def arc(draw, bbox, start, end, fill, width=1, segments=100):
"""
Hack that looks similar to PIL's draw.arc(), but can specify a line width.
"""
# radians
start *= math.pi / 180
end *= math.pi / 180
# angle step
da = (end - start) / segments
# shift end points with half a segment angle
start -= da / 2
end -= da / 2
# ellips radii
rx = (bbox[2] - bbox[0]) / 2
ry = (bbox[3] - bbox[1]) / 2
# box centre
cx = bbox[0] + rx
cy = bbox[1] + ry
# segment length
l = (rx+ry) * da / 2.0
for i in range(segments):
# angle centre
a = start + (i+0.5) * da
# x,y centre
x = cx + math.cos(a) * rx
y = cy + math.sin(a) * ry
# derivatives
dx = -math.sin(a) * rx / (rx+ry)
dy = math.cos(a) * ry / (rx+ry)
draw.line([(x-dx*l,y-dy*l), (x+dx*l, y+dy*l)], fill=fill, width=width)
PIL can't draw wide arcs, but Aggdraw can, and works well with PIL (same author).
Simulate the arc using straight line segments and put the coordinates of those segments into a list. Use draw.line with a width option to draw the arc.
A trick I found that can be pulled is to make a white circle inside the black circle. You can use the pieslice method to break it up as needed. The rendering is sequential, so you just have to get the ordering correct. The hard part is getting the positioning correct, due to Imagedraw's use of bounding boxes as opposed to center and radius coordinates. You have to make sure that the centers of everything end up exactly on each other.
THIS SOLUTION IS GOOD ONLY IN A LIMITED CASE, see comment.

Categories

Resources