Why rotation of a figure breaks if rotating in place? - python

I'm writing a game that needs rotation of an polygon. I have an array of points. I have a rotation around (0,0) origin function:
x, y = xy
xx = x * math.cos(radians) + y * math.sin(radians)
yy = -x * math.sin(radians) + y * math.cos(radians)
return xx, yy
I tried rotating in place like this:
Figure = np.array(((0,8), (8,-8), (0,0), (-8,-8)))
for i in range(7):
for i, point in enumerate(Figure):
Figure[i] = rotate_origin_only(point, math.radians(33))
plt.fill(*list(zip(*Figure)))
plt.show()
But I get something broken
However, if I use a temporal list, like this:
Figure = np.array(((0,8), (8,-8), (0,0), (-8,-8)))
for i in range(80):
temp = []
for point in Figure:
temp.append(rotate_origin_only(point, math.radians(33 * i)))
plt.fill(*list(zip(*temp)))
plt.show()
Everything works as expected.
Why?

It seems like you miss a * i in the first algorithm.
In the first algorithm:
Figure[i] = rotate_origin_only(point, math.radians(33))
In the second:
temp.append(rotate_origin_only(point, math.radians(33 * i)))
Also, you are using i twice in the nested for-loop. This is a bad coding practice and can cause confusion. Commonly the second loop variable is called j, like so:
for i in range(7):
for j, point in enumerate(Figure):

It seems that rotating function or floats has too low accuracy to rotate already rotated form many times. I'll just rewrite my code to always rotate from base form.

Related

plotting n number of equal points in circular direction in python

I am working on the task in which I have to make a circle which is having n number of equal parts. I am provided with centre and radius of the circle which is (0,0) and 4 respectively. To achieve this task, I have written below code,
parts = 36 # desire number of parts of the circle
theta_zero = 360/parts
R = 4 # Radius
x_p = []
y_p = []
n = 0
for n in range(0,36):
x_p.append(R * math.cos(n*theta_zero))
y_p.append(R * math.sin(n*theta_zero))
However, after running this code, I got output like below which does not seem a coorect which I am suppose to have.
Kindly let me know what I am doing wrong and give some suggestion of correct code. Thank you
Aside from the fact that you are generating numbers in degrees and passing them to a function that expects radians, there's a much simpler and less error-prone method for generating evenly-spaced coordinates:
t0 = np.linspace(0, 2 * np.pi, parts, endpoint=False)
x0 = R * np.cos(t0)
y0 = R * np.sin(t0)
endpoint=False ensures that you end up with 36 partitions rather than 35, since otherwise 0 and 2 * np.pi would overlap.
If you wanted to connect the dots for your circle, you would want the overlap. In that case, you would do
t1 = np.linspace(0, 2 * np.pi, parts + 1)
x1 = R * np.cos(t1)
y1 = R * np.sin(t1)
Here is how you would plot a circle with the 36 sectors delineated:
plt.plot(x1, y1)
plt.plot(np.stack((np.zeros(parts), x0), 0),
np.stack((np.zeros(parts), y0), 0))
Finally, if you want your circle to look like a circle, you may want to run plt.axis('equal') after plotting.
Your circle is weird because math.cos and math.sin accept radians while you are passing degrees. You just need to convert the degrees to radians when calling the math functions.
Like this:
parts = 36
theta_zero = 360/parts
R = 4
x_p = []
y_p = []
for n in range(0,36):
x_p.append(R * math.cos(n*theta_zero /180*math.pi))
y_p.append(R * math.sin(n*theta_zero /180*math.pi))
Result:
Alternatively changing theta_zero to 2*math.pi/parts would also work and would be slightly faster but it might be a little less intuitive to work with.
Also as #Mad Physicist mentioned you should probably add plt.axis('equal') to unstretch the image.

Find closest point in 2D mashed array

