-
-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Multivar imshow #30597
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?
Multivar imshow #30597
Conversation
be62c09 to
0f557d5
Compare
0f557d5 to
e2aab93
Compare
e2aab93 to
23e6caf
Compare
1efde4a to
35746f5
Compare
35746f5 to
c334208
Compare
| if len(x.dtype.descr) == 1: | ||
| # Arrays with dtype 'object' get returned here. | ||
| # For example the 'c' kwarg of scatter, which supports multiple types. | ||
| # `plt.scatter([3, 4], [2, 5], c=[(1, 0, 0), 'y'])` | ||
| return x | ||
| else: | ||
| # In case of a dtype with multiple fields | ||
| # for example image data using a MultiNorm | ||
| try: | ||
| mask = np.empty(x.shape, dtype=np.dtype('bool, '*len(x.dtype.descr))) | ||
| for dd, dm in zip(x.dtype.descr, mask.dtype.descr): | ||
| mask[dm[0]] = ~np.isfinite(x[dd[0]]) | ||
| xm = np.ma.array(x, mask=mask, copy=False) | ||
| except TypeError: | ||
| return x | ||
| return xm |
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.
Does this get recycled in other places and if so should it be factored into it;s own private function?
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 don't think this is used anywhere else
lib/matplotlib/axes/_axes.py
Outdated
| Only 'data' is available when using `~matplotlib.colors.BivarColormap` | ||
| or `~matplotlib.colors.MultivarColormap` |
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.
what do you mean by data?
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.
'data' refers to the keyword argument interpolation_stage
interpolation_stage : {'auto', 'data', 'rgba'}, default: 'auto'
Supported values:
- 'data': Interpolation is carried out on the data provided by the user
This is useful if interpolating between pixels during upsampling.
- 'rgba': The interpolation is carried out in RGBA-space after the
color-mapping has been applied. This is useful if downsampling and
combining pixels visually.
- 'auto': Select a suitable interpolation stage automatically. This uses
'rgba' when downsampling, or upsampling at a rate less than 3, and
'data' when upsampling at a higher rate.
See :doc:`/gallery/images_contours_and_fields/image_antialiasing` for
a discussion of image antialiasing.
Only 'data' is available when using `~matplotlib.colors.BivarColormap`
or `~matplotlib.colors.MultivarColormap`
I'm changing the last lines to:
When using a `~matplotlib.colors.BivarColormap` or
`~matplotlib.colors.MultivarColormap`, 'data' is the only valid
interpolation_stage.
in an attempt to increase clarity
lib/matplotlib/axes/_axes.py
Outdated
| C : 2D or 3D array-like | ||
| The color-mapped values. Color-mapping is controlled by *cmap*, | ||
| *norm*, *vmin*, and *vmax*. | ||
| *norm*, *vmin*, and *vmax*. 3D arrays are supported only if the | ||
| cmap supports v channels, where v is the size along the first axis. | ||
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 think (?, ?, ?) notation might help make this clearer?
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.
good idea. Does the following work for you?
Parameters
----------
C : 2D (I, J) or 3D (v, I, J) array-like
The color-mapped values. Color-mapping is controlled by *cmap*,
*norm*, *vmin*, and *vmax*. 3D arrays are supported only if the
cmap supports v channels.
X, Y : array-like, optional
The coordinates of the corners of quadrilaterals of a pcolormesh::
(X[i+1, j], Y[i+1, j]) (X[i+1, j+1], Y[i+1, j+1])
●╶───╴●
│ │
●╶───╴●
(X[i, j], Y[i, j]) (X[i, j+1], Y[i, j+1])
Note that the column index corresponds to the x-coordinate, and
the row index corresponds to y. For details, see the
:ref:`Notes <axes-pcolormesh-grid-orientation>` section below.
If ``shading='flat'`` the dimensions of *X* and *Y* should be one
greater than those of *C*, and the quadrilateral is colored due
to the value at ``C[i, j]``. If *X*, *Y* and *C* have equal
dimensions, a warning will be raised and the last row and column
of *C* will be ignored.
I'm using uppercase I and J in C : 2D (I, J) or 3D (v, I, J) array-like as these refer to the length, while the lowercase versions are used further down for the corresponding indexes.
lib/matplotlib/axes/_axes.py
Outdated
| if colorizer is None: | ||
| cmap = mcolorizer._ensure_cmap(cmap, accept_multivariate=True) | ||
| C = mcolorizer._ensure_multivariate_data(args[-1], cmap.n_variates) | ||
| else: | ||
| C = mcolorizer._ensure_multivariate_data(args[-1], | ||
| colorizer.cmap.n_variates) |
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 colorizer is None: | |
| cmap = mcolorizer._ensure_cmap(cmap, accept_multivariate=True) | |
| C = mcolorizer._ensure_multivariate_data(args[-1], cmap.n_variates) | |
| else: | |
| C = mcolorizer._ensure_multivariate_data(args[-1], | |
| colorizer.cmap.n_variates) | |
| if colorizer is None: | |
| cmap = mcolorizer._ensure_cmap(cmap, accept_multivariate=True) | |
| colorizer = | |
| C = mcolorizer._ensure_multivariate_data(args[-1], | |
| colorizer.cmap.n_variates) |
is there a reason to not instantiate the colorizer here?
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 believe it was designed this way to ensure error handling was done in the same way as previously, but I was able to rework it to a cleaner state by doing
mcolorizer.ColorizingArtist._check_exclusionary_keywords(colorizer,
vmin=vmin, vmax=vmax,
norm=norm, cmap=cmap)
before the creation of the QuadMesh rather than
collection._check_exclusionary_keywords(colorizer, vmin=vmin, vmax=vmax)
after.
| if colorizer is None: | ||
| cmap = mcolorizer._ensure_cmap(cmap, accept_multivariate=True) | ||
| C = mcolorizer._ensure_multivariate_data(args[-1], cmap.n_variates) | ||
| else: | ||
| C = mcolorizer._ensure_multivariate_data(args[-1], | ||
| colorizer.cmap.n_variates) | ||
|
|
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.
potentially turn into a helper function if it repeats?
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.
The following lines are repeated for pcolor and pcolormesh:
mcolorizer.ColorizingArtist._check_exclusionary_keywords(colorizer,
vmin=vmin, vmax=vmax,
norm=norm, cmap=cmap)
if colorizer is None:
colorizer = mcolorizer.Colorizer(cmap=cmap, norm=norm)
C = mcolorizer._ensure_multivariate_data(args[-1],
colorizer.cmap.n_variates)We could make this a helper function :)
If so, should it be part of the Axes class? and do you have a suggestion for a name?
EDIT: this is after modifying it in accordance with one of your comments above :)
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are the other interpolations tested?
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.
Nope!,
I'm changing one of tests so that it is :)
lib/matplotlib/image.py
Outdated
| raise ValueError("'data' is the only valid interpolation_stage " | ||
| "when using multiple color channels, not " | ||
| f"{interpolation_stage}") |
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.
flip this around to start with the error then reason then solution
lib/matplotlib/image.py
Outdated
| A_resampled = [_resample(self, a.astype(_get_scaled_dtype(a)), | ||
| out_shape, t) | ||
| for a in arrs] |
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_resampled = [_resample(self, a.astype(_get_scaled_dtype(a)), | |
| out_shape, t) | |
| for a in arrs] | |
| A_resampled = [_resample(self, | |
| a.astype(_get_scaled_dtype(a)), out_shape, t) | |
| for a in arrs] |
just that the line break here is weird
lib/matplotlib/image.py
Outdated
| mask = (np.where(self._getmaskarray(A), np.float32(np.nan), | ||
| np.float32(1)) |
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.
| mask = (np.where(self._getmaskarray(A), np.float32(np.nan), | |
| np.float32(1)) | |
| mask = (np.where(self._getmaskarray(A), np.float32(np.nan), np.float32(1)) |
lib/matplotlib/image.py
Outdated
| return fig | ||
|
|
||
|
|
||
| def _get_scaled_dtype(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.
make it an inline function inside _make_image
ksunden
left a comment
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.
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
lib/matplotlib/colorizer.pyi
Outdated
| def norm(self) -> colors.Norm: ... | ||
| @norm.setter | ||
| def norm(self, norm: colors.Norm | str | None) -> None: ... | ||
| def norm(self, norm: colors.Norm | str | tuple[str] | None) -> 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.
tuple[str, ...]
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
Exposes the functionality of
MultiNorm,BivarColormapandMultivarColormapto the top level plotting functionsax.imshow(),ax.pcolor()andax.pcolormesh(). This coloses #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: