Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

VectorMappable with data type objects#28428

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

Conversation

@trygvrad
Copy link
Contributor

@trygvradtrygvrad commentedJun 20, 2024
edited
Loading

Creation of the VectorMappable class

**NOTE: This PR builds upon the BivarColormap and MultivarColormap classes introduced in **#28454

PR summary

This PR has been in development during 2024 GSOC as a responese to#14168 Feature request: Bivariate colormapping.
This requires the introduction of multiple new classes, and changes to different parts of the codebase. It has therefore been suggested to introduce this functionality over multiple PRs, so that the changes are easier to reviewthis is the 1st of 3(?)PRs that willresolve#14168.

The end goal of this is to allow for the following funcitonality:

fig, axes = plt.subplots(1, 2, figsize = (12,4))pm = axes[0].pcolormesh((A,B,C), cmap = '3VarSubA', vmax = (0.4, 0.6, 0.5))cb0, cb1, cb2 = fig.colorbars(pm, shape = (-1,2))axes[0].set_title('Subtractive multivariate colormap')pm = axes[1].pcolormesh((A,B,C), cmap = '3VarAddA', vmax = (0.4, 0.6, 0.5))cb0, cb1, cb2 = fig.colorbars(pm, shape = (-1,2))axes[1].set_title('Additive multivariate colormap')

image

fig, axes = plt.subplots(1, 2, figsize = (12,4))pm = axes[0].pcolormesh((im0,im1), cmap = 'BiOrangeBlue', vmin = -1, vmax = 1)cax = fig.colorbar_2D(pm, shape = (-1,2))axes[0].set_title('Square 2d colormap')pm = axes[1].pcolormesh((im0,im1), cmap = 'BiCone', vmin = -1, vmax = 1)cax = fig.colorbar_2D(pm, shape = (-1,2))axes[1].set_title('Circular 2d colormap')

image

The current class hierarchy can be visualized as follows:
rect400

This PR proposes to replace ScalarMappable with a new class VectorMappable, so that multivariate functionality can be inherited by all the relevant plotting methods, as shows below. This includes support for multiple norms (the private variable._norm always stores a list), support for multiple arguments to .set_vmin(), .set_vmax(), changes to .to_rgba() to support bivariate and multivariate colormaps, as well as support functions to parse multivariate data (top level functions in cm.py).
rect484

This PR does not contain the changes needed in the plotting functions and docs. These will follow a separate PR, as outlined in the figure above. (a (slightly outdated) full implementation is available in the branchhere)

PR checklist

¹This is the 2nd out of a series of PRs, and cannot by itselfclose#14168. Most features are tested, but some private functions do not have complete tests, as they are better tested in a later PR where the private API is tested via the publicly available API (colors._ensure_multivariate_params() etc.)

@story645
Copy link
Member

story645 commentedJun 27, 2024
edited
Loading

attn@timhoffm, can you take a quick skim to see if this is a reasonable API and plan/share any concerns?

@timhoffm
Copy link
Member

The initial description says

NOTE: This PR has been superseded by#28454

Is there still something relevant here, or can we close and move the discussion to#28454.

@trygvrad
Copy link
ContributorAuthor

trygvrad commentedJun 28, 2024
edited
Loading

@timhoffm 'superseded' was maybe a bad choice of word. This PR and#28454 will work together, but Ithink there will be some conflicts if both this and#28454 are accepted (since some changes exist in both), I thereforebelieve it is easier if I migrate the changes here to a new branch that builds on top of#28454, and make a new PR (and also includes tests). However, this will take a bit of time.

For now, it I think it is a good idea to keep the discussion of this part of the API here, even if this PR is ultimately replaced by another.
We are trying to separate concerns (and also the discussions), by having#28454 be the new colormap classes, and this be the introduction of vectormappable.

I rebased this code so that it correctly builds on#28454

@trygvradtrygvradforce-pushed theVectorMappable-data-type-objects branch from8ae3a0d to6fb5884CompareJuly 8, 2024 11:41
@trygvradtrygvradforce-pushed theVectorMappable-data-type-objects branch from6fb5884 toe889929CompareJuly 9, 2024 13:28
@trygvradtrygvradforce-pushed theVectorMappable-data-type-objects branch frome889929 to81181c9CompareJuly 9, 2024 13:52
@timhoffm
Copy link
Member

timhoffm commentedJul 9, 2024
edited
Loading

Can you please try to summarize the mapping logic for multivariate and 2D colormaps. I cannot follow the architecture between the implementation details and mixing with ScalarMappable logic.

The current scalar mapping works like this. How do VectorMappable and the new colormap types change the picture?

image

Edit: I think the OP is sufficient. I‘ll have a closer look.

@trygvradtrygvradforce-pushed theVectorMappable-data-type-objects branch from81181c9 tof11c3bfCompareJuly 9, 2024 16:31
@trygvrad
Copy link
ContributorAuthor

trygvrad commentedJul 9, 2024
edited
Loading

Can you please try to summarize the mapping logic for multivariate and 2D colormaps. I cannot follow the architecture between the implementation details and mixing with ScalarMappable logic.

vectormappable

TheVectorMappable stores vector data as a dtype with multiple fields. This is so thatX.shape will return the same shape regardless of the number of variates.

This PR does not contain changes to the plotting methods, but it is intended to go something like this.

classAxes:defpcolor(self,data,cmap=None,norm=None,vmin=None,vmax=None):cmap=colors._ensure_cmap(cmap)data,norm,vmin,vmax=colors._ensure_multivariate_params(cmap.n_variates,data,norm,vmin,vmax)

Which transformsdata from what is reasonable to accept (proposed: scalar array, complex array, sequence of arrays*, dtype with multiple fields) to a dtype with the correct number of fields (as determined bycmap.n_variates).

*I prefer an input signature of (cmap.n_variates, n, m) for multivariate data, but we could use (n, m, cmap.n_variates) instead.

Edit:@timhoffm Thank you for taking a look, I didn't see your edit before I made the additional figure :D

story645 reacted with laugh emoji

@timhoffm
Copy link
Member

timhoffm commentedJul 11, 2024
edited
Loading

Disclaimer: This contains some fundamental thoughts beyond the concrete MultiVar case, but since MultiVar requires significant architecture changes any, I believe it's worth looking at the whole picture.

I have not yet come to a complete solution, but I put down my working thoughts.

  1. Looking at the structures involved, the three usecases behave alike for data channels and norms, but quite different in terms of colormaps and postprocessing:

    ScalarBivarMultivar
    data channels122..N
    norms122..N
    colormaps1x 1D1x 2D2..N x 1D
    postprocessing--merge colors

    It is reasonable to abstact the step "normalized data channels -> color" in different Colormap classes. I'm inclined to have a similar abstraction for the normalization so that we have
    image
    The norm may also have multiple channels (in the simple form just a collection of scalar norms). The mappable should not have to care about managing the details (i.e. a list of scalar norms). To be checked: How much of the ScalarMappable interface exposes details on the number of channels?

    Also t.b.d.: ScalarMappable attaches the mapping logic to artists via inheritance. Would it make sense to extract the data -> color logic into some separate "Mapper" entity (name t.b.d.)?
    image

  2. We have mutual references betweenScalarMappable andColormap
    image
    We still have to determine how this interferes with the more general mappings.

  3. A current limitation of the mapping mechanism is an assumed 1:1 relation between ScalarMappable and Norm concerning auto-scaling: The norm does not know about the data. If it has no predefined limits, they are automatically inferred from the first call. While it is possible to assign a Norm to multiple Mappables, autoscaling will thus only take into account values from the first mappable. When touching the whole mapping infrastructure anyway, can we improve on this so that a norm can conistently serve multiple mappables?

@trygvrad
Copy link
ContributorAuthor

  1. It is reasonable to abstact the step "normalized data channels -> color" in different Colormap classes. I'm inclined to have a similar abstraction for the normalization so that we have ...

It is not quite clear to me what the distinction between between theVectorMappable andMappble class would be in your case. In the weekly GSOC meetings we have considered having aVectorNorm similar to the way we have aVectorMappable (sugegsted by@story645).VectorNorm would then be a generalization ofNormalize to support multiple variates.

Having aVectorNorm class would give us certain advantages, and I believe your inclination to have aMappable class essentially deals with the same issues. From what I can tell, these are:

  • a. When using the same norm on all axes (point 3. above) the litmits should be autoset using the full data, aMappable class could manage this.
  • b. If there is a coupled norm, i.e. the normalization on axis 1 depends on the value on axis 2.
  • c. Separation of concerns - fewer if-statements inVectorMappable

The alternative, as this PR suggests, is by simply attaching a list of norms toVectorMappable. I am a proponent of this approach because I believe it makes it easier to trace the code (fewer levels of inheritance). Also, I have looked for a use case where a coupled norm would be desirable (point b. above), and I have yet to find one. An example would be if the data is represented in an orthogonal coordinate system, but the normalization should map this in a shear coordinate system. While this would make some mathematical sense, I cannot see a use case.[Note that a circular 2D colormap places different limits than a 2D square colormap, but this is handled after the norm (in the cmap), and does not require aVectorNorm. Limiting the colorspace in other ways can be done by subclassingBivarColormap orMultivarColormap (should be a tutorial).]

However, (all of) you are more experienced to evaluate what makes code more easy to maintain than I am, and if you think aMappable class is favorable in this regard, we should implement it.

  1. We have mutual references betweenScalarMappable andColormap ...

I have been thinking we need to determine how the equivalent toColorbar works in the multivariate case, but as you say this topic is probably a bit more general. I have some thoughts on this, but it might be good to discuss this in a weekly development meeting.

  1. A current limitation of the mapping mechanism is an assumed 1:1 relation between ScalarMappable and Norm concerning auto-scaling: ...

To me, the intuitive behavior has always been that each axis has a separate norm:

ax.imshow((A,B),cmap='BiOrangeBlue')# orax.imshow((A,B),cmap='BiOrangeBlue',norm='LogNorm')

should give two separate norms with independent limits.

I further believe that the intuitive action for a user to take if they want identical limits on both axis is to simply set the limits:

ax.imshow((A,B),cmap='BiOrangeBlue',vmin=-1,vmax=2)

However, advanced users might want the behavior you describe above, a shared norm, and we should therefore support the following:

n=mpl.colors.Normalize()ax.imshow((A,B),cmap='BiOrangeBlue',norm=(n,n))

Where both axis are explicitly declared to have the same norm.
With this call signature, I think it would be comparatively difficult for us to change the default (set limits only with first axis), and comparatively easy for the user to get the desired behavior, i.e.:

n=mpl.colors.Normalize()norm.vmin=np.min((A,B))norm.vmax=np.max((A,B))ax.imshow((A,B),cmap='BiOrangeBlue',norm=(n,n))

NOTE: I made acommit this morning that explicitly forbids the call following call signature:

n=mpl.colors.Normalize()ax.imshow((A,B),cmap='BiOrangeBlue',norm=n)# → ValuError('When using a colormap with more than one variate, norm must be None,# a valid string, a sequence of strings, or a sequence of mpl.Colors.Normalize objects.'

because it I found it unclear if this should be a shared norm (shared limits) or if the norm should be duplicated (independent limits), and I believe we should only accept input where the users intent is clear.

@timhoffm As I understand it, the weekly development meeting today is cancelled, but I will try to be there next week.
If you give me some more details on how you expect theMappable class to integrate (call signature?), I could prototype an alternative branch before then.

@timhoffm
Copy link
Member

Thanks for the detailed answer, I'll have a closer look later.

Re: 3. Multiple mappables with one colorbar:
I'm referring tohttps://matplotlib.org/stable/gallery/images_contours_and_fields/multi_image.html. T.b.d. maybe this can be slightly simplified nowadays, but we still have a fundamental disconnect between the images and the colorbar.

trygvrad reacted with thumbs up emoji

@trygvrad
Copy link
ContributorAuthor

T.b.d. maybe this can be slightly simplified nowadays

I took a look, and the norm now seems to connect automatically, but you still need to manually connect the images before you swap out the colormap.

If we made a 'Mappable' class, we could definitely simplify this, by having a single object that holds the norm+colormap for all images in the example above. I imagine this class would then handle everything that deals with the data→norm→colormap transform, i.e.VectorMappable.to_rgba(...) and parts ofImage.make_image(...).

I think you are right that if we want to implement this, it would be a good idea to bundle it with this PR.

Personally I would suggestNormAndColor as a name for the class (I like descriptive names), and I would suggest you can pass it as an alternative to the norm, i.e.:

nac=mpl.cm.NormAndColor(norm='LogNorm',cmap='Viridis')ax[0].imshow(A,norm=nac)ax[1].imshow(B,norm=nac)

If this rhymes with what you are thinking, I'll take a look at implementing it.

@story645
Copy link
Member

story645 commentedJul 11, 2024
edited
Loading

b. If there is a coupled norm, i.e. the normalization on axis 1 depends on the value on axis 2.

Maybe the usecase in#19515 (by@greglucas ) of choosing vmin and vmax, which I've often done that based on histograms so a linked zoom on histogram and update vmin and vmax w/ the corresponding values.

Also just to make sure I'm on the same page, I'm interpreting the figures above as:

$Bivariate: channel_1 \times channel_2 \rightarrow norm_1(channel_1) \times norm_2(channel_2) \rightarrow RGB$

