From b9341d63bedcb6590c1e52d1d82dc61f5fd4f558 Mon Sep 17 00:00:00 2001 From: marcomusy Date: Wed, 8 May 2019 19:02:24 +0200 Subject: [PATCH] 2.1 --- README.md | 9 +- examples/advanced/densifycloud.py | 20 ++++ examples/basic/README.md | 4 +- examples/basic/shadow.py | 9 ++ examples/basic/silhouette.py | 6 +- examples/other/dolfin/README.md | 2 + examples/other/dolfin/ex06_elasticity1.py | 4 +- examples/other/dolfin/run_all.sh | 3 + examples/other/dolfin/scalemesh.py | 25 +++++ examples/other/dolfin/stokes.py | 2 +- examples/other/self_org_maps2d.py | 6 +- examples/run_all.sh | 79 ++++---------- examples/simulations/README.md | 5 + examples/simulations/airplanes.py | 20 ++++ examples/simulations/multiple_pendulum.py | 2 +- examples/simulations/run_all.sh | 58 +++++++++++ examples/volumetric/interpolateVolume.py | 17 +-- setup.py | 15 ++- vtkplotter/actors.py | 121 ++++++++++++++-------- vtkplotter/analysis.py | 56 +++++++++- vtkplotter/docs.py | 17 ++- vtkplotter/dolfin.py | 23 ++-- vtkplotter/plotter.py | 20 +++- vtkplotter/settings.py | 14 ++- vtkplotter/shapes.py | 29 +++++- vtkplotter/version.py | 2 +- vtkplotter/vtkio.py | 24 ++--- 27 files changed, 419 insertions(+), 173 deletions(-) create mode 100644 examples/advanced/densifycloud.py create mode 100644 examples/basic/shadow.py create mode 100644 examples/other/dolfin/scalemesh.py create mode 100644 examples/simulations/airplanes.py create mode 100755 examples/simulations/run_all.sh diff --git a/README.md b/README.md index 8fbb2939..f33cdc7f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Intuitive and straightforward API which can be combined with VTK seamlessly in a program, whilst mantaining access to the full range of VTK native classes. It includes a [**large set of working examples**](https://github.com/marcomusy/vtkplotter/tree/master/examples) -for the all following functionalities: +for all the following functionalities: - Import meshes from VTK format, STL, Wavefront OBJ, 3DS, XML, Neutral, GMSH, OFF, PCD (PointCloud), volumetric TIFF stacks, DICOM, SLC, MHD, 2D images PNG, JPEG. - Export meshes as ASCII or binary to VTK, STL, OBJ, PLY formats. @@ -39,7 +39,7 @@ for the all following functionalities: - Point-surface operations: find nearest points, determine if a point lies inside or outside a mesh. - Create primitive objects like: spheres, arrows, cubes, torus, ellipsoids... - Generate *glyphs* (associating a mesh to each vertex of a source mesh). - - Create animations easily by just defining the position of the displayed objects in the 3D scene. Add trailing lines to moving objects automatically. + - Create animations easily by just defining the position of the displayed objects in the 3D scene. Add trailing lines and shadows to moving objects is also supported. - Straightforward support for multiple *sync-ed* or independent renderers in the same window. - Registration (alignment) of meshes with different techniques. - Mesh smoothing with *Laplacian* and *WindowedSinc* algorithms. @@ -97,7 +97,7 @@ git clone https://github.com/marcomusy/vtkplotter.git cd vtkplotter/examples python tutorial.py ``` -**More than 160 working examples can be found in directories** _(scroll down to see the screenshots):_
+**More than 170 working examples can be found in directories** _(scroll down to see the screenshots):_
[**examples/basic**](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic)
[**examples/advanced**](https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced)
[**examples/volumetric**](https://github.com/marcomusy/vtkplotter/blob/master/examples/volumetric)
@@ -109,6 +109,8 @@ python tutorial.py |:--------:|:-----| | ![rabbit](https://user-images.githubusercontent.com/32848391/50738808-5816ad00-11d8-11e9-9854-c952be6fb941.jpg) | Apply a *Moving Least Squares* algorithm to obtain a smooth surface from a to a large cloud of scattered points in space ([script](https://github.com/marcomusy/vtkplotter/blob/master/examples/advanced/moving_least_squares2D.py))
`python advanced/moving_least_squares2D.py` | | | | +| ![airplanes](https://user-images.githubusercontent.com/32848391/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)| Create 3D animations in just a few lines of code.
Trails and shadows can be added to moving objects easily. ([script](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/airplanes.py))
`python simulations/airplanes.py`| +| | | | ![gyro](https://user-images.githubusercontent.com/32848391/39766016-85c1c1d6-52e3-11e8-8575-d167b7ce5217.gif) | Simulation of a gyroscope hanging from a spring ([script](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/gyroscope1.py))
`python simulations/gyroscope1.py`| | | | | ![qsine2](https://user-images.githubusercontent.com/32848391/47751431-06aae880-dc92-11e8-9fcf-6659123edbfa.gif) | Quantum-tunnelling effect integrating the Schroedinger equation with 4th order Runge-Kutta method. The animation shows the evolution of a particle in a box hitting a sinusoidal potential barrier. ([script](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/tunnelling2.py))
`python simulations/tunnelling2.py` | @@ -119,6 +121,7 @@ python tutorial.py
+ ## References Scientific publications using `vtkplotter` so far: diff --git a/examples/advanced/densifycloud.py b/examples/advanced/densifycloud.py new file mode 100644 index 00000000..9cc6b3b7 --- /dev/null +++ b/examples/advanced/densifycloud.py @@ -0,0 +1,20 @@ +"""Adds new points to an input point cloud. +The new points are created in such a way that +all points in any local neighborhood are +within a target distance of one another. +""" +from vtkplotter import * +import numpy as np +np.random.seed(3) + +npts = 200 # nr. of points +coords = np.random.rand(npts, 3) # range is [0, 1] +scals = np.abs(coords[:, 2]) # let the scalar be the z of point itself + +apts = Points(coords, r=9).addPointScalars(scals, name='scals') + +densecloud = densifyCloud(apts, .05, closestN=10, maxIter=1) +print(apts.N(), '->', densecloud.N()) + +ppp = Points(densecloud.coordinates()) +show(apts, densecloud, Text(__doc__), axes=8) \ No newline at end of file diff --git a/examples/basic/README.md b/examples/basic/README.md index ba427685..cc13d1f2 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -83,7 +83,9 @@ python example.py | | | | [![sliders3d](https://user-images.githubusercontent.com/32848391/52859555-4efcf200-312d-11e9-9290-6988c8295163.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/sliders3d.py)
`sliders3d.py` | Use a 3D sliders to modify interactively the position of a mesh.| | | | -| [![silhouette](https://user-images.githubusercontent.com/32848391/57179369-8e5df380-6e7d-11e9-99b4-3b1a120dd375.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/silhouette.py)
`silhouette.py` |Show the silhouette of a mesh as seen along a specified direction. | +| [![silhouette](https://user-images.githubusercontent.com/32848391/57179369-8e5df380-6e7d-11e9-99b4-3b1a120dd375.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/silhouette.py)
`silhouette.py` |Show the silhouette of a mesh as seen along a specified direction. | +| | | +| [![shadow](https://user-images.githubusercontent.com/32848391/57312574-1d714280-70ee-11e9-8741-04fc5386d692.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/shadow.py)
`shadow.py` | Draw the shadow of a set of meshes on one of the Cartesian planes. | | | | | [![texturecubes](https://user-images.githubusercontent.com/32848391/50738847-be033480-11d8-11e9-8087-8ba949dbf228.jpg)](https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/texturecubes.py)
`texturecubes.py` | Show a cube for each available texture name. Any jpg file can be used as texture.
Built in textures: `['aqua', 'blue', 'bricks', 'gold1', 'gold2', 'grass', 'greenfloor', 'greentiles', 'ivy', 'leather1', 'leather2', 'marble', 'masonry', 'metal1', 'metal2', 'metalfloor1', 'metalfloor2', 'paper', 'water', 'white1', 'white2', 'white3', 'white4', 'wood1', 'wood2', 'wood3', 'wood4', 'wood5', 'wood6', 'wood7', 'wood8', 'wood9']` | | | | diff --git a/examples/basic/shadow.py b/examples/basic/shadow.py new file mode 100644 index 00000000..ec06135b --- /dev/null +++ b/examples/basic/shadow.py @@ -0,0 +1,9 @@ +"""Make a shadow of 2 meshes on the wall.""" +from vtkplotter import * + +a = load(datadir + "spider.ply").normalize().rotateZ(-90) +s = Sphere(pos=[-0.4, 1.5, 0.3], r=0.3) + +shad = Shadow(a, s, direction="x").x(-4) + +show(a, s, shad, Text(__doc__), axes=1, viewup="z", bg="w") diff --git a/examples/basic/silhouette.py b/examples/basic/silhouette.py index f73ce67d..ceab9356 100644 --- a/examples/basic/silhouette.py +++ b/examples/basic/silhouette.py @@ -5,9 +5,9 @@ s = Hyperboloid().rotateX(20) -sx = s.clone().projectOnPlane('yz').addPos(-2,0,0).c('r') -sy = s.clone().projectOnPlane('zx').addPos(0,-2,0).c('g') -sz = s.clone().projectOnPlane('xy').addPos(0,0,-2).c('b') +sx = s.clone().projectOnPlane('x').c('r').x(-3) +sy = s.clone().projectOnPlane('y').c('g').y(-3) +sz = s.clone().projectOnPlane('z').c('b').z(-3) show(s, sx, sy, sz, sx.silhouette(), diff --git a/examples/other/dolfin/README.md b/examples/other/dolfin/README.md index 0d191575..749b5ab9 100644 --- a/examples/other/dolfin/README.md +++ b/examples/other/dolfin/README.md @@ -19,6 +19,8 @@ python example.py # on mac OSX try 'pythonw' instead | | | | [![submesh](https://user-images.githubusercontent.com/32848391/56675428-4e984e80-66bc-11e9-90b0-43dde7e4cc29.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/demo_submesh.py)
`demo_submesh.py` | How to extract matching sub meshes from a common mesh. | | | | +| [![scalemesh](https://user-images.githubusercontent.com/32848391/57393382-431c4b80-71c3-11e9-9a2c-8abb172f5468.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/scalemesh.py)
`scalemesh.py` | Scale a 2D mesh asymmetrically in one coordinate and elevate it along the z-axis with the solution. | +| | | | [![pi_estimate](https://user-images.githubusercontent.com/32848391/56675429-4e984e80-66bc-11e9-9217-a0652a8e74fe.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/pi_estimate.py)
`pi_estimate.py` | Estimate _pi_ by integrating a circle surface. Latex formulas can be added to the renderer directly. | | | | | [![tet_mesh](https://user-images.githubusercontent.com/32848391/53026244-d2d31900-3462-11e9-835a-1fa9d66d3dae.png)](https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/ex02_tetralize-mesh.py)
`ex02_tetralize-mesh.py` | Tetrahedral meshes generation with package _mshr_. | diff --git a/examples/other/dolfin/ex06_elasticity1.py b/examples/other/dolfin/ex06_elasticity1.py index cf3d771b..37c91931 100644 --- a/examples/other/dolfin/ex06_elasticity1.py +++ b/examples/other/dolfin/ex06_elasticity1.py @@ -42,7 +42,7 @@ # ps = point size, only mesh vertices are shown plot(u, mode='mesh', ps=10) -clear() # plot displacements as white arrows, lw controls the mesh visibility -plot(u, mode='arrows', color='w', alpha=0.5, cmap='gist_earth', lw=1) +plot(u, mode='arrows', add=True, + color='w', alpha=0.5, cmap='gist_earth', lw=1) diff --git a/examples/other/dolfin/run_all.sh b/examples/other/dolfin/run_all.sh index f39b31c6..ef65306a 100755 --- a/examples/other/dolfin/run_all.sh +++ b/examples/other/dolfin/run_all.sh @@ -17,6 +17,9 @@ python calc_surface_area.py echo Running markmesh.py python markmesh.py +echo Running scalemesh.py +python scalemesh.py + echo Running pi_estimate.py python pi_estimate.py diff --git a/examples/other/dolfin/scalemesh.py b/examples/other/dolfin/scalemesh.py new file mode 100644 index 00000000..c46c627f --- /dev/null +++ b/examples/other/dolfin/scalemesh.py @@ -0,0 +1,25 @@ +""" +Scale a mesh asymmetrically in one coordinate +""" +from dolfin import * +from mshr import * + +domain = Rectangle(Point(0.0, 0.0), Point(5.0, 0.01)) +mesh = generate_mesh(domain, 20) +V = FunctionSpace(mesh, "CG", 2) + +e = Expression("sin(2*pi*(x[0]*x[0]+x[1]*x[1]))", degree=2) +f = interpolate(e, V) + +#################################################### +from vtkplotter.dolfin import plot + +plt = plot(f, + warpZfactor=0.05, # add z elevation proportional to expression + style=1, + lw=0, + xtitle = 'y-coord is scaled by factor 100', + text=__doc__, + interactive=False) +plt.actors[0].scale([1,100,1]) # retrieve actor object and scale y +plt.show(interactive=True) # refresh scene \ No newline at end of file diff --git a/examples/other/dolfin/stokes.py b/examples/other/dolfin/stokes.py index e42b344e..4ad2222c 100644 --- a/examples/other/dolfin/stokes.py +++ b/examples/other/dolfin/stokes.py @@ -9,7 +9,7 @@ from vtkplotter.dolfin import plot, datadir, Latex t0 = time() -print("calculating... please wait...") +print("calculating... please wait... this takes time!") # Load mesh and subdomains mesh = Mesh(datadir+"dolfin_fine.xml") diff --git a/examples/other/self_org_maps2d.py b/examples/other/self_org_maps2d.py index 2a37aff9..14b00d5c 100644 --- a/examples/other/self_org_maps2d.py +++ b/examples/other/self_org_maps2d.py @@ -9,7 +9,7 @@ # ----------------------------------------------------------------------------- import numpy as np import scipy.spatial -from vtkplotter import Points, Sphere, Grid, ProgressBar, Text, show +from vtkplotter import * class SOM: @@ -52,7 +52,8 @@ def learn(self, n_epoch=10000, sigma=(0.25,0.01), lrate=(0.5,0.01)): for j in range(n): grd.setPoint(i*n+j, (x[i,j], y[i,j], z[i,j])) show(doc, pts, grd, axes=6, bg='w', azimuth=2, interactive=False) - + + interactive() return [self.codebook[:,i].reshape(n,n) for i in range(3)] # ------------------------------------------------------------------------------- @@ -69,4 +70,3 @@ def learn(self, n_epoch=10000, sigma=(0.25,0.01), lrate=(0.5,0.01)): som.samples = s.coordinates() som.learn(n_epoch=7000, sigma=(1, 0.01), lrate=(1, 0.01)) - show(interactive=True) diff --git a/examples/run_all.sh b/examples/run_all.sh index a5fa9116..83865bec 100755 --- a/examples/run_all.sh +++ b/examples/run_all.sh @@ -180,6 +180,9 @@ python basic/glyphs.py echo Running basic/glyphs_arrows.py python basic/glyphs_arrows.py +echo Running basic/shadow.py +python basic/shadow.py + #################################### advanced echo Running advanced/fatlimb.py @@ -248,50 +251,8 @@ python advanced/projectsphere.py echo Running advanced/convexHull.py python advanced/convexHull.py - -################################### simulations -echo Running simulations/aspring.py -python simulations/aspring.py - -echo Running simulations/cell_main.py -python simulations/cell_main.py - -echo Running simulations/brownian2D.py -python simulations/brownian2D.py - -echo Running simulations/gas.py -python simulations/gas.py - -echo Running simulations/gyroscope1.py -python simulations/gyroscope1.py - -echo Running simulations/gyroscope2.py -python simulations/gyroscope2.py - -echo Running simulations/multiple_pendulum.py -python simulations/multiple_pendulum.py - -echo Running simulations/hanoi3d.py -python simulations/hanoi3d.py - -echo Running simulations/pendulum.py -python simulations/pendulum.py - -echo Running simulations/wave_equation.py -python simulations/wave_equation.py - -echo Running simulations/turing.py -python simulations/turing.py - -echo Running simulations/particle_simulator.py -python simulations/particle_simulator.py - -echo Running simulations/doubleslit.py -python simulations/doubleslit.py - -echo Running simulations/tunnelling2.py -python simulations/tunnelling2.py - +echo Running advanced/densifycloud.py +python advanced/densifycloud.py ################################### volumetric echo Running volumetric/readVolumeAsIsoSurface.py @@ -384,18 +345,6 @@ echo Running other/export_x3d.py python other/export_x3d.py -##################################### not ran/ignored: -#basic/annotations.py -#basic/text_just.py -#basic/lights.py -#basic/ids.py -#basic/surfIntersect.py -#other/makeVideo.py -#other/spherical_harmonics2.py -#other/remesh_ACVD.py -#other/tf_learn_embryo.py -#other/self_org_maps2d.py - #################################### echo echo @@ -427,4 +376,22 @@ echo '----------------------------' echo vtkplotter -s "data/2??.vtk" vtkplotter -s data/2??.vtk +echo '\n----------------------------' +echo '----------------------------' +echo 'cd simulations; ./run_all.sh' +echo 'cd other/dolfin; ./run_all.sh' +echo '----------------------------' +echo '----------------------------' + +##################################### not ran/ignored: +#basic/annotations.py +#basic/text_just.py +#basic/lights.py +#basic/ids.py +#basic/surfIntersect.py +#other/makeVideo.py +#other/spherical_harmonics2.py +#other/remesh_ACVD.py +#other/tf_learn_embryo.py +#other/self_org_maps2d.py diff --git a/examples/simulations/README.md b/examples/simulations/README.md index 17f24380..68e4664e 100644 --- a/examples/simulations/README.md +++ b/examples/simulations/README.md @@ -29,6 +29,8 @@ python example.py # on mac OSX try 'pythonw' instead | | | | [![hanoi](https://user-images.githubusercontent.com/32848391/56989284-58c1bd80-6b92-11e9-8f82-1ce95813f846.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/hanoi3d.py)
`hanoi3d.py` | Solve the Tower of Hanoi puzzle (contributed by [G. Jacquenot](https://github.com/Gjacquenot)). | | | | +| [![airplanes](https://user-images.githubusercontent.com/32848391/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/airplanes.py)
`airplanes.py` | Two acrobatic planes casting shadows and leaving a trail. | +| | | | [![ruth](https://user-images.githubusercontent.com/32848391/50738891-db380300-11d8-11e9-84c2-0f55be7228f1.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/particle_simulator.py)
`particle_simulator.py` | Simulates interacting charged particles in 3D space. | | | | | [![tunneling1](https://vtkplotter.embl.es/gifs/tunnelling2.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/tunnelling1.py)
`tunnelling1.py` | Quantum Tunnelling effect using 4th order Runge-Kutta method with arbitrary potential shape.
The animation shows the evolution of a particle of relatively well defined momentum (hence undefined position) in a box hitting a potential barrier. The wave function is forced to be zero at the box walls. | @@ -40,3 +42,6 @@ python example.py # on mac OSX try 'pythonw' instead | [![wave](https://user-images.githubusercontent.com/32848391/50738956-7e891800-11d9-11e9-92d7-fa109b1b8551.gif)](https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/wave_equation.py)
`wave_equation.py` | Simulate a collection of discrete coupled oscillators. We use this as a model of a vibrating string and compare two methods of integration: Euler and Runge-Kutta4.| | | | | [![](https://user-images.githubusercontent.com/32848391/44957809-b2c09500-aed6-11e8-9dc5-c2e52b632f94.gif)](https://github.com/marcomusy/pianoplayer) | Finding and animating the optimal fingering to play a piano score with
[`pianoplayer 2.0`](https://github.com/marcomusy/pianoplayer)| + + + diff --git a/examples/simulations/airplanes.py b/examples/simulations/airplanes.py new file mode 100644 index 00000000..edb46333 --- /dev/null +++ b/examples/simulations/airplanes.py @@ -0,0 +1,20 @@ +# Draw the shadow and trailing lines of 2 planes. This is not really +# a simulation.. just a way to illustrate how to move objects around! +from vtkplotter import * + +world = Box([0,0,0], 30, 15, 8).wire() + +p1 = load(datadir+"cessna.vtk").c("green").addTrail(lw=2, n=50) +p2 = p1.clone().c("tomato").addTrail(lw=2, n=50) # make a copy + +# Setup the scene, creating the Plotter object is automatic +show(world, p1, p2, axes=1, bg="white", viewup="z", resetcam=0, interactive=0) + +for x in arange(0, 3.5, 0.01): + p1.pos(9*x-15, 2-x, sin( 3-x)).rotateX(0+x) # make up some fancy movement + p2.pos(8*x-15, -2+x, sin(-3+x)).rotateX(2-x) + + shad = Shadow(p1, p2, direction='z').z(-4) # fix z position of the shadow + show(world, p1, p2, shad) + +interactive() diff --git a/examples/simulations/multiple_pendulum.py b/examples/simulations/multiple_pendulum.py index 10255658..24dfa793 100644 --- a/examples/simulations/multiple_pendulum.py +++ b/examples/simulations/multiple_pendulum.py @@ -49,7 +49,7 @@ Dt2 = Dt / 2 # Midpoint time step DiaSq = (2 * R) ** 2 # Diameter of bob squared -printc("Press Esc to exit.", c="red") +printc("Press F1 to exit.", c="red") while True: bob_x_m = list(map((lambda x, dx: x + Dt2 * dx), bob_x, x_dot)) # midpoint variables diff --git a/examples/simulations/run_all.sh b/examples/simulations/run_all.sh new file mode 100755 index 00000000..6a41f90b --- /dev/null +++ b/examples/simulations/run_all.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# source run_all.sh +# +printf "\033c" + +echo ############################################# +echo Press F1 at anytime to skip example +echo ############################################# +echo +echo +echo + + +################################### simulations +echo Running aspring.py +python aspring.py + +echo Running cell_main.py +python cell_main.py + +echo Running brownian2D.py +python brownian2D.py + +echo Running gas.py +python gas.py + +echo Running gyroscope1.py +python gyroscope1.py + +echo Running gyroscope2.py +python gyroscope2.py + +echo Running multiple_pendulum.py +python multiple_pendulum.py + +echo Running hanoi3d.py +python hanoi3d.py + +echo Running airplanes.py +python airplanes.py + +echo Running pendulum.py +python pendulum.py + +echo Running wave_equation.py +python wave_equation.py + +echo Running turing.py +python turing.py + +echo Running particle_simulator.py +python particle_simulator.py + +echo Running doubleslit.py +python doubleslit.py + +echo Running tunnelling2.py +python tunnelling2.py diff --git a/examples/volumetric/interpolateVolume.py b/examples/volumetric/interpolateVolume.py index 9628a563..ca126ae2 100644 --- a/examples/volumetric/interpolateVolume.py +++ b/examples/volumetric/interpolateVolume.py @@ -1,24 +1,27 @@ """ Generate a voxel dataset (vtkImageData) by interpolating a scalar which is only known on a scattered set of points or mesh. -Available interpolation kernels are: shepard, gaussian, voronoi, linear +Available interpolation kernels are: shepard, gaussian, voronoi, linear. +The blue layer is the result of thresholding the volume +between 0.3 and 0.4 and assigning it the new value 0.9 """ # Author: Giovanni Dalmasso from vtkplotter import * import numpy as np - -npts = 60 # nr. of points of known scalar value +npts = 500 # nr. of points of known scalar value coords = np.random.rand(npts, 3) # range is [0, 1] scals = np.abs(coords[:, 2]) # let the scalar be the z of point itself -apts = Points(coords).addPointScalars(scals,'scals') +apts = Points(coords).addPointScalars(scals, name='scals') + +vol = interpolateToVolume(apts, kernel='shepard', radius=0.2, dims=(90,90,90)) +vol.c(["tomato", "g", "b"]).alpha([0.4, 0.8]) # set color/opacity transfer functions -vol = interpolateToVolume(apts, kernel='shepard', radius=0.3) -vol.c(["r", "g", "b"]).alpha([0.4, 0.8]) +vol.threshold(vmin=0.3, vmax=0.4, replaceWith=0.9) # replace voxel value in [vmin,vmax] printHistogram(vol, bins=25, c='b') -write(vol, 'cube.vti') +#write(vol, 'cube.vti') show(apts, vol, Text(__doc__), bg="white", axes=8) diff --git a/setup.py b/setup.py index f2b4adba..fb938d16 100644 --- a/setup.py +++ b/setup.py @@ -45,18 +45,21 @@ ############################################################## # # check examples # change version in vtkplotter/version.py + # cd ~/Projects/vtkplotter/ # pip install . # ( sudo -H pip install . ) + # cd examples # ./run_all.sh -# cd other/dolfin +# cd simulations +# cd ../other/dolfin # ./run_all.sh + # check vtkconvert: # vtkconvert data/290.vtk -to ply # check on python2 the same - # git status # git commit -a -m 'comment' # git push @@ -68,13 +71,6 @@ # twine upload dist/vtkplotter-?.?.?.tar.gz -r pypi # make release -# pip: -# # https://pypi.org/project/vtkplotter -# git: -# # check status at https://github.com/marcomusy/vtkplotter - -## to generate gif: ezgif.com - ## to generate documentation: # Install the dependencies in docs/requirements.txt # pip install -r docs/requirements.txt @@ -89,4 +85,5 @@ # cp -r build/html/* ~/Projects/StagingServer/var/www/html/vtkplotter.embl.es/ +## to generate gif: ezgif.com diff --git a/vtkplotter/actors.py b/vtkplotter/actors.py index efc3d587..49dccc11 100644 --- a/vtkplotter/actors.py +++ b/vtkplotter/actors.py @@ -16,6 +16,7 @@ ) __all__ = [ + 'Prop', 'Actor', 'Assembly', 'Image', @@ -29,23 +30,6 @@ ################################################# functions -def mergeActors(actors, tol=0): - """ - Build a new actor formed by the fusion of the polydatas of input objects. - Similar to Assembly, but in this case the input objects become a single mesh. - - .. hint:: |thinplate_grid.py|_ |value-iteration.py|_ - - |thinplate_grid| |value-iteration| - """ - polylns = vtk.vtkAppendPolyData() - for a in actors: - polylns.AddInputData(a.polydata()) - polylns.Update() - pd = polylns.GetOutput() - return Actor(pd) - - def collection(): """ Return the list of actor which have been created so far, @@ -196,10 +180,38 @@ def legosurface(image, vmin=None, vmax=None, cmap='afmhot_r'): return a +def mergeActors(*actors): + """ + Build a new actor formed by the fusion of the polygonal meshes of the input objects. + Similar to Assembly, but in this case the input objects become a single mesh. + + .. hint:: |thinplate_grid.py|_ |value-iteration.py|_ + + |thinplate_grid| |value-iteration| + """ + acts = [] + for a in utils.flatten(actors): + if isinstance(a, vtk.vtkAssembly): + acts += a.getActors() + else: + acts += [a] + if len(acts)==1: + return acts[0].clone() + polylns = vtk.vtkAppendPolyData() + for a in acts: + polylns.AddInputData(a.polydata()) + polylns.Update() + pd = polylns.GetOutput() + return Actor(pd) + + + ################################################# classes class Prop(object): - """Adds functionality to ``Actor``, ``Assembly``, - ``vtkImageData`` and ``vtkVolume`` objects.""" + """Adds functionality to ``Actor``, ``Assembly`` and ``Volume`` objects. + + .. warning:: Do not use this class to instance objects, use the above ones. + """ def __init__(self): @@ -479,7 +491,9 @@ def addTrail(self, offset=None, maxlength=None, n=25, c=None, alpha=None, lw=1): :param n: number of segments to control precision :param lw: line width of the trail - .. hint:: |trail| |trail.py|_ + .. hint:: See examples: |trail.py|_ |airplanes.py|_ + + |airplanes| """ if maxlength is None: maxlength = self.diagonalSize() * 20 @@ -1031,7 +1045,7 @@ def lineWidth(self, lw=None): if lw is not None: if lw == 0: self.GetProperty().EdgeVisibilityOff() - return + return self self.GetProperty().EdgeVisibilityOn() self.GetProperty().SetLineWidth(lw) else: @@ -1179,11 +1193,9 @@ def closestPoint(self, pt, N=1, radius=None, returnIds=False): :param float radius: if given, get all points within that radius. :param bool returnIds: return points IDs instead of point coordinates. - .. hint:: |fitplanes.py|_ + .. hint:: |align1.py|_ |fitplanes.py|_ |quadratic_morphing.py|_ - |align1| |align1.py|_ - - |quadratic_morphing| |quadratic_morphing.py|_ + |align1| |quadratic_morphing| .. note:: The appropriate kd-tree search locator is built on the fly and cached for speed. """ @@ -2244,13 +2256,13 @@ def polydata(self, transformed=True): tp.Update() return tp.GetOutput() - def coordinates(self, transformed=True, copy=True): + def coordinates(self, transformed=True, copy=False): """ Return the list of vertex coordinates of the input mesh. Same as `actor.getPoints()`. :param bool transformed: if `False` ignore any previous trasformation applied to the mesh. :param bool copy: if `False` return the reference to the points - so that they can be modified in place. + so that they can be modified in place, otherwise a copy is built. .. hint:: |align1.py|_ """ @@ -2512,18 +2524,25 @@ def intersectWithLine(self, p0, p1): pts.append(list(intersection)) return pts - - def projectOnPlane(self, plane='xy'): + + def projectOnPlane(self, direction='z'): """ + Project the mesh on one of the Cartesian planes. """ - if plane=='xy': - self.coordinates(copy=False)[:,2] = self.zbounds()[0] - elif plane=='yz': - self.coordinates(copy=False)[:,0] = self.xbounds()[0] - elif plane=='zx': - self.coordinates(copy=False)[:,1] = self.ybounds()[0] - self.alpha(0.1) - self.polydata(False).GetPoints().Modified() + coords = self.coordinates(transformed=True) + if 'z' == direction: + coords[:,2] = self.GetOrigin()[2] + self.z(self.zbounds()[0]) + elif 'x' == direction: + coords[:,0] = self.GetOrigin()[0] + self.x(self.xbounds()[0]) + elif 'y' == direction: + coords[:,1] = self.GetOrigin()[1] + self.y(self.ybounds()[0]) + else: + colors.printc("~times Error in projectOnPlane(): unknown direction", direction, c=1) + exit() + self.alpha(0.1).polydata(False).GetPoints().Modified() return self @@ -2782,6 +2801,8 @@ def imagedata(self): def threshold(self, vmin=None, vmax=None, replaceWith=None): """ Binary or continuous volume thresholding. + Find the voxels that contain the value below/above or inbetween + [vmin, vmax] and replaces it with the provided value. """ th = vtk.vtkImageThreshold() th.SetInputData(self.image) @@ -2789,13 +2810,15 @@ def threshold(self, vmin=None, vmax=None, replaceWith=None): if vmin is not None and vmax is not None: th.ThresholdBetween(vmin, vmax) elif vmin is not None: - th.ThresholdByUpper(vmin) + th.ThresholdByLower(vmin) elif vmax is not None: - th.ThresholdByLower(vmax) + th.ThresholdByUpper(vmax) - if replaceWith: - th.ReplaceOutOn() - th.SetOutValue(replaceWith) + if replaceWith is None: + colors.printc("Error in threshold(); you must provide replaceWith", c=1) + exit() + + th.SetInValue(replaceWith) th.Update() self.image = th.GetOutput() @@ -2842,7 +2865,7 @@ def crop(self, top=None, bottom=None, return self def resize(self, *newdims): - """Increase or reduce the number of voxels of a Volume with interpolation""" + """Increase or reduce the number of voxels of a Volume with interpolation.""" old_dims = np.array(self.imagedata().GetDimensions()) old_spac = np.array(self.imagedata().GetSpacing()) rsz = vtk.vtkImageResize() @@ -2856,7 +2879,19 @@ def resize(self, *newdims): self.GetMapper().SetInputData(self.image) self.GetMapper().Modified() return self + + + def normalize(self): + """Normalize that scalar components for each point.""" + norm = vtk.vtkImageNormalize() + norm.SetInputData(self.imagedata()) + norm.Update() + self.image = norm.GetOutput() + self.GetMapper().SetInputData(self.image) + self.GetMapper().Modified() + return self + def scaleVoxelsScalar(self, scale=1): """Scale the voxel content""" rsl = vtk.vtkImageReslice() diff --git a/vtkplotter/analysis.py b/vtkplotter/analysis.py index 6020f32f..cdd2649e 100644 --- a/vtkplotter/analysis.py +++ b/vtkplotter/analysis.py @@ -2,7 +2,7 @@ import vtkplotter.docs as docs import vtk import numpy as np -from vtk.util.numpy_support import numpy_to_vtk +from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy import vtkplotter.utils as vu import vtkplotter.colors as vc @@ -64,6 +64,7 @@ "interpolateToVolume", "interpolateToStructuredGrid", "streamLines", + "densifyCloud", ] @@ -2248,5 +2249,58 @@ def readPoints(): return sta +def densifyCloud(actor, targetDistance, closestN=6, radius=0, maxIter=None, maxN=None): + """Adds new points to an input point cloud. + The new points are created in such a way that all points in any local neighborhood are + within a target distance of one another. + + The algorithm works as follows. For each input point, the distance to all points + in its neighborhood is computed. If any of its neighbors is further than the target distance, + the edge connecting the point and its neighbor is bisected and a new point is inserted at the + bisection point. A single pass is completed once all the input points are visited. + Then the process repeats to the limit of the maximum number of iterations. + + .. note:: Points will be created in an iterative fashion until all points in their + local neighborhood are the target distance apart or less. + Note that the process may terminate early due to the limit on the + maximum number of iterations. By default the target distance is set to 0.5. + Note that the TargetDistance should be less than the Radius or nothing will change on output. + + .. warning:: This class can generate a lot of points very quickly. + The maximum number of iterations is by default set to =1.0 for this reason. + Increase the number of iterations very carefully. + Also, `maxN` can be set to limit the explosion of points. + It is also recommended that a N closest neighborhood is used. + """ + src = vtk.vtkProgrammableSource() + def readPoints(): + output = src.GetPolyDataOutput() + points = vtk.vtkPoints() + pts = actor.coordinates() + for p in pts: + x, y, z = p + points.InsertNextPoint(x, y, z) + output.SetPoints(points) + src.SetExecuteMethod(readPoints) + + dens = vtk.vtkDensifyPointCloudFilter() + dens.SetInputConnection(src.GetOutputPort()) + dens.InterpolateAttributeDataOn() + dens.SetTargetDistance(targetDistance) + if maxIter: dens.SetMaximumNumberOfIterations(maxIter) + if maxN: dens.SetMaximumNumberOfPoints(maxN) + + if radius: + dens.SetNeighborhoodTypeToRadius() + dens.SetRadius(radius) + elif closestN: + dens.SetNeighborhoodTypeToNClosest() + dens.SetNumberOfClosestPoints(closestN) + else: + vc.printc("Error in densifyCloud: set either radius or closestN", c=1) + exit() + dens.Update() + pts = vtk_to_numpy(dens.GetOutput().GetPoints().GetData()) + return vs.Points(pts, c=None).pointSize(3) diff --git a/vtkplotter/docs.py b/vtkplotter/docs.py index 82c70dce..23f1297e 100644 --- a/vtkplotter/docs.py +++ b/vtkplotter/docs.py @@ -59,8 +59,9 @@ def tips(): msg += "| j to toggle interaction to Joystick Mode |\n" msg += "| C to print current camera info |\n" msg += "| S to save a screenshot |\n" - msg += "| q/e to continue/close the rendering window |\n" - msg += "| Esc to exit program |\n" + msg += "| q to return control to python script |\n" + msg += "| Esc to close the rendering window |\n" + #msg += "| F1 to interrupt execution and exit python |\n" msg += "|------ |\n" msg += "|Mouse: Left-click to rotate scene / pick actors |\n" msg += "| Middle-click to pan scene |\n" @@ -975,7 +976,19 @@ def tips(): :target: silhouette.py_ :alt: silhouette.py +.. |shadow.py| replace:: shadow.py +.. _shadow.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/shadow.py +.. |shadow| image:: https://user-images.githubusercontent.com/32848391/57312574-1d714280-70ee-11e9-8741-04fc5386d692.png + :width: 350 px + :target: shadow.py_ + :alt: shadow.py +.. |airplanes.py| replace:: airplanes.py +.. _airplanes.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/simulations/.py +.. |airplanes| image:: https://user-images.githubusercontent.com/32848391/57341963-b8910900-713c-11e9-898a-84b6d3712bce.gif + :width: 350 px + :target: airplanes.py_ + :alt: airplanes.py """ diff --git a/vtkplotter/dolfin.py b/vtkplotter/dolfin.py index b97ccba3..be964b5e 100644 --- a/vtkplotter/dolfin.py +++ b/vtkplotter/dolfin.py @@ -25,7 +25,7 @@ import vtkplotter.shapes as shapes from vtkplotter.shapes import Text, Latex -from vtkplotter.plotter import show, clear, Plotter, plotMatrix, closeWindow +from vtkplotter.plotter import show, clear, Plotter, plotMatrix, closeWindow, interactive # NB: dolfin does NOT need to be imported at module level @@ -175,12 +175,6 @@ def _inputsort(obj): return (mesh, u) -def interactive(): - """Go back to the rendering window interaction mode.""" - if settings.plotter_instance: - settings.plotter_instance.interactor.Start() - return settings.plotter_instance - def plot(*inputobj, **options): """ @@ -201,7 +195,7 @@ def plot(*inputobj, **options): - `lines`, mesh displacements are plotted as scaled lines. - `tensors`, to be implemented - :param bool add: add the input objects without clearing the previous ones + :param bool add: add the input objects without clearing the already plotted ones :param float density: show only a subset of lines or arrows [0-1] :param bool wire[frame]: visualize mesh as wireframe [False] :param c[olor]: set mesh color [None] @@ -280,15 +274,12 @@ def plot(*inputobj, **options): """ if len(inputobj) == 0: - if settings.plotter_instance: - settings.plotter_instance.interactor.Start() - return settings.plotter_instance + return interactive() mesh, u = _inputsort(inputobj) mode = options.pop("mode", 'mesh') ttime = options.pop("z", None) - #density = options.pop("density", None) #todo add = options.pop("add", False) @@ -751,15 +742,13 @@ def MeshArrows(*inputobj, **options): #ub_test.rename('ub_test','ub_test') #u_test.rename('u_test','u_test') #us.rename('us','us') -#File("output/u.pvd") << u -#File("output/ub_test.pvd") << ub_test -#File("output/u_test.pvd") << u_test -#File("output/us.pvd") << us​ -#import os + + + # #import dolfin #import dolfin.cpp as cpp diff --git a/vtkplotter/plotter.py b/vtkplotter/plotter.py index 40afd24e..a66d1848 100644 --- a/vtkplotter/plotter.py +++ b/vtkplotter/plotter.py @@ -20,7 +20,7 @@ + docs._defs ) -__all__ = ["show", "clear", "Plotter", "plotMatrix", "closeWindow"] +__all__ = ["show", "clear", "Plotter", "plotMatrix", "closeWindow", "interactive"] ######################################################################## @@ -188,6 +188,14 @@ def show(*actors, **options return vp +def interactive(): + """Go back to the rendering window interaction mode.""" + if settings.plotter_instance: + if hasattr(settings.plotter_instance, 'interactor'): + settings.plotter_instance.interactor.Start() + return settings.plotter_instance + + def clear(actor=()): """ Clear specific actor or list of actors from the current rendering window. @@ -517,8 +525,12 @@ def _allowInteraction(): if timenow - self._update_win_clock > 0.1: self._update_win_clock = timenow self._update_observer = self.interactor.CreateRepeatingTimer(1) - self.interactor.Start() - self.interactor.DestroyTimer(self._update_observer) + if hasattr(self, 'interactor') and self.interactor: + self.interactor.Start() + + if hasattr(self, 'interactor') and self.interactor: + # twice otherwise it crashes when pressing Esc (??) + self.interactor.DestroyTimer(self._update_observer) self.allowInteraction = _allowInteraction @@ -1237,7 +1249,7 @@ def scan(wannabeacts): elif interactorStyle == 6 or interactorStyle == "RubberBandZoom": self.interactor.SetInteractorStyle(vtk.vtkInteractorStyleRubberBandZoom()) - if self.interactor and self.interactive: + if hasattr(self, 'interactor') and self.interactor and self.interactive: self.interactor.Start() if rate: diff --git a/vtkplotter/settings.py b/vtkplotter/settings.py index 9467c05f..aca56a82 100644 --- a/vtkplotter/settings.py +++ b/vtkplotter/settings.py @@ -5,11 +5,11 @@ import os __all__ = [ - 'computeNormals', - 'interactorStyle', - 'allowInteraction', - 'usingQt', - 'renderPointsAsSpheres', +# 'computeNormals', +# 'interactorStyle', +# 'allowInteraction', +# 'usingQt', +# 'renderPointsAsSpheres', 'textures', 'textures_path', 'datadir', @@ -43,6 +43,10 @@ ztitle = 'z' +# scale magnification of the screenshot +screeshotScale = 1 +screenshotTransparentBackground = False + ##################### _cdir = os.path.dirname(__file__) diff --git a/vtkplotter/shapes.py b/vtkplotter/shapes.py index 267f46ce..2af7e9fb 100644 --- a/vtkplotter/shapes.py +++ b/vtkplotter/shapes.py @@ -5,7 +5,7 @@ from vtk.util.numpy_support import numpy_to_vtk import vtkplotter.utils as utils import vtkplotter.colors as colors -from vtkplotter.actors import Actor, Assembly +from vtkplotter.actors import Actor, Assembly, mergeActors import vtkplotter.docs as docs __doc__ = ( @@ -46,6 +46,7 @@ "Text", "Latex", "Glyph", + "Shadow", ] @@ -1556,3 +1557,29 @@ def build_img_plt(formula, tfile): return None +def Shadow(*actors, **options): + """ + Generate a shadow out of a number of ``Actor`` or ``Assembly`` (group of actors), + on one of the three Cartesian planes. + The output is a new ``Actor`` representing the shadow. + By default the actor is placed on the bottom/back wall of the bounding box. + + :param str direction: identifies the plane to cast the shadow to ['x', 'y' or 'z']. + + .. hint:: |shadow| |shadow.py|_ + + |airplanes| |airplanes.py|_ + """ + direction = options.pop("direction", 'z') + + am = mergeActors(actors) + + shad = am.projectOnPlane(direction) + shad.c([0.5,0.5,0.5]).alpha(1).wireframe(False) + shad.flat().backFaceCulling() + shad.GetProperty().SetSpecular(0) + shad.GetProperty().SetDiffuse(0) + shad.GetProperty().SetAmbient(1) + return shad + + diff --git a/vtkplotter/version.py b/vtkplotter/version.py index 9d08ec03..7bc2f018 100644 --- a/vtkplotter/version.py +++ b/vtkplotter/version.py @@ -1 +1 @@ -_version='2019.2.0' +_version='2019.2.1' diff --git a/vtkplotter/vtkio.py b/vtkplotter/vtkio.py index 20189717..805d0ba0 100644 --- a/vtkplotter/vtkio.py +++ b/vtkplotter/vtkio.py @@ -849,6 +849,10 @@ def screenshot(filename="screenshot.png"): w2if = vtk.vtkWindowToImageFilter() w2if.ShouldRerenderOff() w2if.SetInput(settings.plotter_instance.window) + s = settings.screeshotScale + w2if.SetScale(s, s) + if settings.screenshotTransparentBackground: + w2if.SetInputBufferTypeToRGBA() w2if.ReadFrontBufferOff() # read from the back buffer w2if.Update() pngwriter = vtk.vtkPNGWriter() @@ -1241,26 +1245,20 @@ def _keypress(iren, event): vp = settings.plotter_instance key = iren.GetKeySym() - # print('Pressed key:', key, event) + #print('Pressed key:', key) if key in ["q", "Q", "space", "Return"]: iren.ExitCallback() return - elif key == "e": - if vp.verbose: - print("closing window...") - iren.GetRenderWindow().Finalize() - iren.TerminateApp() - return - elif key == "Escape": - print() sys.stdout.flush() - iren.TerminateApp() - iren.GetRenderWindow().Finalize() - iren.TerminateApp() - sys.exit(0) + settings.plotter_instance.closeWindow() + + elif key in ["F1", "Pause"]: + sys.stdout.flush() + settings.plotter_instance.closeWindow() + exit() elif key == "m": if vp.clickedActor in vp.getActors():