Kitware / paraviewweb

Web framework for building interactive visualization relying on VTK or ParaView to produce visualization data
http://kitware.github.io/paraviewweb/
BSD 3-Clause "New" or "Revised" License
160 stars 50 forks source link

Send rendering command from a timed thread #502

Closed skoudoro closed 3 years ago

skoudoro commented 4 years ago

Hi Paraviewweb Team and @jourdain ,

We are using the vtk_server.py example and we want to make the cone rotate automatically every 1 second.

Unfortunately, the animation runs only when there is an interaction on the client-side. For example, when we click / pan / zoom, we can see the cone rotation but it stops as soon as we stop to interact.

  1. How can we refresh the client via the python server to force the animation?

  2. Do you have an example of a heavy computation running on a thread and updating a progress bar on the client-side?

Thank you

jourdain commented 4 years ago

Just to be sure, are you using the ParaView proxies or the VTK classes to implement your server side. Based on the file name, I'm leaning toward the VTK side but I just want to be sure.

Also what protocol are you using to have the images sent to the client. The publish based version or the original one where the client only ask for images when interacting?

It might be easy to help you if I could see the code of your server side.

Regarding the progress part, you can indeed do that assuming the server is not fully busy. To achieve that you will need to use the publish/subscribe mechanism. Then you can use the twisted.reator to schedule updates to be sent to the client using timeout or interval.

HTH,

Seb

skoudoro commented 4 years ago

Hi @jourdain,

are you using the ParaView proxies or the VTK classes to implement your server side

We are using VTK classes to implement our server side but we run it with pvpython from paraview.

Also what protocol are you using to have the images sent to the client.

This is where I am confused, I suppose the original one where the client asks for images when interacting.

you will need to use the publish/subscribe mechanism. Then you can use the twisted.reator to schedule updates to be sent to the client using timeout or interval.

Good to know! Do you have an example of that somewhere?

Thank you for your answer. Below, our code simplified. In this case, the progress bar is not here but we have the exact same behavior: if no interaction, the camera does not rotate. Any idea of how to refresh the client from the server-side to see the rotation? (note: we spawn a thread to simulate a heavy computation)

# import to process args
import sys
import os
import threading

# import vtk modules.
import vtk
from vtk.web import protocols
from vtk.web import wslink as vtk_wslink
from wslink import server

try:
    import argparse
except ImportError:
    # since  Python 2.6 and earlier don't have argparse, we simply provide
    # the source for the same as _argparse and we use it instead.
    from vtk.util import _argparse as argparse

# =============================================================================
# Create custom ServerProtocol class to handle clients requests
# =============================================================================

class _WebCone(vtk_wslink.ServerProtocol):

    # Application configuration
    view    = None
    authKey = "wslink-secret"

    def initialize(self):
        global renderer, renderWindow, renderWindowInteractor, cone, mapper, actor

        # Bring used components
        self.registerVtkWebProtocol(protocols.vtkWebMouseHandler())
        self.registerVtkWebProtocol(protocols.vtkWebViewPort())
        self.registerVtkWebProtocol(protocols.vtkWebViewPortImageDelivery())
        self.registerVtkWebProtocol(protocols.vtkWebViewPortGeometryDelivery())

        # Update authentication key to use
        self.updateSecret(_WebCone.authKey)

        # Create default pipeline (Only once for all the session)
        if not _WebCone.view:
            # VTK specific code
            renderer = vtk.vtkRenderer()
            renderWindow = vtk.vtkRenderWindow()
            renderWindow.AddRenderer(renderer)

            renderWindowInteractor = vtk.vtkRenderWindowInteractor()
            renderWindowInteractor.SetRenderWindow(renderWindow)
            renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

            cone = vtk.vtkConeSource()
            mapper = vtk.vtkPolyDataMapper()
            actor = vtk.vtkActor()

            mapper.SetInputConnection(cone.GetOutputPort())
            actor.SetMapper(mapper)

            renderer.AddActor(actor)
            renderer.ResetCamera()
            renderWindow.Render()

            def calc_suare(numbers):
                print("Calculate square numbers")
                for n in numbers:
                    time.sleep(1)
                    print("square:", n*n)
                    renderer.GetActiveCamera().Azimuth(10)

           # We Create a thread to test heavy computation and not make our server busy
            arr = np.random.rand(100)
            t1 = threading.Thread(target=calc_suare, args=(arr))
            t1.start()

            # VTK Web application specific
            _WebCone.view = renderWindow
            self.getApplication().GetObjectIdMap().SetActiveObject("VIEW", renderWindow)

# =============================================================================
# Main: Parse args and start server
# =============================================================================

if __name__ == "__main__":
    # Create argument parser
    parser = argparse.ArgumentParser(description="VTK/Web Cone web-application")

    # Add default arguments
    server.add_arguments(parser)

    # Extract arguments
    args = parser.parse_args()

    # Configure our current application
    _WebCone.authKey = args.authKey

    # Start server
    server.start_webserver(options=args, protocol=_WebCone)
jourdain commented 4 years ago

So on the server side, the protocol that you need to register will be that one.

The only examples are on the ParaView side but the behavior will be the same.

Below is a list of important references:

HTH,

Seb

skoudoro commented 4 years ago

Thank you really much for these links! Quite exciting!

I will play a bit and close this PR as soon as I have something working.

Thank you very much @jourdain! Did you see this @garyfallidis?

Serge

jourdain commented 4 years ago

Actually you should start by creating the server side of that example in pure VTK. That way we can update the example on the vtk.js side. I wanted to do it so we could have both a VTK and PV server with the same client but never got a chance to do it.

Then you can start pushing dynamic update from the server side on your own term... ;-)

Garyfallidis commented 4 years ago

Wonderful! Thank you @jourdain and @skoudoro! :)

jourdain commented 4 years ago

It seems that I found some pure vtk server code that might just work


# import to process args
import os
import sys

# Try handle virtual env if provided
if '--virtual-env' in sys.argv:
  virtualEnvPath = sys.argv[sys.argv.index('--virtual-env') + 1]
  virtualEnv = virtualEnvPath + '/bin/activate_this.py'
  execfile(virtualEnv, dict(__file__=virtualEnv))

import vtk
from vtk.web import protocols
from vtk.web import wslink as vtk_wslink
from wslink import server

import argparse

class _WebCone(vtk_wslink.ServerProtocol):

    # Application configuration
    view    = None
    authKey = "wslink-secret"

    def initialize(self):
        global renderer, renderWindow, renderWindowInteractor, cone, mapper, actor

        # Bring used components
        self.registerVtkWebProtocol(protocols.vtkWebMouseHandler())
        self.registerVtkWebProtocol(protocols.vtkWebViewPort())
        self.registerVtkWebProtocol(protocols.vtkWebPublishImageDelivery(decode=False))
        self.registerVtkWebProtocol(protocols.vtkWebViewPortGeometryDelivery())

        # Update authentication key to use
        self.updateSecret(_WebCone.authKey)

        # tell the C++ web app to use no encoding. vtkWebPublishImageDelivery must be set to decode=False to match.
        self.getApplication().SetImageEncoding(0)

        # Create default pipeline (Only once for all the session)
        if not _WebCone.view:
            # VTK specific code
            renderer = vtk.vtkRenderer()
            renderWindow = vtk.vtkRenderWindow()
            renderWindow.AddRenderer(renderer)

            renderWindowInteractor = vtk.vtkRenderWindowInteractor()
            renderWindowInteractor.SetRenderWindow(renderWindow)
            renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

            cone = vtk.vtkConeSource()
            mapper = vtk.vtkPolyDataMapper()
            actor = vtk.vtkActor()

            mapper.SetInputConnection(cone.GetOutputPort())
            actor.SetMapper(mapper)

            renderer.AddActor(actor)
            renderer.ResetCamera()
            renderWindow.Render()

            # VTK Web application specific
            _WebCone.view = renderWindow
            self.getApplication().GetObjectIdMap().SetActiveObject("VIEW", renderWindow)

if __name__ == "__main__":
    # Create argument parser
    parser = argparse.ArgumentParser(description="VTK/Web Cone web-application")
    # Add default arguments
    server.add_arguments(parser)

    # Just for help and prevent issue when provided
    parser.add_argument("--virtual-env", default=None, help="Path to virtual environment to use")

    # Extract arguments
    args = parser.parse_args()

    # Configure our current application
    _WebCone.authKey = args.authKey

    # Start server
    server.start_webserver(options=args, protocol=_WebCone)
skoudoro commented 4 years ago

Thank you @jourdain, we will try it and provide feedback.

skoudoro commented 3 years ago

Closing this issue. Everything work as expected.