Add a ax.voxels(bool3d) function - #6404
Conversation
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. |
|
|
||
| Returns | ||
| ------- | ||
| list of `Poly3DCollection |
There was a problem hiding this comment.
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>
.
|
WeatherGod
left a comment
There was a problem hiding this comment.
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.
| 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.
What version of numpy was this introduced? I am not familiar with np.index_exp.
There was a problem hiding this comment.
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.
wow... I have never seen this function before. Gonna have to remember this one.
| Returns | ||
| ------- | ||
| list of `Poly3DCollection` | ||
| The square faces which were plotted, in an arbitrary order |
There was a problem hiding this comment.
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.
Should also add a .. verson added :: 2.1 (or whatever the right syntax is)
There was a problem hiding this comment.
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
| 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.
So, is this an array of strings? tuples?
| 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.
why not allow 2D arrays that could be broadcasted to 3D?
There was a problem hiding this comment.
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.
but doesn't the same ambiguity problem apply for 1-D?
| 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.
make this explicitly zip(rinds[:-1], rinds[1:]).
| if filled[i0]: | ||
| boundary_found(p0 + square_rot, color[i0]) | ||
|
|
||
| # draw middle faces |
There was a problem hiding this comment.
could you add a bit more comments here explaining what the extra logic here is for?
There was a problem hiding this comment.
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.
exactly that. "don't double-draw a face" is a nice, clear explanation.
There was a problem hiding this comment.
Comment added at the start of this loop
|
|
||
| quiver3D = quiver | ||
|
|
||
| def voxels(self, filled, color=None): |
There was a problem hiding this comment.
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.
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.
I would use bar3d() or hist3d() as a guide here.
There was a problem hiding this comment.
That sounds like good advice, thanks
There was a problem hiding this comment.
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 😄 |


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 viatransformargument of a(3,4)matrix?x, y zarguments!cmapsupportnp.ma.arrayvariant that omits thefilledargumentReturn something sensibleDone by returning a dict keyed by coordinate