iPython / matplotlib Memory error with imshow #1623

Closed
eelsirhc opened this Issue Apr 18, 2012 · 6 comments

Projects

None yet

3 participants

@eelsirhc

I'm getting a MemoryError exception when using pylab.imshow with arrays of ~3000 by 6000 (16 megapixels). If I run the following code as a script it will (usually?) work fine, but if I run it using ipython and enter the plotting command interactively, I will get a memory error after a few imshow calls.

Code

    import pylab as pl
    import numpy as np

    nx = 2880
    ny = 5760
    shape = (nx,ny)
    sz = nx*ny
    data1 = np.random.rand(sz).reshape(shape)
    data2 = np.random.rand(sz).reshape(shape)
    data3 = data2-data1

    pl.clf() ; pl.imshow(data1)
    pl.clf() ; pl.imshow(data2)
    pl.clf() ; pl.imshow(data3)

Traceback

The traceback is produced after running "ipython -i testcode.py", then running the following command a few times:
pl.clf() ; pl.imshow(data1-data2)

***************************************************************************

IPython post-mortem report

{'commit_hash': '<not found>',
 'commit_source': '(none found)',
 'ipython_path': '/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/IPython',
 'ipython_version': '0.11',
 'os_name': 'posix',
 'platform': 'Darwin-11.3.0-x86_64-i386-32bit',
 'sys_executable': '/Library/Frameworks/Python.framework/Versions/7.1/Resources/Python.app/Contents/MacOS/Python',
 'sys_platform': 'darwin',
 'sys_version': '2.7.2 |EPD 7.1-2 (32-bit)| (default, Jul 27 2011, 13:29:32) \n[GCC 4.0.1 (Apple Inc. build 5493)]'}

***************************************************************************



***************************************************************************

Crash traceback:

---------------------------------------------------------------------------
MemoryErrorPython 2.7.2: /Library/Frameworks/Python.framework/Versions/7.1/Resources/Python.app/Contents/MacOS/Python
                                                   Wed Apr 18 10:15:53 2012
A problem occured executing Python code.  Here is the sequence of function
calls leading up to the error, with the most recent (innermost) call last.
/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/artist.py in draw_wrapper(artist=<matplotlib.figure.Figure object>, renderer=<matplotlib.backends.backend_macosx.RendererMac instance>, *args=(), **kwargs={})
     40         if artist.get_agg_filter() is not None:
     41             renderer.start_filter()
     42 
     43 
     44     def after(artist, renderer):
     45 
     46         if artist.get_agg_filter() is not None:
     47             renderer.stop_filter(artist.get_agg_filter())
     48 
     49         if artist.get_rasterized():
     50             renderer.stop_rasterizing()
     51 
     52     # the axes class has a second argument inframe for its draw method.
     53     def draw_wrapper(artist, renderer, *args, **kwargs):
     54         before(artist, renderer)
---> 55         draw(artist, renderer, *args, **kwargs)
        global draw = undefined
        artist = <matplotlib.figure.Figure object at 0x66300d0>
        renderer = <matplotlib.backends.backend_macosx.RendererMac instance at 0x66223c8>
        args = ()
        kwargs = {}
     56         after(artist, renderer)
     57 
     58     # "safe wrapping" to exactly replicate anything we haven't overridden above
     59     draw_wrapper.__name__ = draw.__name__
     60     draw_wrapper.__dict__ = draw.__dict__
     61     draw_wrapper.__doc__  = draw.__doc__
     62     draw_wrapper._supports_rasterization = True
     63     return draw_wrapper
     64 
     65 
     66 class Artist(object):
     67     """
     68     Abstract base class for someone who renders into a
     69     :class:`FigureCanvas`.
     70     """

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/figure.py in draw(self=<matplotlib.figure.Figure object>, renderer=<matplotlib.backends.backend_macosx.RendererMac instance>)
    783             dsu.append((self.images[0].get_zorder(), draw_composite, []))
    784 
    785         # render the axes
    786         for a in self.axes:
    787             dsu.append( (a.get_zorder(), a.draw, [renderer]))
    788 
    789         # render the figure text
    790         for a in self.texts:
    791             dsu.append( (a.get_zorder(), a.draw, [renderer]))
    792 
    793         for a in self.legends:
    794             dsu.append( (a.get_zorder(), a.draw, [renderer]))
    795 
    796         dsu.sort(key=itemgetter(0))
    797         for zorder, func, args in dsu:
--> 798             func(*args)
        func = <bound method AxesSubplot.draw of <matplotlib.axes.AxesSubplot object at 0x2a3a970>>
        args = [<matplotlib.backends.backend_macosx.RendererMac instance at 0x66223c8>]
    799 
    800         renderer.close_group('figure')
    801 
    802         self._cachedRenderer = renderer
    803 
    804         self.canvas.draw_event(renderer)
    805 
    806     def draw_artist(self, a):
    807         """
    808         draw :class:`matplotlib.artist.Artist` instance *a* only --
    809         this is available only after the figure is drawn
    810         """
    811         assert self._cachedRenderer is not None
    812         a.draw(self._cachedRenderer)
    813 

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/artist.py in draw_wrapper(artist=<matplotlib.axes.AxesSubplot object>, renderer=<matplotlib.backends.backend_macosx.RendererMac instance>, *args=(), **kwargs={})
     40         if artist.get_agg_filter() is not None:
     41             renderer.start_filter()
     42 
     43 
     44     def after(artist, renderer):
     45 
     46         if artist.get_agg_filter() is not None:
     47             renderer.stop_filter(artist.get_agg_filter())
     48 
     49         if artist.get_rasterized():
     50             renderer.stop_rasterizing()
     51 
     52     # the axes class has a second argument inframe for its draw method.
     53     def draw_wrapper(artist, renderer, *args, **kwargs):
     54         before(artist, renderer)
---> 55         draw(artist, renderer, *args, **kwargs)
        global draw = undefined
        artist = <matplotlib.axes.AxesSubplot object at 0x2a3a970>
        renderer = <matplotlib.backends.backend_macosx.RendererMac instance at 0x66223c8>
        args = ()
        kwargs = {}
     56         after(artist, renderer)
     57 
     58     # "safe wrapping" to exactly replicate anything we haven't overridden above
     59     draw_wrapper.__name__ = draw.__name__
     60     draw_wrapper.__dict__ = draw.__dict__
     61     draw_wrapper.__doc__  = draw.__doc__
     62     draw_wrapper._supports_rasterization = True
     63     return draw_wrapper
     64 
     65 
     66 class Artist(object):
     67     """
     68     Abstract base class for someone who renders into a
     69     :class:`FigureCanvas`.
     70     """

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/axes.py in draw(self=<matplotlib.axes.AxesSubplot object>, renderer=<matplotlib.backends.backend_macosx.RendererMac instance>, inframe=False)
   1931             gc = renderer.new_gc()
   1932             gc.set_clip_rectangle(self.bbox)
   1933             gc.set_clip_path(mtransforms.TransformedPath(
   1934                     self.patch.get_path(),
   1935                     self.patch.get_transform()))
   1936 
   1937             renderer.draw_image(gc, round(l), round(b), im)
   1938             gc.restore()
   1939 
   1940         if dsu_rasterized:
   1941             for zorder, a in dsu_rasterized:
   1942                 a.draw(renderer)
   1943             renderer.stop_rasterizing()
   1944 
   1945         for zorder, a in dsu:
