Multivar imshow - #30597
Conversation
be62c09 to
0f557d5
Compare
0f557d5 to
e2aab93
Compare
e2aab93 to
23e6caf
Compare
1efde4a to
35746f5
Compare
35746f5 to
c334208
Compare
| fig, axes = plt.subplots(2, 3) | ||
|
|
||
| # interpolation='nearest' to reduce size of baseline image | ||
| axes[0, 0].imshow(x_1, interpolation='nearest', alpha=0.5) |
There was a problem hiding this comment.
are the other interpolations tested?
There was a problem hiding this comment.
Nope!,
I'm changing one of tests so that it is :)
There was a problem hiding this comment.
feeling silly but can't find the test with this change
There was a problem hiding this comment.
doesn't imshow usually default to nearest though? https://matplotlib.org/devdocs/api/_as_gen/matplotlib.axes.Axes.imshow.html#matplotlib-axes-axes-imshow
Like what happens if interpolation is set to none?
ksunden
left a comment
There was a problem hiding this comment.
General thoughts on return types:
Doing things like float | tuple[float, ...] as is done for several things here (vmin/vmax, clip, etc) is potentially problematic.
Humans may easily work with that, but type checkers will likely yell that they didn't check for all possible outcomes
None is a bit of a special case in being more acceptable (easier to check, etc)
Consider moving these in new code to always return a tuple (even if single element) This keeps the branching needed to a minimum and is not too cumbersome to work for in the single variable case.
Obviously, existing APIs need to maintain back-compat, so this is limited to new code.
Consider whether conceptually an empty tuple is what is truly meant by the None case, but if it is not, retain None
We discussed change the behaviour of colorizer to always return tuples on the call last week. The relevant moving parts here are:
The Norm ABC must be typed as follows for backwards compatibility: For the Colorizer, I think it makes sense to force tuples on the getter, but allow both on the setter: For the _ColorizingInterface we have two options. def get_clim(self):
"""
Return the values (min, max) that are mapped to the colormap limits.
This function is not available for multivariate data.
"""
if self._colorizer.norm.n_components > 1:
raise AttributeError("`.get_clim()` is unavailable when using a colormap "
"with multiple components. Use "
"`.colorizer.get_clim()` instead.")
return self.colorizer.norm.vmin, self.colorizer.norm.vmaxOne reason why I favor option B, is that set_clim is already sufficiently complicated, because for scalar data it allows both signatures: |
d890452 to
b570ecc
Compare
b570ecc to
053e524
Compare
story645
left a comment
There was a problem hiding this comment.
Sorry for the very long delay in reviewing. Minor nits but I think this is fine otherwise.
| fig, axes = plt.subplots(2, 3) | ||
|
|
||
| # interpolation='nearest' to reduce size of baseline image | ||
| axes[0, 0].imshow(x_1, interpolation='nearest', alpha=0.5) |
There was a problem hiding this comment.
feeling silly but can't find the test with this change
Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
timhoffm
left a comment
There was a problem hiding this comment.
Mostly small changes that I noticed when re-reading the PR. Let's still get them in. Please add them as an additional commit for easier review. We'll squash-merge in the end.
| C = mcolorizer._ensure_multivariate_data(args[-1], | ||
| colorizer.cmap.n_variates) |
There was a problem hiding this comment.
I feel _ensure_multivariate_data is not a good name because:
- "ensure" has more connotation of validation, not necessarily conversion
- this does not necessarily output multivariate data.
Good naming is hard and I propose to do this as a follow-up as this is internal and has been here before the PR.
|
@timhoffm
Yes we support structured data as well. On a related note the internal How should we add the option of structured data to the top level functions?
Yes, this has been discussed, and it this keeps coming back up. I believe the primary discussions on this was at the weekly meeting around this time. I know I have made multiple posts on this before, but I have a difficult time finding them among all the different PRs.
I'm changing this to will cause an exception on main, while only if the cmap argument is a valid scalar colormap is the paramater ignored, i.e.:
The multivariate pipeline is triggered by a valid multivariate colormap, thus we have: i.e.: |
|
@timhoffm A follow up on this would be much appreciated :) |
@timhoffm I really need you to finish this review/approve this so that we can get it merged and other people can start reviewing #31214 |
Of the top of my head, I see the primary motivation in that (K, M, N) is the array shape of a list of component arrays: For RGB, the shape would be (M, N, 3), and I'm inclined to draw an analogy from the color channels to components. This would speak for (M, N, K). But the more important argument would be: What is the typical data structure people already have when they are working with multivariate data? - And I'm completely blank here. Do you have insights?
This also means, if you have multivariate data, you must always specify a colormap. Is this documented explicitly? If not please add it because that's important to know. It's probably ok to request this as there would need to be different multivar colormaps depending on the data. OTOH it may be nice to be able to drop some multivar data and matplotlib figures out a nice visualization. This could be added in the future; i.e. infer multivar or not primarily from the data structure; in case of ambiguit, e.g. (3, 3, 3), check the colormap, and if none was given, fall back to a scalar interpretation. This would be a straight forward extension of the current logic. |
Yes. |
|
@timhoffm Thank you for coming back to this :)
This is a good point. I was thinking the docstring could get this across, the I do not think I was sufficiently clear with the language. This should be better: NOTE: I also change the section that describes the array in the docstring of pcolor so that it matches the docstring of pcolormesh. Both support RGB image data, but this was undocumented in the docstring for pcolor. We have the option to detect when the user might be trying to use this feature in
I can mostly speak from my own experience, and I have had use for this feature in two contexts:
In both cases, I end up with two separate handles, data_A, data_B, and the call signature (I started implementing multivariate color mapping in matplotlib because I used to work with dark-field X-ray microscopy which typically produces 4D datasets that reduce to a series of 2D datasets through moment analysis (intensity, center of mass in x[orientation], center of mass in theta [strain], 2nd moments, ...), and we needed 2D colormaps to visualize them. Note that we would often plot things that are qualitatively very different together, such as orientation[mrad] and strain[dimensionless], in order to look for correlations. We would use different computational pipelines to get to the different moments, and represent them with different variable names.) I have limited experience with fluorescence microscopy, but I think it is worth noting that the typical format OMETIFF stores data as (T, C, Z, Y, X), where T is typically time, and C is typically the color [fluorophore? wavelength?]. Both T, C, and Z follow before Y and X because the microscope always only reads 2D images sequentially, and this becomes the natural way to store the data as it comes out of the instruement. The only argument I have seen for (N, M, K), is the analogy to RGB images, however to me this is a very weak argument, as the two approaches are trying to achieve very different things. You should not visualize data from an RGB camera using multivariate color mapping, and likewise, you should not map some arbitrary data space to RGB – doing so is likely to cause you to misinterpret your data. With this in mind, have different call signatures for the two approaches is to me a feature to help avoid mistakes. |
| - a (K, M, N) scalar array or a structured (M, N) array with K fields: | ||
| a K-component M*N mesh for multivariate colormapping. A valid | ||
| `.BivarColormap` (K=2) or K-component `.MultivarColormap` must be | ||
| specified using the *cmap* keyword argument. |
There was a problem hiding this comment.
| - a (K, M, N) scalar array or a structured (M, N) array with K fields: | |
| a K-component M*N mesh for multivariate colormapping. A valid | |
| `.BivarColormap` (K=2) or K-component `.MultivarColormap` must be | |
| specified using the *cmap* keyword argument. | |
| - (K, M, N) scalar array | |
| - structured (M, N) array with K fields | |
| - K-component M*N mesh | |
| A valid `.BivarColormap` (K=2) or K-component `.MultivarColormap` must be | |
| specified using the *cmap* keyword argument. |
Wondering if something like this might be slightly cleaner cause I'm unclear about the various allowed k dimensional inputs.
There was a problem hiding this comment.
I think you are reading the first two lines as three options, rather than two, and if you are reading it this way, it means the current text is not sufficiently clear. Let me see if I can reformulate it in a less confusing way.
@story645 how about this?
- a (K, M, N) scalar array or a structured (M, N) array with K fields.
The K channels are mapped to colors using a `.MultiNorm` and a
`.BivarColormap` (K=2) or K-component `.MultivarColormap`.
This input option is only available when a `.BivarColormap` or
`.MultivarColormap` is provided to the *cmap* keyword argument.
This formulation follows the same structure as the bullet point above to make it easier to parse:
- (M, N) or M*N: a mesh with scalar data. The values are mapped to
colors using normalization and a colormap. See parameters *norm*,
*cmap*, *vmin*, *vmax*.
I would prefer to keep everything regarding multivariate color mapping behind one bullet, so that readers who do not want this can easily skip the entire bulletpoint.
|
This needs to pass PR cleanliness but otherwise looks good to me too. @trygve are you waiting on input from anybody else? |
I think this can be squash merged now, and that should take care of the cleanliness :) |
|
@trygvrad thanks for the thorough discussions and for going through all the review rounds with us! This has been a lot of effort, but I'm convinced we're on a good path for consistently adding multivariate plotting so that it will be intuitive and easy to use within matplotlib. 🚀 |

Exposes the functionality of
MultiNorm,BivarColormapandMultivarColormapto the top level plotting functionsax.imshow(),ax.pcolor()andax.pcolormesh(). This closes #30526, see Bivariate and Multivariate ColormappingAs a side-effect of the pcolor/pcolormesh implementation,
Collectionalso gets the new functionality.In short, this PR allows you to plot multivariate data more easily, but it does not:
These will come in later PRs. See Bivariate and Multivariate Colormapping
Examples demonstrating new functionality: