Skip to content
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

Opacity based on cell-volume data #482

Open
TBody opened this issue Aug 15, 2021 · 6 comments
Open

Opacity based on cell-volume data #482

TBody opened this issue Aug 15, 2021 · 6 comments

Comments

@TBody
Copy link

TBody commented Aug 15, 2021

Setting opacity based on cell-volume data

PyVista newbie, sorry! I'm trying to visualize some 3D data on an unstructured grid (a collection of connected 2D hexahedra, non-overlapping). The dataset is somewhat peculiar in that it has high resolution in 2 dimensions, but low resolution in the other dimension, and so the hexahedra have a small width and breadth, but a very large length (~1000x the other dimensions).

I'd like to set the opacity of the cell based on its value, and so I've tried

mesh = pv.read('simple.vtk')
mesh.plot(opacity='linear')
  • What I expected would happen: the opacity of the entire hexahedron volume is set according to the value.
  • What actually happens: the opacity on the "width x breadth" face is set according to the value, but the faces of "width x length" and "breadth x length" are set to the transparency of the the outermost face.
  • What I think might be the cause -- the hexahedra are connected to each other in the "width and breadth" dimension (opacity set to outer face value), but not in the "length" dimension (faces have correct opacity)

I find a similar behavior in Paraview with "Opacity for surfaces", so I believe that this is me not understanding VTK rather than an issue in PyVista. However, I'm not sure how to go about how to get the desired behavior, any pointers greatly appreciated!

image

Example Data

simple.vtk.zip

@adamgranthendry
Copy link

adamgranthendry commented Aug 17, 2021

@TBody No worries! This seems to be a common question. I'll reiterate my answer from volumetric rendering for volumetric data #472 .

TLDR: To set the opacity for everything to a constant values, you need to make opacity an array of 256 floats with the same value. For example, to make the opacity half (on a scale of 0 to 1 255, of course):

opacity = np.ones((256,)) * 0.5

In regards to your second question, pyvista.add_volume uses 256 colors on the scale bar by default (controlled by the n_colors argument). Your data has 3 color channels and an alpha channel (R, G, B, and A), but assuming each color channel is 8-bit, this means you can represent (2**8)**3 = 2**24 colors (a big gamut!). The n_colors is made 256 (i.e. 2**8) so the lookup table (LUT) for opacity can be contained in one byte and thus provide faster lookups. The opacity argument is intended to be the same length as n_colors, but you can specify fewer arguments.

In essence, when you specify [0, 1, 0] for opacity, you are setting the opacity for the first 3 colors (out of the total 256 colors) to 0, 1, and 0, respectively. The remaining 253 colors preserve their default values. When you specify [0, 1], you set only the first two colors, and the remaining 254 retain their value.

I believe from your code that you think you are setting the opacity of the color channels, but that is not actually how opacity works in pyvista.add_volume. You can instead create an opacity array of length 256 that has varying degrees of opacity from 0 to 1. For example, opacity = np.linspace(0, 1, 256) would make a linearly increasing opacity from 0 at the low values of your data to 1 at the high values. Instead, pyvista already comes with a bunch of pre-made opacity functions (called opacity transfer functions in VTK and pyvista), so you can specify a string that maps to a pre-made opacity transfer function.

The opacity transfer functions are specified in pyvista.plotting.tools in the function opacity_transfer_function. You have the following to choose from:

 transfer_func = {
        'linear': np.linspace(0, 255, n_colors, dtype=np.uint8),
        'geom': np.geomspace(1e-6, 255, n_colors, dtype=np.uint8),
        'geom_r': np.geomspace(255, 1e-6, n_colors, dtype=np.uint8),
        'sigmoid': sigmoid(np.linspace(-10.,10., n_colors)),
        'sigmoid_3': sigmoid(np.linspace(-3.,3., n_colors)),
        'sigmoid_4': sigmoid(np.linspace(-4.,4., n_colors)),
        'sigmoid_5': sigmoid(np.linspace(-5.,5., n_colors)),
        'sigmoid_6': sigmoid(np.linspace(-6.,6., n_colors)),
        'sigmoid_7': sigmoid(np.linspace(-7.,7., n_colors)),
        'sigmoid_8': sigmoid(np.linspace(-8.,8., n_colors)),
        'sigmoid_9': sigmoid(np.linspace(-9.,9., n_colors)),
        'sigmoid_10': sigmoid(np.linspace(-10.,10., n_colors)),
    }

For example, sigmoid will give you a nice continuous variation in opacity. In addition, if you would prefer to reverse the color bar so that low values have more opacity, you can add _r to any of the strings above and it will reverse the colors for you.

I would recommend using sigmoid as that seems to work nice most of the time. In addition, if you want to "filter out" some values from your data set, I would use clim because I think None values won't work, though I could be wrong. For example, you could try clim=[np.nanmin(scalars_a[scalars_a>0]), np.nanmax(scalars_a)].

@MatthewFlamm @akaszynski This would be another great thing to add to our documentation! The volume rendering section could use some TLC.

@adamgranthendry
Copy link

@TBody If this answers your question, please kindly let me know so we can close the issue. No rush!

@akaszynski
Copy link
Member

@adamgranthendry, you're correct, we need to add that. Right now I've got three PRs in the oven. Would you mind submitting one for this? It's a great example.

@adamgranthendry
Copy link

@akaszynski You bet! I know I still owe you some more feedback on one of the other ones too.

@TBody
Copy link
Author

TBody commented Aug 17, 2021

@adamgranthendry Thanks! Reading more into it, I think what I was actually trying to do was volume rendering: i.e. set the cell volumes as semi-transparent, rather than cell-surface rendering.

If I set, for instance opacity=np.ones((256,))*255/2 I still don't change the volume opacity (see attached).

image

If I try volume=True then I get the error "Type <class 'pyvista.core.pointset.UnstructuredGrid'> not supported for volume rendering at this time." -- so I think this is an issue regarding my choice of mesh.

P.S. I found that the opacity range has to be [0, 255] rather than [0, 1] (using pyvista 0.31.3 pyhd8ed1ab_0 conda-forge)

@adamgranthendry
Copy link

adamgranthendry commented Aug 17, 2021

@TBody Oh yes, you're right, my mistake. The docstring clearly states opacity is on the scale of [0, 255]. And yes, pyvista currently only supports UniformGrid data (i.e. ImageData in VTK), although VTK has vtkUnstructuredGridVolumeRayCastMapper.

From what I can see though, your opacity has changed. It has increased for your lower values and decreased for your higher ones. Previously, you were using linear, so your color bar scale was going from 0% opacity ("translucent") to 100% opacity ("opaque"). Now, with opacity set at 127.5, the intensity is 50% all throughout:

Linear:
image

50% Opaque:
image

Do you see how in the first instance, the left end of the color bar is clear and the right end is very intense, but how in the second instance the color is flat all throughout?

Maybe we can figure out how to correctly set the opacity for you so you can see what you're trying to see. What is it exactly that you would like to do?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants