1

I am currently trying to animate a series of images where for each image an initially unknown number of ellipses are drawn. I have tried many things so far, but haven't found a solution yet, though I guess I came close. Here is my code:

import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse

def plot_images(img1, img2, objects, ax):

    im1 = ax.imshow(img1)
    im2 = ax.imshow(img2 with transparency as an overlay)

    # plotting an ellipse for each object
    e = [None]*len(objects)
    for j in range(len(objects)):
        e[j] = Ellipse(xy=(objects['x'][j], objects['y'][j]),
                            width=6 * objects['a'][j],
                            height=6 * objects['b'][j],
                            angle=objects['theta'][j] * 180. / np.pi)
        e[j].set_facecolor('none')
        e[j].set_edgecolor('red')
        ax.add_artist(e[j])

    return im1, im2, e


def animate(j):

    # extracting objects
    im1, im2, objects = object_finder_function()

    imm1, imm2, e = plot_images(im1, im2, objects, axs)


    return imm1, imm2, e

fig, axs = plt.subplots()
ani = animation.FuncAnimation(fig, animate, frames=image_number, interval=50, blit=True)
plt.show()

Now when I try this code, I get the following error message:

AttributeError: 'list' object has no attribute 'get_zorder'

So I tried different things, but ultimately, I found that when, as a test, I put in the plot_images function

return im1, im2, e[0], e[1], e[2]

and also change the animate function accordingly, i.e.

imm1, imm2, e0, e1, e2 = plot_images(im1, im2, objects, axs)

and

return imm1, imm2, e0, e1, e2

I don't get an error message and the ellipses are actually plotted in the respective frames as I intended. Now the problem is, that for one, there are many hundred ellipses per image that I would like to plot, so I would have to manually write that all down (i.e. e[0], e[1], e[2] -- e[k], and the same for the animate function) and this doesn't seem to be the right way. The other thing is that as I already said the number of ellipses changes for each image and is not previously known so I cannot possibly adjust the functions accordingly.

How can I return this list of ellipses so that the animation reads it as if I would have written them all down separately as it is done in the working example?

mapf
  • 1,906
  • 1
  • 14
  • 40

3 Answers3

1

It sounds like you want to flatten e.

You can either create a list with the already flat variables and extend it with e:

    return tuple([im1, im2] + e)

Or unpack e everywhere you want to use it.

  • Thank you all guys! You were really quick. Amazing. Apparently I made a pretty obvious mistake (sorry, I am new to this) and you all pinned it down. I made changes according to your suggestions and it now works as intended (though I still have to learn about what I actually did there). I am not sure how I can reply to all of you so that you will notice so I am just going to comment this to all solutions. – mapf Nov 28 '18 at 14:43
1

Your code is a bit unpythonic, so I cleaned up it just a bit for clarity. Your AttributeError has to do with the get_zorder function, which is used in matplotlib for figuring out how to layer plots. With the things you tried I can tell you just need to unpack your list_of_ellipses at the end.

def plot_images(img1, img2, objects, ax):

    im1 = ax.imshow(img1)
    im2 = ax.imshow(img2 with transparency as an overlay)

    list_of_ellipses = []
    for j in range(len(objects)):
        my_ellipse = Ellipse(xy=(objects['x'][j], objects['y'][j]),
                        width=6 * objects['a'][j],
                        height=6 * objects['b'][j],
                        angle=objects['theta'][j] * 180. / np.pi)

        my_ellipse.set_facecolor('none')
        my_ellipse.set_edgecolor('red')
        ax.add_artist(my_ellipse)
        list_of_ellipses.append(my_ellipse)
    return im1, im2, list_of_ellipses


def animate():
    im1, im2, objects = object_finder_function()
    imm1, imm2, list_of_ellipses = plot_images(im1, im2, objects, axs)
    return (imm1, imm2)+tuple(list_of_ellipses)

fig, axs = plt.subplots()
ani = animation.FuncAnimation(fig, animate, frames=image_number, interval=50, blit=True)
plt.show()
Jaden Baptista
  • 656
  • 5
  • 16
  • Thank you all guys! You were really quick. Amazing. Apparently I made a pretty obvious mistake (sorry, I am new to this) and you all pinned it down. I made changes according to your suggestions and it now works as intended (though I still have to learn about what I actually did there). I am not sure how I can reply to all of you so that you will notice so I am just going to comment this to all solutions. – mapf Nov 28 '18 at 14:43
  • 1
    Hey @mapf if this answer solved your problem, please accept it by clicking the green checkmark. It helps others find the right answer and gives reputation to the person who helped you. Thanks! – Jaden Baptista Dec 13 '18 at 19:24
1

Assuming that you are using matplotlib.animation, animate should be returning an iterable and you are returning one that contains three objects. return imm1, imm2, e is returning a tuple of three instances. The final one is a list. You should be able to return a list instead of a tuple by changing the animate function to:

def animate(j):
    im1, im2, objects = object_finder_function()
    imm1, imm2, e = plot_images(im1, im2, objects, axs)

    return [imm1, imm2] + e

However, I would change plot_images to return a list instead. Maybe something like the following:

def create_ellipse(objects, object_idx, artists):
    ellipse = Ellipse(
        xy=(objects['x'][object_idx], objects['y'][object_idx]),
        width=(6 * objects['a'][object_idx]),
        height=(6 * objects['b'][object_idx]),
        angle=(objects['theta'][object_idx] * 180.0 / np.pi))
    ellipse.set_facecolor('none')
    ellipse.set_edgecolor('red')
    artists.add_artists(ellipse)
    return ellipse

def plot_images(img1, img2, objects, ax):
    renderables = [ax.imshow(img1),
                   ax.imshow(img2 with transparency as an overlay)]
    renderables.extend(create_ellipse(objects, idx, ax)
                       for idx in range(len(objects)))
    return renderables
D.Shawley
  • 58,213
  • 10
  • 98
  • 113
  • Thank you all guys! You were really quick. Amazing. Apparently I made a pretty obvious mistake (sorry, I am new to this) and you all pinned it down. I made changes according to your suggestions and it now works as intended (though I still have to learn about what I actually did there). I am not sure how I can reply to all of you so that you will notice so I am just going to comment this to all solutions. – mapf Nov 28 '18 at 14:42