I made a Fourier Series/Transform Tkinter app, and so far everything works as I want it to, except that I am having issues with the circles misaligning.
Here is an image explaining my issue (the green and pink were added after the fact to better explain the issue):
I have narrowed down the problem to the start of the lines, as it seems that they end in the correct place, and the circles are in their correct places.
The distance between the correct positions and the position where the lines start seems to grow, but is actually proportional to the speed of the circle rotating, as the circle rotates by larger amounts, thus going faster.
Here is the code:
from tkinter import *
import time
import math
import random
root = Tk()
myCanvas = Canvas(root, width=1300, height=750)
myCanvas.pack()
myCanvas.configure(bg="#0A2239")
global x,y, lines, xList, yList
NumOfCircles = 4
rList = [200]
n=3
for i in range(0, NumOfCircles):
rList.append(rList[0]/n)
n=n+2
print(rList)
num = 250/sum(rList)
for i in range(0, NumOfCircles):
rList[i] = rList[i]*num
x=0
y=0
lines = []
circles = []
centerXList = [300]
for i in range(0,NumOfCircles):
centerXList.append(0)
centerYList = [300]
for i in range(0,NumOfCircles):
centerYList.append(0)
xList = [0]*NumOfCircles
yList = [0]*NumOfCircles
waveLines = []
wavePoints = []
con=0
endCoord = []
for i in range(0, NumOfCircles):
endCoord.append([0,0])
lastX = 0
lastY = 0
count = 0
randlist = []
n=1
for i in range(0, NumOfCircles):
randlist.append(200/n)
n=n+2
def createCircle(x, y, r, canvasName):
x0 = x - r
y0 = y - r
x1 = x + r
y1 = y + r
return canvasName.create_oval(x0, y0, x1, y1, width=r/50, outline="#094F9A")
def updateCircle(i):
newX = endCoord[i-1][0]
newY = endCoord[i-1][1]
centerXList[i] = newX
centerYList[i] = newY
x0 = newX - rList[i]
y0 = newY - rList[i]
x1 = newX + rList[i]
y1 = newY + rList[i]
myCanvas.coords(circles[i], x0, y0, x1, y1)
def circleWithLine(i):
global line, lines
circle = createCircle(centerXList[i], centerYList[i], rList[i], myCanvas)
circles.append(circle)
line = myCanvas.create_line(centerXList[i], centerYList[i], centerXList[i], centerYList[i], width=2, fill="#1581B7")
lines.append(line)
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
def lineBetweenTwoPoints(x, y, x2, y2):
line = myCanvas.create_line(x, y, x2, y2, fill="white")
return line
def lineForWave(y1, y2, y3, y4, con):
l = myCanvas.create_line(700+con, y1, 702+con, y2, 704+con, y3, 706+con, y4, smooth=1, fill="white")
waveLines.append(l)
for i in range(0,NumOfCircles):
circleWithLine(i)
myCanvas.create_line(700, 20, 700, 620, fill="black", width = 3)
myCanvas.create_line(700, 300, 1250, 300, fill="red")
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 0.5)
myCanvas.create_line(300, 0, 300, 600, fill="red", width = 0.5)
while True:
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
time.sleep(0.01)
root.mainloop()
I am aware that this is not the best way to achieve what I am trying to achieve, as using classes would be much better. I plan to do that in case nobody can find a solution, and hope that when it is re-written, this issue does not persist.
The main problem that you are facing is that you receive floating point numbers from your calculations but you can only use integers for pixels. In the following I will show you where you fail and the quickest way to solve the issue.
First your goal is to have connected lines and you calculate the points here:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
when you add the following code into this function you see that it fails there.
if i != 0:
print(i,x,y)
print(i,endCoord[i-1][0], endCoord[i-1][1])
Because x and y should always match with the last point (end of the previous line) that will be endCoord[i-1][0] and endCoord[i-1][1].
to solve your problem I simply skipt the match for the sarting point of the follow up lines and took the coordinates of the previous line with the following alternated function:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
if i == 0:
points = x, y, endCoord[i][0], endCoord[i][1]
else:
points = endCoord[i-1][0], endCoord[i-1][1], endCoord[i][0], endCoord[i][1]
myCanvas.coords(lines[i], *points)
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
Additional proposals are:
don't use wildcard imports
import just what you really use in the code random isnt used in your example
the use of global in the global namespace is useless
create functions to avoid repetitive code
def listinpt_times_circles(inpt):
return [inpt]*CIRCLES
x_list = listinpt_times_circles(0)
y_list = listinpt_times_circles(0)
center_x_list = listinpt_times_circles(0)
center_x_list.insert(0,300)
center_y_list = listinpt_times_circles(0)
center_y_list.insert(0,300)
use .after(ms,func,*args) instead of a interrupting while loop and blocking call time.sleep
def animate():
global count,con,lastX,lastY
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update_idletasks()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
root.after(10,animate)
animate()
root.mainloop()
read the PEP 8 -- Style Guide for Python
use intuitive variable names to make your code easier to read for others and yourself in the future
list_of_radii = [200] #instead of rList
as said pixels will be expressed with integers not with floating point numbers
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 1) #0.5 has no effect compare 0.1 to 1
using classes and a canvas for each animation will become handy if you want to show more cycles
dont use tkinters update method
As #Thingamabobs said, the main reason for the misalignment is that pixel coordinates work with integer values. I got excited about your project and decided to make an example using matplotlib, this way I do not have to work with integer values for the coordinates. The example was made to work with any function, I implemented samples with sine, square and sawtooth functions.
I also tried to follow some good practices for naming, type annotations and so on, I hope this helps you
from numbers import Complex
from typing import Callable, Iterable, List
import matplotlib.pyplot as plt
import numpy as np
def fourier_series_coeff_numpy(f: Callable, T: float, N: int) -> List[Complex]:
"""Get the coefficients of the Fourier series of a function.
Args:
f (Callable): function to get the Fourier series coefficients of.
T (float): period of the function.
N (int): number of coefficients to get.
Returns:
List[Complex]: list of coefficients of the Fourier series.
"""
f_sample = 2 * N
t, dt = np.linspace(0, T, f_sample + 2, endpoint=False, retstep=True)
y = np.fft.fft(f(t)) / t.size
return y
def evaluate_fourier_series(coeffs: List[Complex], ang: float, period: float) -> List[Complex]:
"""Evaluate a Fourier series at a given angle.
Args:
coeffs (List[Complex]): list of coefficients of the Fourier series.
ang (float): angle to evaluate the Fourier series at.
period (float): period of the Fourier series.
Returns:
List[Complex]: list of complex numbers representing the Fourier series.
"""
N = np.fft.fftfreq(len(coeffs), d=1/len(coeffs))
N = filter(lambda x: x >= 0, N)
y = 0
radius = []
for n, c in zip(N, coeffs):
r = 2 * c * np.exp(1j * n * ang / period)
y += r
radius.append(r)
return radius
def square_function_factory(period: float):
"""Builds a square function with given period.
Args:
period (float): period of the square function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 if x % period < period / 2 else -1.0 for x in t]
elif isinstance(t, float):
return 1.0 if t % period < period / 2 else -1.0
return f
def saw_tooth_function_factory(period: float):
"""Builds a saw-tooth function with given period.
Args:
period (float): period of the saw-tooth function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 - 2 * (x % period / period) for x in t]
elif isinstance(t, float):
return 1.0 - 2 * (t % period / period)
return f
def main():
PERIOD = 1
GRAPH_RANGE = 3.0
N_COEFFS = 30
f = square_function_factory(PERIOD)
# f = lambda t: np.sin(2 * np.pi * t / PERIOD)
# f = saw_tooth_function_factory(PERIOD)
coeffs = fourier_series_coeff_numpy(f, 1, N_COEFFS)
radius = evaluate_fourier_series(coeffs, 0, 1)
fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(10, 5))
ang_cum = []
amp_cum = []
for ang in np.linspace(0, 2*np.pi * PERIOD * 3, 200):
radius = evaluate_fourier_series(coeffs, ang, 1)
x = np.cumsum([x.imag for x in radius])
y = np.cumsum([x.real for x in radius])
x = np.insert(x, 0, 0)
y = np.insert(y, 0, 0)
axs[0].plot(x, y)
axs[0].set_ylim(-GRAPH_RANGE, GRAPH_RANGE)
axs[0].set_xlim(-GRAPH_RANGE, GRAPH_RANGE)
ang_cum.append(ang)
amp_cum.append(y[-1])
axs[1].plot(ang_cum, amp_cum)
axs[0].axhline(y=y[-1],
xmin=x[-1] / (2 * GRAPH_RANGE) + 0.5,
xmax=1.2,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
min_x, max_x = axs[1].get_xlim()
line_end_x = (ang - min_x) / (max_x - min_x)
axs[1].axhline(y=y[-1],
xmin=-0.2,
xmax=line_end_x,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
plt.pause(0.01)
axs[0].clear()
axs[1].clear()
if __name__ == '__main__':
main()
Related
Given a list of points that form a polygon, how can I create evenly spaced lines within that polygon that are parallel to it's longest side?
I am able to rotate the lines and get the even spacing, but I can't seem to place them within the polygon. My intention after getting the lines within the polygon is to find where they intercept it.
Here is the point at which I am now stuck:
import matplotlib.pyplot as plt
import numpy as np
import math
def longest_side(points):
"""
Returns the points of the longest side
"""
max_length = 0
for i in range(len(points)-1):
cur_length = np.linalg.norm(np.array(points[i])-np.array(points[i+1]))
if cur_length > max_length:
max_length = cur_length
cur_longest = [points[i], points[i+1]]
return cur_longest
def rotate(origin, point, angle):
"""
Rotate point around origin
"""
ox, oy = origin
px, py = point
qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy)
qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy)
return qx, qy
def create_lines(points, spacing):
"""
Fill polygon with lines
"""
# Get the longest side
longest_lines = longest_side(points)
x1,y1 = longest_lines[0]
x2,y2 = longest_lines[1]
# Arrange the points in acending x-value
if x2 < x1:
tmp = (x1, y1)
x1 = x2
y1 = y2
x2 = tmp[0]
y2 = tmp[1]
# Get the angle between the longest line and the horizontal axis
angle = math.atan2(y2 - y1, x2 - x1)
# Create lines parallel to the longest line with given spacing
for y in np.arange(min(y1, y2), max(y1, y2), spacing):
xr, yr = rotate(origin=[min(x), y], point=[max(x), y], angle=angle)
plt.plot([min(x), xr], [y, yr])
if __name__ == "__main__":
points = ([0, 8], [2, 10], [10, 4], [10, 0], [0, 8])
x = [p[0] for p in points]
y = [p[1] for p in points]
create_lines(points=points, spacing=1)
plt.plot(x, y, 'ro-')
plt.axis('scaled')
plt.show()
Is there a general way this problem can be solved given any list of points?
The short answer: you need to do some geometry.
The long answer:
Create a Line Segment class to easily calculate line intersections and acceptable range of intercepts of lines with fixed slope m that still intersect with the line segment.
Turn your points into Line Segments, find the longest line, find the range of intercepts needed to fill the polygon, then find the intersections for each line generated by each intercept.
Class definition:
import matplotlib.pyplot as plt
import numpy as np
import math
# Represent a non-vertical line segment from start_pt to end_pt
# as y = mx + b and minv <= x <= maxv.
# For vertical lines x = b, m = None and minv <= y <= maxv
class LineSeg():
def __init__(self, start_pt, end_pt):
self.x, self.y = start_pt
self.x2, self.y2 = end_pt
if self.x != self.x2:
self.m = (self.y2 - self.y) / (self.x2 - self.x)
self.b = self.y - self.m*self.x
self.minv = min(self.x, self.x2)
self.maxv = max(self.x, self.x2)
else:
self.m = None
self.b = self.x
self.minv = min(self.y, self.y2)
self.maxv = max(self.y, self.y2)
def length(self):
return np.linalg.norm([self.x2-self.x, self.y2-self.y])
# Find intersection (x, y) with line y = mx + b
def intersect_w_line(self, m, b):
# Parallel lines
if m == self.m:
return (None, None)
# Line is vertical but line segment is not
elif m == None:
if self.minv <= b <= self.maxv:
return (b, self.m*b + self.b)
else:
return (None, None)
# Line segment is vertical, but line is not
elif self.m == None:
y = m*self.b + b
if self.minv <= y <= self.maxv:
return (self.b, y)
else:
return (None, None)
else:
x = (b - self.b) / (self.m - m)
y = self.m*x + self.b
if self.minv <= x <= self.maxv:
return (x, y)
else:
return (None, None)
# Find intercept range with line y = mx + b
def intercept_range(self, m):
if self.m == m:
return (self.b, self.b)
# Line is vertical, but segment is not
elif m == None:
return sorted([self.x, self.x2])
# Line is not vertical
else:
b = self.y - m*self.x
b2 = self.y2 - m*self.x2
return sorted([b, b2])
Plotting:
points = ([0, 8], [2, 10], [10, 4], [10, 0])
linesegs = [LineSeg(points[i], points[i+1]) if i+1 < len(points) else LineSeg(points[i], points[0]) for i in range(len(points))]
lengths = [lineseg.length() for lineseg in linesegs]
longest_seg = [lineseg for lineseg in linesegs if lineseg.length() == max(lengths)]
m = longest_seg[0].m
b = longest_seg[0].b
intercept_ranges = [lineseg.intercept_range(m) for lineseg in linesegs]
max_intercept = np.max(intercept_ranges)
min_intercept = np.min(intercept_ranges)
num_lines = 10
spacing = (max_intercept - min_intercept) / (num_lines+1)
intercepts = np.arange(min_intercept + spacing, max_intercept, spacing)
line_pts = [[lineseg.intersect_w_line(m, intercept) for lineseg in linesegs if lineseg.intersect_w_line(m, intercept)[0] is not None] for intercept in intercepts]
plt.close('all')
fig, ax = plt.subplots(1, 1)
polygon = mpl.patches.Polygon(points, closed = True, fill = False)
ax.add_artist(polygon)
for start, end in line_pts:
line = mpl.lines.Line2D([start[0], end[0]], [start[1], end[1]])
ax.add_artist(line)
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
I was about to plot a Poincare section of the following DE, which is quite meaningful to have a periodic potential function V(x) = - cos(x) in this equation.
After calculating the solution using RK4 with time interval dt = 0.001, the one that python drew was as the following plot.
But according to the textbook(referred to 2E by J.M.T. Thompson and H.B. Stewart), the section would look like as
:
it has so much difference. For my personal opinion, since Poincare section does not appear as what writers draw, there must be some error in my code. However, I actually done for other forced oscillation DE, including Duffing's equation, and obtained the identical one as those in the textbook. So, I was wodering if there are some typos in the equation given by the textbook, or somewhere else. I posted my code, but might be quite messy to understand. So appreicate dealing with it.
import numpy as np
import matplotlib.pylab as plt
import matplotlib as mpl
import sys
import time
state = [1]
def print_percent_done(index, total, state, title='Please wait'):
percent_done2 = (index+1)/total*100
percent_done = round(percent_done2, 1)
print(f'\t⏳{title}: {percent_done}% done', end='\r')
if percent_done2 > 99.9 and state[0]:
print('\t✅'); state = [0]
####
no = 1
####
def multiple(n, q):
m = n; i = 0
while m >= 0:
m -= q
i += 1
return min(abs(n - (i - 1)*q), abs(i*q - n))
# system(2)
#Basic info.
filename = 'sinPotentialWell'
# a = 1
# alpha = 0.01
# w = 4
w0 = .5
n = 1000000
h = .01
t_0 = 0
x_0 = 0.1
y_0 = 0
A = [(t_0, x_0, y_0)]
def f(t, x, y):
return y
def g(t, x, y):
return -0.5*y - np.sin(x) + 1.1*np.sin(0.5*t)
for i in range(n):
t0 = A[i][0]; x0 = A[i][1]; y0 = A[i][2]
k1 = f(t0, x0, y0)
u1 = g(t0, x0, y0)
k2 = f(t0 + h/2, x0 + h*k1/2, y0 + h*u1/2)
u2 = g(t0 + h/2, x0 + h*k1/2, y0 + h*u1/2)
k3 = f(t0 + h/2, x0 + h*k2/2, y0 + h*u2/2)
u3 = g(t0 + h/2, x0 + h*k2/2, y0 + h*u2/2)
k4 = f(t0 + h, x0 + h*k3, y0 + h*u3)
u4 = g(t0 + h, x0 + h*k3, y0 + h*u3)
t = t0 + h
x = x0 + (k1 + 2*k2 + 2*k3 + k4)*h/6
y = y0 + (u1 + 2*u2 + 2*u3 + u4)*h/6
A.append([t, x, y])
if i%1000 == 0: print_percent_done(i, n, state, 'Solving given DE')
#phase diagram
print('showing 3d_(x, y, phi) graph')
PHI=[[]]; X=[[]]; Y=[[]]
PHI_period1 = []; X_period1 = []; Y_period1 = []
for i in range(n):
if w0*A[i][0]%(2*np.pi) < 1 and w0*A[i-1][0]%(2*np.pi) > 6:
PHI.append([]); X.append([]); Y.append([])
PHI_period1.append((w0*A[i][0])%(2*np.pi)); X_period1.append(A[i][1]); Y_period1.append(A[i][2])
phi_period1 = np.array(PHI_period1); x_period1 = np.array(X_period1); y_period1 = np.array(Y_period1)
print('showing Poincare Section at phi=0')
plt.plot(x_period1, y_period1, 'gs', markersize = 2)
plt.plot()
plt.title('phi=0 Poincare Section')
plt.xlabel('x'); plt.ylabel('y')
plt.show()
If you factor out some of the computation blocks, you can make the code more flexible and computations more direct. No need to reconstruct something if you can construct it in the first place. You want to catch the points where w0*t is a multiple of 2*pi, so just construct the time loops so you integrate in chunks of 2*pi/w0 and only remember the interesting points.
num_plot_points = 2000
h = .01
t,x,y = t_0,x_0,y_0
x_section,y_section = [],[]
T = 2*np.pi/w0
for k in range(num_plot_points):
t = 0;
while t < T-1.2*h:
x,y = RK4step(t,x,y,h)
t += h
x,y = RK4step(t,x,y,T-t)
if k%100 == 0: print_percent_done(k, num_plot_points, state, 'Solving given DE')
x_section.append(x); y_section.append(y)
with RK4step just containing the code of the RK4 step.
This will not solve the mystery. The veil gets lifted if you consider that x is the angle theta (of a forced pendulum with friction) on a circle. Thus to get points with the same spacial location it needs to be reduced by multiples of 2*pi. Doing that,
plt.plot([x%(2*np.pi) for x in x_section], y_section, 'gs', markersize = 2)
results in the expected plot
I changed some things but i still have a similar problem. I am working on Mandelbrot zoom. I try to zoom in deeper at branches. I count the consecutive points in the set and return the branch if it reaches the defined length. Then I zoom into that area and repeat. But the change gets smaller and the returned branch is nearly the same as the last one.
variables
X0,Y0,X1 = verticies of current area
branch = current branch
n = number of iterations
p = how many times i want to zoom
b = minimum length of branch i am looking for
c = current complex number
z = current value
k = pixel size
the function that returns the branch
def fractal(n, X0, Y0, X1): # searching for new branch
branch = []
k = (X1 - X0) / x_axis # pixel size
b = 5 # the number of black points int the set i am looking for
for y in range(y_axis):
try:
for x in range(x_axis):
c = X0 + k * x + Y0 - k * y * 1.j # new c
z = c
for m in range(n):
if abs(z) <= 2:
z = z * z + c # new z
else:
break
if abs(z) <= 2:
branch.append((x,y)) # the coordinates for those points
else:
if b < len(branch) < 50:
raise BreakOutOfALoop # break if a branch was found
branch = []
except BreakOutOfALoop:
break
return branch, k
the function that calculates the verticies of the new area
def new_area(branch, X0, Y0, X1, k): # calculating new area
print(branch)
X0 = X0 + k * branch[0][0]
Y0 = Y0 - k * branch[0][1]
X1 = X1 + k * branch[-1][0] # new area verticies
return X0, Y0, X1
the loop that calls the functions
for i in range(p):
areaImage = Image.new('RGB', (x_axis,y_axis), "white")
area_pixels = areaImage.load() # image load
branch, k = fractal(n,X0, Y0, X1)
X0, Y0, X1 = new_area(branch, X0, Y0, X1, k)
file_name = "/" + str(i) + "_" + str(n) + "_" + str(x_axis) + "_" + str(y_axis) + ".png" # image save
areaImage = areaImage.save(f"{areaImage_path}{file_name}")
What am I overlooking?
Here are some pictures for different p.
p = 3
p = 10
Is there a way to draw direction fields in python?
My attempt is to modify http://www.compdigitec.com/labs/files/slopefields.py giving
#!/usr/bin/python
import math
from subprocess import CalledProcessError, call, check_call
def dy_dx(x, y):
try:
# declare your dy/dx here:
return x**2-x-2
except ZeroDivisionError:
return 1000.0
# Adjust window parameters
XMIN = -5.0
XMAX = 5.0
YMIN = -10.0
YMAX = 10.0
XSCL = 0.5
YSCL = 0.5
DISTANCE = 0.1
def main():
fileobj = open("data.txt", "w")
for x1 in xrange(int(XMIN / XSCL), int(XMAX / XSCL)):
for y1 in xrange(int(YMIN / YSCL), int(YMAX / YSCL)):
x= float(x1 * XSCL)
y= float(y1 * YSCL)
slope = dy_dx(x,y)
dx = math.sqrt( DISTANCE/( 1+math.pow(slope,2) ) )
dy = slope*dx
fileobj.write(str(x) + " " + str(y) + " " + str(dx) + " " + str(dy) + "\n")
fileobj.close()
try:
check_call(["gnuplot","-e","set terminal png size 800,600 enhanced font \"Arial,12\"; set xrange [" + str(XMIN) + ":" + str(XMAX) + "]; set yrange [" + str(YMIN) + ":" + str(YMAX) + "]; set output 'output.png'; plot 'data.txt' using 1:2:3:4 with vectors"])
except (CalledProcessError, OSError):
print "Error: gnuplot not found on system!"
exit(1)
print "Saved image to output.png"
call(["xdg-open","output.png"])
return 0
if __name__ == '__main__':
main()
However the best image I get from this is.
How can I get an output that looks more like the first image? Also, how can I add the three solid lines?
You can use this matplotlib code as a base. Modify it for your needs.
I have updated the code to show same length arrows. The important option is to set the angles option of the quiver function, so that the arrows are correctly printed from (x,y) to (x+u,y+v) (instead of the default, which just takes into account of (u,v) when computing the angles).
It is also possible to change the axis form "boxes" to "arrows". Let me know if you need that change and I could add it.
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
fig = plt.figure()
def vf(x, t):
dx = np.zeros(2)
dx[0] = 1.0
dx[1] = x[0] ** 2 - x[0] - 2.0
return dx
# Solution curves
t0 = 0.0
tEnd = 10.0
# Vector field
X, Y = np.meshgrid(np.linspace(-5, 5, 20), np.linspace(-10, 10, 20))
U = 1.0
V = X ** 2 - X - 2
# Normalize arrows
N = np.sqrt(U ** 2 + V ** 2)
U = U / N
V = V / N
plt.quiver(X, Y, U, V, angles="xy")
t = np.linspace(t0, tEnd, 100)
for y0 in np.linspace(-5.0, 0.0, 10):
y_initial = [y0, -10.0]
y = odeint(vf, y_initial, t)
plt.plot(y[:, 0], y[:, 1], "-")
plt.xlim([-5, 5])
plt.ylim([-10, 10])
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
I had a lot of fun making one of these as a hobby project using pygame. I plotted the slope at each pixel, using shades of blue for positive and shades of red for negative. Black is for undefined. This is dy/dx = log(sin(x/y)+cos(y/x)):
You can zoom in & out - here is zoomed in on the middle upper part here:
and also click on a point to graph the line going through that point:
It's just 440 lines of code, so here is the .zip of all the files. I guess I'll excerpt relevant bits here.
The equation itself is input as a valid Python expression in a string, e.g. "log(sin(x/y)+cos(y/x))". This is then compiled. This function here graphs the color field, where self.func.eval() gives the dy/dx at the given point. The code is a bit complicated here because I made it render in stages - first 32x32 blocks, then 16x16, etc. - to make it snappier for the user.
def graphcolorfield(self, sqsizes=[32,16,8,4,2,1]):
su = ScreenUpdater(50)
lastskip = self.xscreensize
quitit = False
for squaresize in sqsizes:
xsquaresize = squaresize
ysquaresize = squaresize
if squaresize == 1:
self.screen.lock()
y = 0
while y <= self.yscreensize:
x = 0
skiprow = y%lastskip == 0
while x <= self.xscreensize:
if skiprow and x%lastskip==0:
x += squaresize
continue
color = (255,255,255)
try:
m = self.func.eval(*self.ct.untranscoord(x, y))
if m >= 0:
if m < 1:
c = 255 * m
color = (0, 0, c)
else:
#c = 255 - 255 * (1.0/m)
#color = (c, c, 255)
c = 255 - 255 * (1.0/m)
color = (c/2.0, c/2.0, 255)
else:
pm = -m
if pm < 1:
c = 255 * pm
color = (c, 0, 0)
else:
c = 255 - 255 * (1.0/pm)
color = (255, c/2.0, c/2.0)
except:
color = (0, 0, 0)
if squaresize > 1:
self.screen.fill(color, (x, y, squaresize, squaresize))
else:
self.screen.set_at((x, y), color)
if su.update():
quitit = True
break
x += xsquaresize
if quitit:
break
y += ysquaresize
if squaresize == 1:
self.screen.unlock()
lastskip = squaresize
if quitit:
break
This is the code which graphs a line through a point:
def _grapheqhelp(self, sx, sy, stepsize, numsteps, color):
x = sx
y = sy
i = 0
pygame.draw.line(self.screen, color, (x, y), (x, y), 2)
while i < numsteps:
lastx = x
lasty = y
try:
m = self.func.eval(x, y)
except:
return
x += stepsize
y = y + m * stepsize
screenx1, screeny1 = self.ct.transcoord(lastx, lasty)
screenx2, screeny2 = self.ct.transcoord(x, y)
#print "(%f, %f)-(%f, %f)" % (screenx1, screeny1, screenx2, screeny2)
try:
pygame.draw.line(self.screen, color,
(screenx1, screeny1),
(screenx2, screeny2), 2)
except:
return
i += 1
stx, sty = self.ct.transcoord(sx, sy)
pygame.draw.circle(self.screen, color, (int(stx), int(sty)), 3, 0)
And it runs backwards & forwards starting from that point:
def graphequation(self, sx, sy, stepsize=.01, color=(255, 255, 127)):
"""Graph the differential equation, given the starting point sx and sy, for length
length using stepsize stepsize."""
numstepsf = (self.xrange[1] - sx) / stepsize
numstepsb = (sx - self.xrange[0]) / stepsize
self._grapheqhelp(sx, sy, stepsize, numstepsf, color)
self._grapheqhelp(sx, sy, -stepsize, numstepsb, color)
I never got around to drawing actual lines because the pixel approach looked too cool.
Try changing your values for the parameters to this:
XSCL = .2
YSCL = .2
These parameters determine how many points are sampled on the axes.
As per your comment, you'll need to also plot the functions for which the derivation dy_dx(x, y) applies.
Currently, you're only calculating and plotting the slope lines as calculated by your function dy_dx(x,y). You'll need to find (in this case 3) functions to plot in addition to the slope.
Start by defining a function:
def f1_x(x):
return x**3-x**2-2x;
and then, in your loop, you'll have to also write the desired values for the functions into the fileobj file.
I have this programme to discuss and I think its a challenging one.. Here I have a yml file which contains the data for an image. The image has x,y,z values and intensity data which is stored in this yml file. I have used opencv to load the data and its working fine with masking.. but I am having problems in dynamically appending the masks created.. Here is the code I made for solving the problem :
import cv
from math import floor, sqrt, ceil
from numpy import array, dot, subtract, add, linalg as lin
mask_size = 9
mask_size2 = mask_size / 2
f = open("Classified_Image1.txt", "w")
def distance(centre, point):
''' To find out the distance between centre and the point '''
dist = sqrt(
((centre[0]-point[0])**2) +
((centre[1]-point[1])**2) +
((centre[2]-point[2])**2)
)
return dist
def CalcCentre(points): # Calculates centre for a given set of points
centre = array([0,0,0])
count = 0
for p in points:
centre = add(centre, array(p[:3]))
count += 1
centre = dot(1./count, centre)
print centre
return centre
def addrow(data, points, x, y, ix , iy ):# adds row to the mask
iy = y + 1
for dx in xrange(-mask_size2 , mask_size2 + 2):
ix = x + dx
rowpoints = addpoints(data, points, iy, ix)
return rowpoints
def addcolumn(data, points, x, y, ix , iy ):# adds column to the mask
ix = x + 1
for dy in xrange(-mask_size2-1 , mask_size2 + 1):
iy = y + dy
columnpoints = addpoints(data, points, iy, ix)
return columnpoints
def addpoints (data, points, iy, ix): # adds a list of relevant points
if 0 < ix < data.width and 0 < iy < data.height:
pnt = data[iy, ix]
if pnt != (0.0, 0.0, 0.0):
print ix, iy
print pnt
points.append(pnt)
return points
def CreateMask(data, y, x):
radius = 0.3
points = []
for dy in xrange(-mask_size2, mask_size2 + 1): ''' Masking the data '''
for dx in xrange(-mask_size2, mask_size2 + 1):
ix, iy = x + dx, y + dy
points = addpoints(data, points, iy , ix )
if len(points) > 3:
centre = CalcCentre(points)
distances = []
for point in points :
dist = distance(centre, point)
distances.append(dist)
distancemax = max(distances)
print distancemax
if distancemax < radius: ''' Dynamic Part of the Programme'''
#while dist < radius: # Going into infinite loop .. why ?
p = addrow(data, points, x, y, ix , iy )
q = addcolumn(data, points, x, y, ix , iy )
dist = distance(centre, point) # While should not go in infinite
#loop as dist is changing here
print dist
print len(p), p
print len(q), q
points = p + q
points = list(set(points)) # To remove duplicate points in the list
print len(points), points
def ComputeClasses(data):
for y in range(0, data.height):
for x in range(0, data.width):
CreateMask(data, y, x)
if __name__ == "__main__":
data = cv.Load("Z:/data/xyz_00000_300.yml")
print "data loaded"
ComputeClasses(data)
Feel free to suggest alternative methods/ideas to solve this problem.
Thanks in advance.