Home > other >  Matplotlib FuncAnimation Created twice - duplicate when embbeded in tkinter
Matplotlib FuncAnimation Created twice - duplicate when embbeded in tkinter

Time:10-14

I have a troubleing bug that i just could not understands it's origin. Several days of attempts and still no luck.

I'm trying to create a line cursor that correspond to played audio with FuncAnimation and for some reason, the animation is created twice ONLY when the callback (line_select_callback) that activates the function is triggered from RectangleSelector widget after drawing wiith the mouse. when I use a standard TK button to activate the SAME function (line_select_callback), it operates well.

some debugging code with reevant prints is present.

I've created minimal working example. My guess is it has something to do with the figure that is not attached to the tk window, and is silently activated in addition to the embedded figure, I'm not really sure.

Any help will be very much appreciated, Thanks! :)

import os
import threading
import tkinter as tk


from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg)
from matplotlib.widgets import RectangleSelector
import numpy as np
import matplotlib.pyplot as plt
import matplotlib

from matplotlib import animation

class LineAnimation:
    def __init__(self,  fig, ax):
        print(' enter LineAnimation ctor')

        # Parameters
        self.ax = ax
        self.fig = fig
        self.xdata, self.ydata = [], []
        self.ln, = plt.plot([], [], 'ro')

        # Print figures list
        figures = [manager.canvas.figure
                   for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
        print('figures BEFORE animation: ', figures)

        self.animation = animation.FuncAnimation(fig=self.fig,
                                                 func=self.update,
                                                 init_func=self.init,
                                                 frames=np.linspace(0, 2 * np.pi, 128),
                                                 interval=25,
                                                 blit=True, repeat=False,
                                                 cache_frame_data=False)

        self.fig.canvas.draw()
        # Print figures list
        figures = [manager.canvas.figure
                   for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
        print('figures AFTER animation: ', figures, '\n')

    def init(self):
        # Prints for debugging
        print('\nenter init animate')
        print('Thread id: ', threading.get_ident())
        print('Process id: ', os.getpid(), '\n')


        # Init
        self.ax.set_xlim(0, 2*np.pi)
        self.ax.set_ylim(-1, 1)
        return self.ln,

    def update(self, frame):
        self.xdata.append(frame)
        self.ydata.append(np.sin(frame))
        self.ln.set_data(self.xdata, self.ydata)
        return self.ln,

class Example:

    def __init__(self):

        # init window
        self.root = tk.Tk(className=' Species segmentation')
        self.fig, self.ax = plt.subplots()

        # init sine audio file
        self.fs = 44100
        self.dur = 2
        self.freq = 440
        self.x = np.sin(2*np.pi*np.arange(self.fs*self.dur)*self.freq/self.fs)
        # plt.ion()

        # Embedd in tk
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)  # A tk.DrawingArea.
        self.canvas.draw()
        self.canvas.get_tk_widget().grid()

        # Plot something
        self.N = 100000
        self.xp = np.linspace(0, 10, self.N)
        self.ax.plot(self.xp, np.sin(2*np.pi*self.xp))
        self.ax.set_title(
            "Plot for demonstration purpuse")

        # init Rectangle Selector
        self.RS = RectangleSelector(self.ax, self.line_select_callback,
                                   drawtype='box', useblit=True,
                                   button=[1, 3],  # avoid using middle button
                                   minspanx=5, minspany=5,
                                   spancoords='pixels', interactive=True,
                                   rectprops={'facecolor': 'yellow', 'edgecolor': 'black', 'alpha': 0.15, 'fill': True})

        self.canvas.draw()
        # plt.show()


        tk.mainloop()

    def line_select_callback(self, eclick, erelease):
        print('enter line_select_callback')

        self.anim = LineAnimation(
            self.fig,
            self.ax)

        self.fig.canvas.draw()

        # plt.show()

Example()

CodePudding user response:

I managed to isolate the cause for this issue: The presence of the rectangle selector (which uses blitting) and the use of animation (which also uses blitting) on the same axes. I've managed to create the animation properly, but only when I disabled the rectangle selector

    self.RS.set_active(False)
    self.RS.update()
    self.canvas.flush_events()

and removed his artists (i needed to do that manually in my code) using:

    for a in self.RS.artists:
        a.set_visible(False)

after that, The animation worked properly.

  • Related