Skip to content

MacOSX does not render frames in which new artists are added when blitting #17445

Closed

Description

@mfosterw

Bug report

Bug summary

Using FuncAnimation with the MacOSX backend and blitting turned on, every frame in which a new artist or patch is added to the axes is blank, while all other frames render normally. This does not happen with the TkAgg backend, and presumable MacOSX is the only affected backend.

Code for reproduction

from random import random

import matplotlib
matplotlib.use('MacOSX')
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig = plt.figure()
axe = fig.add_axes([0, 0, 1, 1], frameon=False)
circles = []

def update(i):
    if not i % 10:
        new_circle = plt.Circle((random(), random()), 0.05, color='black')
        axe.add_patch(new_circle)
        circles.append(new_circle)

    for circle in circles:
        circle.center = (random(), random())

    return circles

animation = FuncAnimation(fig, update, frames=60, interval=1000/2, repeat=False, blit=True)
plt.show()

Actual outcome

Every 10th frame, when a new circle is added to the axes, the figure is blank; every other frame outputs normally.

Expected outcome

Every frame should render.

Matplotlib version

  • Operating system: macOS Mojave 10.14.6
  • Matplotlib version: 3.2.1
  • Matplotlib backend (print(matplotlib.get_backend())): MacOSX
  • Python version: 3.8.2

Installed using Conda from conda-forge.

Activity

  1. HaaLeo commented on Nov 16, 2020

    @HaaLeo

    I run into a similar bug with macOS Catalina as well as with Big Sur and matplotlib 3.3.2.
    I try to set the positions of a PathCollection (scatter) for every frame. Furthermore, I create new quiver for every frame.
    The corresponding code can be found here.

    With this setup I experience the same issue as described above. Only in my case none of the mentioned artists is rendered at all. Probably because I update them for every frame.

  2. tacaswell commented on Nov 16, 2020

    @tacaswell
    Member

    @mfosterw Does it work if you do fig.stale_callback = None before starting the animation does it solve the problem?

    @HaaLeo Can you reduce your problem to a minimal reproducible example?

  3. HaaLeo commented on Nov 16, 2020

    @HaaLeo

    @tacaswell sure thing.
    The example should show a marker moving smoothly from [0, 0] to [1, 1] along an arrow.
    It works with the TkAgg backend but not with MacOSX. With the latter one neither a marker nor arrow is shown.

    Details
    import matplotlib.pyplot as plt
    from matplotlib import animation
    import numpy as np
    
    class BaseVisualizer():
        def __init__(self, **kwargs):
            self.__iteration_number = kwargs.get('iteration_number', 1)
            self.__intervals = 1
            self.__interval_ms = kwargs.get('interval', 2000)
    
    
            self._marker_size = 4
            self._index = 0
    
            self._positions = np.zeros(2)
            self._velocities = np.ones(2)
            self.__frame_interval = 50  # ms
    
        def replay(self):
            self._fig = plt.figure()
    
            ax = self._fig.add_subplot(1, 1, 1, label='BaseAxis')
    
            # Plot all particle pos
            self.__particles = ax.scatter([], [], marker='o', zorder=2, color='red')
    
            # Plot all velocities
            self.__particle_vel = ax.quiver([], [], [], [], angles='xy', scale_units='xy', scale=1)
    
            self.__rectangle = plt.Rectangle([0,0], 1, 1, ec='none', lw=2, fc='none')
            ax.add_patch(self.__rectangle)
    
            # Amount of frames to play considering one interval should last __interval ms.
            frames = int(self.__intervals*self.__interval_ms/self.__frame_interval)
    
            # iteration_number+1 for initialization frame
            _ = animation.FuncAnimation(self._fig, self._animate, frames=frames, interval=self.__frame_interval,
                                        blit=True, repeat=False, fargs=[frames])
    
            plt.show()
    
        def _animate(self, i: int, frames: int):
            """
            Animation function for animations. Only used for FuncAnimation
            """
    
            self._marker_size = int(50 * self._fig.get_figwidth()/self._fig.dpi)
    
            # Calculate the scale to apply to the data in order to generate a more dynamic visualization
            scale = i / (frames/self.__intervals)
    
            # Calculate scaled position and velocity
    
            pos_scaled = self._positions + scale * self._velocities
            vel_scaled = (1-scale)*self._velocities
    
            # Update the particle position
            self.__particles.set_offsets(pos_scaled)
            self.__particles.set_sizes([self._marker_size**2])
    
            # Update the velocities
            ax = self._fig.gca(label='BaseAxis')
            self.__particle_vel = ax.quiver(pos_scaled[0], pos_scaled[1], vel_scaled[0], vel_scaled[1], angles='xy', scale_units='xy', scale=1, color='#CFCFCF', width=self._marker_size*0.001)
    
            return self.__particles, self.__particle_vel
    
    
    if __name__ == '__main__':
        vis = BaseVisualizer()
        vis.replay()
  4. HaaLeo commented on Nov 25, 2020

    @HaaLeo

    @tacaswell can you reproduce the error with the sample code above?

  5. tacaswell commented on Nov 25, 2020

    @tacaswell
    Member

    Thank you for re-upping this, I missed your initial reply.

    @HaaLeo I can not reproduce the issue (but I do not have a mac). Can you try the fig.stale_callback = None?

    If you add

    self._fig.canvas.mpl_connect('draw_event', lambda ev: print('Drawing!'))

    to the top of replay how many times does it print 'Drawing'? My guess is that something about the osx backend is triggering an extra full-draw when an artist is added. However, when you are using blitting the animated artists are excluded from that draw pass (so that we can get a "clean" background image to animate on top of). If the order is "add artists, updating via blitting, re-draw!", then you will see blank frames when new artists are added.

    attn @dopplershift


    ps, that is a very cool example.

  6. HaaLeo commented on Nov 25, 2020

    @HaaLeo

    @tacaswell thx for your fast reply (and the feedback on the example 😉)
    I updated the example above with the following lines:

    # ... other code
        def replay(self):
            self._fig = plt.figure()
            self._fig.canvas.mpl_connect('draw_event', lambda ev: print('Drawing!'))
            self._fig.stale_callback = None
    
    # ... other code

    With the MacOSX backend it prints 41 times Drawing!. With the TkAgg backend only three times.

    Adding self._fig.stale_callback = None does not change that behavior.

  7. tacaswell commented on Nov 25, 2020

    @tacaswell
    Member

    Ok, so that confirms my guess as to why we get a blank screen and changes the question to "Why is OSX drawing 41 times?!".

  8. added this to the v3.4.0 milestone on Nov 25, 2020
  9. modified the milestones: v3.4.0, v3.5.0 on Jan 27, 2021
  10. modified the milestones: v3.5.0, v3.6.0 on Sep 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions