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

[WIP] FEATURE: Convenience functions for volume rendering transfer functions #178

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

GenevieveBuckley
Copy link
Contributor

@GenevieveBuckley GenevieveBuckley commented Sep 23, 2018

I propose adding convenience functions for volume rendering transfer functions.

  • single color linear ramp transfer functions
  • matplotlib colormaps as transfer functions
  • function for easy loading of a number of predefined transfer functions
  • function tests - still in progress
  • notebook for example docs?

Example use

import ipyvolume as ipv
aquarius = ipv.datasets.aquariusA2.fetch()

Transfer function from single RGB value with linear opacity ramp

magenta = ipv.transferfunction.linear_transfer_function('magenta')
ipv.quickvolshow(aquarius.data, tf=magenta)

screen shot 2018-09-24 at 1 47 37 pm

Transfer functions based on matplotlib colormaps

jet = ipv.transferfunction.matplotlib_transfer_function('jet')
ipv.quickvolshow(aquarius.data, tf=jet)

screen shot 2018-09-24 at 1 42 02 pm

Load all predefined transfer functions to a dictionary

# You can also load all predefinied transfer functions into a dictionary,
# for quick and easy testing of multiple display options
my_transfer_functions = ipv.transferfunction.load_transfer_functions()
print(list(my_transfer_functions))  # all predefined transfer function names
['grey', 'grey_r', 'red', 'red_r', 'green', 'green_r', 'blue', 'blue_r', 'yellow', 'yellow_r', 'magenta', 'magenta_r', 'cyan', 'cyan_r', 'Accent', 'Blues', 'BrBG', 'BuGn', 'BuPu', 'CMRmap', 'Dark2', 'GnBu', 'Greens', 'Greys', 'OrRd', 'Oranges', 'PRGn', 'Paired', 'Pastel1', 'Pastel2', 'PiYG', 'PuBu', 'PuBuGn', 'PuOr', 'PuRd', 'Purples', 'RdBu', 'RdGy', 'RdPu', 'RdYlBu', 'RdYlGn', 'Reds', 'Set1', 'Set2', 'Set3', 'Spectral', 'Wistia', 'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd', 'afmhot', 'autumn', 'binary', 'bone', 'brg', 'bwr', 'cividis', 'cool', 'coolwarm', 'copper', 'cubehelix', 'flag', 'gist_earth', 'gist_gray', 'gist_heat', 'gist_ncar', 'gist_rainbow', 'gist_stern', 'gist_yarg', 'gnuplot', 'gnuplot2', 'gray', 'hot', 'hsv', 'inferno', 'jet', 'magma', 'nipy_spectral', 'ocean', 'pink', 'plasma', 'prism', 'rainbow', 'seismic', 'spring', 'summer', 'tab10', 'tab20', 'tab20b', 'tab20c', 'terrain', 'viridis', 'winter', 'Accent_r', 'Blues_r', 'BrBG_r', 'BuGn_r', 'BuPu_r', 'CMRmap_r', 'Dark2_r', 'GnBu_r', 'Greens_r', 'Greys_r', 'OrRd_r', 'Oranges_r', 'PRGn_r', 'Paired_r', 'Pastel1_r', 'Pastel2_r', 'PiYG_r', 'PuBuGn_r', 'PuBu_r', 'PuOr_r', 'PuRd_r', 'Purples_r', 'RdBu_r', 'RdGy_r', 'RdPu_r', 'RdYlBu_r', 'RdYlGn_r', 'Reds_r', 'Set1_r', 'Set2_r', 'Set3_r', 'Spectral_r', 'Wistia_r', 'YlGnBu_r', 'YlGn_r', 'YlOrBr_r', 'YlOrRd_r', 'afmhot_r', 'autumn_r', 'binary_r', 'bone_r', 'brg_r', 'bwr_r', 'cividis_r', 'cool_r', 'coolwarm_r', 'copper_r', 'cubehelix_r', 'flag_r', 'gist_earth_r', 'gist_gray_r', 'gist_heat_r', 'gist_ncar_r', 'gist_rainbow_r', 'gist_stern_r', 'gist_yarg_r', 'gnuplot2_r', 'gnuplot_r', 'gray_r', 'hot_r', 'hsv_r', 'inferno_r', 'jet_r', 'magma_r', 'nipy_spectral_r', 'ocean_r', 'pink_r', 'plasma_r', 'prism_r', 'rainbow_r', 'seismic_r', 'spring_r', 'summer_r', 'tab10_r', 'tab20_r', 'tab20b_r', 'tab20c_r', 'terrain_r', 'viridis_r', 'winter_r']

And use them like so:

ipv.quickvolshow(aquarius.data, tf=my_transfer_functions['grey'])

Discussion

Now that it's possible to have multiple volumes displayed in the same figure, we need something like this to increase the useability (and it looks like this has been suggested before):

I think this can now be done using multivolume rendering. Render each channel as a separate volume, and give them 'ramp' transfer function, having a constant color (say red for the first, green for the second), and opacity increasing from 0 to 1.
#81 (comment)

I've also seen this issue play out too, since the default choice is very non-standard for my field:

When I've explained the current system with the 3 RGB gaussians to my
professors, it's always taken a minute for them to understand that red is
low, green is medium, and blue is high. I think having a single linear
color gradient from low to high could eliminate that confusion.
#76 (comment)

Let me know what your thoughts are, happy to discuss etc.

@GenevieveBuckley
Copy link
Contributor Author

Travis CI build is currently failing due to the issues with unpkg (mjackson/unpkg#134)

Copy link
Collaborator

@maartenbreddels maartenbreddels left a comment

Choose a reason for hiding this comment

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

Excellent work, this is also what I had in mind but never got to code up cleanly. Some comments on the code, maybe you always want to add some unittests?

@@ -164,3 +165,141 @@ def control(self, max_opacity=0.2):
return ipywidgets.VBox(
[ipywidgets.HBox([ipywidgets.Label(value="levels:"), l1, l2, l3]), ipywidgets.HBox([ipywidgets.Label(value="opacities:"), o1, o2, o3])]
)


def linear_transfer_function(rgb_values,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of rgb_color, we can just name it color, and let matplotlib deal with it:
https://github.com/glue-viz/glue-jupyter/blob/7254451a2a671cdea07296861abd3f0cf50c6409/glue_jupyter/ipyvolume/volume.py#L43
It seems to accept named colors like 'red', hex values '#f0e6a4', and float tuples.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's much more elegant, thanks. Also means we can probably use this in conjunction with the color picker ipywidget, too! (Let me check that last part, but that would be great).

Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be great if we do it on the frontend, and indeed use the color picker!

Copy link
Collaborator

Choose a reason for hiding this comment

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

What I've done in a previous project, was to put all the colormaps of matplotlib in 1 texture, of #texture x 1024 pixels, and then the index selected the row of the texture.

ipyvolume/transferfunction.py Outdated Show resolved Hide resolved
ipyvolume/transferfunction.py Outdated Show resolved Hide resolved
ipyvolume/transferfunction.py Outdated Show resolved Hide resolved
return transfer_function


def load_transfer_functions(include_rgb_linear=True,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would not call it load (get?), and I'd make it 'singleton' like, so it will always return the same widgets, what do you think? (it will basically cache the widgets).
My guess is this will be used as part of a gui to select a particular colormap?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's call it 'predefined_transfer_functions'.

I'm not sure I understand correctly: do you want this to stay a function but always return the same dictionary, or have me make a singleton class for it?

I have no major plans for a colormap gui. But I do want to set future efforts at a GUI up for success as much as possible, so if you have advice on things I'm doing (or not doing here I'm happy to hear it.

What I want in the short term is a bunch of transfer function widgets so I can easily switch between them programmatically for the multivolume rendering I'm working on now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What I would like to see, is when a transfer function is created with the same set of parameters (same key for the dictionary), that it returns the same transfer function. That will limit the number of widgets created, what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure how best to implement the idea, but I think it's a good strategy/goal.

Copy link
Collaborator

Choose a reason for hiding this comment

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

sth like this:

tf_cache = {}
def get_transfer_functions(...)
  tf = tf_cache.get(key)
  if not tf:
    tf = ....
    tf_cache[key] = tf
  ...

ipyvolume/transferfunction.py Outdated Show resolved Hide resolved
@maartenbreddels
Copy link
Collaborator

I should also mention @vidartf 's work on ipyscales:
https://ipyscales.readthedocs.io/en/latest/examples/colorbar.html
would be really nice to integrate with that.

@GenevieveBuckley
Copy link
Contributor Author

Thanks for the link, I didn't know that about @vidartf. So far I've thought exactly zero about how to display colorbars next to figures, but that will be a pretty necessary next step.

@GenevieveBuckley
Copy link
Contributor Author

@maartenbreddels can you explain the difference between the four different transfer function classes in python?

Those python classes are:

  1. TransferFunction
  2. TransferFunctionJsBumps
  3. TransferFunctionWidgetJs3
  4. TransferFunctionWidget3

Found in ipyvolume/ipyvolume/transferfunctions.py

And on the javascript side, there's
a. TransferFunctionModel
b. TransferFunctionJsBumpsModel
c. TransferFunctionWidgetJs3Model
d. TransferFunctionView

Found in ipyvolume/js/tf.js

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

Successfully merging this pull request may close these issues.

None yet

2 participants