You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Python中创建互不干扰的独立绘图窗口,避免动画与频谱图冲突

问题解决方法

你遇到的冲突核心原因有两个:

  1. 全程使用matplotlib pyplot的全局接口绘图,没有明确绑定绘图对象,动画和频谱图绘制时会争抢全局活动画布,导致渲染异常。
  2. 没有对旧动画实例做销毁处理,多次切换信号后后台会存在多个动画任务同时运行,占用资源导致程序冻结。

如果要实现点击Spec按钮弹出独立频谱窗口,按以下方案修改即可:

  • 新增变量存储当前生效的信号数据和动画实例,每次切换信号前先停止上一个动画,避免后台任务堆积
  • 所有绘图操作明确指定对应的Axes对象,放弃全局plt.xxx调用,避免状态混乱
  • 频谱图绘制时直接创建新的matplotlib独立窗口,不占用主界面的画布资源,完全和动画逻辑隔离

修改后完整代码

import PySimpleGUI as sg
import matplotlib.pyplot as plt
import neurokit2 as nk
from matplotlib.animation import FuncAnimation
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk, FigureCanvasTkAgg


class Toolbar(NavigationToolbar2Tk):
    # 仅保留需要的按钮
    toolitems = [t for t in NavigationToolbar2Tk.toolitems if
                 t[0] in ('Home', 'Pan', 'Zoom')]

    def __init__(self, *args, **kwargs):
        super(Toolbar, self).__init__(*args, **kwargs)


def draw_figure(canvas, figure, canvas_toolbar=None):
    figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
    figure_canvas_agg.draw_idle()
    if canvas_toolbar is not None:
        toolbar = Toolbar(figure_canvas_agg, canvas_toolbar)
        toolbar.update()
    figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
    return figure_canvas_agg


def main():
    # 定义布局
    layout = [[sg.Text('Signal Viewer', size=(40, 1), justification='center', font='Helvetica 20')],
              [sg.Canvas(key='controls_cv', pad=((280, 0), 3))],
              [sg.Canvas(size=(50, 36), key='-CANVAS-')],
              [sg.Button('ECG', pad=((250, 0), 3)), sg.Button('EMG'), sg.Button('RSP'), sg.Button('Spec')],
              [sg.Button('Exit', size=(5, 1), pad=((280, 0), 3), font='Helvetica 14')]]

    # 创建主窗口
    window = sg.Window('Signal Viewer', layout, finalize=True)

    canvas_elem = window['-CANVAS-']
    canvas = canvas_elem.TKCanvas
    # 存储当前信号、当前动画实例
    current_signal = None
    current_ani = None
    x = []
    # 动画专用画布,明确绑定axes
    fig, ax = plt.subplots()
    ax.grid(True)
    fig_agg = draw_figure(canvas, fig, window.FindElement('controls_cv').TKCanvas)

    def show_spec():
        if current_signal is None:
            sg.popup("请先选择信号类型")
            return
        # 新建独立窗口画频谱图,和主画布完全隔离
        spec_fig, spec_ax = plt.subplots(figsize=(8,4))
        spec_ax.specgram(current_signal, NFFT=64, Fs=256, noverlap=32)
        spec_ax.set_title("信号频谱图")
        spec_ax.set_xlabel("时间 (s)")
        spec_ax.set_ylabel("频率 (Hz)")
        # block=False避免阻塞主界面
        plt.show(block=False)

    def wave_form(ecg100):
        nonlocal current_ani, x
        # 先停止上一个动画
        if current_ani is not None:
            current_ani.event_source.stop()
            del current_ani
        # 清空历史数据
        x.clear()
        ax.cla()
        ax.grid(True)

        def update_time():
            t = 0
            t_max = len(ecg100)
            while t < t_max and t >= 0:
                t += ani.direction
                yield t

        def animate(frame):
            st, end = frame / 2, frame + 0.5
            x.append(ecg100[frame])
            ax.cla()
            ax.set_ylim([ecg100.min(), ecg100.max()])
            ax.plot(x)
            ax.grid()
            ax.set_xlim(st, end)
            fig.tight_layout()

        def on_press(event1):
            print(event1.key)
            if event1.key.isspace():
                if ani.running:
                    ani.event_source.stop()
                else:
                    ani.event_source.start()
                ani.running ^= True
            elif event1.key == 'left':
                ani.direction = -1
            elif event1.key == 'right':
                ani.direction = +1
            if event1.key in ['left', 'right']:
                t = next(ani.frame_seq)
                animate(t)

        fig.canvas.mpl_connect('key_press_event', on_press)
        # 明确指定当前figure创建动画,不用全局gcf
        ani = FuncAnimation(fig, animate, frames=update_time(), interval=5, repeat=True)
        ani.running = True
        ani.direction = +1
        current_ani = ani
        fig_agg.draw_idle()

    while True:
        event, values = window.read(timeout=10)
        if event in ('Exit', sg.WIN_CLOSED):
            plt.close('all')
            window.close()
            exit(0)
        elif event == 'ECG':
            current_signal = nk.ecg_simulate(duration=10, noise=0.01, heart_rate=100)
            wave_form(current_signal)
        elif event == 'EMG':
            current_signal = nk.emg_simulate(duration=10, sampling_rate=100, burst_number=4)
            wave_form(current_signal)
        elif event == 'RSP':
            current_signal = nk.rsp_simulate(duration=30, sampling_rate=50, noise=0.01)
            wave_form(current_signal)
        elif event == 'Spec':
            show_spec()


if __name__ == '__main__':
    main()

内容的提问来源于stack exchange,提问作者Abbas Mohammed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 15:39:01