Draw Hexahedral Mesh Efficiently Using OpenGL - python

I try to implement some kind of mesh visualiser for Finite-Element-Programms in Python. For this, I want to use PyQtGraph. I was able to implement a first version of the Visualiser which is able to plot a 3D mesh as shown in the picture below.
However, operations such as zooming and rotating take quite long for larger meshes. I plot the mesh using GLLinePlotItem. I guess the performance is poor due to the huge amount of lines generated with large meshes.
I am wondering whether there is an efficient way to display my mesh rather than using the GLLineItem. I had a look at GLMeshItem, however, this represents the mesh using triangles and not quads.
Here is my code for the visualiser:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import pyqtgraph as pg
import numpy as np
import sys
class Visualizer(object):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
self.w = gl.GLViewWidget()
self.w.opts['distance'] = 400
self.w.setWindowTitle('Mesh Visualiser')
self.w.setGeometry(0, 110, 1920, 1080)
self.w.show()
def start(self):
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
def add_line_item(self,pts,width):
item = gl.GLLinePlotItem(pos=pts, width=width, antialias=False)
self.w.addItem(item)
if __name__ == '__main__':
v = Visualizer()
# Code which generates the GlLineItems is called here ...
# ...
v.start()
I also have a code which is called at the position of the comment in the code and generates the GlLinePlotItems from the nodes of the mesh. The GlLinePlotItems are added to the GlViewWidget using the `add_line_item() method.

Related

How do the scaling paramters of ViewBox.setLimits() work?

I'm trying to limit how much a ViewBox can zoom in/out and how much it can be moved.
I know that I must use setLimits() and I've read the documentation here
https://pyqtgraph.readthedocs.io/en/latest/graphicsItems/viewbox.html#pyqtgraph.ViewBox.setLimits
While the panning limits are pretty self evident, I can't really understand how the scaling limits work.
What's the unit of measure? Is it pixels? Percentage?
I've reached a usable point with these values, but not understanding why is bugging me!
view.setLimits(xMin=-image.shape[0]*0.05, xMax=image.shape[0]*1.05,
minXRange=100, maxXRange=2000,
yMin=-image.shape[1]*0.05, yMax=image.shape[1]*1.05,
minYRange=100, maxYRange=2000)
I think it's a more theoretical question than anything else, but in case you want to try some code, here it is
# import the necessary packages
from pyqtgraph.graphicsItems.ImageItem import ImageItem
from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem
import requests
import numpy as np
import cv2
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
image = cv2.imread('aggraffatura.jpg') # Change the picture here!
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
app = QtGui.QApplication([])
## Create window with GraphicsView widget
w = pg.GraphicsView()
w.show()
w.resize(image.shape[0]/2, image.shape[1]/2) # Depending on the picture you may not need to resize
w.setWindowTitle('Test')
view = pg.ViewBox()
view.setLimits(xMin=-image.shape[0]*0.05, xMax=image.shape[0]*1.05,
minXRange=100, maxXRange=2000,
yMin=-image.shape[1]*0.05, yMax=image.shape[1]*1.05,
minYRange=100, maxYRange=2000)
w.setCentralItem(view)
## lock the aspect ratio
view.setAspectLocked(True)
## Add image item
item = ImageItem(image)
view.addItem(item)
## Add line item
line = LinearRegionItem()
view.addItem(line)
def mouseClicked(evt):
pos = evt[0]
print(pos)
proxyClicked = pg.SignalProxy(w.scene().sigMouseClicked, rateLimit=60, slot=mouseClicked)
## 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_()
What's the unit of measure? Is it pixels? Percentage?
Short answer: The unit of measure of scaling limits is the same as the panning limits.
Long answer:
In class ViewBox, method setLimits calls method updateViewRange, which update view range to match the target view range as closely as possible, given aspect ratio constraints. Inside updateViewRange method, there is a section which loop through both axis and set the max view range to the smaller of max view range (the max scaling limit) and the absolute difference of lower and upper bounds (i.e. max-min, the difference of panning limits) (If scaling limit is not given, than it will be set to the difference of panning limits). Since the two limits can be interchangeable, they should have the same unit of measure.
Only by checking the source code one can see that max range cannot be larger than bounds, if they are given. This piece of information should be added to the document.
Note: when you zoom in to the limit you are actually setting the view range to the minRange of scaling limit.
Example: Here I will use op's example to illustrate the concept. Download this image and rename it to '500x500' to test the example.. On start you should see that the view range is set to maxRange(400px) which is the diameter of the green circle. By zooming in, you should see that the view range can never be smaller than the red circle, which is 100px in diameter. The panning limit is set to the shape of the image, i.e. 500 X 500px.
# import the necessary packages
from pyqtgraph.graphicsItems.ImageItem import ImageItem
from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem
import requests
import numpy as np
import cv2
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
# Name the image to 500x500
image = cv2.imread('500x500.jpg') # Change the picture here!
image = cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
app = QtGui.QApplication([])
## Create window with GraphicsView widget
w = pg.GraphicsView()
w.show()
w.resize(image.shape[0], image.shape[1]) # Depending on the picture you may not need to resize
w.setWindowTitle('Test')
view = pg.ViewBox()
view.setLimits(xMin=0, xMax=image.shape[0],
minXRange=100, maxXRange=400,
yMin=0, yMax=image.shape[1],
minYRange=100, maxYRange=400)
w.setCentralItem(view)
## lock the aspect ratio
view.setAspectLocked(True)
## Add image item
item = ImageItem(image)
view.addItem(item)
## Add line item
line = LinearRegionItem()
view.addItem(line)
def mouseClicked(evt):
pos = evt[0]
print(pos)
proxyClicked = pg.SignalProxy(w.scene().sigMouseClicked, rateLimit=60, slot=mouseClicked)
## 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_()

pyqtgraph's exporter shifts plot components

I have a sample code that produces a standard scatterplot with pairs of X & Y. For the project I'm working on, we cannot use matplotlib, but stick to pyqtgraph instead (it is part of a PyQt project).
from PyQt4 import QtGui
import pyqtgraph as pg
import pyqtgraph.exporters
import numpy as np
x = np.random.normal(size=100)
y = np.random.normal(size=100)
app = QtGui.QApplication([]) # create the application
w = QtGui.QWidget()
pwidget = pg.PlotWidget()
pwidget.addLegend(size=(100, 10)) # add a legend
pwidget.plot(x, y, pen=None, symbol="o", name="My Data") # plot the data
line = pg.InfiniteLine(angle=45, movable=False) # add a line through origin
pwidget.addItem(line)
pwidget.setXRange(-3, 3)
pwidget.setYRange(-3, 3)
layout = QtGui.QGridLayout()
w.setLayout(layout)
layout.addWidget(pwidget)
w.show()
app.exec_()
Now this code works just fine and does, what it should do. This is a screenshot of the popup window:
When trying to export like this, however:
exporter = pg.exporters.ImageExporter(pwidget.plotItem)
exporter.export('D:/file_example.png')
I get the following error:
File
"C:\OSGeo4W64\apps\Python27\Lib\site-packages\pyqtgraph\exporters\ImageExporter.py",
line 70, in export
bg = np.empty((self.params['width'], self.params['height'], 4), dtype=np.ubyte) TypeError: 'float' object cannot be interpreted as an
index
Dr. Google showed me that this may be a bug of the version I (have to) use and that there is the workaround of setting width and height manually. So I updated the code like this:
exporter = pg.exporters.ImageExporter(pwidget.plotItem)
exporter.params.param('width').setValue(1024, blockSignal=exporter.widthChanged)
exporter.params.param('height').setValue(860, blockSignal=exporter.heightChanged)
exporter.export('D:/file_example.png')
with the following rather disturbing result:
I played around with the exporter.params, but nothing changed the result.
Any ideas are highly appreciated, thanks!

corner coordinates of rectangular pyqtgraph roi

Suppose you have a rectangular pyqtgraph roi instance with some data:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
data = np.random.random(size=(50,50,50))
app = QtGui.QApplication([])
w = pg.ImageView()
roi = pg.RectROI([20, 20], [20, 20], pen=(0,9))
roi.addRotateHandle([1, 0], [0.5, 0.5])
w.setImage(data)
w.addItem(roi)
w.show()
How can I extract the 4 corner coordinates of the roi after scaling/rotating it? It think it is possible to calculate them trigonometrically after calling
pos_x, pos_y = roi.pos()
angle = roi.angle()
size_x, size_y = roi.size()
However, it is not that straight forward since the angle can take values >360° etc. I feel like I have missed some build-in solution.
smiet
i am looking for something similar, but after looking into the documentation, source code and web, i think you are indeed left with your trigonometrical solution. nevertheless you could save two lines of code by calling
roi.getState()
which holds your wanted information in a dictionary.
regarding your problem with angles over 360° - shouldn't the modulo operator do the trick?
angle = 365 % 360
..or did i get your problem wrong?

How do I update graph fast with PyQtGraph and ScatterPlotItem (>1 fps)?

Hi all PyQtGraph users,
I am trying to use Matplotlibs jet colors in the pg.ScatterPlotItem for real-time graph update using data from a datalogger. I convert jet colors to RGBA code in the following few lines:
z=np.random.randint(0,10000,10000) #random z value
norm = mpl.colors.Normalize(vmin=min(z), vmax=max(z))
m = cm.ScalarMappable(norm=norm, cmap=cm.jet)
colors = m.to_rgba(z, bytes=True)
I have noticed that the speed in the scatterplot update can be an issue if the number of points is 10k or higher. For instance, on my laptop I get an update speed of 0.16 fps for 10k points if I run example code from the ScatterplotTestSpeed.py. However, if I append lists as tuples I can triple the update speed to 0.45 fps:
colors_=[]
for i in colors:
colors_.append(pg.mkBrush(tuple(i)))
This is a small trick I discovered by simple try and see method, but I was wondering if there are more of such tricks to speed up the fps number?!
Here is my entire code to test the update speed. It is heavily based on the ScatterPlotSpeedTest.py form the pyqtgraphs example library. Any help is appreciated :-)
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
For testing rapid updates of ScatterPlotItem under various conditions.
(Scatter plots are still rather slow to draw; expect about 20fps)
"""
## Add path to library (just for examples; you do not need this)
import initExample
import matplotlib as mpl
import matplotlib.cm as cm
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
import numpy as np
import pyqtgraph as pg
from pyqtgraph.ptime import time
#QtGui.QApplication.setGraphicsSystem('raster')
app = QtGui.QApplication([])
#mw = QtGui.QMainWindow()
#mw.resize(800,800)
if USE_PYSIDE:
from ScatterPlotSpeedTestTemplate_pyside import Ui_Form
else:
from ScatterPlotSpeedTestTemplate_pyqt import Ui_Form
win = QtGui.QWidget()
win.setWindowTitle('pyqtgraph example: ScatterPlotSpeedTest')
ui = Ui_Form()
ui.setupUi(win)
win.show()
p = ui.plot
p.setRange(xRange=[-500, 500], yRange=[-500, 500])
data = np.random.normal(size=(50,10000), scale=100)
sizeArray = (np.random.random(500) * 20.).astype(int)
ptr = 0
lastTime = time()
fps = None
def update():
global curve, data, ptr, p, lastTime, fps
p.clear()
if ui.randCheck.isChecked():
size = sizeArray
else:
size = ui.sizeSpin.value()
z=np.random.randint(0,10000,10000)
norm = mpl.colors.Normalize(vmin=min(z), vmax=max(z))
m = cm.ScalarMappable(norm=norm, cmap=cm.jet)
colors = m.to_rgba(z, bytes=True)
colors_=[]
for i in colors:
colors_.append(pg.mkBrush(tuple(i)))
#colors.append(pg.intColor(np.random.randint(0,255), 100))
curve = pg.ScatterPlotItem(x=data[ptr%50], y=data[(ptr+1)%50],
pen='w', brush=colors_, size=size,
pxMode=ui.pixelModeCheck.isChecked())
'''
curve = pg.ScatterPlotItem(pen='w', size=size, pxMode=ui.pixelModeCheck.isChecked())
spots3=[]
for i,j,k in zip(data[ptr%50],data[(ptr+1)%50],colors):
spots3.append({'pos': (i, j), 'brush':pg.mkBrush(tuple(k))})
curve.addPoints(spots3)
'''
p.addItem(curve)
ptr += 1
now = time()
dt = now - lastTime
lastTime = now
if fps is None:
fps = 1.0/dt
else:
s = np.clip(dt*3., 0, 1)
fps = fps * (1-s) + (1.0/dt) * s
p.setTitle('%0.2f fps' % fps)
p.repaint()
#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 think that I have found a solution to my problem. It seems that ScatterPlotItem is simply too slow to continuously redraw large number of points (>500) with different colors specified by 'brush'. Instead, I am just using plotItem and 'symbolBrush' to color incoming data points with Jet colors (that will give me 2.5D plot I was initially searching for). The speed of the plot update is almost instant no matter how many data points are added, 100 points are recolored just as fast as 10000 points (from the user point of view).

pyqtgraph LinearRegionItem get curve data between selected region

I am new to pyqtGraph and using LinearRegionItem for selection. Is there a way i can get data for curves only for selection ?
For me getting data which lies between selection is important to process.
Any help of pointer in right direction will be helpful
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
#
app = QtGui.QApplication([])
#
win = pg.GraphicsWindow()
win.resize(1000, 600)
#
p1 = win.addPlot(title="Multiple curves")
p1.plot(np.random.normal(size=100), pen=(255, 0, 0), name="Red curve")
p1.plot(np.random.normal(size=110) + 5, pen=(0, 255, 0), name="Blue curve")
# LinearRegionItem
#
def updateRegion(window, viewRange):
region = lr.getRegion()
print region
#
lr = pg.LinearRegionItem([10, 40])
lr.setZValue(-10)
p1.addItem(lr)
p1.sigXRangeChanged.connect(updateRegion)
#
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Pyqtgraphs linearregionitem has a signal called sigRegionChanged.
With this signal the regionItem emits itself when the user drags it or when it is changed programatically. Using getRegion() you can then get the low and high of the linearregionitem.
def regionUpdated(regionItem):
lo,hi = regionItem.getRegion()
print lo,hi
lr.sigRegionChanged.connect(regionUpdated)
This will output the position low and high when dragged, e.g.
9.50787175868 13.9172032101
If you have your red curve as a numpy array then you can slice it using the lo and hi.
red = np.random.normal(size=100)
red[9.50787175868:13.9172032101]
Which gives
[ 0.13231953 -0.5609786 -0.13632821 0.79973 ]
Slicing an index array with floats can feel a bit weird, but numpy runs int() on the indices first, basically making the call red[9:13]. See the question "Why ndarray allow floating point index?" for more about this.
The slicing can be done in regionUpdated and then you can do anything you want with the slice, e.g. print it!
def regionUpdated(regionItem):
lo,hi = regionItem.getRegion()
print red[lo:hi]

Categories

Resources