使用 Python 和 Tkinter 进行傅立叶级数/变换中的错误圆

发布于 2025-01-10 02:44:25 字数 3647 浏览 0 评论 0原文

我制作了一个傅立叶级数/变换 Tkinter 应用程序,到目前为止,一切都按我想要的方式工作,除了我遇到了圆未对齐的问题。 这是解释我的问题的图像(事后添加绿色和粉色是为了更好地解释问题):

在此处输入图像描述

我已将问题范围缩小到行的开头,因为它们似乎在正确的位置结束,并且圆圈位于正确的位置。 正确位置与线条开始位置之间的距离看似变大,但实际上与圆旋转的速度成正比,圆旋转的量越大,速度就越快。

这是代码:

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 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):

enter image description here

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

被翻牌 2025-01-17 02:44:25

您面临的主要问题是您从计算中收到浮点数,但只能使用整数作为像素。下面我将向您展示失败的地方以及解决问题的最快方法。

首先,您的目标是连接线,然后在此处计算点:

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])

当您将以下代码添加到此函数中时,您会发现它在那里失败。

if i != 0:
    print(i,x,y)
    print(i,endCoord[i-1][0], endCoord[i-1][1])

因为 xy 应始终与最后一个点(上一行的末尾)匹配,即 endCoord[i-1][0]endCoord[i-1][1]

为了解决您的问题,我只是跳过后续行的起始点的匹配,并使用以下替代函数获取上一行的坐标:

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])

其他建议是

  • 不要使用通配符导入
  • 只导入您在代码中实际使用的内容 random 在您的示例中未使用
  • global 命名空间中使用 global 是无用的
  • 创建函数以避免重复代码

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)
  • 使用.after(ms,func,*args) 而不是中断 while 循环和阻塞调用 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()

list_of_radii = [200] #instead of rList

  • 因为所说的像素将用整数表示,而不是使用类的浮点数

myCanvas.create_line(0, 300, 600, 300, fill="red", width = 1) #0.5 has no effect compare 0.1 to 1

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()

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

何必那么矫情 2025-01-17 02:44:25

正如@Thingamabobs 所说,未对齐的主要原因是像素坐标使用整数值。我对你的项目感到兴奋,并决定使用 matplotlib 制作一个示例,这样我就不必使用坐标的整数值。该示例适用于任何函数,我使用正弦波、方波和锯齿波函数实现了示例。

我还尝试遵循一些命名、类型注释等方面的良好实践,希望这对您有所帮助

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()

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()

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文