Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

MultiNorm class#29876

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
trygvrad wants to merge5 commits intomatplotlib:main
base:main
Choose a base branch
Loading
fromtrygvrad:multivariate-plot-prapare
Open
Show file tree
Hide file tree
Changes fromall 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
272 changes: 263 additions & 9 deletionslib/matplotlib/colors.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1420,10 +1420,10 @@
combination_mode: str, 'sRGB_add' or 'sRGB_sub'
Describe how colormaps are combined in sRGB space

- If 'sRGB_add' -> Mixing produces brighter colors
`sRGB = sum(colors)`
- If 'sRGB_sub' -> Mixing produces darker colors
`sRGB = 1 - sum(1 - colors)`
- If 'sRGB_add': Mixing produces brighter colors
``sRGB = sum(colors)``
- If 'sRGB_sub': Mixing produces darker colors
``sRGB = 1 - sum(1 - colors)``
name : str, optional
The name of the colormap family.
"""
Expand DownExpand Up@@ -1595,15 +1595,15 @@

Parameters
----------
bad: :mpltype:`color`, default: None
bad: :mpltype:`color`, default: None
If Matplotlib color, the bad value is set accordingly in the copy

under tuple of :mpltype:`color`, default: None
If tuple, the `under` value of each component is set with the values
under:tuple of :mpltype:`color`, default: None
If tuple, the ``under`` value of each component is set with the values
from the tuple.

over tuple of :mpltype:`color`, default: None
If tuple, the `over` value of each component is set with the values
over:tuple of :mpltype:`color`, default: None
If tuple, the ``over`` value of each component is set with the values
from the tuple.

Returns
Expand DownExpand Up@@ -2320,6 +2320,11 @@
self._scale = None
self.callbacks = cbook.CallbackRegistry(signals=["changed"])

@property
def n_variables(self):
# To be overridden by subclasses with multiple inputs
return 1

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

View check run for this annotation

Codecov/ codecov/patch

lib/matplotlib/colors.py#L2326

Added line #L2326 was not covered by tests

@property
def vmin(self):
return self._vmin
Expand DownExpand Up@@ -3219,6 +3224,224 @@
return value


class MultiNorm(Normalize):
"""
A mixin class which contains multiple scalar norms
"""

def __init__(self, norms, vmin=None, vmax=None, clip=False):
"""
Parameters
----------
norms : List of strings or `Normalize` objects
Copy link
Member

Choose a reason for hiding this comment

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

None seems to be left out here:

Suggested change
norms :Listofstringsor`Normalize`objects
norms :listof(str,`Normalize`,orNone)

The constituent norms. The list must have a minimum length of 2.
vmin, vmax : float, None, or list of float or None
Limits of the constituent norms.
If a list, each value is assigned to each of the constituent
norms. Single values are repeated to form a list of appropriate size.

clip : bool or list of bools, default: False
Determines the behavior for mapping values outside the range
``[vmin, vmax]`` for the constituent norms.
If a list, each value is assigned to each of the constituent
norms. Single values are repeated to form a list of appropriate size.

"""

if isinstance(norms, str) or not np.iterable(norms):
Copy link
Member

Choose a reason for hiding this comment

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

This is equivalent, I think?

Suggested change
ifisinstance(norms,str)ornotnp.iterable(norms):
ifcbook.is_scalar_or_string(norms):

raise ValueError("A MultiNorm must be assigned multiple norms")

norms = [*norms]
for i, n in enumerate(norms):
if n is None:
norms[i] = Normalize()

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

View check run for this annotation

Codecov/ codecov/patch

lib/matplotlib/colors.py#L3257

Added line #L3257 was not covered by tests
elif isinstance(n, str):
scale_cls = _get_scale_cls_from_str(n)
norms[i] = mpl.colorizer._auto_norm_from_scale(scale_cls)()
elif not isinstance(n, Normalize):
raise ValueError(
"MultiNorm must be assigned multiple norms, where each norm "
f"is of type `None` `str`, or `Normalize`, not {type(n)}")

# Convert the list of norms to a tuple to make it immutable.
# If there is a use case for swapping a single norm, we can add support for
# that later
self._norms = tuple(norms)
Copy link
Member

Choose a reason for hiding this comment

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

Does this not need a check that the length matchesself.n_variables?


self.callbacks = cbook.CallbackRegistry(signals=["changed"])

self.vmin = vmin
self.vmax = vmax
self.clip = clip

for n in self._norms:
n.callbacks.connect('changed', self._changed)

@property
def n_variables(self):
return len(self._norms)

@property
def norms(self):
return self._norms

@property
def vmin(self):
return tuple(n.vmin for n in self._norms)

@vmin.setter
def vmin(self, value):
value = np.broadcast_to(value, self.n_variables)
with self.callbacks.blocked():
for i, v in enumerate(value):
if v is not None:
self.norms[i].vmin = v
self._changed()

@property
def vmax(self):
return tuple(n.vmax for n in self._norms)

@vmax.setter
def vmax(self, value):
value = np.broadcast_to(value, self.n_variables)
with self.callbacks.blocked():
for i, v in enumerate(value):
if v is not None:
self.norms[i].vmax = v
self._changed()

@property
def clip(self):
return tuple(n.clip for n in self._norms)

@clip.setter
def clip(self, value):
value = np.broadcast_to(value, self.n_variables)
with self.callbacks.blocked():
for i, v in enumerate(value):
if v is not None:
self.norms[i].clip = v
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')

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

Each variate in the input is assigned to the constituent norm.

Parameters
----------
value
Data to normalize. Must be of length `n_variables` or have a data type with
`n_variables` fields.
clip : list of bools or bool, optional
See the description of the parameter *clip* in Normalize.
If ``None``, defaults to ``self.clip`` (which defaults to
``False``).

Returns
-------
Data
Copy link
Member

Choose a reason for hiding this comment

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

Single returns should be the type, not a name.

Normalized input values as a list of length `n_variables`

Notes
-----
If not already initialized, ``self.vmin`` and ``self.vmax`` are
initialized using ``self.autoscale_None(value)``.
"""
if clip is None:
clip = self.clip
elif not np.iterable(clip):
clip = [clip]*self.n_variables

value = self._iterable_variates_in_data(value, self.n_variables)
result = [n(v, clip=c) for n, v, c in zip(self.norms, value, clip)]
return result

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

Parameters
----------
value
Normalized value. Must be of length `n_variables` or have a data type with
`n_variables` fields.
"""
value = self._iterable_variates_in_data(value, self.n_variables)
result = [n.inverse(v) for n, v in zip(self.norms, value)]
return result

def autoscale(self, A):
"""
For each constituent norm, Set *vmin*, *vmax* to min, max of the corresponding
variate in *A*.
"""
with self.callbacks.blocked():
# Pause callbacks while we are updating so we only get
# a single update signal at the end
A = self._iterable_variates_in_data(A, self.n_variables)
for n, a in zip(self.norms, A):
n.autoscale(a)
self._changed()

def autoscale_None(self, A):
Copy link
Member

Choose a reason for hiding this comment

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

Seems to be missing a test?

"""
If *vmin* or *vmax* are not set on any constituent norm,
use the min/max of the corresponding variate in *A* to set them.

Parameters
----------
A
Data, must be of length `n_variables` or be an np.ndarray type with
`n_variables` fields.
Comment on lines +3404 to +3405
Copy link
Member

Choose a reason for hiding this comment

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

This seems to be the only docstring for a data parameter that mentionsnp.ndarray; is that required or not? The others should be made consistent either way.

"""
with self.callbacks.blocked():
A = self._iterable_variates_in_data(A, self.n_variables)

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

View check run for this annotation

Codecov/ codecov/patch

lib/matplotlib/colors.py#L3407-L3408

Added lines #L3407 - L3408 were not covered by tests
for n, a in zip(self.norms, A):
n.autoscale_None(a)
self._changed()

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

View check run for this annotation

Codecov/ codecov/patch

lib/matplotlib/colors.py#L3410-L3411

Added lines #L3410 - L3411 were not covered by tests

def scaled(self):
"""Return whether both *vmin* and *vmax* are set on all constituent norms"""
return all([(n.vmin is not None and n.vmax is not None) for n in self.norms])

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

View check run for this annotation

Codecov/ codecov/patch

lib/matplotlib/colors.py#L3415

Added line #L3415 was not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

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

A bit too many parentheses, not that it really matters.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I'm changing this to:

returnall([n.scaledforninself.norms])

which should be more readable

Copy link
Member

Choose a reason for hiding this comment

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

This wasn't changed?


@staticmethod
def _iterable_variates_in_data(data, n_variables):
"""
Provides an iterable over the variates contained in the data.

An input array with `n_variables` fields is returned as a list of length n
referencing slices of the original array.

Parameters
----------
data : np.ndarray, tuple or list
The input array. It must either be an array with n_variables fields or have
a length (n_variables)

Returns
-------
list of np.ndarray
Copy link
Member

Choose a reason for hiding this comment

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

Dedent.


"""
if isinstance(data, np.ndarray) and data.dtype.fields is not None:
data = [data[descriptor[0]] for descriptor in data.dtype.descr]

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

View check run for this annotation

Codecov/ codecov/patch

lib/matplotlib/colors.py#L3437

Added line #L3437 was not covered by tests
if len(data) != n_variables:
raise ValueError("The input to this `MultiNorm` must be of shape "

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

View check run for this annotation

Codecov/ codecov/patch

lib/matplotlib/colors.py#L3439

Added line #L3439 was not covered by tests
f"({n_variables}, ...), or have a data type with "
f"{n_variables} fields.")
return data


def rgb_to_hsv(arr):
"""
Convert an array of float RGB values (in the range [0, 1]) to HSV values.
Expand DownExpand Up@@ -3856,3 +4079,34 @@

norm = BoundaryNorm(levels, ncolors=n_data_colors)
return cmap, norm


def _get_scale_cls_from_str(scale_as_str):
"""
Returns the scale class from a string.

Used in the creation of norms from a string to ensure a reasonable error
in the case where an invalid string is used. This would normally use
`_api.check_getitem()`, which would produce the error
> 'not_a_norm' is not a valid value for norm; supported values are
> 'linear', 'log', 'symlog', 'asinh', 'logit', 'function', 'functionlog'
which is misleading because the norm keyword also accepts `Normalize` objects.

Parameters
----------
scale_as_str : string
A string corresponding to a scale

Returns
-------
A subclass of ScaleBase.

"""
try:
scale_cls = scale._scale_mapping[scale_as_str]
except KeyError:
raise ValueError(
"Invalid norm str name; the following values are "
f"supported: {', '.join(scale._scale_mapping)}"
) from None
return scale_cls
30 changes: 30 additions & 0 deletionslib/matplotlib/colors.pyi
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -263,6 +263,8 @@ class Normalize:
@vmax.setter
def vmax(self, value: float | None) -> None: ...
@property
def n_variables(self) -> int: ...
@property
def clip(self) -> bool: ...
@clip.setter
def clip(self, value: bool) -> None: ...
Expand DownExpand Up@@ -387,6 +389,34 @@ class BoundaryNorm(Normalize):

class NoNorm(Normalize): ...

class MultiNorm(Normalize):
# Here "type: ignore[override]" is used for functions with a return type
# that differs from the function in the base class.
# i.e. where `MultiNorm` returns a tuple and Normalize returns a `float` etc.
def __init__(
self,
norms: ArrayLike,
vmin: ArrayLike | float | None = ...,
vmax: ArrayLike | float | None = ...,
clip: ArrayLike | bool = ...
) -> None: ...
@property
def norms(self) -> tuple[Normalize, ...]: ...
@property # type: ignore[override]
def vmin(self) -> tuple[float | None, ...]: ...
@vmin.setter
def vmin(self, value: ArrayLike | float | None) -> None: ...
@property # type: ignore[override]
def vmax(self) -> tuple[float | None, ...]: ...
@vmax.setter
def vmax(self, value: ArrayLike | float | None) -> None: ...
@property # type: ignore[override]
def clip(self) -> tuple[bool, ...]: ...
@clip.setter
def clip(self, value: ArrayLike | bool) -> None: ...
def __call__(self, value: ArrayLike, clip: ArrayLike | bool | None = ...) -> list: ... # type: ignore[override]
def inverse(self, value: ArrayLike) -> list: ... # type: ignore[override]

def rgb_to_hsv(arr: ArrayLike) -> np.ndarray: ...
def hsv_to_rgb(hsv: ArrayLike) -> np.ndarray: ...

Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp