Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
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
base:main
Are you sure you want to change the base?
MultiNorm class#29876
Changes fromall commits
5e0266a
6985111
f42d65b
73713e7
78b173e
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -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)`` | ||||||
name : str, optional | ||||||
The name of the colormap family. | ||||||
""" | ||||||
@@ -1595,15 +1595,15 @@ | ||||||
Parameters | ||||||
---------- | ||||||
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 | ||||||
from the tuple. | ||||||
over:tuple of :mpltype:`color`, default: None | ||||||
If tuple, the ``over`` value of each component is set with the values | ||||||
from the tuple. | ||||||
Returns | ||||||
@@ -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 | ||||||
@property | ||||||
def vmin(self): | ||||||
return self._vmin | ||||||
@@ -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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more.
Suggested change
| ||||||
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): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This is equivalent, I think? Suggested change
| ||||||
raise ValueError("A MultiNorm must be assigned multiple norms") | ||||||
norms = [*norms] | ||||||
for i, n in enumerate(norms): | ||||||
if n is None: | ||||||
norms[i] = Normalize() | ||||||
elif isinstance(n, str): | ||||||
scale_cls = _get_scale_cls_from_str(n) | ||||||
norms[i] = mpl.colorizer._auto_norm_from_scale(scale_cls)() | ||||||
trygvrad marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||||||
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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Does this not need a check that the length matches | ||||||
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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This seems to be the only docstring for a data parameter that mentions | ||||||
""" | ||||||
with self.callbacks.blocked(): | ||||||
A = self._iterable_variates_in_data(A, self.n_variables) | ||||||
for n, a in zip(self.norms, A): | ||||||
n.autoscale_None(a) | ||||||
self._changed() | ||||||
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]) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. A bit too many parentheses, not that it really matters. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||||||
if len(data) != n_variables: | ||||||
raise ValueError("The input to this `MultiNorm` must be of shape " | ||||||
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. | ||||||
@@ -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 | ||||||
trygvrad marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||||||
> '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 |
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.