Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Rewrite of image infrastructure #5718

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

Merged
merged 65 commits into from
Feb 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
cf11aea
Remove Image class. Replace with resample func
mdboom Dec 22, 2015
3b7d1bb
Agg backend shouldn't do its own image interp
mdboom Dec 22, 2015
c9cae50
Update interpolation_methods.py to use viridis
mdboom Dec 22, 2015
4a05302
Remove svg.image_noscale rcParam
mdboom Dec 22, 2015
0aa222a
Rewrite image classes
mdboom Dec 22, 2015
0793aa2
Update backends for new API
mdboom Dec 22, 2015
1384c7c
Use transforms to rotate image, not hack
mdboom Dec 22, 2015
9148c7f
Update tests
mdboom Dec 22, 2015
820479e
Decrease generated code size
mdboom Dec 22, 2015
fad84c8
Remove explicit LICENSE.
mdboom Dec 22, 2015
64e84ea
Numpy 1.6 compatibility
mdboom Dec 30, 2015
0be04c0
Add Agg filter test
mdboom Dec 30, 2015
a8643f1
Fix formatting
mdboom Dec 30, 2015
5df79f0
Mark test as known fail on Numpy 1.6
mdboom Dec 30, 2015
b436f1a
Update draw_image API doc
mdboom Dec 30, 2015
dfbc39d
Fix reference counting
mdboom Dec 30, 2015
9d3b468
[skip-ci] PEP8
mdboom Dec 30, 2015
f9d8534
Update what's new
mdboom Dec 30, 2015
7b32cd8
Fix demo_annotation_box.py
mdboom Dec 30, 2015
6e60ebf
Fix #3057: Don't composite when interpolation == 'none'
mdboom Dec 30, 2015
dc66ed0
Remove broken example
mdboom Dec 30, 2015
7cccfb3
Support 16-bit grayscale
mdboom Dec 30, 2015
5073f6e
Fix permissions on demo_image_affine.py
mdboom Dec 31, 2015
625e80a
Fix alpha handling for interpolation == 'none'
mdboom Dec 31, 2015
c778cfc
Use `kind` instead of `issubclass`
mdboom Dec 31, 2015
9fe6658
Update docstring
mdboom Dec 31, 2015
d61ac2a
Revert change
mdboom Dec 31, 2015
b3318fd
Make demo_text_path.py work again
mdboom Dec 31, 2015
ffbbf05
Remove extraneous space
mdboom Dec 31, 2015
fbb55c8
Privatize self._images
mdboom Dec 31, 2015
34d6cc8
Simplify expression
mdboom Jan 1, 2016
4c5a3bf
Update docstring
mdboom Jan 1, 2016
9448208
Fix #5520: Don't round up if already rounded
mdboom Jan 5, 2016
83f4bc9
Merge pull request #17 from tacaswell/image-interpolation
mdboom Jan 4, 2016
d5030a4
Use abs() for NEAREST check
mdboom Jan 5, 2016
55c1abb
Image composite respecting z-order
mdboom Jan 5, 2016
fe376c4
Fix #4280: Preserve size when saving images
mdboom Jan 5, 2016
5266fde
Python 2.x fix
mdboom Jan 5, 2016
9285c3a
Update composite_images docstring
mdboom Jan 7, 2016
e2b2bbe
Refine can_composite
mdboom Jan 7, 2016
b894683
Improve make_image docstring
mdboom Jan 7, 2016
4e43c8b
Use X.ndim over len(X.shape)
mdboom Jan 7, 2016
8665697
Use float32 when applying alpha
mdboom Jan 7, 2016
d21a6ed
Use output.shape
mdboom Jan 7, 2016
2d2a126
Add unsampled kwarg everywhere
mdboom Jan 7, 2016
6d3518a
PEP8
mdboom Jan 7, 2016
0b8d693
Use super()
mdboom Jan 7, 2016
0f43f68
Put shape in exception
mdboom Jan 7, 2016
cd0e7e0
Case insensitive check for PNG extension
mdboom Jan 7, 2016
e62832f
Fix Numpy version check
mdboom Jan 7, 2016
e7d54d5
@cleanup unnecessary here
mdboom Jan 7, 2016
3e532e1
Make BboxImage work
mdboom Jan 7, 2016
cfad8d9
Reinstate strictness of test_imsave_color_alpha
mdboom Jan 7, 2016
e52c26f
PEP8
mdboom Jan 7, 2016
0469ab7
Fix has image test in Axes
mdboom Jan 7, 2016
b391f08
Refactor image compositing logic into a single function
mdboom Jan 7, 2016
89365c1
Properly handle NULL composite image
mdboom Jan 11, 2016
7f2f375
PEP8
mdboom Jan 11, 2016
9827d1f
Consistent handling of empty composite images
mdboom Jan 11, 2016
5be66de
Rename lena variable
mdboom Jan 29, 2016
bd521cd
Update docstring to reflect reality
mdboom Jan 29, 2016
d6b22a5
Address more minor comments in the PR
mdboom Feb 2, 2016
fff1d66
Increase tolerance on Windows
mdboom Feb 17, 2016
da6c00b
Increase tolerance of png_suite test
mdboom Feb 17, 2016
c9b2425
PEP8
mdboom Feb 17, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/api/api_changes/2015-12-30_draw_image.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Change in the ``draw_image`` backend API
----------------------------------------

