VTK update position of multiple render windows
Asked Answered
B

1

8

I'm running into a bit of a problem when trying to run multiple render windows in a Python VTK application I'm writing. The application is an attempt to render a 3D model in two separate views for a stereo application (i.e. Left render and right render), but I'm having an issue with updating the cameras of each window simultaneously. I currently have two nearly identical pipelines set up, each with its own vtkCamera, vtkRenderWindow, vtkRenderer, and vtkRenderWindowInteractor, the only difference being that the right camera is positionally shifted 30 units along the X axis.

Each of the render window interactors is being updated via the vtkRenderWindowInteractor.AddObserver() method that calls a simple function to reset the cameras to their original positions and orientations. The biggest issue is that this only seems to occur on one window at a time, specifically the window in focus at the time. It's as if the interactor's timer just shuts off once the interactor loses focus. In addition, when I hold down the mouse (And thus move the camera around), the rendered image begins to 'drift', resetting to a less and less correct position even though I have hardcoded the coordinates into the function.

Obviously I'm very new to VTK, and much of what goes on is fairly confusing as so much is hidden in the backend, so it would be amazing to acquire some assistance on the matter. My code is below. Thanks guys!

from vtk import*
from parse import *
import os
import time, signal, threading

def ParseSIG(signum, stack):
        print signum
        return

class vtkGyroCallback():
        def __init__(self):
                pass
        def execute(self, obj, event):
                #Modified segment to accept input for leftCam position 
                gyro = (raw_input())
                xyz = parse("{} {} {}", gyro)
                #This still prints every 100ms, but camera doesn't update!
                print xyz

                #These arguments are updated and the call is made.
                self.leftCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
                self.leftCam.SetFocalPoint(0,0,0)
                self.leftCam.SetViewUp(0,1,0)
                self.leftCam.OrthogonalizeViewUp()

                self.rightCam.SetPosition(10, 40, 100)
                self.rightCam.SetFocalPoint(0,0,0)
                self.rightCam.SetViewUp(0,1,0)
                self.rightCam.OrthogonalizeViewUp()

                #Just a guess
                obj.Update()
                return

def main():

        # create two cameras
        cameraR = vtkCamera()
        cameraR.SetPosition(0,0,200)
        cameraR.SetFocalPoint(0,0,0)

        cameraL = vtkCamera()
        cameraL.SetPosition(40,0,200)
        cameraL.SetFocalPoint(0,0,0)



        # create a rendering window and renderer
        renR = vtkRenderer()
        renR.SetActiveCamera(cameraR)

        renL = vtkRenderer()
        renL.SetActiveCamera(cameraL)

        # create source
        reader = vtkPolyDataReader()
        path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
        reader.SetFileName(path)
        reader.Update()

        # create render window

        renWinR = vtkRenderWindow()
        renWinR.AddRenderer(renR)
        renWinR.SetWindowName("Right")

        renWinL = vtkRenderWindow()
        renWinL.AddRenderer(renL)
        renWinL.SetWindowName("Left")

        # create a render window interactor
        irenR = vtkRenderWindowInteractor()
        irenR.SetRenderWindow(renWinR)

        irenL = vtkRenderWindowInteractor()
        irenL.SetRenderWindow(renWinL)

        # mapper
        mapper = vtkPolyDataMapper()
        mapper.SetInput(reader.GetOutput())

        # actor
        actor = vtkActor()
        actor.SetMapper(mapper)

        # assign actor to the renderer
        renR.AddActor(actor)
        renL.AddActor(actor)

        # enable user interface interactor
        renWinR.Render()
        renWinL.Render()
        irenR.Initialize()
        irenL.Initialize()

        #Create callback object for camera manipulation
        cb = vtkGyroCallback()
        cb.rightCam = cameraR
        cb.leftCam = cameraL
        renWinR.AddObserver('InteractionEvent', cb.execute)
        renWinL.AddObserver('InteractionEvent', cb.execute)
        irenR.AddObserver('TimerEvent', cb.execute)
        irenL.AddObserver('TimerEvent', cb.execute)
        timerIDR = irenR.CreateRepeatingTimer(100)
        timerIDL = irenL.CreateRepeatingTimer(100)

        irenR.Start()
        irenL.Start()

if __name__ == '__main__':
    main()

EDIT:

Upon further viewing it seems like the TimerEvents aren't firing more than once in a row after a MouseClickEvent and I have no idea why.

EDIT 2: Scratch that, they are most definitely firing as per some test outputs I embedded in the code. I modified the code to accept user input for the self.leftCam.SetPosition() call within the vtkGyroCallback.execute() method (Thus replacing the hardcoded "10, 40, 100" parameters with three input variables) then piped the output of a script that simply displayed three random values into my main program. What this should have accomplished was having a render window that would constantly change position. Instead, nothing happens until I click on the screen, at which point the expected functionality begins. The whole time, timer events are still firing and inputs are still being accepted, yet the cameras refuse to update until a mouse event occurs within the scope of their window. What is the deal?

EDIT 3: I've dug around some more and found that within the vtkObject::InvokeEvent() method that is called within every interaction event there is a focus loop that overrides all observers that do not pertain to the object in focus. I'm going to investigate if there is a way to remove focus so that it will instead bypass this focus loop and go to the unfocused loop that handles non focused objects.