-> 1946             a.draw(renderer)
        a.draw = <bound method AxesImage.draw of <matplotlib.image.AxesImage object at 0x87fb250>>
        renderer = <matplotlib.backends.backend_macosx.RendererMac instance at 0x66223c8>
   1947 
   1948         renderer.close_group('axes')
   1949         self._cachedRenderer = renderer
   1950 
   1951     def draw_artist(self, a):
   1952         """
   1953         This method can only be used after an initial draw which
   1954         caches the renderer.  It is used to efficiently update Axes
   1955         data (axis ticks, labels, etc are not updated)
   1956         """
   1957         assert self._cachedRenderer is not None
   1958         a.draw(self._cachedRenderer)
   1959 
   1960     def redraw_in_frame(self):
   1961         """

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/artist.py in draw_wrapper(artist=<matplotlib.image.AxesImage object>, renderer=<matplotlib.backends.backend_macosx.RendererMac instance>, *args=(), **kwargs={})
     40         if artist.get_agg_filter() is not None:
     41             renderer.start_filter()
     42 
     43 
     44     def after(artist, renderer):
     45 
     46         if artist.get_agg_filter() is not None:
     47             renderer.stop_filter(artist.get_agg_filter())
     48 
     49         if artist.get_rasterized():
     50             renderer.stop_rasterizing()
     51 
     52     # the axes class has a second argument inframe for its draw method.
     53     def draw_wrapper(artist, renderer, *args, **kwargs):
     54         before(artist, renderer)
---> 55         draw(artist, renderer, *args, **kwargs)
        global draw = undefined
        artist = <matplotlib.image.AxesImage object at 0x87fb250>
        renderer = <matplotlib.backends.backend_macosx.RendererMac instance at 0x66223c8>
        args = ()
        kwargs = {}
     56         after(artist, renderer)
     57 
     58     # "safe wrapping" to exactly replicate anything we haven't overridden above
     59     draw_wrapper.__name__ = draw.__name__
     60     draw_wrapper.__dict__ = draw.__dict__
     61     draw_wrapper.__doc__  = draw.__doc__
     62     draw_wrapper._supports_rasterization = True
     63     return draw_wrapper
     64 
     65 
     66 class Artist(object):
     67     """
     68     Abstract base class for someone who renders into a
     69     :class:`FigureCanvas`.
     70     """

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/image.py in draw(self=<matplotlib.image.AxesImage object>, renderer=<matplotlib.backends.backend_macosx.RendererMac instance>, *args=(), **kwargs={})
    339         if (self.axes.get_xscale() != 'linear' or
    340             self.axes.get_yscale() != 'linear'):
    341             warnings.warn("Images are not supported on non-linear axes.")
    342 
    343         l, b, widthDisplay, heightDisplay = self.axes.bbox.bounds
    344         gc = renderer.new_gc()
    345         gc.set_clip_rectangle(self.axes.bbox.frozen())
    346         gc.set_clip_path(self.get_clip_path())
    347 
    348         if self._check_unsampled_image(renderer):
    349             self._draw_unsampled_image(renderer, gc)
    350         else:
    351             if self._image_skew_coordinate is not None:
    352                 warnings.warn("Image will not be shown correctly with this backend.")
    353 
--> 354             im = self.make_image(renderer.get_image_magnification())
        im = undefined
        self.make_image = <bound method AxesImage.make_image of <matplotlib.image.AxesImage object at 0x87fb250>>
        renderer.get_image_magnification = <bound method RendererMac.get_image_magnification of <matplotlib.backends.backend_macosx.RendererMac instance at 0x66223c8>>
    355             if im is None:
    356                 return
    357             im._url = self.get_url()
    358             renderer.draw_image(gc, l, b, im)
    359         gc.restore()
    360 
    361     def contains(self, mouseevent):
    362         """
    363         Test whether the mouse event occured within the image.
    364         """
    365         if callable(self._contains): return self._contains(self,mouseevent)
    366         # TODO: make sure this is consistent with patch and patch
    367         # collection on nonlinear transformed coordinates.
    368         # TODO: consider returning image coordinates (shouldn't
    369         # be too difficult given that the image is rectilinear

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/image.py in make_image(self=<matplotlib.image.AxesImage object>, magnification=1.0)
    554 
    555         # image is created in the canvas coordinate.
    556         x1, x2, y1, y2 = self.get_extent()
    557         trans = self.get_transform()
    558         xy = trans.transform(np.array([(x1, y1),
    559                                        (x2, y2),
    560                                        ]))
    561         _x1, _y1 = xy[0]
    562         _x2, _y2 = xy[1]
    563 
    564         transformed_viewLim = mtransforms.TransformedBbox(self.axes.viewLim,
    565                                                           trans)
    566 
    567         im, xmin, ymin, dxintv, dyintv, sx, sy = \
    568             self._get_unsampled_image(self._A, [_x1, _x2, _y1, _y2],
--> 569                                       transformed_viewLim)
        transformed_viewLim = TransformedBbox(Bbox(array([[ -5.00000000e-01,   2.87950000e+03],
       [  5.75950000e+03,  -5.00000000e-01]])), CompositeGenericTransform(TransformWrapper(BlendedAffine2D(IdentityTransform(),IdentityTransform())), CompositeAffine2D(BboxTransformFrom(TransformedBbox(Bbox(array([[ -5.00000000e-01,   2.87950000e+03],
       [  5.75950000e+03,  -5.00000000e-01]])), TransformWrapper(BlendedAffine2D(IdentityTransform(),IdentityTransform())))), BboxTransformTo(TransformedBbox(Bbox(array([[ 0.125     ,  0.24166667],
       [ 0.9       ,  0.75833333]])), BboxTransformTo(TransformedBbox(Bbox(array([[ 0.,  0.],
       [ 8.,  6.]])), Affine2D(array([[ 80.,   0.,   0.],
       [  0.,  80.,   0.],
       [  0.,   0.,   1.]])))))))))
    570 
    571         fc = self.axes.patch.get_facecolor()
    572         bg = mcolors.colorConverter.to_rgba(fc, 0)
    573         im.set_bg( *bg)
    574 
    575         # image input dimensions
    576         im.reset_matrix()
    577         numrows, numcols = im.get_size()
    578         if numrows < 1 or numcols < 1:   # out of range
    579             return None
    580         im.set_interpolation(self._interpd[self._interpolation])
    581 
    582         im.set_resample(self._resample)
    583 
    584         # the viewport translation

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/image.py in _get_unsampled_image(self=<matplotlib.image.AxesImage object>, A=masked_array(data =
 [[ 0.04123991  0.67285343 -...       mask =
 False,
       fill_value = 1e+20)
, image_extents=[80.0, 576.0, 116.0, 364.0], viewlim=TransformedBbox(Bbox(array([[ -5.00000000e-01,  ...  80.,   0.],
       [  0.,   0.,   1.]]))))))))))
    186             sy = dyintv/viewlim.height
    187         else:
    188             yslice = slice(0, numrows)
    189 
    190         if xslice != self._oldxslice or yslice != self._oldyslice:
    191             self._imcache = None
    192             self._oldxslice = xslice
    193             self._oldyslice = yslice
    194 
    195         if self._imcache is None:
    196             if self._A.dtype == np.uint8 and len(self._A.shape) == 3:
    197                 im = _image.frombyte(self._A[yslice,xslice,:], 0)
    198                 im.is_grayscale = False
    199             else:
    200                 if self._rgbacache is None:
--> 201                     x = self.to_rgba(self._A, self._alpha)
        x = undefined
        self.to_rgba = <bound method AxesImage.to_rgba of <matplotlib.image.AxesImage object at 0x87fb250>>
        self._A = masked_array(data =
 [[ 0.04123991  0.67285343 -0.30138726 ..., -0.68485087 -0.35417419
   0.09215035]
 [ 0.28293837 -0.22191341 -0.79903377 ..., -0.47681786  0.52410916
  -0.86621049]
 [ 0.81228474 -0.34128834  0.05141819 ..., -0.21608041 -0.23106414
  -0.13715958]
 ..., 
 [-0.6178362   0.08344633 -0.27361924 ...,  0.40299445  0.55222963
  -0.40225582]
 [-0.34884671  0.09620163 -0.1711352  ...,  0.16216432 -0.18783969
  -0.76421311]
 [ 0.64917036  0.86766703  0.14287547 ..., -0.3064389   0.86533881
  -0.11360834]],
             mask =
 False,
       fill_value = 1e+20)

        self._alpha = None
    202                     self._rgbacache = x
    203                 else:
    204                     x = self._rgbacache
    205                 im = _image.fromarray(x[yslice,xslice], 0)
    206                 if len(self._A.shape) == 2:
    207                     im.is_grayscale = self.cmap.is_gray()
    208                 else:
    209                     im.is_grayscale = False
    210             self._imcache = im
    211 
    212             if self.origin=='upper':
    213                 im.flipud_in()
    214         else:
    215             im = self._imcache
    216 

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/cm.py in to_rgba(self=<matplotlib.image.AxesImage object>, x=masked_array(data =
 [[0.520608513035 0.83649106... False False False]],
       fill_value = 1e+20)
, alpha=None, bytes=False)
    179                     m, n = x.shape[:2]
    180                     xx = np.empty(shape=(m,n,4), dtype = x.dtype)
    181                     xx[:,:,:3] = x
    182                     xx[:,:,3] = _alpha
    183                 elif x.shape[2] == 4:
    184                     xx = x
    185                 else:
    186                     raise ValueError("third dimension must be 3 or 4")
    187                 if bytes and xx.dtype != np.uint8:
    188                     xx = (xx * 255).astype(np.uint8)
    189                 return xx
    190         except AttributeError:
    191             pass
    192         x = ma.asarray(x)
    193         x = self.norm(x)
--> 194         x = self.cmap(x, alpha=alpha, bytes=bytes)
        x = masked_array(data =
 [[0.520608513035 0.836491060114 0.349253816911 ..., 0.157476001103
  0.322854017203 0.546069841837]
 [0.641486744304 0.389000277705 0.100370850022 ..., 0.261517464562
  0.762101079045 0.0667744286714]
 [0.906223445837 0.329298489453 0.525698874967 ..., 0.391917479741
  0.384423816105 0.431387360172]
 ..., 
 [0.190991376147 0.541716785646 0.363141157188 ..., 0.701529192316
  0.7761646887 0.298807431842]
 [0.325518399239 0.548095970403 0.414395475326 ..., 0.581085229946
  0.4060412252 0.117785359127]
 [0.824646685833 0.933921238374 0.571438490047 ..., 0.346727392664
  0.932756847432 0.443165808154]],
             mask =
 [[False False False ..., False False False]
 [False False False ..., False False False]
 [False False False ..., False False False]
 ..., 
 [False False False ..., False False False]
 [False False False ..., False False False]
 [False False False ..., False False False]],
       fill_value = 1e+20)

        self.cmap = <matplotlib.colors.LinearSegmentedColormap instance at 0x8a9a990>
        alpha = None
        bytes = False
    195         return x
    196 
    197     def set_array(self, A):
    198         'Set the image array from numpy array *A*'
    199         self._A = A
    200         self.update_dict['array'] = True
    201 
    202     def get_array(self):
    203         'Return the array'
    204         return self._A
    205 
    206     def get_cmap(self):
    207         'return the colormap'
    208         return self.cmap
    209 

/Library/Frameworks/Python.framework/Versions/7.1/lib/python2.7/site-packages/matplotlib/colors.py in __call__(self=<matplotlib.colors.LinearSegmentedColormap instance>, X=masked_array(data =
 [[0.520608513035 0.83649106... False False False]],
       fill_value = 1e+20)
, alpha=None, bytes=False)
    536             lut = self._lut.copy() # Don't let alpha modify original _lut.
    537 
    538         if alpha is not None:
    539             alpha = min(alpha, 1.0) # alpha must be between 0 and 1
    540             alpha = max(alpha, 0.0)
    541             if (lut[-1] == 0).all():
    542                 lut[:-1, -1] = alpha
    543                 # All zeros is taken as a flag for the default bad
    544                 # color, which is no color--fully transparent.  We
    545                 # don't want to override this.
    546             else:
    547                 lut[:,-1] = alpha
    548                 # If the bad value is set to have a color, then we
    549                 # override its alpha just as for any other value.
    550 
--> 551         rgba = np.empty(shape=xa.shape+(4,), dtype=lut.dtype)
        rgba = undefined
        global np.empty = <built-in function empty>
        global shape = undefined
        xa.shape = (2880, 5760)
        global dtype = undefined
        lut.dtype = dtype('float64')
    552         lut.take(xa, axis=0, mode='clip', out=rgba)
    553                     #  twice as fast as lut[xa];
    554                     #  using the clip or wrap mode and providing an
    555                     #  output array speeds it up a little more.
    556         if vtype == 'scalar':
    557             rgba = tuple(rgba[0,:])
    558         return rgba
    559 
    560     def set_bad(self, color = 'k', alpha = None):
    561         '''Set color to be used for masked values.
    562         '''
    563         self._rgba_bad = colorConverter.to_rgba(color, alpha)
    564         if self._isinit: self._set_extremes()
    565 
    566     def set_under(self, color = 'k', alpha = None):

MemoryError: 

***************************************************************************

History of session input:pl.clf() ; pl.imshow(data1-data2)pl.clf() ; pl.imshow(data1-data2)pl.clf() ; pl.imshow(data1-data2)
*** Last line of input (may not be in above history):
pl.clf() ; pl.imshow(data1-data2)
@eelsirhc

I notice that during the construction of the image the array 'rgba' is created as nx * ny * 4 * (64 bits), which for a 16 million point image is 500 megabytes, which could start to impact the 8GB free memory I have if the memory isn't being freed fast enough/ at all.

@ivanov
Member
ivanov commented Apr 18, 2012

This likely has to do with the fact that since you don't have semicolons at the end of your calls to imshow, you get back instances of the axes image object from matplotlib in your Out[] array (output cache) in ipython.

In [7]: plt.imshow(np.random.rand(10,10))
Out[7]: <matplotlib.image.AxesImage at 0x3b78e90>

In [8]: plt.imshow(np.random.rand(10,10));

Basically, without the semicolon, you can at a later point refer to the object returned after running the code for In[7] by either Out[7] or _7 which are equivalent. Depending on the version of IPython you're running, you may be able to run %reset out to flush out this output cache. Also, you can disable the caching via the --cache-size parameter (run ipython --help to read about it.

Note that if you really find yourself doing this a lot, you'll likely get better performance (and more sensible memory usage) by getting a handle on the AxesImage returned after the first call to imshow, and then using that instance's set_data method to replace the data it has

@takluyver
Member

I'm going to close this since, as Paul explained, the output caching is a feature. We know it causes problems in some situations like this, and there are a few options to work round it or disable it. If you think we can improve on those options, or document them better, please let us know. Thanks for taking the time to report it.

@takluyver takluyver closed this Apr 18, 2012
@eelsirhc

Thanks Paul. Using the semi-colon allows me to run the same imshow command (seemingly) as many times as I want.
Thanks also for the set_data information. I apparently need to call draw to update the plot but it also works perfectly.

I'm happy for you to close this issue. Apart from me not knowing that ipython cached objects like this, this issue is clearly a case of user error!

I did a quick search for "ipython caching" and the first result is essentially my error from a year ago. Had I known that caching was the cause of the problem I could have found the solution, but I probably would have been further confused by the documentation for version 0.9 (the first ipython result on Google) which notes

Put a ‘;’ at the end of a line to suppress the printing of output. This is useful when doing calculations which generate
long output you are not interested in seeing. The _* variables and the Out[] list do get updated with the contents
of the output, even if it is not printed. You can thus still access the generated results this way for further processing.

This suggests to me that the presence of the semi-colon only controls whether repr or str is called on the returned object, not whether it is stored. The 0.12 documentation notes the configuration option cache_size but not the semi-colon behavior (in http://ipython.org/ipython-doc/rel-0.12/interactive/reference.html#output-caching-system)

@takluyver
Member

In fact it is documented, although it doesn't mention memory use. I'll
push a quick update to add to it.

http://ipython.org/ipython-doc/rel-0.12/interactive/tips.html#suppress-output

@takluyver
Member

Added that detail in b2d5229.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment