Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c558e35

Browse files
committed
MultiNorm class
1 parent c9402f3 commit c558e35

2 files changed

Lines changed: 273 additions & 0 deletions

File tree

lib/matplotlib/colors.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2320,6 +2320,16 @@ def __init__(self, vmin=None, vmax=None, clip=False):
23202320
self._scale = None
23212321
self.callbacks = cbook.CallbackRegistry(signals=["changed"])
23222322

2323+
@property
2324+
def n_input(self):
2325+
# To be overridden by subclasses with multiple inputs
2326+
return 1
2327+
2328+
@property
2329+
def n_output(self):
2330+
# To be overridden by subclasses with multiple outputs
2331+
return 1
2332+
23232333
@property
23242334
def vmin(self):
23252335
return self._vmin
@@ -3219,6 +3229,237 @@ def inverse(self, value):
32193229
return value
32203230

32213231

3232+
class MultiNorm(Normalize):
3233+
"""
3234+
A mixin class which contains multiple scalar norms
3235+
"""
3236+
3237+
def __init__(self, norms, vmin=None, vmax=None, clip=False):
3238+
"""
3239+
Parameters
3240+
----------
3241+
norms : List of strings or `Normalize` objects
3242+
The constituent norms. The list must have a minimum length of 2.
3243+
vmin, vmax : float, None, or list of float or None
3244+
Limits of the constituent norms.
3245+
If a list, each each value is assigned to one of the constituent
3246+
norms. Single values are repeated to form a list of appropriate size.
3247+
3248+
clip : bool or list of bools, default: False
3249+
Determines the behavior for mapping values outside the range
3250+
``[vmin, vmax]`` for the constituent norms.
3251+
If a list, each each value is assigned to one of the constituent
3252+
norms. Single values are repeated to form a list of appropriate size.
3253+
3254+
"""
3255+
3256+
if isinstance(norms, str) or not np.iterable(norms):
3257+
raise ValueError("A MultiNorm must be assigned multiple norms")
3258+
norms = [n for n in norms]
3259+
for i, n in enumerate(norms):
3260+
if n is None:
3261+
norms[i] = Normalize()
3262+
elif isinstance(n, str):
3263+
try:
3264+
scale_cls = scale._scale_mapping[n]
3265+
except KeyError:
3266+
raise ValueError(
3267+
"Invalid norm str name; the following values are "
3268+
f"supported: {', '.join(scale._scale_mapping)}"
3269+
) from None
3270+
norms[i] = mpl.colorizer._auto_norm_from_scale(scale_cls)()
3271+
3272+
# Convert the list of norms to a tuple to make it immutable.
3273+
# If there is a use case for swapping a single norm, we can add support for
3274+
# that later
3275+
self._norms = tuple(n for n in norms)
3276+
3277+
self.callbacks = cbook.CallbackRegistry(signals=["changed"])
3278+
3279+
self.vmin = vmin
3280+
self.vmax = vmax
3281+
self.clip = clip
3282+
3283+
self._id_norms = [n.callbacks.connect('changed',
3284+
self._changed) for n in self._norms]
3285+
3286+
@property
3287+
def n_input(self):
3288+
return len(self._norms)
3289+
3290+
@property
3291+
def n_output(self):
3292+
return len(self._norms)
3293+
3294+
@property
3295+
def norms(self):
3296+
return self._norms
3297+
3298+
@property
3299+
def vmin(self):
3300+
return tuple(n.vmin for n in self._norms)
3301+
3302+
@vmin.setter
3303+
def vmin(self, value):
3304+
if not np.iterable(value):
3305+
value = [value]*self.n_input
3306+
if len(value) != self.n_input:
3307+
raise ValueError(f"Invalid vmin for `MultiNorm` with {self.n_input}"
3308+
" inputs.")
3309+
with self.callbacks.blocked():
3310+
for i, v in enumerate(value):
3311+
if v is not None:
3312+
self.norms[i].vmin = v
3313+
self._changed()
3314+
3315+
@property
3316+
def vmax(self):
3317+
return tuple(n.vmax for n in self._norms)
3318+
3319+
@vmax.setter
3320+
def vmax(self, value):
3321+
if not np.iterable(value):
3322+
value = [value]*self.n_input
3323+
if len(value) != self.n_input:
3324+
raise ValueError(f"Invalid vmax for `MultiNorm` with {self.n_input}"
3325+
" inputs.")
3326+
with self.callbacks.blocked():
3327+
for i, v in enumerate(value):
3328+
if v is not None:
3329+
self.norms[i].vmax = v
3330+
self._changed()
3331+
3332+
@property
3333+
def clip(self):
3334+
return tuple(n.clip for n in self._norms)
3335+
3336+
@clip.setter
3337+
def clip(self, value):
3338+
if not np.iterable(value):
3339+
value = [value]*self.n_input
3340+
with self.callbacks.blocked():
3341+
for i, v in enumerate(value):
3342+
if v is not None:
3343+
self.norms[i].clip = v
3344+
self._changed()
3345+
3346+
def _changed(self):
3347+
"""
3348+
Call this whenever the norm is changed to notify all the
3349+
callback listeners to the 'changed' signal.
3350+
"""
3351+
self.callbacks.process('changed')
3352+
3353+
def __call__(self, value, clip=None):
3354+
"""
3355+
Normalize the data and return the normalized data.
3356+
Each variate in the input is assigned to the a constituent norm.
3357+
3358+
Parameters
3359+
----------
3360+
value
3361+
Data to normalize. Must be of length `n_input` or have a data type with
3362+
`n_input` fields.
3363+
clip : List of bools or bool, optional
3364+
See the description of the parameter *clip* in Normalize.
3365+
If ``None``, defaults to ``self.clip`` (which defaults to
3366+
``False``).
3367+
3368+
Returns
3369+
-------
3370+
Data
3371+
Normalized input values as a list of length `n_input`
3372+
3373+
Notes
3374+
-----
3375+
If not already initialized, ``self.vmin`` and ``self.vmax`` are
3376+
initialized using ``self.autoscale_None(value)``.
3377+
"""
3378+
if clip is None:
3379+
clip = self.clip
3380+
else:
3381+
if not np.iterable(clip):
3382+
value = [value]*self.n_input
3383+
3384+
value = self._iterable_variates_in_data(value, self.n_input)
3385+
result = [n(v, clip=c) for n, v, c in zip(self.norms, value, clip)]
3386+
return result
3387+
3388+
def inverse(self, value):
3389+
"""
3390+
Maps the normalized value (i.e., index in the colormap) back to image
3391+
data value.
3392+
3393+
Parameters
3394+
----------
3395+
value
3396+
Normalized value. Must be of length `n_input` or have a data type with
3397+
`n_input` fields.
3398+
"""
3399+
value = self._iterable_variates_in_data(value, self.n_input)
3400+
result = [n.inverse(v) for n, v in zip(self.norms, value)]
3401+
return result
3402+
3403+
def autoscale(self, A):
3404+
"""
3405+
For each constituent norm, Set *vmin*, *vmax* to min, max of the corresponding
3406+
variate in *A*.
3407+
"""
3408+
with self.callbacks.blocked():
3409+
# Pause callbacks while we are updating so we only get
3410+
# a single update signal at the end
3411+
self.vmin = self.vmax = None
3412+
self.autoscale_None(A)
3413+
3414+
def autoscale_None(self, A):
3415+
"""
3416+
If *vmin* or *vmax* are not set on any constituent norm,
3417+
use the min/max of the corresponding variate in *A* to set them.
3418+
3419+
Parameters
3420+
----------
3421+
A
3422+
Data, must be of length `n_input` or be an np.ndarray type with
3423+
`n_input` fields.
3424+
"""
3425+
with self.callbacks.blocked():
3426+
A = self._iterable_variates_in_data(A, self.n_input)
3427+
for n, a in zip(self.norms, A):
3428+
n.autoscale_None(a)
3429+
self._changed()
3430+
3431+
def scaled(self):
3432+
"""Return whether both *vmin* and *vmax* are set on all constitient norms"""
3433+
return all([(n.vmin is not None and n.vmax is not None) for n in self.norms])
3434+
3435+
@staticmethod
3436+
def _iterable_variates_in_data(data, n_input):
3437+
"""
3438+
Provides an iterable over the variates contained in the data.
3439+
3440+
An input array with n_input fields is returned as a list of length n referencing
3441+
slices of the original array.
3442+
3443+
Parameters
3444+
----------
3445+
data : np.ndarray, tuple or list
3446+
The input array. It must either be an array with n_input fields or have
3447+
a length (n_input)
3448+
3449+
Returns
3450+
-------
3451+
list of np.ndarray
3452+
3453+
"""
3454+
if isinstance(data, np.ndarray) and data.dtype.fields is not None:
3455+
data = [data[descriptor[0]] for descriptor in data.dtype.descr]
3456+
if not len(data) == n_input:
3457+
raise ValueError("The input to this `MultiNorm` must be of shape "
3458+
f"({n_input}, ...), or have a data type with {n_input} "
3459+
"fields.")
3460+
return data
3461+
3462+
32223463
def rgb_to_hsv(arr):
32233464
"""
32243465
Convert an array of float RGB values (in the range [0, 1]) to HSV values.

lib/matplotlib/colors.pyi

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ class Normalize:
263263
@vmax.setter
264264
def vmax(self, value: float | None) -> None: ...
265265
@property
266+
def n_input(self) -> int: ...
267+
@property
268+
def n_output(self) -> int: ...
269+
@property
266270
def clip(self) -> bool: ...
267271
@clip.setter
268272
def clip(self, value: bool) -> None: ...
@@ -387,6 +391,34 @@ class BoundaryNorm(Normalize):
387391

388392
class NoNorm(Normalize): ...
389393

394+
class MultiNorm(Normalize):
395+
# Here "type: ignore[override]" is used for functions with a return type
396+
# that differs from the function in the base class.
397+
# i.e. where `MultiNorm` returns a tuple and Normalize returns a `float` etc.
398+
def __init__(
399+
self,
400+
norms: ArrayLike,
401+
vmin: ArrayLike | float | None = ...,
402+
vmax: ArrayLike | float | None = ...,
403+
clip: ArrayLike | bool = ...
404+
) -> None: ...
405+
@property
406+
def norms(self) -> tuple: ...
407+
@property # type: ignore[override]
408+
def vmin(self) -> tuple[float | None]: ...
409+
@vmin.setter
410+
def vmin(self, value: ArrayLike | float | None) -> None: ...
411+
@property # type: ignore[override]
412+
def vmax(self) -> tuple[float | None]: ...
413+
@vmax.setter
414+
def vmax(self, value: ArrayLike | float | None) -> None: ...
415+
@property # type: ignore[override]
416+
def clip(self) -> tuple[bool]: ...
417+
@clip.setter
418+
def clip(self, value: ArrayLike | bool) -> None: ...
419+
def __call__(self, value: ArrayLike, clip: ArrayLike | bool | None) -> list: ... # type: ignore[override]
420+
def inverse(self, value: ArrayLike) -> list: ... # type: ignore[override]
421+
390422
def rgb_to_hsv(arr: ArrayLike) -> np.ndarray: ...
391423
def hsv_to_rgb(hsv: ArrayLike) -> np.ndarray: ...
392424

0 commit comments

Comments
 (0)