#python #render #vtk #stereo-3d
#python #визуализация #vtk #стерео-3d
Вопрос:
Я столкнулся с небольшой проблемой при попытке запустить несколько окон рендеринга в приложении Python VTK, которое я пишу. Приложение представляет собой попытку рендеринга 3D-модели в двух отдельных представлениях для стереоприложения (т. Е. левого рендеринга и правого рендеринга), но у меня возникла проблема с одновременным обновлением камер каждого окна. В настоящее время у меня настроено два почти идентичных конвейера, каждый со своими vtkCamera
, vtkRenderWindow
, vtkRenderer
и vtkRenderWindowInteractor
, с той лишь разницей, что правая камера позиционно сдвинута на 30 единиц вдоль оси X.
Каждый из взаимодействующих элементов окна рендеринга обновляется с помощью vtkRenderWindowInteractor.AddObserver()
метода, который вызывает простую функцию для сброса камер в их исходные положения и ориентации. Самая большая проблема заключается в том, что, похоже, это происходит только в одном окне одновременно, в частности, в окне, находящемся в фокусе в данный момент. Это как если бы таймер взаимодействия просто отключался, как только взаимодействие теряет фокус. Кроме того, когда я удерживаю мышь нажатой (и, таким образом, перемещаю камеру), визуализированное изображение начинает «дрейфовать», возвращаясь во все менее и менее правильное положение, даже несмотря на то, что я жестко закодировал координаты в функцию.
Очевидно, что я очень новичок в VTK, и многое из того, что происходит, довольно запутанно, поскольку многое скрыто в серверной части, поэтому было бы замечательно получить некоторую помощь по этому вопросу. Мой код приведен ниже. Спасибо, ребята!
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()
Редактировать:
При дальнейшем просмотре кажется, что TimerEvents запускаются не более одного раза подряд после MouseClickEvent, и я понятия не имею, почему.
РЕДАКТИРОВАТЬ 2: Поцарапайте это, они, безусловно, срабатывают в соответствии с некоторыми тестовыми выводами, которые я встроил в код. Я изменил код, чтобы принимать пользовательский ввод для self.leftCam.SetPosition()
вызова внутри vtkGyroCallback.execute()
метода (таким образом, заменив жестко заданные параметры «10, 40, 100» тремя входными переменными), затем передал вывод скрипта, который просто отображал три случайных значения в моей основной программе. Чего это должно было достичь, так это наличия окна рендеринга, которое постоянно меняло бы положение. Вместо этого ничего не происходит, пока я не нажму на экран, после чего начнется ожидаемая функциональность. Все это время события таймера все еще срабатывают, и входные данные все еще принимаются, однако камеры отказываются обновляться, пока событие мыши не произойдет в пределах их окна. В чем заключается сделка?
ПРАВКА 3: Я покопался еще немного и обнаружил, что в vtkObject::InvokeEvent()
методе, который вызывается в каждом событии взаимодействия, есть цикл фокусировки, который переопределяет всех наблюдателей, которые не относятся к объекту в фокусе. Я собираюсь исследовать, есть ли способ убрать фокус, чтобы вместо этого он обходил этот цикл фокусировки и переходил к несфокусированному циклу, который обрабатывает не сфокусированные объекты.
Комментарии:
1. Все исправлено! Если вы хотите вознаграждение, скажите мне, как заставить его работать плавно (частота кадров в настоящее время составляет 2-3 кадра в секунду, решение показано в моем комментарии ниже): D
2. Поцарапайте это, решите и эту проблему тоже! Я должен заплатить сам за это 😉 Мой метод использования двух отдельных
vtkRenderWindow
ов был медленным и громоздким. Я переключился на использование двухvtkRenderer
файлов внутри одногоvtkRenderWindow
, и частота кадров легко возросла до 60 кадров в секунду. Кроме того, Oculus Rift не требует нескольких окон, поскольку технически каждый экран eye является частью одного монитора, поэтому все ваше приложение может находиться в одном окне.
Ответ №1:
Итак, решение было на удивление простым, но из-за отсутствия качественной документации, предоставленной VTK, мне пришлось покопаться в исходном коде, чтобы найти его. Фактически все, что вам нужно сделать, это псевдопотоковые Render()
вызовы от каждого из взаимодействующих через любой метод обратного вызова, который вы используете для обработки ваших TimerEvent
файлов. Я сделал это, используя свойства идентификатора, добавленные к каждому взаимодействующему (см. приведенный ниже код). Вы можете видеть, что каждый раз, когда TimerEvent
запускается внутренний таймер irenR
взаимодействия ( irenR
обрабатывает правый глаз), вызывается функция irenL
‘s Render()
, и наоборот.
Чтобы решить эту проблему, я сначала понял, что стандартные функциональные возможности взаимодействия (события мыши и тому подобное) Работают нормально. Итак, я покопался в исходном коде в vtkRenderWindowInteractor.cxx и понял, что эти методы были абстрагированы для отдельных vtkInteractorStyle
реализаций. Покопавшись в исходном коде vtkInteractorStyleTrackball.cxx, я обнаружил, что в Render()
классе действительно существует vtkRenderWindowInteractor
функция. Поди разберись! В документации это точно не упоминалось!
К сожалению, два рендеринга одновременно на самом деле очень медленные. Если я выполняю этот метод только с одним окном (в этот момент это становится ненужным), он работает замечательно. Однако частота кадров зависит от второго окна. Ну и что ты можешь сделать?
Вот мой исправленный код (наконец-то я могу начать работать над тем, что я должен был разрабатывать):
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()
Комментарии:
1. Забавный факт более двух лет спустя: вы можете просто использовать одно окно с несколькими средствами рендеринга внутри него, используя функцию setViewport. У меня нет с собой кода (вероятно, он все еще находится на каком-нибудь школьном жестком диске в 700 милях отсюда), но при выполнении этого способа частота кадров не является проблемой. Ознакомьтесь с официальным примером от ребят из VTK, чтобы лучше понять, что я имею в виду.