$multivariate: channel_1 \times channel_2 \rightarrow norm_1(channel_1) \times norm_2(channel_2) \rightarrow RGB_1 \times RGB_2 \rightarrow RGB$

And now I'm wondering where one of the use cases I was originally thinking of fits:

$nvariate: channel_1 \times channel_2 \rightarrow norm(channel_1, channel_2) \rightarrow RGB$

ETA: nvariate is also weird cause it could maybe work w/ scalermappables and the hard part is it knowing how to input match. And yes these all have the same top level signature:
$vnorm: channel_0 \times ...\times channel_i \times ... \times channel_n \rightarrow RGB$

@trygvrad
Copy link
ContributorAuthor

trygvrad commentedJul 11, 2024
edited
Loading

@story645 The PR as it stands works like this:
$multivariate:\underbrace{channel_1×channel_2}_{VectorMappabe} \underbrace{→ norm_1×norm_2} _{norm\ layer} \underbrace{→RGB×RGB →RGB} _{cmap\ layer}$

$bivariate: \underbrace{channel_1×channel_2} _{VectorMappable} \underbrace{→ norm_1×norm_2} _{norm\ layer} \underbrace{→ RGB} _{cmap\ layer}$

as you say it does not support:
$\underbrace{channel_1×channel_2} _{VectorMappable} \underbrace{→ norm(channel_1,channel_2)} _{norm\ layer} \underbrace{→ RGB} _{cmap\ layer}$
with a 1D colormap, nor does it support
$\underbrace{channel_1×channel_2} _{VectorMappable} \underbrace{→ norm _1(channel_1,channel_2)×norm _2(channel_1,channel_2)} _{norm\ layer} \underbrace{→ RGB} _{cmap\ layer}$
with a 2D colormap, etc..

b. If there is a coupled norm, i.e. the normalization on axis 1 depends on the value on axis 2.

Maybe the usecase in#19515 (by@greglucas ) of choosing vmin and vmax, which I've often done that based on histograms so a linked zoom on histogram and update vmin and vmax w/ the corresponding values.

When I was talking about a coupled norm, I was thinking of norms that take two arguments, i.e.$norm(channel_1,channel_2)$, which this PR does not support right now.

If we want to introduce norms that take two arguments, we definitely need aNormAndColor kind of object to hold them, and classes for$norm(channel_1,channel_2)$ objects.
However, it is not clear to me what mathematical operation$norm(channel_1,channel_2)$ should perform.
@story645 do you have a specific use case (mathematical transform or example) in mind?

EDIT: I'll take a look at implementing aNormAndColor kind of object anyways, and then we will at least have the infrastructure to support$norm(channel_1,channel_2)$ kinds of things

@trygvrad
Copy link
ContributorAuthor

Re: 3. Multiple mappables with one colorbar: I'm referring tohttps://matplotlib.org/stable/gallery/images_contours_and_fields/multi_image.html. T.b.d. maybe this can be slightly simplified nowadays, but we still have a fundamental disconnect between the images and the colorbar.

I took a crack at implementing aNormAndColor class (name subject to change).
Using this as a container allows us to easily set up multiple plots that share a$data→norm→color$ pipeline.
https://matplotlib.org/stable/gallery/images_contours_and_fields/multi_image.html can now be simplified as follows:

importmatplotlibmatplotlib.use('qtagg')importmatplotlib.pyplotaspltimportnumpyasnpnp.random.seed(19680801)data=np.linspace(0.1,0.6,6)[:,np.newaxis,np.newaxis]*np.random.rand(6,10,20)nac=matplotlib.cm.NormAndColor()nac.vmin=np.min(data)nac.vmax=np.max(data)fig,axs=plt.subplots(2,3)fig.suptitle('Multiple images')fori,axinenumerate(axs.ravel()):im=ax.imshow(data[i],norm=nac)ax.label_outer()cb=fig.colorbar(im,ax=axs,orientation='horizontal',fraction=.1)plt.show()

The video shows how the images couple:

multiple_images.mp4

