Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed Image and Renderer pickling #3627

Merged
merged 2 commits into from Oct 10, 2014
Merged

Conversation

pelson
Copy link
Member

@pelson pelson commented Oct 9, 2014

Fixes #3614 and uqfoundation/dill#4 as well as likely fixing #3392.

ax = fig.add_subplot(1, 1, 1)
ax.plot([1, 2, 3], [1, 2, 3])

# Uncomment to debug any unpicklable objects. This is slow so is not
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keeping this comment around might be helpful...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops. I put it back in the wrong place. Still, it applies unilaterally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see it now.

tacaswell added a commit that referenced this pull request Oct 10, 2014
BUG : Fixed Image and Renderer pickling
@tacaswell tacaswell merged commit 97eb612 into matplotlib:v1.4.x Oct 10, 2014
@pelson pelson deleted the pickle_bug branch October 15, 2014 10:17
@pelson
Copy link
Member Author

pelson commented Feb 1, 2015

Received this email:

Your solution to 'pop' the _imcache attribute from the instance solves the initial pickle error. 

def __getstate__(self):
        state = super(_AxesImageBase, self).__getstate__()
        # We can't pickle the C Image cached object.
        state.pop('_imcache', None)
        return state

However, I think it causes a bug after reinstating this object by unpickling. I noticed I sometimes got the following stacktrace after a figure.canvas.draw:

File "/usr/local/lib/python2.7/dist-packages/matplotlib/backends/backend_qt5agg.py", line 148, in draw
    FigureCanvasAgg.draw(self)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/backends/backend_agg.py", line 469, in draw
    self.figure.draw(self.renderer)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/artist.py", line 59, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/figure.py", line 1079, in draw
    func(*args)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/artist.py", line 59, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/axes/_base.py", line 2092, in draw
    a.draw(renderer)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/artist.py", line 59, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/image.py", line 373, in draw
    im = self.make_image(renderer.get_image_magnification())
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/image.py", line 597, in make_image
    transformed_viewLim)
  File "/usr/local/lib/python2.7/dist-packages/matplotlib/image.py", line 206, in _get_unsampled_image
    if self._imcache is None:
AttributeError: 'AxesImage' object has no attribute '_imcache'

I think it may be due to the attribute being removed during pickling, while its value is being tested when a draw is attempted in the canvas
at line 206 in image.py:  if self._imcache is None: . This is before _imcache has been reinstated as a member of the AxesImage object. 

I guess an easy solution would be to set _imcache = None in  __getstate__ instead of popping it.

Suggesting that it may be necessary to add and _imache = None on re-initialization.

@tacaswell
Copy link
Member

@pelson I am very close to giving up on #4012 and tagging 1.4.3rc1 tonight, does this need to be dealt with first?

@pelson
Copy link
Member Author

pelson commented Feb 1, 2015

does this need to be dealt with first?

Not necessarily. It is an easy fix though. Give me 5 minutes and I'll push up a PR.

anntzer added a commit to anntzer/matplotlib that referenced this pull request Jul 24, 2016
Upon drawing, Line2D objects would store a reference to one of their own
bound methods as their `_lineFunc` argument.  This would lead to them
being gc'ed not when going out of scope, but only when the "true" gc
kicks in; additionally this led to some pickle-related bugs (matplotlib#3627).

One can easily sidestep this problem by not storing this bound method.

To check the behavior, try (py3.4+ only):

```
import gc
import weakref
from matplotlib import pyplot as plt

def f():
    fig, ax = plt.subplots()
    img = ax.imshow([[0, 1], [2, 3]])
    weakref.finalize(img, print, "gc'ing image")
    l, = plt.plot([0, 1])
    weakref.finalize(l, print, "gc'ing line")
    fig.canvas.draw()
    img.remove()
    l.remove()

f()
print("we have left the function")
gc.collect()
print("and cleaned up our mess")
```

Before the patch, the AxesImage is gc'ed when the function exits but the
Line2D only upon explicit garbage collection.  After the patch, both are
collected immediately.
anntzer added a commit to anntzer/matplotlib that referenced this pull request Jul 24, 2016
Upon drawing, Line2D objects would store a reference to one of their own
bound methods as their `_lineFunc` argument.  This would lead to them
being gc'ed not when going out of scope, but only when the "true" gc
kicks in; additionally this led to some pickle-related bugs (matplotlib#3627).

One can easily sidestep this problem by not storing this bound method.

To check the behavior, try (py3.4+ only):

```
import gc
import weakref
from matplotlib import pyplot as plt

def f():
    fig, ax = plt.subplots()
    img = ax.imshow([[0, 1], [2, 3]])
    weakref.finalize(img, print, "gc'ing image")
    l, = plt.plot([0, 1])
    weakref.finalize(l, print, "gc'ing line")
    fig.canvas.draw()
    img.remove()
    l.remove()

f()
print("we have left the function")
gc.collect()
print("and cleaned up our mess")
```

Before the patch, the AxesImage is gc'ed when the function exits but the
Line2D only upon explicit garbage collection.  After the patch, both are
collected immediately.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants