Skip to content

[MRG+1] Fix imshow masked interpolation #8024

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 1 commit into from
Feb 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ before_install:

install:
- ccache -s
- git describe
Copy link
Member

Choose a reason for hiding this comment

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

I'm not concerned about this, but would like to make sure that you intended to have this here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did, was a bit worried that gh/travis were having caching / timeout issues and not running the code I thought it was running (turns out the problem is I had accidentally depended on dictionary ordering in 3.6). I think this is a good idea to keep around so that we can verify exactly what code travis runs for testing locally (it should be running on the merge into the target branch, when it re-sets what that merge is is not something I fully understand yet).

# Upgrade pip and setuptools. Mock has issues with the default version of
# setuptools
- |
Expand Down
50 changes: 43 additions & 7 deletions lib/matplotlib/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,9 +371,20 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
# this is to work around spurious warnings coming
# out of masked arrays.
with np.errstate(invalid='ignore'):
rgba[..., 1] = A < 0 # under data
rgba[..., 2] = A > 1 # over data
rgba[..., 3] = ~A.mask # bad data
rgba[..., 1] = np.where(A < 0, np.nan, 1) # under data
rgba[..., 2] = np.where(A > 1, np.nan, 1) # over data
# Have to invert mask, Agg knows what alpha means
# so if you put this in as 0 for 'good' points, they
# all get zeroed out
rgba[..., 3] = 1
if A.mask.shape == A.shape:
# this is the case of a nontrivial mask
mask = np.where(A.mask, np.nan, 1)
else:
# this is the case that the mask is a
# numpy.bool_ of False
mask = A.mask
# ~A.mask # masked data
A = rgba
output = np.zeros((out_height, out_width, 4),
dtype=A.dtype)
Expand Down Expand Up @@ -414,12 +425,37 @@ def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0,
# Convert back to a masked greyscale array so
# colormapping works correctly
hid_output = output
# any pixel where the a masked pixel is included
# in the kernel (pulling this down from 1) needs to
# be masked in the output
if len(mask.shape) == 2:
out_mask = np.empty((out_height, out_width),
dtype=mask.dtype)
_image.resample(mask, out_mask, t,
_interpd_[self.get_interpolation()],
True, 1,
self.get_filternorm() or 0.0,
self.get_filterrad() or 0.0)
out_mask = np.isnan(out_mask)
else:
out_mask = mask
# we need to mask both pixels which came in as masked
# and the pixels that Agg is telling us to ignore (relavent
# to non-affine transforms)
# Use half alpha as the threshold for pixels to mask.
out_mask = out_mask | (hid_output[..., 3] < .5)
output = np.ma.masked_array(
hid_output[..., 0], hid_output[..., 3] < 0.5)
# relabel under data
output[hid_output[..., 1] > .5] = -1
hid_output[..., 0],
out_mask)
# 'unshare' the mask array to
# needed to suppress numpy warning
del out_mask
invalid_mask = ~output.mask * ~np.isnan(output.data)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would & work here instead of *? Would make more sense given the boolean masks.

Copy link
Member Author

Choose a reason for hiding this comment

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

Are there performance rather than semantic advantages? I think ~(a & b) would also work and have one less temprorary.

# relabel under data. If any of the input data for
# the pixel has input out of the norm bounds,
output[np.isnan(hid_output[..., 1]) * invalid_mask] = -1
# relabel over data
output[hid_output[..., 2] > .5] = 2
output[np.isnan(hid_output[..., 2]) * invalid_mask] = 2

output = self.to_rgba(output, bytes=True, norm=False)

Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 37 additions & 3 deletions lib/matplotlib/tests/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@
from matplotlib.testing.noseclasses import KnownFailureTest
from copy import copy
from numpy import ma
import matplotlib.image as mimage
import matplotlib.colors as colors
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import numpy as np

import nose

Expand Down Expand Up @@ -60,6 +58,7 @@ def test_image_interps():
ax3.imshow(X, interpolation='bicubic')
ax3.set_ylabel('bicubic')


@image_comparison(baseline_images=['interp_nearest_vs_none'],
extensions=['pdf', 'svg'], remove_text=True)
def test_interp_nearest_vs_none():
Expand Down Expand Up @@ -757,6 +756,41 @@ def test_imshow_endianess():
ax2.imshow(Z.astype('>f8'), **kwargs)


@image_comparison(baseline_images=['imshow_masked_interpolation'],
remove_text=True, style='default')
def test_imshow_masked_interpolation():

cm = copy(plt.get_cmap('viridis'))
cm.set_over('r')
cm.set_under('b')
cm.set_bad('k')

N = 20
n = colors.Normalize(vmin=0, vmax=N*N-1)

# data = np.random.random((N, N))*N*N
data = np.arange(N*N, dtype='float').reshape(N, N)

data[5, 5] = -1

data[15, 5] = 1e5

# data[3, 3] = np.nan

data[15, 15] = np.inf

mask = np.zeros_like(data).astype('bool')
mask[5, 15] = True

data = np.ma.masked_array(data, mask)

fig, ax_grid = plt.subplots(3, 6)
for interp, ax in zip(sorted(mimage._interpd_), ax_grid.ravel()):
ax.set_title(interp)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think there's any point adding a title if remove_text is True in the decorator. Otherwise this patch looks good to me.

Copy link
Member Author

Choose a reason for hiding this comment

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

The title is there for human debuggers later (I often copy-past tests into another buffer and just run them)

Copy link
Member

Choose a reason for hiding this comment

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

👍

ax.imshow(data, norm=n, cmap=cm, interpolation=interp)
ax.axis('off')
Copy link
Member

Choose a reason for hiding this comment

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

Same as above, prseumably pointless with remove_text == True



@cleanup
def test_imshow_no_warn_invalid():
with warnings.catch_warnings(record=True) as warns:
Expand Down