-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
xarray imshow and pcolormesh behave badly when the array does not contain values larger the BoundaryNorm vmax #7014
Comments
It seems that the Line 1182 in c0011e1
which calls Line 854 in c0011e1
which then calls Line 154 in c0011e1
and this method has some special casing of BoundaryNorm that will create a new cmap
Not sure what all this code is supposed to do, though... |
Is there any update on this issue? I have been running into the same problem recently and am happy to see that this issue was already recognized by others. |
I'm still struggling to understand that whole workflow and it looks like this is no easy way of getting to the bottom of this. But adding kwarg The docstring gives a hint:
So in first case everything works nice since Not sure if aligning |
Debugging this, @headtr1ck points correctly to _determine_cmap_params: if levels is not None or isinstance(norm, mpl.colors.BoundaryNorm):
cmap, newnorm = _build_discrete_cmap(cmap, levels, extend, filled)
norm = newnorm if norm is None else norm The problem lies in the second line. In What then happens, is that the norm calls into the cmap. Calling into cmap doesn't do any checks whether the value is larger than N, it just takes the highest available value. The examples in #4061 show this quite clearly, but to illustrate: import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
data = np.arange(100).reshape((10, 10))
cmap = mpl.cm.get_cmap("viridis")
print(cmap.N) # 256
boundaries = [0, 25, 50, 75, 100]
norm = mpl.colors.BoundaryNorm(boundaries, cmap.N)
fig, ax = plt.subplots()
ax.imshow(data, norm=norm, cmap=cmap)
# %%
colors = [cmap(i/255) for i in np.linspace(0, cmap.N, len(boundaries) - 1)]
new_cmap, new_norm = mpl.colors.from_levels_and_colors(boundaries, colors)
print(new_cmap.N) # 4
fig, ax = plt.subplots()
ax.imshow(data, norm=new_norm, cmap=new_cmap)
# %%
# Mismatched
fig, ax = plt.subplots()
ax.imshow(data, norm=norm, cmap=new_cmap)
# %%
This is avoided here by removing the conditional in the second line, or just making sure both cmap and norm are replaced by their new values: if levels is not None or isinstance(norm, mpl.colors.BoundaryNorm):
cmap, norm = _build_discrete_cmap(cmap, levels, extend, filled) Then, the cmap and norm remain in sync. However, when running the tests in def test_norm_sets_vmin_vmax(self) -> None:
vmin = self.data.min()
vmax = self.data.max()
for norm, extend, levels in zip(
[
mpl.colors.Normalize(),
mpl.colors.Normalize(),
mpl.colors.Normalize(vmin + 0.1, vmax - 0.1),
mpl.colors.Normalize(None, vmax - 0.1),
mpl.colors.Normalize(vmin + 0.1, None),
],
["neither", "neither", "both", "max", "min"],
[7, None, None, None, None],
):
test_min = vmin if norm.vmin is None else norm.vmin
test_max = vmax if norm.vmax is None else norm.vmax
cmap_params = _determine_cmap_params(self.data, norm=norm, levels=levels)
assert cmap_params["vmin"] is None
assert cmap_params["vmax"] is None
assert cmap_params["norm"].vmin == test_min
assert cmap_params["norm"].vmax == test_max
assert cmap_params["extend"] == extend
> assert cmap_params["norm"] == norm
E assert <matplotlib.colors.BoundaryNorm object at 0x000001C7A4CB07C0> == <matplotlib.colors.Normalize object at 0x000001C7A50C4760> I don't understand why the conditional is there. At first sight, it doesn't make a lot of sense to create a new norm and cmap, but then take the original norm? |
I just combined @Huite's suggestion with splitting the if-statement. This works for both solving the issue and keeping the testcases in
This could replace this code: Line 307 in 1de881e
However, a bit up in the code there is a Line 277 in 1de881e
I think it is a potential solution nevertheless, but some help is appreciated with the last steps. Also since the case of @ghiggi seems not to be solved with this fix. It does solve #4061 though. |
Proposing update to fix pydata#7014
added testcase for pydata#7014
The related issues #4061 and Deltares/xugrid#49 are fixed by supplying |
Thanks to all the people above that have started digging into the problem ! |
@ghiggi: If I understand it correctly, your issue/examplecode covers multiple issues. Since one subissue might be using |
@veenstrajelmer I am not sure I understand what you are saying. In the example I pass only
|
What happened?
If
cmap.set_over
is specified, the array color mapping and the colorbar behave badly if the array does not contain values above thenorm.vmax
.Let's take an array and apply a colormap and norm (see code below)
Now, if in the array I change the array values larger than the
norm.vmax
(the 2 bottom right pixels) with other values inside the norm:What did you expect to happen?
The colorbar should not "shift" and the array should be colormapped correctly
This is possibily related also to #4061
Minimal Complete Verifiable Example
MVCE confirmation
Relevant log output
No response
Anything else we need to know?
No response
Environment
INSTALLED VERSIONS
commit: None
python: 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:56:21)
[GCC 10.3.0]
python-bits: 64
OS: Linux
OS-release: 5.4.0-124-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: ('en_US', 'UTF-8')
libhdf5: 1.12.1
libnetcdf: 4.8.1
xarray: 2022.6.0
pandas: 1.4.3
numpy: 1.22.4
scipy: 1.9.0
netCDF4: 1.6.0
pydap: None
h5netcdf: 1.0.2
h5py: 3.7.0
Nio: None
zarr: 2.12.0
cftime: 1.6.1
nc_time_axis: None
PseudoNetCDF: None
rasterio: 1.3.0
cfgrib: None
iris: None
bottleneck: 1.3.5
dask: 2022.7.1
distributed: 2022.7.1
matplotlib: 3.5.2
cartopy: 0.20.3
seaborn: 0.11.2
numbagg: None
fsspec: 2022.7.1
cupy: None
pint: 0.19.2
sparse: None
flox: None
numpy_groupies: None
setuptools: 63.3.0
pip: 22.2.2
conda: None
pytest: None
IPython: 7.33.0
sphinx: 5.1.1
/home/ghiggi/anaconda3/envs/gpm_geo/lib/python3.9/site-packages/_distutils_hack/init.py:33: UserWarning: Setuptools is replacing distutils.
warnings.warn("Setuptools is replacing distutils.")
The text was updated successfully, but these errors were encountered: