| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Voxels doesn't do colormapping so how are we to infer the colormap map from the artist? Please provide a complete example, but this doesn't seem possible.
It seems reasonable that we "promote" the artists used in Voxel to be a scalar mappable.
The API extension would be straight forward: ax.voxels([x, y, z,] /, filled, ...
filled : 3D np.array of bool
A 3D array of values, with truthy values indicating which voxels
to fill
alternatively to bool, filled could accepts numeric values to be mapped (with NaN meaning unfilled).
The main task however is to create a new VoxelCollection Artist. Currently we return a dict of Poly3DCollection, but we need one container Artist that derives from ScalarMappable and can carry the numeric and colormapping information.
Voxels doesn't do colormapping so how are we to infer the colormap map from the artist? Please provide a complete example, but this doesn't seem possible.
I'm not sure if you need a working exmaple anymore but here it is :
let's say you have 4D data ie a value for every point in 3D and that is is sparse enough not to make voxels in the whole 3D space.
My personal case is a Histogram ie a at every point in the 3D space you have the number of occurence of your three simultaenous axes variables most of it value being 0.
The 3 axes variables binned and the 3D histogram :
#Mock data X,Y,Z = np.meshgrid(np.linspace(0,5,10),np.linspace(0,5,10),np.linspace(0,5,10),indexing="ij") Hist=np.zeros((9,9,9)) Hist[2,5,5],Hist[5,5,3],Hist[5,7,5],Hist[5,0,5]=1,55,4,39
To create the BoolArray for the voxels method I use a mask on those the minimal value I want to display (let's say 0 for simplicity):
BoolArr=(Hist > 0)
To color the voxels by the value in the histogram I use a colormap on the normalized data :
CArr = cmap(NPArr/np.max(NPArr)) CArr[:,:,:,3]=0.9
The slight transparency will make the plot more readable. I now have a color for every point in space, since the BoolArray will provide the actual voxels that need to be plotted I don't have to mask this ColorArray.
I can now call :
fig=plt.figure() ax = fig.add_subplot(projection='3d') ax.voxels(X, Y, Z, BoolArr, facecolors=CArr)
which makes the plot I'm looking for.
However for the cbar it is a bit more complicated,
Here is the whole code adapted from the one I currently use :
`
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
levels=8
cmap="viridis"
#Mock data
X,Y,Z = np.meshgrid(np.linspace(0,5,10),np.linspace(0,5,10),np.linspace(0,5,10),indexing="ij")
Hist=np.zeros((9,9,9))
Hist[2,5,5],Hist[5,5,3],Hist[5,7,5],Hist[5,0,5]=1,55,4,39
palette=mpl.cm.get_cmap("viridis",lut=levels).copy() #simulate the level arg of other plot methods
palette.set_under((0,0,0,0)) #So vmin=0 has an effect
BoolArr=(Hist > 0)
vmax=np.max(Hist)
CArr = palette(Hist/vmax)
print(np.shape(CArr))
CArr[:,:,:,3]=0.9
fig=plt.figure()
ax = fig.add_subplot(projection='3d')
primitive = ax.voxels(X, Y, Z, BoolArr, facecolors=CArr)
#Now we have to create the cbar
norm = mpl.colors.Normalize(0,vmax) #Necessary because there is no mappable from voxels, instead of normalizing the data it is easier to normalize the "cmap"
m = mpl.cm.ScalarMappable(cmap=palette,norm=norm) #Creates Mappable
m.set_array([]) #Makes it the right type so get() methods exist
ticks = np.linspace(1,vmax,levels+1)
fig.colorbar(m, ax=ax, extend="min", ticks=ticks) #Finally creates the cmap with other cmap arguments that can't be passed to the mappable
plt.show()
`
So voxels would need a new argument "value" and that we can pass on a cmap so thats it store internally the data to give the right mappable. Or perhaps something less complicated would be that the BoolArray could actually be an array of float and that the arguments of vmin and vmax would control which values to be displayed by masking them out. The Nan values would replace the False values so we could imagine a FloatArray anyway where Nan values would be ignored in the plotting it would makes sense in the plotting of datasets too !
I hope it helps
The API extension would be straight forward: ax.voxels([x, y, z,] /, filled, ...
filled : 3D np.array of bool A 3D array of values, with truthy values indicating which voxels to fillalternatively to bool, filled could accepts numeric values to be mapped (with NaN meaning unfilled).
The main task however is to create a new VoxelCollection Artist. Currently we return a dict of Poly3DCollection, but we need one container Artist that derives from ScalarMappable and can carry the numeric and colormapping information.
Oh ! That's exactly what I had in mind. I have no idea if my request is actually tough though, I don't know much of this part of the API.
@Luluser, in case it is of interest, we are having a new contributors meeting this next week. We could walk through this section of the API then if that would be helpful at all. https://discourse.matplotlib.org/t/matplotlib-devel-announcing-the-new-contributors-meeting/22808
The main task however is to create a new VoxelCollection Artist
I think that needs to be a different axes method?
But regardless, currently you are doing the mapping manually so it makes sense that you need to create the colorbar manually.
This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!
@trygvrad would this be easier to implement w/ the colorizer architecture?
would this be easier to implement w/ the colorizer architecture?
Yes, I believe it is.
From what I can see, ax.voxels() returns a dictionary of Poly3DCollection, which inherits:
Poly3DCollection ← PolyCollection ← _CollectionWithSizes ← Collection ← ColorizingArtist
This means that there is already a Colorizer object associated with each voxel as it is rendered, because the creation of a ColorizingArtist instantiates a Colorizer if one is not provided as a keyword argument.
If we include a colorizer=colorizer to the initialization of each Poly3DCollection, it is trivial to have a single colorizer that is responsible for all voxels.
I made a branch that implements the required changes with as single commit
with this you are able to do:
import numpy as np
import matplotlib.pyplot as plt
#Mock data
X,Y,Z = np.meshgrid(np.linspace(0,5,10), np.linspace(0,5,10), np.linspace(0,5,10), indexing="ij")
Hist = np.zeros((9,9,9))
Hist[2,5,5], Hist[5,5,3], Hist[5,7,5], Hist[5,0,5] = 1, 55, 4, 39
BoolArr = (Hist > 0)
# plot
fig=plt.figure()
ax = fig.add_subplot(projection='3d')
res = ax.voxels(X, Y, Z, BoolArr, facecolors=Hist, cmap='rainbow', norm='log', alpha=0.5)
fig.colorbar(list(res.values())[0])
For the implementation I let the data be input via the facecolors argument:
facecolors, edgecolors : array-like, optional
The color to draw the faces and edges of the voxels. Can only be
passed as keyword arguments.
These parameters can be:
- A single color value, to color all voxels the same color. This
can be either a string, or a 1D RGB/RGBA array
- ``None``, the default, to use a single color for the faces, and
the style default for the edges.
- A 3D `~numpy.ndarray` of color names, with each item the color
for the corresponding voxel. The size must match the voxels.
- facecolors only: A 3D `~numpy.ndarray` of scalar values, with
each value mapped using a norm and colormap.
- A 4D `~numpy.ndarray` of RGB/RGBA data, with the components
along the last axis.
This makes the most sense to me, but we could alternatively add an additional keyword argument, as has been suggested previously.
The line
fig.colorbar(list(res.values())[0])is not very elegant. It arises because of the return type of ax.voxels(), which is a dictionary.
We can fix this by creating a new class as the return type for ax.voxels(), but I am not sure if this is strictly necessary.
@story645 what do you think?
If you think this is a reasonable solution I can add tests to my branch and make a PR.
If you think this is a reasonable solution I can add tests to my branch and make a PR.
Thanks for the quick workup. I'm not sure about passing in via facecolors b/c then in theory folks may also want to do edgecolors - I like @timhoffm's suggestion of using filled #22969 (comment)?
We can fix this by creating a new class as the return type for ax.voxels(), but I am not sure if this is strictly necessary.
If it doesn't get too cranky, I think a VoxelCollection artist would make sense for a couple of reasons, and we've been introducing more semantic artists lately.
But that can also all be discussed on the PR.
hmmm, I can take a crack at seeing what would be required of a VoxelCollection,
Will VoxelCollection contain a dictionary of Poly3DCollection objects, i.e. an artist containing other artists, or do we think that we can make the VoxelCollection the only artist?
I'm not familiar enough with the 3D pipeline to know what is needed for the objects to be ordered correctly in screen depth, and if this has any impact of single/multiple artists.
(are there other kinds of artists that contain artists?)
| Back | FazBrowse Home | New Git URL |
Problem
Hi;
I'm working with 3D plots and I use colored voxels to plots a 4th dimension.
When one wants to use a colorbar, one usually uses an image or a contourf in the colorbar creation method like so :
It is very handful to handle all the colormap arguments (such as vmin etc) directly from the image but doesn't work with voxels.
Currently I use this :
`
palette=mpl.cm.get_cmap("viridis",lut=levels).copy() #simulate the level arg of other plot methods palette.set_under((0,0,0,0)) #So vmin has an effect if add_colorbar: vmax = np.max(NPArr) #NPArr is the data being plotted norm = mpl.colors.Normalize(0,vmax) #Necessary because there is no mappable from voxels, instead of normalizing the data it is easier to normalize the "cmap" m = cm.ScalarMappable(cmap=palette,norm=norm) #Creates Mappable m.set_array([]) #Makes it the right type so get() methods exist ticks = np.linspace(1,vmax,levels+1) fig.colorbar(m, ax=ax, extend="min", ticks=ticks) #Finally creates the cmap with other cmap arguments that can't be passed to the mappable`
Voxels give dictionnary of 3d objet so I have tried to use one of its value as mappable which works but doesn't bring all the data info for the colormap.
Proposed solution
No response