61

I'm working on some computer vision algorithm and I'd like to show how a numpy array changes in each step.

What works now is that if I have a simple imshow( array ) at the end of my code, the window displays and shows the final image.

However what I'd like to do is to update and display the imshow window as the image changes in each iteration.

So for example I'd like to do:

import numpy as np
import matplotlib.pyplot as plt
import time

array = np.zeros( (100, 100), np.uint8 )

for i in xrange( 0, 100 ):
    for j in xrange( 0, 50 ):
        array[j, i] = 1

        #_show_updated_window_briefly_
        plt.imshow( array )
        time.sleep(0.1)

The problem is that this way, the Matplotlib window doesn't get activated, only once the whole computation is finished.

I've tried both native matplotlib and pyplot, but the results are the same. For plotting commands I found an .ion() switch, but here it doesn't seem to work.

Q1. What is the best way to continuously display updates to a numpy array (actually a uint8 greyscale image)?

Q2. Is it possible to do this with an animation function, like in the dynamic image example? I'd like to call a function inside a loop, thus I don't know how to achieve this with an animation function.

hyperknot
  • 13,454
  • 24
  • 98
  • 153
  • 1
    It may depend on which backend you use, but try calling at least one `show()` or `draw()` before starting your loop -- See this [answer](http://stackoverflow.com/questions/2130913/no-plot-window-in-matplotlib). – Bonlenfum Jul 24 '13 at 13:29
  • this worked for me https://stackoverflow.com/questions/51520143/update-matplotlib-image-in-a-function – Esteban M. Correa Oct 05 '20 at 01:25

6 Answers6

55

You don't need to call imshow all the time. It is much faster to use the object's set_data method:

myobj = imshow(first_image)
for pixel in pixels:
    addpixel(pixel)
    myobj.set_data(segmentedimg)
    draw()

The draw() should make sure that the backend updates the image.

UPDATE: your question was significantly modified. In such cases it is better to ask another question. Here is a way to deal with your second question:

Matplotlib's animation only deals with one increasing dimension (time), so your double loop won't do. You need to convert your indices to a single index. Here is an example:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

nx = 150
ny = 50

fig = plt.figure()
data = np.zeros((nx, ny))
im = plt.imshow(data, cmap='gist_gray_r', vmin=0, vmax=1)

def init():
    im.set_data(np.zeros((nx, ny)))

def animate(i):
    xi = i // ny
    yi = i % ny
    data[xi, yi] = 1
    im.set_data(data)
    return im

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=nx * ny,
                               interval=50)
Pugsley
  • 1,146
  • 14
  • 14
tiago
  • 22,602
  • 12
  • 72
  • 88
  • 1
    Unfortunately it doesn't work, the same thing happens. Maybe I should use the animation functions, like in the dynamic image example: http://matplotlib.org/examples/animation/dynamic_image.html but I don't know how can I transform that into a loop-based code. – hyperknot Jul 24 '13 at 15:33
  • @zsero: if the simpler version doesn't work, I wonder if the more complex animations will work. I've just added an example that works for me (matplotlib 1.2), see if it works for you. – tiago Jul 24 '13 at 15:53
  • 4
    Just tried to modify your example and I think `im = imshow(data, ...)` should read `im = plt.imshow(data, ...)`. Also, in order to run the animation you need `plt.show()`. Cheers – Chrigi Apr 07 '14 at 16:19
  • @Chrigi you are right. I usually have `--pylab`, so I didn't see the problem. Just updated the answer. – tiago Apr 07 '14 at 16:46
14

I struggled to make it work because many post talk about this problem, but no one seems to care about providing a working example. In this case however, the reasons were different :

  • I couldn't use Tiago's or Bily's answers because they are not in the same paradigm as the question. In the question, the refresh is scheduled by the algorithm itself, while with funcanimation or videofig, we are in an event driven paradigm. Event driven programming is unavoidable for modern user interface programming, but when you start from a complex algorithm, it might be difficult to convert it to an event driven scheme - and I wanted to be able to do it in the classic procedural paradigm too.
  • Bub Espinja reply suffered another problem : I didn't try it in the context of jupyter notebooks, but repeating imshow is wrong since it recreates new data structures each time which causes an important memory leak and slows down the whole display process.

Also Tiago mentioned calling draw(), but without specifying where to get it from - and by the way, you don't need it. the function you really need to call is flush_event(). sometime it works without, but it's because it has been triggered from somewhere else. You can't count on it. The real tricky point is that if you call imshow() on an empty table, you need to specify vmin and vmax or it will fail to initialize it's color map and set_data will fail too.

Here is a working solution :

IMAGE_SIZE = 500
import numpy as np
import matplotlib.pyplot as plt


plt.ion()

fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
fig3, ax3 = plt.subplots()

