Fast way of saving many images with matplotlib and for loop - python

I noticed that there is a big performance gap between the following two solutions to load and save images with matplotlib. Can anyone explain why, and what is the best (and fastest) way to save images in a python for loop?
Implementation 1:
create a figure outside the for loop, update what is displayed and then save.
fig, a = plt.subplots(1, 3, figsize=(30, 20)) # <--------
# list_of_fnames is just a list of file names
for k, fname in enumerate(list_of_fnames):
with Image.open(fname) as img:
x = np.array(img)
y = process_image_fn1(x)
z = process_image_fn2(x)
a[0].imshow(x)
a[1].imshow(y)
a[2].imshow(z)
output_filename = f'results_{k}.png'
plt.savefig(output_filename, dpi=320, format='png', transparent=False, bbox_inches='tight', pad_inches=0)
Implementation 2:
create a figure inside the for loop, save it, finally destroy it.
# list_of_fnames is just a list of file names
for k, fname in enumerate(list_of_fnames):
with Image.open(fname) as img:
x = np.array(img)
y = process_image_fn1(x)
z = process_image_fn2(x)
fig, a = plt.subplots(1, 3, figsize=(30, 20)) # <--------
a[0].imshow(x)
a[1].imshow(y)
a[2].imshow(z)
output_filename = f'results_{k}.png'
plt.savefig(output_filename, dpi=320, format='png', transparent=False, bbox_inches='tight', pad_inches=0)
plt.close() # <--------

The first option could be improved in a couple of ways.
removing the previously plotted AxesImages (from imshow), so that you don't keep increasing the number of plotted images on the axes
fig, a = plt.subplots(1, 3, figsize=(30, 20)) # <--------
# list_of_fnames is just a list of file names
for k, fname in enumerate(list_of_fnames):
for ax in a:
ax.images.pop()
with Image.open(fname) as img:
x = np.array(img)
y = process_image_fn1(x)
z = process_image_fn2(x)
a[0].imshow(x)
a[1].imshow(y)
a[2].imshow(z)
output_filename = f'results_{k}.png'
plt.savefig(output_filename, dpi=320, format='png', transparent=False, bbox_inches='tight', pad_inches=0)
alternatively, create the AxesImages once per axes, then rather than replot them each iteration, use .set_array() to change what is plotted on the AxesImage
fig, a = plt.subplots(1, 3, figsize=(30, 20)) # <--------
# list_of_fnames is just a list of file names
for k, fname in enumerate(list_of_fnames):
with Image.open(fname) as img:
x = np.array(img)
y = process_image_fn1(x)
z = process_image_fn2(x)
if k == 0:
im0 = a[0].imshow(x)
im1 = a[1].imshow(y)
im2 = a[2].imshow(z)
else:
im0.set_array(x)
im1.set_array(y)
im2.set_array(z)
output_filename = f'results_{k}.png'
plt.savefig(output_filename, dpi=320, format='png', transparent=False, bbox_inches='tight', pad_inches=0)

Related

Python imshow will only display last line

I have the following code that when run separately, displays two maps:
Map1:
f = h5py.File(filename[0], 'r')
group_id='Soil_Moisture_Retrieval_Data_AM'
var_id = 'soil_moisture'
a = f[group_id][var_id][:,:]
plt.figure(figsize=(10, 10))
a[a==f[group_id][var_id].attrs['_FillValue'].astype(int)]==np.nan;
plt.imshow(a,vmin=0.,vmax=0.55, cmap = 'terrain_r');
cbar = plt.colorbar(orientation='horizontal')
cbar.set_label('$cm^3 cm^{-3}$')
Map2:
f1 = h5py.File(filename[1], 'r')
b = f1[group_id][var_id][:,:]
b[b==f1[group_id][var_id].attrs['_FillValue'].astype(int)]==np.nan;
plt.figure(figsize=(10, 10))
plt.imshow(b,vmin=0.,vmax=0.55, cmap = 'terrain_r');
cbar = plt.colorbar(orientation='horizontal')
cbar.set_label('$cm^3 cm^{-3}$')
Trying to overlay the two maps will only display the last line (in this case, b):
plt.figure(figsize=(10, 10))
plt.imshow(a,vmin=0.,vmax=0.55, cmap = 'terrain_r')
plt.imshow(b,vmin=0.,vmax=0.55, cmap = 'terrain_r')
What am I possibly missing? Happy to provide more info if needed...

Removing white spaces, axis and all the numbers. Just want to see images next to each other

i have a problem where i cannot figure out how to place images next to each other in irregular grid and remove all the grid liens, axes and values.
import os
import matplotlib.pyplot as plt
# directory and single image size in the grid
images_dir = '../Desktop/test' # source directory
result_grid_filename = '../Desktop/grid2.jpg' # destination directory and file name
result_figsize_resolution = 100 # single image size
images_list = os.listdir(images_dir)
images_count = len(images_list)
print('Images count: ', images_count)
Nrows = 3
Ncolumes = 6
# Create plt plot:
fig, ax = plt.subplots(Nrows, Ncolumes, figsize=(result_figsize_resolution, result_figsize_resolution))
current_file_number = 0
ax = []
for image_filename in images_list:
img = plt.imread(images_dir + '/' + images_list[current_file_number])
# create subplot and append to ax
ax.append(fig.add_subplot(Nrows, Ncolumes, current_file_number + 1))
plt.imshow(img)
plt.axis('off')
current_file_number += 1
plt.subplots_adjust(wspace=0, hspace=0, left=0.0, right=1.0, bottom=0.0, top=1.0)
plt.savefig(result_grid_filename, bbox_inches="tight")

Updating Image with Matplotlib

i'm trying to update three of the plots in my subplot. I think the problem is that the old image isn't cleared, so the new one is plotted on top of the old one..but i`m not really sure..maybe there is also another problem. But it's possible to plot a new image with cv2.imshow()...problem is that i need to plot multiple images in a subplot. How can i fix it ?
Depending on the position of the slider the mask is changing.
Thanks a lot!
img.fig2, img.ax2 = plt.subplots()
for i in range(3):
plt.subplot(2, 3, i + 1)
plt.imshow(img[i])
plt.xticks([])
plt.yticks([])
def update (val):
...........
for i in range(3):
res[i] = cv2.cvtColor(res[i], cv2.COLOR_HSV2RGB)
fig2 = plt.figure(2)
fig2.add_subplot(2, 3, 4 + i)
plt.imshow(res[i])
plt.xticks([])
plt.yticks([])
plt.draw()
plt.show()
whole code of interest:
fig2, ax2 = plt.subplots()
for i in range(3):
plt.subplot(2, 3, i + 1)
plt.imshow(img[i])
plt.xticks([])
plt.yticks([])
plt.pause(0.1)
def update(val):
hsv1 = cv2.cvtColor(img1, cv2.COLOR_BGR2HSV)
hsv2 = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)
hsv3 = cv2.cvtColor(img3, cv2.COLOR_BGR2HSV)
l_b = np.array([shl.val, ssl.val, svl.val])
u_b = np.array([shu.val, ssu.val, svu.val])
mask1 = cv2.inRange(hsv1, l_b, u_b)
mask2 = cv2.inRange(hsv2, l_b, u_b)
mask3 = cv2.inRange(hsv3, l_b, u_b)
res1 = cv2.bitwise_and(img1, img1, mask=mask1)
res2 = cv2.bitwise_and(img2, img2, mask=mask2)
res3 = cv2.bitwise_and(img3, img3, mask=mask3)
res = [res1, res2, res3]
plt.clf()
for i in range(3):
res[i] = cv2.cvtColor(res[i], cv2.COLOR_HSV2RGB)
fig2 = plt.figure(2)
fig2.add_subplot(2, 3, 4 + i)
plt.imshow(res[i])
plt.xticks([])
plt.yticks([])
fig2.canvas.draw_idle()
plt.pause(0.01)
plt.draw_all()
shl.on_changed(update)
ssl.on_changed(update)
svl.on_changed(update)
shu.on_changed(update)
ssu.on_changed(update)
svu.on_changed(update)
plt.show()
You're right about imshow() : it adds new images on top of the existing ones and you need to call it only once before your loop. However, it also sets the color table and you'll need to provide it with a way to know the data range. Then you can call set_data() on the AxesImage imshow returned and you will need to call flush_events() to update the screen.
Sometimes flush_events() will happen transparently because another plot activates it, but you need to make sure it happens at least once.
Here is a working solution (link)

Overlapping text when saving multiple Matplotlib images with text in a loop

I created this randomly-valued/coloured 'chessboard':
with the code:
rndm = np.random.rand(8,8)
my_cmap = plt.get_cmap('gray')
plt.figure(figsize=(5,4))
plt.imshow(rndm, cmap=my_cmap, interpolation = 'none')
plt.axis('off')
plt.show()
I wanted to apply a function to the values in a loop, for example raising all values in the array to an increasing power, and then show all in different subplots. Here's the code:
fig, axs = plt.subplots(2,2, figsize=(10, 10))
axs = axs.ravel()
for i, n in zip(np.arange(4), np.arange(2,6)):
axs[i].imshow(np.power(rndm, n), cmap=my_cmap, interpolation = 'none')
axs[i].axis('off')
axs[i].text(1, 1, str(n), fontsize=14, color = 'y')
and the result:
But what I would really like is to cycle through 4 colormaps, for example:
cmaps = ['viridis', 'inferno', 'plasma', 'magma']
fig, axs = plt.subplots(2,2, figsize=(10, 10))
axs = axs.ravel()
for i, n in zip(np.arange(4), np.arange(2,6)):
axs[i].imshow(np.power(rndm, n), cmap=cmaps[i], interpolation = 'none')
axs[i].axis('off')
axs[i].text(1, 1, str(n), fontsize=14, color = 'y')
Good. But here's where my code breaks. I want to save these as individual images.
I wrote this, which is fine except for the fact that the text numbers end up superimposed:
for i, n in zip(np.arange(4), np.arange(2,6)):
plt.imshow(np.power(rndm, n), cmap=cmaps[i], interpolation = 'none')
plt.axis('off')
plt.text(1, 1, str(n), fontsize=14, color = 'y')
plt.savefig("test_n = " + str(n) +".png", dpi=300, bbox_inches='tight', pad_inches=0)
How do I clear them each time?
Shouldn't you use cmaps[i] instead of cmap=my_cmap?
Create a figure in each loop using plt.figure() and it'll work correctly (tested).
for i, n in zip(np.arange(4), np.arange(2,6)):
plt.figure()
plt.imshow(np.power(rndm, n), cmap=cmaps[i], interpolation = 'none')
plt.axis('off')
plt.text(1, 1, str(n), fontsize=14, color = 'y')
plt.savefig("test_n = " + str(n) +".png", dpi=300, bbox_inches='tight', pad_inches=0)
result:
The main image (not saved) :
-Saved images:
Note that the labels starts from 2 because of this line :
plt.text(1, 1, str(n), fontsize=14, color = 'y')
If you want it start from 1 , change str(n) to str(i).

matplotlib for loop to show, save and redraw all plots

Here's my python code,
import numpy as np
import matplotlib.pyplot as plt
from pylab import *
from matplotlib.pyplot import savefig
a = np.genfromtxt('do_cv.csv', skiprows = 1, delimiter = ',')
for i in xrange(2):
t = a[i+1:(i+1)*60, 2]
z = a[i+1:(i+1)*60, 3]
est_z = a[i+1:(i+1)*60, 6]
figure(i+1)
plt.plot(t, z, 'bo-', t, est_z, 'go-')
plt.xlabel('time')
plt.ylabel('data value')
plt.grid(True)
plt.legend(['sample data', 'estimated sample data'])
plt.savefig('test + str(i).png')
plt.show()
then 2 windows come out, like this,
figure 2 contains plots of figure 1, how to redraw the plot before the second loop begins?
And I only got 1 png file saved in my folder.
How to modify my code and get the result I want? Please give me some suggestions, thanks a lot.
You should write your self a helper function:
def my_plotter(ax, t, z, est_z):
ln1 = ax.plot(t, z, 'bo-', label='sample data')
ln2 = ax.plot(t, est_z, 'go-', label='estimated sample data')
ax.xlabel('time')
ax.ylabel('data value')
ax.grid(True)
ax.legend()
return ln1 + ln2
for i in xrange(2):
# get the data
t = a[i+1:(i+1)*60, 2]
z = a[i+1:(i+1)*60, 3]
est_z = a[i+1:(i+1)*60, 6]
# make the figure
fig, ax = plt.subplots()
# do the plot
my_plotter(ax, t, z, est_Z)
# save
fig.savefig('test_{}.png'.format(i))
Now if you decide you want to put both of these is one figure as sub-plots, all you have to do is:
# make one figure with 2 axes
fig, ax_lst = plt.subplots(1, 2)
for i, ax in zip(xrange(2), ax_lst):
# get the data
t = a[i+1:(i+1)*60, 2]
z = a[i+1:(i+1)*60, 3]
est_z = a[i+1:(i+1)*60, 6]
# do the plot
my_plotter(ax, t, z, est_Z)
# save the figure with both plots
fig.savefig('both.png')
You overwrite your png file every iteration of the loop, that's why you only have one.
plt.savefig('test + str(i).png')
Should be
plt.savefig('test ' + str(i) + '.png')

Categories