The ``draw_image`` method implemented by backends has changed its interface.

This change is only relevant if the backend declares that it is able
to transform images by returning ``True`` from ``option_scale_image``.
See the ``draw_image`` docstring for more information.
18 changes: 18 additions & 0 deletions doc/users/whats_new/2015-12-30_image-interpolation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Improved image support
----------------------

Prior to version 2.0, matplotlib resampled images by first applying
the color map and then resizing the result. Since the resampling was
performed on the colored image, this introduced colors in the output
image that didn't actually exist in the color map. Now, images are
resampled first (and entirely in floating-point, if the input image is
floating-point), and then the color map is applied.

In order to make this important change, the image handling code was
almost entirely rewritten. As a side effect, image resampling uses
less memory and fewer datatype conversions than before.

The experimental private feature where one could "skew" an image by
setting the private member ``_image_skew_coordinate`` has been
removed. Instead, images will obey the transform of the axes on which
they are drawn.
14 changes: 12 additions & 2 deletions doc/users/whats_new/rcparams.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@ Configuration (rcParams)
|`svg.hashsalt` | see note |
+----------------------------+--------------------------------------------------+

``svg.hashsalt``
````````````````
Added ``svg.hashsalt`` key to rcParams
```````````````````````````````````````

If ``svg.hashsalt`` is ``None`` (which it is by default), the svg
backend uses ``uuid4`` to generate the hash salt. If it is not
``None``, it must be a string that is used as the hash salt instead of
``uuid4``. This allows for deterministic SVG output.


Removed the ``svg.image_noscale`` rcParam
`````````````````````````````````````````

As a result of the extensive changes to image handling, the
``svg.image_noscale`` rcParam has been removed. The same
functionality may be achieved by setting ``interpolation='none'`` on
individual images or globally using the ``image.interpolation``
rcParam.
28 changes: 4 additions & 24 deletions examples/api/demo_affine_image.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#!/usr/bin/env python


