paraview trame and animation example with timekeeper

I’m trying to figure out how to make a clip scan through a volume of an XDMF dataset and do that as a trame application. Using trame/examples/07_paraview/TimeAnimation/app.py at master · Kitware/trame · GitHub as an example - I can seek around to the end or middle or hard code the time I want to use from the XDMF dataset but I can’t figure out how to make the scene animation / timekeeper advance the clip plane like I can in paraview normal / python traces. The XDMF data source will display but when I hide it and display the clip the scene becomes empty. For the clip I’m trying to do a psuedo time of 0-1 interpolated to 0-100 in y for the animation and after I seek to my desired dataset time I set time_keeper.SetSuppressTimeSource(source). Animations seem to work a bit differently in trame.

Do you need animation cue? In trame you can programmatically drive the clip plane location independently from the time and just create an animation of that.

switched to a threshold; tried with an additional python cue - only a bit of luck.
app.py (10.0 KB)

It behaves rather oddly - it seems to pick up some cells at the edge of the threshold boundary. So it’s not drawing results correctly with the animation.

It also seems to be loading the geometry every time even though I marked the base reader datasource as an ignored time source, this makes the animation much slower than it should be since it’s really just trying to sweep through a single time snapshot.

r"""
Installation requirements:
    pip install trame trame-vuetify trame-vtk
"""

import paraview.web.venv  # Available in PV 5.10-RC2+
import os
import json
import asyncio
import numpy as np

from trame.app import get_server, asynchronous
from trame.widgets import vuetify, paraview
from trame.ui.vuetify import SinglePageLayout

import paraview as pv
from paraview import simple

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

server = get_server(client_type="vue2")
state, ctrl = server.state, server.controller

# -----------------------------------------------------------------------------

animation_scene = simple.GetAnimationScene()
time_keeper = animation_scene.TimeKeeper

metadata = None
time_values = []
representation = None

time_values = []
representation = None
reader = None
animationScene = None
pipelineEnd = None
pipelineEndDisplay = None
_min = 0
_max = 1

PythonAnimationCue1 = simple.PythonAnimationCue()
PythonAnimationCue1.Script= """
def start_cue(self): 
    Text1 = Text()
    Text1.Text = ''
    Show()

def tick(self): 
    animationScene1 = GetAnimationScene()
    animationTime = animationScene1.TimeKeeper.Time
    Text1 = FindSource("Text1")
    Text1.Text=str(animationTime)
    threshold = FindSource('threshold')
    threshold.UpperThreshold = animationTime #_min
def end_cue(self): pass
"""

def load_data(**kwargs):
    global time_values, representation, reader, animationScene, pipelineEnd, pipelineEndDisplay
    global _min, _max
    # CLI
    args, _ = server.cli.parse_known_args()
    full_path = os.path.abspath(args.data)
    #base_path = os.path.dirname(full_path)
    print(full_path)
    files = []
    reader_props = {}

    case_path = full_path
    animationScene = simple.GetAnimationScene()

    #code for setting up a reader
    reader = simple.XDMFReader(FileNames=case_path)
    simple.MergeBlocks()

    reader.UpdatePipeline()
    animationScene.GoToFirst()
    animationScene.GoToLast()
    animationScene.Stride = 1
    print(list(time_keeper.TimestepValues))
    #view = simple.GetActiveViewOrCreate('RenderView')
    #display = simple.Show(reader, view)

    data_info = reader.GetDataInformation()
    cell_fields = reader.CellData
    fields = list(reader.CellData.keys())
    print(fields)

    representation = simple.Show(reader, view)
    representation.Representation = 'Surface'
    representation.Opacity = 0.1
    simple.ColorBy(representation, None)
    time_values = list(time_keeper.TimestepValues)

    state.fields = fields

    reader.UpdatePipeline()
    print(animationScene.AnimationTime)
    if len(reader.CellData.keys()) == 0:
        print("Failed to find any cell fields do you have no fields at time index 0?  Also check if case type should be recomposed or decomposed")
    else:
        print(reader.CellData.keys())

    #adjust to  suitable field to threshold on
    foi = 'foi'
    _min, _max = reader.CellData.GetArray(foi).GetRange()

    threshold = simple.Threshold(registrationName='threshold', Input=reader)
    threshold.UpperThreshold =  _min
    threshold.Scalars = ['CELLS', foi]
    threshold.UseContinuousCellRange = 0
    threshold.AllScalars = 0
    threshold.ThresholdMethod = 'Above Upper Threshold'   

    renderView = simple.GetActiveViewOrCreate('RenderView')
    representation = simple.Show(threshold, renderView, 'UnstructuredGridRepresentation')
    pipelineEnd = threshold

    representation.Representation = 'Surface'
    pipelineEndDisplay = representation
    simple.Hide(reader, renderView)
    representation.UpdatePipeline()
    renderView.Update()

    simple.ColorBy(representation, None)


    keyFrame0 = simple.CompositeKeyFrame(KeyTime=_min, KeyValues=[_min], Interpolation='Ramp')
    keyFrame1 = simple.CompositeKeyFrame(KeyTime = _max, KeyValues=[_max], Interpolation='Ramp')

    cue = simple.GetAnimationTrack('UpperThreshold', index=0, proxy=threshold)

    cue.KeyFrames = [keyFrame0, keyFrame1]

    animationScene.GoToLast()
    simple.Render()
    time_keeper.SetSuppressTimeSource(reader, True)

    #animationScene.Cues.append(PythonAnimationCue1)

    animationScene.StartTime = _min
    animationScene.PlayMode = 'Sequence'
    #animationScene.Duration=[_max - _min]
    animationScene.NumberOfFrames = 100
    animationScene.FramesPerTimestep = 1
    animationScene.Loop = 1

    animationScene.GoToFirst()

    state.time_value = _min
    state.times = [float(x) for x in np.arange(_min, _max, animationScene.NumberOfFrames).tolist()]
    state.time_min = state.times[0]
    state.time_max = state.times[-1]

    #simple.HideScalarBarIfNotNeeded(pLUT, renderView1)

    #animationScene.Play()

    update_color_by(None, fields, "remote")

    simple.ResetCamera()
    view.CenterOfRotation = view.CameraFocalPoint
    update_view("local")


ctrl.on_server_ready.add(load_data)

# -----------------------------------------------------------------------------
# ParaView pipeline
# -----------------------------------------------------------------------------

# Rendering setup
view = simple.GetRenderView()
view.UseColorPaletteForBackground = 0
view.Background = [0.8, 0.8, 0.8]
view.OrientationAxesVisibility = 0
view = simple.Render()

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

@state.change("active_array")
def update_color_by(active_array, fields, viewMode="remote", **kwargs):
    print('update_color_by')
    #calculate?
    if len(fields) == 0:
        return
    displayProperties = pipelineEndDisplay
    displayProperties.UpdatePipeline()
    fieldName = active_array #fields[active_array]
    displayProperties.ColorArrayName = ['CELLS', fieldName]
    simple.UpdateScalarBars(view)
    displayProperties.RescaleTransferFunctionToDataRange()

    name = fieldName
    if name is not None:
        lut = simple.GetColorTransferFunction(name)
        pwf = simple.GetOpacityTransferFunction(name)
        try:
            _min, _max = reader.CellData.GetArray(fieldName).GetRange()
            pwf.RescaleTransferFunction(_min, _max)
        except AttributeError as e:
            print(f"error accessing on {fieldName}")
            pass
    update_view(viewMode)


@state.change("time")
def update_time(time, viewMode, **kwargs):
    print(f'update_time time {time} {viewMode}')
    if len(time_values) == 0:
        return

    if time >= time_values[-1] or len(time_values) == 0:
        time = 0
        state.time = time
    time_value = time
    time_keeper.Time = time_value
    state.time_value = time_value
    threshold = simple.FindSource('threshold')
    threshold.UpperThreshold = time
    print(f"update time post update threshold Upper: {threshold.UpperThreshold}")
    update_view(viewMode)


@state.change("play")
@asynchronous.task
async def update_play(**kwargs):
    print(f'update_play')
    while state.play:
        with state:
            print(f'update_play time= {state.time}')
            #dt...
            state.time += 1
            update_time(state.time, state.viewMode)

        #await asyncio.sleep(0.1)
        await asyncio.sleep(3)


@state.change("viewMode")
def update_view(viewMode, **kwargs):
    print(f'update_view')
    simple.Render()
    ctrl.view_update()
    #ctrl.view_update_image()
    #we don't need to update the geometry if it's colormap only mods..
    #todo: test if we can make this conditional?
    #ctrl.view_update_geometry()
    #if viewMode == "local":
    #    ctrl.view_update_geometry()


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

state.trame__title = "ParaView"

with SinglePageLayout(server) as layout:
    layout.title.set_text("Time")
    layout.icon.click = ctrl.view_reset_camera

    with layout.toolbar:
        vuetify.VSpacer()
        vuetify.VSelect(
            v_model=("active_array", 0),
            items=("fields", []),
            style="max-width: 200px",
            hide_details=True,
            dense=True,
        )
        vuetify.VTextField(
            v_model=("time_value", 0),
            disabled=True,
            hide_details=True,
            dense=True,
            style="max-width: 200px",
            classes="mx-2",
        )
        vuetify.VSlider(
            v_model=("time", 0),
            min=("time_min", 0),
            max=("time_max", 0),
            hide_details=True,
            dense=True,
            style="max-width: 600px",
        )

        vuetify.VCheckbox(
            v_model=("play", False),
            off_icon="mdi-play",
            on_icon="mdi-stop",
            hide_details=True,
            dense=True,
            classes="mx-2",
        )

        with vuetify.VBtn(icon=True, click=ctrl.view_reset_camera):
            vuetify.VIcon("mdi-crop-free")

        vuetify.VCheckbox(
            v_model=("viewMode", "remote"),
            true_value="remote",
            false_value="local",
            off_icon="mdi-rotate-3d",
            on_icon="mdi-video-image",
            hide_details=True,
            dense=True,
            classes="mx-2",
        )

        vuetify.VProgressLinear(
            indeterminate=True,
            absolute=True,
            bottom=True,
            active=("trame__busy",),
        )

    with layout.content:
        with vuetify.VContainer(fluid=True, classes="pa-0 fill-height"):
            html_view = paraview.VtkRemoteLocalView(view, namespace="view")
            ctrl.view_update = html_view.update
            ctrl.view_update_geometry = html_view.update_geometry
            ctrl.view_update_image = html_view.update_image
            ctrl.view_reset_camera = html_view.reset_camera


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

if __name__ == "__main__":
    server.cli.add_argument("--data", help="Path to state file", dest="data")
    server.start()

Why do you insist on using the time animation infrastructure when your code could be so much simpler without it?

Why do you use VtkRemoteLocalView instead of VtkRemoteView?

If you need help and dedicated guidance, Kitware offer some easy to use support packages.

what’s here is based on the example in trame trame/examples/07_paraview/TimeAnimation/app.py at master · Kitware/trame · GitHub , definitely for the VtkRemotelocalView

Sorry about the animation cue; so you’re saying just do a async time loop and manipulate the planes / thresholds directly?

Yes, that’s what I was saying all along. That example was just using the ParaView Python API to make the reader pick a new time (since that was what we wanted to animate). But in your case, time is not relevant, just the animation part with the async loop.