-
-
Notifications
You must be signed in to change notification settings - Fork 7.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 our terms of service and privacy 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
Conversation
lib/matplotlib/colors.py
Outdated
in the case where an invalid string is used. This cannot use | ||
`_api.check_getitem()`, because the norm keyword accepts arguments | ||
other than strings. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused because this function is only called for isinstance(norm, str)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this function exists because the norm keyword accepts Normalize
objects in addition to strings.
This is fundamentally the same error you get if you give an invalid norm to a Colorizer
object.
In main, the @norm.setter
on colorizer.Colorizer
reads:
@norm.setter
def norm(self, norm):
_api.check_isinstance((colors.Normalize, str, None), norm=norm)
if norm is None:
norm = colors.Normalize()
elif isinstance(norm, str):
try:
scale_cls = scale._scale_mapping[norm]
except KeyError:
raise ValueError(
"Invalid norm str name; the following values are "
f"supported: {', '.join(scale._scale_mapping)}"
) from None
norm = _auto_norm_from_scale(scale_cls)()
...
The _get_scale_cls_from_str()
exists in this PR because this functionality is now needed by both colorizer.Colorizer.norm()
and colors.MultiNorm
.
Note this PR does not include changes to colorizer.Colorizer.norm()
so that it makes use of _get_scale_cls_from_str()
. These changes follow in the next PR: #29877 .
55b85e3
to
f42d65b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some minor points, plus re-pinging @timhoffm in case he has an opinion re: n_input / input_dims naming?
See #29876 (comment) |
Thank you @timhoffm |
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is broadcasting reasonable here? I would assume that most MultiNorms have different scales and thus need per-element entries anyway. It could also be an oversight to pass a single value instead of multiple values.
I'm therefore tempted to not allow scalars here but require exactly n_variables values. A more narrow and explicit interface may be the better start. We can always later expand the API to broadcast scalars if we see that's a typical case and reasonable in terms of usability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@timhoffm Perhaps this is also a topic for the weekly meeting :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm perfectly fine with removing this here, and perhaps that is a good starting point.
My entry into this topic was a use case (dark-field X-ray microscopy, DFXRM) where we typically want vmax0 = vmax1 = -vmin0 =-vmin1
, i.e. equal normalizations, and centered on zero, and given that entry point it felt natural to me to include broadcasting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need a decision here. I slightly favor the more restrictive version of always requiring a tuple, but could be convinced otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for clarification, any iterable, or strictly tuple?
I'm thinking any iterable. In any case, I can remove the broadcasting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, any iterable, but internally coerced to tuple - as with norms in __init__
.
6b86d63
to
32247f5
Compare
This is on hold until we sort out #30149 (Norm Protocol) |
@timhoffm Thank you for taking the time to give detailed comments! |
@timhoffm You made a comment here #29876 (comment):
The variable has since changed name to The current status is the public property: @property
def n_components(self):
"""Number of norms held by this `MultiNorm`."""
return len(self._norms) I think we actually have 3 options:
This impacts how we communicate with the user via docstrings, i.e. in Here we could have:
i.e. we can choose to make Once the top level plotting functions are made, there will be a check if the data pipeline is consistent, i.e. On the other hand, while I am quite certain that mulitvariate plotting functionality will be used by a number of users, it is unclear to me if any/how many will build advanced functionality that requires access to @timhoffm Let me know if I should remove |
ab61439
to
910b343
Compare
I think making it public is the right way to go. While it slightly complicates the mental model for all the current 1d norms, it is by design that it’s now only the special case and hence it is slightly more complicated. |
910b343
to
dbeca30
Compare
lib/matplotlib/colors.py
Outdated
""" | ||
Parameters | ||
---------- | ||
norms : list of (str, `Normalize` or None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we accept None
here? I don't see an advantage of [None, None]
over ["linear", "linear"]
. Quite the opposite, the first one is less readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe in case folks want to update the list later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't make sense to me. (1) None
is resolved to Normalize
immediately. (2) Not sure why one wanted to lated update but that's possible either way.
I rather suspect that it's because it along the lines of: In 1D some other places - e.g. ScalarMappable.set_norm
- accept None. But likely the othe places only accept it intentionally or unintentionally because norm=None is somewhere a default kwarg and that is passed through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I rather suspect that it's because it along the lines of: In 1D some other places - e.g. ScalarMappable.set_norm - accept None. But likely the othe places only accept it intentionally or unintentionally because norm=None is somewhere a default kwarg and that is passed through.
Exactly this.
My thinking was that a common use case is not to supply a norm:
ax.imshow((A, B), cmap='BiOrangeBlue')
In this case the norm
keyword (default: None
) is singular, but to be compatible with the data/cmap it needs to have a length of 2, so in this case the norm keyword is repeated and becomes [None, None]
, and this then gets passed to the MultiNorm
constructor.
In any case, it is better if, as you say we only accept strings.
I will make the change later.
see colorizer.py → _ensure_norm(norm, n_variates=n_variates)
here for the proposed implementation. (This can also be simplified if we no longer accept None
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't make sense to me. (1) None is resolved to Normalize immediately. (2) Not sure why one wanted to lated update but that's possible either way.
What happens with ['linear', None]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be parsed to ['linear', 'linear']
, but will now (with the update) produce an error.
If we find we need this functionality, we can always bring it back in a later PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So how do you denote ['linear', mpl.NoNorm()]
? is that ['linear', 'none']
?
23777b3
to
3a4f6b9
Compare
Thank you @timhoffm , the changes should now be in :) |
dd8f0c6
to
67b8264
Compare
This commit merges a number of commits now contained in https://github.com/trygvrad/matplotlib/tree/multivariate-plot-prapare-backup , keeping only the MultiNorm class
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
81ab0d5
to
babbee0
Compare
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
2707405
to
da1ac73
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @trygvrad! I think we're almost there. You can see the progress in moving from architecture and design questions to docs. :)
|
||
result = tuple(n(v, clip=c) for n, v, c in zip(self.norms, values, clip)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit. As with inverse
we don't need separation of the commands by empty lines.
result = tuple(n(v, clip=c) for n, v, c in zip(self.norms, values, clip)) | |
result = tuple(n(v, clip=c) for n, v, c in zip(self.norms, values, clip)) |
- If iterable, must be of length `n_components` | ||
- If structured array, must have `n_components` fields. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameters | ||
---------- | ||
A |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A | |
A : array-like |
values : array-like | ||
The input data, as an iterable or a structured numpy array. | ||
- If iterable, must be of length `n_components` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- If iterable, must be of length `n_components` | |
- If iterable, must be of length `n_components`. Each element can be a | |
scalar or array-like and is normalized through the correspong norm. |
Generally, we should describe the inputs and outputs in more detail. Holds also for inverse
, autoscale
and autoscale_None
. I'm a bit torn whether we should do this in every functions or once in the class docstring.
The input data, as an iterable or a structured numpy array. | ||
- If iterable, must be of length `n_components` | ||
- If structured array, must have `n_components` fields. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- If structured array, must have `n_components` fields. | |
- If structured array, must have `n_components` fields. Each field | |
is normalized through the the corresponding norm. |
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We still need a decision here. I slightly favor the more restrictive version of always requiring a tuple, but could be convinced otherwise.
PR summary
This PR continues the work of #28658 and #28454, aiming to close #14168. (Feature request: Bivariate colormapping)
This is part one of the former PR, #29221. Please see #29221 for the previous discussion
Features included in this PR:
MultiNorm
class. This is a subclass ofcolors.Normalize
and holdsn_variate
norms.MultiNorm
classFeatures not included in this PR:
MultiNorm
together withBivarColormap
andMultivarColormap
to the plotting functionsaxes.imshow(...)
,axes.pcolor
, and `axes.pcolormesh(...)