To give y'all some context, I'm doing this inversion technique where I am trying to reproduce a profile using the integrated values. To do that I need to find the value within an array along a certain line(s). To exemplify my issue I have the following code:
fig, ax = plt.subplots(1, figsize = (10,10))
#Create the grid (different grid spacing):
X = np.arange(0,10.01,0.25)
Y = np.arange(0,10.01,1.00)
#Create the 2D array to be plotted
Z = []
for i in range(np.size(X)):
Zaux = []
for j in range(np.size(Y)):
Zaux.append(i*j + j)
ax.scatter(X[i],Y[j], color = 'red', s = 0.25)
Z.append(Zaux)
#Mesh the 1D grids:
Ymesh, Xmesh = np.meshgrid(Y, X)
#Plot the color plot:
ax.pcolor(Y,X, Z, cmap='viridis', vmin=np.nanmin(Z), vmax=np.nanmax(Z))
#Plot the points in the grid of the color plot:
for i in range(np.size(X)):
for j in range(np.size(Y)):
ax.scatter(Y[j],X[i], color = 'red', s = 3)
#Create a set of lines:
for i in np.linspace(0,2,5):
X_line = np.linspace(0,10,256)
Y_line = i*X_line*3.1415-4
#Plot each line:
ax.plot(X_line,Y_line, color = 'blue')
ax.set_xlim(0,10)
ax.set_ylim(0,10)
plt.show()
That outputs this graph:
I need to find the closest points in Z that are being crossed by each of the lines. The idea is to integrate the values in Z that are crossed by the blue lines and plot that as a function of slope of the lines. Anyone has a good solution for it? I've tried a set of for loops, but I think it's kind of clunky.
Anyway, thanks for your time...
I am not sure about the closest points thing. That seems "clunky" too. What if it passes exactly in the middle between two points? Also I already had written code that weighs the four neighbor pixels by their closeness for an other project so I am going with that. Also I take the liberty of not rescaling the picture.
i,j = np.meshgrid(np.arange(41),np.arange(11))
Z = i*j + j
class Image_knn():
def fit(self, image):
self.image = image.astype('float')
def predict(self, x, y):
image = self.image
weights_x = [1-(x % 1), x % 1]
weights_y = [1-(y % 1), y % 1]
start_x = np.floor(x).astype('int')
start_y = np.floor(y).astype('int')
return sum([image[np.clip(np.floor(start_x + x), 0, image.shape[0]-1).astype('int'),
np.clip(np.floor(start_y + y), 0, image.shape[1]-1).astype('int')] * weights_x[x]*weights_y[y]
for x,y in itertools.product(range(2),range(2))])
And a little sanity check it returns the picture if we give it it's coordinates.
image_model = Image_knn()
image_model.fit(Z)
assert np.allclose(image_model.predict(*np.where(np.ones(Z.shape, dtype='bool'))).reshape((11,41)), Z)
I generate m=100 lines and scale the points on them so that they are evenly spaced. Here is a plot of every 10th of them.
n = 1000
m = 100
slopes = np.linspace(1e-10,10,m)
t, slope = np.meshgrid(np.linspace(0,1,n), slopes)
x_max, y_max = Z.shape[0]-1, Z.shape[1]-1
lines_x = t
lines_y = t*slope
scales = np.broadcast_to(np.stack([x_max/lines_x[:,-1], y_max/lines_y[:,-1]]).min(axis=0), (n,m)).T
lines_x *= scales
lines_y *= scales
And finally I can get the "points" consisting of slope and "integral" and draw it. You probably should take a closer look at the "integral" it's just a ruff guess of mine.
%%timeit
points = np.array([(slope, np.mean(image_model.predict(lines_x[i],lines_y[i]))
*np.linalg.norm(np.array((lines_x[i,-1],lines_y[i,-1]))))
for i,slope in enumerate(slopes)])
plt.scatter(points[:,0],points[:,1])
Notice the %%timeit in the last block. This takes ~38.3 ms on my machine and therefore wasn't optimized. As Donald Knuth puts it "premature optimization is the root of all evil". If you were to optimize this you would remove the for loop, shove all the coordinates for line points in the model at once by reshaping and reshaping back and then organize them with the slopes. But I saw no reason to put myself threw that for a few ms.
And finally we get a nice cusp as a reward. Notice that it makes sense that the maximum is at 4 since the diagonal is at a slope of 4 for our 40 by 10 picture. The intuition for the cusp is a bit harder to explain but I guess you probably have that already. For the length it comes down to the function (x,y) -> sqrt(x^2+y^2) having different directional differentials when going up and when going left on the rectangle.

Running a python script multiple times with different variables each iteration

I am currently simulation light passing through an optics system with python and Zemax. I have it set up currently where I define the x and y boundaries of the "sensor" to that i can choose the size of the area I want to simulate. I get 1 rectangle.
I'd like to simulate nine rectangles, in a 3x3 Grid. I am unsure which way would be the most elegant... my first Idea was to "hardcode" the different intervals into 9 different scripts and run those through a bash script, but it seemst a bit to "unelegant".
How do I have to define xmax, xmin, ymax, ymin now so that i can run the same simulation and get those nine retangles?
My thought was to maybe create some sort of list where the boundaries are defined, and then perhaps rerun the simulation with a different boundary each time and finally merging the images that appear.
The current code is quite long, but the parameters are all set in a main functions which looks like this:
if __name__ == '__main__':
DirNameZmx = r'C:\Some\Path'
FileNameZmx = r"Optics.zmx"
DirNameResults = r"C:\Some\Other\Path"
FileNameResults = r"Interferogram_Result"
(QueueFieldsOut, QueueToDetector, ProcessRaytracing, ProcessesPsfWorkers, ProcessDetector) = \
InitializeSimulation(DirNameZmx=DirNameZmx, FileNameZmx=FileNameZmx,
DirNameResults=DirNameResults, FileNameResults=FileNameResults,
FieldAngleHxMin=-0.02, FieldAngleHxMax=+0.02, dFieldAngleX=0.001,
FieldAngleHyMin=-0.06, FieldAngleHyMax=+0.06, dFieldAngleY=0.001,
NbrWavelength=1, Configurations=[1, 2], NbrRaysFieldRow=32, RAperture=0.99,
DetectorImageSize=11., DetectorPixelSize=0.011, ZeroPadding=8,
BatchRaysMax=512**2, NbrProcessWorkers=2)
print(ProcessRaytracing.join())
for Process in ProcessesPsfWorkers:
print(Process.join())
print(Process.name, Process.exitcode)
print(ProcessDetector.join())
data = np.load(os.path.join(DirNameResults, FileNameResults+'.npy'))
plt.imshow(data, cmap="coolwarm")
plt.show()
The FieldAngleHxMin/Max and FieldAngleHyMin/Max are the rectangle boundaries. The result looks like this:
Simple iteration will do the work.
Try this:
def nine_squares(FieldAngleHxMin, FieldAngleHxMax, FieldAngleHyMin, FieldAngleHyMax):
xstep = (FieldAngleHxMax - FieldAngleHxMin) / 3
ystep = (FieldAngleHyMax - FieldAngleHyMin) / 3
for i in range(3):
for j in range(3):
xstartpoint = xstep + i * FieldAngleHxMin
xendpoint = xstep + (i + 1) * FieldAngleHxMin
ystartpoint = ystep + i * FieldAngleHyMin
yendpoint = ystep + (i + 1) * FieldAngleHyMin
yield (xstartpoint, xendpoint, ystartpoint, yendpoint)
It will return lists of start and end coordinates every time it is called.

How do I construct a point from x,y, line and angle?

I am making a geometry interface in python (currently using tkinter) but I have stumbled upon a major problem: I need a function that is able to return a point, that is at a certain angle with a certain line segment, is a certain length apart from the vertex of the angle. We know the coordinates of the points of the line segment, and also the angle at which we want the point to be. I have attached an image below for a more graphical view of my question.
The problem: I can calculate it using trigonometry, where
x, y = vertex.getCoords()
endx = x + length * cos(radians(angle))
endy = y + length * sin(radians(angle))
p = Point(endx, endy)
The angle I input is in degrees. That calculation is true only when the line segment is parallel to the abscissa. But the sizes of the angles I get back are very strange, to say the least. I want the function to work wherever the first two points are on the tkinter canvas, whatever the angle is. I am very lost as to what I should do to fix it. What I found out: I get as output a point that when connected to the vertex, makes a line that is at the desired angle to the abscissa. So it works when the first arm(leg, shoulder) of the angle is parallel to the abscissa, then the function runs flawlessly (because of cross angles) - the Z formation. As soon as I make it not parallel, it becomes weird. This is because we are taking the y of the vertex, not where the foot of the perpendicular lands(C1 on the attached image). I am pretty good at math, so feel free to post some more technical solutions, I will understand them
EDIT: I just wanted to make a quick recap of my question: how should I construct a point that is at a certain angle from a line segment. I have already made functions that create the angle in respect to the X and Y axes, but I have no idea how i can make it in respect to the line inputted. Some code for the two functions:
def inRespectToXAxis(vertex, angle, length):
x, y = vertex.getCoords()
newx = x + length * cos(radians(angle))
newy = y + length * sin(radians(angle))
p = Point(abs(newx), abs(newy))
return p
def inRespectToYAxis(vertex, length, angle):
x, y = vertex.getCoords()
theta_rad = pi / 2 - radians(angle)
newx = x + length * cos(radians(angle))
newy = y + length * sin(radians(angle))
p = Point(newx, newy)
return p
Seems you want to add line segment angle to get proper result. You can calculate it using segment ends coordinates (x1,y1) and (x2,y2)
lineAngle = math.atan2(y2 - y1, x2 - x1)
Result is in radians, so apply it as
endx = x1 + length * cos(radians(angle) + lineAngle) etc

Complete turn x angle values are mapped to half turn using asin function, how to mirror them back?

