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

VTK local rendering with scalar bar #101

Open
jourdain opened this issue Aug 8, 2022 · 15 comments
Open

VTK local rendering with scalar bar #101

jourdain opened this issue Aug 8, 2022 · 15 comments
Labels
available-with-wasm Not supported with vtk.js but works with WASM

Comments

@jourdain
Copy link
Collaborator

jourdain commented Aug 8, 2022

Need to better support scalarbar with and without widget along NaN and other settings.

@pazars
Copy link

pazars commented Sep 23, 2022

Is there an estimate to when this issue might be addressed?
Need to decide whether to use Trame for a project and rendering scalar bars is a crucial feature.

@jourdain
Copy link
Collaborator Author

jourdain commented Sep 23, 2022

Which limitation are you currently running into? It should mostly work already if you don't use it within a widget.
That issue is to make sure we fully support all the details rather than just the basic (which is currently supported).

@jourdain
Copy link
Collaborator Author

Also having an example highlighting the short comings could help speedup that process.

@pazars
Copy link

pazars commented Sep 29, 2022

Here is a demo of the issue colorbar_issue.zip
It contains the Python script, source file, and an image of the expected output.
Tested with Trame 2.2.0.

@pazars
Copy link

pazars commented Oct 5, 2022

Found that in <python_lib>\site-packages\trame_vtk\modules\common\serve\trame-vtk.js there is a parameter drawNanAnnotation: !0 in function Cs that trickles down to instances of vtkScalarBarActor. Setting drawNanAnnotation: !1 fixed the issue.

Not sure though what for and how exactly this trame-vtk.js is used.

@jourdain
Copy link
Collaborator Author

jourdain commented Oct 5, 2022

So I finally managed to look at your example and from what I see, the scalar bar is there but horizontal rather than vertical?
The drawNanAnnotation is to have or remove the red color for NaN values?

So I'm not sure what is the issue you would like us to fix? The positioning or the fact that you don't want to see the NaN color?

@jourdain
Copy link
Collaborator Author

jourdain commented Oct 5, 2022

For me it is working like you wanted. I just had to fix your UI code so the bottom does not get cropped.

My code with edit is below

"""Basic example demonstrating an issue with colorbars."""

from trame.app import get_server

from trame.ui.vuetify import SinglePageWithDrawerLayout
from trame.ui.router import RouterViewLayout
from trame.widgets import vtk, vuetify, trame, html, router

from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkColorTransferFunction,
    vtkDataSetMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
)

from vtkmodules.vtkIOXML import vtkXMLUnstructuredGridReader
from vtkmodules.vtkCommonDataModel import vtkDataObject
from vtkmodules.vtkCommonCore import vtkLookupTable
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkRenderingAnnotation import vtkScalarBarActor
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera


# Required for interactor initialization
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch  # noqa

# Required for rendering initialization, not necessary for
# local rendering, but doesn't hurt to include it
import vtkmodules.vtkRenderingOpenGL2  # noqa

import pathlib

# -----------------------------------------------------------------------------
# Globals
# -----------------------------------------------------------------------------


class Representation:
    Points = 0
    Wireframe = 1
    Surface = 2
    SurfaceWithEdges = 3


class LookupTable:
    Rainbow = 0
    Inverted_Rainbow = 1
    Greyscale = 2
    Inverted_Greyscale = 3


# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

# Source/Reader
source_path = pathlib.Path(__file__).with_name("patch_antenna.vtu")
if not source_path.exists():
    raise FileNotFoundError(source_path.as_posix())


vtk_source = vtkXMLUnstructuredGridReader()
vtk_source.SetFileName(source_path.as_posix())
vtk_source.Update()
show_array = "ElectricField"


# Misc stuff
colors = vtkNamedColors()

# Lookup table & color transfer function
num_colors = 256

ctf = vtkColorTransferFunction()
ctf.SetColorSpaceToDiverging()
ctf.AddRGBPoint(0.0, 0.230, 0.299, 0.754)
ctf.AddRGBPoint(1.0, 0.706, 0.016, 0.150)

lut = vtkLookupTable()
lut.SetNumberOfTableValues(num_colors)
lut.Build()

for i in range(0, num_colors):
    rgb = list(ctf.GetColor(float(i) / num_colors))
    rgb.append(1.0)
    lut.SetTableValue(i, *rgb)

scalar_range = vtk_source.GetOutput().GetScalarRange()

