Skip to content

[Bug]: ValueError thrown when levels is set to a lower value than vmin when using contours method of Axes #26531

Closed

Description

@ymzayek

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 maxvalue

Expected 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

  1. ksunden commented on Aug 15, 2023

    @ksunden
    Member

    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 vmin gets updated before vmax.

    Essentially it is first autoscalling, then applying the vmin/vmax that were specified, so it goes:

    vmin   vmax
    ----   ----
    0.0    None
    0.0    0.1    Autoscaled
    0.2    0.1    vmin set (error happens here)
    0.2    0.8    vmax set (back to OK)
    

    This pattern of changed being called is not actually new, but what did change to is that now the callback calls update_scalarmappable where it didn't before, which causes the norm to be evaluated in its incomplete state.

    This can be suppressed (on our end) by doing:

    diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py
    index 79f66b8961..ab88faf28c 100644
    --- a/lib/matplotlib/contour.py
    +++ b/lib/matplotlib/contour.py
    @@ -887,11 +887,12 @@ class ContourSet(ContourLabeler, mcoll.Collection):
             self.set_cmap(cmap)
             if norm is not None:
                 self.set_norm(norm)
    -        if vmin is not None:
    -            self.norm.vmin = vmin
    -        if vmax is not None:
    -            self.norm.vmax = vmax
    -        self._process_colors()
    +        with self.norm.callbacks.blocked(signal="changed"):
    +            if vmin is not None:
    +                self.norm.vmin = vmin
    +            if vmax is not None:
    +                self.norm.vmax = vmax
    +            self._process_colors()
     
             if self._paths is None:
                 self._paths = self._make_paths_from_contour_generator()

    But perhaps there are more areas where such supression needs to happen? Or perhaps update_scalarmappable is not needed in changed?

    @anntzer thoughts?

  2. anntzer commented on Aug 15, 2023

    @anntzer
    Contributor

    A better fix is likely possible, but your patch seems correct at first glance.

  3. added this to the v3.8.0 milestone on Aug 16, 2023
  4. ymzayek commented on Aug 17, 2023

    @ymzayek
    Author

    Thanks @ksunden and @anntzer for looking into it!

  5. added
    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
    on Aug 17, 2023
  6. anntzer commented on Aug 23, 2023

    @anntzer
    Contributor

    (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)

  7. added a commit that references this issue on Aug 25, 2023
    ce385fe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions