Repository navigation
Description
Bug summary
With the recent mpl prerelease, we started to have a failure in the test suite of nilearn, a package that depends on matplotlib for visualization. I reproduced the error with a matplotlib example and it seems that when calling Axes.contours it's not possible to set the levels parameter to values less than vmin using array-like input. This happens even when vmax is also set by the user and it seems to be because vmax ends up being set to 0 internally. The code reproduced below will throw ValueError: minvalue must be less than or equal to maxvalue. I'm not sure if it's a usage error or a bug so let me know if this is expected behavior, however as I understand it the values for levels and vmin and vmax should be independent.
Code for reproduction
import matplotlib.pyplot as plt
import numpy as np
feature_x = np.arange(0, 50, 2)
feature_y = np.arange(0, 50, 3)
# Creating 2-D grid of features
[X, Y] = np.meshgrid(feature_x, feature_y)
fig, ax = plt.subplots(1, 1)
Z = np.cos(X / 2) + np.sin(Y / 4)
# plots contour lines
ax.contour(X, Y, Z, levels=[0.0, 0.1], vmin=0.2, vmax=0.8)Actual outcome
ValueError Traceback (most recent call last)
[/home/yasmin/.config/Code/User/globalStorage/buenon.scratchpads/scratchpads/47a6066b9dfaca1c2b13ea2e99f4e929/scratch.py](https://file+.vscode-resource.vscode-cdn.net/home/yasmin/.config/Code/User/globalStorage/buenon.scratchpads/scratchpads/47a6066b9dfaca1c2b13ea2e99f4e929/scratch.py) in ()
[109](file:///home/yasmin/.config/Code/User/globalStorage/buenon.scratchpads/scratchpads/47a6066b9dfaca1c2b13ea2e99f4e929/scratch.py?line=108) Z = np.cos(X [/](https://file+.vscode-resource.vscode-cdn.net/) 2) + np.sin(Y [/](https://file+.vscode-resource.vscode-cdn.net/) 4)
[111](file:///home/yasmin/.config/Code/User/globalStorage/buenon.scratchpads/scratchpads/47a6066b9dfaca1c2b13ea2e99f4e929/scratch.py?line=110) # plots contour lines
---> [112](file:///home/yasmin/.config/Code/User/globalStorage/buenon.scratchpads/scratchpads/47a6066b9dfaca1c2b13ea2e99f4e929/scratch.py?line=111) ax.contour(X, Y, Z, levels=[0.0, 0.1], vmin=0.2, vmax=0.8)
File [~/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py:1465](https://file+.vscode-resource.vscode-cdn.net/home/yasmin/nilearn/nilearn/~/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py:1465), in _preprocess_data..inner(ax, data, *args, **kwargs)
[1462](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py?line=1461) @functools.wraps(func)
[1463](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py?line=1462) def inner(ax, *args, data=None, **kwargs):
[1464](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py?line=1463) if data is None:
-> [1465](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py?line=1464) return func(ax, *map(sanitize_sequence, args), **kwargs)
[1467](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py?line=1466) bound = new_sig.bind(ax, *args, **kwargs)
[1468](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py?line=1467) auto_label = (bound.arguments.get(label_namer)
[1469](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/__init__.py?line=1468) or bound.kwargs.get(label_namer))
File [~/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py:6498](https://file+.vscode-resource.vscode-cdn.net/home/yasmin/nilearn/nilearn/~/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py:6498), in Axes.contour(self, *args, **kwargs)
[6489](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py?line=6488) """
[6490](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py?line=6489) Plot contour lines.
[6491](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py?line=6490)
(...)
[6495](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py?line=6494) %(contour_doc)s
[6496](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py?line=6495) """
[6497](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py?line=6496) kwargs['filled'] = False
-> [6498](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/axes/_axes.py?line=6497) contours = mcontour.QuadContourSet(self, *args, **kwargs)
...
-> [1361](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/colors.py?line=1360) raise ValueError("minvalue must be less than or equal to maxvalue")
[1362](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/colors.py?line=1361) else:
[1363](file:///home/yasmin/miniconda3/envs/nilearn-dev/lib/python3.10/site-packages/matplotlib/colors.py?line=1362) if clip:
ValueError: minvalue must be less than or equal to maxvalueExpected outcome
A plot matplotlib.contour.QuadContourSet object
Additional information
The test that led to this failure did pass before this version
Operating system
Ubuntu 22.04
Matplotlib Version
3.8.0rc1
Matplotlib Backend
module://matplotlib_inline.backend_inline
Python version
3.10.4
Jupyter version
No response
Installation
pip
Activity
ksunden commentedon Aug 15, 2023
This appears to be related to #25247, which changed the inheritance of the
ContourSet.In this case, it is not actually a fatal error, as far as I can tell... when I run it it continues after printing the traceback.
The problem is that it is doing checks at every step, so
vmingets updated beforevmax.Essentially it is first autoscalling, then applying the vmin/vmax that were specified, so it goes:
This pattern of
changedbeing called is not actually new, but what did change to is that now the callback callsupdate_scalarmappablewhere it didn't before, which causes the norm to be evaluated in its incomplete state.This can be suppressed (on our end) by doing:
But perhaps there are more areas where such supression needs to happen? Or perhaps
update_scalarmappableis not needed inchanged?@anntzer thoughts?
anntzer commentedon Aug 15, 2023
A better fix is likely possible, but your patch seems correct at first glance.
ymzayek commentedon Aug 17, 2023
Thanks @ksunden and @anntzer for looking into it!
anntzer commentedon Aug 23, 2023
(As a side point, perhaps there should be a helper method that lets one set both vmin and vmax on a norm and only trigger the validation and signals once both have been set -- in essence wrapping the patch written by @ksunden)
Avoid checking limits when updating both min and max for contours