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

Mplot3d masked surface#18114

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

Closed
tomneep wants to merge7 commits intomatplotlib:masterfromtomneep:mplot3d_masked_surface
Closed

Mplot3d masked surface#18114

tomneep wants to merge7 commits intomatplotlib:masterfromtomneep:mplot3d_masked_surface

Conversation

tomneep
Copy link
Contributor

PR Summary

This allowsnp.ma.MaskedArray to be used as the data inplot_surface().
I expect that the solution is the same as the one proposed in#487.
The colormap min/max is normalised to the data that is shown in the plot, as is the zlim.

Demo

Taking an example inspired by that of#487 produces the following

importnumpyasnpimportmatplotlib.pyplotaspltx=np.arange(1,10,1)y=np.arange(1,10,1)x,y=np.meshgrid(x,y)z=x**3+y**3-500# To test what happens when one of the masked values# is far off the other points (z-axis range, colormap, etc)z[0,0]=-1e9z[-1,-1]=1e9z=np.ma.masked_array(z,z<0)z.mask[-1,-1]=1fig=plt.figure(figsize=(8,8))ax1=fig.add_subplot(221,projection='3d')ax2=fig.add_subplot(222)surf=ax1.plot_surface(x,y,z,rstride=1,cstride=1,linewidth=0,cmap='inferno')plt.colorbar(surf,ax=ax1,shrink=0.7,orientation='horizontal')ax2.imshow(z,cmap='inferno')fig.show()

surface_mask