mapper = vtkDataSetMapper()
mapper.SetInputConnection(vtk_source.GetOutputPort())
mapper.ScalarVisibilityOn()
mapper.SetScalarRange(scalar_range)
mapper.SetLookupTable(lut)

# Actors
actor = vtkActor()
actor.SetMapper(mapper)
# Mesh: Setup default representation to surface
actor.GetProperty().SetRepresentationToSurface()
actor.GetProperty().SetPointSize(1)
actor.GetProperty().EdgeVisibilityOn()

scalar_bar = vtkScalarBarActor()
scalar_bar.SetLookupTable(mapper.GetLookupTable())
scalar_bar.SetNumberOfLabels(7)
scalar_bar.UnconstrainedFontSizeOn()
scalar_bar.SetMaximumWidthInPixels(100)
scalar_bar.SetMaximumHeightInPixels(800 // 3)
scalar_bar.SetTitle(show_array)


max_scalar = scalar_range[1]
if max_scalar < 1:
    precision = 4
elif max_scalar < 10:
    precision = 3
elif max_scalar < 100:
    precision = 2
else:
    precision = 1
scalar_bar.SetLabelFormat(f"%-#6.{precision}f")

# Render stuff
renderer = vtkRenderer()
renderer.SetBackground(colors.GetColor3d("SlateGray"))  # SlateGray
renderer.AddActor(actor)
renderer.AddActor2D(scalar_bar)

render_window = vtkRenderWindow()
render_window.SetSize(300, 300)
render_window.AddRenderer(renderer)
render_window.SetWindowName("VTK Test")

render_window_interactor = vtkRenderWindowInteractor()
interactor_style = vtkInteractorStyleTrackballCamera()
render_window_interactor.SetInteractorStyle(interactor_style)
render_window_interactor.SetRenderWindow(render_window)
renderer.ResetCamera()

# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------

server = get_server()
state, ctrl = server.state, server.controller

state.setdefault("active_ui", "geometry")
state.vtk_bground = "SlateGray"

# -----------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------

# Representation Callbacks
def update_representation(actor, mode):
    property = actor.GetProperty()
    if mode == Representation.Points:
        property.SetRepresentationToPoints()
        property.SetPointSize(5)
        property.EdgeVisibilityOff()
    elif mode == Representation.Wireframe:
        property.SetRepresentationToWireframe()
        property.SetPointSize(1)
        property.EdgeVisibilityOff()
    elif mode == Representation.Surface:
        property.SetRepresentationToSurface()
        property.SetPointSize(1)
        property.EdgeVisibilityOff()
    elif mode == Representation.SurfaceWithEdges:
        property.SetRepresentationToSurface()
        property.SetPointSize(1)
        property.EdgeVisibilityOn()


@state.change("mesh_representation")
def update_mesh_representation(mesh_representation, **kwargs):
    update_representation(actor, mesh_representation)
    ctrl.view_update()


# Color By Callbacks
def color_by_array(actor, array):
    _min, _max = array.get("range")
    mapper = actor.GetMapper()
    mapper.SelectColorArray(array.get("text"))
    mapper.GetLookupTable().SetRange(_min, _max)
    if array.get("type") == vtkDataObject.FIELD_ASSOCIATION_POINTS:
        mapper.SetScalarModeToUsePointFieldData()
    else:
        mapper.SetScalarModeToUseCellFieldData()
    mapper.SetScalarVisibility(True)
    mapper.SetUseLookupTableScalarRange(True)


# Opacity Callbacks
@state.change("mesh_opacity")
def update_mesh_opacity(mesh_opacity, **kwargs):
    actor.GetProperty().SetOpacity(mesh_opacity)
    ctrl.view_update()


def toggle_background():
    bgcolor = "SlateGray"
    if state.vtk_bground == "SlateGray":
        bgcolor = "black"

    state.vtk_bground = bgcolor
    renderer.SetBackground(colors.GetColor3d(bgcolor))

    ctrl.view_update()


# -----------------------------------------------------------------------------
# GUI ELEMENTS
# -----------------------------------------------------------------------------


def ui_card(title, ui_name):
    with vuetify.VCard(to="/", v_show=f"active_ui == '{ui_name}'"):
        vuetify.VCardTitle(
            title,
            classes="grey lighten-1 py-1 grey--text text--darken-3",
            style="user-select: none; cursor: pointer",
            hide_details=True,
            dense=True,
        )
        content = vuetify.VCardText(classes="py-2")
    return content


def mesh_card():
    with ui_card(title="Geometry", ui_name="geometry"):

        with vuetify.VRow(classes="pt-2", dense=True):
            vuetify.VSelect(
                # Representation
                v_model=("mesh_representation", Representation.Surface),
                items=(
                    "representations",
                    [
                        {"text": "Points", "value": 0},
                        {"text": "Wireframe", "value": 1},
                        {"text": "Surface", "value": 2},
                        {"text": "Surface With Edges", "value": 3},
                    ],
                ),
                label="Representation",
                hide_details=True,
                dense=True,
                outlined=True,
                classes="pt-1",
            )

        vuetify.VSlider(
            # Opacity
            v_model=("mesh_opacity", 1.0),
            min=0,
            max=1,
            step=0.1,
            label="Opacity",
            classes="mt-1",
            hide_details=True,
            dense=True,
        )


# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

with RouterViewLayout(server, "/"):
    with html.Div(style="height: 100%; width: 100%;"):
        view = vtk.VtkLocalView(render_window)
        ctrl.view_update.add(view.update)
        ctrl.on_server_ready.add(view.update)

with RouterViewLayout(server, "/foo"):
    with vuetify.VCard():
        vuetify.VCardTitle("This is foo")
        with vuetify.VCardText():
            vuetify.VBtn("Take me back", click="$router.back()")


with SinglePageWithDrawerLayout(server) as layout:
    layout.title.set_text("Colorbar issue example")

    with layout.toolbar as toolbar:
        toolbar.dense = True
        vuetify.VSpacer()
        vuetify.VDivider(vertical=True, classes="mx-2")
        vuetify.VSwitch(
            v_model=("$vuetify.theme.dark"),
            inset=True,
            hide_details=True,
            dense=True,
            change=toggle_background,
        )

    with layout.drawer as drawer:
        drawer.width = 325
        with vuetify.VList(shaped=True, v_model=("selectedRoute", 0)):
            with vuetify.VListGroup(value=("true",), sub_group=True):
                with vuetify.Template(v_slot_activator=True):
                    vuetify.VListItemTitle("3D View")
                mesh_card()

    with layout.content:
        with vuetify.VContainer(fluid=True, classes="pa-0 fill-height"):
            router.RouterView(style="width: 100%; height: 100%")


# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    server.start()

@jourdain
Copy link
Collaborator Author

jourdain commented Oct 5, 2022

Screen Shot 2022-10-05 at 4 09 13 PM

@pazars
Copy link

pazars commented Oct 10, 2022

At the time of writing I had never encountered NaN as part of the color bar. It suggested that there are NaN values in the dataset, which was not true, so I thought it was a bug. Later I found in vtkScalarBar's documentation that it's just a visual prop that can be toggled with DrawNanAnnotationOn and DrawNanAnnotationOff functions, irrespective of whether there actually are NaN values or not. However, I could not get them to work in Trame.

Looking at it now, I'm not sure if this is a bug. It just seems that the default behavior is to display the NaN annotation. However, it's a bit confusing from a user's perspective and not consistent with defaults for other views since this happens only for vtk.VtkLocalView.

@jourdain
Copy link
Collaborator Author

This has to do with the correct mapping between server object (VTK) properties vs local ones (vtk.js).
This is part of that issue. But the part that matter to you is just the NaN visibility.

@jourdain
Copy link
Collaborator Author

The NaN and text color will be managed in Kitware/trame-vtk#5

@j-hallen
Copy link

I've been trying to configure a scalar bar in my own Trame app, but the included code actually demonstrates the issue. The following call seems to have no effect:
scalar_bar.SetNumberOfLabels(7)

How can we increase from the default number of labels? And also, how to include the min and max values in the color bar labels? I've tried the following:
scalar_bar.AddRangeLabels = 1
which I found from the Paraview trace, but this also has no effect.

@jourdain
Copy link
Collaborator Author

If you want a 1-to-1 mapping, do remote rendering. Otherwise, improvement will have to be made in vtk.js to fully support all of those options.

@j-hallen
Copy link

j-hallen commented Aug 1, 2023

Thanks Sebastien. Remote rendering does not really work for my use case, so I'll try the custom annotations as a workaround. Do you know if extending vtk.js to approach 1:1 mapping is planned?

@jourdain
Copy link
Collaborator Author

jourdain commented Aug 2, 2023

We are exploring a path with wasm, but either way, I don't see it happening in the coming months unless it became a priority of a project with funding.

@jourdain jourdain added the available-with-wasm Not supported with vtk.js but works with WASM label Mar 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
available-with-wasm Not supported with vtk.js but works with WASM
Projects
None yet
Development

No branches or pull requests

3 participants