-
-
Notifications
You must be signed in to change notification settings - Fork 7.8k
[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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there performance rather than semantic advantages? I think |
||
# 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) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
@@ -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(): | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there's any point adding a title if There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
ax.imshow(data, norm=n, cmap=cm, interpolation=interp) | ||
ax.axis('off') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above, prseumably pointless with |
||
|
||
|
||
@cleanup | ||
def test_imshow_no_warn_invalid(): | ||
with warnings.catch_warnings(record=True) as warns: | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).