Accessing and Modifying VTK camera of PyVista Panel subplots - python

I am currently making the Panel widgets that could manipulate 3D shaped with the use of PyVista / VTK extension, but I could not find a way to modify the camera viewpoint of another subplot.
Given an example in VTKInterative. It was found that Panel encapsulates pl.ren_win as vtkpan, and its camera position could access and edited through vtkpan.camera following the param.Parameterized manner. But when using two or more subplots. vtkpan.camera is sticking to the first subplot.
pl = pv.Plotter(shape=(1, 2))
pyramid = pv.Pyramid()
pl.subplot(0, 0) # A
actor_A = pl.add_mesh(pyramid, color='r')
pl.camera_position = np.random.random((3,3))
pl.set_scale(0.8, 0.8, 0.8)
pl.subplot(0, 1) # B
actor_B = pl.add_mesh(pyramid, color='b')
pl.camera_position = np.random.random((3,3))
pl.set_scale(0.8, 0.8, 0.8)
vtkpan = pn.panel(pl.ren_win, sizing_mode='fixed', width=400)
vtkpan
vtkpan.camera = {'position': np.array(vtkpan.camera['position']) * [-1],
'viewUp': np.array(vtkpan.camera['viewUp']) * [-1]}
actor_B.GetProperty().SetColor(np.random.random((3)))
vtkpan.synchronize() # change actor_B properties
By modifying vtkpan.camera params, only the left camera viewpoint was changed. Even though modifying VTK camera directly with list(pl.ren_win.GetRenderers())[0].GetActiveCamera().SetViewUp([...]) nothing has happened, suggesting that synchronize() only syncs the renderer's actors. I know that there is a workaround to separate it into 2 vtkpan. However, if there is a way, please tell me.

Related

Vispy surface plot highlight particular section with color and image

