Create matplotlib-style 3d scattergraph z-axis in Vispy - python

I am conducting PhD research into extremely high density lidar point clouds(1M points +), and am having real difficulty plotting these files on a 3D scattergraph. Matplotlib isn't optimised for datasets that large, so I am attempting to use Vispy to achieve this. Right now, I am having real trouble trying to get a scattergraph-style plot up and running, so I can start building my pipeline.
I'm after something like this:
matplotlib 3D scattergraph
Using Vispy, I am finding it difficult to apply a z-axis and I am having trouble finding what part of documentation can help me with this. Here's my code:
"""
This example demonstrates the use of the SurfacePlot visual.
"""
import sys
import numpy as np
from vispy import app, scene
from vispy.util.filter import gaussian_filter
canvas = scene.SceneCanvas(keys='interactive', bgcolor='w')
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera(up='z', fov=60)
# Simple surface plot example
# x, y values are not specified, so assumed to be 0:50
z = np.random.normal(size=(250, 250), scale=200)
z[100, 100] += 50000
z = gaussian_filter(z, (10, 10))
print("This is z {0}".format(z))
p1 = scene.visuals.SurfacePlot(z=z, color=(0.3, 0.3, 1, 1))
p1.transform = scene.transforms.MatrixTransform()
p1.transform.scale([1/249., 1/249., 1/249.])
p1.transform.translate([-0.5, -0.5, 0])
view.add(p1)
# p1._update_data() # cheating.
# cf = scene.filters.ZColormapFilter('fire', zrange=(z.max(), z.min()))
# p1.attach(cf)
xax = scene.Axis(pos=[[-0.5, -0.5], [0.5, -0.5]], tick_direction=(0, -1),
font_size=16, axis_color='k', tick_color='k', text_color='k',
parent=view.scene)
xax.transform = scene.STTransform(translate=(0, 0, -0.2))
yax = scene.Axis(pos=[[-0.5, -0.5], [-0.5, 0.5]], tick_direction=(-1, 0),
font_size=16, axis_color='k', tick_color='k', text_color='k',
parent=view.scene)
yax.transform = scene.STTransform(translate=(0, 0, -0.2))
zax = scene.Axis(pos=[[1.0, 1.0], [-1.0, 1.0]], tick_direction=(-1, 0),
font_size=16, axis_color='k', tick_color='k', text_color='k',
parent=view.scene)
zax.transform = scene.STTransform(translate=(0.0, 0.0, -0.2))
# Add a 3D axis to keep us oriented
axis = scene.visuals.XYZAxis(parent=view.scene)
if __name__ == '__main__':
canvas.show()
if sys.flags.interactive == 0:
app.run()
And it makes a graph that looks like this:
vispy_example_plot
You can see in the above image my problem. I want to orient the third axis perpendicular to the others, and a grid to the to the walls of the plot, so the data is more defined.
Unfortunately sub-sampling and other tricks to display the data using matplotlib, are not optimal for me because I have to display the original data, rather than showing a subset. Of course, if there are other strategies I can use to display my data as I would like, then I am all ears.
Thanks in advance, hopefully someone can help

i faced the same issue doing research on camera depth images and cfar radar data. since vispy is supporting 2d only for axes, you could rotate the z-axis after creation. here, a slightly modified version of your code:
import sys
import numpy as np
from vispy import app, scene
from vispy.util.filter import gaussian_filter
canvas = scene.SceneCanvas(keys='interactive', bgcolor='w')
view = canvas.central_widget.add_view()
view.camera = scene.TurntableCamera(up='z', fov=60)
z = np.random.normal(size=(250, 250), scale=200)
z[100, 100] += 50000
z = gaussian_filter(z, (10, 10))
p1 = scene.visuals.SurfacePlot(z=z, color=(0.3, 0.3, 1, 1))
p1.transform = scene.transforms.MatrixTransform()
p1.transform.scale([1/249., 1/249., 1/249.])
view.add(p1)
xax = scene.Axis(pos=[[0, 0], [1, 0]], tick_direction=(0, -1), axis_color='r', tick_color='r', text_color='r', font_size=16, parent=view.scene)
yax = scene.Axis(pos=[[0, 0], [0, 1]], tick_direction=(-1, 0), axis_color='g', tick_color='g', text_color='g', font_size=16, parent=view.scene)
zax = scene.Axis(pos=[[0, 0], [-1, 0]], tick_direction=(0, -1), axis_color='b', tick_color='b', text_color='b', font_size=16, parent=view.scene)
zax.transform = scene.transforms.MatrixTransform() # its acutally an inverted xaxis
zax.transform.rotate(90, (0, 1, 0)) # rotate cw around yaxis
zax.transform.rotate(-45, (0, 0, 1)) # tick direction towards (-1,-1)
if __name__ == '__main__':
canvas.show()
if sys.flags.interactive == 0:
app.run()
Regards

Related

How to join 2 surfaces on Z acess into one mesh?

So I have 2 surfaces (PolyData in PyVista) one on top of another:
They are shaped a little differently on Z access yet whenever a top one has a Z value on X, Y plane we are sure a-bottom one has the same. So how one can merge two surfaces X, Y aligned into one solid mesh?
What I try:
import numpy as np
import pyvista as pv
import vtk
def extruder(mesh, val_z):
extrude = vtk.vtkLinearExtrusionFilter()
extrude.SetInputData(mesh)
extrude.SetVector(0, 0, val_z)
extrude.Update()
extruded_mesh = pv.wrap(extrude.GetOutput())
return extruded_mesh
# generate two sheets of input data
noise = pv.perlin_noise(2, (0.2, 0.2, 0.2), (0, 0, 0))
bounds_2d = (-10, 10, -10, 10)
dim = (40, 50, 1)
bottom, top = [
pv.sample_function(noise, dim=dim, bounds=bounds_2d + (z, z)).warp_by_scalar()
for z in [-5, 5]
]
bottom = bottom.extract_surface(nonlinear_subdivision=5)
top = top.extract_surface(nonlinear_subdivision=5)
top = extruder(top, -50).triangulate()
bottom = extruder(bottom, 50).triangulate()
intersection = bottom.boolean_cut(top)
#top = top.clip_surface(bottom, invert=False, compute_distance=True)
#top = top.extrude([0, 0, -50]).triangulate()
#bottom = bottom.extrude([0, 0, 50]).triangulate()
#intersection = bottom.boolean_cut(top).triangulate()
p = pv.Plotter()
p.add_mesh(top, cmap="hot", opacity=0.15)
p.add_mesh(bottom, cmap="RdYlBu", opacity=0.15)
p.add_mesh(intersection, cmap="Dark2", opacity=1)
p.show()
What do I get:
What I expected:
only middle to be filled.
So had to do this:
import numpy as np
import pyvista as pv
# generate two sheets of input data
noise = pv.perlin_noise(2, (0.2, 0.2, 0.2), (0, 0, 0))
bounds_2d = (-10, 10, -10, 10)
dim = (40, 50, 1)
bottom, top = [
pv.sample_function(noise, dim=dim, bounds=bounds_2d + (z, z)).warp_by_scalar()
for z in [-5, 5]
]
bottom = bottom.extract_surface()
top = top.extract_surface()
topm = top.extrude([0, 0, -50]).triangulate().clean()
bottomm = bottom.extrude([0, 0, 50]).triangulate().clean()
topm = topm.clip_surface(bottom, invert=False)
bottomm = bottomm.clip_surface(top, invert=True)
intersection = topm.boolean_add(bottomm).triangulate().clean().subdivide(2).clean()
p = pv.Plotter()
#p.add_mesh(topm, cmap="hot", opacity=0.15)
#p.add_mesh(bottomm, cmap="gnuplot2", opacity=0.15)
p.add_mesh(intersection, cmap="Dark2", opacity=1)
p.show()
the resulting mesh is really bad, yet it has desired shape and gets to be computed in usable time:

How to rotate a 1D line graph array in python/numpy by angle?