# this example doesn't work because array only contains zeroes
array = np.zeros(shape=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
axim1 = ax1.imshow(array)

# In order to solve this, one needs to set the color scale with vmin/vman
# I found this, thanks to @jettero's comment.
array = np.zeros(shape=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
axim2 = ax2.imshow(array, vmin=0, vmax=99)

# alternatively this process can be automated from the data
array[0, 0] = 99 # this value allow imshow to initialise it's color scale
axim3 = ax3.imshow(array)

del array

for _ in range(50):
    print(".", end="")
    matrix = np.random.randint(0, 100, size=(IMAGE_SIZE, IMAGE_SIZE), dtype=np.uint8)
    
    axim1.set_data(matrix)
    fig1.canvas.flush_events()
    
    axim2.set_data(matrix)
    fig1.canvas.flush_events()
    
    axim3.set_data(matrix)
    fig1.canvas.flush_events()
print()

UPDATE : I added the vmin/vmax solution based on @Jettero's comment (I missed it at first).

Camion
  • 1,264
  • 9
  • 22
  • tested your code in Jupyter Lab with `sleep(0.1)` added in the `for` loop. It seems, need `plt.draw()` in the `for` loop in order to img to update. What's bizzar is that, it will only loop 25 times. I am guessing that the `draw()` is considerred an triggering event that triggers the nex flush and draw...? – eliu Jan 21 '22 at 22:21
  • If I remember correctly (i didn't play with this for months), the problem with `draw()`, is that it creates new objects atop the others, so using it in a loop without removing the previous objects is creating a memory leak which is probably the reason why your program crashes after 25 iterations. However, I couldn't tell you much more, because I'm not a Jupyter Lab user. I only wrote and tested this code with the regular python interpreter and Idle on linux. – Camion Jan 23 '22 at 11:38
  • not crashing, but it only redraw 25 times, meaning the loop is 50times, but the refresh is only 25 times – eliu Jan 23 '22 at 17:33
  • This didn't work for me, but `fg.canvas.draw(); fg.canvas.flush_events()` did. Python 3.10.4, matplotlib 3.5.2 from with Jupyter-lab. – MRule Aug 04 '22 at 15:12
  • This solution does not apply to jupyter. – Camion Aug 05 '22 at 17:26
9

If you are using Jupyter, maybe this answer interests you. I read in this site that the emmbebed function of clear_output can make the trick:

%matplotlib inline
from matplotlib import pyplot as plt
from IPython.display import clear_output

plt.figure()
for i in range(len(list_of_frames)):
    plt.imshow(list_of_frames[i])
    plt.title('Frame %d' % i)
    plt.show()
    clear_output(wait=True)

It is true that this method is quite slow, but it can be used for testing purposes.

Bub Espinja
  • 4,029
  • 2
  • 29
  • 46
  • I mentionned it in my reply, but repeating imshow is wrong since it recreates new data structures each time which causes an important memory leak and slows down the whole display process. – Camion Mar 20 '21 at 18:23
3

I implemented a handy script that just suits your needs. Try it out here

An example that shows images in a custom directory is like this:

  import os
  import glob
  from scipy.misc import imread

  img_dir = 'YOUR-IMAGE-DIRECTORY'
  img_files = glob.glob(os.path.join(video_dir, '*.jpg'))

  def redraw_fn(f, axes):
    img_file = img_files[f]
    img = imread(img_file)
    if not redraw_fn.initialized:
      redraw_fn.im = axes.imshow(img, animated=True)
      redraw_fn.initialized = True
    else:
      redraw_fn.im.set_array(img)
  redraw_fn.initialized = False

  videofig(len(img_files), redraw_fn, play_fps=30)
Bily
  • 751
  • 6
  • 15
  • He is not asking about updating a plot, but a 2D image – Prof Huster Dec 06 '19 at 22:00
  • 1
    The provided script is general. You can use it to update any artists drawn by matplotlib. Examples are updated to show how to update 2D images. I have used it for 3 years and it never fails me. Hopefully, it can help others. @ProfHuster – Bily Dec 09 '19 at 02:36
1

I had a similar problem - want to update image, don't want to repeatedly replace the axes, but plt.imshow() (nor ax.imshow()) was not updating the figure displayed.

I finally discovered that some form of draw() was required. But fig.canvas.draw(), ax.draw() ... all did not work. I finally found the solution here:

%matplotlib notebook  #If using Jupyter Notebook
import matplotlib.pyplot as plt
import numpy as np

imData    = np.array([[1,3],[3,1]])

  # Setup and plot image
fig = plt.figure()
ax  = plt.subplot(111)
im  = ax.imshow(imData)

  # Change image contents
newImData = np.array([[2,2],[2,2]])
im.set_data( newImData )
im.draw()
Owen
  • 448
  • 3
  • 11
  • 2
    I'm using Python3 version 3.10.4 with matplotlib 3.5.2, and this no longer works: im.draw() [and ax.draw()] both want an argument "renderer", but I can't find clear documentation on how to retrieve said object. – MRule Aug 04 '22 at 15:09
0
import numpy as np
import matplotlib.pyplot as plt
k = 10
plt.ion()
array = np.zeros((k, k))
for i in range(k):
    for j in range(k):
        array[i, j] = 1
        plt.imshow(array)
        plt.show()
        plt.pause(0.001)
        plt.clf()
  • 11
    Your answer could be improved by adding an explanation which will educate and inform. Code only answers do not make clear why this solution is better than any other. Please [edit] to add more details. – Brian Tompsett - 汤莱恩 Jan 28 '22 at 09:17