How to return an unknown number of objects in Pyth

2019-08-15 03:02发布

问题:

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?

回答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(e[j])
        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()


回答2:

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.



回答3:

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