#python #multithreading #listener #render #vtk
#python #многопоточность #слушатель #визуализация #vtk
Вопрос:
Я работал над прототипом приложения на Python для стереоскопического отображения 3D-модели с использованием VTK, но я столкнулся с некоторыми проблемами в интерфейсе. Цель приведенного ниже кода на данный момент состоит в том, чтобы увеличить масштаб обоих окон визуализации при нажатии средней мыши. Однако при вызове vtkRenderWindowInteractor.Start()
функции мои vtkRenderWindowInteractor
файлы фактически останавливают всю программу, как если бы они выполнялись в одном потоке. Еще более любопытно то, что прерывания клавиатуры не выдаются, когда я использую CTRL-C (я работаю в оболочке UNIX), пока я не закрою окна рендеринга вручную с помощью кнопки «x». Если я просто закрою окно вручную, не нажимая CTRL-C, программа запустится сразу после Start()
вызова (например, в приведенном ниже коде, бесконечный цикл while). Я привел последовательность снимков экрана в конце этого поста, чтобы точно представить, что происходит в том случае, если мое объяснение сбивает с толку.
Я пробовал несколько обходных путей, чтобы исправить это, но пока ни один из них не сработал. Разделение рендеринга на изолированные потоки не имело никакого значения, даже когда я пытался использовать ncurses для ввода, в то время как разветвление их на новый процесс привело к некоторым проблемам с ОС, с которыми я бы предпочел не иметь дела. Самый современный метод стилей взаимодействия (показан ниже), в котором я просто использую встроенные прослушиватели VTK, работает в определенной степени, позволяя мне обнаруживать входные данные, когда окна находятся в фокусе, а интеракторы активны, но из-за отсутствия связи между камерой и MyInteractorStyle
классом я не могу получить доступ камеры без включения повторяют цикл после Start()
звонка, что приводит меня прямо к тому, с чего я начал.
Есть какие-нибудь мысли? Я просто неправильно понимаю, как предполагается использовать инструменты рендеринга VTK?
from vtk import*
import os.path
#import thread
#import time
#import threading
#import curses
class MyInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
pos1 = [0, 0, 200]
foc1 = [0, 0, 0]
pos2 = [40, 0, 200]
foc2 = [0, 0, 0]
def __init__(self,parent=None):
self.AddObserver("MiddleButtonPressEvent", self.middleButtonPressEvent)
self.AddObserver("MiddleButtonReleaseEvent", self.middleButtonReleaseEvent)
def middleButtonPressEvent(self,obj,event):
print "Middle button pressed"
self.pos1[2] = 10
self.pos2[2] = 30
self.OnMiddleButtonDown()
return
def middleButtonReleaseEvent(self,obj,event):
print "Middle button released"
self.OnMiddleButtonUp()
return
def main():
# create two cameras
camera1 = vtkCamera()
camera1.SetPosition(0,0,200)
camera1.SetFocalPoint(0,0,0)
camera2 = vtkCamera()
camera2.SetPosition(40,0,200)
camera2.SetFocalPoint(0,0,0)
# create a rendering window and renderer
ren1 = vtkRenderer()
ren1.SetActiveCamera(camera1)
ren2 = vtkRenderer()
ren2.SetActiveCamera(camera2)
# create source
reader = vtkPolyDataReader()
path = "/home/compilezone/Documents/3DSlicer/SlicerScenes/LegoModel-6_25/Model_5_blood.vtk"
reader.SetFileName(path)
print(path)
reader.Update()
# create render window
renWin1 = vtkRenderWindow()
renWin1.AddRenderer(ren1)
renWin2 = vtkRenderWindow()
renWin2.AddRenderer(ren2)
# create a render window interactor
inputHandler = MyInteractorStyle()
iren1 = vtkRenderWindowInteractor()
iren1.SetRenderWindow(renWin1)
iren1.SetInteractorStyle(inputHandler)
iren2 = vtkRenderWindowInteractor()
iren2.SetRenderWindow(renWin2)
iren2.SetInteractorStyle(inputHandler)
# mapper
mapper = vtkPolyDataMapper()
mapper.SetInput(reader.GetOutput())
# actor
actor = vtkActor()
actor.SetMapper(mapper)
# assign actor to the renderer
ren1.AddActor(actor)
ren2.AddActor(actor)
# enable user interface interactor
iren1.Initialize()
iren2.Initialize()
renWin1.Render()
renWin2.Render()
iren1.Start()
iren2.Start()
print "Test"
while 1:
pos1 = iren1.GetInteractorStyle().pos1
foc1 = iren1.GetInteractorStyle().foc1
pos2 = iren2.GetInteractorStyle().pos2
foc2 = iren2.GetInteractorStyle().foc2
print
if __name__ == '__main__':
main()
Запущенная программа
KeyboardInterrupt (нажатие CTRL-C и повторение в терминале, но ничего не происходит)
Рендеринг окон вручную закрыт, вызван KeyboardInterrupt
Комментарии:
1. По-видимому
Start()
, согласно документации, метод должен возвращаться только при ручном закрытии окна, но на самом деле это вызывает больше вопросов, чем ответов. Как можно было бы вызвать обаiren1
метода ‘s иiren2
‘sStart()
, еслиiren1
‘sStart()
не возвращались до закрытия окна?
Ответ №1:
Вызов Start()
RenderWindowInteractor запускает цикл событий, необходимый для выполнения событий визуализации, так же, как цикл событий в графическом интерфейсе. Итак, то, что вы пытаетесь сделать, запуская два цикла событий, на самом деле не имеет смысла.
Концептуальным обходным решением было бы не вызывать Start
RenderWindowInteractors, а написать небольшой графический интерфейс с несколькими специфичными для инструментария RenderWindowInteractors и использовать цикл событий этого GUI.
В качестве примера, вот как это делается с помощью специфичного для GUI toolkit кода в классе wxvtktrenderwindowinteractor от tvtk, который не вызывает start в RenderWindowInteractor, а вместо этого использует цикл событий GUI для управления событиями:
def wxVTKRenderWindowInteractorConeExample():
"""Like it says, just a simple example
"""
# every wx app needs an app
app = wx.PySimpleApp()
# create the top-level frame, sizer and wxVTKRWI
frame = wx.Frame(None, -1, "wxVTKRenderWindowInteractor", size=(400,400))
widget = wxVTKRenderWindowInteractor(frame, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(widget, 1, wx.EXPAND)
frame.SetSizer(sizer)
frame.Layout()
# It would be more correct (API-wise) to call widget.Initialize() and
# widget.Start() here, but Initialize() calls RenderWindow.Render().
# That Render() call will get through before we can setup the
# RenderWindow() to render via the wxWidgets-created context; this
# causes flashing on some platforms and downright breaks things on
# other platforms. Instead, we call widget.Enable(). This means
# that the RWI::Initialized ivar is not set, but in THIS SPECIFIC CASE,
# that doesn't matter.
widget.Enable(1)
widget.AddObserver("ExitEvent", lambda o,e,f=frame: f.Close())
ren = vtk.vtkRenderer()
widget.GetRenderWindow().AddRenderer(ren)
cone = vtk.vtkConeSource()
cone.SetResolution(8)
coneMapper = vtk.vtkPolyDataMapper()
coneMapper.SetInput(cone.GetOutput())
coneActor = vtk.vtkActor()
coneActor.SetMapper(coneMapper)
ren.AddActor(coneActor)
# show the window
frame.Show()
app.MainLoop()
(Обратите внимание, что этот код не изменен и имеет некоторые явные отличия от того, что вы пытаетесь сделать.)
Кроме того, причина, по которой ctrl C не работает, заключается в том, что цикл событий VTK ничего не делает с этим событием. Некоторые графические интерфейсы действительно уважают это событие, включая wxpython. Но если вы не используете графический интерфейс, который учитывает это событие (например, Qt), вы можете вручную указать интерпретатору python перехватить это событие и аварийно завершить работу вместо пересылки события в цикл событий GUI:
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
Комментарии:
1. Полезно знать, почему CTRL-C не работал. Я бы предпочел не использовать другой графический интерфейс, поскольку я уверен, что это можно сделать только в VTK. Есть ли у меня какой-нибудь способ написать прослушиватель событий, который самостоятельно управляет камерой, подобно, скажем,
leftButtonPressEvent()
VTK? Я искал источник для этого слушателя, но все, что я нашел, это заголовки, поэтому я не знаю примеров. Я поддержал вас, потому что предоставленная вами информация была хорошей, но предоставленное вами решение не соответствует масштабу моего проекта. Есть ли шанс, что вы могли бы предоставить чисто решение VTK?2. Что означает «прослушиватель событий, который сам управляет камерой»? Это кажется несовместимым с идеей прослушивателя событий, который прослушивает события. Я даже не знаю, что вы имеете в виду под этим. Вы имеете в виду создать прослушиватель событий, который прослушивает периодическое событие, которое повторяется через регулярные промежутки времени? Я подозреваю, что вы могли бы это сделать, но это было бы сложно, и я не понимаю, как это могло бы помочь.
3. Прошу прощения, я уточню. В предоставленных
vtkInteractorStyles
классах в VTK есть обработчики событийmiddleButtonPressEvent()
, такие какleftButtonPressEvent()
, и т.д. Я пытаюсь выяснить, как использовать эти обработчики (или написать свои собственные), чтобы я мог управлять камерой через свои собственные обработчики, а не через включенные. Конечная цель — иметь возможность управлять камерой с помощью данных гироскопа, а не с помощью мыши. Очевидно, это означает, что графический интерфейс не нужен, поэтому мне нужен способ изменения ориентации камеры, положения и т.д. без использования предоставленногоvtkInteractorStyles
4. Взгляните на ‘vtkCustomInteractorStyle’. Также посмотрите, как графические интерфейсы переопределяют стили взаимодействия по умолчанию, перехватывая события графического интерфейса.
5. Я только что еще раз перечитал свой первоначальный вопрос, и вы полностью ответили на него. Мне следовало бы задать новый вопрос по поводу моей
vtkInteractorStyles
проблемы, но, тем не менее, благодарю за ответы. Я проверю это.
Ответ №2:
Для тех, кто случайно столкнется с такой же проблемой, как невозможность управлять камерой из vtkInteractorStyle
классов, ознакомьтесь с Dolly()
, Pan()
, Spin()
, Rotate()
, Zoom()
, и UniformScale()
. Все это должно позволить вам получить доступ к камере из вашего дочернего класса vtkInteractorStyle
, который вы используете. Удачи!
РЕДАКТИРОВАТЬ: еще лучше, просто прикрепите свою камеру к своему классу, который наследуется от vtkInteractorStyle
как свойство, например:
style = MyInteractorStyleClass()
style.camera = myCam
Таким образом, вы можете получить к ней доступ из любого места вашего пользовательского класса! Довольно простой, но он пролетел мимо меня.