I am doing a animated surface plot via tcp-data.
When the data incoming are small (random 0 - 5) i get a nicely colored Graph, but when i send bigger data (e.g. -50 to +50) the colors get messed up (pictures below), where the whole surface graph is white. I've tried some matplotlib Colormaps, but the result was similiar, with only the white changing to another color, but no surface was visible due to everything having the same color.
Heres my code:
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
import datetime
from matplotlib import cm
numberOfData = 1000
widthOfData = 500
x = np.linspace(-widthOfData / 2, widthOfData / 2, widthOfData)
y = np.linspace(-numberOfData / 2, numberOfData / 2, numberOfData)
#colormap = cm.get_cmap('jet') # cm.get_cmap("CMRmap") 'viridis'
#colormap._init()
#lut = (colormap._lut * 255).view(np.ndarray) # Convert matplotlib colormap from 0-1 to 0 -255 for Qt
p4 = gl.GLSurfacePlotItem(x, y, shader='heightColor', computeNormals=False,
smooth=False) # smooth true = faster; dont turn on computenormals
p4.shader()['colorMap'] = np.array([0.2, 2, 0.5, 0.2, 1, 1, 0.2, 0, 2]) #lut
# p4.setGLOptions('opaque')
data = np.zeros((widthOfData, numberOfData), dtype=int)
index = 0
def init():
global p4, data, index
## Create a GL View widget to display data
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
w.setWindowTitle('PAS Surfaceplot')
w.setGeometry(100, 100, 1500, 800) # distance && resolution
w.setCameraPosition(distance=1000)
## Create axis
# axis = pg.AxisItem('left', pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True)
# axis.show()
# axis = pg.AxisItem('left', pen = None)
# xAxis.paint()
# Axis.setSize(self.valueNumber, self.valueNumber, self.valueNumber)
# axis.setStyle(showValues = True)
# axis.show()
# --------------------
axis = gl.GLAxisItem()
# xAxis.paint()
# axis.setSize(self.valueNumber, self.valueNumber, self.valueNumber)
w.addItem(axis)
## Add a grid to the view
g = gl.GLGridItem()
g.setSize(x=widthOfData * 2, y=numberOfData * 2)
# g.scale(2,2,1000)
g.setDepthValue(10) # draw grid after surfaces since they may be translucent
w.addItem(g)
## create a surface plot, tell it to use the 'heightColor' shader
## since this does not require normal vectors to render (thus we
## can set computeNormals=False to save time when the mesh updates)
# p4.translate(100, 100, 0)
w.addItem(p4)
# timer = QtCore.QTimer()
# timer.timeout.connect(updateSelf)
# timer.start(20)
## Start Qt event loop unless running in interactive mode.
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
#update via timer
def updateSelf():
global p4, data, index
timeBeforeUpdate = datetime.datetime.now()
data = np.delete(data, 0, 0)
newValues = np.random.randint(5, size=(1, numberOfData))
# print('newval ', newValues)
data = np.concatenate((data, newValues))
p4.setData(z=data)
timeAfterUpdate = datetime.datetime.now()
timeDiff = timeAfterUpdate - timeBeforeUpdate
elapsed_ms = (timeDiff.days * 86400000) + (timeDiff.seconds * 1000) + (timeDiff.microseconds / 1000)
# print(elapsed_ms, ' ms')
#update via tcp
def update(framesList):
global p4, data, index
timeBeforeUpdate = datetime.datetime.now()
for frame in framesList:
data = np.delete(data, 0, 0)
frame = np.array(frame, ndmin=2)
# print('data: ', data)
# print('frame: ', frame)
data = np.concatenate((data, frame))
p4.setData(z=data)
timeAfterUpdate = datetime.datetime.now()
timeDiff = timeAfterUpdate - timeBeforeUpdate
elapsed_ms = (timeDiff.days * 86400000) + (timeDiff.seconds * 1000) + (timeDiff.microseconds / 1000)
print(elapsed_ms, ' ms')
# init()
# timer = QtCore.QTimer()
# timer.timeout.connect(updateSelf)
# timer.start(20)
How do i fix this?
working colors
messed up colors
i have solved the issue with
self.surfacePlot.shader()['colorMap'] = np.array([0.01, 40, 0.5, 0.01, 40, 1, 0.01, 40, 2]) # lut
The problem was indeed that the color brightens way too fast.
Colormaps are defined by 3 triples, where index_0^index_3 determines the color. So in my case it is now 0.01^0.5, 0.01^1 and 0.01^2.
Sadly i can't find the wiki-code example where i found this in a comment
From what I can think of, the unified color is because of high density of the data. Since the variation occurs as you increase your data, what you can try is reducing the range of data, i.e. for -50 to 50, try dividing it first by 10, so as to get the values in range of -10 to 10. That should help.
Related
I'm having a problem with the font scaling of TextItems in pyqtgraph, like you can see from the following code when I zoom in/zoom out in the main graph the font of the TextItems stays the same while I'm trying to make It scale in the same exact way (rate) of the QGraphicsRectItem. I've tried to look on all the forums I know but I haven't find an answer so I really hope someone has a solution for this.
import sys
import pyqtgraph as pg
from PyQt6.QtWidgets import QApplication, QGraphicsRectItem
from pyqtgraph.Qt import QtCore
app = QApplication(sys.argv)
view = pg.GraphicsView()
l = pg.GraphicsLayout()
view.setCentralItem(l)
view.show()
view.resize(800, 600)
p0 = l.addPlot(0, 0)
p0.showGrid(x=True, y=True, alpha=1.0)
# have no x-axis tickmark below the upper plot (coordinate 0,0)
# without these lines, there will be separate coordinate systems with a gap inbetween
ay0 = p0.getAxis('left') # get handle to y-axis 0
ay0.setStyle(showValues=False) # this will remove the tick labels and reduces gap b/w plots almost to zero
# there will be a double line separating the plot rows
# ay02 = p0.getAxis('right')
# ay02.setStyle(showValues=False)
p0.hideAxis('right')
ax02 = p0.getAxis('top')
ax02.setStyle(showValues=False)
p1 = l.addPlot(0, 1)
# p1.showGrid(x=True, y=True, alpha=1.0)
p1.setYLink(p0)
l.layout.setSpacing(0.5)
l.setContentsMargins(0., 0., 0., 0.)
p1.setFixedWidth(300)
# p1.setFixedHeight(h-451)
p1.setMouseEnabled(x=False)
# ay1 = p1.getAxis('left')
# ay1.setStyle(showValues=False)
ax12 = p1.getAxis('top')
ax12.setStyle(showValues=False)
# ax1 = p1.getAxis('bottom')
# ax1.setStyle(showValues=False)
p1.showAxis('right')
p1.hideAxis('left')
p1.setXRange(0, 6, padding=0) # Then add others like 1 pip
# p1.getAxis('bottom').setTextPen('black')
board = ['123456',
'abcdef',
'ghilmn']
def draw_board(board2):
for j, row in enumerate(board2):
for i, cell in enumerate(row):
rect_w = 1
rect_h = 1
r = QGraphicsRectItem(i, -j+2, rect_w, rect_h)
r.setPen(pg.mkPen((0, 0, 0, 100)))
r.setBrush(pg.mkBrush((50, 50, 200)))
p1.addItem(r)
t_up = pg.TextItem(cell, (255, 255, 255), anchor=(0, 0))
t_up.setPos(i, -j+1+2)
p1.addItem(t_up)
draw_board(board)
if __name__ == '__main__':
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QApplication.instance().exec()
Scaling of a text item is quite difficult, as you need to consider a constant aspect ratio of the base scale, and the problems related to the way fonts are positioned and drawn relative to the origin point.
Assuming that the displayed text will always be a single character and that the characters used are standard ascii letters and numbers, the only possibility is to cycle through all possible characters, and create properly aligned paths for each of them.
So, for every character:
construct a QPainterPath;
add the letter to the path;
get the max() of that path width and the others;
get the minimum Y and maximum bottom of the bounding rectangle;
translate the path based on all other values computed above (in a separate loop);
Then, you have to set a reference size for the letter (using the maximum width above and the font metrics' height) and get the aspect ratio for that size.
The last part is implemented in the paint() function of the QGraphicsRectItem subclass, which is required to get the proper geometry of the item (if any transformation is applied to a parent item, the item will not know it), and get the maximum rectangle for the reference size based on the current rectangle size.
class NumberRectItem(QGraphicsRectItem):
textSize = None
textPaths = {}
textPath = None
def __init__(self, x, y, width, height, letter=''):
super().__init__(x, y, width, height)
if letter:
if not self.textPaths:
self._buildTextPaths()
self.textPath = self.textPaths[letter]
def _buildTextPaths(self):
from string import ascii_letters, digits
font = QApplication.font()
fm = QFontMetricsF(font)
maxWidth = 0
minY = 1000
maxY = 0
for l in ascii_letters + digits:
path = QPainterPath()
path.addText(0, 0, font, l)
br = path.boundingRect()
maxWidth = max(maxWidth, br.width())
minY = min(minY, br.y())
maxY = max(maxY, br.bottom())
self.textPaths[l] = path
self.__class__.textSize = QSizeF(maxWidth, fm.height())
self.__class__.textRatio = self.textSize.height() / self.textSize.width()
middle = minY + (maxY - minY) / 2
for path in self.textPaths.values():
path.translate(
-path.boundingRect().center().x(),
-middle)
def paint(self, qp, opt, widget=None):
super().paint(qp, opt, widget)
if not self.textPath:
return
qp.save()
qp.resetTransform()
view = widget.parent()
sceneRect = self.mapToScene(self.rect())
viewRect = view.mapFromScene(sceneRect).boundingRect()
rectSize = QSizeF(viewRect.size())
newSize = self.textSize.scaled(rectSize, Qt.KeepAspectRatio)
if newSize.width() == rectSize.width():
# width is the maximum
ratio = newSize.width() / self.textSize.width()
else:
ratio = newSize.height() / self.textSize.height()
transform = QTransform().scale(ratio, ratio)
path = transform.map(self.textPath)
qp.setRenderHint(qp.Antialiasing)
qp.setPen(Qt.NoPen)
qp.setBrush(Qt.white)
qp.drawPath(path.translated(viewRect.center()))
qp.restore()
def draw_board(board2):
for j, row in enumerate(board2):
for i, cell in enumerate(row):
rect_w = 1
rect_h = 1
r = NumberRectItem(i, -j+2, rect_w, rect_h, letter=cell)
r.setPen(pg.mkPen((150, 0, 0, 255)))
r.setBrush(pg.mkBrush((50, 50, 200, 128)))
p1.addItem(r)
Note: for PyQt6 you need to use the full enum names: Qt.GlobalColor.white, etc.
I would like to plot a 2D spectrum where the x coordinate is the spectral dimension (wavelength) and the y coordinate is the spatial dimension (in arcseconds of the sky) using pyqtgraph.
I've been able to do this using an ImageItem() but I cannot seem to figure out how to display the x and y axes in the right coordinates.
I don't want to just change the labels or the ticks but indeed the coordinates of the plot because I later need to perform operations using these values (wavelength and arcsecs).
Here is a minimal working example:
import pyqtgraph as pg
import numpy as np
# The fake data
wvlg = np.linspace(300, 600, 5000)
arcsec = np.linspace(-5, 5, 100)
flux = np.ones((wvlg.shape[0], arcsec.shape[0])) * np.exp(-(arcsec)**2/0.1)
flux += np.random.normal(0, 0.1, size=(wvlg.shape[0], arcsec.shape[0]))
# The plotting
win = pg.GraphicsLayoutWidget(show=True)
ax2D = win.addPlot(title='2D spectrum', row=0, col=0)
img = pg.ImageItem()
img.setImage(flux)
ax2D.addItem(img)
# Some line converting the x and y values to wvlg and arcsec
This gives an image where the x and y axis show the index value, whereas I would like to show the corresponding wavelength and arcsec values.
Is there an easy way to do this that I just grossly overlooked in the documentation?
You can use the setRect method of the ImageItem class to set the extent of the data. See my example below.
Note that I moved the image by half a pixel so that the pixel centers match the exact coordinates. Otherwise the coordinates would align with one of the pixel's corner points.
import pyqtgraph as pg
import numpy as np
from PyQt5 import QtCore, QtWidgets
def main():
app = QtWidgets.QApplication([])
# The fake data
wvlg = np.linspace(300, 600, 5000)
arcsec = np.linspace(-5, 5, 100)
flux = np.ones((wvlg.shape[0], arcsec.shape[0])) * np.exp(-(arcsec)**2/0.1)
flux += np.random.normal(0, 0.1, size=(wvlg.shape[0], arcsec.shape[0]))
# The plotting
win = pg.GraphicsLayoutWidget()
ax2D = win.addPlot(title='2D spectrum', row=0, col=0)
img = pg.ImageItem()
img.setImage(flux)
ax2D.addItem(img)
print(flux.shape)
# Move the image by half a pixel so that the center of the pixels are
# located at the coordinate values
dx = wvlg[1]-wvlg[0]
dy = arcsec[1]-arcsec[0]
print("pixel size x: {}, pixel size y: {}".format(dx, dy))
rect = QtCore.QRectF(wvlg[0] - dx/2, arcsec[0] - dy/2,
wvlg[-1] - wvlg[0], arcsec[-1] - arcsec[0])
print(rect)
img.setRect(rect)
ax2D.setLabels(left='arcsec', bottom='wvlg')
win.show()
win.raise_()
app.exec_()
if __name__ == "__main__":
main()
I'm trying to plot several surfaces, each of a different color, in Plotly for Python.
Specifically, a surface shows the predicted reward function for taking an action at different points in phase space. Since I have several possible actions at each point, each is a different surface. I'd like to color each surface uniquely, but independent of the x,y, or z coordinate.
I've tried to follow answer in R, but I can't figure out what I've done wrong. I always get the same blue color. Since I'm using PyPlot in other parts of my code, I'm choosing colors from the default matplotlib tableau.
Here's a basic example with toy data.
import matplotlib.pyplot as plt
import numpy as np
import plotly.graph_objs as go
import plotly.offline as off
off.init_notebook_mode()
make_int = np.vectorize(int)
cmap = plt.get_cmap("tab10")
saddle = np.array([[x**2-y**2 for x in np.arange(-10,11)] for y in np.arange(-10,11)])
paraboloid = np.array([[x**2 + y**2-100 for x in np.arange(-10,11)] for y in np.arange(-10,11)])
mycolors_a = make_int(256*np.array(cmap(1)[0:3])).reshape((1, 1,-1)).repeat(21, axis = 0).repeat(21, axis =1)
mycolors_b = make_int(256*np.array(cmap(2)[0:3])).reshape((1, 1,-1)).repeat(21, axis = 0).repeat(21, axis =1)
trace_a = go.Surface(z = saddle, surfacecolor = mycolors_a, opacity = .7, showscale = False, name = "Trace A")
trace_b = go.Surface(z = paraboloid, surfacecolor = mycolors_b, opacity = .7, showscale = False, name = "Trace B")
data = [trace_a, trace_b]
off.iplot(data)
Produces the following:
I should see a blue saddle and an orange paraboloid, but I don't. Note that even if I change the argument to cmap, I always get the same blue color. Thanks for your help!
The documentation is a bit cryptic here.
surfacecolor
(list, numpy array, or Pandas series of numbers, strings, or datetimes.)
Sets the surface color values, used for setting a color scale independent of z.
I never managed to put a list of strings, i.e. color values like 'rgb(0.3, 0.5, 0)', or RGB tuples in it.
But you can define your own color scale with the needed colors.
colorscale = [[0, 'rgb' + str(cmap(1)[0:3])],
[1, 'rgb' + str(cmap(2)[0:3])]]
and then provide a numeric array with the same dimensions as your plotted values.
colors_saddle = np.zeros(shape=saddle.shape)
All values are set to 0 and will therefore map to the first color in your colorscale. The same for the next color.
In addition you need to set cmax and cmin manually.
Complete code
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objs as go
import plotly.offline as off
off.init_notebook_mode()
make_int = np.vectorize(int)
cmap = plt.get_cmap("tab10")
saddle = np.array([[x**2-y**2 for x in np.arange(-10,11)] for y in np.arange(-10,11)])
paraboloid = np.array([[x**2 + y**2-100 for x in np.arange(-10,11)] for y in np.arange(-10,11)])
colors_saddle = np.zeros(shape=saddle.shape)
colors_paraboloid = np.ones(shape=paraboloid.shape)
colorscale = [[0, 'rgb' + str(cmap(1)[0:3])],
[1, 'rgb' + str(cmap(2)[0:3])]]
trace_a = go.Surface(z=saddle,
surfacecolor=colors_saddle,
opacity=.7,
name="Trace A",
cmin=0,
cmax=1,
colorscale=colorscale)
trace_b = go.Surface(z=paraboloid,
surfacecolor=colors_paraboloid,
opacity=.7,
name="Trace B",
cmin=0,
cmax=1,
showscale=False,
colorscale=colorscale)
data = [trace_a, trace_b]
off.iplot(data)
You can combine all surfaces in one and set in colorscale range for each surface
It can also resolve overlapping problem, so you would see the line of surfaces intersection clearly like here
import numpy as np
import plotly.graph_objs as go
# normalize values to range [start,end] for getting color from cmap
def norm_v_in_range(v,start,end):
v_min = v.min()
v_max = v.max()
range_length = (end - start)
if v_min-v_max == 0 :
v.fill(range_length/5 + start)
return v
return (v-v_min)/(v_max-v_min)*range_length + start
def combine_all_surfaces_in_one(X,Y,*Z) :
# prepare colors and ranges for diffrent surfaces
colors = [ 'rgb(180, 110, 20)', 'rgb( 20, 180, 110)', 'rgb(110, 20, 180)',
'rgb(180, 180, 20)', 'rgb( 20, 180, 180)', 'rgb(180, 20, 180)',
'rgb(180, 20, 20)', 'rgb( 20, 180, 20)', 'rgb( 20, 20, 180)',
'rgb(180, 110, 20)', 'rgb( 20, 180, 110)', 'rgb(110, 20, 180)',
'rgb(255, 127, 127)', 'rgb(127, 255, 127)']
N = len(Z)
points = np.linspace(0, 1, N + 1)
custom_colorscale = []
ranges = []
for i in range(1,N+1) :
ranges.append([points[i-1],points[i]-0.05])
custom_colorscale.append([points[i-1], colors[i]])
custom_colorscale.append([points[i]-0.05,'rgb(255, 250, 220)'])
custom_colorscale.append([1, 'rgb(220, 250, 220)'])
# transparent connection between grahps: np.nan in z prevent ploting points
transparen_link = np.empty_like(X[0], dtype=object)
transparen_link.fill(np.nan)
# include first graph
combined_X = X
combined_Y = Y
combined_Z = Z[0]
# prepare collor matrix for first graph (Z[0])
start = ranges[0][0]
end = ranges[0][1]
custom_surfacecolor = norm_v_in_range(Z[0],start,end)
# second aray combined with first in backward direction, so connection would on one side of graphs, not intersect them
direction = -1
range_index = 1
for next_Z in Z[1:] :
combined_X = np.vstack([combined_X, combined_X[-1], X[::direction][0], X[::direction][0], X[::direction]])
combined_Y = np.vstack([combined_Y, combined_Y[-1], Y[::direction][0], Y[::direction][0], Y[::direction]])
combined_Z = np.vstack([combined_Z, combined_Z[-1], transparen_link, next_Z[::direction][0], next_Z[::direction]])
# prepare collors for next Z_
start = ranges[range_index][0]
end = ranges[range_index][1]
next_surfacecolor = norm_v_in_range(next_Z,start,end)
custom_surfacecolor = np.vstack([custom_surfacecolor,custom_surfacecolor[-1], transparen_link, next_surfacecolor[::direction][0], next_surfacecolor[::direction]])
# change direction
direction *= -1
range_index += 1
return combined_X, combined_Y, combined_Z, custom_surfacecolor, custom_colorscale
X = np.arange(-1.2, 1.06, 0.1)
Y = np.arange(0.2, 1.06, 0.1)
X, Y = np.meshgrid(X, Y)
Z1 = 2*np.sin(np.sqrt(20*X**2+20*Y**2))
Z2 = 2*np.cos(np.sqrt(20*X**2+20*Y**2))
Z3 = X*2+0.5
Z4 = Y*0+1.0
Z5 = Y*0-1.0
Z6 = Y*0+0.0
x,y,z,custom_surfacecolor,custom_colorscale = combine_all_surfaces_in_one(X,Y,Z1,Z2,Z3,Z4,Z5)
# opacity =0.9 - many overlaped areas, better witot it
fig = go.Figure(data=[go.Surface(x=x, y=y, z=z,
surfacecolor=custom_surfacecolor, cmin=0, cmax = 1,
colorscale=custom_colorscale,showscale=False,
)] )
fig.show()
I am trying to generate the real-world coordinates from my MS Kinect V2.
I have managed to piece together a pyqt + opengl scatter plot and show the depth data from the Kinect using pylibfreenect2.
I noticed immediately that the depth data was not the same as point cloud data. Notice my room's ceiling is very distorted (what should be a flat ceiling begins to resemble a hockey stick graph)
Result of plotting the depth frame
After some reading and digging through source files I managed to find a function which seemed very promising.
getPointXYZ - Construct a 3-D point in a point cloud.
As it only works on one pixel at a time I wrote a simple nested for loop. In the code below you should see the lines:
out = np.zeros((d.shape[0]*d.shape[1], 3)) #shape = (217088, 3)
for row in range(d.shape[0]):
for col in range(d.shape[1]):
world = registration.getPointXYZ(undistorted, row, col) #convert depth pixel to real-world coordinate
out[row + col] = world
Result of coordinates from getPointXYZ()
Not sure what's going on there. It looks more like a straight line and sometimes its resembles a rectangle and it's very flat (yet it sits at arbitrary angels in all three dimensions). When I move my hand in front of the sensor I can see some points move around but no declarable shapes are visible. It appears that all points are being crammed together.
The following is a Python script that will show a pyQt application window containing an openGL scatter plot. Frames are received from the Kinect sensor through pylibfreenect2 and the scatter plot's points are generated by iterating over each row and column of the depth data and sending it through getPointXYZ (This is really slow and doesn't work...).
# coding: utf-8
# An example using startStreams
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
import cv2
import sys
from pylibfreenect2 import Freenect2, SyncMultiFrameListener
from pylibfreenect2 import FrameType, Registration, Frame, libfreenect2
fn = Freenect2()
num_devices = fn.enumerateDevices()
if num_devices == 0:
print("No device connected!")
sys.exit(1)
serial = fn.getDeviceSerialNumber(0)
device = fn.openDevice(serial)
types = 0
types |= FrameType.Color
types |= (FrameType.Ir | FrameType.Depth)
listener = SyncMultiFrameListener(types)
# Register listeners
device.setColorFrameListener(listener)
device.setIrAndDepthFrameListener(listener)
device.start()
# NOTE: must be called after device.start()
registration = Registration(device.getIrCameraParams(),
device.getColorCameraParams())
undistorted = Frame(512, 424, 4)
registered = Frame(512, 424, 4)
#QT app
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
#initialize some points data
pos = np.zeros((1,3))
sp2 = gl.GLScatterPlotItem(pos=pos)
w.addItem(sp2)
def update():
frames = listener.waitForNewFrame()
ir = frames["ir"]
color = frames["color"]
depth = frames["depth"]
d = depth.asarray()
registration.apply(color, depth, undistorted, registered)
#There are 3 optionally commented methods for generating points data (the last one is not commented here).
#First will generate points using depth data only.
#Second will generate colored points and pointcloud xyz coordinates.
#Third is simply the pointcloud xyz coordinates without the color mapping.
"""
#Format depth data to be displayed
m, n = d.shape
R, C = np.mgrid[:m, :n]
out = np.column_stack((d.ravel() / 4500, C.ravel()/m, (-R.ravel()/n)+1))
"""
"""
#Format undistorted and regisered data to real-world coordinates with mapped colors (dont forget color=out_col in setData)
out = np.zeros((d.shape[0]*d.shape[1], 3)) #shape = (217088, 3)
out_col = np.zeros((d.shape[0]*d.shape[1], 3)) #shape = (217088, 3)
for row in range(d.shape[0]):
for col in range(d.shape[1]):
world = registration.getPointXYZRGB(undistorted, registered, row, col)
out[row + col] = world[0:3]
out_col[row + col] = np.array(world[3:6]) / 255
"""
# Format undistorted data to real-world coordinates
out = np.zeros((d.shape[0]*d.shape[1], 3)) #shape = (217088, 3)
for row in range(d.shape[0]):
for col in range(d.shape[1]):
world = registration.getPointXYZ(undistorted, row, col)
out[row + col] = world
sp2.setData(pos=out, size=2)
listener.release(frames)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
## 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_()
device.stop()
device.close()
sys.exit(0)
I am unsure what I should do next in order to get the actual point cloud coordinate data.
Does anyone have any suggestions as to what I'm doing wrong?
My operating system is Ubuntu 16.0.4 with Python 3.5
Thanks.
The answer was actually to resolve a mistake I made in those nested loops. I noticed it was not indexing an array correctly:
#From:
out[row + col]
#To:
out[row * n_columns + col]
Vertexes are now accurately positioned in 3d space and all looks good!
Here's the revised and fully functional code:
# coding: utf-8
# An example using startStreams
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl
import numpy as np
import cv2
import sys
from pylibfreenect2 import Freenect2, SyncMultiFrameListener
from pylibfreenect2 import FrameType, Registration, Frame, libfreenect2
fn = Freenect2()
num_devices = fn.enumerateDevices()
if num_devices == 0:
print("No device connected!")
sys.exit(1)
serial = fn.getDeviceSerialNumber(0)
device = fn.openDevice(serial)
types = 0
types |= FrameType.Color
types |= (FrameType.Ir | FrameType.Depth)
listener = SyncMultiFrameListener(types)
# Register listeners
device.setColorFrameListener(listener)
device.setIrAndDepthFrameListener(listener)
device.start()
# NOTE: must be called after device.start()
registration = Registration(device.getIrCameraParams(),
device.getColorCameraParams())
undistorted = Frame(512, 424, 4)
registered = Frame(512, 424, 4)
#QT app
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
g = gl.GLGridItem()
w.addItem(g)
#initialize some points data
pos = np.zeros((1,3))
sp2 = gl.GLScatterPlotItem(pos=pos)
w.addItem(sp2)
def update():
colors = ((1.0, 1.0, 1.0, 1.0))
frames = listener.waitForNewFrame()
ir = frames["ir"]
color = frames["color"]
depth = frames["depth"]
d = depth.asarray()
registration.apply(color, depth, undistorted, registered)
listener.release(frames)
"""
#Format raw depth data to be displayed
m, n = d.shape
R, C = np.mgrid[:m, :n]
out = np.column_stack((d.ravel() / 4500, C.ravel()/m, (-R.ravel()/n)+1))
"""
#Format undistorted and regisered data to real-world coordinates with mapped colors (dont forget color=out_col in setData)
n_rows = d.shape[0]
n_columns = d.shape[1]
out = np.zeros((d.shape[0] * d.shape[1], 3), dtype=np.float64)
colors = np.zeros((d.shape[0] * d.shape[1], 3), dtype=np.float64)
for row in range(n_rows):
for col in range(n_columns):
X, Y, Z, B, G, R = registration.getPointXYZRGB(undistorted, registered, row, col)
out[row * n_columns + col] = np.array([X, Y, Z]) # np.array(pt, dtype=np.float64)
colors[row * n_columns + col] = np.divide([R, G, B], 255) # np.array(pt, dtype=np.float64)
"""
#Format undistorted depth data to real-world coordinates
n_rows = d.shape[0]
n_columns = d.shape[1]
out = np.zeros((d.shape[0] * d.shape[1], 3), dtype=np.float64)
for row in range(n_rows):
for col in range(n_columns):
X, Y, Z = registration.getPointXYZ(undistorted, row, col)
out[row * n_columns + col] = np.array([X, Y, Z]) # np.array(pt, dtype=np.float64)
"""
sp2.setData(pos=np.array(out, dtype=np.float64), color=colors, size=2)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
## 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_()
device.stop()
device.close()
sys.exit(0)
[EDIT]
Please see This Post for additional information
I would like to plot diffusion tensors(ellipsoid) in diffusion MRI. The data have three Eigenvalues of the corresponding diffusion tensor. I want to draw an 3D Ellipsoid with its semi-axes lengths corresponding to those three Eigenvalues.
How to do it with Mayavi?
Google brought me here and to the answer. I found how to render an ellipsoid here: https://github.com/spyke/spyke/blob/master/demo/mayavi_test.py and combined it with the arrow from here https://stackoverflow.com/a/20109619/2389450 to produce something like: http://imageshack.com/a/img673/7664/YzbTHY.png
Cheers,
Max
Code:
from mayavi.api import Engine
from mayavi.sources.api import ParametricSurface
from mayavi.modules.api import Surface
from mayavi import mlab
from tvtk.tools import visual
import numpy as np
def Arrow_From_A_to_B(x1, y1, z1, x2, y2, z2,scale=None):
ar1=visual.arrow(x=x1, y=y1, z=z1)
ar1.length_cone=0.4
arrow_length=np.sqrt((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)
if scale is None:
ar1.actor.scale=[arrow_length, arrow_length, arrow_length]
else:
ar1.actor.scale=scale
ar1.pos = ar1.pos/arrow_length
ar1.axis = [x2-x1, y2-y1, z2-z1]
return ar1
engine = Engine()
engine.start()
scene = engine.new_scene()
scene.scene.disable_render = True # for speed
visual.set_viewer(scene)
surfaces = []
for i in range(2):
source = ParametricSurface()
source.function = 'ellipsoid'
engine.add_source(source)
surface = Surface()
source.add_module(surface)
actor = surface.actor # mayavi actor, actor.actor is tvtk actor
#actor.property.ambient = 1 # defaults to 0 for some reason, ah don't need it, turn off scalar visibility instead
actor.property.opacity = 0.7
actor.property.color = (0,0,1) # tuple(np.random.rand(3))
actor.mapper.scalar_visibility = False # don't colour ellipses by their scalar indices into colour map
actor.property.backface_culling = True # gets rid of weird rendering artifact when opacity is < 1
actor.property.specular = 0.1
#actor.property.frontface_culling = True
actor.actor.orientation = np.array([1,0,0]) * 360 # in degrees
actor.actor.origin = np.array([0,0,0])
actor.actor.position = np.array([0,0,0])
actor.actor.scale = np.array([ 0.26490647, 0.26490647, 0.92717265])
actor.enable_texture=True
actor.property.representation = ['wireframe', 'surface'][i]
surfaces.append(surface)
Arrow_From_A_to_B(0,0,0, 0.26490647, 0, 0,np.array([0.26490647,0.4,0.4]))
Arrow_From_A_to_B(0,0,0, 0, 0.26490647, 0,np.array([0.4,0.26490647,0.4]))
Arrow_From_A_to_B(0,0,0, 0, 0, 0.92717265,np.array([0.4,0.4,0.92717265]))
source.scene.background = (1.0,1.0,1.0)
scene.scene.disable_render = False # now turn it on
# set the scalars, this has to be done some indeterminate amount of time
# after each surface is created, otherwise the scalars get overwritten
# later by their default of 1.0
for i, surface in enumerate(surfaces):
vtk_srcs = mlab.pipeline.get_vtk_src(surface)
print('len(vtk_srcs) = %d' % len(vtk_srcs))
vtk_src = vtk_srcs[0]
try: npoints = len(vtk_src.point_data.scalars)
except TypeError:
print('hit the TypeError on surface i=%d' % i)
npoints = 2500
vtk_src.point_data.scalars = np.tile(i, npoints)
# on pick, find the ellipsoid with origin closest to the picked coord,
# then check if that coord falls within that nearest ellipsoid, and if
# so, print out the ellispoid id, or pop it up in a tooltip
mlab.show()