如何在Python中创建互不干扰的独立绘图窗口,避免动画与频谱图冲突
问题解决方法
你遇到的冲突核心原因有两个:
- 全程使用matplotlib pyplot的全局接口绘图,没有明确绑定绘图对象,动画和频谱图绘制时会争抢全局活动画布,导致渲染异常。
- 没有对旧动画实例做销毁处理,多次切换信号后后台会存在多个动画任务同时运行,占用资源导致程序冻结。
如果要实现点击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
相关产品推荐
相关产品推荐

