Matplotlib.animation: how to remove white margin
Asked Answered
A

5

16

I try to generate a movie using the matplotlib movie writer. If I do that, I always get a white margin around the video. Has anyone an idea how to remove that margin?

Adjusted example from http://matplotlib.org/examples/animation/moviewriter.html

# This example uses a MovieWriter directly to grab individual frames and
# write them to a file. This avoids any event loop integration, but has
# the advantage of working with even the Agg backend. This is not recommended
# for use in an interactive setting.
# -*- noplot -*-

import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.animation as manimation

FFMpegWriter = manimation.writers['ffmpeg']
metadata = dict(title='Movie Test', artist='Matplotlib',
        comment='Movie support!')
writer = FFMpegWriter(fps=15, metadata=metadata, extra_args=['-vcodec', 'libx264'])

fig = plt.figure()
ax = plt.subplot(111)
plt.axis('off')
fig.subplots_adjust(left=None, bottom=None, right=None, wspace=None, hspace=None)
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
plt.axis('off')

with writer.saving(fig, "writer_test.mp4", 100):
    for i in range(100):
        mat = np.random.random((100,100))
        ax.imshow(mat,interpolation='nearest')
        writer.grab_frame()
Adigun answered 8/4, 2013 at 14:56 Comment(0)
S
22

Passing None as an arguement to subplots_adjust does not do what you think it does (doc). It means 'use the deault value'. To do what you want use the following instead:

fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None)

You can also make your code much more efficent if you re-use your ImageAxes object

mat = np.random.random((100,100))
im = ax.imshow(mat,interpolation='nearest')
with writer.saving(fig, "writer_test.mp4", 100):
    for i in range(100):
        mat = np.random.random((100,100))
        im.set_data(mat)
        writer.grab_frame()

By default imshow fixes the aspect ratio to be equal, that is so your pixels are square. You either need to re-size your figure to be the same aspect ratio as your images:

fig.set_size_inches(w, h, forward=True)

or tell imshow to use an arbitrary aspect ratio

im = ax.imshow(..., aspect='auto')
Socman answered 8/4, 2013 at 15:52 Comment(3)
Using your subplots_adjust only removes the margin on the top and bottom. I still have a white margin on the right and left of the video. Also somehow I could not do im.set_cdata(mat). I got the error: AttributeError: 'AxesImage' object has no attribute 'set_cdata'Adigun
awesome, that solved my problem. There are no margins left, and using set_data makes the whole process much faster indeed!Adigun
your additional comment "You can also make your code much more efficient if you re-use your ImageAxes object" really helped me. Due to this change program used less memory during animation.Aquamarine
H
3

I searched all day for this and ended up using this solution from @matehat when creating each image.

import matplotlib.pyplot as plt
import matplotlib.animation as animation

To make a figure without the frame :

fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)

To make the content fill the whole figure

ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)

Draw the first frame, assuming your movie is stored in 'imageStack':

movieImage = ax.imshow(imageStack[0], aspect='auto')

I then wrote an animation function:

def animate(i):
    movieImage.set_array(imageStack[i])
    return movieImage

anim = animation.FuncAnimation(fig,animate,frames=len(imageStack),interval=100)
anim.save('myMovie.mp4',fps=20,extra_args=['-vcodec','libx264']

It worked beautifully!

Here is the link to the whitespace removal solution:

1: remove whitespace from image

Hypervitaminosis answered 21/2, 2020 at 22:52 Comment(1)
I'd recommend to point out exactly the part that helped you and/or answers the user's question, and then reference the link :)Blatant
S
1

In a recent build of matplotlib, it looks like you can pass arguments to the writer:

def grab_frame(self, **savefig_kwargs):
        '''
        Grab the image information from the figure and save as a movie frame.
        All keyword arguments in savefig_kwargs are passed on to the 'savefig'
        command that saves the figure.
        '''
        verbose.report('MovieWriter.grab_frame: Grabbing frame.',
                       level='debug')
        try:
            # Tell the figure to save its data to the sink, using the
            # frame format and dpi.
            self.fig.savefig(self._frame_sink(), format=self.frame_format,
                dpi=self.dpi, **savefig_kwargs)
        except RuntimeError:
            out, err = self._proc.communicate()
            verbose.report('MovieWriter -- Error running proc:\n%s\n%s' % (out,
                err), level='helpful')
            raise

If this was the case, you could pass bbox_inches="tight" and pad_inches=0 to grab_frame -> savefig and this should remove most of the border. The most up to date version on Ubuntu however, still has this code:

def grab_frame(self):
    '''
    Grab the image information from the figure and save as a movie frame.
    '''
    verbose.report('MovieWriter.grab_frame: Grabbing frame.',
                   level='debug')
    try:
        # Tell the figure to save its data to the sink, using the
        # frame format and dpi.
        self.fig.savefig(self._frame_sink(), format=self.frame_format,
            dpi=self.dpi)
    except RuntimeError:
        out, err = self._proc.communicate()
        verbose.report('MovieWriter -- Error running proc:\n%s\n%s' % (out,
            err), level='helpful')
        raise

So it looks like the functionality is being put in. Grab this version and give it a shot!

Steerageway answered 8/4, 2013 at 15:23 Comment(6)
I have had really bad experience with bbox_inches='tight' as the pixel size of the grabbed frames and the pixelsize of the writer were configured with don't match and get a movie of garbage. If you want to test this, set the bbox_inches value via rcparam.Socman
@tcaswell Interesting, that sounds like a bug and should be reported. I was unable to test with the version on this computer so I tried to divine an answer from the source.Steerageway
It is not a bug, it is just a bad interaction between mp4s requiring a fixed frame size in pixels and mpl generating varying size images when it uses the tight bounding box. Controlling this better would (I think) require breaking down some of the nice abstraction barriers mpl has between the user and rendering details.Socman
I just updated to matplotlib 1.2.1 via pip. Its not included there yet. I am a bit anxious to update to git HEAD, as I need the plotting to be stable. I will wait and try at the next version.Adigun
@Adigun The alternative is to forgo the matplotlib animation writer and make the movie yourself. It really isn't too hard (esp if you're using a Unix distribution), you simply have to generate the movie as images sequentially and run something like ffmpeg over them. At that level you'll have complete control.Steerageway
yeah, I will do that now. I just thought, maybe the matplotlib approach is a bit more efficient. But as I see it, under the hood it is doing the same thing anyway.Adigun
S
0

If you "just" want to save a matshow/imshow rendering of a matrix without axis annotation then newest developer version of scikit-video (skvideo) may also be relevant, - if you have avconv installed. An example in the distribution shows a dynamic image constructed from numpy function: https://github.com/aizvorski/scikit-video/blob/master/skvideo/examples/test_writer.py

Here is my modification of the example:

# Based on https://github.com/aizvorski/scikit-video/blob/master/skvideo/examples/test_writer.py
from __future__ import print_function

from skvideo.io import VideoWriter
import numpy as np

w, h = 640, 480

checkerboard = np.tile(np.kron(np.array([[0, 1], [1, 0]]), np.ones((30, 30))), (30, 30))
checkerboard = checkerboard[:h, :w]

filename = 'checkerboard.mp4'
wr = VideoWriter(filename, frameSize=(w, h), fps=8)

wr.open()
for frame_num in range(300):
    checkerboard = 1 - checkerboard
    image = np.tile(checkerboard[:, :, np.newaxis] * 255, (1, 1, 3))
    wr.write(image)
    print("frame %d" % (frame_num))

wr.release()
print("done")
Simplistic answered 11/6, 2015 at 20:16 Comment(0)
Z
0

For those using the save() method of the Animation class, you can pass {"bbox_inches": "tight"} into the savefig_kwargs argument.

ani.save(
  filename='animation.mp4',
  writer='ffmpeg',
  savefig_kwargs={ "bbox_inches": "tight" }
)
Zulemazullo answered 28/6, 2024 at 17:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.