Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions doc/api/colors_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Color norms
:toctree: _as_gen/
:template: autosummary.rst

Norm
Normalize
NoNorm
AsinhNorm
Expand Down
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
166 changes: 112 additions & 54 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
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,100 @@ def _init(self):
self._isinit = True


class Normalize:
class Norm(ABC):

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring seems like it should be on the class, not the __init__.


Subclasses include `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

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

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

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

@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

@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

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

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

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

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,15 +2401,15 @@ def __init__(self, vmin=None, vmax=None, clip=False):
-----
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):
"""Lower limit of the input data interval; maps to 0."""
# docstring inherited
return self._vmin

@vmin.setter
Expand All @@ -2327,7 +2421,7 @@ def vmin(self, value):

@property
def vmax(self):
"""Upper limit of the input data interval; maps to 1."""
# docstring inherited
return self._vmax

@vmax.setter
Expand All @@ -2339,11 +2433,7 @@ def vmax(self, value):

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

See the *clip* parameter in `.Normalize`.
"""
# docstring inherited
return self._clip

@clip.setter
Expand All @@ -2352,13 +2442,6 @@ def clip(self, value):
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 Expand Up @@ -2400,24 +2483,7 @@ def process_value(value):
return result, is_scalar

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)``.
"""
# docstring inherited
if clip is None:
clip = self.clip

Expand Down Expand Up @@ -2447,15 +2513,7 @@ def __call__(self, value, clip=None):
return result

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

Parameters
----------
value
Normalized value.
"""
# docstring inherited
if not self.scaled():
raise ValueError("Not invertible until both vmin and vmax are set")
(vmin,), _ = self.process_value(self.vmin)
Expand All @@ -2468,7 +2526,7 @@ def inverse(self, value):
return vmin + value * (vmax - vmin)

def autoscale(self, A):
"""Set *vmin*, *vmax* to min, max of *A*."""
# docstring inherited
with self.callbacks.blocked():
# Pause callbacks while we are updating so we only get
# a single update signal at the end
Expand All @@ -2477,7 +2535,7 @@ def autoscale(self, A):
self._changed()

def autoscale_None(self, A):
"""If *vmin* or *vmax* are not set, use the min/max of *A* to set them."""
# docstring inherited
A = np.asanyarray(A)

if isinstance(A, np.ma.MaskedArray):
Expand All @@ -2491,7 +2549,7 @@ def autoscale_None(self, A):
self.vmax = A.max()

def scaled(self):
"""Return whether *vmin* and *vmax* are both set."""
# docstring inherited
return self.vmin is not None and self.vmax is not None


Expand Down Expand Up @@ -2775,7 +2833,7 @@ def _make_norm_from_scale(
unlike to arbitrary lambdas.
"""

class Norm(base_norm_cls):
class ScaleNorm(base_norm_cls):
def __reduce__(self):
cls = type(self)
# If the class is toplevel-accessible, it is possible to directly
Expand Down Expand Up @@ -2855,15 +2913,15 @@ def autoscale_None(self, A):
return super().autoscale_None(in_trf_domain)

if base_norm_cls is Normalize:
Norm.__name__ = f"{scale_cls.__name__}Norm"
Norm.__qualname__ = f"{scale_cls.__qualname__}Norm"
ScaleNorm.__name__ = f"{scale_cls.__name__}Norm"
ScaleNorm.__qualname__ = f"{scale_cls.__qualname__}Norm"
else:
Norm.__name__ = base_norm_cls.__name__
Norm.__qualname__ = base_norm_cls.__qualname__
Norm.__module__ = base_norm_cls.__module__
Norm.__doc__ = base_norm_cls.__doc__
ScaleNorm.__name__ = base_norm_cls.__name__
ScaleNorm.__qualname__ = base_norm_cls.__qualname__
ScaleNorm.__module__ = base_norm_cls.__module__
ScaleNorm.__doc__ = base_norm_cls.__doc__

return Norm
return ScaleNorm


def _create_empty_object_of_class(cls):
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.
Loading