Question 1: I am trying to highlight particular section from Vispy Surface Plot example in a particular colour somewhat similar to below modified image.
Question 2: Similarly I would like to add an image data as overlay texture to the Vispy surface plot
Can SurfacePlotVisual be used for this?
I am unable to find any examples of the SurfacePlotVisual on the internet.
Can anyone please direct me to the efficient way of getting it done using vispy.
Thanks
Update 1:
Adding Sample code for testing
import sys
import numpy as np
from vispy import app, scene, color
from vispy.util.filter import gaussian_filter
from vispy.visuals.filters import TextureFilter
from vispy.io import imread, load_data_file, read_mesh
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
print(z.shape)
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.])
p1.transform.translate([-0.5, -0.5, 0])
#verts = p1._meshdata.get_vertices()
verts = p1._meshdata.get_vertices()[:, :2]
texcoords = (verts - verts.min()) / (verts.max() - verts.min())
texture = imread('spot.png')
texture = np.flip(texture, 0) // flip added to get correct position of the image
print("spot.shape:", texture.shape)
print("textcoords:", texcoords)
texture_filter = TextureFilter(texture, texcoords)
p1.attach(texture_filter)
view.add(p1)
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))
# 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()
Update 2:
Have updated above code and output image for reference.
Surface plot output is as above...
Would like to remove surface plot shade and instead would like to have same color output for the surface plot output as compared to image....
Answer 1
The set_data method of the SurfacePlotVisual takes an array of colors:
https://github.com/vispy/vispy/blob/df6c6be9c5aa6a67abf7e3072780264886e2be77/vispy/visuals/surface_plot.py#L132-L151
You should be able to pass whatever colors you want to that as an RGBA array (MxNx4). Note these are colors for the vertices apparently (based on the code I'm seeing) so colors will be interpolated between vertices.
Answer 2
The SurfacePlot is a subclass of the MeshVisual which is able to have a TextureFilter applied to it. I've never done it, but theoretically you should be able to follow this example to add a texture to your SurfacePlot:
https://vispy.org/gallery/scene/mesh_texture.html
The key parts are the loading of the image data from spot.png and then creating and attaching the TextureFilter.
Update 1
Here's what I get if I change the texcoords line to:
verts = p1._meshdata.get_vertices()[:, :2]
Kind of creepy. The point is that we're just making an array that maps the individual surface plot verticies (the mesh vertices) to points on the image. These coordinates need to be between 0 and 1. So we're cheating and taking the vertex coordinates of the mesh and normalizing them between 0 and 1. This may not be anything like what you want, but the point is we have texcoords with a shape of (N, 2) where N is the number of mesh vertices and an (x, y) coordinate for each.

How can I go about adding a pause and go button and some sliders to my fluid simulation to update it in real time without resetting?

I have some code that creates a fluid simulation and I changed it so that it keeps running and advancing the system indefinitely until the user closes the window. Next, I would like to add a pause and go button and a few sliders for changing certain parameters (ie viscosity, inflow velocity, etc.) to make it more interactive. Every time the user moves one of the sliders, I want the animation to continue running but with the new parameters rather than starting all over again. I have not been able to find any resources that answer this question for updating live animations. How would I go about doing this? Thank you.
Here is my code to show you what I have now. I'm trying to do this in pure matplotlib. This code does work but just doesn't have the sliders and buttons added yet
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from fluid import Fluid
RESOLUTION = 85, 160
VISCOSITY = 1e-5
INFLOW_RADIUS = RESOLUTION[0]*0.12
INFLOW_VELOCITY = RESOLUTION[1]*0.14
directions = (np.array([-0.2960987711,0.9551573264]), np.array([0,0]), np.array([0.2960987711,-0.9551573264]))
points = (np.array([RESOLUTION[0]*(0.225), 0]), np.array([-100, 0]), np.array([RESOLUTION[0]*(0.775), RESOLUTION[1]]))
channels = 'r', 'g', 'b'
fluid = Fluid(RESOLUTION, VISCOSITY, channels)
inflow_dye_field = np.zeros((fluid.size, len(channels))) # creating the array to be iterated over. This is the environment that the fluid will exist in
inflow_velocity_field = np.zeros_like(fluid.velocity_field) # creating the array for representing the initial velocity field which will tell the fluid how it needs to advance through successive iterations
def setup_flow():
for i, p in enumerate(points):
distance = np.linalg.norm(fluid.indices - p, axis=1)
mask = distance <= INFLOW_RADIUS
for d in range(2):
inflow_velocity_field[..., d][mask] = directions[i][d] * INFLOW_VELOCITY
inflow_dye_field[..., i][mask] = 1
setup_flow()
fig, ax = plt.subplots(figsize=(12,9))
fig.canvas.set_window_title('Fluid Simulation')
def animate(args):
fluid.advect_diffuse()
fluid.velocity_field += inflow_velocity_field
for i, k in enumerate(channels):
fluid.quantities[k] += inflow_dye_field[..., i]
fluid.project()
rgb = np.dstack(tuple(fluid.quantities[c] for c in channels))
rgb = rgb.reshape((*RESOLUTION, 3))
rgb = (np.clip(rgb, 0, 1) * 255).astype('uint8')
im.set_data(rgb)
rgb = np.dstack(tuple(fluid.quantities[c] for c in channels))
rgb = rgb.reshape((*RESOLUTION, 3))
rgb = (np.clip(rgb, 0, 1) * 255).astype('uint8')
ax.xaxis.set_visible(False); ax.yaxis.set_visible(False)
im = ax.imshow(rgb, animated=True)
ani = animation.FuncAnimation(fig, animate, interval=30)
plt.show()
Edit: I have tried using Tkinter to do this and while it's not too difficult to embed a matplotlib animation in a Tkinter window, the buttons and sliders in Tkinter don't work for the embedded plot. Also, changing the rest of my code so that it's in pure Tkinter will work but it will be an absolute nightmare and I am not confident enough with that library yet to be able to do that given the amount of time I have.

pyqtgraph plotwidget multiple Y axis plots in wrong area

I have an embedded widget in a QT5 gui connected via PlotWidget.
I am trying to plot 2 streams of live data Voltage (self.p1) & Current (self.p2). Voltage on the left axis & Current on the right. So far I have each data stream associated with its relevant axis.
However my problem is that the Current plot (self.p2) is not in the correct area of the display. This particular plot appears in the upper left hand corner of the widget, it appears before the LHD axis. Its best to view the image to view the problem.
View Me
.
I know the problem lies in the setup function & self.p2 (Current) is being placed in the wrong location but my searching hasn't produced an answer.
Could someone please help?
Code used to generate graph, is called once on start up:
def pg_plot_setup(self): # not working still on left axis
self.p1 = self.graphicsView.plotItem
# x axis
self.p1.setLabel('bottom', 'Time', units='s', color='g', **{'font-size':'12pt'})
self.p1.getAxis('bottom').setPen(pg.mkPen(color='g', width=3))
# Y1 axis
self.p1.setLabel('left', 'Voltage', units='V', color='r', **{'font-size':'12pt'})
self.p1.getAxis('left').setPen(pg.mkPen(color='r', width=3))
self.p2 = pg.ViewBox()
self.p1.showAxis('right')
self.p1.scene().addItem(self.p2)
self.p1.getAxis('right').linkToView(self.p2)
self.p2.setXLink(self.p1)
# Y2 axis
self.p1.setLabel('right', 'Current', units="A", color='c', **{'font-size':'12pt'}) #<font>Ω</font>
self.p1.getAxis('right').setPen(pg.mkPen(color='c', width=3))
and code used to update display, is called via QTimer:
def update_graph_plot(self):
start = time.time()
X = np.asarray(self.graph_X, dtype=np.float32)
Y1 = np.asarray(self.graph_Y1, dtype=np.float32)
Y2 = np.asarray(self.graph_Y2, dtype=np.float32)
pen1=pg.mkPen(color='r',width=1.0)
pen2=pg.mkPen(color='c',width=1.0)
self.p1.plot(X,Y1,pen=pen1, name="V", clear=True)
self.p2.addItem(pg.PlotCurveItem(X,Y2,pen=pen2, name="I"))
I found the answer buried in here MultiplePlotAxes.py
adding this self.p2.setGeometry(self.p1.vb.sceneBoundingRect()) to function 'update_graph_plot' adjusts the size of viewbox each time the scene is updated, but it has to be in the update loop.
or add this self.p1.vb.sigResized.connect(self.updateViews) to function 'pg_plot_setup' as part of the setup, which shall then automatically call this function
def updateViews(self):
self.p2.setGeometry(self.p1.vb.sceneBoundingRect())
to resize viewbox (self.p2) each time self.p1 updates.

python memory intensive script

After about 4 weeks of learning, experimenting, etc. I finally have a script which does what I want. It changes the perspective of images according to a certain projection matrix I have created. When I run the script for one image it works fine, however I would like to plot six images in one figure. When I try to do this I get a memory error.
All the images are 2448px in width and 2048 px in height each. My script:
files = {'cam1': 'c1.jpg',
'cam2': 'c2.jpg',
'cam3': 'c3.jpg',
'cam4': 'c4.jpg',
'cam5': 'c5.jpg',
'cam6': 'c6.jpg'}
fig, ax = plt.subplots()
for camname in files:
img = Image.open(files[camname])
gray_img = np.asarray(img.convert("L"))
img = np.asarray(img)
height, width, channels = img.shape
usedP = np.array(P[camname][:,[0,1,3]])
usedPinv = np.linalg.inv(usedP)
U, V = np.meshgrid(range(gray_img.shape[1]),
range(gray_img.shape[0]))
UV = np.vstack((U.flatten(),
V.flatten())).T
ones = np.ones((UV.shape[0],1))
UV = np.hstack((UV, ones))
# create UV_warped
UV_warped = usedPinv.dot(UV.T).T
# normalize vector by dividing by the third column (which should be 1)
normalize_vector = UV_warped[:,2].T
UV_warped = UV_warped/normalize_vector[:,None]
# masks
# pixels that are above the horizon and where the V-projection is therefor positive (X in argus): make 0, 0, 1
# pixels that are to far: make 0,0,1
masks = [UV_warped[:,0]<=0, UV_warped[:,0]>2000, UV_warped[:,1]>5000, UV_warped[:,1]<-5000] # above horizon: => [0,0,1]
total_mask = masks[0] | masks[1] | masks[2] | masks[3]
UV_warped[total_mask] = np.array([[0.0, 0.0, 1.0]])
# show plot
X_warped = UV_warped[:,0].reshape((height, width))
Y_warped = UV_warped[:,1].reshape((height, width))
gray_img = gray_img[:-1, :-1]
# add colors
rgb = img[:,:-1,:].reshape((-1,3)) / 255.0 # we have 1 less faces than grid cells
rgba = np.concatenate((rgb, np.ones((rgb.shape[0],1))), axis=1)
plotimg = ax.pcolormesh(X_warped, Y_warped, img.mean(-1)[:,:], cmap='Greys')
plotimg.set_array(None)
plotimg.set_edgecolor('none')
plotimg.set_facecolor(rgba)
ax.set_aspect('equal')
plt.show()
I have the feeling that numpy.meshgrid is quite memory intensive, but I'm not sure. Does anybody see where my memory gets eaten away rapidly? (BTW, I have a laptop with 12Gb of RAM, which is only used by other programs for a very small part)
You might want to profile your code with this library.
It will show you where your script is using memory.
There is a Stackoverflow question here about memory profilers. Also, I've used the trick in this answer in the past as a quick way to get an idea where in the code memory is going out of control. I just print the resource.getrusage() results all over the place. It's not clean, and it doesn't always work, but it's part of the standard library and it's easy to do.
I ordinarily profile with the profile and cProfile modules, as it makes testing individual sections of code fairly easy.
Python Profilers

Change color depending on height in Mayavi iso_surface

Is is possible to change the colour of an iso-surface depending on height of the points (in python / mayavi) ?
I can create an iso-surface visualization with my script, but I don't know how to make the iso_surface change colour with z axis so that it will be let's say black at the bottom and white at the top of the plot.
I need this in order to make sense of the visualization when it is viewed from directly above the graph.
If you know any other way to achieve this, please let me know as well.
I only want to show one iso_surface plot.
I managed to do this by combining some code from examples http://docs.enthought.com/mayavi/mayavi/auto/example_atomic_orbital.html#example-atomic-orbital and http://docs.enthought.com/mayavi/mayavi/auto/example_custom_colormap.html . Basically you must create a surface as in atomic-orbital example and then make it change colour depending on x. You must create an array of values for x. My code is (the relevant part) :
#src.image_data.point_data.add_array(np.indices(list(self.data.shape)[self.nx,self.ny,self.nz])[2].T.ravel())
src.image_data.point_data.add_array(np.indices(list(self.data.shape))[0].T.ravel())
src.image_data.point_data.get_array(1).name = 'z'
# Make sure that the dataset is up to date with the different arrays:
src.image_data.point_data.update()
# We select the 'scalar' attribute, ie the norm of Phi
src2 = mlab.pipeline.set_active_attribute(src, point_scalars='scalar')
# Cut isosurfaces of the norm
contour = mlab.pipeline.contour(src2)
# contour.filter.contours=[plotIsoSurfaceContours]
# contour.filter.contours=[plotIsoSurfaceContours[0]]
min_c = min(contour.filter._data_min * 1.05,contour.filter._data_max)
max_c = max(contour.filter._data_max * 0.95,contour.filter._data_min)
plotIsoSurfaceContours = [ max(min(max_c,x),min_c) for x in plotIsoSurfaceContours ]
contour.filter.contours= plotIsoSurfaceContours
# Now we select the 'angle' attribute, ie the phase of Phi
contour2 = mlab.pipeline.set_active_attribute(contour, point_scalars='z')
# And we display the surface. The colormap is the current attribute: the phase.
# mlab.pipeline.surface(contour2, colormap='hsv')
xxx = mlab.pipeline.surface(contour2, colormap='gist_ncar')
colorbar = xxx.module_manager.scalar_lut_manager
colorbar.reverse_lut = True
lut = xxx.module_manager.scalar_lut_manager.lut.table.to_array()
lut[:,-1] = int(plotIsoSurfaceOpacity * 254)
xxx.module_manager.scalar_lut_manager.lut.table = lut
# mlab.colorbar(title='Phase', orientation='vertical', nb_labels=3)
self.data is my data, and for unknown reasons if you want to set opacity of your surface you must reverse the lut first and then set the opacity. Multiplication by 254 instead of 255 is done to avoid a possible bug in mayavi.
I hope this helps someone.

Categories

Resources