Skip to content

Multivariate plotting in imshow, pcolor and pcolormesh #29221

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

trygvrad
Copy link
Contributor

@trygvrad trygvrad commented Dec 3, 2024

PR summary

This PR continues the work of #28658 and #28454, aiming to close #14168.

Features included in this PR:

  1. A MultiNorm class. This is a subclass of colors.Normalize and holds n_variate norms.
  2. Exposes the functionality provided by MultiNorm together with BivarColormap and MultivarColormap to the plotting functions axes.imshow(...), axes.pcolor, and `axes.pcolormesh(...)
import matplotlib.pyplot as plt
import numpy as np

im_A = np.arange(500)[np.newaxis, :]*np.ones((100, 500))
im_B = np.arange(100)[:, np.newaxis]*np.ones((100, 500))
im_A[:, :] = np.sin(im_A**0.5)**2
im_B[:, :] = np.sin(im_B**0.5)**2

fig, axes = plt.subplots(3, 1, figsize=(8, 6))

cm = axes[0].imshow(im_A, cmap='grey', vmin=0, vmax=1)
axes[0].set_title('A')

cm = axes[1].imshow(im_B, cmap='grey', vmin=0, vmax=1)
axes[1].set_title('B')

cm = axes[2].imshow((im_A, im_B),
                    cmap='BiOrangeBlue',
                    vmin=(0, 0), vmax=(1, 1))
axes[2].set_title('A & B')
for ax in axes:
    ax.set_xticks([])
    ax.set_yticks([])

image


Features not included in this PR:

  • Multivariate equivalent of fig.colorbar(...). I will make a draft for this, but I may need help with regards to the layout engine.
  • Examples. Some documentation is ready on a separate branch, see this, and this, but the PR that includes these will be made after we find a fig.colorbar(...) solution for bivariate/multivariate colormaps.
  • A selection of colormaps. I have written about this here and here. You can see the proposed colormaps/documentation here and [here](file:///home/trygvrad/trygvrad.github.io/mpl_docs/Bivariate colormap reference.html), but we will need a separate discussion on these (in particular regarding the names)
  • The multivariate implementation for axes.imshow(...) included here only accepts interpolation_stage=='rgba', and not interpolation_stage=='data'.

PR checklist

@trygvrad
Copy link
Contributor Author

trygvrad commented Dec 3, 2024

Also: this does not (yet) include changes to the docstrings, i.e. the following will need to change:

"""

Parameters
----------
X : array-like or PIL image
    The image data. Supported array shapes are:

    - (M, N): an image with scalar data. The values are mapped to
      colors using normalization and a colormap. See parameters *norm*,
      *cmap*, *vmin*, *vmax*.
    - (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
    - (M, N, 4): an image with RGBA values (0-1 float or 0-255 int),
      i.e. including transparency.

    The first two dimensions (M, N) define the rows and columns of
    the image.

    Out-of-range RGB(A) values are clipped.
"""

to include a line like

"""
etc.
    - (K, M, N): multiple images with scalar data when combined with a
    multivariate or bivariate colormap using a MultiNorm.
"""

@trygvrad
Copy link
Contributor Author

trygvrad commented Jan 6, 2025

I updated the docstrings, so now this PR should be ready for review.

There is an error with the docs on circleci, but I haven't found a link between the error and the changes made in this PR, so I believe it to be unrelated.

@tacaswell tacaswell modified the milestones: v3.10.0-doc, v3.11.0 Jan 23, 2025
@trygvrad
Copy link
Contributor Author

trygvrad commented Feb 8, 2025

@story645
Thank you for the feedback :)
I believe I have addressed all the comments now (I'll mark them as resolved in the coming days).

also: this will need a squash-merge

# pass scalar data
# and already formatted data
return data
elif data.dtype in [np.complex64, np.complex128]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option 1 please.

if A.ndim == 3 and A.shape[-1] == 1:
A = A.squeeze(-1) # If just (M, N, 1), assume scalar and apply colormap.
if not (A.ndim == 2 or A.ndim == 3 and A.shape[-1] in [3, 4]):
if A.ndim == 3 and A.shape[0] == 2:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to verify, these are here as a hint to the user to use multi-variate color maps and data that would have failed before would still fail now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is the intention

raise TypeError(f"Image data of dtype {A.dtype} cannot be "
f"converted to float")
else:
for key in A.dtype.fields:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this fails to catch n_input >1 + non-float-like + non-structured data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be true.
I'm trying to think of a data type that can not be cast to float and is not structured in order to test this

If you try something with data type object, it depends a bit on the shape, all the cases I have tried have been caught:

fig, ax = plt.subplots()

im = np.empty((5,5), dtype='object')
ax.imshow(im, cmap='BiOrangeBlue')
> ValueError: Invalid data entry for mutlivariate data. The data must contain complex numbers, or have a first dimension 2, or be of a dtype with 2 fields

im = np.empty((2,5,5), dtype='object')
ax.imshow(im, cmap='BiOrangeBlue')
> TypeError: Image data of dtype [('f0', 'O'), ('f1', 'O')] cannot be converted to a sequence of floats

Where the first error originates in colorizer._ensure_multivariate_data()

Do you have some idea of other tests I should run, or do we want to have specific tests for _normalize_image_array()?

"""
Check validity of image-like input *A* and normalize it to a format suitable for
Image subclasses.
"""
A = mcolorizer._ensure_multivariate_data(n_input, A)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor thing. bit jarring that sometimes the API in f(n_input, A) and othertimes f(A, n_input). I think we should prefer f(A, n_input) everywhere for internal consistency and no-API change on existing functions (like the one we are in now).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good comment :) I will try to address it soon

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the call signature of colorizer._ensure_multivariate_data(...) to colorizer._ensure_multivariate_data(A, n_input)

I looked to see if there were any other functions with a similar signature which should be flipped, but I did not find any in my quick search.

@story645
Copy link
Member

If you've got the bandwidth, @greglucas can you give this a look (skim) since you've been working on colormapping?

@trygvrad
Copy link
Contributor Author

@greglucas It would be great to know if/when you will have time to look at this :)
Just let us know if you don't have the bandwidth right now, and we can poke someone else

@trygvrad
Copy link
Contributor Author

trygvrad commented Mar 27, 2025

@story645 @tacaswell would you two be willing to finish your review of this?
Or perhaps @ksunden?

EDIT: my apologies @story645 I didn't see you had just assigned this when I wrote my comment :)

@story645
Copy link
Member

story645 commented Apr 1, 2025

@anntzer asked if the more cleanup/refactor/not directly necessary for implementation parts of this PR could be spun off into a different set and if this PR could get rebased to clean up the commit history.

@anntzer
Copy link
Contributor

anntzer commented Apr 1, 2025

(Just to give an example, I'm currently splitting off #29807 into a bunch of smaller preparatory PRs: #29817, #29828, #29838, #29843, #29829.)

@trygvrad
Copy link
Contributor Author

trygvrad commented Apr 1, 2025

@anntzer I'll see what I can do.
I thinking of splitting it as:

1. MultiNorm class
2a. Add support for multivariate pcolor and pcolormesh + tests 
2b. Add support for multivariate imshow + tests

I'll also see if there are some misc changes that can be a separate PR between 1 and 2.
In any case, 1. will have to be merged before 2a and b. Would you like me to make all the branches at once, or does it make more sense to make the PR for 2a and b once 1. is merged?

@anntzer
Copy link
Contributor

anntzer commented Apr 2, 2025

I guess it doesn't really matter, because we know the end result thanks to this PR anyways.
(I won't be able to review anything before next week, in any case.)

@trygvrad
Copy link
Contributor Author

trygvrad commented Apr 6, 2025

@anntzer @story645 I opened PRs:
#29876 → The MultiNorm class
#29877 → Other changes needed to expose the functionality.

Hopefully this is easier to review :)

trygvrad and others added 12 commits April 13, 2025 13:13
If an image is shown, and the user hovers over the image, the value at that coordinate should be displayed. The number formating is handled by colorizer.Colorizer._format_cursor_data_override(). This is now updated to also handle the case where a MultiNorm is used (i.e. a bivariat or multivariate colormap), in which case multiple values are displayed.
The previous version of this test passed for numpy 1.x but not for 2.x. This should pass for both
Co-authored-by: Thomas A Caswell <tcaswell@gmail.com>
changed from  colorizer._ensure_multivariate_data(n_input, A)
to  colorizer._ensure_multivariate_data(A, n_input)
@trygvrad trygvrad force-pushed the multivariate-norm branch from 29093b3 to fa40bef Compare April 13, 2025 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: Bivariate colormapping
4 participants