In VTK, I have a surface and a line, and the line is included by the surface. Then, I need to pick one point in the line. I implement myself interactor and get the world coordinate by a right button click. I hope the select point could be located in the line. I show the selected line in the renderer when right button release. However, I find I can not select a point in the line. My code is:
import vtk, os, sys
import numpy as np
from PyQt5.QtWidgets import *
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from vtk.util.numpy_support import vtk_to_numpy, numpy_to_vtk
def numpyToVtk(data, type=vtk.VTK_FLOAT):
flat_data_array = data.transpose(2,1,0).flatten()
vtk_data_array = numpy_to_vtk(flat_data_array)
vtk_data = numpy_to_vtk(num_array=vtk_data_array, deep=True, array_type=type)
img = vtk.vtkImageData()
img.GetPointData().SetScalars(vtk_data)
img.SetDimensions(data.shape)
img.SetOrigin(0, 0, 0)
img.SetSpacing(1, 1, 1)
return img
class ourInteractor(vtk.vtkInteractorStyleTrackballCamera):
def __init__(self, renderer=None, renWindow=None):
super(ourInteractor, self).__init__()
self.AddObserver("RightButtonReleaseEvent", self.OnRightButtonUp)
self.ren = renderer
self.renWin = renWindow
def OnRightButtonUp(self, obj, event):
super(ourInteractor, self).OnRightButtonUp()
pos = self.GetInteractor().GetEventPosition()
coordinate = vtk.vtkCoordinate()
coordinate.SetCoordinateSystemToDisplay()
coordinate.SetValue(pos[0], pos[1], 0)
worldCoor = coordinate.GetComputedWorldValue(
self.GetInteractor().GetRenderWindow().GetRenderers().GetFirstRenderer())
print('screen coor: ', pos, 'world coor: ', worldCoor)
points = vtk.vtkPoints()
vertices = vtk.vtkCellArray()
id = points.InsertNextPoint(worldCoor[0], worldCoor[1], worldCoor[2])
vertices.InsertNextCell(1)
vertices.InsertCellPoint(id)
point = vtk.vtkPolyData()
point.SetPoints(points)
point.SetVerts(vertices)
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputData(point)
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetPointSize(10)
actor.GetProperty().SetColor(0, 1, 0)
self.ren.AddActor(actor)
self.renWin.Render()
class AirwaySkeleton(QMainWindow):
def __init__(self, parent=None):
super(AirwaySkeleton, self).__init__(parent=parent)
self.setWindowTitle("Airway Skeleton")
widget = QWidget()
self.setCentralWidget(widget)
layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
widget.setLayout(layout)
self.mainLayout = layout
frame = QFrame()
vtkWidget = QVTKRenderWindowInteractor(frame)
self.mainLayout.addWidget(vtkWidget)
ren = vtk.vtkRenderer()
vtkWidget.GetRenderWindow().AddRenderer(ren)
iren = vtkWidget.GetRenderWindow().GetInteractor()
style = ourInteractor(renderer=ren, renWindow=vtkWidget.GetRenderWindow())
iren.SetInteractorStyle(style)
ren.SetBackground(0, 0, 0)
self.ren = ren
mask = np.zeros(shape=[200, 200, 200], dtype=np.uint8)
mask[20:80, 50:150, 50:150] = 1
mask[80:150, 80:120, 80:120] = 1
mask[150:170, 50:150, 50:150] = 1
xs = np.arange(20, 170, 0.1)
line = []
for x in xs:
line.append([x, 100, 100])
actors = self.createActorsForLines([np.array(line)])
vtkMask = numpyToVtk(data=mask, type=vtk.VTK_CHAR)
mesh = self.maskToMesh(vtkMask)
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(mesh.GetOutputPort())
mapper.ScalarVisibilityOff()
actor = vtk.vtkLODActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(1, 1, 1)
actor.GetProperty().SetOpacity(0.4)
self.ren.AddActor(actor)
for lineActor in actors:
self.ren.AddActor(lineActor)
self.renWin = vtkWidget.GetRenderWindow()
iren.Initialize()
self.iren = iren
def maskToMesh(self, mask):
contour = vtk.vtkDiscreteMarchingCubes()
contour.SetInputData(mask)
contour.SetValue(0, 1)
contour.Update()
smoother = vtk.vtkWindowedSincPolyDataFilter()
smoother.SetInputConnection(contour.GetOutputPort())
smoother.SetNumberOfIterations(30)
smoother.BoundarySmoothingOff()
smoother.NonManifoldSmoothingOn()
smoother.NormalizeCoordinatesOn()
smoother.Update()
triangleCellNormals = vtk.vtkPolyDataNormals()
triangleCellNormals.SetInputConnection(smoother.GetOutputPort())
triangleCellNormals.ComputeCellNormalsOn()
triangleCellNormals.ComputePointNormalsOff()
triangleCellNormals.ConsistencyOn()
triangleCellNormals.AutoOrientNormalsOn()
triangleCellNormals.Update()
return triangleCellNormals
def createActorsForLines(self, lines):
actors = []
endPoints = vtk.vtkPoints()
for line in lines:
n = line.shape[0]
endPoints.InsertNextPoint(line[0, 0], line[0, 1], line[0, 2])
endPoints.InsertNextPoint(line[-1, 0], line[-1, 1], line[-1, 2])
points = vtk.vtkPoints()
vtkLines = vtk.vtkCellArray()
vtkLines.InsertNextCell(n)
for i in range(n):
points.InsertNextPoint(line[i, 0], line[i, 1], line[i, 2])
vtkLines.InsertCellPoint(i)
polygonPolyData = vtk.vtkPolyData()
polygonPolyData.SetPoints(points)
polygonPolyData.SetLines(vtkLines)
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputData(polygonPolyData)
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(1, 0, 0)
actors.append(actor)
polyData = vtk.vtkPolyData()
polyData.SetPoints(endPoints)
sphereSource = vtk.vtkSphereSource()
sphereSource.SetRadius(1)
glyph3D = vtk.vtkGlyph3D()
glyph3D.SetSourceConnection(sphereSource.GetOutputPort())
glyph3D.SetInputData(polyData)
glyph3D.Update()
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(glyph3D.GetOutputPort())
actor = vtk.vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(0, 0, 1)
actors.append(actor)
return actors
if __name__ == '__main__':
app = QApplication(sys.argv)
window = AirwaySkeleton()
window.show()
sys.exit(app.exec_())
What's wrong with my code? Any suggestion is appreciated!
In addition, how can I pick the point in the surface?
Solution with vtkplotter is simply:
from vtkplotter import *
import numpy as np
mask = np.zeros(shape=[200,200,200], dtype=np.uint8)
mask[ 20:80, 50:150, 50:150] = 1
mask[ 80:150, 80:120, 80:120] = 1
mask[150:170, 50:150, 50:150] = 1
vol = Volume(mask) # returns vtkVolume
iso = vol.isosurface(threshold=1).c('grey').alpha(0.3).pickable(0)
smoothed_iso = iso.smoothLaplacian(niter=30)
aline = Line((20,100,100), (170,100,100), lw=10) # vtkActor
def onLeftClick(mesh):
printc("clicked 3D point:", mesh.picked3d, c='red')
vp.add(Sphere(pos=mesh.picked3d, r=2, c="green"))
vp = Plotter(verbose=0, axes=8, bg='black')
vp.mouseLeftClickFunction = onLeftClick
vp.show(smoothed_iso, aline)
can be embedded in Qt following examples here.
Related
I want to generate 2D panarmoic view from the series of 3D cbct dental images by python.
I am converting the series of 3D Cbct images to 2D panaromic view by python. The code is working fine but the output is not complete.
import sys
import vtkmodules.all as vtk
from PyQt5 import QtCore, QtWidgets
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
def create_reader(dir):
reader = vtk.vtkDICOMImageReader()
reader.SetDirectoryName(dir)
reader.Update()
return reader
def create_center(reader):
(xMin, xMax, yMin, yMax, zMin, zMax) = reader.GetExecutive().GetWholeExtent(reader.GetOutputInformation(0))
(xSpacing, ySpacing, zSpacing) = reader.GetOutput().GetSpacing()
(x0, y0, z0) = reader.GetOutput().GetOrigin()
center = [x0 + xSpacing * 0.5 * (xMin + xMax),
y0 + ySpacing * 0.5 * (yMin + yMax),
z0 + zSpacing * 0.5 * (zMin + zMax)]
return center
def create_sagittal_slice_matrix(center):
#Create Sagittal Slice Matrix
sagittal = vtk.vtkMatrix4x4()
sagittal.DeepCopy((0, 0, -1, center[0],
1, 0, 0, center[1],
0, -1, 0, center[2],
0, 0, 0, 1))
return sagittal
def create_resliced_image(reader, sagittal, frame):
# Reslice image
widget = QVTKRenderWindowInteractor(frame)
slice = vtk.vtkImageReslice()
slice.SetInputConnection(reader.GetOutputPort())
slice.SetOutputDimensionality(2)
slice.SetResliceAxes(sagittal)
slice.SetInterpolationModeToLinear()
return widget, slice
def create_display_image_actor(slice):
# Display the image
actor = vtk.vtkImageActor()
actor.GetMapper().SetInputConnection(slice.GetOutputPort())
# renderer = vtk.vtkRenderer()
return actor
def adjust_renderer_settings(renderer, widget, actor):
# Remove Renderer And Reset
renderer.RemoveAllViewProps()
renderer.ResetCamera()
widget.GetRenderWindow().Render()
renderer.AddActor(actor)
widget.GetRenderWindow().AddRenderer(renderer)
return widget
def setup_interaction(widget):
# Set up the interaction
slice_interactorStyle = vtk.vtkInteractorStyleImage()
slice_interactor = widget.GetRenderWindow().GetInteractor()
slice_interactor.SetInteractorStyle(slice_interactorStyle)
widget.GetRenderWindow().SetInteractor(slice_interactor)
widget.GetRenderWindow().Render()
return slice_interactor
# Start interaction
# slice_interactor.Start()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent = None):
QtWidgets.QMainWindow.__init__(self, parent)
self.frame = QtWidgets.QFrame()
self.vl = QtWidgets.QVBoxLayout()
reader = create_reader(r"C:\work.ateeb\2.16.840.114421.10241.9572018034.9603554034\DECOMPRESSED\One")
center = create_center(reader)
sagittal = create_sagittal_slice_matrix(center)
self.vtkWidget, slice = create_resliced_image(reader, sagittal, self.frame)
self.vl.addWidget(self.vtkWidget)
# create renderer
renderer = vtk.vtkRenderer()
# create actor
actor = create_display_image_actor(slice)
self.vtkWidget = adjust_renderer_settings(renderer, self.vtkWidget, actor)
slice_interactor = setup_interaction(self.vtkWidget)
self.show()
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
slice_interactor.Initialize()
slice_interactor.Start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
sys.exit(app.exec_())
I was expecting the output like this
enter image description here
But the output the code is generating is:
enter image description here
I am generating and saving Saggital Reconstuction of the Dicom CBCT images by using Vtk and PyQt application. During the Flow of Program it open the Vtk PyQT win32 Open GL window for some seconds and then save the file in png. But I donot want to open that window during the flow of program. I just want to save the image, donot want to render it.
import sys
import vtkmodules.all as vtk
from PyQt5 import QtCore, QtWidgets
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
import pydicom
import os
from vtkmodules.vtkIOImage import (
vtkBMPWriter,
vtkJPEGWriter,
vtkPNGWriter,
vtkPNMWriter,
vtkPostScriptWriter,
vtkTIFFWriter
)
from datetime import datetime
from vtkmodules.vtkRenderingCore import (
vtkActor,
vtkPolyDataMapper,
vtkRenderWindow,
vtkRenderWindowInteractor,
vtkRenderer,
vtkWindowToImageFilter
)
from flask import Flask, request, send_file
import base64
app = Flask(__name__)
savePath = r'F:\Nixaam\Jan 2023\Saggital View'
savePath = savePath + '\\'
def create_reader(dir):
reader = vtk.vtkDICOMImageReader()
reader.SetDirectoryName(dir)
reader.Update()
return reader
def create_center(reader):
(xMin, xMax, yMin, yMax, zMin, zMax) = reader.GetExecutive().GetWholeExtent(reader.GetOutputInformation(0))
(xSpacing, ySpacing, zSpacing) = reader.GetOutput().GetSpacing()
(x0, y0, z0) = reader.GetOutput().GetOrigin()
center = [x0 + xSpacing * 0.5 * (xMin + xMax),
y0 + ySpacing * 0.5 * (yMin + yMax),
z0 + zSpacing * 0.5 * (zMin + zMax)]
return center
def create_sagittal_slice_matrix(center):
#Create Sagittal Slice Matrix
sagittal = vtk.vtkMatrix4x4()
sagittal.DeepCopy((0, 0, -1, center[0],
1, 0, 0, center[1],
0, -1, 0, center[2],
0, 0, 0, 1))
return sagittal
def create_resliced_image(reader, sagittal, frame):
# Reslice image
widget = QVTKRenderWindowInteractor(frame)
slice = vtk.vtkImageReslice()
slice.SetInputConnection(reader.GetOutputPort())
slice.SetOutputDimensionality(2)
slice.SetResliceAxes(sagittal)
slice.SetInterpolationModeToLinear()
return widget, slice
def create_display_image_actor(slice):
# Display the image
actor = vtk.vtkImageActor()
actor.GetMapper().SetInputConnection(slice.GetOutputPort())
# renderer = vtk.vtkRenderer()
return actor
def adjust_renderer_settings(renderer, widget, actor):
# Remove Renderer And Reset
renderer.RemoveAllViewProps()
renderer.ResetCamera()
widget.GetRenderWindow().Render()
renderer.AddActor(actor)
widget.GetRenderWindow().AddRenderer(renderer)
return widget
def setup_interaction(widget):
# Set up the interaction
slice_interactorStyle = vtk.vtkInteractorStyleImage()
slice_interactor = widget.GetRenderWindow().GetInteractor()
slice_interactor.SetInteractorStyle(slice_interactorStyle)
widget.GetRenderWindow().SetInteractor(slice_interactor)
widget.GetRenderWindow().Render()
return slice_interactor
# Start interaction
# slice_interactor.Start()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, path, parent = None):
QtWidgets.QMainWindow.__init__(self, parent)
self.frame = QtWidgets.QFrame()
self.vl = QtWidgets.QVBoxLayout()
reader = create_reader(path)
center = create_center(reader)
sagittal = create_sagittal_slice_matrix(center)
self.vtkWidget, slice = create_resliced_image(reader, sagittal, self.frame)
self.vl.addWidget(self.vtkWidget)
vtk_out = vtk.vtkOutputWindow()
vtk_out.SetInstance(vtk_out)
# create renderer
renderer = vtk.vtkRenderer()
# create actor
actor = create_display_image_actor(slice)
self.vtkWidget = adjust_renderer_settings(renderer, self.vtkWidget, actor)
slice_interactor = setup_interaction(self.vtkWidget)
renWin = vtkRenderWindow()
renWin.AddRenderer(renderer)
slice_interactor.SetRenderWindow(renWin)
renWin.SetSize(1366,768)
# renWin.SetWindowName('Window')
# renWin.Render()
windowto_image_filter = vtkWindowToImageFilter()
windowto_image_filter.SetInput(renWin)
windowto_image_filter.SetScale(1) # image quality
windowto_image_filter.SetInputBufferTypeToRGB()
# # Read from the front buffer.
# windowto_image_filter.ReadFrontBufferOff()
# windowto_image_filter.Update()
writer = vtkPNGWriter()
path = r'F:\Nixaam\Jan 2023\Saggital View'
path = path + '\\'
writer.SetFileName(f'{path}saggital_view.png')
writer.SetInputConnection(windowto_image_filter.GetOutputPort())
writer.Write()
seriesFilePath = r'F:\Nixaam\Jan 2023\Saggital View\Decompressed\2023-01-13 21-10-32'
app = QtWidgets.QApplication(sys.argv)
window = MainWindow(seriesFilePath)
I donot want this window during the program.
I would like to add the ability to update a data field (number) on my current pyqt script. I was trying to use "addLabel" but I can't update their content dynamically.
Any suggestion on how to make it? I am using addPlot function to create the plots which will be processing some data from a UDP packet. In some cases, I want to just show the value dynamically. Not sure what will be the best widget to use for this purpose?
Current script:
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
import struct
from struct import pack, unpack
import socket
app = QtGui.QApplication([])
data = []
data_throttle = []
data_brake = []
data_steering = []
data_state = []
data_error = []
pg.setConfigOption('background', (0,0,0))
#pg.setConfigOption(antialias=True)
win = pg.GraphicsWindow()
win.resize(500,200)
p1 = win.addPlot(title="Sequence")
p1.setWindowTitle('Sequence')
p1.setRange(QtCore.QRectF(0, 0, 100, 150))
#p1.setLabel('bottom', 'Packet', units=' ')
p1.showGrid(True, True)
p2 = win.addPlot(title="Throttle")
p2.setWindowTitle('Throttle')
p2.setRange(QtCore.QRectF(0, 0, 100, 255))
#p2.setLabel('bottom', 'Packet', units=' ')
p2.showGrid(True, True)
p3 = win.addPlot(title="Brake")
p3.setWindowTitle('Brake')
p3.setRange(QtCore.QRectF(0, 0, 100, 255))
#p3.setLabel('bottom', 'Packet', units=' ')
p3.showGrid(True, True)
p4 = win.addPlot(title="Steering")
p4.setWindowTitle('Steering')
p4.setRange(QtCore.QRectF(0, -1, 100, 2))
#p4.setLabel('bottom', 'Packet', units=' ')git
p4.showGrid(True, True)
p5 = win.addPlot(title="Vehicle State")
p5.setWindowTitle('Vehicle State')
p5.setRange(QtCore.QRectF(0, 0, 100, 10))
#p5.setLabel('bottom', 'Packet', units=' ')
p5.showGrid(True, True)
p6 = win.addPlot(title="Errors")
p6.setWindowTitle('Errors')
p6.setRange(QtCore.QRectF(0, 0, 100, 10))
#p6.setLabel('bottom', 'Packet', units=' ')
p6.showGrid(True, True)
top_label = "Percent"
bottom_label = "85"
l_labels = win.addLayout(col=1, colspan=1)
l_labels.addLabel(top_label, row=0, col=0, rowspan=1, colspan=1, size='30pt', bold=True)
l_labels.addLabel(bottom_label, row=2, col=0, rowspan=4, colspan=1, size='200pt', color='606060')
l_labels.setContentsMargins(0, 0, 0, 100)
curve = p1.plot(pen='r')
curve2 = p2.plot(pen='b')
curve3 = p3.plot(pen='g')
curve4 = p4.plot(pen='y')
curve5 = p5.plot(pen='r')
curve6 = p6.plot(pen='b')
# Receive UDP packets transmitted by a broadcasting service
RECEIVE_IP = ''
RECEIVE_PORT = 2337
RECEIVE_ADDRESS = (RECEIVE_IP, RECEIVE_PORT)
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_socket.bind(RECEIVE_ADDRESS)
JOY_CODE = '=HBBBhHQQB'
ACK_CODE = '=HHBBHBBhBqB'
ptr = 0
lastTime = time()
fps = None
def update():
global recv_socket, fScale
global curve, data, data_throttle, data_brake, data_steering, ptr, p, lastTime, fps, data_state, data_error
recv_message, address = recv_socket.recvfrom(22)
message_id, ack_payload, ack_seq, vehicle_state, vehicle_speed, throttle, brake, steering, error, timestamp, checksum = struct.unpack(ACK_CODE, recv_message)
#data = throttle
data.append(ack_seq)
data = data[-100:]
data_throttle.append(throttle)
data_throttle = data_throttle[-100:]
data_brake.append(brake)
data_brake = data_brake[-100:]
data_steering.append(steering / 32768)
data_steering = data_steering[-100:]
vehicle_state = vehicle_state & 0x07
data_state.append(vehicle_state)
data_state = data_state[-100:]
data_error.append(error)
data_error = data_error[-100:]
#print(throttle)
#print("got data", data, " ", lastTime, "\n")
# print data
#curve.setData(fScale,data,_callSync='off')
curve.setData(data,_callSync='off')
curve2.setData(data_throttle,_callSync='off')
curve3.setData(data_brake,_callSync='off')
curve4.setData(data_steering,_callSync='off')
curve5.setData(data_state, _callSync='off')
curve6.setData(data_error, _callSync='off')
bottom_label = vehicle_state
now = time()
dt = now - lastTime
lastTime = now
app.processEvents() ## force complete redraw for every plot
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I was able to figure the solution by using TextItem inside a Plot
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
import struct
from struct import pack, unpack
import socket
app = QtGui.QApplication([])
data = []
data_throttle = []
data_brake = []
data_steering = []
data_state = []
data_error = []
pg.setConfigOption('background', (0,0,0))
#pg.setConfigOption(antialias=True)
win = pg.GraphicsWindow()
win.resize(1600,1000)
p1 = win.addPlot(title="Sequence",row=0, col=1)
p1.setWindowTitle('Sequence')
p1.setRange(QtCore.QRectF(0, 0, 100, 150))
p1.showGrid(True, True)
p2 = win.addPlot(title="Throttle",row=0, col=2)
p2.setWindowTitle('Throttle')
p2.setRange(QtCore.QRectF(0, 0, 100, 255))
p2.showGrid(True, True)
p3 = win.addPlot(title="Brake",row=0, col=3)
p3.setWindowTitle('Brake')
p3.setRange(QtCore.QRectF(0, 0, 100, 255))
p3.showGrid(True, True)
p4 = win.addPlot(title="Steering",row=1, col=1)
p4.setWindowTitle('Steering')
p4.setRange(QtCore.QRectF(0, -1, 100, 2))
p4.showGrid(True, True)
p5 = win.addPlot(title="Vehicle State",row=1, col=2)
p5.setWindowTitle('Vehicle State')
p5.setRange(QtCore.QRectF(0, 0, 100, 10))
p5.showGrid(True, True)
p6 = win.addPlot(title="Errors",row=1, col=3)
p6.setWindowTitle('Errors')
p6.setRange(QtCore.QRectF(0, 0, 100, 10))
p6.showGrid(True, True)
p7 = win.addPlot(title="Counter",row=0, col=4)
p8 = win.addPlot(title="Counter",row=1, col=4)
font=QtGui.QFont()
font.setPixelSize(300)
anchor = pg.TextItem()
anchor.setText("0")
anchor.setColor(QtGui.QColor(255, 255, 255))
anchor.setFont(font)
anchor1 = pg.TextItem()
anchor1.setText("0")
anchor1.setColor(QtGui.QColor(255, 255, 255))
anchor1.setFont(font)
p7.addItem(anchor)
p7.showGrid(False, False)
p7.hideAxis('bottom')
p7.hideAxis('left')
p8.addItem(anchor1)
p8.showGrid(False, False)
p8.hideAxis('bottom')
p8.hideAxis('left')
curve = p1.plot(pen='r')
curve2 = p2.plot(pen='b')
curve3 = p3.plot(pen='g')
curve4 = p4.plot(pen='y')
curve5 = p5.plot(pen='r')
curve6 = p6.plot(pen='b')
# Receive UDP packets transmitted by a broadcasting service
RECEIVE_IP = ''
RECEIVE_PORT = 2337
RECEIVE_ADDRESS = (RECEIVE_IP, RECEIVE_PORT)
recv_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_socket.bind(RECEIVE_ADDRESS)
JOY_CODE = '=HBBBhHQQB'
ACK_CODE = '=HHBBHBBhBqB'
ptr = 0
lastTime = time()
fps = None
def update():
global recv_socket, fScale
global curve, data, data_throttle, data_brake, data_steering, ptr, p, lastTime, fps, data_state, data_error
recv_message, address = recv_socket.recvfrom(22)
message_id, ack_payload, ack_seq, vehicle_state, vehicle_speed, throttle, brake, steering, error, timestamp, checksum = struct.unpack(ACK_CODE, recv_message)
#data = throttle
data.append(ack_seq)
data = data[-100:]
data_throttle.append(throttle)
data_throttle = data_throttle[-100:]
data_brake.append(brake)
data_brake = data_brake[-100:]
data_steering.append(steering / 32768)
data_steering = data_steering[-100:]
vehicle_state = vehicle_state & 0x07
data_state.append(vehicle_state)
data_state = data_state[-100:]
data_error.append(error)
data_error = data_error[-100:]
curve.setData(data,_callSync='off')
curve2.setData(data_throttle,_callSync='off')
curve3.setData(data_brake,_callSync='off')
curve4.setData(data_steering,_callSync='off')
curve5.setData(data_state, _callSync='off')
curve6.setData(data_error, _callSync='off')
#Updating Text Value
anchor.setText(str(vehicle_state))
anchor1.setText(str(vehicle_state))
now = time()
dt = now - lastTime
lastTime = now
app.processEvents() ## force complete redraw for every plot
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I've a PyQt code which keeps on crashes giving me error message QPixmap::fromImage: QPixmap cannot be created without a QGuiApplication QPixmap: Must construct a QGuiApplication before a QPixmap
It's a fairly simple application in which I read frames from one class named CameraWidget and apply a function def transform_perspective(self, frame, points) to get the transform perspective of that frame. I've divided my screen in two equal haves, the right half section displays the frame as seen by the camera and the left half displays the perspective transformation of it (for which I get the coordinates from another class named Canvas).
There is one more issue: the left half doesn't occupy it's entire region. A lot of portion just appears black. Here is a pic for your reference
It's a fairly long program so below I'm including the class where I believe the problem lies.
class TopView(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TopView, self).__init__(parent)
self.original_frame = CameraWidget('Abc.ts')
# Layouts and frames
self.frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.frame)
layout.setContentsMargins(0,0,0,0)
layout.setSpacing(0)
self.setLayout(layout)
# frame left
self.frame_left = QtWidgets.QFrame()
self.get_frame_thread = Thread(target=self.transform_frame, args=())
self.get_frame_thread.daemon = True
self.get_frame_thread.start()
self.top_view_label = QtWidgets.QLabel()
self.top_view_label.setScaledContents(True)
self.layout_left = QtWidgets.QVBoxLayout()
self.layout_left.addWidget(self.top_view_label)
self.layout_left.setContentsMargins(0,0,0,0)
self.layout_left.setSpacing(0)
self.frame_left.setLayout(self.layout_left)
# frame right
self.frame_right = QtWidgets.QFrame()
self.frame_right.setStyleSheet("background-color: rgb(153, 187, 255)")
self.video_frame_1 = self.original_frame
# Create camera widgets
print('Creating Camera Widgets...')
self.layout_right = QtWidgets.QVBoxLayout()
self.layout_right.addWidget(self.video_frame_1)
self.layout_right.setContentsMargins(5,5,5,5)
self.frame_right.setLayout(self.layout_right)
self.layout_inner = QtWidgets.QHBoxLayout()
self.layout_inner.addWidget(self.frame_left, 50)
self.layout_inner.addWidget(self.frame_right, 50)
self.layout_inner.setContentsMargins(0,0,0,0)
self.layout_inner.setSpacing(0)
self.frame.setLayout(self.layout_inner)
self.setLayout(layout)
sizeObject = QtWidgets.QDesktopWidget().screenGeometry(0)
self.screen_width = int(0.7*sizeObject.width())
self.screen_height = int(0.7*sizeObject.height())
def event(self, e):
if e.type() in (QtCore.QEvent.Show, QtCore.QEvent.Resize):
print('')
return QtWidgets.QWidget.event(self, e)
def transform_frame(self):
while True:
try:
self.top_view_frame = self.transform_perspective(self.original_frame.get_video_frame(), self.original_frame.canvas.mapped_list)
h, w, ch = self.top_view_frame.shape
bytesPerLine = ch * w
self.img = QtGui.QImage(self.top_view_frame, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
self.pix = QtGui.QPixmap.fromImage(self.img)
if not sip.isdeleted(self.top_view_label):
self.top_view_label.setPixmap(self.pix)
except Exception as e:
print(e)
def transform_perspective(self, frame, points):
points = np.float32(points)
p2 = np.float32([[0, 0], [600, 0], [0, 600], [600, 600]])
self.matrix = cv2.getPerspectiveTransform(points, p2)
frame = cv2.warpPerspective(frame, self.matrix, (400, 600))
return frame
Although the OP does not provide an MRE it is easy to notice that the error is that it is creating a QPixmap in a secondary thread which is prohibited. Instead you should send the QImage to the GUI thread, and in the GUI thread just convert it to QPixmap:
class ImageProcessor(QtCore.QObject):
imageChanged = QtCore.pyqtSignal(QtGui.QImage)
def process(self, video_frame, mapped_list):
thread = Thread(
target=self._execute,
args=(
video_frame,
mapped_list,
),
)
thread.daemon = True
thread.start()
def _execute(self, video_frame, mapped_list):
top_view_frame = self.transform_perspective(video_frame, mapped_list)
qimage = self.convert_np_to_qimage(top_view_frame)
self.imageChanged.emit(qimage.copy())
def transform_perspective(self, frame, points):
points = np.float32(points)
p2 = np.float32([[0, 0], [600, 0], [0, 600], [600, 600]])
matrix = cv2.getPerspectiveTransform(points, p2)
frame = cv2.warpPerspective(frame, matrix, (400, 600))
return frame
def convert_np_to_qimage(self, array):
h, w, ch = array.shape
bytesPerLine = ch * w
img = QtGui.QImage(array.data, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
return img.copy()
class TopView(QtWidgets.QWidget):
def __init__(self, parent=None):
super(TopView, self).__init__(parent)
self.original_frame = CameraWidget("Abc.ts")
# Layouts and frames
self.frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.frame)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# frame left
self.frame_left = QtWidgets.QFrame()
self.top_view_label = QtWidgets.QLabel()
self.top_view_label.setScaledContents(True)
self.layout_left = QtWidgets.QVBoxLayout(self.frame_left)
self.layout_left.addWidget(self.top_view_label)
self.layout_left.setContentsMargins(0, 0, 0, 0)
self.layout_left.setSpacing(0)
# frame right
self.frame_right = QtWidgets.QFrame()
self.frame_right.setStyleSheet("background-color: rgb(153, 187, 255)")
self.video_frame_1 = self.original_frame
# Create camera widgets
print("Creating Camera Widgets...")
self.layout_right = QtWidgets.QVBoxLayout(self.frame_right)
self.layout_right.addWidget(self.video_frame_1)
self.layout_right.setContentsMargins(5, 5, 5, 5)
self.layout_inner = QtWidgets.QHBoxLayout(self.frame)
self.layout_inner.addWidget(self.frame_left, 50)
self.layout_inner.addWidget(self.frame_right, 50)
self.layout_inner.setContentsMargins(0, 0, 0, 0)
self.layout_inner.setSpacing(0)
sizeObject = QtWidgets.QDesktopWidget().screenGeometry(0)
self.screen_width = int(0.7 * sizeObject.width())
self.screen_height = int(0.7 * sizeObject.height())
self.processor = ImageProcessor()
self.processor.imageChanged.connect(self.handle_imageChanged)
self.launch_processor()
def launch_processor(self):
self.processor.process(
self.original_frame.get_video_frame(),
self.original_frame.canvas.mapped_list,
)
#QtCore.pyqtSlot(QtGui.QImage)
def handle_imageChanged(self, qimage):
qpixmap = QtGui.QPixmap.fromImage(qimage)
self.top_view_label.setPixmap(qpixmap)
QtCore.QTimer.singleShot(0, self.launch_processor)
I'm trying use VTK to plot points, then interactively update their locations with a given set of point locations.
I can interactively use a polydata object to plot points, however they do not update when I call self.polydata.Update(). The points will update when I call self.polydata.GetCellData().SetScalars(someCharArray)
Is this a bug in VTK, or am I not updating the point coordinates correctly?
I have included an example script. If you comment out self.polydata.GetCellData().SetScalars(someCharArray) in sliderCallback, the plot will not update the point's coordinates when you use the slider. However they will update if you leave that line in.
Thanks!
import numpy as np
import vtk
from vtk.qt4.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt4 import QtGui
import sys
class ViewerWithScrollBar(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ViewerWithScrollBar, self).__init__(parent)
# Define the renderer and Qt window ------------------------
self.frame = QtGui.QFrame()
self.hl = QtGui.QHBoxLayout()
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
self.hl.addWidget(self.vtkWidget)
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
self.ren.ResetCamera()
self.frame.setLayout(self.hl)
self.setCentralWidget(self.frame)
# Point coordinate data ---------------------------------
self.coordData = {}
self.coordData[0] = np.array([[0,0,0], [1,0,0], [1,1,0]])
self.coordData[1] = self.coordData[0] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
self.coordData[2] = self.coordData[1] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
# Define the slider bar and add it to the window ---------------
slider = QtGui.QSlider()
slider.setAccessibleName('Time index')
slider.setRange(0, len(self.coordData)-1)
slider.valueChanged.connect(self.sliderCallback)
self.hl.addWidget(slider)
# Create the polydata object -----------------------------
points = vtk.vtkPoints()
points.SetNumberOfPoints(len(self.coordData[0]))
self.polydata = vtk.vtkPolyData()
for i in range(len(self.coordData[0])):
points.SetPoint(i, self.coordData[0][i])
self.polydata.SetPoints(points)
ptsFilter = vtk.vtkVertexGlyphFilter()
ptsFilter.SetInputConnection(self.polydata.GetProducerPort())
ptsMapper = vtk.vtkPolyDataMapper()
ptsMapper.SetInputConnection(ptsFilter.GetOutputPort())
ptsActor = vtk.vtkActor()
ptsActor.SetMapper(ptsMapper)
ptsActor.GetProperty().SetPointSize(10)
self.ren.AddActor(ptsActor)
self.show()
self.iren.Initialize()
def sliderCallback(self):
index = self.sender().value() # The index that the slider bar is currently on
someCharArray = vtk.vtkUnsignedCharArray()
points = self.polydata.GetPoints()
for i in range(len(self.coordData[index])):
points.SetPoint(i, self.coordData[index][i])
self.polydata.GetCellData().SetScalars(someCharArray) # For some reason the polydata won't update unless this is called.
# self.polydata.Update()
self.iren.Render()
return
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = ViewerWithScrollBar()
sys.exit(app.exec_())
With lib's advice, I modified the code. Calling self.polydata.Modified() in the sliderCallback method fixed the problem
import numpy as np
import vtk
from vtk.qt4.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt4 import QtGui
import sys
class ViewerWithScrollBar(QtGui.QMainWindow):
def __init__(self, parent=None):
super(ViewerWithScrollBar, self).__init__(parent)
# Define the renderer and Qt window ------------------------
self.frame = QtGui.QFrame()
self.hl = QtGui.QHBoxLayout()
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
self.hl.addWidget(self.vtkWidget)
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
self.ren.ResetCamera()
self.frame.setLayout(self.hl)
self.setCentralWidget(self.frame)
# Point coordinate data ---------------------------------
self.coordData = {}
self.coordData[0] = np.array([[0,0,0], [1,0,0], [1,1,0]])
self.coordData[1] = self.coordData[0] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
self.coordData[2] = self.coordData[1] + np.array([[0.2, 0.1, -0.05], [0,0,0], [0,0,0]])
# Define the slider bar and add it to the window ---------------
slider = QtGui.QSlider()
slider.setAccessibleName('Time index')
slider.setRange(0, len(self.coordData)-1)
slider.valueChanged.connect(self.sliderCallback)
self.hl.addWidget(slider)
# Create the polydata object -----------------------------
points = vtk.vtkPoints()
points.SetNumberOfPoints(len(self.coordData[0]))
self.polydata = vtk.vtkPolyData()
for i in range(len(self.coordData[0])):
points.SetPoint(i, self.coordData[0][i])
self.polydata.SetPoints(points)
ptsFilter = vtk.vtkVertexGlyphFilter()
ptsFilter.SetInputData(self.polydata)
ptsMapper = vtk.vtkPolyDataMapper()
ptsMapper.SetInputConnection(ptsFilter.GetOutputPort())
ptsActor = vtk.vtkActor()
ptsActor.SetMapper(ptsMapper)
ptsActor.GetProperty().SetPointSize(10)
self.ren.AddActor(ptsActor)
self.show()
self.iren.Initialize()
def sliderCallback(self):
index = self.sender().value() # The index that the slider bar is currently on
points = self.polydata.GetPoints()
for i in range(len(self.coordData[index])):
points.SetPoint(i, self.coordData[index][i])
self.polydata.Modified()
self.iren.Render()
return
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = ViewerWithScrollBar()
sys.exit(app.exec_())
I revised it to the version of PyQt5 and ran and tested the code above using latest vtk 8.1.0 and PyQt5 5.10.1 under Windows 10, PyCharm 2018.1 (Community Edition). Look like "points" needs to be modified instead of "self.polydata". Otherwise, it won't get the updated point shown.
import numpy as np
import vtk
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from PyQt5 import Qt
import sys
class ViewerWithScrollBar(Qt.QMainWindow):
def __init__(self, parent=None):
super(ViewerWithScrollBar, self).__init__(parent)
# Define the renderer and Qt window ------------------------
self.frame = Qt.QFrame()
self.hl = Qt.QHBoxLayout()
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
self.hl.addWidget(self.vtkWidget)
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
self.ren.ResetCamera()
self.frame.setLayout(self.hl)
self.setCentralWidget(self.frame)
# Point coordinate data ---------------------------------
self.coordData = {}
self.coordData[0] = np.array([[0,0,0], [1,0,0], [1,1,0]])
self.coordData[1] = self.coordData[0] + np.array([[0.2, 0.0, -0.05], [0,2,0], [0,0,3.5]])
self.coordData[2] = self.coordData[1] + np.array([[0.2, 10.0, -0.05], [0,5.0,0], [0,0,0]])
# Define the slider bar and add it to the window ---------------
slider = Qt.QSlider()
slider.setAccessibleName('Time index')
slider.setRange(0, len(self.coordData)-1)
slider.valueChanged.connect(self.sliderCallback)
self.hl.addWidget(slider)
# Create the polydata object -----------------------------
points = vtk.vtkPoints()
points.SetNumberOfPoints(len(self.coordData[0]))
self.polydata = vtk.vtkPolyData()
for i in range(len(self.coordData[0])):
points.SetPoint(i, self.coordData[0][i])
self.polydata.SetPoints(points)
self.ptsFilter = vtk.vtkVertexGlyphFilter()
self.ptsFilter.SetInputData(self.polydata)
ptsMapper = vtk.vtkPolyDataMapper()
ptsMapper.SetInputConnection(self.ptsFilter.GetOutputPort())
self.ptsActor = vtk.vtkActor()
self.ptsActor.SetMapper(ptsMapper)
self.ptsActor.GetProperty().SetPointSize(10)
self.ren.AddActor(self.ptsActor)
self.show()
self.iren.Initialize()
self.iren.Start()
def sliderCallback(self):
index = self.sender().value() # The index that the slider bar is currently on
points = self.polydata.GetPoints()
for i in range(len(self.coordData[index])):
points.SetPoint(i, self.coordData[index][i])
points.Modified() # Here you need to call Modified for points
self.show()
self.iren.Render()
return
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = ViewerWithScrollBar()
sys.exit(app.exec_())