-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add a ax.voxels(bool3d) function #6404
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
Conversation
Cool! I suggest returning the whole set of collections added to the axes. This should probably get a test. How does this do with alpha in the colors? attn @WeatherGod |
Not entirely sure how to do this, especially since I'm unable to
Untested. The machine I tested this on had an old enough version of matplotlib that It doesn't draw internal faces, so a solid cube surrounded by translucent cubes will be invisible. It also becomes hard to decide what color to draw the faces if alpha is involved. |
Older versions of mplot3d does not handle alphas correctly, this was fixed I'll look through this in the next few days. On Thu, May 12, 2016 at 12:41 PM, Eric Wieser notifications@github.com
|
Behaviour with alphas in a newer matplotlib: fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x, y, z = np.indices((10, 10, 10))
v1 = x == y
v2 = np.abs(x - y) < 2
colors = np.zeros((10, 10, 10, 4))
colors[v2] = [1, 0, 0, 0.5]
colors[v1] = [0, 1, 0, 0.5]
ax.voxels(v1 | v2, colors)
plt.show() Note that the inner green voxels are not rendered, because we only render the surface |
See matplotlib#6404 for screenshots
Updated to return something |
This build failure seems entirely unrelated |
2 of them are unreleated and known transient failures see # However the one that builds the docs is probably related.
|
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
|
||
Returns | ||
------- | ||
list of `Poly3DCollection |
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.
Ah, missed a ``` here
It's most likely because you are missing a ``` after |
Yep, just caught that too. Fixup'd and repushed |
I don't think this error is real? |
@WeatherGod, is this still in your queue? |
Yeah, it still is in my queue. Just been punting on it as this would have On Tue, Jul 19, 2016 at 3:51 AM, Eric Wieser notifications@github.com
|
Nice job on the 2.0 release! Could you give me some direction on how to construct the image tests for this? Do I need to commit the PNGs as well? Do I need a specific rendering backend available for the tests to pass? |
I'd still appreciate some tips on finishing this off - it would be great to get it in |
If I don't get to it this week, then someone at the SciPy conference should
sit me down and force me to review this before having any beers.
…On Fri, Jun 30, 2017 at 2:16 PM, Thomas A Caswell ***@***.***> wrote:
@tacaswell <https://github.com/tacaswell> requested your review on:
matplotlib/matplotlib#6404
<#6404> Add a
ax.voxels(bool3d) function.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#6404 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AARy-Cz0AoY2KwWo3THnhfLbY1v0JQFDks5sJTuJgaJpZM4Icczh>
.
|
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 need to see more clarification in the documentation before deciding how I feel about the return type. What is the rational for not keeping everything in one single Poly3DCollection? If everything was in one collection, we stand a chance to actually avoid "Escher Effects".
In addition, we need a "What's New" entry and unit tests.
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
if np.ndim(color) <= 1: | ||
color, _ = np.broadcast_arrays( | ||
color, | ||
filled[np.index_exp[...] + np.index_exp[np.newaxis] * np.ndim(color)] |
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 version of numpy was this introduced? I am not familiar with np.index_exp
.
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.
Since numpy/numpy@f897e1f back in 2002, so I assume this is fine. It's essentially the same as np.s_[...]
, but returns a tuple
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.
wow... I have never seen this function before. Gonna have to remember this one.
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
Returns | ||
------- | ||
list of `Poly3DCollection` | ||
The square faces which were plotted, in an arbitrary order |
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.
From this description, it isn't clear what is the structure of the return object. Is it that each voxel is a Poly3DCollection of six faces, and that each element of the returned list is a voxel?
Also, "arbitrary order" is too ambiguous.
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.
Should also add a .. verson added :: 2.1
(or whatever the right syntax is)
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.
Each element of the returned list is a single face.
Also, "arbitrary order" is too ambiguous.
The order is [XY-oriented] + [YZ-orientated] + [ZX-orientated]
, where each of those is in a lexicographic ordering over some permutation of their position.
The goal here was just to return a set of objects that borders etc could be put on, so the ordering is not documented.
I suppose in principle the return value could be a dict indexed by coordinate, containing a single Poly3DCollection
for all the faces rendered for that voxel, but that would be more work
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
color : array_like | ||
Either a single value or an array the same shape as filled, | ||
indicating what color to draw the faces of the voxels. If None, | ||
plot all voxels in the same color, the next in the color sequence |
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.
So, is this an array of strings? tuples?
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
color, | ||
filled[np.index_exp[...] + np.index_exp[np.newaxis] * np.ndim(color)] | ||
) | ||
elif np.ndim(color) < 3: |
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.
why not allow 2D arrays that could be broadcasted to 3D?
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.
Because that would make 3d arrays ambiguous.
The issue here is that I want to support colors[x,y,z] == 'the-color-name'
(.shape=(X,Y,Z)
) and colors[x,y,z] == [r,g,b]
(.shape=(X,Y,Z,3)
).
If we allowed broadcasting from 2D, then it's not clear whether to interpret a 3d array as .shape=(Y,Z,3)
or .shape=(X, Y, Z)
.
I suppose I could switch on dtype, but I'm not clear what all the supported color formats are.
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.
but doesn't the same ambiguity problem apply for 1-D?
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
boundary_found(p0 + square_rot, color[i0]) | ||
|
||
# draw middle faces | ||
for r1, r2 in zip(rinds, rinds[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.
make this explicitly zip(rinds[:-1], rinds[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.
Good catch
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
if filled[i0]: | ||
boundary_found(p0 + square_rot, color[i0]) | ||
|
||
# draw middle faces |
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.
could you add a bit more comments here explaining what the extra logic here is for?
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.
Which extra logic are you referring to? The goal here is to not double-draw a face, which would cause clipping issues
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.
exactly that. "don't double-draw a face" is a nice, clear explanation.
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.
Comment added at the start of this loop
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
@@ -2649,6 +2649,114 @@ def calc_arrow(uvw, angle=15): | |||
|
|||
quiver3D = quiver | |||
|
|||
def voxels(self, filled, color=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.
There is no support here for specifying other properties such as edgecolors and edgewidth. Perhaps allow a **kwarg that can be any valid Poly3DCollection property?
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 problem with adding **kwarg
is that it becomes harder to introduce new kwargs to voxels
, in case they clash with Poly3DCollection
properties. But I guess matplotlib
has already gone down that route, so that doesn't seem unreasonable.
If I do that, then I should presumably rename color
to facecolor
, to make it clear that voxels
is in control of that behaviour?
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 would use bar3d() or hist3d() as a guide 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.
That sounds like good advice, thanks
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.
Ok, forwarding of kwargs is done, and there's a test
Thanks for the review! I'll go through your comments shortly
Can you point me to some documentation on how best to do these? In particular, how do I generate the image that the tests compare against? Isn't that platform-dependent? |
What are the odds of that working on Windows? |
@eric-wieser High, we run tests on appevyor 😄 and the same image tests pass on mac, windows, and linux. The appevyor configuration files may be a good guide to getting a dev enviroment set up with conda. |
If |
@QuLogic: Can you show me what you mean? I'm trying to make all of these cases work, as those are the cases allowed by the PEP 457 spec of voxels(filled, **kwargs)
voxels(filled=filled, **kwargs)
voxels(x, y, z, filled, **kwargs)
voxels(x, y, z, filled=filled, **kwargs) Additionally, this should fail: voxels(x, y, z, filled, filled=filled2, **kwargs)
# TypeError: voxels() got multiple values for argument 'filled' The clever trick does a very good job of providing this behaviour, and provides the right error messages on python 3 too. It also makes it super easy to add more both-positional-and-keyword arguments |
The behavior you are looking for looks a lot like |
Just my 2c; I would prefer all these crazy argument parsing tricks to just use inspect.Signature.{bind,apply_defaults} (yes, that'd need the proper backport to be done). |
Unfortunately I do think you're right that a generic argument-parsing helper might be handy, but I don't think that
pcolor() # TypeError: Illegal arguments to pcolor; see help(pcolor)
voxels() # TypeError: voxels() missing 1 required positional argument: 'filled'
pcolor(arr2d) # ok
voxels(arr3d) # ok
pcolor(C=arr2d) # TypeError: Illegal arguments to pcolor; see help(pcolor)
voxels(filled=arr3d) # ok
pcolor(arr2d, C=arr2d) # AttributeError: Unknown property c
voxels(arr3d, filled=arr3d) #TypeError: voxels() got multiple values for argument 'filled' |
Here's a possibly clearer implementation: # Use a lambda function to parse out the two forms of arguments.
# Leading underscores are used to indicate positional-only arguments.
if len(args) >= 3:
parser = lambda __x, __y, __z, filled, **kwargs: (
(__x,__y,__z), filled, kwargs)
else:
parser = lambda filled, **kwargs: (
None, filled, kwargs)
parser.__name__ = str('voxels') # to avoid unicode_literals
xyz, filled, kwargs = parser(*args, **kwargs) Would that be acceptable? |
Actually it nearly does, it's just tucked in a private function (used to parse Argument Clinic signature strings):
(if we don't want to rely on a private function we can just copy-paste the implementation). Square brackets (optional positional-only arguments) are not implemented (I guess because it's not really possible to represent them as Signature objects anyways), but a possibility would be to provide a list of signatures which would be tried one after the other until one matches. |
Oops, you're right. I did know about that, but I assumed it worked by
I think I'd be in favor of using |
That's definitely not a blocker. (I agree it should be a separate PR.) |
I've updated the implementation to use a form similar to the above comment. It fails, but only because of |
Actually I think I would still favor something like
(but not a big deal either) |
@anntzer: That approach makes it difficult to produce useful error messages. |
Actually it's the opposite: Python is not able to autogenerate error messages for such signature-overloaded functions, so it should be better to just provide the error message ourselves. |
Sorry, I did not intend there to be such a long detour on what was ultimately a misunderstanding on my part wrt whether On to something I did understand though, PEP457 may be a standard, but I'm not so sure we can rely on users having read it (I certainly haven't until now.) So I think you will have to list out all the individual ways of calling the function (as we do in, e.g., |
Done, along with some other fixes to the parameter documentation |
Thanks @eric-wieser ! This is a pretty cool feature to get in 😄 |
Pleased to see it finally made it! Thanks everyone for the feedback :) |
I found myself needing a voxel plot to visualize a subset of RGB space, and thought that the plot was sufficiently useful that it would do well to belong upstream.
I'm not convinced the argument choices I've made line up too well with the rest of matplotlib, so I'd appreciate feedback on what I should be parameterizing this with.
Plotting a simple boolean array
Attaching color information
Possible improvements:
Allow control of origin, scale, and rotation - possibly aDone viatransform
argument of a(3,4)
matrix?x, y z
arguments!cmap
supportnp.ma.array
variant that omits thefilled
argumentReturn something sensibleDone by returning a dict keyed by coordinate