Skip to content

Abstract base class for Normalize #30178

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/matplotlib/colorizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def norm(self):

@norm.setter
def norm(self, norm):
_api.check_isinstance((colors.Normalize, str, None), norm=norm)
_api.check_isinstance((colors.Norm, str, None), norm=norm)
if norm is None:
norm = colors.Normalize()
elif isinstance(norm, str):
Expand Down
14 changes: 7 additions & 7 deletions lib/matplotlib/colorizer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ class Colorizer:
def __init__(
self,
cmap: str | colors.Colormap | None = ...,
norm: str | colors.Normalize | None = ...,
norm: str | colors.Norm | None = ...,
) -> None: ...
@property
def norm(self) -> colors.Normalize: ...
def norm(self) -> colors.Norm: ...
@norm.setter
def norm(self, norm: colors.Normalize | str | None) -> None: ...
def norm(self, norm: colors.Norm | str | None) -> None: ...
def to_rgba(
self,
x: np.ndarray,
Expand Down Expand Up @@ -63,18 +63,18 @@ class _ColorizerInterface:
def get_cmap(self) -> colors.Colormap: ...
def set_cmap(self, cmap: str | colors.Colormap) -> None: ...
@property
def norm(self) -> colors.Normalize: ...
def norm(self) -> colors.Norm: ...
@norm.setter
def norm(self, norm: colors.Normalize | str | None) -> None: ...
def set_norm(self, norm: colors.Normalize | str | None) -> None: ...
def norm(self, norm: colors.Norm | str | None) -> None: ...
def set_norm(self, norm: colors.Norm | str | None) -> None: ...
def autoscale(self) -> None: ...
def autoscale_None(self) -> None: ...


class _ScalarMappable(_ColorizerInterface):
def __init__(
self,
norm: colors.Normalize | None = ...,
norm: colors.Norm | None = ...,
cmap: str | colors.Colormap | None = ...,
*,
colorizer: Colorizer | None = ...,
Expand Down
106 changes: 97 additions & 9 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""
A module for converting numbers or color arguments to *RGB* or *RGBA*.

Expand Down Expand Up @@ -41,6 +41,7 @@

import base64
from collections.abc import Sequence, Mapping
from abc import ABC, abstractmethod
import functools
import importlib
import inspect
Expand Down Expand Up @@ -2257,7 +2258,101 @@
self._isinit = True


class Normalize:
class Norm(ABC):

def __init__(self):
"""
Abstract base class for normalizations.

Subclasses include `colors.Normalize` which maps from a scalar to
a scalar. However, this class makes no such requirement, and subclasses may
support the normalization of multiple variates simultaneously, with
separate normalization for each variate.
"""
self.callbacks = cbook.CallbackRegistry(signals=["changed"])

@property
@abstractmethod
def vmin(self):
"""Lower limit of the input data interval; maps to 0."""
pass

Check warning on line 2278 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2278

Added line #L2278 was not covered by tests

@property
@abstractmethod
def vmax(self):
"""Upper limit of the input data interval; maps to 1."""
pass

Check warning on line 2284 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2284

Added line #L2284 was not covered by tests

@property
@abstractmethod
def clip(self):
"""
Determines the behavior for mapping values outside the range ``[vmin, vmax]``.

See the *clip* parameter in `.Normalize`.
"""
pass

Check warning on line 2294 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2294

Added line #L2294 was not covered by tests

@abstractmethod
def __call__(self, value, clip=None):
"""
Normalize the data and return the normalized data.

Parameters
----------
value
Data to normalize.
clip : bool, optional
See the description of the parameter *clip* in `.Normalize`.

If ``None``, defaults to ``self.clip`` (which defaults to
``False``).

Notes
-----
If not already initialized, ``self.vmin`` and ``self.vmax`` are

initialized using ``self.autoscale_None(value)``.
"""
pass

Check warning on line 2317 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2317

Added line #L2317 was not covered by tests

@abstractmethod
def inverse(self, value):
"""
Maps the normalized value (i.e., index in the colormap) back to image
data value.

Parameters
----------
value
Normalized value.
"""
pass

Check warning on line 2330 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2330

Added line #L2330 was not covered by tests

@abstractmethod
def autoscale(self, A):
"""Set *vmin*, *vmax* to min, max of *A*."""
pass

Check warning on line 2335 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2335

Added line #L2335 was not covered by tests

@abstractmethod
def autoscale_None(self, A):
"""If *vmin* or *vmax* are not set, use the min/max of *A* to set them."""
pass

Check warning on line 2340 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2340

Added line #L2340 was not covered by tests

@abstractmethod
def scaled(self):
"""Return whether *vmin* and *vmax* are both set."""
pass

Check warning on line 2345 in lib/matplotlib/colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/colors.py#L2345

Added line #L2345 was not covered by tests

def _changed(self):
"""
Call this whenever the norm is changed to notify all the
callback listeners to the 'changed' signal.
"""
self.callbacks.process('changed')


class Normalize(Norm):
"""
A class which, when called, maps values within the interval
``[vmin, vmax]`` linearly to the interval ``[0.0, 1.0]``. The mapping of
Expand Down Expand Up @@ -2307,11 +2402,11 @@
-----
If ``vmin == vmax``, input data will be mapped to 0.
"""
super().__init__()
self._vmin = _sanitize_extrema(vmin)
self._vmax = _sanitize_extrema(vmax)
self._clip = clip
self._scale = None
self.callbacks = cbook.CallbackRegistry(signals=["changed"])

@property
def vmin(self):
Expand Down Expand Up @@ -2352,13 +2447,6 @@
self._clip = value
self._changed()

def _changed(self):
"""
Call this whenever the norm is changed to notify all the
callback listeners to the 'changed' signal.
"""
self.callbacks.process('changed')

@staticmethod
def process_value(value):
"""
Expand Down
26 changes: 25 additions & 1 deletion lib/matplotlib/colors.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
from abc import ABC, abstractmethod
from matplotlib import cbook, scale
import re

Expand Down Expand Up @@ -249,8 +250,31 @@ class BivarColormapFromImage(BivarColormap):
origin: Sequence[float] = ..., name: str = ...
) -> None: ...

class Normalize:
class Norm(ABC):
callbacks: cbook.CallbackRegistry
def __init__(self) -> None: ...
@property
@abstractmethod
def vmin(self) -> float | tuple[float] | None: ...
@property
@abstractmethod
def vmax(self) -> float | tuple[float] | None: ...
@property
@abstractmethod
def clip(self) -> bool | tuple[bool]: ...
@abstractmethod
def __call__(self, value: np.ndarray, clip: bool | None = ...) -> ArrayLike: ...
@abstractmethod
def inverse(self, value: ArrayLike) -> ArrayLike: ...
@abstractmethod
def autoscale(self, A: ArrayLike) -> None: ...
@abstractmethod
def autoscale_None(self, A: ArrayLike) -> None: ...
@abstractmethod
def scaled(self) -> bool: ...


class Normalize(Norm):
def __init__(
self, vmin: float | None = ..., vmax: float | None = ..., clip: bool = ...
) -> None: ...
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions lib/matplotlib/tests/test_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from PIL import Image
import pytest
import base64
import platform

from numpy.testing import assert_array_equal, assert_array_almost_equal

Expand Down Expand Up @@ -1829,3 +1830,49 @@
cmap([value for value, _ in value_color_tuples]),
to_rgba_array([color for _, color in value_color_tuples]),
)


@image_comparison(['test_norm_abc.png'], remove_text=True,
tol=0 if platform.machine() == 'x86_64' else 0.05)
def test_norm_abc():

class CustomHalfNorm(mcolors.Norm):
def __init__(self):
super().__init__()

@property
def vmin(self):
return 0

Check warning on line 1845 in lib/matplotlib/tests/test_colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_colors.py#L1845

Added line #L1845 was not covered by tests

@property
def vmax(self):
return 1

Check warning on line 1849 in lib/matplotlib/tests/test_colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_colors.py#L1849

Added line #L1849 was not covered by tests

@property
def clip(self):
return False

Check warning on line 1853 in lib/matplotlib/tests/test_colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_colors.py#L1853

Added line #L1853 was not covered by tests

def __call__(self, value, clip=None):
return value / 2

def inverse(self, value):
return 2 * value

Check warning on line 1859 in lib/matplotlib/tests/test_colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_colors.py#L1859

Added line #L1859 was not covered by tests

def autoscale(self, A):
pass

Check warning on line 1862 in lib/matplotlib/tests/test_colors.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_colors.py#L1862

Added line #L1862 was not covered by tests

def autoscale_None(self, A):
pass

def scaled(self):
return True

fig, axes = plt.subplots(2,2)

r = np.linspace(-1, 3, 16*16).reshape((16,16))
norm = CustomHalfNorm()
colorizer = mpl.colorizer.Colorizer(cmap='viridis', norm=norm)
c = axes[0,0].imshow(r, colorizer=colorizer)
axes[0,1].pcolor(r, colorizer=colorizer)
axes[1,0].contour(r, colorizer=colorizer)
axes[1,1].contourf(r, colorizer=colorizer)
Loading