PR Checklist

  • Has Pytest style unit tests
  • Code isFlake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/next_api_changes/* if API changed in a backward-incompatible way

Copy link
Member

@dstansbydstansby left a comment

Choose a reason for hiding this comment

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

Thanks for opening the PR! I've added a couple of comments/questions, but this looks 👍 overall

@@ -1559,8 +1559,8 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None,
"Z contains NaN values. This may result in rendering "
"artifacts.")

# TODO: Support masked arrays
X, Y, Z = np.broadcast_arrays(X, Y, Z)
mask = np.ma.getmaskarray(Z)
Copy link
Member

Choose a reason for hiding this comment

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

What happens if X or Y have a mask? My guess is we should take the union of all the masks to compute an 'overall' mask here.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Good question, I see thatpcolormesh doesn't accept maskedX andY values (but it looks likepcolor might). I've not testedcontour but itlooks like masks are ignored onX andY values?

@sdbbs
Copy link

I would just like to mention: patching this PR with the following hack (easiest I can do is diff in shell code):

$ wget https://raw.githubusercontent.com/tomneep/matplotlib/d73d20ad25d4341b1003fcfe04b17779a0e91207/lib/mpl_toolkits/mplot3d/axes3d.py....2021-04-09 17:10:45 (6.90 MB/s) - ‘axes3d.py’ saved [103843/103843]$ diff -Naur axes3d.py /mingw64/lib/python3.8/site-packages/mpl_toolkits/mplot3d/axes3d.py--- axes3d.py   2021-04-09 17:10:45.077761400 +0200+++ /mingw64/lib/python3.8/site-packages/mpl_toolkits/mplot3d/axes3d.py 2021-04-09 17:09:50.724401000 +0200@@ -1638,7 +1638,9 @@                         colset.append(fcolors[rs][cs])             # For the masking we have to loop because the polys may             # not be regularly shaped+            pmask = [ not( np.any(p[:, 3]) ) for p in polys ] # save first, for correspondingly masking colset             polys = [p[:, :3] for p in polys if not np.any(p[:, 3])]+            colset = [colset[ic] for ic, p in enumerate(pmask) if p] # apply same polys mask to colset         # note that the striding causes some polygons to have more coordinates         # than others

... will allow to also use facecolors, that would be "adjusted" to the masking of z; here is an example, that otherwise fails with this PR, with "ValueError: operands could not be broadcast together with shapes (22,1) (81,4)":

importnumpyasnpimportmatplotlib.pyplotaspltdimsize=256# 10x=np.linspace(0,1.0,dimsize)y=np.linspace(0,1.0,dimsize)xg,yg=np.meshgrid(x,y)z=0.5*(xg**3+yg**3)zcol=np.reshape(np.reshape(z, (-1,1))*np.array([1,1,0.5]), (z.shape[0],z.shape[1],3) )zm=np.ma.masked_array(z,z<0.3)fig=plt.figure(figsize=(8,8))ax1=fig.add_subplot(221,projection='3d')ax2=fig.add_subplot(222)surf=ax1.plot_surface(xg,yg,zm,rstride=1,cstride=1,linewidth=0,facecolors=zcol                 )ax1.set_zlim(0,1)plt.colorbar(surf,ax=ax1,shrink=0.7,orientation='horizontal')ax2.imshow(zm,cmap='inferno')plt.show()

With the hack, this results with the following image:

image

@jklymak
Copy link
Member

Is this still a viable PR? I'm not sure what still needs to be done here.@tomneep please feel free to ping us for reviews - things fall off the PR queue unfortunately. Thanks!

@jklymakjklymak marked this pull request as draftMay 12, 2021 20:16
@tomneep
Copy link
ContributorAuthor

Hi@jklymak, it was completely my fault, I got busy with other things. I'm happy to attempt to wrap this up. I've just pushed one change aiming to address one of the comments I received (and it simultaneously solves the problem raised by@sdbbs too). I'm a long way behind the master now, so lets see what the CI says.

jklymak reacted with thumbs up emoji

@tomneeptomneep marked this pull request as ready for reviewJuly 7, 2021 18:48
@tomneep
Copy link
ContributorAuthor

tomneep commentedJul 8, 2021
edited
Loading

I had a quick look for related issues to this and#4941 is an interesting one. Before applying the changes in this branch, the example given there gives

mask_test_release

and with this branch

mask_test_branch

I'd say that the fix gives what we'd want for masked data in 3D space, but the issue says they'd expect the masked data to be shown in red (the "bad" setting of the colormap). I'm not sure what the suggestion is here though, as if the area was present but red one would still be able to see the z values. I could do with some feedback on this.

@jklymak
Copy link
Member

This is a conflict between what a mask means for the elevation, and what it means for the colormapping. Unfortunately those are the same thing forplot_surface. Personally I feel relatively strongly that the mask should apply to the elevation, in which case it is impossible to apply it to the colormap because there is nothing to color. How would we even know where to place the surface level? I guess we could look at the masked values and plot them if they make any sense, but then colormap them like they are bad data, but I really don't think that is going to be very reliable.

Its a bit awkward, but to get what is wanted in#4941 I think the user wants to colormap manually and pass infacecolor=C. I actually think we should provide aC argument here, and allowC=Z if it is not passed, and then C maps into the provided colormap.

@@ -1693,6 +1697,12 @@ def plot_surface(self, X, Y, Z, *args, norm=None, vmin=None,
cbook._array_perimeter(a[rs:rs_next+1, cs:cs_next+1])
for a in (X, Y, Z)
]
# If any of the perimeters are masked, then skip the
Copy link
Member

Choose a reason for hiding this comment

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

Can you test this?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I've added another test for this based on the exampleabove

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this behavior desirable? If everythingbut the perimeter of the polygon is masked, do we really want to draw the polygon? Some other choices would be:

  • Draw the polygon only ifnone of the contained elements are masked
  • Draw the polygon if less than half (or a user-configurable parameter of the contained elements are masked
  • Set the opacity of the polygon to be proportional to the maskedness
  • Attempt to precisely outline the masked regions

Whatever you decide on, I think it's important to describe the choice in the docstring.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It is a good question and I'm happy to have more input on this topic.

My line of thought is that as long as the perimeters of the polygons don't contain masked data, then it is ok to draw the polygon. If some data inside of the polygon is masked but is strided over, then it is not used to make the plot anyway so we aren't "revealing" masked data in the plot. I understand this might cause a bit of confusion due to the nature of thexstride/xcount settings.

Draw the polygon if less than half (or a user-configurable parameter of the contained elements are masked

and

Set the opacity of the polygon to be proportional to the maskedness

These seem like "advanced" use cases to me but happy to hear if others think differently. Though I still don't think anyone would want the perimeter of a polygon to contain masked data. As far as I can tell, no one has requested this kind of feature before.

Attempt to precisely outline the masked regions

Probably the ideal solution, if any one has suggestions how the can be accomplished then I'm happy to hear them!

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 I want input from@ianthomas23 about how he handled masked data for the contouring functions. I think it would make sense to be consistent with those rules.

Copy link
Member

Choose a reason for hiding this comment

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

Of course, the concepts don't perfectly overlap because the contouring algorithms don't have a concept of striding, but I am still interested in his input.

Copy link
Member

Choose a reason for hiding this comment

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

I don't believe striding does anything other than a simple decimation at the moment? If so I don't see why it would do something fancy for a mask. You could easily have the same complaint about a sharp spike that gets strided over. If folks want to properly decimate their data they can do so before passing to mpl.

tomneep reacted with thumbs up emoji
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't believe striding does anything other than a simple decimation at the moment?

It doesn't decimate the space fully, as the perimeter is still drawn at full precision. But looking closely, the colormap color is computed based only on the perimeter, so I guess the implemented behavior is consistent.

@tomneep
Copy link
ContributorAuthor

Personally I feel relatively strongly that the mask should apply to the elevation, in which case it is impossible to apply it to the colormap because there is nothing to color. How would we even know where to place the surface level? I guess we could look at the masked values and plot them if they make any sense, but then colormap them like they are bad data, but I really don't think that is going to be very reliable.

Yes I completely agree with you. I'd say that what this PR does is the right thing to do and moves things in the right direction.

jklymak and timhoffm reacted with thumbs up emoji

Copy link
Member

@QuLogicQuLogic left a comment

Choose a reason for hiding this comment

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

If you update the images and want to keep your other commits split apart when merged (instead of squash merged), please amend the commit with the image so that the old ones are not kept in the repo.

Y = np.linspace(-1, 1)
X, Y = np.meshgrid(X, Y)
Z = X**2 + Y**2
Z[10:20, 10:20] = 0.0
Copy link
Member

Choose a reason for hiding this comment

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

The location of the hole is a bit difficult to see; can you move it somewhere move visible?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks for the suggestion. I've updated the image with the hole in a more prominent place. I amended the commit though I have no preference on whether this is squash merged or not.

@tomneep
Copy link
ContributorAuthor

Thanks for the reviews and discussion on this so far. During my investigations of this I think I've found a better way of addressing this, while fixing a few other open issues too. The approach is to allow NaNs in data passed toplot_surface and is implemented in#20646. Then dealing with masked arrays is just a case of adding

ifisinstance(Z,np.ma.MaskedArray):Z=Z.astype(float).filled(np.nan)

somewhere inplot_surface. Getting the example from#4941 to work (really) nicely also requires a solution to#16470 too, which I have ready to go once I get some feedback on#20646. So in the end allowing NaNs would (eventually) close five issues, rather than the two this PR does. The final plots after all these changes also look nicer (in my opinion) as triangular polygons are included and not ignored like in this PR (compare the image below with the example at the top of the PR)

example_fixed_3d

Of course, the big question is whether#20646 is viable? If so I think I can close this PR and move forwards with that

@ianthomas23
Copy link
Member

Thanks for the ping@WeatherGod.

To compare withcontour/contourf handling of maskedz, here is an example based on the Contour Corner Mask example (https://matplotlib.org/stable/gallery/images_contours_and_fields/contour_corner_mask.html#sphx-glr-gallery-images-contours-and-fields-contour-corner-mask-py):

importmatplotlib.pyplotaspltimportnumpyasnpx,y=np.meshgrid(np.arange(7),np.arange(10))z=np.sin(0.5*x)*np.cos(0.52*y)mask=np.zeros_like(z,dtype=bool)mask[2,3:5]=Truemask[3:5,4]=Truemask[7,2]=Truemask[5,0]=Truemask[0,6]=Truez=np.ma.array(z,mask=mask)fig=plt.figure(figsize=(16,4))fori,corner_maskinenumerate([False,True]):ax=fig.add_subplot(1,3,i+1)cs=ax.contourf(x,y,z,corner_mask=corner_mask)fig.colorbar(cs,ax=ax)ax.set_title('corner_mask = {0}'.format(corner_mask))ax.grid(c='k',ls='-',alpha=0.3)ax.plot(np.ma.array(x,mask=~mask),y,'ro')ax=fig.add_subplot(1,3,3,projection='3d')ax.elev=65surf=ax.plot_surface(x,y,z,rstride=1,cstride=1,cmap='viridis')fig.colorbar(surf,ax=ax)plt.show()

Using the latest commit (3eace1e) from this PR the output is
1
showing that theplot_surface here is consistent withcorner_mask=False contouring.

The approach of@tomneep's previous comment looks equivalent tocorner_mask=True, and as this is the default for contouring I would prefer it. Of course, if only one ofcorner_mask=True andcorner_mask=False is implemented, eventually someone will come along asking for the other as well!

@jklymak
Copy link
Member

I'll move this to draft while you sort out#20646, but feel free to move back if you think they can be looked at independently.

@jklymakjklymak marked this pull request as draftJuly 22, 2021 00:08
@QuLogic
Copy link
Member

Replaced by#20725, I believe.

@tomneeptomneep deleted the mplot3d_masked_surface branchAugust 19, 2021 08:42
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@WeatherGodWeatherGodWeatherGod left review comments

@QuLogicQuLogicQuLogic left review comments

@eric-wiesereric-wiesereric-wieser left review comments

@jklymakjklymakjklymak left review comments

@dstansbydstansbydstansby left review comments

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

8 participants
@tomneep@sdbbs@jklymak@ianthomas23@QuLogic@WeatherGod@eric-wieser@dstansby

[8]ページ先頭

©2009-2025 Movatter.jp