I have angles that form a complete turn in an array x, from -90 to 270 e.g. (it may be defined otherwise, like from 0 to 360 or -180 to 180) with step 1 or whatever.
asin function is valid only between -90 and +90.
Thus, angles < -90 or > 90 would be "mapped" between these values.
E.g. y = some_asin_func(over_sin(x)) will end up in an y value that is always between -90 and +90. So y is stuck between -90 and +90.
I do need to retrieve to which x-input is y related, because it's ambiguous yet: for example, the function over (x) will give the same y values for x = 120 and x = 60, or x = -47 and x = 223. Which is not what I want.
Put an other way; I need y making a complete turn as x does, ranging from where x starts up to where x ends.
An image will be better:
Here, x ranges between -90 (left) to 270 (right of the graph).
The valid part of the curve is between x=-90 and x=+90 (left half of the graph).
All other values are like mirrored about y=90 or y=-90.
For x=180 for example, I got y=0 and it should be y=180.
For x=270, I have y=-90 but it should be y=270, thus +360.
Here's a code sample:
A = 50 # you can make this value vary to have different curves like in the images, when A=0 -> shape is triangle-like, when A=90-> shape is square-like.
x = np.linspace(-90,270,int(1e3))
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
plt.plot(x,y)
plt.grid(True)
Once again, first left half of the graph is completely correct.
The right half is also correct in its behavior, but in final, here, it must be mirrored about an horizontal axis at position y=+90 when x>90, like this:
That is, it's like the function is mirrored about y=-90 and y=+90 for y where x is out of the range [-90,+90] and only where where x is out of the range [-90,+90].
I want to un-mirror it outside the valid [-90,+90] range:
about y=-90 where y is lower than -90
about y=+90 where y is greater than +90
And of course, modulo each complete turn.
Here an other example where x ranges from -180 to 180 and the desired behavior:
Yet:
Wanted:
I have first tested some simple thing up now:
A = 50
x = np.linspace(-180,180,int(1e3))
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
for i,j in np.ndenumerate(x):
xval = (j-180)%180-180
if (xval < -90):
y[i] = y[i]-val
elif (xval > 90):
y[i] = y[i]+val
plt.plot(x,y);
plt.grid(True)
plt.show()
which doesn't work at all but I think the background idea is there...
I guess it may be some kind of modulo trick but can't figure it out.
Here a solution that fixes the periodicity of the cos function 'brute force' by calculating an offset and a sign correction based on the x value. I'm sure there is something better out there, but I would almost need a drawing with the angles and distances involved.
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,1, figsize=(4,4))
x = np.linspace(-540,540,1000)
sign = np.sign(np.cos(np.pi*x/180))
offset = ((x-90)//180)*180
for A in range(1,91,9):
u = np.sin(np.pi*A/180)*np.cos(np.pi*x/180)
v = 180*(np.arcsin(u))/np.pi
y = 180*np.arcsin(np.sin(np.pi*x/180)/np.cos(np.pi*v/180))/np.pi
y = sign*y + offset
ax.plot(x,y)
ax.grid(True)
plt.show()
The result for the interval [-540, 540] looks like this:
Note that you can get pi also from numpy, so you don't need to import math -- I altered the code accordingly.
EDIT:
Apparently I first slightly misunderstood the OP's desired output. If the calculation of offset is just slightly changed, the result is as requested:
from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,1, figsize=(4,4))
x = np.linspace(-720,720,1000)
sign = np.sign(np.cos(np.pi*x/180))
offset = ((x-90)//180 +1 )*180 - ((x-180)//360+1)*360
for A in range(1,91,9):
u = np.sin(np.pi*A/180)*np.cos(np.pi*x/180)
v = 180*(np.arcsin(u))/np.pi
y = 180*np.arcsin(np.sin(np.pi*x/180)/np.cos(np.pi*v/180))/np.pi
y = sign*y + offset
ax.plot(x,y)
ax.grid(True)
plt.show()
The result now looks like this:
Thank you #Thomas Kühn, it seems fine except I wanted to restrict the function in a single same turn in respect to y-values. Anyway, it's only aesthetics.
Here's what I found by my side. It's maybe not perfect but it works:
A = 50
u = np.sin(math.pi*A/180)*np.cos(math.pi*x/180)
v = 180*(np.arcsin(u))/math.pi
y = 180*np.arcsin(np.sin(math.pi*x/180)/np.cos(math.pi*v/180))/math.pi
for i,j in np.ndenumerate(x):
val = (j-180)%360-180
if (val < -90):
y[i] = -180-y[i]
elif (val > 90):
y[i] = 180-y[i]
Here are some expected results:
Range from -180 to +180
Range from 0 to +360
Range from -720 to +720
Range from -360 to +360 with some different A values.
Funny thing is that it reminds me some electronics diagrams as well.
Periodic phenomenons are everywhere!

Categories