"""
For the backends that supports draw_image with optional affine
transform (e.g., agg, ps backend), the image of the output should
Expand All @@ -24,22 +21,15 @@ def get_image():
return Z


def imshow_affine(ax, z, *kl, **kwargs):
im = ax.imshow(z, *kl, **kwargs)
x1, x2, y1, y2 = im.get_extent()
im._image_skew_coordinate = (x2, y1)
return im


if 1:

# image rotation

fig, (ax1, ax2) = plt.subplots(1, 2)
fig, ax1 = plt.subplots(1, 1)
Z = get_image()
im1 = imshow_affine(ax1, Z, interpolation='none',
origin='lower',
extent=[-2, 4, -3, 2], clip_on=True)
im1 = ax1.imshow(Z, interpolation='none',
Copy link
Member

Choose a reason for hiding this comment

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

sigh... did we seriously have this private member documented in our examples? That kinda sends mixed messages to our users, doesn't it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it does. Not much we can do except stop doing that going forward. On the up side, the experimental method is also completely redundant to the more flexible and consistent-with-everything-else transforms infrastructure, so this isn't a regression in terms of available functionality.

origin='lower',
extent=[-2, 4, -3, 2], clip_on=True)

trans_data2 = mtransforms.Affine2D().rotate_deg(30) + ax1.transData
im1.set_transform(trans_data2)
Expand All @@ -53,13 +43,3 @@ def imshow_affine(ax, z, *kl, **kwargs):

ax1.set_xlim(-3, 5)
ax1.set_ylim(-4, 4)

# image skew

im2 = ax2.imshow(Z, interpolation='none',
origin='lower',
extent=[-2, 4, -3, 2], clip_on=True)
im2._image_skew_coordinate = (3, -2)

plt.show()
#plt.savefig("demo_affine_image")
3 changes: 2 additions & 1 deletion examples/images_contours_and_fields/interpolation_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']

np.random.seed(0)
grid = np.random.rand(4, 4)

fig, axes = plt.subplots(3, 6, figsize=(12, 6),
Expand All @@ -26,7 +27,7 @@
fig.subplots_adjust(hspace=0.3, wspace=0.05)

for ax, interp_method in zip(axes.flat, methods):
ax.imshow(grid, interpolation=interp_method)
ax.imshow(grid, interpolation=interp_method, cmap='viridis')
ax.set_title(interp_method)

plt.show()
6 changes: 4 additions & 2 deletions examples/pylab_examples/demo_annotation_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

arr = np.arange(100).reshape((10, 10))
im = OffsetImage(arr, zoom=2)
im.image.axes = ax

ab = AnnotationBbox(im, xy,
xybox=(-50., 50.),
Expand All @@ -62,9 +63,10 @@

from matplotlib._png import read_png
fn = get_sample_data("grace_hopper.png", asfileobj=False)
arr_lena = read_png(fn)
arr_img = read_png(fn)

imagebox = OffsetImage(arr_lena, zoom=0.2)
imagebox = OffsetImage(arr_img, zoom=0.2)
imagebox.image.axes = ax

ab = AnnotationBbox(imagebox, xy,
xybox=(120., -80.),
Expand Down
2 changes: 1 addition & 1 deletion extern/agg24-svn/include/agg_span_image_filter_gray.h
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ namespace agg
fg_ptr = (const value_type*)base_type::source().next_y();
}

fg >>= image_filter_shift;
fg = color_type::downshift(fg, image_filter_shift);
Copy link
Member

Choose a reason for hiding this comment

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

Has this been fed back to Agg?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, though I've received no response.

if(fg < 0) fg = 0;
if(fg > color_type::full_value()) fg = color_type::full_value();
span->v = (value_type)fg;
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ def matplotlib_fname():
'savefig.extension': ('savefig.format', lambda x: x, None),
'axes.color_cycle': ('axes.prop_cycle', lambda x: cycler('color', x),
lambda x: [c.get('color', None) for c in x]),
'svg.image_noscale': ('image.interpolation', None, None),
}

_deprecated_ignore_map = {
Expand Down
45 changes: 1 addition & 44 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2323,15 +2323,6 @@ def draw(self, renderer=None, inframe=False):
artists.remove(self._left_title)
artists.remove(self._right_title)

# add images to dsu if the backend supports compositing.
# otherwise, does the manual compositing without adding images to dsu.
if len(self.images) <= 1 or renderer.option_image_nocomposite():
_do_composite = False
else:
_do_composite = True
for im in self.images:
artists.remove(im)

if self.figure.canvas.is_saving():
dsu = [(a.zorder, a) for a in artists]
else:
Expand All @@ -2356,46 +2347,12 @@ def draw(self, renderer=None, inframe=False):
if self.axison and self._frameon:
self.patch.draw(renderer)

if _do_composite:
# make a composite image, blending alpha
# list of (mimage.Image, ox, oy)

zorder_images = [(im.zorder, im) for im in self.images
if im.get_visible()]
zorder_images.sort(key=lambda x: x[0])

mag = renderer.get_image_magnification()
ims = [(im.make_image(mag), 0, 0, im.get_alpha())
for z, im in zorder_images]

l, b, r, t = self.bbox.extents
width = int(mag * ((np.round(r) + 0.5) - (np.round(l) - 0.5)))
height = int(mag * ((np.round(t) + 0.5) - (np.round(b) - 0.5)))
im = mimage.from_images(height,
width,
ims)

im.is_grayscale = False
l, b, w, h = self.bbox.bounds
# composite images need special args so they will not
# respect z-order for now

gc = renderer.new_gc()
gc.set_clip_rectangle(self.bbox)
gc.set_clip_path(mtransforms.TransformedPath(
self.patch.get_path(),
self.patch.get_transform()))

renderer.draw_image(gc, round(l), round(b), im)
gc.restore()

if dsu_rasterized:
for zorder, a in dsu_rasterized:
a.draw(renderer)
renderer.stop_rasterizing()

for zorder, a in dsu:
a.draw(renderer)
mimage._draw_list_compositing_images(renderer, self, dsu)

renderer.close_group('axes')
self._cachedRenderer = renderer
Expand Down
11 changes: 9 additions & 2 deletions lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ def get_image_magnification(self):
"""
return 1.0

