Skip to content

Image interpolation wrong for pixel values exceeding vmax  #8631

Closed
@geggo

Description

@geggo

Bug report

Bug summary

With imshow(), image interpolation gives wrong results near pixels with values exceeding vmax. A large rectangular region (given by the interpolation kernel size) is filled with color corresponding to maximum value.

Code for reproduction

t = zeros((50, 50))
t[10, 10] = 1
t[10, 20] = 2
imshow(t, vmin=0, vmax=1, interpolation='lanczos')

see gist

Actual outcome

download 2

Expected outcome

Ok with matplotlib 2.0.0

download

Matplotlib version

  • Operating System: macOS
  • Matplotlib Version: 2.0.1, 2.0.2
  • Python Version: 2.7
  • Jupyter Version (if applicable):
  • Other Libraries:

matplotlib installed via conda-forge

Activity

added this to the 2.0.3 (next bug fix release) milestone on May 16, 2017
dstansby

dstansby commented on May 16, 2017

@dstansby
Member

Can confirm this on master - the problem bisects to 1a7c4ef

QuLogic

QuLogic commented on May 16, 2017

@QuLogic
Member

Does #8300 help with this then?

tacaswell

tacaswell commented on May 16, 2017

@tacaswell
Member

No, I do not expect #8300 to help with this.

The issue is that Agg does some aggressive clipping (not sure exactly when in the interpolation steps) so I am not sure that the 2.0 case is actually interpolating correctly (ex a value that should have been scaled to 2 in the normalized units is clipped to 1 before it is interpolated).

More generally, poisoning every screen pixel that include out-of-range source pixels seems like the 'right' thing to do.

geggo

geggo commented on May 18, 2017

@geggo
Author

Hi,
thanks for all responses.
Indeed, changes of commit 1a7c4ef induced the new behaviour, and it seems this was intentionally. I am missing the background why. But from my point of view this very strict handling of over/under values is not a good way: My current use case is displaying real-world grayscale images, to adjust contrast I set vmin/vmax. Some pixel values are outside this range, with the new behaviour they mess up the result, with the old they just saturate - a very natural behaviour.

With some testing and looking at the sources, I could observe that Agg clips values after interpolation, in this sense interpolation is done correctly, only the information about under/over values is lost. To recover this, I am currently playing with an approach to compare the interpolation result of the raw image and one with values scaled by 0.5. If the interpolation results don't match up, there must have been some clipping. Do you think this makes sense?
Or would it be better to modify Agg, such that for float data clipping can be suppressed?

Gregor

tacaswell

tacaswell commented on May 23, 2017

@tacaswell
Member

Doing the interpolation twice is almost certainly too expensive. Comparing where pixels == 1 or 0 in the output plane and are in the mask where they have been poisoned might be a better approach? I am definitely open to changing this (I was thinking much more about nan / inf when I did this before) and it would break some of the degeneracy in over/under regions overlapping.

Another reasonable idea may be to just scale down the input to [.1, .9] so we can do the clipping our selves.

This will still not correctly capture pixels where the interpolation pushes the result value out of range despite everything around it being with-in range (which I think some of the higher-order interpolations can do.

We have a vendored version of the last non-gpl version of Agg which has slightly divereged from upstream. We are not the only project with a vendored-and-changed Agg and it does cause problems (like seg-fault problems) so I am wary of making aggressive changes to Agg it's self, but using different functions from Agg is on the table.

geggo

geggo commented on May 23, 2017

@geggo
Author
geggo

geggo commented on May 24, 2017

@geggo
Author

First results, I tried the approach to scale the image values to 0.25-0.75 before interpolation, so clipping for the unscaled image can be detected, for a proper assignment of under/over mask. This needs very few changes

diff --git a/lib/matplotlib/image.py b/lib/matplotlib/image.py
index b80f3dd..d2c6cde 100644
--- a/lib/matplotlib/image.py
+++ b/lib/matplotlib/image.py
@@ -375,8 +375,10 @@ class _ImageBase(martist.Artist, cm.ScalarMappable):
                     # this is to work around spurious warnings coming
                     # out of masked arrays.
                     with np.errstate(invalid='ignore'):
-                        rgba[..., 1] = np.where(A < 0, np.nan, 1)  # under data
-                        rgba[..., 2] = np.where(A > 1, np.nan, 1)  # over data
+                        # rgba[..., 1] = np.where(A < 0, np.nan, 1)  # under data
+                        # rgba[..., 2] = np.where(A > 1, np.nan, 1)  # over data
+                        rgba[..., 1] = A * 0.5 + 0.25 #scale raw image to 0.25...0.75
+
                     # 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
@@ -457,9 +459,11 @@ class _ImageBase(martist.Artist, cm.ScalarMappable):
                 invalid_mask = ~output.mask * ~np.isnan(output.data)
                 # 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
+                # output[np.isnan(hid_output[..., 1]) * invalid_mask] = -1
                 # relabel over data
-                output[np.isnan(hid_output[..., 2]) * invalid_mask] = 2
+                hid_nonclipped = (hid_output[..., 1]-0.5)*2 + 0.5 # interpolation without clipping
+                output[hid_nonclipped < -0.05] =  -1 # with safety margin
+                output[hid_nonclipped > 1.05] = 2
 
             output = self.to_rgba(output, bytes=True, norm=False)

Demonstration of the outcome for different strength of the outliers (1%, 10%, 100%, 1000% of max) with over/under masking based on values after interpolation (new approach):

test_interpolation_8024 clipping

for comparison result with mpl 2.0.0, where over/under mask relies on interpolating the raw over/under mask:

test_interpolation_8024 mpl 200

tacaswell

tacaswell commented on May 25, 2017

@tacaswell
Member

I like it! The oscillations around the strong peak from the higher-order kernels is wild, but I think correct.

Why rescale the values in plane 1, wouldn't it be simpler to just compare them against [.25, .75]?

I also do not understand the 5% margin.

Is the over/under pair in the top-right symetric? It looks like the red one is a bit weaker than the cyan one.

modified the milestone: 2.1 (next point release) on Jun 17, 2017
added
Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
on Jun 17, 2017
self-assigned this
on Jul 25, 2017

17 remaining items

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

Metadata

Metadata

Assignees

Labels

Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.status: confirmed bug

Type

No type

Projects

No projects

Relationships

None yet

    Development

    No branches or pull requests

      Participants

      @tacaswell@QuLogic@geggo@dstansby@tomchor

      Issue actions

        Image interpolation wrong for pixel values exceeding vmax · Issue #8631 · matplotlib/matplotlib