-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Project points/surface to a plane #20
Comments
Hi @S0Phon, these are interesting questions!
I'll make a new feature request out of this in the main PyVista repo and keep you posted.
This is something I've done before actually - let me dig through some old code and try to make an example for you |
Here's an example of projecting the points of import numpy as np
import pyvista as pv
from pyvista import examples
def make_example_data():
surface = examples.download_saddle_surface()
points = examples.download_sparse_points()
poly = surface.interpolate(points, radius=12.0)
return poly
poly = make_example_data()
poly.plot() Now let's project that data to a plane defined by some origin and some normal: def project_points_to_plane(mesh, origin=None, normal=(0,0,1), inplace=False):
"""Project points of this mesh to a plane"""
if not isinstance(mesh, (pv.PolyData)):
raise TypeError('Please use surface meshes only.')
import vtk
if origin is None:
origin = mesh.center
if not inplace:
mesh = mesh.copy()
# Make plane
normal = normal / np.linalg.norm(normal) # MUST HAVE MAGNITUDE OF 1
plane = vtk.vtkPlane()
plane.SetOrigin(origin)
plane.SetNormal(normal)
# Perform projection in place on the copied mesh
f = lambda p: plane.ProjectPoint(p, p)
np.apply_along_axis(f, 1, mesh.points)
if not inplace:
return mesh
return
# Project that surface to a plane
og = poly.center
og[-1] -= poly.length / 3.
projected = project_points_to_plane(poly, origin=og)
p = pv.Plotter()
p.add_mesh(poly,)
p.add_mesh(projected, )
p.show() |
Thank you very much for your help @banesullivan! For reorganising my questions into the appropriate repositories and for the above method. I have tried the code you provided above using the example data and then again with data from an stl file and it works very well. |
We probably want to save all of these as examples on the docs. |
I have reopened this as I noticed an issue with the code when the origin is not defined and the plane normal vector is changed significantly. In this case I used a normal vector of normal=(-1,0,1) (this was the only thing I changed). As you can see the new projected plane just interects the old one. This may not be an issue in some cases (and is not for my specific case) but if needing both the original data and the projected data in the same image then it is an issue. The zip folder containing the stl file I used for the image above. |
@S0Phon - this seems like an issue with the default parameter for how a plane is chosen. Do you have any suggestions for how to choose a default plane location (the Right now, the function just uses the center of the input mesh for the plane location... so it will always intersect: if origin is None:
origin = mesh.center Maybe we should do something more like: if origin is None:
origin = mesh.center
origin[-1] -= mesh.length but even that is not guaranteed to never overlap |
I included this example in pyvista/pyvista#276 so it'll be in the gallery! Also, my intention with using a GitHub repo for a support forum like this was that all of these discussions/examples will be here permanently so that we can search through them and add good examples to the docs when we have time in the future without trying to dig through emails/slack discussions |
I am not very good at writing neat code or pythonic code or whatever but I think something along these lines should work: if origin is None:
original_origin = (mesh.center)
origin = [original_origin[0] - normal[0]*mesh.length/2, original_origin[1] - normal[1]*mesh.length/2, original_origin[2] - normal[2]*mesh.length/2] What I am doing here is creating a new point to be the origin where the new point is a distance of mesh.length/2 along the normal vector.
or if using the slightly longer distance mesh.length*0.6:
This may benefit from some rounding as in the example I tried I had 19 significant figures for the new origin. It is a little unsatisfying as the distance of the projection from the edge of the object is not the same for all possible normal vectors but I am not sure how to get the dimensions of the boundary box so cannot suggest a fix for this at the moment. Possible Issue |
Thanks for the suggestion, @S0Phon! I went ahead and used if origin is None:
origin = np.array(self.center) - np.array(normal)*self.length/2.
I agree... to get the dimensions of the bounding box, you could use the >>> mesh.bounds
[-20.012727737426758, 20.0, -0.647991418838501, 40.23965072631836, -0.6093360781669617, 15.127287864685059] And you could get the ranges (distance along each axis of the bounding box) by: >>> np.array(mesh.bounds).reshape(-1, 2).ptp(axis=1)
[40.01272774, 40.88764215, 15.73662394] If you have a better solution using that, let me know! On your issue: what you describe is very strange for this example... I haven't experienced this.
Could you install a new package I've made called From command line: pip install --upgrade scooby then copy paste the output of: import scooby
core = ['pyvista', 'vtk', 'numpy', 'imageio', 'appdirs', 'scooby']
optional = ['matplotlib', 'PyQt5', 'IPython', 'ipywidgets', 'colorcet',
'cmocean']
# This will output a report in an interactive Python shell
scooby.Report(core=core, optional=optional) If that doesn't show anything/ if you are using script files, use the print(scooby.Report(core=core, optional=optional)) |
Output is as follows
I hope this is what is needed as I had to change one of the lines as said in the note below. I ran this at the beginning of the script after the module imports. Note: I think scooby.investigte should be replaced with scooby.Report as it threw an attribute error saying that module 'scooby' has no attribute 'investigate' and when checking the issues section for scooby I found one that said that investigate has been deleted and to use Report. A more full description of the problem is that roughly every second or third time I run the script, I get python crashing when the second window opens to show the projected image. When it crashes the window opens black and stays that way for several seconds before the windows says not responding. |
Thank you for sharing all the above info, @S0Phon! To make sure I try reproducing this corectly, can you outline your steps exactly? I'm assuming they are:
import numpy as np
import pyvista as pv
from pyvista import examples
def make_example_data():
surface = examples.download_saddle_surface()
points = examples.download_sparse_points()
poly = surface.interpolate(points, radius=12.0)
return poly
poly = make_example_data()
poly.plot()
def project_points_to_plane(mesh, origin=None, normal=(0,0,1), inplace=False):
"""Project points of this mesh to a plane"""
if not isinstance(mesh, (pv.PolyData)):
raise TypeError('Please use surface meshes only.')
import vtk
if origin is None:
origin = np.array(mesh.center) - np.array(normal)*mesh.length/2.
if not inplace:
mesh = mesh.copy()
# Make plane
normal = normal / np.linalg.norm(normal) # MUST HAVE MAGNITUDE OF 1
plane = vtk.vtkPlane()
plane.SetOrigin(origin)
plane.SetNormal(normal)
# Perform projection in place on the copied mesh
f = lambda p: plane.ProjectPoint(p, p)
np.apply_along_axis(f, 1, mesh.points)
if not inplace:
return mesh
return
# Project that surface to a plane
projected = project_points_to_plane(poly)
p = pv.Plotter()
p.add_mesh(poly,)
p.add_mesh(projected, )
p.show()
$ python my_file.py
Or are you doing something different? |
I have been doing something different. I copied the above into visual studio code, saved it as a python file and ran it using an anaconda distribution of python using the debug feature of VS Code. This is the only way I have been using python so far. When I tried the above using the anaconda prompt, I get an issue 'No module named 'pyvista'. I cannot understand this as I have checked in the environment library list for it and it shows up and when I try to reintall it, it says that I already have it. I then made sure was in the correct enviroment and still the same issue. To further confuse matters, when I tried using a seperate installation of python and run the file I get the error 'NameError: name 'self' not defined. I appreciate your patience and help with this. I am quite new to programming and have yet to figure out a lot of the management of installations and libraries. |
Oh whoops the |
When trying with the Visual Studio Code still has issues but as it seems to be running properly from terminal I think that is a seperate issue. Thanks you for all your help! |
Strange indeed - if it is working in anaconda prompt and command prompt then all seems good. I have a feeling that Visual Studio might not be cleaning up the session between runs (which to me seems like a VS Python issue in general), but I've never used VS before so I can't hypothesize further. I'm going to close this issue as we implemented this filter in the last release of PyVista 🚀 |
then, if I have two surfaces, how could I project the points in the first surface to the second one, and display the color map? |
Different to doingbthe above twice? |
I find a example in pyvista to calculate the surface distance. |
I have an stl file and would like to get the indices of all the cells visible from a chosen viewpoint. I know this is possible in vtk using the vtkHardwareSelector but am curious if it is possible in pyvista?
In addition I would also like to know if there is an existing fuction for projecting 3D point data onto a plane?
The text was updated successfully, but these errors were encountered: