-
Notifications
You must be signed in to change notification settings - Fork 233
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
base: master
Are you sure you want to change the base?
Changes from 4 commits
c8bb77a
47caedb
8de9b84
9bc0e9f
68b330e
89a6d79
a4d809c
2321bf7
551ef9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
from traittypes import Array | ||
import traitlets | ||
import numpy as np | ||
import matplotlib.cm | ||
from . import serialize | ||
from .serialize import array_rgba_png_serialization, array_serialization | ||
N = 1024 | ||
|
@@ -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, | ||
min_opacity=0, | ||
max_opacity=0.05, | ||
reverse_opacity=False): | ||
"""Transfer function from a single RGB value with a linear opacity. | ||
|
||
:param rgb_values: Tuple or list containing the RGB values | ||
of red, green and blue, respectively. | ||
:param min_opacity: Minimum opacity, default value is 0.0. | ||
Lowest possible value is 0.0, optional. | ||
:param max_opacity: Maximum opacity, default value is 0.05. | ||
Highest possible value is 1.0, optional. | ||
:param reverse_opacity: Linearly decrease opacity, optional. | ||
:type rgb: listlike | ||
:type min_opacity: float, int | ||
:type max_opacity: float, int | ||
:type reverse_opacity: bool | ||
:return: transfer_function | ||
:rtype: ipyvolume TransferFunction | ||
|
||
:Example: | ||
>>> import ipyvolume as ipv | ||
>>> rgb = (0, 255, 0) # RGB value for green | ||
>>> green_tf = ipv.transfer_function.linear_transfer_function(rgb) | ||
>>> ds = ipv.datasets.aquariusA2.fetch() | ||
>>> ipv.volshow(ds.data[::4,::4,::4], tf=green_tf) | ||
>>> ipv.show() | ||
|
||
.. seealso:: matplotlib_transfer_function() | ||
""" | ||
_num_elements = 256 # length of rgba transfer function array | ||
GenevieveBuckley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
r, g, b = [value/255. for value in rgb_values] # rescales 0-255 to float | ||
opacity = np.linspace(min_opacity, max_opacity, num=_num_elements) | ||
if reverse_opacity: opacity = np.flip(opacity, axis=0) | ||
GenevieveBuckley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
rgba = np.transpose(np.stack([[r] * _num_elements, | ||
[g] * _num_elements, | ||
[b] * _num_elements, | ||
opacity])) | ||
transfer_function = TransferFunction(rgba=rgba) | ||
return transfer_function | ||
|
||
|
||
def matplotlib_transfer_function(colormap_name, | ||
min_opacity=0, | ||
max_opacity=0.05, | ||
reverse_colormap=False, | ||
reverse_opacity=False): | ||
"""Transfer function from matplotlib colormaps. | ||
|
||
:param colormap_name: name of matplotlib colormap | ||
:param min_opacity: Minimum opacity, default value is 0. | ||
Lowest possible value is 0, optional. | ||
:param max_opacity: Maximum opacity, default value is 0.05. | ||
Highest possible value is 1.0, optional. | ||
:param reverse_colormap: reversed matplotlib colormap, optional. | ||
:param reverse_opacity: Linearly decrease opacity, optional. | ||
:type colormap_name: str | ||
:type min_opacity: float, int | ||
:type max_opacity: float, int | ||
:type reverse_colormap: bool | ||
:type reverse_opacity: bool | ||
:return: transfer_function | ||
:rtype: ipyvolume TransferFunction | ||
|
||
:Example: | ||
>>> import ipyvolume as ipv | ||
>>> rgb = (0, 255, 0) # RGB value for green | ||
>>> green_tf = ipv.transfer_function.matplotlib_transfer_function('viridis') | ||
>>> ds = ipv.datasets.aquariusA2.fetch() | ||
>>> ipv.volshow(ds.data[::4,::4,::4], tf=green_tf) | ||
>>> ipv.show() | ||
|
||
.. seealso:: linear_transfer_function() | ||
""" | ||
_num_elements = 256 # length of rgba transfer function array | ||
GenevieveBuckley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
cmap = matplotlib.cm.get_cmap(name=colormap_name) | ||
rgba = np.array([cmap(i) for i in np.linspace(0, 1, _num_elements)]) | ||
if reverse_colormap: rgba = np.flip(rgba, axis=0) | ||
GenevieveBuckley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Create opacity values to overwrite default matplotlib opacity=1.0 | ||
opacity = np.linspace(min_opacity, max_opacity, num=_num_elements) | ||
if reverse_opacity: opacity = np.flip(opacity, axis=0) | ||
rgba[:,-1] = opacity # replace opacity=1 with actual opacity | ||
transfer_function = TransferFunction(rgba=rgba) | ||
return transfer_function | ||
|
||
|
||
def load_transfer_functions(include_rgb_linear=True, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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). There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sth like this:
|
||
include_matplotlib=True, | ||
include_matplotlib_reversed=True): | ||
"""Load predefined transfer functions into a dictionary. | ||
|
||
:param include_rgb_linear: load transfer functions from individual | ||
RGB values & linear opacity, optional. | ||
:param include_matplotlib: load transfer functions from matplotlib | ||
colormaps & linear opacity, optional. | ||
:param include_matplotlib_reversed: load transfer functions from | ||
REVERSED matplotlib colormaps, optional. | ||
:type include_rgb_linear: bool | ||
:type include_matplotlib: bool | ||
:type include_matplotlib_reversed: bool | ||
:return: dictionary of predefined transfer functions. | ||
:rtype: dict of ipyvolume TransferFunction instances | ||
""" | ||
transfer_functions = {} | ||
# RGB primary and secondary colors | ||
if include_rgb_linear: | ||
colors = {'grey': (0, 0, 0), | ||
'red': (255, 0, 0), | ||
'green': (0, 255, 0), | ||
'blue': (0, 0, 255), | ||
'yellow': (255, 255, 0), | ||
'magenta': (255, 0, 255), | ||
'cyan': (0, 255, 255)} | ||
for color_key in colors: | ||
rgb = colors[color_key] | ||
tf = linear_transfer_function(rgb) | ||
transfer_functions[color_key] = tf | ||
tf_reversed = linear_transfer_function(rgb, reverse_opacity=True) | ||
transfer_functions[color_key + '_r'] = tf_reversed | ||
# matplotlib colormaps | ||
if include_matplotlib: | ||
matplotlib_colormaps = [m for m in dir(matplotlib.cm) if not m.endswith("_r")] | ||
for colormap in matplotlib_colormaps: | ||
try: | ||
transfer_functions[colormap] = matplotlib_transfer_function(colormap) | ||
except ValueError: | ||
continue | ||
# reversed matplotlib colormaps | ||
if include_matplotlib_reversed: | ||
reversed_matplotlib_colormaps = [m for m in dir(matplotlib.cm) if m.endswith("_r")] | ||
for colormap in reversed_matplotlib_colormaps: | ||
try: | ||
transfer_functions[colormap] = matplotlib_transfer_function(colormap) | ||
except ValueError: | ||
continue | ||
return transfer_functions |
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.
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.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'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).
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.
It would be great if we do it on the frontend, and indeed use the color picker!
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 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.