Brushwork answered 2/7, 2014 at 20:17 Comment(2)
All fixed! If you want the bounty, tell me how to get it to run smoothly (Framerate currently sits at 2-3 FPS with solution shown in my comment below) :DBrushwork
Scratch that, solved that issue too! I should pay myself for this stuff ;) My method of using two separate vtkRenderWindows was slow and bulky. I switched to using two vtkRenderers inside of a single vtkRenderWindow and the framerate shot up to easily 60FPS. Additionally, the Oculus Rift doesn't require multiple windows as each eye screen is technically part of the same monitor, so your whole application can be on a single window.Brushwork
B
9

So the solution was surprisingly simple, but thanks to the lack of quality documentation provided by VTK, I was left to dig through the source to find it. Effectively all you have to do is pseudo-thread Render() calls from each of the interactors via whatever callback method you're using to handle your TimerEvents. I did this using ID properties added to each interactor (seen in code provided below). You can see that every time a TimerEvent is fired from the irenR interactor's internal timer (irenR handles the right eye), the irenL's Render() function is called, and vice versa.

To solve this I first realized that the standard interactor functionalities (Mouse events and the like), worked normally. So I dug around the source in vtkRenderWindowInteractor.cxx and realized that those methods were abstracted to the individual vtkInteractorStyle implementations. After rooting around in the vtkInteractorStyleTrackball.cxx source, I found that there was actually a Render() function within the vtkRenderWindowInteractor class. Go figure! The documentation sure didn't mention that!

Unfortunately, two renders at once is actually very slow. If I do this method with just one window (At which point it becomes unnecessary), it runs wonderfully. Framerate tanks with a second window though. Oh well, what can you do?

Here's my corrected code (Finally I can start working on what I was supposed to be developing):

from vtk import*
from parse import *
import os
import time, signal, threading

def ParseSIG(signum, stack):
        print signum
        return

class vtkGyroCallback():
        def __init__(self):
                pass
        def execute(self, obj, event):
                #Modified segment to accept input for leftCam position 
                gyro = (raw_input())
                xyz = parse("{} {} {}", gyro)
                #print xyz 



                # "Thread" the renders. Left is called on a right TimerEvent and right is called on a left TimerEvent.
                if obj.ID == 1 and event == 'TimerEvent':
                        self.leftCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
                        self.irenL.Render()
                        #print "Left"
                elif obj.ID == 2 and event == 'TimerEvent':
                        self.rightCam.SetPosition(float(xyz[0]), float(xyz[1]), float(xyz[2]))
                        self.irenR.Render()
                        #print "Right"
                return

def main():

        # create two cameras
        cameraR = vtkCamera()
        cameraR.SetPosition(0,0,200)
        cameraR.SetFocalPoint(0,0,0)

        cameraL = vtkCamera()
        cameraL.SetPosition(40,0,200)
        cameraL.SetFocalPoint(0,0,0)



        # create a rendering window and renderer
        renR = vtkRenderer()
        renR.SetActiveCamera(cameraR)

        renL = vtkRenderer()
        renL.SetActiveCamera(cameraL)

        # create source
        reader = vtkPolyDataReader()
        path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
        reader.SetFileName(path)
        reader.Update()

        # create render window

        renWinR = vtkRenderWindow()
        renWinR.AddRenderer(renR)
        renWinR.SetWindowName("Right")

        renWinL = vtkRenderWindow()
        renWinL.AddRenderer(renL)
        renWinL.SetWindowName("Left")

        # create a render window interactor
        irenR = vtkRenderWindowInteractor()
        irenR.SetRenderWindow(renWinR)

        irenL = vtkRenderWindowInteractor()
        irenL.SetRenderWindow(renWinL)

        # mapper
        mapper = vtkPolyDataMapper()
        mapper.SetInput(reader.GetOutput())

        # actor
        actor = vtkActor()
        actor.SetMapper(mapper)

        # assign actor to the renderer
        renR.AddActor(actor)
        renL.AddActor(actor)


        # enable user interface interactor
        renWinR.Render()
        renWinL.Render()
        irenR.Initialize()
        irenL.Initialize()

        #Create callback object for camera manipulation
        cb = vtkGyroCallback()
        cb.rightCam = renR.GetActiveCamera()#cameraR
        cb.leftCam = renL.GetActiveCamera()#cameraL
        cb.irenR = irenR
        cb.irenL = irenL

        irenR.ID = 1
        irenL.ID = 2
        irenR.AddObserver('TimerEvent', cb.execute)
        irenL.AddObserver('TimerEvent', cb.execute)
        timerIDR = irenR.CreateRepeatingTimer(100)
        timerIDL = irenL.CreateRepeatingTimer(100)

        irenL.Start()
        irenR.Start()

if __name__ == '__main__':

        main()
Brushwork answered 7/7, 2014 at 20:55 Comment(1)
Fun fact over two years later: You can just use one window with multiple renderers inside of it using the setViewport function. I don't have the code on me (It's probably still on some school hard drive 700 miles away), but when done this way the framerate is not an issue. Check out the official example from the VTK guys to get a better idea of what I mean.Brushwork

© 2022 - 2024 — McMap. All rights reserved.