I’d like to rotate a line graph horizontally. So far, I have the target angle but I’m not able to rotate the graph array (the blue graph in the blot).
import matplotlib.pyplot as plt
import numpy as np
x = [5, 6.5, 7, 8, 6, 5, 3, 4, 3, 0]
y = range(len(x))
best_fit_line = np.poly1d(np.polyfit(y, x, 1))(y)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], best_fit_line[-1] - best_fit_line[0]))
print("angle: " + str(angle))
plt.figure(figsize=(8, 6))
plt.plot(x)
plt.plot(best_fit_line, "--", color="r")
plt.show()
The target calculations of the array should look like this (please ignore the red line):
If you have some advice, please let me know. Thanks.
This question is very helpful, in particular the answer by #Mr Tsjolder. Adapting that to your question, I had to subtract 90 from the angle you calculated to get the result you want:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import transforms
x = [5, 6.5, 7, 8, 6, 5, 3, 4, 3, 0]
y = range(len(x))
best_fit_line = np.poly1d(np.polyfit(y, x, 1))(y)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], best_fit_line[-1] - best_fit_line[0]))
print("angle: " + str(angle))
plt.figure(figsize=(8, 6))
base = plt.gca().transData
rotation = transforms.Affine2D().rotate_deg(angle - 90)
plt.plot(x, transform = rotation + base)
plt.plot(best_fit_line, "--", color="r", transform = rotation + base)
Follow-up question: What if we just need the numerical values of the rotated points?
Then the matplotlib approach can still be useful. From the rotation object we introduced above, matplotlib can extract the transformation matrix, which we can use to transform any point:
# extract transformation matrix from the rotation object
M = transforms.Affine2DBase.get_matrix(rotation)[:2, :2]
# example: transform the first point
print((M * [0, 5])[:, 1])
[-2.60096617 4.27024297]
The slicing was done to get the dimensions we're interested in, since the rotation happens only in 2D. You can see that the first point from your original data gets transformed to (-2.6, 4.3), agreeing with my plot of the rotated graph above.
In this way you can rotate any point you're interested in, or write a loop to catch them all.
Arne's awnser is perfect if you like to rotate the graph with matplotlib. If not, you can take a look a this code:
import matplotlib.pyplot as plt
import numpy as np
def rotate_vector(data, angle):
# source:
# https://datascience.stackexchange.com/questions/57226/how-to-rotate-the-plot-and-find-minimum-point
# make rotation matrix
theta = np.radians(angle)
co = np.cos(theta)
si = np.sin(theta)
rotation_matrix = np.array(((co, -si), (si, co)))
# rotate data vector
rotated_vector = data.dot(rotation_matrix)
return rotated_vector
x = [5, 6.5, 7, 8, 6, 5, 3, 4, 3, 0]
y = range(len(x))
best_fit_line = np.poly1d(np.polyfit(y, x, 1))(y)
angle = np.rad2deg(np.arctan2(y[-1] - y[0], best_fit_line[-1] - best_fit_line[0]))
print("angle:", angle)
# rotate blue line
d = np.hstack((np.vstack(y), np.vstack(x)))
xr = rotate_vector(d, -(angle - 90))
# rotate red line
dd = np.hstack((np.vstack(y), np.vstack(best_fit_line)))
xxr = rotate_vector(dd, -(angle - 90))
plt.figure(figsize=(8, 6))
plt.plot(xr[:, 1]) # or plt.plot(xr[:, 0], xr[:, 1])
plt.plot(xxr[:, 1], "--", color="r")
plt.show()

How to set absolute position of GLMeshItem in 3D pyqtgraph implementation

I am building a visualizer for some data and want to use 3D spheres plotted in pyqtgraphs 3D OpenGL components to represent targets identified within the data provided.
I am able to generate the spheres and move them using GLMeshItem.translate() commands, however I am not able to find a convenient way of setting coordinates of the sphere without first getting the current position of said spheres through a call to .transform() and then generating a translate command from it's current position to the new absolute coordinates I would like it to be moved to. It may be that that is the only way to accomplish this, I just suspect there is a more direct set the mesh items absolute coordinates that I just can't seem to identify.
The following code shows a basic framework for what I am doing, and also the current method I am using to move the sphere.
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import numpy as np
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.showMaximized()
w.setWindowTitle('pyqtgraph example: GLMeshItem')
w.setCameraPosition(distance=40)
g = gl.GLGridItem()
g.scale(2,2,1)
w.addItem(g)
verts = np.array([
[0, 0, 0],
[2, 0, 0],
[1, 2, 0],
[1, 1, 1],
])
faces = np.array([
[0, 1, 2],
[0, 1, 3],
[0, 2, 3],
[1, 2, 3]
])
colors = np.array([
[1, 0, 0, 0.3],
[0, 1, 0, 0.3],
[0, 0, 1, 0.3],
[1, 1, 0, 0.3]
])
md = gl.MeshData.sphere(rows=4, cols=4)
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[::2,0] = 0
colors[:,1] = np.linspace(0, 1, colors.shape[0])
md.setFaceColors(colors)
m3 = gl.GLMeshItem(meshdata=md, smooth=False)#, shader='balloon')
w.addItem(m3)
target = gl.MeshData.sphere(4,4,10)
targetMI = gl.GLMeshItem(meshdata = target, drawFaces = True,smooth = False)
w.addItem(targetMI)
while(1):
targetMI.translate(0.1,0,0)
app.processEvents()
## 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_()
As can be seen in this example. translate works fine for moving relative to the current position. I am just curious as to whether there is a method for doing an absolute position move on the GLMeshItem (in this case targetMI) such that I could make it move to a coordinate without having to first get the transform and then calculating the translate required to move to the desired coordinates.
An option is to reset the item’s transform to an identity transformation by resetTransform(), before you set the absolute position by translate(). e.g:
targetMI.resetTransform()
targetMI.translate(10, 0, 0)

