You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Wrong scaling of NonUniformImage with logarithmic axes #29368
I'm trying to produce a density plot of logarithmically spaced data. Using NonUniformImage used to work before matplotlib version 3.10, where I know exhibit a weirdly scaled axis.
Code for reproduction
importnumpyasnpimportmatplotlib.pyplotaspltfrommatplotlib.imageimportNonUniformImagex=np.linspace(0, 1, 2)
y=np.geomspace(1e1, 1e5, 3)
z=np.random.uniform(size=(len(x), len(y)))
_, (ax1, ax2) =plt.subplots(ncols=2)
# create the non-uniform imageim=NonUniformImage(ax1, extent=[0, 1, 1e0, 1e6], origin="lower")
im.set_data(x, y, z.T)
ax1.add_image(im)
ax1.set_yscale("log")
ax1.set_ylim(1e0, 1e6) # omitting this leads to very weird behaviorax1.set_title("NonUniformImage")
# use pcolormesh to get reference imageax2.pcolormesh([0, 0.5, 1], [1e0, 1e2, 1e4, 1e6], z.T)
ax2.set_yscale("log")
ax2.set_title("pcolormesh")
Actual outcome
The code above produces the following image
Expected outcome
I expect the left panel to look like the right panel, which was produced using pcolormesh. The reason I don't want to use pcolormesh for these plots is that the quality in PDFs is often much worse (with lines showing up between patches) and the file size can easily be 10 times larger.
changed the title [-][Bug]: Wrong `NonUniformImage` with logarithmic axes[/-][+][Bug]: Wrong scaling of `NonUniformImage` with logarithmic axes[/+]on Dec 21, 2024
I've not bisected, but the change is possibly related to #27964.
I think the new behavior is actually correct. Pixel values are specified at their centers (y=[10^1, 10^3, 10^5]). The values in between are determined by the interpolation method (default: "nearest"). Therefore the pixel boundary between the pixels at 10^3 and 10^5 is at (10^5 - 10^3)/2 = 49500, which matches the image. This interpolation happens in data space and therefore is independent of the axis scaling.
I believe the 3.10 behavior is correct and it's a topic on expectation/documentation of the interpolation behavior.
For large pcolormesh in pdf use "rasterized=True".
I don't like this solution, because it is difficult to predict the right resolution. If the resolution is too coarse, details of the density will be lost. If the resolution is too fine, the file size is increased. In any case, the resolution would need to be much finer than the actual data to not move boundaries around.
I think there should be a stable solution for adding images with rectangular, equal-sized pixels with logarithmic scaling!
I don't like this solution, because it is difficult to predict the right resolution
Your output device sets the resolution. Indeed it looks to me like NonUniformImage just makes a 72 dpi raster for a PDF, which is worse than what you will get from setting the dpi of the pcolormesh raster. The reason is that PDF and ps do not have any concept of a "NonUniformImage", and hence when we write the output we can either rasterize (at some dpi) or we can make polygons. We choose to rasterize the NonUniformImage, which is exactly what happens with pcolormesh(..., rasterized=True). The alternative is to draw individual polygons, which is what pcolormesh does by default, but this will create the anti-aliasing artifacts you are seeing in PDF (btw those go away if you turn anti-aliasing off in your PDF viewer).
The only reason I think NonUniformImage exists is to be faster than pcolormesh. I'm not sure it particularly matters in 2024, and maybe we should just deprecate it.
The only reason I think NonUniformImage exists is to be faster than pcolormesh. I'm not sure it particularly matters in 2024, and maybe we should just deprecate it.
That's helpful! I understand now that NonUniformImage in principle does lead to the same result as a rasterized pcolormesh. I played around with these two options and their behavior indeed seems to be very similar.
However, I actually don't need a non-uniform image in my case – all I want is to display my uniform image with a log-scaled axis. This used to work several years ago, but now imshow rescales the image when I set the axis to log-scale. I thought NonUniformImage would be a good work-around, but I now see that this is not really the case.
Can you just use imshow and add a secondary axis for the ticks?
This works in principle, but it becomes awkward when one adds a colorbar later, which might steal space from the original axis.
Thanks – this is certainly a work-around for specific situations, but I would like to provide a general function densityplot, which as arguments simply takes a 2d array of intensity values and two 1d arrays indicating the points along which the data has been sampled (and in most cases the two 1d arrays will be either linear or log-spaced). The densityplot function should then in principle set the axes automatically and allow the user to add colorbars etc afterwards. In essence, I'm envisioning a slightly modified pcolormesh, except that the density plot should in principle be a simple image, which hopefully can be shown with imshow. The workaround suggested above and in the StackOverflow question unfortunately don't provide the necessary flexibility.
Perhaps you could provide an example where pcolormesh(..., rasterized=True) actually returns a worse output than NonUniformImage, and that would help clarify the problem.
I played around with different options, and I'm pleasantly surprised by the rasterized pcolormesh. This seems to solve my original issue, so I will adopt it in the future. Thanks for the help!
However, the original problem discussed in this issue still stands (even though I will now not be affected by it).
What exactly do you as the "original problem"? Comparing NonuniformImage and pcolormesh directly is difficult. In both cases, you have a discrete 2D array of values. You have to somehow determine the coordinates for the transition from one value to the next. pcolormesh does this explicitly (you've specified the 4 y-values of the borders). In contrast, for NonUniformImage you only give the pixel centers. There's no explicit specification where the borders should be. What we do is nearest neighbor interploation in data coordinates. Therefore, as written in #29368 (comment) I believe the behavior is correct.
Activity
[-][Bug]: Wrong `NonUniformImage` with logarithmic axes[/-][+][Bug]: Wrong scaling of `NonUniformImage` with logarithmic axes[/+]timhoffm commentedon Dec 21, 2024
I've not bisected, but the change is possibly related to #27964.
I think the new behavior is actually correct. Pixel values are specified at their centers (y=[10^1, 10^3, 10^5]). The values in between are determined by the interpolation method (default: "nearest"). Therefore the pixel boundary between the pixels at 10^3 and 10^5 is at (10^5 - 10^3)/2 = 49500, which matches the image. This interpolation happens in data space and therefore is independent of the axis scaling.
I believe the 3.10 behavior is correct and it's a topic on expectation/documentation of the interpolation behavior.
jklymak commentedon Dec 21, 2024
For large pcolormesh in pdf use "rasterized=True".
rcomer commentedon Dec 21, 2024
Confirmed that the behaviour changed at #27964, but before that commit and back to v3.6 I get

david-zwicker commentedon Dec 21, 2024
I don't like this solution, because it is difficult to predict the right resolution. If the resolution is too coarse, details of the density will be lost. If the resolution is too fine, the file size is increased. In any case, the resolution would need to be much finer than the actual data to not move boundaries around.
I think there should be a stable solution for adding images with rectangular, equal-sized pixels with logarithmic scaling!
jklymak commentedon Dec 21, 2024
Your output device sets the resolution. Indeed it looks to me like NonUniformImage just makes a 72 dpi raster for a PDF, which is worse than what you will get from setting the dpi of the pcolormesh raster. The reason is that PDF and ps do not have any concept of a "NonUniformImage", and hence when we write the output we can either rasterize (at some dpi) or we can make polygons. We choose to rasterize the NonUniformImage, which is exactly what happens with
pcolormesh(..., rasterized=True). The alternative is to draw individual polygons, which is what pcolormesh does by default, but this will create the anti-aliasing artifacts you are seeing in PDF (btw those go away if you turn anti-aliasing off in your PDF viewer).The only reason I think
NonUniformImageexists is to be faster thanpcolormesh. I'm not sure it particularly matters in 2024, and maybe we should just deprecate it.rcomer commentedon Dec 21, 2024
Can you just use
imshowand add a secondary axis for the ticks?I'm afraid I haven't figured out how to make the minor ticks show up. Hopefully someone else can help with that.
david-zwicker commentedon Dec 21, 2024
That's helpful! I understand now that
NonUniformImagein principle does lead to the same result as a rasterizedpcolormesh. I played around with these two options and their behavior indeed seems to be very similar.However, I actually don't need a non-uniform image in my case – all I want is to display my uniform image with a log-scaled axis. This used to work several years ago, but now
imshowrescales the image when I set the axis to log-scale. I thoughtNonUniformImagewould be a good work-around, but I now see that this is not really the case.This works in principle, but it becomes awkward when one adds a colorbar later, which might steal space from the original axis.
rcomer commentedon Dec 21, 2024
I updated my comment to include colorbars.
david-zwicker commentedon Dec 21, 2024
Thanks – this is certainly a work-around for specific situations, but I would like to provide a general function
densityplot, which as arguments simply takes a 2d array of intensity values and two 1d arrays indicating the points along which the data has been sampled (and in most cases the two 1d arrays will be either linear or log-spaced). Thedensityplotfunction should then in principle set the axes automatically and allow the user to add colorbars etc afterwards. In essence, I'm envisioning a slightly modifiedpcolormesh, except that the density plot should in principle be a simple image, which hopefully can be shown withimshow. The workaround suggested above and in the StackOverflow question unfortunately don't provide the necessary flexibility.jklymak commentedon Dec 21, 2024
Perhaps you could provide an example where
pcolormesh(..., rasterized=True)actually returns a worse output thanNonUniformImage, and that would help clarify the problem.david-zwicker commentedon Dec 22, 2024
I played around with different options, and I'm pleasantly surprised by the rasterized pcolormesh. This seems to solve my original issue, so I will adopt it in the future. Thanks for the help!
However, the original problem discussed in this issue still stands (even though I will now not be affected by it).
timhoffm commentedon Dec 31, 2024
What exactly do you as the "original problem"? Comparing
NonuniformImageandpcolormeshdirectly is difficult. In both cases, you have a discrete 2D array of values. You have to somehow determine the coordinates for the transition from one value to the next.pcolormeshdoes this explicitly (you've specified the 4 y-values of the borders). In contrast, forNonUniformImageyou only give the pixel centers. There's no explicit specification where the borders should be. What we do is nearest neighbor interploation in data coordinates. Therefore, as written in #29368 (comment) I believe the behavior is correct.david-zwicker commentedon Dec 31, 2024
Thank you for the detailed explanation! I agree that the current behavior is valid and nothing needs to be changed.