How would I resize a polygon to fit in the canvas? Python Tkinter - python

I'm currently coding a congruent triangle drawer, it looks like this: GUI Of Drawer
It basically works this way: You input the length of the base that is the side BC and then you input the degrees of the point ⋪B and ⋪C, and then it calculates the interception between two lines that are made from the angles( A point to draw ∆ABC ), I visually drew them so you can understand it better here,
b = (b_x, b_y)
b_a = (b_x + math.cos(to_radian(-b_angle)) * 400, b_y + math.sin(to_radian(-b_angle)) * 400)
c = (c_x, c_y)
c_a = (c_x - math.cos(to_radian(c_angle)) * 400, c_y - math.sin(to_radian(c_angle)) * 400)
Now the problem is that when you input long degrees, the interception of the two lines (A point coordinates) the A point is made out of the canvas so you can't actually see the whole triangle(as you can see in the first image), so I'm here for a solution to resize the triangle(or polygon) and make it fit in the canvas, I already thought about checking if the A coordinates are greater than the canvas size, but since I'm not familiar with tkinter and python, I don't know how to resize the triangle and make it fit.

You can use scale() to resize the polygon.
Assume canv is the instance of Canvas and the triangle is the id of the polygon object:
x1, y1, x2, y2 = canv.bbox(triangle) # get the bounding box of the triangle
w, h = x2-x1+10, y2-y1+10 # make the w and h a bit larger
canv_w, canv_h = canv.winfo_width(), canv.winfo_height() # get canvas width and height
r = min(canv_w/w, canv_h/h) # determine the scale ratio
canv.scale(triangle, 0, 0, r, r) # resize triangle
canv.config(scrollregion=canv.bbox(triangle)) # adjust the view window

I did this, but it doesn't draw the triangle and I can't see any changes :
triangle = self.canvas.create_polygon(coords, fill="light blue", outline="black")
canvas_w, canvas_h = self.canvas.winfo_width(), self.canvas.winfo_height()
if a[0] > canvas_w or a[1] > canvas_h:
x1, y1, x2, y2 = self.canvas.bbox(triangle)
w, h = x2 - x1 + 10, y2 - y1 + 10
scale_r = min(canvas_w / w, canvas_h / h)
self.canvas.scale(triangle, 0, 0, scale_r, scale_r)
self.canvas.config(scrollregion=self.canvas.bbox(triangle))
Note: "a" is equal to the tuple of coordinates of the point "A"
a = (a_x, a_y)

Related

How to enlarge and reduce an already drawn lines, using certain buttons?

Suppose I have the following program made with Tkinter in Python, in which you can click to draw any shape.
import tkinter
canvas = tkinter.Canvas(width=500, height=500, bg='white')
canvas.pack()
line = ()
def draw():
canvas.delete('all')
if len(line) >= 4:
canvas.create_line(line, fill='red', width=2)
def newline(coordinates):
global line
line = line + (coordinates.x, coordinates.y)
draw()
canvas.bind('<Button-1>', newline)
I have tried this for smaller image, but it didn't work.
def reduced():
line_reduced = ()
for i in newline:
line_reduced += (i/2,)
canvas.delete('all')
canvas.create_line(line_reduced, fill='red', width=4, tags='foto1')
I would need to add it so that this shape can then be reduced or enlarged using two keys. The image would therefore remain the same (shape), it would only be reduced / enlarged.
I will be grateful for any advice.
If you are trying to scale a bunch of lines on a canvas you could use scale, but then everything new that you add to the canvas would also have to be scaled or it starts getting weird. What you likely really need is math. Specifically this equation N = Scale * (N - Pn) + Pn. Below is a simple drawing interface with the buttons you requested. Primarily it's just a bunch of storing points and creating lines. The part of interest to you is this line:
line[n] = scale * (point - center) + center.
Lines are stored as [x1, y1, x2, y2] in my example code (the beginning and end of a line). line would refer to such a list. line[n] refers to each x and y value in that list as n is incremented. That being said:
line[n] = scale * (point - center) + center is really saying to subtract the center from the point, multiply it by the scale number, and then add the center back in so the scaled point stays it's scaled distance from the center.
Let's run through this one time so you definitely understand.
Chalkboard:
x1 = 100
center = 200
scale = 2
formula: x1 = scale * (x1 - center) + center
1 : x1 = 2 * (100 - 200) + 200
2 : x1 = 2 * (-100) + 200
3 : x1 = -200 + 200
4 : x1 = 0
The old x1 was 100 away from the center
now x1 is 200 away from the center
x1 is scale times further from the center than it was before
Code:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg='white')
canvas.pack()
#for storing line canvas ids and line coordinates
line_ids, lines = [], []
#line properties ~ width and fill
global w, f
w, f = 2, 'red'
#draw a line
def draw(event):
if event.type is tk.EventType.ButtonPress:
#store line start coordinate
lines.append([event.x, event.y])
#create a dummy line
line_ids.append(canvas.create_line(*lines[-1], *lines[-1], fill=f, width=w))
elif event.type is tk.EventType.Motion:
#keep deleting and redrawing the current line til you release the mouse
canvas.delete(line_ids.pop())
line_ids.append(canvas.create_line(*lines[-1], event.x, event.y, fill=f, width=w))
elif event.type is tk.EventType.ButtonRelease:
#append the end coordinate to the last line
lines[-1] += [event.x, event.y]
#add mouse events to canvas for drawing functionality
for event in ['<B1-Motion>', '<Button-1>', '<ButtonRelease-1>']:
canvas.bind(event, draw)
#scale lines
def scale_lines(scale):
#remove all canvas references to lines
canvas.delete("all")
line_ids = []
#traverse every line
for i, line in enumerate(lines):
#traverse every point in THIS line
for n, point in enumerate(line):
#toggle between center x and center y depending if point is an x or y
center = canvas.winfo_width()/2 if not n%2 else canvas.winfo_height()/2
#scale this point
line[n] = scale * (point - center) + center
#create a new line with scaled points
line_ids.append(canvas.create_line(*line, fill=f, width=w))
#increase/decrease buttons
tk.Button(root, command=lambda: scale_lines(.5), text='decrease').pack(side='left')
tk.Button(root, command=lambda: scale_lines(2), text='increase').pack(side='left')
root.mainloop()