def draw_image(self, gc, x, y, im):
def draw_image(self, gc, x, y, im, trans=None):
"""
Draw the image instance into the current axes;

Expand All @@ -531,7 +531,14 @@ def draw_image(self, gc, x, y, im):
is the distance from bottom

*im*
the :class:`matplotlib._image.Image` instance
An NxMx4 array of RGBA pixels (of dtype uint8).

*trans*
If the concrete backend is written such that
`option_scale_image` returns `True`, an affine
transformation may also be passed to `draw_image`. The
backend should apply the transformation to the image
before applying the translation of `x` and `y`.
"""
raise NotImplementedError

Expand Down
17 changes: 7 additions & 10 deletions lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,9 @@ def option_image_nocomposite(self):

def option_scale_image(self):
"""
agg backend support arbitrary scaling of image.
agg backend doesn't support arbitrary scaling of image.
"""
return True
return False

def restore_region(self, region, bbox=None, xy=None):
"""
Expand Down Expand Up @@ -389,28 +389,25 @@ def post_processing(image, dpi):
# For agg_filter to work, the rendere's method need
# to overridden in the class. See draw_markers, and draw_path_collections

from matplotlib._image import fromarray

width, height = int(self.width), int(self.height)

buffer, bounds = self.tostring_rgba_minimized()

l, b, w, h = bounds


self._renderer = self._filter_renderers.pop()
self._update_methods()

if w > 0 and h > 0:
img = np.fromstring(buffer, np.uint8)
img, ox, oy = post_processing(img.reshape((h, w, 4)) / 255.,
self.dpi)
image = fromarray(img, 1)

gc = self.new_gc()
self._renderer.draw_image(gc,
l+ox, height - b - h +oy,
image)
if img.dtype.kind == 'f':
img = np.asarray(img * 255., np.uint8)
img = img[::-1]
self._renderer.draw_image(
gc, l + ox, height - b - h + oy, img)


def new_figure_manager(num, *args, **kwargs):
Expand Down
14 changes: 9 additions & 5 deletions lib/matplotlib/backends/backend_cairo.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,18 @@ def draw_image(self, gc, x, y, im):
# bbox - not currently used
if _debug: print('%s.%s()' % (self.__class__.__name__, _fn_name()))

rows, cols, buf = im.color_conv (BYTE_FORMAT)
surface = cairo.ImageSurface.create_for_data (
buf, cairo.FORMAT_ARGB32, cols, rows, cols*4)
if sys.byteorder == 'little':
im = im[:, :, (2, 1, 0, 3)]
else:
im = im[:, :, (3, 0, 1, 2)]
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to check that image is RGBA (I am assuming that that is what the 4 bytes that are getting swapped around are for, right)? I am also going to assume that the swaps are correct. I have never been able to remember byte order stuff correctly.

Copy link
Member Author

Choose a reason for hiding this comment

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

RGBA is always passed to draw_image (it's the responsibility of the other layers, not the backend, to make it so). This should be documented as well in BackendBases.draw_image.

I checked the swaps on both x86_64 and an old PPC Mac-running-Linux that I keep around to check byte order issues. Cairo is the only backend we have that requires ARGB in native byte order like this.

surface = cairo.ImageSurface.create_for_data(
memoryview(im.flatten()), cairo.FORMAT_ARGB32, im.shape[1], im.shape[0],
im.shape[1]*4)
ctx = gc.ctx
y = self.height - y - rows
y = self.height - y - im.shape[0]

ctx.save()
ctx.set_source_surface (surface, x, y)
ctx.set_source_surface(surface, float(x), float(y))
if gc.get_alpha() != 1.0:
ctx.paint_with_alpha(gc.get_alpha())
else:
Expand Down
7 changes: 2 additions & 5 deletions lib/matplotlib/backends/backend_gdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,14 @@ def draw_image(self, gc, x, y, im):
# int(w), int(h))
# set clip rect?

rows, cols, image_str = im.as_rgba_str()

image_array = np.fromstring(image_str, np.uint8)
image_array.shape = rows, cols, 4
rows, cols = im.shape[:2]

pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,
has_alpha=True, bits_per_sample=8,
width=cols, height=rows)

array = pixbuf_get_pixels_array(pixbuf)
array[:,:,:] = image_array[::-1]
array[:, :, :] = im[::-1]

gc = self.new_gc()

Expand Down
15 changes: 9 additions & 6 deletions lib/matplotlib/backends/backend_mixed.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import (absolute_import, division, print_function,
unicode_literals)

import numpy as np

from matplotlib.externals import six

from matplotlib._image import frombuffer
from matplotlib.backends.backend_agg import RendererAgg
from matplotlib.tight_bbox import process_figure_for_rasterizing

Expand Down Expand Up @@ -51,7 +52,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
# A reference to the figure is needed as we need to change
# the figure dpi before and after the rasterization. Although
# this looks ugly, I couldn't find a better solution. -JJL
self.figure=figure
self.figure = figure
self._figdpi = figure.get_dpi()

self._bbox_inches_restore = bbox_inches_restore
Expand All @@ -68,6 +69,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
draw_gouraud_triangles option_scale_image
_text2path _get_text_path_transform height width
""".split()

def _set_current_renderer(self, renderer):
self._renderer = renderer

Expand All @@ -90,7 +92,7 @@ def start_rasterizing(self):
# change the dpi of the figure temporarily.
self.figure.set_dpi(self.dpi)

if self._bbox_inches_restore: # when tight bbox is used
if self._bbox_inches_restore: # when tight bbox is used
r = process_figure_for_rasterizing(self.figure,
self._bbox_inches_restore)
self._bbox_inches_restore = r
Expand All @@ -114,12 +116,13 @@ def stop_rasterizing(self):
if self._rasterizing == 0:
self._set_current_renderer(self._vector_renderer)

width, height = self._width * self.dpi, self._height * self.dpi
height = self._height * self.dpi
buffer, bounds = self._raster_renderer.tostring_rgba_minimized()
l, b, w, h = bounds
if w > 0 and h > 0:
image = frombuffer(buffer, w, h, True)
image.is_grayscale = False
image = np.frombuffer(buffer, dtype=np.uint8)
image = image.reshape((h, w, 4))
image = image[::-1]
gc = self._renderer.new_gc()
# TODO: If the mixedmode resolution differs from the figure's
# dpi, the image must be scaled (dpi->_figdpi). Not all
Expand Down
Loading