Can I do something like imsave() with text overlay

2019-03-01 14:38发布

问题:

I am using imsave() sequentially to make many PNGs that I will combine as an AVI and I would like to add moving text annotations. I use ImageJ to make AVIs or GIFs.

I don't want the axes, numbers, borders or anything, just the color image (as imsave() provides for example) with text (and maybe arrows) inside. These will change frame by frame. Pardon the use of jet.

I could use savefig() with ticks off and then do cropping as post processing, but is there a more convenient, direct, or "matplotlibithic" way to do this that wouldn't be so hard on my hard drive? (final thing will be pretty big).

A code snippet, added by request:

import numpy as np
import matplotlib.pyplot as plt

nx, ny = 101, 101

phi   = np.zeros((ny, nx), dtype = 'float')
do_me = np.ones_like(phi, dtype='bool')

x0, y0, r0 = 40, 65, 12

x = np.arange(nx, dtype = 'float')[None,:]
y = np.arange(ny, dtype = 'float')[:,None]
rsq = (x-x0)**2 + (y-y0)**2

circle = rsq <= r0**2

phi[circle] = 1.0
do_me[circle] = False

do_me[0,:], do_me[-1,:], do_me[:,0], do_me[:,-1] = False, False, False, False

n, nper = 100, 100
phi_hold = np.zeros((n+1, ny, nx))
phi_hold[0] = phi

for i in range(n):

    for j in range(nper):
        phi2 = 0.25*(np.roll(phi,  1, axis=0) +
                     np.roll(phi, -1, axis=0) +
                     np.roll(phi,  1, axis=1) +
                     np.roll(phi, -1, axis=1) )

        phi[do_me] = phi2[do_me]

    phi_hold[i+1] = phi

change = phi_hold[1:] - phi_hold[:-1]

places = [(32, 20), (54,25), (11,32), (3, 12)]

plt.figure()
plt.imshow(change[50])
for (x, y) in places:
    plt.text(x, y, "WOW", fontsize=16)
plt.text(5, 95, "Don't use Jet!", color="white", fontsize=20)
plt.show()

回答1:

Method 1

Using an excellent answer to another question as a reference, I came up with the following simplified variant which seems to work nicely - just make sure the figsize (which is given in inches) aspect ratio matches the size ratio of the plot data:

import numpy as np
import matplotlib.pyplot as plt

test_image = np.eye(100)
fig = plt.figure(figsize=(4,4))
ax = plt.axes(frameon=False, xticks=[],yticks=[])
ax.imshow(test_image)
plt.savefig('test.png', bbox_inches='tight', pad_inches=0)

Note that I am using imshow with a test_image, which might behave differently from other plotting functions... please let me know in a comment in case you'd like to do something else.

Also note that the image will be (re-) sampled, so the figsize will influence the resolution of the written image.

As pointed out in the comments, the figsize setting doesn't match the size of the output image (or the size on screen, for that matter). To overcome this, use...

Method 2

Reading the FAQ entry Move the edge of an axes to make room for tick labels, I found a way to make the figsize parameter set the output image size directly, by moving the axes' ticks out of the visible area:

import numpy as np
import matplotlib.pyplot as plt

test_image = np.eye(100)
fig = plt.figure(figsize=(4,4))
ax = fig.add_axes([0,0,1,1])
ax.imshow(test_image)
plt.savefig('test.png')

Note that savefig has a default DPI setting (100 in my case) which - in combination with figsize - determines the number of pixels in x and y directions of the saved image. You can override this with the dpi keyword argument to savefig.

If you want to display the image on screen rather than saving it (by using plt.show() instead of the plt.savefig line in the code above), the size of the figure is dependent on (apart from the already familiar figsize parameter) the figure's DPI setting, which also has a default (80 on my system). This value can be overridden by passing the dpi keyword argument to the plt.figure() call.