Why add and subtract 1000 for hough line transformation?

Here is some code from a tutorial. Why do we add 1000 and subtract 1000, and why is b negative?
import cv2
import numpy as np
img = cv2.imread('sudoku.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLines(edges, 1, np.pi /180, 200)
for line in lines:
rho, theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
In Python/OpenCV HoughLines, it is to define the endpoints of the lines so that they go to (and past) the sides of the image when drawing the lines. The hough transform you have used only returns the angle and distance from the original for the lines. So the extra computation finds the intersection of a line from the origin perpendicular to the line, so that it can identify some point on the line. But it does not know how long the line should be. So it extends the line by this much from that point along the line. Since it knows the angle of the line and a point on the line, it just provides a distance for the two end points from the given point on the line. If your image is larger than about 2*1000 pixels in dimension, then you may need to increase the 1000 value if you want the lines to go to the sides of the image.
The minus sign (-b) occurs as follows:
The direction from the origin in a direction perpendicular to the line is given by its slope b/a = sin(theta)/cos(theta)=tan(theta). See the diagram at https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghlines/py_houghlines.html. But the direction of the line itself is 90 deg from that and its angle is given by -1/tan(theta) = -cos(theta)/sin(theta) = -a/b or a/-b. That is its slope is (a/-b)=(y-yo)/(x-xo)=delY/delX. See https://byjus.com/maths/slope-of-line/. So to get the two end points you can start at any point as given by xo, yo and go along the line in either direction, so that the end point X component is xo +- 1000delX = xo +- 1000cos(perp_angle) = xo +- 1000*(-b) and the end point Y component is yo +- 1000delY = yo +- 1000sin(perp_angle) = yo +- 1000*a. Where perp_angle is the direction along the actual line.

How to map rectangle image to quadrilateral with PIL?

Python PIL library allows me to map any quadrilateral in an image to rectangle using
im.transform(size, QUAD, data)
What I need is a function that does the opposite, i.e. map a rectangular image to specified quadrilateral.
I figured this might be achieved with the above mentioned function like this:
I.e. I would find such quad (the red one in the image) that would, using the function im.transform(size, QUAD, data) transform the image to quad I want. The problem is I don't know how to find the red quad.
I would appreciate any idea on how to find the red quad or any other way to map a rect image to quad, only with PIL if possible.
So I solved the issue with a simple forward mapping, rather than inverse mapping, which is usually better, but in my application I only ever map the rectangle to a quad that is smaller than the rectangle, so there are usually no holes in the transformed image. The code is as follows:
def reverse_quad_transform(image, quad_to_map_to, alpha):
# forward mapping, for simplicity
result = Image.new("RGBA",image.size)
result_pixels = result.load()
width, height = result.size
for y in range(height):
for x in range(width):
result_pixels[x,y] = (0,0,0,0)
p1 = (quad_to_map_to[0],quad_to_map_to[1])
p2 = (quad_to_map_to[2],quad_to_map_to[3])
p3 = (quad_to_map_to[4],quad_to_map_to[5])
p4 = (quad_to_map_to[6],quad_to_map_to[7])
p1_p2_vec = (p2[0] - p1[0],p2[1] - p1[1])
p4_p3_vec = (p3[0] - p4[0],p3[1] - p4[1])
for y in range(height):
for x in range(width):
pixel = image.getpixel((x,y))
y_percentage = y / float(height)
x_percentage = x / float(width)
# interpolate vertically
pa = (p1[0] + p1_p2_vec[0] * y_percentage, p1[1] + p1_p2_vec[1] * y_percentage)
pb = (p4[0] + p4_p3_vec[0] * y_percentage, p4[1] + p4_p3_vec[1] * y_percentage)
pa_to_pb_vec = (pb[0] - pa[0],pb[1] - pa[1])
# interpolate horizontally
p = (pa[0] + pa_to_pb_vec[0] * x_percentage, pa[1] + pa_to_pb_vec[1] * x_percentage)
try:
result_pixels[p[0],p[1]] = (pixel[0],pixel[1],pixel[2],min(int(alpha * 255),pixel[3]))
except Exception:
pass
return result

Stop pyplot.contour from drawing a contour along a discontinuity

I have a 2d map of a coordinate transform. The data at each point is the aximuthal angle in the original coordinate system, which goes from 0 to 360. I'm trying to use pyplot.contour to plot lines of constant angle, e.g. 45 degrees. The contour appears along the 45 degree line between the two poles, but there's an additional part to the contour that connects the two poles along the 0/360 discontinuity. This makes a very jagged ugly line as it basically just traces the pixels with a number close to 0 on one side and another close to 360 on the other.
Examples:
Here is an image using full colour map:
You can see the discontinuity along the blue/red curve on the left side. One side is 360 degrees, the other is 0 degrees. When plotting contours, I get:
Note that all contours connect the two poles, but even though I have NOT plotted the 0 degree contour, all the other contours follow along the 0 degree discontinuity (because pyplot thinks if it's 0 on one side and 360 on the other, there must be all other angles in between).
Code to produce this data:
import numpy as np
import matplotlib.pyplot as plt
jgal = np.array(
[
[-0.054875539726, -0.873437108010, -0.483834985808],
[0.494109453312, -0.444829589425, 0.746982251810],
[-0.867666135858, -0.198076386122, 0.455983795705],
]
)
def s2v3(rra, rdec, r):
pos0 = r * np.cos(rra) * np.cos(rdec)
pos1 = r * np.sin(rra) * np.cos(rdec)
pos2 = r * np.sin(rdec)
return np.array([pos0, pos1, pos2])
def v2s3(pos):
x = pos[0]
y = pos[1]
z = pos[2]
if np.isscalar(x):
x, y, z = np.array([x]), np.array([y]), np.array([z])
rra = np.arctan2(y, x)
low = np.where(rra < 0.0)
high = np.where(rra > 2.0 * np.pi)
if len(low[0]):
rra[low] = rra[low] + (2.0 * np.pi)
if len(high[0]):
rra[high] = rra[high] - (2.0 * np.pi)
rxy = np.sqrt(x ** 2 + y ** 2)
rdec = np.arctan2(z, rxy)
r = np.sqrt(x ** 2 + y ** 2 + z ** 2)
if x.size == 1:
rra = rra[0]
rdec = rdec[0]
r = r[0]
return rra, rdec, r
def gal2fk5(gl, gb):
rgl = np.deg2rad(gl)
rgb = np.deg2rad(gb)
r = 1.0
pos = s2v3(rgl, rgb, r)
pos1 = np.dot(pos.transpose(), jgal).transpose()
rra, rdec, r = v2s3(pos1)
dra = np.rad2deg(rra)
ddec = np.rad2deg(rdec)
return dra, ddec
def make_coords(resolution=50):
width = 9
height = 6
px = width * resolution
py = height * resolution
coords = np.zeros((px, py, 4))
for ix in range(0, px):
for iy in range(0, py):
l = 360.0 / px * ix - 180.0
b = 180.0 / py * iy - 90.0
dra, ddec = gal2fk5(l, b)
coords[ix, iy, 0] = dra
coords[ix, iy, 1] = ddec
coords[ix, iy, 2] = l
coords[ix, iy, 3] = b
return coords
coords = make_coords()
# now do one of these
# plt.imshow(coords[:,:,0],origin='lower') # color plot
plt.contour(
coords[:, :, 0], levels=[45, 90, 135, 180, 225, 270, 315]
) # contour plot with jagged ugliness
plt.show()
How can I either:
stop pyplot.contour from drawing a contour along the discontinuity
make pyplot.contour recognize that the 0/360 discontinuity in angle is not a real discontinuity at all.
I can just increase the resolution of the underlying data, but before I get a nice smooth line it starts to take a very long time and a lot of memory to plot.
I will also want to plot a contour along 0 degrees, but if I can figure out how to hide the discontinuity I can just shift it to somewhere else not near a contour. Or, if I can make #2 happen, it won't be an issue.
This is definitely still a hack, but you can get nice smooth contours with a two-fold approach:
Plot contours of the absolute value of the phase (going from -180˚ to 180˚) so that there is no discontinuity.
Plot two sets of contours in a finite region so that numerical defects close to the tops and bottoms of the extrema do not creep in.
Here is the complete code to append to your example:
Z = np.exp(1j*np.pi*coords[:,:,0]/180.0)
Z *= np.exp(0.25j*np.pi/2.0) # Shift to get same contours as in your example
X = np.arange(300)
Y = np.arange(450)
N = 2
levels = 90*(0.5 + (np.arange(N) + 0.5)/N)
c1 = plt.contour(X, Y, abs(np.angle(Z)*180/np.pi), levels=levels)
c2 = plt.contour(X, Y, abs(np.angle(Z*np.exp(0.5j*np.pi))*180/np.pi), levels=levels)
One can generalize this code to get smooth contours for any "periodic" function. What is left to be done is to generate a new set of contours with the correct values so that colormaps apply correctly, labels will be applied correctly etc. However, there does not seem to be a simple way of doing this with matplotlib: the relevant QuadContourSet class does everything and I do not see a simple way of constructing an appropriate contour object from the contours c1 and c2.
I was interested in the exact same problem. One solution is to NaN out the contours along the branch cut; see here; another is to use the max_jump argument in matplotx's contour().
I molded the solution into a Python package, cplot.
import cplot
import numpy as np
def f(z):
return np.exp(1 / z)
cplot.show(f, (-1.0, +1.0, 400), (-1.0, +1.0, 400))

Distance between two objects

So I've been trying to figure out how to find the distance between two objects on a canvas and I've exhausted most relevant links on Google with little success.
I'm trying to make it so that it calculates the distance between the drawn ovals and the line on the canvas.
from __future__ import division
from Tkinter import *
import tkMessageBox
class MyApp(object):
def __init__(self):
self.root = Tk()
self.root.wm_title("Escape")
self.canvas = Canvas(self.root, width=800, height=800, bg='white')
self.canvas.pack()
self.canvas.create_line(100, 100, 200, 200, fill='black')
self.canvas.bind("<B1-Motion>", self.tracer)
self.root.mainloop()
def tracer(self, e):
self.canvas.create_oval(e.x-5, e.y-5, e.x+5, e.y+5, fill='blue', outline='blue')
rx = "%d" % (e.x)
ry = "%d" % (e.y)
print rx, ry
MyApp()
Two circles:
dist = math.sqrt((circle1.x-circle2.x)**2 + (circle1.y-circle2.y)**2) - circle1.r - circle2.r
Kind of obvious, their Euclid distance is calculated using the Pythagorean theorem.
Point/segment:
a = segment[1].y - segment[0].y
b = segment[0].x - segment[1].x
c = - segment[0].x * a - segment[0].y * b
dx = segment[1].x - segment[0].x
dy = segment[1].y - segment[0].y
nc0 = - segment[0].x * dx - segment[0].y * dy
nc1 = - segment[1].x * dx - segment[1].y * dy
if ((dx * x + dy * y + nc0) < 0) dist = math.sqrt((x-segment[0].x)**2 + (y-segment[0].y)**2)
elif((dx * x + dy * y + nc1) < 0) dist = math.sqrt((x-segment[1].x)**2 + (y-segment[1].y)**2)
else dist = (a*x + b*y + c) / math.sqrt(a**2 + b**2)
Circle/segment - same as point/segment, just substract circle's radius
Polygon/polygon - Loop through each vertex of polygon 1 and segment of polygon 2, then loop through each vertex of polygon 2 and segment of polygon 1, then find the smallest.
Don't use magic numbers inside your code. That radius of 5 isn't good.
dist = math.sqrt((oval.x-point.x)**2 + (oval.y-point.y)**2)
not really sure if this answers your question but this is the distance formula
there are several problems with the original question.
at what point are you trying to find the distance?
you are not saving the oval positions so it will be very hard to look up later
why are you trying to get the distance?/what are you planning on doing with it?
are you trying to find distance to edge or distance to center?
in general I would guess you want to get it in the def trace function
step 1. Point of interest is (e.x,e.y)
step 2. Find the closest point on the line (line = (100,100) to (200,200) ) (this is probably a question in its own right. http://nic-gamedev.blogspot.com/2011/11/using-vector-mathematics-and-bit-of_08.html )
step 3. Apply distance method to point of interest and closest point on line
def dist(pt1,pt2):
return ((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)**.5
on another side note ... your ovals look an awful lot like circles ...

Categories