(Additionally, inhttps://matplotlib.org/stable/gallery/images_contours_and_fields/multi_image.html, changing the colormap on the colorbar does not propagate to the images, but these changes propagate in the video above)

This implementation also allows us to couple ND colormaps and 1D colormaps, so that changes in one propagates to the others:

importmatplotlibmatplotlib.use('qtagg')importmatplotlib.pyplotaspltimportnumpyasnpnp.random.seed(19680801)data=np.random.rand(2,10,20)fig,axes=plt.subplots(1,3,figsize=(10,3))im_2D=axes[0].imshow(data,cmap='BiOrangeBlue')im_0=axes[1].imshow(data[0],norm=im_2D.nac[0])im_1=axes[2].imshow(data[1],norm=im_2D.nac[1])cb=fig.colorbar(im_0)cb=fig.colorbar(im_1)foraxinaxes:ax.label_outer()plt.show()
coupled_bivar.mp4

@timhoffm Is this what you had in mind?

I had to build this in a branch further down the pipeline than this PR, because it needs some features of the plotting functions not available in this PR. If you want to take a look, theNormAndColor class can be found here:https://github.com/trygvrad/matplotlib/blob/Multivariate_NormAndColor_class/lib/matplotlib/cm.py

story645 reacted with eyes emoji

Creation and tests for classes containing multivariate and bivariate colormaps.
Adds support for __getitem__ on colors.BivarColormap, i.e.: BivarColormap[0] and BivarColormap[1], which returns (1D) Colormap objects along the selected axes
removal of ColormapBaseAddition of _repr_png_() for MultivarColormapaddition of _get_rgba_and_mask() for Colormap to clean up __call__()
Also adds a an improvement to colors.BIvarColormap.__getitem__() so that this returns a ListedColormap object instead of a Colormap object
Also removed all set_ functions, and replaced them with with_extremes()
@trygvrad
Copy link
ContributorAuthor

I pushed some updates to this branch, but this is largely just because I want to see the code coverage, and this is the easiest way for me to see how the code coverage changes.

Provisional names areColorizer instead ofMapper (in cm.py)
ColorizingArtist as the base class for image and collection (in artist.py).
andColorizerShim to provide access from to the variables on theColorizer from a class that subclasses bothColorizingArtist andColorizerShim [i.e. to match theScalarMappable interface] (in cm.py)
Note that the methods on theColorizerShim are not used internally, and all internal use acts onColorizer objects directly.

@trygvrad
Copy link
ContributorAuthor

We discussed this in the Matplotlib weekly meeting today, and we will split this into 2 PRs:

  1. Minimal implementation of theColorizer,ColorizingArtist andColorizerShim classes
  2. Changes needed to support multivariate data.

I'll make a comment here once 1. is ready.

timhoffm, story645, and tacaswell reacted with thumbs up emoji

@trygvradtrygvradforce-pushed theVectorMappable-data-type-objects branch fromf9b2bf0 toe271b85CompareAugust 2, 2024 09:55
VectorMappable with data type objectsCreation of the VectorMappable classcorrectetion to _ensure_multivariate_norm()since colors.Normalize does not have a copy() function, we cannot easily convert a single norm to a sequence of norms.better use of union for colormap typesDefines ColorMapType as Union[colors.Colormap, colors.BivarColormap, colors.MultivarColormap] in typing.pySuport for multivariate colormaps in imshow, pcolor and pcolormeshsafe_masked_invalid() for data types with multiple fieldsNormAndColor classcleanup of _ImageBase._make_image()Rename To Mapper and reorganizatinochanged name of new class from Mapper to Colorizer and removed calls to old apichanged name of new class from Mapper to ColorizerMapper → ColorizerScalarMappableShim → ColorizerShimColorableArtist → ColorizingArtistalso removed all internal calls using the ColorizerShim api
@trygvradtrygvradforce-pushed theVectorMappable-data-type-objects branch frome271b85 tob4af71eCompareAugust 2, 2024 09:58
@trygvradtrygvrad mentioned this pull requestAug 3, 2024
3 tasks
@trygvrad
Copy link
ContributorAuthor

I made a new PR with a minimal implementation of theColorizer class#28658
I would like to move further discussion of the implementation to#28658
and keep any further discussion of feature branch vs main etc. in#28428 (this thread)

story645 reacted with hooray emoji

@story645
Copy link
Member

and keep any further discussion of feature branch vs main etc. in#28428 (this thread)

Also, just to explain why#28658 was opened on main, consensus from the call is that we're gonna try the "merging to main" approach in the stages/PRs@trygvrad outlined.

@trygvradtrygvradforce-pushed theVectorMappable-data-type-objects branch from4a87c8b toc5b1d06CompareAugust 5, 2024 10:35
@trygvradtrygvrad mentioned this pull requestMay 5, 2025
@timhoffm
Copy link
Member

@trygvrad I think this is superseeded. Can this be closed?

@trygvrad
Copy link
ContributorAuthor

yes

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Projects

Status: Waiting for author

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Feature request: Bivariate colormapping

6 participants

@trygvrad@story645@timhoffm@jklymak@tacaswell@QuLogic

[8]ページ先頭

©2009-2025 Movatter.jp