Skew/rotate/otherwise transform 3D text in Matplotlib

I have a graph in Matplotlib that looks like this:
I would like to skew the text (by an x-shear of 10.3 and a y shear of 9.0) in order to make it appear to be parallel with the bars. Here is my code that I am using:
_x = [0,1,2,3]
_y = [1, 2]
_xx, _yy = np.meshgrid(_x, _y)
x, y = _xx.ravel(), _yy.ravel()
top = [1, 0, 0, 3, 0, 0, 0, 0]
bottom = np.zeros_like(top)
width = 1
depth = 0.25
# graph is set up here
for i, iXZ in enumerate(zip(x, top)):
iX, iZ = iXZ
if iZ:
t = ax1.text(iX + 0.5, 1, -0.85, ('U\n' if i == 0 else 'E\n') * iZ, (0, 0, 0),
ha='center', va='bottom', fontfamily="Century Gothic", color='black', fontsize=40)
# skew by 10.3 and 9.0
t.set_transform(mtrns.CompositeGenericTransform(t.get_transform(), mtrns.Affine2D().skew(10.3, 9.0)))
However, when this code is run, matplotlib outputs this:
I feel like I am using the wrong type of transform, since Affine2D implies that it's only for "2D" objects. Unfortunately, I cannot find the "right" method in the documentation. How would I be able to skew the text effectively?

Adding an animation to a random walk plot [Python]

I have written a code that plots random walks. There are traj different random walks generated and each consists of n steps. I would like to animate their moves. How can I do that?
My code below:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def random_walk_2D(n, traj = 1):
for i in range(traj):
skoki = np.array([[0, 1], [1, 0], [-1, 0], [0, -1]])
losy = np.random.randint(4, size = n)
temp = skoki[losy, :]
x = np.array([[0, 0]])
temp1 = np.concatenate((x, temp), axis = 0)
traj = np.cumsum(temp1, axis = 0)
plt.plot(traj[:, 0], traj[:, 1])
plt.plot(traj[-1][0], traj[-1][1], 'ro') #the last point
plt.show()
As it stands now, you generate traj in one shot. I mean that traj in traj = np.cumsum(temp1, axis = 0) already contains all the "story" from the beginning to the end. If you want to create an animation that is in "real time", you should not generate traj in one shot, but iteratively, plotting new steps as they come. What about doing:
import numpy as np
import matplotlib.pyplot as plt
def real_time_random_walk_2D_NT(
nb_steps, nb_trajs, with_dots=False, save_trajs=False, tpause=.01
):
"""
Parameters
----------
nb_steps : integer
number of steps
nb_trajs : integer
number of trajectories
save_trajs : boolean (optional)
If True, entire trajectories are saved rather than
saving only the last steps needed for plotting.
False by default.
with_dots : boolean (optional)
If True, dots representative of random-walking entities
are displayed. Has precedence over `save_trajs`.
False by default.
tpause : float (optional)
Pausing time between 2 steps. .01 secondes by default.
"""
skoki = np.array([[0, 1], [1, 0], [-1, 0], [0, -1]])
trajs = np.zeros((nb_trajs, 1, 2))
for i in range(nb_steps):
_steps = []
for j in range(nb_trajs):
traj = trajs[j,:,:]
losy = np.random.randint(4, size = 1)
temp = skoki[losy, :]
traj = np.concatenate((traj, temp), axis = 0)
traj[-1,:] += traj[-2,:]
_steps.append(traj)
if save_trajs or with_dots:
trajs = np.array(_steps)
if with_dots:
plt.cla()
plt.plot(trajs[:,i, 0].T, trajs[:,i, 1].T, 'ro') ## There are leeway in avoiding these costly transpositions
plt.plot(trajs[:,:i+1, 0].T, trajs[:,:i+1, 1].T)
else:
plt.plot(trajs[:,-1+i:i+1, 0].T, trajs[:,-1+i:i+1, 1].T)
else:
trajs = np.array(_steps)[:,-2:,:]
plt.plot(trajs[:,:, 0].T, trajs[:,:, 1].T)
plt.pause(tpause)
real_time_random_walk_2D_NT(50, 6, with_dots=True)
real_time_random_walk_2D_NT(50, 6)

Categories

Resources