From 52fe0fec2c24931f37bf3e775e691f6c7a4ca0ec Mon Sep 17 00:00:00 2001 From: marcomusy Date: Thu, 16 May 2019 20:33:40 +0200 Subject: [PATCH] 3.0 --- README.md | 3 +- bin/vtkplotter | 15 +- examples/advanced/fatlimb.py | 2 +- examples/advanced/mesh_smoothers.py | 2 +- examples/advanced/quadratic_morphing.py | 3 +- examples/basic/buildmesh.py | 18 + examples/basic/buildpolydata.py | 17 - examples/basic/cutter.py | 10 +- examples/basic/largestregion.py | 2 +- examples/basic/manyspheres.py | 4 +- examples/basic/multiblocks.py | 8 +- examples/other/dolfin/elastodynamics.py | 2 +- examples/other/dolfin/ex06_elasticity2.py | 4 +- examples/other/export_x3d.py | 2 +- examples/other/icon.py | 4 +- examples/other/inset.py | 3 +- examples/other/spherical_harmonics1.py | 122 ++- examples/other/tf_learn_embryo.py | 12 +- examples/run_all.sh | 10 +- examples/volumetric/legosurface.py | 6 +- examples/volumetric/lowpassfilter.py | 2 +- examples/volumetric/probeLine.py | 4 +- examples/volumetric/probePlane.py | 4 +- examples/volumetric/probePoints.py | 2 +- examples/volumetric/readVolume.py | 5 +- examples/volumetric/readVolumeAsIsoSurface.py | 25 +- examples/volumetric/volumeOperations.py | 4 +- setup.py | 1 + vtkplotter/__init__.py | 15 + vtkplotter/actors.py | 213 +++-- vtkplotter/addons.py | 85 +- vtkplotter/analysis.py | 4 +- vtkplotter/data/fourobj.wrl | 82 -- vtkplotter/docs.py | 32 +- vtkplotter/dolfin.py | 17 +- vtkplotter/plotter.py | 68 +- vtkplotter/settings.py | 38 +- vtkplotter/shapes.py | 20 +- vtkplotter/utils.py | 151 ++- vtkplotter/version.py | 2 +- vtkplotter/vtkio.py | 870 +++++++----------- 41 files changed, 1024 insertions(+), 869 deletions(-) create mode 100644 examples/basic/buildmesh.py delete mode 100644 examples/basic/buildpolydata.py delete mode 100644 vtkplotter/data/fourobj.wrl diff --git a/README.md b/README.md index b37dfa01..2b526aa2 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,7 @@ for all the following functionalities: - Find the intersection of a mesh with a line (or with another mesh). - Analysis of *Point Clouds*: - *Moving Least Squares* smoothing of 2D, 3D and 4D clouds - - Fit lines, planes and spheres in space - - Perform PCA (Principal Component Analysis) on point coordinates + - Fit lines, planes and spheres and ellipses in space - Identify outliers in a distribution of points - Decimate a cloud to a uniform distribution. - Basic histogramming and function plotting in 1D and 2D. diff --git a/bin/vtkplotter b/bin/vtkplotter index c78c4f98..c4dc58eb 100755 --- a/bin/vtkplotter +++ b/bin/vtkplotter @@ -358,7 +358,8 @@ elif args.slicer: ######################################################################## # normal mode for single VOXEL file with Isosurface Slider elif nfiles == 1 and (".slc" in args.files[0] or ".vti" in args.files[0] or ".tif" in args.files[0]): - from vtkplotter import loadImageData, Actor, legosurface, printHistogram + from vtkplotter import Actor, legosurface + from vtkplotter.vtkio import loadImageData image = loadImageData(args.files[0]) scrange = image.GetScalarRange() @@ -392,7 +393,7 @@ elif nfiles == 1 and (".slc" in args.files[0] or ".vti" in args.files[0] or ".ti cf.SetValue(0, threshold) cf.Update() - act = Actor(cf.GetOutput(), ic, alpha=abs(args.alpha), wire=args.wireframe) + act = Actor(cf.GetOutput(), ic, alpha=abs(args.alpha)).wire(args.wireframe) act.phong() ############################## threshold slider @@ -403,7 +404,7 @@ elif nfiles == 1 and (".slc" in args.files[0] or ".vti" in args.files[0] or ".ti cf.SetValue(0, widget.GetRepresentation().GetValue()) cf.Update() poly = cf.GetOutput() - a = Actor(poly, ic, alpha=act.alpha(), wire=args.wireframe) + a = Actor(poly, ic, alpha=act.alpha()).wire(args.wireframe) a.phong() vp.actors = [] vp.renderer.RemoveActor(vp.getActors()[0]) @@ -461,7 +462,9 @@ elif (not args.scrolling_mode) or nfiles == 1: if args.show_scalars: ic = None - actor = vp.load(f, c=ic, alpha=alpha, wire=wire) + actor = vp.load(f, c=ic, alpha=alpha) + if hasattr(actor, 'wire'): + actor.wire(wire) if leg: actor.legend(os.path.basename(f)) @@ -509,7 +512,9 @@ else: if args.show_scalars: ic = None - actor = vp.load(f, c=ic, alpha=alpha, wire=wire) + actor = vp.load(f, c=ic, alpha=alpha) + if hasattr(actor, 'wire'): + actor.wire(wire) actor.legend(leg) actors.append(actor) if args.point_size > 0: diff --git a/examples/advanced/fatlimb.py b/examples/advanced/fatlimb.py index 894aacc3..a9ade511 100644 --- a/examples/advanced/fatlimb.py +++ b/examples/advanced/fatlimb.py @@ -12,7 +12,7 @@ vp = Plotter(axes=0, verbose=0, bg="w") -s = vp.load(datadir+"290.vtk", c="red", bc="plum") +s = vp.load(datadir+"290.vtk", c="red") c = s.centerOfMass() vp.add(Point(c)) diff --git a/examples/advanced/mesh_smoothers.py b/examples/advanced/mesh_smoothers.py index d2b00b8d..b7fdc674 100644 --- a/examples/advanced/mesh_smoothers.py +++ b/examples/advanced/mesh_smoothers.py @@ -10,7 +10,7 @@ vp = Plotter(shape=(1, 3), axes=4) # Load a mesh and show it -a0 = vp.load(datadir+"embryo.tif", c="v") +a0 = vp.load(datadir+"embryo.tif", threshold=True, c="v") vp.show(a0, at=0) # Adjust mesh using Laplacian smoothing diff --git a/examples/advanced/quadratic_morphing.py b/examples/advanced/quadratic_morphing.py index 5b8505aa..35a5e807 100644 --- a/examples/advanced/quadratic_morphing.py +++ b/examples/advanced/quadratic_morphing.py @@ -127,7 +127,8 @@ def draw_shapes(self): mr = Morpher() mr.source = vp.load(datadir+"270.vtk", c="g", alpha=0.4) - mr.target = vp.load(datadir+"290.vtk", c="b", alpha=0.3, wire=1) + mr.target = vp.load(datadir+"290.vtk", c="b", alpha=0.3) + mr.target.wireframe() mr.allowScaling = True mr.bound = 0.4 # limits the parameter value diff --git a/examples/basic/buildmesh.py b/examples/basic/buildmesh.py new file mode 100644 index 00000000..686eaa6a --- /dev/null +++ b/examples/basic/buildmesh.py @@ -0,0 +1,18 @@ +""" +Manually build a mesh. +""" +from vtkplotter import Actor, Text, show + +verts = [(50,50,50), (70,40,50), (50,40,80), (80,70,50)] +faces = [(0,1,2), (2,1,3), (1,0,3)] +# (the first triangle face is formed by vertex 0, 1 and 2) + +a = Actor([verts, faces]) +a.backColor('violet').lineColor('black').lineWidth(1) + +# the way vertices are assembled into polygons can be retrieved +# in two different formats: +print('getCells() format is :', a.getCells()) +print('getConnectivity() format is:', a.getConnectivity()) + +show(a, Text(__doc__), viewup='z', axes=8) \ No newline at end of file diff --git a/examples/basic/buildpolydata.py b/examples/basic/buildpolydata.py deleted file mode 100644 index 075e31f9..00000000 --- a/examples/basic/buildpolydata.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Load a mesh, extract the vertex coordinates, -and build a new vtkPolyData object. -Faces (vertex connectivity) can be specified too. - -(press p to increase point size) -""" -from vtkplotter import * - - -pts = load(datadir+"bunny.obj").subdivide(2).coordinates() - -poly = buildPolyData(pts, faces=None) # vtkPolyData made of just vertices - -doc = Text(__doc__) - -show(poly, doc) diff --git a/examples/basic/cutter.py b/examples/basic/cutter.py index b26d91e1..872106ee 100644 --- a/examples/basic/cutter.py +++ b/examples/basic/cutter.py @@ -1,15 +1,15 @@ """ -Load a vtkImageData (tif stack) and convert on the the fly to an isosurface. -Invoke a tool to cut off parts of a mesh -Press X to save the mesh or to add new cut planes +Load a Volume (atif stack). +Invoke a tool to cut off parts of it. """ print(__doc__) from vtkplotter import Plotter, datadir -vp = Plotter(axes=4, bg="w") +vp = Plotter(axes=4) -act = vp.load(datadir+"embryo.tif", c="blue") +act = vp.load(datadir+"embryo.tif") vp.addCutterTool(act) +vp.show() diff --git a/examples/basic/largestregion.py b/examples/basic/largestregion.py index a93af9d2..02904536 100644 --- a/examples/basic/largestregion.py +++ b/examples/basic/largestregion.py @@ -5,7 +5,7 @@ from vtkplotter import * -act1 = load(datadir+"embryo.slc", c="y") +act1 = load(datadir+"embryo.slc", threshold=True, c="y") printc("area1 =", act1.area(), c="y") act2 = extractLargestRegion(act1).color("b") diff --git a/examples/basic/manyspheres.py b/examples/basic/manyspheres.py index 67536578..67c645f1 100644 --- a/examples/basic/manyspheres.py +++ b/examples/basic/manyspheres.py @@ -16,8 +16,8 @@ # all have same radius but different colors: s0 = Spheres(pts, c=cols, r=0.1, res=3) # res= theta-phi resolution -# all have same color (texture) but different radius along y: -s1 = Spheres(pts, r=rads, c="lb", res=8).texture('marble') +# all have same color but different radius along y: +s1 = Spheres(pts, r=rads, c="lb", res=8) print("..rendering spheres:", N * 2) show(s0, at=0, N=2, axes=2, viewup=(-0.7, 0.7, 0)) diff --git a/examples/basic/multiblocks.py b/examples/basic/multiblocks.py index 1b685951..f0d112e9 100644 --- a/examples/basic/multiblocks.py +++ b/examples/basic/multiblocks.py @@ -7,7 +7,7 @@ scals = cube.coordinates()[:,1] poly = cube.addPointScalars(scals, 'scalsname').polydata() -img = loadImageData(datadir+'vase.vti') +img = load(datadir+'vase.vti').imagedata() filename = "multiblock.vtm" @@ -16,8 +16,8 @@ "and corresponding directory", c='g') # load back from file into a list of actors/volumes -acts = loadMultiBlockData(filename) +mbacts = load(filename) # loads and unpacks a MultiBlockData obj -show(acts, Text('loadMultiBlockData("file.vtm")', c='k'), bg='w') +show(mbacts, Text('load("file.vtm") #MultiBlockData', c='k'), bg='w') -show(mblock, Text('show(multiblock)'), newPlotter=True, pos=(800,0)) +show(mblock, Text('show(multiblock)', c='w'), newPlotter=True, pos=(800,0)) diff --git a/examples/other/dolfin/elastodynamics.py b/examples/other/dolfin/elastodynamics.py index c6bd03c7..d5a658e3 100644 --- a/examples/other/dolfin/elastodynamics.py +++ b/examples/other/dolfin/elastodynamics.py @@ -224,7 +224,7 @@ def local_project(v, V, u=None): energies[i+1, :] = np.array([E_elas, E_kin, E_damp, E_tot]) plot(u, box, tex, - mode='warped mesh', + mode='displace', style='matplotlib', axes=0, # no axes scalarbar=False, diff --git a/examples/other/dolfin/ex06_elasticity2.py b/examples/other/dolfin/ex06_elasticity2.py index 65a7520b..d88fbbfd 100644 --- a/examples/other/dolfin/ex06_elasticity2.py +++ b/examples/other/dolfin/ex06_elasticity2.py @@ -44,7 +44,7 @@ ~pin alpha=0.2, depthpeeling=True ~pin mode='mesh warp lines', lw=.05""", c='blue') -plot(u, mode='my warped mesh please!!', azimuth=45) -exportWindow('ex06_elasticity2.x3d') +plot(u, mode='my displaced mesh please!!', azimuth=45) +#exportWindow('ex06_elasticity2.x3d') printc('~smile Thanks for using vtkplotter!', c='green') diff --git a/examples/other/export_x3d.py b/examples/other/export_x3d.py index b7e71089..e44fcc3d 100644 --- a/examples/other/export_x3d.py +++ b/examples/other/export_x3d.py @@ -3,7 +3,7 @@ x3dom and vtkplotter""" from vtkplotter import * -e = load(datadir+'embryo.tif').decimate(0.5) +e = load(datadir+'embryo.tif', threshold=True).decimate(0.5) ec = e.coordinates() e.pointColors(ec[:,1]) # add dummy colors along y diff --git a/examples/other/icon.py b/examples/other/icon.py index d9f4aee7..d66e768a 100644 --- a/examples/other/icon.py +++ b/examples/other/icon.py @@ -3,13 +3,13 @@ and place it in one of the 4 corners within the same renderer. """ -from vtkplotter import Plotter, load, Text, datadir +from vtkplotter import * vp = Plotter(axes=5, bg="white") # axes type 5 builds an annotated orientation cube -vp.load(datadir+"270.vtk", c="blue", bc="v") +vp.load(datadir+'porsche.ply').lighting('metallic') vp.show(interactive=0) diff --git a/examples/other/inset.py b/examples/other/inset.py index f21ed4be..356c93d9 100644 --- a/examples/other/inset.py +++ b/examples/other/inset.py @@ -6,7 +6,8 @@ vp = Plotter(axes=1, bg="white") -e = load(datadir+"embryo.tif").normalize().c("gold") +e = load(datadir+"embryo.tif", threshold=True) # automatic isosurfacing +e.normalize().c("gold") msg = Text(__doc__) vp.show(e, msg, viewup='z', interactive=0) diff --git a/examples/other/spherical_harmonics1.py b/examples/other/spherical_harmonics1.py index f8df94ca..f222b979 100644 --- a/examples/other/spherical_harmonics1.py +++ b/examples/other/spherical_harmonics1.py @@ -1,8 +1,3 @@ -from __future__ import division, print_function - -import numpy as np -from vtkplotter import Plotter, Points, cos, datadir, mag, sin - """ Example on how to use the intersectWithLine() method: intersect an actor with lines from the origin @@ -14,67 +9,116 @@ and then truncate the expansion to a specific lmax and reconstruct the projected points in red """ - +from __future__ import division, print_function +import numpy as np +from scipy.interpolate import griddata +from vtkplotter import show, load, Points, datadir, mag, spher2cart print(__doc__) -########################################################## -N = 100 # number of sample points on the unit sphere -lmax = 15 # maximum degree of the expansion -rmax = 2.0 # line length -rbias = 0.5 # subtract a constant average value +############################################################# +N = 30 # number of grid intervals on the unit sphere +lmax = 15 # maximum degree of the expansion +rmax = 2.0 # line length x0 = [0, 0, 0] # set object at this position -########################################################## +############################################################# + +shape = load(datadir + "pumpkin.vtk").normalize().pos(x0).lineWidth(.1) -vp = Plotter(shape=[1, 2], verbose=0, axes=0) -shape = vp.load(datadir + "icosahedron.vtk").normalize().pos(x0).lineWidth(2) +show(shape, at=0, N=2, bg='w') + +############################################################ +# cast rays from the center and find intersections agrid, pts = [], [] for th in np.linspace(0, np.pi, N, endpoint=True): lats = [] - for ph in np.linspace(0, 2 * np.pi, N, endpoint=True): - p = np.array([sin(th) * cos(ph), sin(th) * sin(ph), cos(th)]) * rmax - intersections = shape.intersectWithLine([0, 0, 0], p) # <-- + for ph in np.linspace(0, 2*np.pi, N, endpoint=True): + p = spher2cart(rmax, th, ph) + intersections = shape.intersectWithLine([0, 0, 0], p) # <-------------- if len(intersections): value = mag(intersections[0]) - lats.append(value - rbias) + lats.append(value) pts.append(intersections[0]) else: - lats.append(rmax - rbias) + lats.append(rmax) pts.append(p) agrid.append(lats) agrid = np.array(agrid) -vp.add(Points(pts, c="b", r=2)) -vp.show(at=0) ############################################################ -try: - import pyshtools -except ModuleNotFoundError: - print("Please install pyshtools to run this example") - print("Follow instructions at https://shtools.oca.eu/shtools") - exit(0) +# Please install pyshtools to continue this example +# Follow instructions at https://shtools.oca.eu/shtools +import pyshtools grid = pyshtools.SHGrid.from_array(agrid) -grid.plot() # plots the scalars in a 2d plots latitudes vs longitudes - clm = grid.expand() -clm.plot_spectrum2d() # plot the value of the sph harm. coefficients - grid_reco = clm.expand(lmax=lmax) # cut "high frequency" components -grid_reco.plot() + +#grid.plot() # plots the scalars in a 2d plots latitudes vs longitudes +#clm.plot_spectrum2d() # plot the value of the sph harm. coefficients +#grid_reco.plot() + agrid_reco = grid_reco.to_array() -pts = [] +pts1 = [] +ll = [] for i, longs in enumerate(agrid_reco): ilat = grid_reco.lats()[i] - for j, value in enumerate(longs): + for j, r in enumerate(longs): ilong = grid_reco.lons()[j] th = np.deg2rad(90 - ilat) ph = np.deg2rad(ilong) - r = value + rbias - p = np.array([sin(th) * cos(ph), sin(th) * sin(ph), cos(th)]) * r - pts.append(p) + p = spher2cart(r, th, ph) + pts1.append(p) + ll.append((ilat, ilong)) + +radii = agrid_reco.ravel() + +############################################################# +# make a finer grid + +#ll.append((-90, 360)) +#radii = np.append(radii, radii[0]) +#print(agrid_reco.shape) +#print(np.array(ll).min(axis=0)) +#print(np.array(ll).max(axis=0)) + +n = 200j +lmin, lmax = np.array(ll).min(axis=0), np.array(ll).max(axis=0) +grid = np.mgrid[lmin[0]:lmax[0]:n, lmin[1]:lmax[1]:n] +grid_x, grid_y = grid +agrid_reco_finer = griddata(ll, radii, (grid_x, grid_y), method='cubic') + +pts2 = [] +for i, lat in enumerate(grid_x[:,1]): + for j, long in enumerate(grid_y[0]): + th = np.deg2rad(90 -lat) + ph = np.deg2rad(long) + r = agrid_reco_finer[i][j] + p = spher2cart(r, th, ph) + pts2.append(p) + +act1 = Points(pts1, r=8, c="b", alpha=0.5) +act2 = Points(pts2, r=2, c="r", alpha=0.5) + +show(act1, act2, at=1, interactive=1) + + + + + + + + + + + + + + + + + + -act = Points(pts, c="r", r=8, alpha=0.5) -vp.show(act, at=1, interactive=1) diff --git a/examples/other/tf_learn_embryo.py b/examples/other/tf_learn_embryo.py index 3ee3c0f0..4dabd266 100644 --- a/examples/other/tf_learn_embryo.py +++ b/examples/other/tf_learn_embryo.py @@ -1,13 +1,13 @@ import numpy as np from keras.models import Sequential from keras.layers import Dense -from vtkplotter import loadImageData, Volume, isosurface, show, datadir +from vtkplotter import load, Volume, isosurface, show, datadir maxradius = 0.2 neurons = 30 epochs = 20 -image = loadImageData(datadir+"embryo.tif") +image = load(datadir+"embryo.tif").imagedata() vmin, vmax = image.GetScalarRange() nx, ny, nz = image.GetDimensions() @@ -39,7 +39,7 @@ model.add(Dense(neurons, activation="relu", input_dim=3)) model.add(Dense(neurons, activation="relu")) model.add(Dense(neurons, activation="relu")) -model.add(Dense(1, activation="sigmoid")) +model.add(Dense(1, activation="sigmoid")) model.compile(optimizer="rmsprop", loss="mse", metrics=["mae"]) @@ -59,7 +59,7 @@ v1 = Volume(visdata) v2 = Volume(vispred) -s1 = isosurface(v1.image, threshold=0).alpha(0.8) -s2 = isosurface(v2.image, threshold=0).alpha(0.8) +s1 = isosurface(v1, threshold=0).alpha(0.8) +s2 = isosurface(v2, threshold=0).alpha(0.8) -show([v1, v2, s1, s2], N=4, axes=8, bg="w", depthpeeling=1) +show([v1, v2, s1, s2], N=4, axes=8, bg="w") diff --git a/examples/run_all.sh b/examples/run_all.sh index 013a3362..36fd027a 100755 --- a/examples/run_all.sh +++ b/examples/run_all.sh @@ -20,6 +20,9 @@ python basic/a_first_example.py echo Running basic/acollection.py python basic/acollection.py +echo Running basic/buildmesh.py +python basic/buildmesh.py + #################################### echo Running basic/align1.py python basic/align1.py @@ -39,9 +42,6 @@ python basic/bgImage.py echo Running basic/boolean.py python basic/boolean.py -echo Running basic/buildpolydata.py -python basic/buildpolydata.py - echo Running basic/carcrash.py python basic/carcrash.py @@ -312,6 +312,9 @@ python volumetric/streamlines2.py echo Running volumetric/streamribbons.py python volumetric/streamribbons.py +echo Running volumetric/lowpassfilter.py +python volumetric/lowpassfilter.py + cd volumetric echo Running office.py python office.py @@ -349,7 +352,6 @@ python other/tf_learn_volume.py echo Running other/voronoi3d.py python other/voronoi3d.py - echo Running other/export_x3d.py python other/export_x3d.py diff --git a/examples/volumetric/legosurface.py b/examples/volumetric/legosurface.py index a43181d5..2162edbf 100644 --- a/examples/volumetric/legosurface.py +++ b/examples/volumetric/legosurface.py @@ -7,10 +7,10 @@ # https://matplotlib.org/users/colormaps.html from vtkplotter import * -img = loadImageData(datadir+'embryo.tif') -printHistogram(img, logscale=True) +vol = load(datadir+'embryo.tif') # load Volume +printHistogram(vol, logscale=True) -vol = Volume(img).crop(back=0.5) # crop 50% from neg. y +vol.crop(back=0.5) # crop 50% from neg. y # show lego blocks whose value is between vmin and vmax lego = legosurface(vol, vmin=60, cmap='seismic') diff --git a/examples/volumetric/lowpassfilter.py b/examples/volumetric/lowpassfilter.py index 20507d33..38f8d207 100644 --- a/examples/volumetric/lowpassfilter.py +++ b/examples/volumetric/lowpassfilter.py @@ -2,7 +2,7 @@ print('..this can take ~30 sec / n_cores to run!') -v1 = loadVolume(datadir+'embryo.tif').c('blue') +v1 = load(datadir+'embryo.tif').c('blue') # cutoff range is roughly in the range of 1 / size of object v2 = frequencyPassFilter(v1, highcutoff=.001, order=1).c('green') diff --git a/examples/volumetric/probeLine.py b/examples/volumetric/probeLine.py index 5cdfe239..ae180d45 100644 --- a/examples/volumetric/probeLine.py +++ b/examples/volumetric/probeLine.py @@ -1,9 +1,9 @@ """ Intersect a Volume (voxel dataset) with planes. """ -from vtkplotter import show, loadVolume, probeLine, vector, Text, datadir +from vtkplotter import show, load, probeLine, vector, Text, datadir -vol = loadVolume(datadir+"embryo.slc") +vol = load(datadir+"embryo.slc") pos = vol.imagedata().GetCenter() diff --git a/examples/volumetric/probePlane.py b/examples/volumetric/probePlane.py index 3cd9a1b8..523a8caf 100644 --- a/examples/volumetric/probePlane.py +++ b/examples/volumetric/probePlane.py @@ -1,9 +1,9 @@ """ Intersect a Volume (voxel dataset) with planes """ -from vtkplotter import show, loadVolume, probePlane, vector, Text, datadir +from vtkplotter import show, load, probePlane, vector, Text, datadir -vol = loadVolume(datadir+"embryo.slc") +vol = load(datadir+"embryo.slc") planes = [] for i in range(6): diff --git a/examples/volumetric/probePoints.py b/examples/volumetric/probePoints.py index 1378683e..ebe4d902 100644 --- a/examples/volumetric/probePoints.py +++ b/examples/volumetric/probePoints.py @@ -4,7 +4,7 @@ from vtkplotter import * import numpy as np -vol = loadVolume(datadir+"embryo.slc") +vol = load(datadir+"embryo.slc") pts = np.random.rand(1000, 3)*256 diff --git a/examples/volumetric/readVolume.py b/examples/volumetric/readVolume.py index f5c4bab4..a306a704 100644 --- a/examples/volumetric/readVolume.py +++ b/examples/volumetric/readVolume.py @@ -9,7 +9,7 @@ # to define the opacity transfer function in the range of the scalar. # E.g.: setting alpha=[0, 0, 0, 1, 0, 0, 0] would make visible # only voxels with value close to 98.5 (see print output). -vol = loadVolume(datadir+"embryo.slc", spacing=[1, 1, 1]) +vol = load(datadir+"embryo.slc", spacing=[1, 1, 1]) vol.c("green").alpha([0, 0.4, 0.9, 1]) # vtkVolume # can relocate volume in space: @@ -20,5 +20,4 @@ doc = Text(__doc__, c="k") # show both vtkVolume and vtkActor -show(vol, sph, doc, - axes=8, verbose=0, bg="w", zoom=1.4) +show(vol, sph, doc, axes=8, verbose=0, bg="w", zoom=1.4) diff --git a/examples/volumetric/readVolumeAsIsoSurface.py b/examples/volumetric/readVolumeAsIsoSurface.py index d6073506..e4aaac94 100644 --- a/examples/volumetric/readVolumeAsIsoSurface.py +++ b/examples/volumetric/readVolumeAsIsoSurface.py @@ -1,18 +1,12 @@ """ Example to read volumetric data in the form of a tiff stack -or SLC (StereoLithography Contour) from files with automatic isosurfacing: +or SLC (StereoLithography Contour) from files +with or without automatic isosurfacing: A tiff stack is a set of image slices in z. The scalar value (intensity of white) is used to create an isosurface by fixing a threshold. -In this example the level of white is in the range 0=black -> 150=white If threshold=None this is set to 1/3 of the scalar range. -- Setting connectivity to True discards the small isolated pieces of -surface and only keeps the largest connected surface. - -- Smoothing applies a gaussian smoothing with a standard deviation -which is expressed in units of pixels. - - If the spacing of the tiff stack is uneven in xyz, this can be fixed by setting scaling factors with scaling=[xfac,yfac,zfac] """ @@ -21,14 +15,17 @@ # Read volume data from a tif file: f = datadir+"embryo.tif" -a0 = load(f, threshold=80, connectivity=1) # isosurfacing on the fly -a1 = load(f, threshold=80, connectivity=0) -a2 = load(f, smoothing=2) -vp1 = show(a0, a1, a2, shape=(1, 3), axes=1) +v = load(f) # Volume +a = load(f, threshold=80) # isosurfacing on the fly +vp1 = show(v, a, shape=(1, 2), axes=8, viewup='z') # Can also read SLC files -a3 = load(datadir+"embryo.slc", c="g", smoothing=1, connectivity=1) +vol = load(datadir+"embryo.slc") # Volume +vol.color(['lb','db','dg','dr']) # color transfer values along range +vol.alpha([0.0, 0.0, 0.2, 0.6, 0.8, 1]) # opacity values along range # newPlotter triggers the instantiation of a new Plotter object -vp2 = show(a3, pos=(300, 300), newPlotter=True) +vp2 = show(vol, pos=(300, 300), bg='white', + viewup='z', zoom=1.5, + newPlotter=True) diff --git a/examples/volumetric/volumeOperations.py b/examples/volumetric/volumeOperations.py index 5876a198..3a20768e 100644 --- a/examples/volumetric/volumeOperations.py +++ b/examples/volumetric/volumeOperations.py @@ -6,11 +6,11 @@ """ print(__doc__) -from vtkplotter import Plotter, loadVolume, volumeOperation, datadir +from vtkplotter import Plotter, load, volumeOperation, datadir vp = Plotter(N=8, axes=0, bg="w") -v0 = loadVolume(datadir+"embryo.slc").c(0) # vtkVolume +v0 = load(datadir+"embryo.slc").c(0) # vtkVolume vp.show(v0, at=0) v1 = volumeOperation(v0, "gradient") diff --git a/setup.py b/setup.py index 33fcc90d..f503cd7d 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,7 @@ # python setup.py sdist bdist_wheel # twine upload dist/vtkplotter-?.?.?.tar.gz -r pypi # make release +# add vtkplotter/version.py ## to generate documentation: # Install the dependencies in docs/requirements.txt diff --git a/vtkplotter/__init__.py b/vtkplotter/__init__.py index 6c23cabc..765ba374 100644 --- a/vtkplotter/__init__.py +++ b/vtkplotter/__init__.py @@ -75,3 +75,18 @@ settings._init() ############### + +## deprecations +def loadImageData(*args, **kargs): + "Do not use." + printc("~bomb loadImageData has been retired in version>=3.0. Use instead:", c=1) + printc("img = load('file.tif').imagedata() # or better:") + printc("vol = load('file.tif') # returns a Volume") + printc("Abort.", c=1) + exit(0) + + + + + + diff --git a/vtkplotter/actors.py b/vtkplotter/actors.py index ec97b201..734a8834 100644 --- a/vtkplotter/actors.py +++ b/vtkplotter/actors.py @@ -74,13 +74,13 @@ def isolines(actor, n=10, vmin=None, vmax=None): return zbandsact -def isosurface(volume, smoothing=0, threshold=None, connectivity=False): +def isosurface(volume, threshold=True, connectivity=False, smoothing=0): """Return a ``vtkActor`` isosurface extracted from a ``vtkImageData`` object. - :param float smoothing: gaussian filter to smooth vtkImageData, in units of sigmas - :param threshold: value or list of values to draw the isosurface(s) + :param threshold: value or list of values to draw the isosurface(s) :type threshold: float, list :param bool connectivity: if True only keeps the largest portion of the polydata + :param float smoothing: gaussian filter to smooth vtkImageData, in units of sigmas .. hint:: |isosurfaces| |isosurfaces.py|_ """ @@ -98,9 +98,6 @@ def isosurface(volume, smoothing=0, threshold=None, connectivity=False): image = smImg.GetOutput() scrange = image.GetScalarRange() - if scrange[1] > 1e10: - print("Warning, high scalar range detected:", scrange) - cf = vtk.vtkContourFilter() cf.SetInputData(image) cf.UseScalarTreeOn() @@ -112,8 +109,10 @@ def isosurface(volume, smoothing=0, threshold=None, connectivity=False): cf.SetValue(i, t) cf.Update() else: - if not threshold: + if threshold is True: threshold = (2 * scrange[0] + scrange[1]) / 3.0 + print('automatic threshold set to ' + utils.precision(threshold,3), end=' ') + print('in ['+ utils.precision(scrange[0],3) + ', '+ utils.precision(scrange[1],3)+']') cf.SetValue(0, threshold) cf.Update() @@ -561,7 +560,7 @@ def addTrail(self, offset=None, maxlength=None, n=50, c=None, alpha=None, lw=2): alpha = self.GetProperty().GetOpacity() mapper.SetInputData(poly) - tline = Actor(c=col, alpha=alpha) + tline = Actor(poly, c=col, alpha=alpha) tline.SetMapper(mapper) tline.GetProperty().SetLineWidth(lw) self.trail = tline # holds the vtkActor @@ -622,20 +621,65 @@ def box(self, scale=1): def printHistogram(self, bins=10, height=10, logscale=False, minbin=0, horizontal=False, char=u"\U00002589", c=None, bold=True, title='Histogram'): + """ + Ascii histogram printing. + Input can also be ``Volume`` or ``Actor``. + Returns the raw data before binning (useful when passing vtk objects). + + :param int bins: number of histogram bins + :param int height: height of the histogram in character units + :param bool logscale: use logscale for frequencies + :param int minbin: ignore bins before minbin + :param bool horizontal: show histogram horizontally + :param str char: character to be used + :param str,int c: ascii color + :param bool char: use boldface + :param str title: histogram title + + :Example: + .. code-block:: python + + from vtkplotter import printHistogram + import numpy as np + d = np.random.normal(size=1000) + data = printHistogram(d, c='blue', logscale=True, title='my scalars') + data = printHistogram(d, c=1, horizontal=1) + print(np.mean(data)) # data here is same as d + + |printhisto| + """ utils.printHistogram(self, bins, height, logscale, minbin, horizontal, char, c, bold, title) return self def printInfo(self): + """Print information about a vtk object.""" utils.printInfo(self) return self + def c(self, color=False): + """ + Shortcut for `color()`. + If None is passed as input, will use colors from active scalars. + """ + return self.color(color) + + #################################################### # Actor inherits from vtkActor and Prop class Actor(vtk.vtkActor, Prop): """Build an instance of object ``Actor`` derived from ``vtkActor``. - Either ``vtkPolyData`` or ``vtkActor`` is expected as input. + Input can be ``vtkPolyData``, ``vtkActor``, or a python list of [vertices, faces]. + + If input is any of ``vtkUnstructuredGrid``, ``vtkStructuredGrid`` or ``vtkRectilinearGrid`` + the goemetry is extracted. In this case the original VTK data structures can be accessed as: + `actor.UnstructuredGrid`, `actor.RectilinearGrid` and `actor.StructuredGrid`, + + Finally input can be a list of vertices and their connectivity (faces of the polygonal mesh). + For point clouds - e.i. no faces - just substitute the `faces` list with ``None``. + + E.g.: `Actor( [ [[x1,y1,z1],[x2,y2,z2], ...], [[0,1,2], [1,2,3], ...] ] )` :param c: color in RGB format, hex, symbol or name :param float alpha: opacity value @@ -643,8 +687,10 @@ class Actor(vtk.vtkActor, Prop): :param bc: backface color of internal surface :param str texture: jpg file name or surface texture name :param bool computeNormals: compute point and cell normals at creation - - |basicshapes| + + .. hint:: A mesh can be built from vertices and their connectivity. See e.g.: + + |buildmesh| |buildmesh.py|_ """ def __init__( @@ -660,31 +706,51 @@ def __init__( ): vtk.vtkActor.__init__(self) Prop.__init__(self) + + self.UnstructuredGrid = None + self.StructuredGrid = None + self.RectilinearGrid = None inputtype = str(type(poly)) + if poly is None: + self.poly = vtk.vtkPolyData() + self.mapper = vtk.vtkPolyDataMapper() if "vtkActor" in inputtype: self.mapper = poly.GetMapper() self.poly = self.mapper.GetInput() + elif "vtkPolyData" in inputtype: + if poly.GetNumberOfCells() == 0: + carr = vtk.vtkCellArray() + for i in range(poly.GetNumberOfPoints()): + carr.InsertNextCell(1) + carr.InsertCellPoint(i) + poly.SetVerts(carr) + self.poly = poly # cache vtkPolyData and mapper for speed + self.mapper = vtk.vtkPolyDataMapper() elif "vtkUnstructuredGrid" in inputtype: gf = vtk.vtkGeometryFilter() gf.SetInputData(poly) gf.Update() + self.UnstructuredGrid = poly self.poly = gf.GetOutput() self.mapper = vtk.vtkPolyDataMapper() elif "vtkStructuredGrid" in inputtype: gf = vtk.vtkGeometryFilter() gf.SetInputData(poly) gf.Update() + self.StructuredGrid = poly self.poly = gf.GetOutput() self.mapper = vtk.vtkPolyDataMapper() elif "vtkRectilinearGrid" in inputtype: gf = vtk.vtkGeometryFilter() gf.SetInputData(poly) gf.Update() + self.RectilinearGrid = poly self.poly = gf.GetOutput() self.mapper = vtk.vtkPolyDataMapper() - else: - self.poly = poly # cache vtkPolyData and mapper for speed + elif utils.isSequence(poly) and len(poly)==2: + from vtkplotter.vtkio import buildPolyData + self.poly = buildPolyData(poly[0], poly[1]) self.mapper = vtk.vtkPolyDataMapper() if self.mapper: @@ -867,7 +933,7 @@ def getPoint(self, i): .. warning:: if used in a loop this can slow down the execution by a lot. - .. seealso:: ``actor.Points()`` + .. seealso:: ``actor.getPoints()`` """ poly = self.polydata(True) p = [0, 0, 0] @@ -920,6 +986,32 @@ def setPoints(self, pts): self.PokeMatrix(vtk.vtkMatrix4x4()) return self + def getCells(self): + """Get cell connettivity ids as a 1D array. The vtk format is: + [nids1, id0 ... idn, niids2, id0 ... idm, etc]. + """ + poly = self.polydata() + return vtk_to_numpy(poly.GetPolys().GetData()) + + def getConnectivity(self): + """Get cell connettivity ids as a python ``list``. The format is: + [[id0 ... idn], [id0 ... idm], etc]. + """ + poly = self.polydata() + arr1d = vtk_to_numpy(poly.GetPolys().GetData()) + i=0 + conn=[] + n = len(arr1d) + for idummy in range(n): + cell=[] + for k in range(arr1d[i]): + cell.append(arr1d[i+k+1]) + conn.append(cell) + i += arr1d[i]+1 + if i >= n: + break + return conn + def computeNormals(self): """Compute cell and vertex normals for the actor's mesh. @@ -1081,14 +1173,6 @@ def color(self, c=False): self.trail.GetProperty().SetColor(cc) return self - def c(self, color=False): - """ - Shortcut for `actor.color()`. - If None is passed as input, will use colors from active scalars. - Same as `color()`. - """ - return self.color(color) - def backColor(self, bc=None): """ Set/get actor's backface color. @@ -1752,7 +1836,7 @@ def threshold(self, scalars, vmin=None, vmax=None, useCells=False): self.addPointScalars(scalars, "threshold") scalars = "threshold" elif self.scalars(scalars) is None: - colors.printc("~times No scalars found with name", scalars, c=1) + colors.printc("~times No scalars found with name/nr:", scalars, c=1) exit() thres = vtk.vtkThreshold() @@ -1841,6 +1925,9 @@ def pointColors(self, scalars, cmap="jet", alpha=1, bands=None, vmin=None, vmax= n = len(scalars) except TypeError: #invalid type return self + + if hasattr(scalars, 'astype'): + scalars = scalars.astype(np.float) useAlpha = False if n != poly.GetNumberOfPoints(): @@ -1921,6 +2008,9 @@ def cellColors(self, scalars, cmap="jet", alpha=1, bands=None, vmin=None, vmax=N if isinstance(scalars, str): # if a name is passed scalars = vtk_to_numpy(poly.GetCellData().GetArray(scalars)) + if hasattr(scalars, 'astype'): + scalars = scalars.astype(np.float) + n = len(scalars) useAlpha = False if n != poly.GetNumberOfCells(): @@ -1994,6 +2084,9 @@ def addPointScalars(self, scalars, name): colors.printc('~times pointScalars Error: Number of scalars != nr. of points', len(scalars), poly.GetNumberOfPoints(), c=1) exit() + if hasattr(scalars, 'astype'): + scalars = scalars.astype(np.float) + arr = numpy_to_vtk(np.ascontiguousarray(scalars), deep=True) arr.SetName(name) poly.GetPointData().AddArray(arr) @@ -2013,6 +2106,8 @@ def addCellScalars(self, scalars, name): if len(scalars) != poly.GetNumberOfCells(): colors.printc("~times Number of scalars != nr. of cells", c=1) exit() + if hasattr(scalars, 'astype'): + scalars = scalars.astype(np.float) arr = numpy_to_vtk(np.ascontiguousarray(scalars), deep=True) arr.SetName(name) poly.GetCellData().AddArray(arr) @@ -2169,13 +2264,18 @@ def subdivide(self, N=1, method=0): sdf.Update() return self.updateMesh(sdf.GetOutput()) - def decimate(self, fraction=0.5, N=None, boundaries=False, verbose=True): + def decimate(self, fraction=0.5, N=None, method='quadric', boundaries=False): """ Downsample the number of vertices in a mesh. :param float fraction: the desired target of reduction. :param int N: the desired number of final points (**fraction** is recalculated based on it). - :param bool boundaries: (True), decide whether to leave boundaries untouched or not. + :param str method: can be either 'quadric' or 'pro'. In the first case triagulation + will look like more regular, irrespective of the mesh origianl curvature. + In the second case triangles are more irregular but mesh is more precise on more + curved regions. + :param bool boundaries: (True), in `pro` mode decide whether + to leave boundaries untouched or not. .. note:: Setting ``fraction=0.1`` leaves 10% of the original nr of vertices. @@ -2188,18 +2288,19 @@ def decimate(self, fraction=0.5, N=None, boundaries=False, verbose=True): if fraction >= 1: return self - decimate = vtk.vtkDecimatePro() + if 'quad' in method: + decimate = vtk.vtkQuadricDecimation() + decimate.VolumePreservationOn() + else: + decimate = vtk.vtkDecimatePro() + decimate.PreserveTopologyOn() + if boundaries: + decimate.BoundaryVertexDeletionOff() + else: + decimate.BoundaryVertexDeletionOn() decimate.SetInputData(poly) decimate.SetTargetReduction(1 - fraction) - decimate.PreserveTopologyOff() - if boundaries: - decimate.BoundaryVertexDeletionOff() - else: - decimate.BoundaryVertexDeletionOn() decimate.Update() - if verbose: - print("Nr. of pts, input:", poly.GetNumberOfPoints(), end="") - print(" output:", decimate.GetOutput().GetNumberOfPoints()) return self.updateMesh(decimate.GetOutput()) def addGaussNoise(self, sigma): @@ -2810,14 +2911,15 @@ class Volume(vtk.vtkVolume, Prop): |read_vti| |read_vti.py|_ """ - def __init__(self, img, c="blue", alpha=(0.0, 0.4, 0.9, 1), alphas=None): + def __init__(self, img, + c='b', + alpha=(0.0, 0.4, 0.9, 1)): """Derived class of ``vtkVolume``. :param c: sets colors along the scalar range :type c: list, str :param alpha: sets transparencies along the scalar range :type c: float, list - :param alphas: DEPRECATED, use `alpha` instead. if a `list` of values is used for `alpha` this is interpreted as a transfer function along the range. @@ -2850,13 +2952,16 @@ def __init__(self, img, c="blue", alpha=(0.0, 0.4, 0.9, 1), alphas=None): # volumeProperty.SetSpecular(1) # volumeProperty.SetSpecularPower(2.0) self.SetProperty(volumeProperty) - if alphas is not None: - colors.printc('\nKeyword alphas is deprecated: use alpha instead!', c=1) - alpha=alphas - self.c(c).alpha(alpha) + self.color(c).alpha(alpha) + + + def imagedata(self): + """Return the underlying ``vtkImagaData`` object.""" + return self.image - def c(self, color="blue"): - """Assign a color or a set of colors to a volume along the range of the scalar value""" + + def color(self, color="blue"): + """Assign a color or a set of colors to a volume along the range of the scalar value.""" smin, smax = self.image.GetScalarRange() volumeProperty = self.GetProperty() colorTransferFunction = vtk.vtkColorTransferFunction() @@ -2878,15 +2983,22 @@ def c(self, color="blue"): return self def alpha(self, alpha=(0.0, 0.4, 0.9, 1)): - """Assign a set of tranparencies to a volume along the range of the scalar value""" + """Assign a set of tranparencies to a volume along the range of the scalar value. + + E.g.: say alpha=(0.0, 0.3, 0.9, 1) and the scalar range goes from -10 to 150. + Then all voxels with a value close to -10 will be completely transparent, voxels at 1/4 + of the range will get an alpha equal to 0.3 and voxels with value close to 150 + will be completely opaque. + """ volumeProperty = self.GetProperty() smin, smax = self.image.GetScalarRange() opacityTransferFunction = vtk.vtkPiecewiseFunction() - for i, al in enumerate(alpha): - xalpha = smin + (smax - smin) * i / (len(alpha) - 1) - # Create transfer mapping scalar value to opacity - opacityTransferFunction.AddPoint(xalpha, al) - #colors.printc(" alpha at", round(xalpha, 1), "\tset to", al, c="b", bold=0) + if utils.isSequence(alpha): + for i, al in enumerate(alpha): + xalpha = smin + (smax - smin) * i / (len(alpha) - 1) + # Create transfer mapping scalar value to opacity + opacityTransferFunction.AddPoint(xalpha, al) + #colors.printc("alpha at", round(xalpha, 1), "\tset to", al, c="b", bold=0) # The property describes how the data will look volumeProperty.SetScalarOpacity(opacityTransferFunction) @@ -2895,11 +3007,6 @@ def alpha(self, alpha=(0.0, 0.4, 0.9, 1)): return self - def imagedata(self): - """Return the underlying ``vtkImagaData`` object.""" - return self.image - - def threshold(self, vmin=None, vmax=None, replaceWith=None): """ Binary or continuous volume thresholding. diff --git a/vtkplotter/addons.py b/vtkplotter/addons.py index c306370c..480be083 100644 --- a/vtkplotter/addons.py +++ b/vtkplotter/addons.py @@ -473,8 +473,7 @@ def addButton( if not vp.renderer: colors.printc("~timesError: Use addButton() after rendering the scene.", c=1) return - import vtkplotter.vtkio as vtkio - bu = vtkio.Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle) + bu = Button(fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle) vp.renderer.AddActor2D(bu.actor) vp.window.Render() vp.buttons.append(bu) @@ -524,6 +523,7 @@ def addCutterTool(actor): act1Mapper = vtk.vtkPolyDataMapper() # the part which is cut away act1Mapper.SetInputConnection(clipper.GetClippedOutputPort()) # needs OutputPort?? + act1Mapper.ScalarVisibilityOff() act1 = vtk.vtkActor() act1.SetMapper(act1Mapper) act1.GetProperty().SetOpacity(0.02) @@ -540,7 +540,7 @@ def SelectPolygons(vobj, event): boxWidget = vtk.vtkBoxWidget() boxWidget.OutlineCursorWiresOn() boxWidget.GetSelectedOutlineProperty().SetColor(1, 0, 1) - boxWidget.GetOutlineProperty().SetColor(0.1, 0.1, 0.1) + boxWidget.GetOutlineProperty().SetColor(0.2, 0.2, 0.2) boxWidget.GetOutlineProperty().SetOpacity(0.8) boxWidget.SetPlaceFactor(1.05) boxWidget.SetInteractor(vp.interactor) @@ -587,16 +587,15 @@ def ClipVolumeRender(obj, event): boxWidget.SetInputData(vol.image) boxWidget.OutlineCursorWiresOn() boxWidget.GetSelectedOutlineProperty().SetColor(1, 0, 1) - boxWidget.GetOutlineProperty().SetColor(0.1, 0.1, 0.1) + boxWidget.GetOutlineProperty().SetColor(0.2, 0.2, 0.2) boxWidget.GetOutlineProperty().SetOpacity(0.7) - boxWidget.SetPlaceFactor(1.05) + boxWidget.SetPlaceFactor(1.0) boxWidget.PlaceWidget() boxWidget.InsideOutOn() boxWidget.AddObserver("InteractionEvent", ClipVolumeRender) colors.printc("Mesh Cutter Tool:", c="m", invert=1) - colors.printc(" Move gray handles to cut away parts of the mesh", c="m") - colors.printc(" Press X to save file to: clipped.vtk", c="m") + colors.printc(" Move gray handles to cut parts of the volume", c="m") vp.renderer.ResetCamera() boxWidget.On() @@ -1118,3 +1117,75 @@ def addLegend(): vtklegend.SetBackgroundOpacity(0.6) vtklegend.LockBorderOn() vp.renderer.AddActor(vtklegend) + + +########################################################################################### +class Button: + """ + Build a Button object to be shown in the rendering window. + + .. hint:: |buttons| |buttons.py|_ + """ + + def __init__(self, fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle): + """ + Build a Button object to be shown in the rendering window. + """ + self._status = 0 + self.states = states + self.colors = c + self.bcolors = bc + self.function = fnc + self.actor = vtk.vtkTextActor() + self.actor.SetDisplayPosition(pos[0], pos[1]) + self.framewidth = 3 + self.offset = 5 + self.spacer = " " + + self.textproperty = self.actor.GetTextProperty() + self.textproperty.SetJustificationToCentered() + if font.lower() == "courier": + self.textproperty.SetFontFamilyToCourier() + elif font.lower() == "times": + self.textproperty.SetFontFamilyToTimes() + else: + self.textproperty.SetFontFamilyToArial() + self.textproperty.SetFontSize(size) + self.textproperty.SetBackgroundOpacity(alpha) + self.textproperty.BoldOff() + if bold: + self.textproperty.BoldOn() + self.textproperty.ItalicOff() + if italic: + self.textproperty.ItalicOn() + self.textproperty.ShadowOff() + self.textproperty.SetOrientation(angle) + self.showframe = hasattr(self.textproperty, "FrameOn") + self.status(0) + + def status(self, s=None): + """ + Set/Get the status of the button. + """ + if s is None: + return self.states[self._status] + if isinstance(s, str): + s = self.states.index(s) + self._status = s + self.textproperty.SetLineOffset(self.offset) + self.actor.SetInput(self.spacer + self.states[s] + self.spacer) + s = s % len(self.colors) # to avoid mismatch + self.textproperty.SetColor(colors.getColor(self.colors[s])) + bcc = numpy.array(colors.getColor(self.bcolors[s])) + self.textproperty.SetBackgroundColor(bcc) + if self.showframe: + self.textproperty.FrameOn() + self.textproperty.SetFrameWidth(self.framewidth) + self.textproperty.SetFrameColor(numpy.sqrt(bcc)) + + def switch(self): + """ + Change/cycle button status to the next defined status in states list. + """ + self._status = (self._status + 1) % len(self.states) + self.status(self._status) diff --git a/vtkplotter/analysis.py b/vtkplotter/analysis.py index 18d5297f..c7e411e5 100644 --- a/vtkplotter/analysis.py +++ b/vtkplotter/analysis.py @@ -827,7 +827,7 @@ def smoothMLS3D(actors, neighbours=10): kd = KDTree(coords4d, leafsize=neighbours) suggest = "" - pb = vio.ProgressBar(0, len(coords4d)) + pb = vu.ProgressBar(0, len(coords4d)) for i in pb.range(): mypt = coords4d[i] @@ -895,7 +895,7 @@ def smoothMLS2D(actor, f=0.2, decimate=1, recursive=0, showNPlanes=0): locator.BuildLocator() vtklist = vtk.vtkIdList() variances, newsurf, acts = [], [], [] - pb = vio.ProgressBar(0, ncoords) + pb = vu.ProgressBar(0, ncoords) for i, p in enumerate(coords): pb.print("smoothing...") if i % decimate: diff --git a/vtkplotter/data/fourobj.wrl b/vtkplotter/data/fourobj.wrl deleted file mode 100644 index ac9dc185..00000000 --- a/vtkplotter/data/fourobj.wrl +++ /dev/null @@ -1,82 +0,0 @@ -#VRML V2.0 utf8 -# X3D-to-VRML-97 XSL translation autogenerated by X3dToVrml97.xslt -# http://www.web3d.org/x3d/content/X3dToVrml97.xslt -# Generated using XSLT processor: Saxonica - -# [X3D] VRML V3.0 utf8 -# PROFILE Interchange -# [X3D] version=3.0 -# [X3D] noNamespaceSchemaLocation=http://www.web3d.org/specifications/x3d-3.0.xsd -# [head] - -# META "title" "Example09.x3d" -# META "translator" "Don Brutzman" -# META "created" "26 June 2000" -# META "modified" "26 November 2015" -# META "creator" "X3D working group" -# META "description" "VRML 97 specification example: A DirectionalLight source illuminates only the objects in its enclosing grouping node. The light illuminates everything within this coordinate system, including the objects that precede it in the scene graph." -# META "reference" "originals/exampleD_9.wrl" -# META "specificationSection" "VRML 97, ISO/IEC 14772-1, Part 1: Functional specification and UTF-8 encoding, D.9 Directional light" -# META "specificationUrl" "http://www.web3d.org/documents/specifications/14772/V2.0/part1/examples.html#D.9" -# META "identifier" "http://www.web3d.org/x3d/content/examples/Basic/Vrml97Specification/Example09.x3d" -# META "generator" "X3D-Edit 3.3, https://savage.nps.edu/X3D-Edit" -# META "license" "../license.html" - -# [Scene] ========== ========== ========== - -NavigationInfo { type [ "EXAMINE" "ANY" ] } ### Default X3D NavigationInfo - -Group { - children [ - DEF UnlitShapeOne Transform { - translation -3.0 0.0 0.0 - children [ - Shape { - geometry Box { - } - appearance DEF App Appearance { - material Material { - diffuseColor 0.8 0.4 0.2 - } - } - } - ] - } - DEF LitParent Group { - children [ - DEF LitShapeOne Transform { - translation 0.0 2.0 0.0 - children [ - Shape { - geometry Sphere { - } - appearance USE App - } - ] - } - DirectionalLight { - } - DEF LitShapeTwo Transform { - translation 0.0 -2.0 0.0 - children [ - Shape { - geometry Cylinder { - } - appearance USE App - } - ] - } - ] - } - DEF UnlitShapeTwo Transform { - translation 3.0 0.0 0.0 - children [ - Shape { - geometry Cone { - } - appearance USE App - } - ] - } - ] -} diff --git a/vtkplotter/docs.py b/vtkplotter/docs.py index 1f4ea60a..0567b290 100644 --- a/vtkplotter/docs.py +++ b/vtkplotter/docs.py @@ -11,8 +11,9 @@ - `examples/advanced `_ , - `examples/volumetric `_, - `examples/simulations `_. - - `examples/other `_. - + - `examples/other `_ + - `examples/other/dolfin `_. + :raw-html-m2r:`
` .. image:: https://user-images.githubusercontent.com/32848391/51558920-ec436e00-1e80-11e9-9d96-aa9b7c72d58b.png @@ -23,6 +24,8 @@ """ from __future__ import division, print_function +__all__ = [] + def onelinetip(): import vtk, sys @@ -438,19 +441,6 @@ def tips(): :target: thinplate.py_ :alt: thinplate.py -.. |readStructuredPoints.py| replace:: readStructuredPoints.py -.. _readStructuredPoints.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/volumetric/readStructuredPoints.py -.. |readStructuredPoints| image:: https://user-images.githubusercontent.com/32848391/48198462-3b393700-e359-11e8-8272-670bd5f2db42.jpg - :width: 250 px - :target: readStructuredPoints.py_ - :alt: readStructuredPoints.py - -.. |buildpolydata.py| replace:: buildpolydata.py -.. _buildpolydata.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/volumetric/buildpolydata.py -.. |buildpolydata| image:: https://user-images.githubusercontent.com/32848391/51032546-bf4dac00-15a0-11e9-9e1e-035fff9c05eb.png - :width: 250 px - :alt: buildpolydata.py - .. |colorcubes.py| replace:: colorcubes.py .. _colorcubes.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/colorcubes.py .. |colorcubes| image:: https://user-images.githubusercontent.com/32848391/50738867-c0658e80-11d8-11e9-9e05-ac69b546b7ec.png @@ -1010,7 +1000,7 @@ def tips(): .. |elasticbeam.py| replace:: elasticbeam.py .. _elasticbeam.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/other/dolfin/elasticbeam.py .. |elasticbeam| image:: https://user-images.githubusercontent.com/32848391/57476429-d7a3ae00-7296-11e9-9f50-8f456823ef3d.png - :width: 250 px + :width: 300 px :target: elasticbeam.py_ :alt: elasticbeam.py @@ -1040,7 +1030,13 @@ def tips(): .. |idealpass| image:: https://raw.githubusercontent.com/lorensen/VTKExamples/master/src/Testing/Baseline/Cxx/ImageProcessing/TestIdealHighPass.png :width: 250 px :target: idealpass.link_ - - + +.. |buildmesh.py| replace:: buildmesh.py +.. _buildmesh.py: https://github.com/marcomusy/vtkplotter/blob/master/examples/basic/buildmesh.py +.. |buildmesh| image:: https://user-images.githubusercontent.com/32848391/57858625-b0e2fb80-77f1-11e9-94f0-1973ed86ae70.png + :width: 250 px + :target: buildmesh.py_ + :alt: buildmesh.py + """ diff --git a/vtkplotter/dolfin.py b/vtkplotter/dolfin.py index 120adaed..9c83f610 100644 --- a/vtkplotter/dolfin.py +++ b/vtkplotter/dolfin.py @@ -8,7 +8,7 @@ import numpy as np import vtkplotter.utils as utils -from vtkplotter.utils import printHistogram +from vtkplotter.utils import printHistogram, ProgressBar import vtkplotter.docs as docs @@ -20,7 +20,7 @@ from vtkplotter.actors import Actor, isolines import vtkplotter.vtkio as vtkio -from vtkplotter.vtkio import load, ProgressBar, screenshot, Video, exportWindow +from vtkplotter.vtkio import load, screenshot, Video, exportWindow import vtkplotter.shapes as shapes from vtkplotter.shapes import Text, Latex @@ -137,12 +137,12 @@ def _inputsort(obj): for ob in obj: inputtype = str(type(ob)) + #printc('inputtype is', inputtype, c=2) if "vtk" in inputtype: # skip vtk objects, will be added later continue if "dolfin" in inputtype: - #printc('inputtype is', inputtype, c=2) if "MeshFunction" in inputtype: mesh = ob.mesh() @@ -178,6 +178,10 @@ def _inputsort(obj): u = ob elif "Mesh" in inputtype: mesh = ob + + if "str" in inputtype: + import dolfin + mesh = dolfin.Mesh(ob) if u and not mesh and hasattr(u, "function_space"): V = u.function_space() @@ -458,7 +462,10 @@ def plot(*inputobj, **options): if add and settings.plotter_instance: actors = settings.plotter_instance.actors - if 'mesh' in mode or 'color' in mode: + if 'mesh' in mode or 'color' in mode or 'warp' in mode or 'displac' in mode: + if 'warp' in mode: #deprecation + printc("~bomb Please use 'displacement' instead of 'warp' in mode!", c=1) + actor = MeshActor(u, mesh, wire=wire) if lighting: actor.lighting(lighting) @@ -502,7 +509,7 @@ def plot(*inputobj, **options): else: actor.addScalarBar(horizontal=False, vmin=vmin, vmax=vmax) - if 'warp' in mode or 'displace' in mode: + if 'warp' in mode or 'displac' in mode: if delta is None: delta = [u(p) for p in mesh.coordinates()] movedpts = mesh.coordinates() + delta diff --git a/vtkplotter/plotter.py b/vtkplotter/plotter.py index 21fc3b2c..32009f34 100644 --- a/vtkplotter/plotter.py +++ b/vtkplotter/plotter.py @@ -501,6 +501,7 @@ def __init__( for i in reversed(range(shape[0])): for j in range(shape[1]): arenderer = vtk.vtkRenderer() + arenderer.SetUseHiddenLineRemoval(settings.hiddenLineRemoval) arenderer.SetUseDepthPeeling(depthpeeling) if "jpg" in str(self.backgrcol).lower() or "jpeg" in str(self.backgrcol).lower(): if i == 0: @@ -600,34 +601,47 @@ def _allowInteraction(): self.allowInteraction = _allowInteraction #################################################### - def load( - self, - inputobj, - c="gold", - alpha=1, - wire=False, - bc=None, - texture=None, - smoothing=None, - threshold=None, - connectivity=False, - ): + def load(self, inputobj, c="gold", alpha=1, threshold=False, spacing=(), unpack=True): """ - Returns a ``vtkActor`` from reading a file, directory or ``vtkPolyData``. - + Load Actors and Volumes from file. + The output will depend on the file extension. See examples below. + :param c: color in RGB format, hex, symbol or name - :param alpha: transparency (0=invisible) - :param wire: show surface as wireframe - :param bc: backface color of internal surface - :param texture: any png/jpg file can be used as texture - - For volumetric data (tiff, slc, vti files): - - :param smoothing: gaussian filter to smooth vtkImageData - :param threshold: value to draw the isosurface - :param bool connectivity: if True only keeps the largest portion of the polydata + :param alpha: transparency (0=invisible) + + For volumetric data (tiff, slc, vti etc): + :param float threshold: value to draw the isosurface, False by default to return a ``Volume`` + :param list spacing: specify the voxel spacing in the three dimensions + :param bool unpack: only for multiblock data, if True returns a flat list of objects. + + :Example: + .. code-block:: python + + from vtkplotter import datadir, load, show + + # Return an Actor + g = load(datadir+'ring.gmsh') + show(g) + + # Return a list of 2 Actors + g = load([datadir+'250.vtk', datadir+'290.vtk']) + show(g) + + # Return a list of actors by reaading all files in a directory + # (if directory contains DICOM files then a Volume is returned) + g = load(datadir+'timecourse1d/') + show(g) + + # Return a Volume. Color/Opacity transfer function can be specified too. + g = load(datadir+'embryo.slc') + g.c(['y','lb','w']).alpha((0.0, 0.4, 0.9, 1)) + show(g) + + # Return an Actor from a SLC volume with automatic thresholding + g = load(datadir+'embryo.slc', threshold=True) + show(g) """ - acts = vtkio.load(inputobj, c, alpha, wire, bc, texture, smoothing, threshold, connectivity) + acts = vtkio.load(inputobj, c, alpha, threshold, spacing, unpack) if utils.isSequence(acts): self.actors += acts else: @@ -1292,11 +1306,11 @@ def scan(wannabeacts): elif viewup == "z": b = self.renderer.ComputeVisiblePropBounds() fp = (b[1]+b[0])/2, (b[3]+b[2])/2, (b[5]+b[4])/2 - sz = numpy.array([b[1]-b[0], b[3]-b[2], b[5]-b[4]]) + sz = numpy.array([b[3]-b[2], b[1]-b[0], (b[5]-b[4])/2]) #swap xy if sz[2]==0: sz[2] = min(sz[0], sz[1]) self.camera.SetViewUp([0, 0.001, 1]) - self.camera.SetPosition(fp+1.95*sz) + self.camera.SetPosition(fp+2.1*sz) if camera is not None: cm_pos = camera.pop("pos", None) diff --git a/vtkplotter/settings.py b/vtkplotter/settings.py index 7f461ef1..06dba0c4 100644 --- a/vtkplotter/settings.py +++ b/vtkplotter/settings.py @@ -1,12 +1,30 @@ """ Global settings. -""" -from __future__ import division, print_function -import os -__all__ = [ - 'datadir', -] +.. role:: raw-html-m2r(raw) + :format: html + + +.. note:: **Please check out the** `git repository `_. + + A full list of examples can be found in directories: + + - `examples/basic `_ , + - `examples/advanced `_ , + - `examples/volumetric `_, + - `examples/simulations `_. + - `examples/other `_ + - `examples/other/dolfin `_. + +:raw-html-m2r:`
` + +.. image:: https://user-images.githubusercontent.com/32848391/51558920-ec436e00-1e80-11e9-9d96-aa9b7c72d58b.png + +:raw-html-m2r:`
` +:raw-html-m2r:`
` + +""" +__all__ = ['datadir'] #################################################################################### # recompute vertex and cell normals @@ -27,6 +45,9 @@ # on some vtk versions/platforms points are redered as ugly squares renderPointsAsSpheres = True +# remove hidden lines when in wireframe mode +hiddenLineRemoval = False + # path to Voro++ library # http://math.lbl.gov/voro++ voro_path = '/usr/local/bin' @@ -42,6 +63,7 @@ #################################################################################### +import os _cdir = os.path.dirname(__file__) if _cdir == "": _cdir = "." @@ -52,10 +74,6 @@ fonts_path = _cdir + "/fonts/" fonts = [] -##################### -collectable_actors = [] - - ##################### def _init(): global plotter_instance, plotter_instances, collectable_actors diff --git a/vtkplotter/shapes.py b/vtkplotter/shapes.py index a5b06d17..66a69d57 100644 --- a/vtkplotter/shapes.py +++ b/vtkplotter/shapes.py @@ -146,7 +146,7 @@ def _colorPoints(plist, cols, r, alpha): return actor -def Glyph(actor, glyphObj, orientationArray="", +def Glyph(actor, glyphObj, orientationArray=None, scaleByVectorSize=False, c=None, alpha=1): """ At each vertex of a mesh, another mesh - a `'glyph'` - is shown with @@ -190,7 +190,7 @@ def Glyph(actor, glyphObj, orientationArray="", gly.SetSourceData(glyphObj) gly.SetColorModeToColorByScalar() - if orientationArray != "": + if orientationArray is not None: gly.OrientOn() gly.SetScaleFactor(1) @@ -199,8 +199,12 @@ def Glyph(actor, glyphObj, orientationArray="", else: gly.SetScaleModeToDataScalingOff() - if orientationArray == "normals" or orientationArray == "Normals": - gly.SetVectorModeToUseNormal() + if isinstance(orientationArray, str): + if orientationArray.lower() == "normals": + gly.SetVectorModeToUseNormal() + else: # passing a name + gly.SetInputArrayToProcess(0, 0, 0, 0, orientationArray) + gly.SetVectorModeToUseVector() elif isinstance(orientationArray, vtk.vtkAbstractArray): actor.GetMapper().GetInput().GetPointData().AddArray(orientationArray) actor.GetMapper().GetInput().GetPointData().SetActiveVectors("glyph_vectors") @@ -209,13 +213,11 @@ def Glyph(actor, glyphObj, orientationArray="", elif utils.isSequence(orientationArray): # passing a list actor.addPointVectors(orientationArray, "glyph_vectors") gly.SetInputArrayToProcess(0, 0, 0, 0, "glyph_vectors") - else: # passing a name - gly.SetInputArrayToProcess(0, 0, 0, 0, orientationArray) - gly.SetVectorModeToUseVector() + if cmap: - gly.SetColorModeToColorByVector () + gly.SetColorModeToColorByVector() else: - gly.SetColorModeToColorByScalar () + gly.SetColorModeToColorByScalar() gly.Update() diff --git a/vtkplotter/utils.py b/vtkplotter/utils.py index 9da738f5..dd564329 100644 --- a/vtkplotter/utils.py +++ b/vtkplotter/utils.py @@ -3,6 +3,7 @@ import numpy as np import vtkplotter.colors as colors import vtkplotter.docs as docs +import time __doc__ = ( """ @@ -12,6 +13,7 @@ ) __all__ = [ + "ProgressBar", "isSequence", "vector", "mag", @@ -32,6 +34,129 @@ "printHistogram", ] +########################################################################### +class ProgressBar: + """ + Class to print a progress bar with optional text message. + + :Example: + .. code-block:: python + + import time + pb = ProgressBar(0,400, c='red') + for i in pb.range(): + time.sleep(.1) + pb.print('some message') # or pb.print(counts=i) + + |progbar| + """ + + def __init__(self, start, stop, step=1, c=None, ETA=True, width=24, char=u"\U000025AC"): + + char_arrow = u"\U000025BA" + if sys.version_info[0]<3: + char="=" + char_arrow = '>' + + self.start = start + self.stop = stop + self.step = step + self.color = c + self.width = width + self.char = char + self.char_arrow = char_arrow + self.bar = "" + self.percent = 0 + self.clock0 = 0 + self.ETA = ETA + self.clock0 = time.time() + self._remt = 1e10 + self._update(0) + self._counts = 0 + self._oldbar = "" + self._lentxt = 0 + self._range = np.arange(start, stop, step) + self._len = len(self._range) + + def print(self, txt="", counts=None): + """Print the progress bar and optional message.""" + if counts: + self._update(counts) + else: + self._update(self._counts + self.step) + if self.bar != self._oldbar: + self._oldbar = self.bar + eraser = [" "] * self._lentxt + ["\b"] * self._lentxt + eraser = "".join(eraser) + if self.ETA: + vel = self._counts / (time.time() - self.clock0) + self._remt = (self.stop - self._counts) / vel + if self._remt > 60: + mins = int(self._remt / 60) + secs = self._remt - 60 * mins + mins = str(mins) + "m" + secs = str(int(secs + 0.5)) + "s " + else: + mins = "" + secs = str(int(self._remt + 0.5)) + "s " + vel = str(round(vel, 1)) + eta = "ETA: " + mins + secs + "(" + vel + " it/s) " + if self._remt < 1: + dt = time.time() - self.clock0 + if dt > 60: + mins = int(dt / 60) + secs = dt - 60 * mins + mins = str(mins) + "m" + secs = str(int(secs + 0.5)) + "s " + else: + mins = "" + secs = str(int(dt + 0.5)) + "s " + eta = "Elapsed time: " + mins + secs + "(" + vel + " it/s) " + txt = "" + else: + eta = "" + txt = eta + str(txt) + s = self.bar + " " + eraser + txt + "\r" + if self.color: + colors.printc(s, c=self.color, end="") + else: + sys.stdout.write(s) + sys.stdout.flush() + if self.percent == 100: + print("") + self._lentxt = len(txt) + + def range(self): + """Return the range iterator.""" + return self._range + + def len(self): + """Return the number of steps.""" + return self._len + + def _update(self, counts): + if counts < self.start: + counts = self.start + elif counts > self.stop: + counts = self.stop + self._counts = counts + self.percent = (self._counts - self.start) * 100 + self.percent /= self.stop - self.start + self.percent = int(round(self.percent)) + af = self.width - 2 + nh = int(round(self.percent / 100 * af)) + if nh == 0: + self.bar = "["+self.char_arrow+"%s]" % (" " * (af - 1)) + elif nh == af: + self.bar = "[%s]" % (self.char * af) + else: + self.bar = "[%s%s%s]" % (self.char *(nh-1), self.char_arrow, " " *(af-nh)) + if self.percent < 100: # and self._remt > 1: + ps = " " + str(self.percent) + "%" + else: + ps = "" + self.bar += ps + ############################################################################## def isSequence(arg): @@ -345,16 +470,20 @@ def printvtkactor(actor, tab=""): colors.printc(actor.centerOfMass(), c="g", bold=0) colors.printc(tab + " ave. size: ", c="g", bold=1, end="") - colors.printc(precision(actor.averageSize(), 4), c="g", bold=0) + colors.printc(precision(actor.averageSize(), 6), c="g", bold=0) colors.printc(tab + " diag. size: ", c="g", bold=1, end="") - colors.printc(actor.diagonalSize(), c="g", bold=0) - - colors.printc(tab + " area: ", c="g", bold=1, end="") - colors.printc(precision(actor.area(), 8), c="g", bold=0) + colors.printc(precision(actor.diagonalSize(), 6), c="g", bold=0) + + _area = actor.area() + if _area: + colors.printc(tab + " area: ", c="g", bold=1, end="") + colors.printc(precision(_area, 6), c="g", bold=0) - colors.printc(tab + " volume: ", c="g", bold=1, end="") - colors.printc(precision(actor.volume(), 8), c="g", bold=0) + _vol = actor.volume() + if _vol: + colors.printc(tab + " volume: ", c="g", bold=1, end="") + colors.printc(precision(_vol, 6), c="g", bold=0) colors.printc(tab + " bounds: ", c="g", bold=1, end="") bx1, bx2 = precision(bnds[0], 3), precision(bnds[1], 3) @@ -381,7 +510,9 @@ def printvtkactor(actor, tab=""): except: tt = ptdata.GetArray(i).GetDataType() colors.printc("name=" + name, "type=" + tt, c="g", bold=0, end="") - colors.printc(" range:", ptdata.GetArray(i).GetRange(), c="g", bold=0) + rng = ptdata.GetArray(i).GetRange() + colors.printc(" range: (" + precision(rng[0],4) + ',' + + precision(rng[1],4) + ')', c="g", bold=0) if poly.GetCellData(): cldata = poly.GetCellData() @@ -394,7 +525,9 @@ def printvtkactor(actor, tab=""): except: tt = cldata.GetArray(i).GetDataType() colors.printc("name=" + name, "type=" + tt, c="g", bold=0, end="") - colors.printc(" range:", cldata.GetArray(i).GetRange(), c="g", bold=0) + rng = cldata.GetArray(i).GetRange() + colors.printc(" range: (" + precision(rng[0],4) + ',' + + precision(rng[1],4) + ')', c="g", bold=0) if not obj: return diff --git a/vtkplotter/version.py b/vtkplotter/version.py index c5f9ce26..51d96354 100644 --- a/vtkplotter/version.py +++ b/vtkplotter/version.py @@ -1 +1 @@ -_version='2019.2.2' +_version='2019.3.0' diff --git a/vtkplotter/vtkio.py b/vtkplotter/vtkio.py index 288d276d..fd7b76d9 100644 --- a/vtkplotter/vtkio.py +++ b/vtkplotter/vtkio.py @@ -2,7 +2,6 @@ import vtk import os import sys -import time import numpy import vtkplotter.utils as utils @@ -21,250 +20,231 @@ __all__ = [ "load", - "loadPolyData", - "loadImage", - "loadVolume", - "loadImageData", - "loadParallelData", - "loadXMLGenericData", "loadStructuredPoints", "loadStructuredGrid", "loadUnStructuredGrid", "loadRectilinearGrid", - "loadMultiBlockData", - "load3DS", - "loadDolfin", - "loadNeutral", - "loadGmesh", - "loadPCD", - "loadOFF", - "loadDICOM", "write", + "save", + "exportWindow", "screenshot", "Video", - "ProgressBar", - "convertNeutral2Xml", - "buildPolyData", - "Button", - "exportWindow", ] -def load( - inputobj, - c="gold", - alpha=None, - wire=False, - bc=None, - texture=None, - smoothing=None, - threshold=None, - connectivity=False, -): +def load(inputobj, c="gold", alpha=1, threshold=False, spacing=(), unpack=True): """ - Returns a ``vtkActor`` from reading a file, directory or ``vtkPolyData``. - + Load ``Actor`` and ``Volume`` from file. + + The output will depend on the file extension. See examples below. + :param c: color in RGB format, hex, symbol or name - :param alpha: transparency (0=invisible) - :param wire: show surface as wireframe - :param bc: backface color of internal surface - :param texture: any png/jpg file can be used as texture - - For volumetric data (tiff, slc, vti files): + :param alpha: transparency/opacity of the polygonal data. - :param smoothing: gaussian filter to smooth vtkImageData - :param threshold: value to draw the isosurface - :param connectivity: if True only keeps the largest portion of the polydata + For volumetric data `(tiff, slc, vti etc..)`: + + :param list c: can be a list of any length of colors. This list represents the color + transfer function values equally spaced along the range of the volumetric scalar. + :param list alpha: can be a list of any length of tranparencies. This list represents the + transparency transfer function values equally spaced along the range of the volumetric scalar. + :param float threshold: value to draw the isosurface, False by default to return a ``Volume``. + If set to True will return an ``Actor`` with automatic choice of the isosurfacing threshold. + :param list spacing: specify the voxel spacing in the three dimensions + :param bool unpack: only for multiblock data, if True returns a flat list of objects. + + :Examples: + .. code-block:: python + + from vtkplotter import datadir, load, show + + # Return an Actor + g = load(datadir+'250.vtk') + show(g) + + # Return a list of 2 Actors + g = load([datadir+'250.vtk', datadir+'270.vtk']) + show(g) + + # Return a list of actors by reading all files in a directory + # (if directory contains DICOM files then a Volume is returned) + g = load(datadir+'timecourse1d/') + show(g) + + # Return a Volume. Color/Opacity transfer functions can be specified too. + g = load(datadir+'embryo.slc') + g.c(['y','lb','w']).alpha((0.0, 0.4, 0.9, 1)) + show(g) + + # Return an Actor from a SLC volume with automatic thresholding + g = load(datadir+'embryo.slc', threshold=True) + show(g) """ - if alpha is None: - alpha = 1 - - if isinstance(inputobj, vtk.vtkPolyData): - a = Actor(inputobj, c, alpha, wire, bc, texture) - if inputobj and inputobj.GetNumberOfPoints() == 0: - colors.printc("~lightning Warning: actor has zero points.", c=5) - return a - acts = [] - if isinstance(inputobj, list): + if utils.isSequence(inputobj): flist = inputobj else: import glob flist = sorted(glob.glob(inputobj)) for fod in flist: - if os.path.isfile(fod): - if fod.endswith(".vtm"): - acts += loadMultiBlockData(fod, unpack=True) - else: - a = _loadFile(fod, c, alpha, wire, bc, texture, - smoothing, threshold, connectivity) - acts.append(a) - elif os.path.isdir(fod): - acts = _loadDir(fod, c, alpha, wire, bc, texture, - smoothing, threshold, connectivity) - if not len(acts): - colors.printc("~times Error in load(): cannot find", inputobj, c=1) - return None + if os.path.isfile(fod): ### it's a file + a = _load_file(fod, c, alpha, threshold, spacing, unpack) + acts.append(a) + elif os.path.isdir(fod):### it's a directory or DICOM + flist = os.listdir(fod) + if '.dcm' in flist[0]: ### it's DICOM + acts.append(loadDICOM(fod, spacing)) + else: ### it's a normal directory + utils.humansort(flist) + for ifile in flist: + a = _load_file(fod+'/'+ifile, c, alpha, threshold, spacing, unpack) + acts.append(a) + else: + colors.printc("~times Error in load(): cannot find", fod, c=1) if len(acts) == 1: + if not acts[0]: + colors.printc("~times Error in load(): cannot find", inputobj, c=1) return acts[0] + elif len(acts) == 0: + colors.printc("~times Error in load(): cannot find", inputobj, c=1) + return None else: return acts -def _loadFile(filename, c, alpha, wire, bc, texture, smoothing, threshold, connectivity): +def _load_file(filename, c, alpha, threshold, spacing, unpack): fl = filename.lower() + + ################################################################# other formats: if fl.endswith(".xml") or fl.endswith(".xml.gz"): # Fenics tetrahedral file - actor = loadDolfin(filename, c, alpha, wire, bc) + actor = loadDolfin(filename) elif fl.endswith(".neutral") or fl.endswith(".neu"): # neutral tetrahedral file - actor = loadNeutral(filename, c, alpha, wire, bc) + actor = loadNeutral(filename) elif fl.endswith(".gmsh"): # gmesh file - actor = loadGmesh(filename, c, alpha, wire, bc) + actor = loadGmesh(filename) elif fl.endswith(".pcd"): # PCL point-cloud format - actor = loadPCD(filename, c, alpha) + actor = loadPCD(filename) + actor.GetProperty().SetPointSize(2) elif fl.endswith(".off"): - actor = loadOFF(filename, c, alpha, wire, bc) - elif fl.endswith(".3ds"): # 3ds point-cloud format + actor = loadOFF(filename) + elif fl.endswith(".3ds"): # 3ds format actor = load3DS(filename) + elif fl.endswith(".foam"): # OpenFoam + reader = vtk.vtkOpenFOAMReader() + reader.SetFileName(filename) + reader.Update() + actor = Actor(reader.GetOutput(), c, alpha) + + ################################################################# volumetric: elif fl.endswith(".tif") or fl.endswith(".slc") or fl.endswith(".vti") \ or fl.endswith(".mhd") or fl.endswith(".nrrd"): - # tiff stack or slc mhd, or vti - img = loadImageData(filename) - actor = isosurface(img, smoothing, threshold, connectivity) + img = loadImageData(filename, spacing) + if threshold is not False: + actor = isosurface(img, threshold=threshold) + actor.color(c).alpha(alpha) + else: + if c is "gold" and alpha is 1: + c = ['b','lb','lg','y','r'] # good for blackboard background + alpha = (0.0, 0.0, 0.2, 0.4, 0.8, 1) + #c = ['lb','db','dg','dr'] # good for white backgr + #alpha = (0.0, 0.0, 0.2, 0.6, 0.8, 1) + actor = Volume(img, c, alpha) + + ################################################################# 2D images: elif fl.endswith(".png") or fl.endswith(".jpg") or fl.endswith(".bmp") or fl.endswith(".jpeg"): - actor = loadImage(filename, alpha) + if ".png" in fl: + picr = vtk.vtkPNGReader() + elif ".jpg" in fl or ".jpeg" in fl: + picr = vtk.vtkJPEGReader() + elif ".bmp" in fl: + picr = vtk.vtkBMPReader() + picr.Allow8BitBMPOff() + picr.SetFileName(filename) + picr.Update() + actor = Image() # object derived from vtk.vtkImageActor() + actor.SetInputData(picr.GetOutput()) + if alpha is None: + alpha = 1 + actor.SetOpacity(alpha) + + ################################################################# multiblock: + elif fl.endswith(".vtm"): + read = vtk.vtkXMLMultiBlockDataReader() + read.SetFileName(filename) + read.Update() + mb = read.GetOutput() + if unpack: + acts = [] + for i in range(mb.GetNumberOfBlocks()): + b = mb.GetBlock(i) + if isinstance(b, (vtk.vtkPolyData, + vtk.vtkImageData, + vtk.vtkUnstructuredGrid, + vtk.vtkStructuredGrid, + vtk.vtkRectilinearGrid)): + acts.append(b) + return acts + else: + return mb + + ################################################################# polygonal mesh: else: - poly = loadPolyData(filename) + if fl.endswith(".vtk"): + reader = vtk.vtkPolyDataReader() + elif fl.endswith(".ply"): + reader = vtk.vtkPLYReader() + elif fl.endswith(".obj"): + reader = vtk.vtkOBJReader() + elif fl.endswith(".stl"): + reader = vtk.vtkSTLReader() + elif fl.endswith(".byu") or fl.endswith(".g"): + reader = vtk.vtkBYUReader() + elif fl.endswith(".vtp"): + reader = vtk.vtkXMLPolyDataReader() + elif fl.endswith(".vts"): + reader = vtk.vtkXMLStructuredGridReader() + elif fl.endswith(".vtu"): + reader = vtk.vtkXMLUnstructuredGridReader() + elif fl.endswith(".txt"): + reader = vtk.vtkParticleReader() # (format is x, y, z, scalar) + elif fl.endswith(".xyz"): + reader = vtk.vtkParticleReader() + elif fl.endswith(".pvtk"): + reader = vtk.vtkPDataSetReader() + elif fl.endswith(".pvtr"): + reader = vtk.vtkXMLPRectilinearGridReader() + elif fl.endswith("pvtu"): + reader = vtk.vtkXMLPUnstructuredGridReader() + elif fl.endswith(".pvti"): + reader = vtk.vtkXMLPImageDataReader() + else: + reader = vtk.vtkDataReader() + reader.SetFileName(filename) + reader.Update() + poly = reader.GetOutput() + + if fl.endswith(".vts") or fl.endswith(".vtu"): # un/structured grid + gf = vtk.vtkGeometryFilter() + gf.SetInputData(poly) + gf.Update() + poly = gf.GetOutput() + if not poly: colors.printc("~noentry Unable to load", filename, c=1) return None - actor = Actor(poly, c, alpha, wire, bc, texture) + + actor = Actor(poly, c, alpha) if fl.endswith(".txt") or fl.endswith(".xyz"): actor.GetProperty().SetPointSize(4) + actor.filename = filename return actor -def _loadDir(mydir, c, alpha, wire, bc, texture, smoothing, threshold, connectivity): - if not os.path.exists(mydir): - colors.printc("~noentry Error in loadDir: Cannot find", mydir, c=1) - exit(0) - acts = [] - flist = os.listdir(mydir) - utils.humansort(flist) - for ifile in flist: - a = _loadFile(mydir+'/'+ifile, c, alpha, wire, bc, texture, - smoothing, threshold, connectivity) - acts.append(a) - return acts - - -def loadPolyData(filename): - """Load a file and return a ``vtkPolyData`` object (not a ``vtkActor``).""" - if not os.path.exists(filename): - colors.printc("~noentry Error in loadPolyData: Cannot find", filename, c=1) - return None - fl = filename.lower() - if fl.endswith(".vtk"): - reader = vtk.vtkPolyDataReader() - elif fl.endswith(".ply"): - reader = vtk.vtkPLYReader() - elif fl.endswith(".obj"): - reader = vtk.vtkOBJReader() - elif fl.endswith(".stl"): - reader = vtk.vtkSTLReader() - elif fl.endswith(".byu") or fl.endswith(".g"): - reader = vtk.vtkBYUReader() - elif fl.endswith(".vtp"): - reader = vtk.vtkXMLPolyDataReader() - elif fl.endswith(".vts"): - reader = vtk.vtkXMLStructuredGridReader() - elif fl.endswith(".vtu"): - reader = vtk.vtkXMLUnstructuredGridReader() - elif fl.endswith(".txt"): - reader = vtk.vtkParticleReader() # (x y z scalar) - elif fl.endswith(".xyz"): - reader = vtk.vtkParticleReader() - else: - reader = vtk.vtkDataReader() - reader.SetFileName(filename) - if fl.endswith(".vts"): # structured grid - reader.Update() - gf = vtk.vtkStructuredGridGeometryFilter() - gf.SetInputConnection(reader.GetOutputPort()) - gf.Update() - poly = gf.GetOutput() - elif fl.endswith(".vtu"): # unstructured grid - reader.Update() - gf = vtk.vtkGeometryFilter() - gf.SetInputConnection(reader.GetOutputPort()) - gf.Update() - poly = gf.GetOutput() - else: - try: - reader.Update() - poly = reader.GetOutput() - except: - poly = None - - if not poly: - return None - - cleanpd = vtk.vtkCleanPolyData() - cleanpd.SetInputData(poly) - cleanpd.Update() - return cleanpd.GetOutput() - - -def loadParallelData(filename): - fl = filename.lower() - if fl.endswith(".pvtk"): - reader = vtk.vtkPDataSetReader() - elif fl.endswith(".pvtr"): - reader = vtk.vtkXMLPRectilinearGridReader() - elif fl.endswith("pvtu"): - reader = vtk.vtkXMLPUnstructuredGridReader() - elif fl.endswith(".pvti"): - reader = vtk.vtkXMLPImageDataReader() - reader.SetFileName(filename) - reader.Update() - return reader.GetOutput() - - -def loadMultiBlockData(filename, unpack=True): - read = vtk.vtkXMLMultiBlockDataReader() - read.SetFileName(filename) - read.Update() - mb = read.GetOutput() - if unpack: - acts = [] - for i in range(mb.GetNumberOfBlocks()): - b = mb.GetBlock(i) - if isinstance(b, (vtk.vtkPolyData, - vtk.vtkImageData, - vtk.vtkUnstructuredGrid, - vtk.vtkStructuredGrid, - vtk.vtkRectilinearGrid)): - acts.append(b) - return acts - else: - return mb - - -def loadXMLGenericData(filename): - """Read any type of vtk data object encoded in XML format.""" - reader = vtk.vtkXMLGenericDataObjectReader() - reader.SetFileName(filename) - reader.Update() - return reader.GetOutput() - - +################################################################### def loadStructuredPoints(filename): - """Load a ``vtkStructuredPoints`` object from file. - - .. hint:: |readStructuredPoints| |readStructuredPoints.py|_ - """ + """Load a ``vtkStructuredPoints`` object from file.""" reader = vtk.vtkStructuredPointsReader() reader.SetFileName(filename) reader.Update() @@ -295,6 +275,15 @@ def loadRectilinearGrid(filename): return reader.GetOutput() +def loadXMLGenericData(filename): + """Read any type of vtk data object encoded in XML format.""" + reader = vtk.vtkXMLGenericDataObjectReader() + reader.SetFileName(filename) + reader.Update() + return reader.GetOutput() + + +################################################################### def load3DS(filename): """Load ``3DS`` file format from file. Return an ``Assembly(vtkAssembly)`` object.""" renderer = vtk.vtkRenderer() @@ -316,7 +305,7 @@ def load3DS(filename): return Assembly(acts) -def loadOFF(filename, c="gold", alpha=1, wire=False, bc=None): +def loadOFF(filename): """Read OFF file format.""" if not os.path.exists(filename): colors.printc("~noentry Error in loadOFF: Cannot find", filename, c=1) @@ -357,10 +346,10 @@ def loadOFF(filename, c="gold", alpha=1, wire=False, bc=None): ids += [int(xx) for xx in ts[1:]] faces.append(ids) - return Actor(buildPolyData(vertices, faces), c, alpha, wire, bc) + return Actor(buildPolyData(vertices, faces)) -def loadDolfin(filename, c="gold", alpha=0.5, wire=None, bc=None): +def loadDolfin(filename): """Reads a `Fenics/Dolfin` file format. Return an ``Actor(vtkActor)`` object.""" if not os.path.exists(filename): colors.printc("~noentry Error in loadDolfin: Cannot find", filename, c=1) @@ -408,21 +397,36 @@ def loadDolfin(filename, c="gold", alpha=0.5, wire=None, bc=None): connectivity.append([v0, v1, v2, v3]) poly = buildPolyData(coords, connectivity) - return Actor(poly, c, alpha, True, bc) + return Actor(poly, alpha=0.5) -def loadNeutral(filename, c="gold", alpha=1, wire=False, bc=None): +def loadNeutral(filename): """Reads a `Neutral` tetrahedral file format. Return an ``Actor(vtkActor)`` object.""" if not os.path.exists(filename): colors.printc("~noentry Error in loadNeutral: Cannot find", filename, c=1) return None + f = open(filename, "r") + lines = f.readlines() + f.close() + + ncoords = int(lines[0]) + fdolf_coords = [] + for i in range(1, ncoords + 1): + x, y, z = lines[i].split() + fdolf_coords.append([float(x), float(y), float(z)]) + + ntets = int(lines[ncoords + 1]) + idolf_tets = [] + for i in range(ncoords + 2, ncoords + ntets + 2): + text = lines[i].split() + v0, v1, v2, v3 = text[1], text[2], text[3], text[4] + idolf_tets.append([int(v0) - 1, int(v1) - 1, int(v2) - 1, int(v3) - 1]) - coords, connectivity = convertNeutral2Xml(filename) - poly = buildPolyData(coords, connectivity, indexOffset=0) - return Actor(poly, c, alpha, wire, bc) + poly = buildPolyData(fdolf_coords, idolf_tets, indexOffset=0) + return Actor(poly) -def loadGmesh(filename, c="gold", alpha=1, wire=False, bc=None): +def loadGmesh(filename): """Reads a `gmesh` file format. Return an ``Actor(vtkActor)`` object.""" if not os.path.exists(filename): colors.printc("~noentry Error in loadGmesh: Cannot find", filename, c=1) @@ -458,10 +462,10 @@ def loadGmesh(filename, c="gold", alpha=1, wire=False, bc=None): poly = buildPolyData(node_coords, elements, indexOffset=1) - return Actor(poly, c, alpha, wire, bc) + return Actor(poly) -def loadPCD(filename, c="gold", alpha=1): +def loadPCD(filename): """Return ``vtkActor`` from `Point Cloud` file format. Return an ``Actor(vtkActor)`` object.""" if not os.path.exists(filename): colors.printc("~noentry Error in loadPCD: Cannot find file", filename, c=1) @@ -494,24 +498,28 @@ def loadPCD(filename, c="gold", alpha=1): if not poly: colors.printc("~noentry Unable to load", filename, c="red") return False - actor = Actor(poly, colors.getColor(c), alpha) + actor = Actor(poly) actor.GetProperty().SetPointSize(4) return actor def loadDICOM(dirname, spacing=()): + '''Load A DICOM directory and return a ``Volume``.''' reader = vtk.vtkDICOMImageReader() reader.SetDirectoryName(dirname) reader.Update() image = reader.GetOutput() - print("scalar range:", image.GetScalarRange()) if len(spacing) == 3: image.SetSpacing(spacing[0], spacing[1], spacing[2]) - return image + return Volume(image) def loadImageData(filename, spacing=()): - """Read and return a ``vtkImageData`` object from file.""" + """Read and return a ``vtkImageData`` object from file. + DEPRECATED. + Use ``loadVolume`` instead. + E.g. `img = loadVolume('myfile.tif').imagedata()` + """ if not os.path.isfile(filename): colors.printc("~noentry File not found:", filename, c=1) return None @@ -522,7 +530,7 @@ def loadImageData(filename, spacing=()): reader = vtk.vtkSLCReader() if not reader.CanReadFile(filename): colors.printc("~prohibited Sorry bad slc file " + filename, c=1) - exit(1) + return None elif ".vti" in filename.lower(): reader = vtk.vtkXMLImageDataReader() elif ".mhd" in filename.lower(): @@ -530,6 +538,7 @@ def loadImageData(filename, spacing=()): elif ".nrrd" in filename.lower(): reader = vtk.vtkNrrdReader() if not reader.CanReadFile(filename): + colors.printc("~prohibited Sorry bad nrrd file " + filename, c=1) return None reader.SetFileName(filename) reader.Update() @@ -538,50 +547,23 @@ def loadImageData(filename, spacing=()): image.SetSpacing(spacing[0], spacing[1], spacing[2]) return image -def loadVolume(filename, spacing=(), c="blue", alphas=(0.0, 0.4, 0.9, 1)): - - img = loadImageData(filename, spacing) - return Volume(img, c, alphas) - -########################################################### -def loadImage(filename, alpha=1): - """Read a JPEG/PNG/BMP image from file. Return an ``Image(vtkImageActor)`` object. - - .. hint:: |rotateImage| |rotateImage.py|_ - """ - fl = filename.lower() - if ".png" in fl: - picr = vtk.vtkPNGReader() - elif ".jpg" in fl or ".jpeg" in fl: - picr = vtk.vtkJPEGReader() - elif ".bmp" in fl: - picr = vtk.vtkBMPReader() - picr.Allow8BitBMPOff() - else: - colors.printc("~times File must end with png, bmp or jp(e)g", c=1) - exit(1) - picr.SetFileName(filename) - picr.Update() - vactor = Image() # vtk.vtkImageActor() - vactor.SetInputData(picr.GetOutput()) - if alpha is None: - alpha = 1 - vactor.SetOpacity(alpha) - return vactor +########################################################### def write(objct, fileoutput, binary=True): """ - Write 3D object to file. + Write 3D object to file. (same as `save()`). Possile extensions are: - - vtk, vti, ply, obj, stl, byu, vtp, xyz, tif, png, bmp. + - vtk, vti, ply, obj, stl, byu, vtp, vti, mhd, xyz, tif, png, bmp. """ obj = objct - if isinstance(obj, Actor): + if isinstance(obj, Actor): # picks transformation obj = objct.polydata(True) elif isinstance(obj, (vtk.vtkActor, vtk.vtkVolume)): obj = objct.GetMapper().GetInput() + elif isinstance(obj, (vtk.vtkPolyData, vtk.vtkImageData)): + obj = objct fr = fileoutput.lower() if ".vtk" in fr: @@ -612,12 +594,39 @@ def write(objct, fileoutput, binary=True): w.SetFileDimensionality(len(obj.GetDimensions())) elif ".vti" in fr: w = vtk.vtkXMLImageDataWriter() + elif ".mhd" in fr: + w = vtk.vtkMetaImageWriter() elif ".png" in fr: w = vtk.vtkPNGWriter() elif ".jpg" in fr: w = vtk.vtkJPEGWriter() elif ".bmp" in fr: w = vtk.vtkBMPWriter() + elif ".xml" in fr: # write tetrahedral dolfin xml + vertices = obj.coordinates() + faces = obj.cells() + ncoords = vertices.shape[0] + ntets = faces.shape[0] + outF = open(fileoutput, "w") + outF.write('\n') + outF.write('\n') + outF.write(' \n') + outF.write(' \n') + for i in range(ncoords): + x, y, z = vertices[i] + outF.write(' \n') + outF.write(' \n') + outF.write(' \n') + for i in range(ntets): + v0, v1, v2, v3 = faces[i] + outF.write(' \n') + outF.write(' \n') + outF.write(" \n") + outF.write("\n") + outF.close() + return objct else: colors.printc("~noentry Unknown format", fileoutput, "file not saved.", c="r") return objct @@ -636,7 +645,17 @@ def write(objct, fileoutput, binary=True): colors.printc("~noentry Error saving: " + fileoutput, "\n", e, c="r") return objct +def save(objct, fileoutput, binary=True): + """ + Save 3D object to file. (same as `write()`). + + Possile extensions are: + - vtk, vti, ply, obj, stl, byu, vtp, xyz, tif, vti, mhd, png, bmp. + """ + return write(objct, fileoutput, binary) + +########################################################### def exportWindow(fileoutput, binary=False, speed=None, html=True): ''' Exporter which writes out the renderered scene into an OBJ or X3D file. @@ -688,51 +707,56 @@ def exportWindow(fileoutput, binary=False, speed=None, html=True): return +########################################################### +def buildPolyDataFast(vertices, faces=None, indexOffset=None): + """ + Build a ``vtkPolyData`` object from a list of vertices + where faces represents the connectivity of the polygonal mesh. -def convertNeutral2Xml(infile, outfile=None): - """Convert Neutral file format to Dolfin XML.""" - - f = open(infile, "r") - lines = f.readlines() - f.close() - - ncoords = int(lines[0]) - fdolf_coords = [] - for i in range(1, ncoords + 1): - x, y, z = lines[i].split() - fdolf_coords.append([float(x), float(y), float(z)]) + E.g. : + - ``vertices=[[x1,y1,z1],[x2,y2,z2], ...]`` + - ``faces=[[0,1,2], [1,2,3], ...]`` + """ + from vtk.util.numpy_support import numpy_to_vtk, numpy_to_vtkIdTypeArray - ntets = int(lines[ncoords + 1]) - idolf_tets = [] - for i in range(ncoords + 2, ncoords + ntets + 2): - text = lines[i].split() - v0, v1, v2, v3 = text[1], text[2], text[3], text[4] - idolf_tets.append([int(v0) - 1, int(v1) - 1, int(v2) - 1, int(v3) - 1]) + dts = vtk.vtkIdTypeArray().GetDataTypeSize() + ast = numpy.int32 + if dts != 4: + ast = numpy.int64 - if outfile: # write dolfin xml - outF = open(outfile, "w") - outF.write('\n') - outF.write('\n') - outF.write(' \n') + if not utils.isSequence(vertices): # assume a dolfin.Mesh + from dolfin import Mesh, BoundaryMesh + mesh = Mesh(vertices) + mesh = BoundaryMesh(mesh, "exterior") + vertices = mesh.coordinates() + faces = mesh.cells() - outF.write(' \n') - for i in range(ncoords): - x, y, z = fdolf_coords[i] - outF.write(' \n') - outF.write(' \n') + # must fix dim=3 of vertices.. todo + + poly = vtk.vtkPolyData() + vpts = vtk.vtkPoints() + vpts.SetData(numpy_to_vtk(vertices, deep=True)) + poly.SetPoints(vpts) - outF.write(' \n') - for i in range(ntets): - v0, v1, v2, v3 = idolf_tets[i] - outF.write(' \n') - outF.write(' \n') + cells = vtk.vtkCellArray() + if faces is not None: + nf, nc = faces.shape + dts = vtk.vtkIdTypeArray().GetDataTypeSize() + ast = numpy.int32 + if dts != 4: + ast = numpy.int64 + hs = numpy.hstack((numpy.zeros(nf)[:,None] + nc, faces)).astype(ast).ravel() + arr = numpy_to_vtkIdTypeArray(hs, deep=True) + cells.SetCells(nf, arr) + poly.SetPolys(cells) + else: + sourceVertices = vtk.vtkCellArray() + for i in range(len(vertices)): + sourceVertices.InsertNextCell(1) + sourceVertices.InsertCellPoint(i) + poly.SetVerts(sourceVertices) - outF.write(" \n") - outF.write("\n") - outF.close() - return fdolf_coords, idolf_tets + return poly def buildPolyData(vertices, faces=None, indexOffset=0): @@ -745,11 +769,8 @@ def buildPolyData(vertices, faces=None, indexOffset=0): - ``faces=[[0,1,2], [1,2,3], ...]`` Use ``indexOffset=1`` if face numbering starts from 1 instead of 0. - - .. hint:: |buildpolydata| |buildpolydata.py|_ """ - - if not utils.isSequence(vertices): # assume a dolfin.Mesh + if 'dolfin' in str(vertices): # assume a dolfin.Mesh faces = vertices.cells() vertices = vertices.coordinates() @@ -774,67 +795,67 @@ def buildPolyData(vertices, faces=None, indexOffset=0): showbar = False if len(faces) > 25000: showbar = True - pb = ProgressBar(0, len(faces), ETA=False) + pb = utils.ProgressBar(0, len(faces), ETA=False) for f in faces: n = len(f) - if n == 4: + if n == 4: #ugly but a bit faster: ele0 = vtk.vtkTriangle() ele1 = vtk.vtkTriangle() ele2 = vtk.vtkTriangle() ele3 = vtk.vtkTriangle() + if indexOffset: + for i in [0,1,2,3]: + f[i] -= indexOffset f0, f1, f2, f3 = f - if indexOffset: # for speed.. - ele0.GetPointIds().SetId(0, f0 - indexOffset) - ele0.GetPointIds().SetId(1, f1 - indexOffset) - ele0.GetPointIds().SetId(2, f2 - indexOffset) - - ele1.GetPointIds().SetId(0, f0 - indexOffset) - ele1.GetPointIds().SetId(1, f1 - indexOffset) - ele1.GetPointIds().SetId(2, f3 - indexOffset) - - ele2.GetPointIds().SetId(0, f1 - indexOffset) - ele2.GetPointIds().SetId(1, f2 - indexOffset) - ele2.GetPointIds().SetId(2, f3 - indexOffset) - - ele3.GetPointIds().SetId(0, f2 - indexOffset) - ele3.GetPointIds().SetId(1, f3 - indexOffset) - ele3.GetPointIds().SetId(2, f0 - indexOffset) - else: - ele0.GetPointIds().SetId(0, f0) - ele0.GetPointIds().SetId(1, f1) - ele0.GetPointIds().SetId(2, f2) - - ele1.GetPointIds().SetId(0, f0) - ele1.GetPointIds().SetId(1, f1) - ele1.GetPointIds().SetId(2, f3) - - ele2.GetPointIds().SetId(0, f1) - ele2.GetPointIds().SetId(1, f2) - ele2.GetPointIds().SetId(2, f3) - - ele3.GetPointIds().SetId(0, f2) - ele3.GetPointIds().SetId(1, f3) - ele3.GetPointIds().SetId(2, f0) + pid0 = ele0.GetPointIds() + pid1 = ele1.GetPointIds() + pid2 = ele2.GetPointIds() + pid3 = ele3.GetPointIds() + + pid0.SetId(0, f0) + pid0.SetId(1, f1) + pid0.SetId(2, f2) + + pid1.SetId(0, f0) + pid1.SetId(1, f1) + pid1.SetId(2, f3) + + pid2.SetId(0, f1) + pid2.SetId(1, f2) + pid2.SetId(2, f3) + + pid3.SetId(0, f2) + pid3.SetId(1, f3) + pid3.SetId(2, f0) sourcePolygons.InsertNextCell(ele0) sourcePolygons.InsertNextCell(ele1) sourcePolygons.InsertNextCell(ele2) sourcePolygons.InsertNextCell(ele3) + +# if n == 4: #problematic because of faces orientation +# ele = vtk.vtkTetra() +# pids = ele.GetPointIds() +# for i in reversed(range(4)): +# pids.SetId(i, f[i] - indexOffset) +# sourcePolygons.InsertNextCell(ele) elif n == 3: ele = vtk.vtkTriangle() + pids = ele.GetPointIds() for i in range(3): - ele.GetPointIds().SetId(i, f[i] - indexOffset) + pids.SetId(i, f[i] - indexOffset) sourcePolygons.InsertNextCell(ele) else: ele = vtk.vtkPolygon() - ele.GetPointIds().SetNumberOfIds(n) + pids = ele.GetPointIds() + pids.SetNumberOfIds(n) for i in range(n): - ele.GetPointIds().SetId(i, f[i] - indexOffset) + pids.SetId(i, f[i] - indexOffset) sourcePolygons.InsertNextCell(ele) if showbar: - pb.print("converting mesh..") + pb.print("converting mesh... ") poly = vtk.vtkPolyData() poly.SetPoints(sourcePoints) @@ -925,203 +946,6 @@ def close(self): colors.printc("~save Video saved as", self.name, c="green") return - -########################################################################### -class ProgressBar: - """ - Class to print a progress bar with optional text message. - - :Example: - .. code-block:: python - - import time - pb = ProgressBar(0,400, c='red') - for i in pb.range(): - time.sleep(.1) - pb.print('some message') # or pb.print(counts=i) - - |progbar| - """ - - def __init__(self, start, stop, step=1, c=None, ETA=True, width=24, char=u"\U000025AC"): - - char_arrow = u"\U000025BA" - if sys.version_info[0]<3: - char="=" - char_arrow = '>' - - self.start = start - self.stop = stop - self.step = step - self.color = c - self.width = width - self.char = char - self.char_arrow = char_arrow - self.bar = "" - self.percent = 0 - self.clock0 = 0 - self.ETA = ETA - self.clock0 = time.time() - self._remt = 1e10 - self._update(0) - self._counts = 0 - self._oldbar = "" - self._lentxt = 0 - self._range = numpy.arange(start, stop, step) - self._len = len(self._range) - - def print(self, txt="", counts=None): - """Print the progress bar and optional message.""" - if counts: - self._update(counts) - else: - self._update(self._counts + self.step) - if self.bar != self._oldbar: - self._oldbar = self.bar - eraser = [" "] * self._lentxt + ["\b"] * self._lentxt - eraser = "".join(eraser) - if self.ETA: - vel = self._counts / (time.time() - self.clock0) - self._remt = (self.stop - self._counts) / vel - if self._remt > 60: - mins = int(self._remt / 60) - secs = self._remt - 60 * mins - mins = str(mins) + "m" - secs = str(int(secs + 0.5)) + "s " - else: - mins = "" - secs = str(int(self._remt + 0.5)) + "s " - vel = str(round(vel, 1)) - eta = "ETA: " + mins + secs + "(" + vel + " it/s) " - if self._remt < 1: - dt = time.time() - self.clock0 - if dt > 60: - mins = int(dt / 60) - secs = dt - 60 * mins - mins = str(mins) + "m" - secs = str(int(secs + 0.5)) + "s " - else: - mins = "" - secs = str(int(dt + 0.5)) + "s " - eta = "Elapsed time: " + mins + secs + "(" + vel + " it/s) " - txt = "" - else: - eta = "" - txt = eta + str(txt) - s = self.bar + " " + eraser + txt + "\r" - if self.color: - colors.printc(s, c=self.color, end="") - else: - sys.stdout.write(s) - sys.stdout.flush() - if self.percent == 100: - print("") - self._lentxt = len(txt) - - def range(self): - """Return the range iterator.""" - return self._range - - def len(self): - """Return the number of steps.""" - return self._len - - def _update(self, counts): - if counts < self.start: - counts = self.start - elif counts > self.stop: - counts = self.stop - self._counts = counts - self.percent = (self._counts - self.start) * 100 - self.percent /= self.stop - self.start - self.percent = int(round(self.percent)) - af = self.width - 2 - nh = int(round(self.percent / 100 * af)) - if nh == 0: - self.bar = "["+self.char_arrow+"%s]" % (" " * (af - 1)) - elif nh == af: - self.bar = "[%s]" % (self.char * af) - else: - self.bar = "[%s%s%s]" % (self.char *(nh-1), self.char_arrow, " " *(af-nh)) - if self.percent < 100: # and self._remt > 1: - ps = " " + str(self.percent) + "%" - else: - ps = "" - self.bar += ps - - -############# -class Button: - """ - Build a Button object to be shown in the rendering window. - - .. hint:: |buttons| |buttons.py|_ - """ - - def __init__(self, fnc, states, c, bc, pos, size, font, bold, italic, alpha, angle): - """ - Build a Button object to be shown in the rendering window. - """ - self._status = 0 - self.states = states - self.colors = c - self.bcolors = bc - self.function = fnc - self.actor = vtk.vtkTextActor() - self.actor.SetDisplayPosition(pos[0], pos[1]) - self.framewidth = 3 - self.offset = 5 - self.spacer = " " - - self.textproperty = self.actor.GetTextProperty() - self.textproperty.SetJustificationToCentered() - if font.lower() == "courier": - self.textproperty.SetFontFamilyToCourier() - elif font.lower() == "times": - self.textproperty.SetFontFamilyToTimes() - else: - self.textproperty.SetFontFamilyToArial() - self.textproperty.SetFontSize(size) - self.textproperty.SetBackgroundOpacity(alpha) - self.textproperty.BoldOff() - if bold: - self.textproperty.BoldOn() - self.textproperty.ItalicOff() - if italic: - self.textproperty.ItalicOn() - self.textproperty.ShadowOff() - self.textproperty.SetOrientation(angle) - self.showframe = hasattr(self.textproperty, "FrameOn") - self.status(0) - - def status(self, s=None): - """ - Set/Get the status of the button. - """ - if s is None: - return self.states[self._status] - if isinstance(s, str): - s = self.states.index(s) - self._status = s - self.textproperty.SetLineOffset(self.offset) - self.actor.SetInput(self.spacer + self.states[s] + self.spacer) - s = s % len(self.colors) # to avoid mismatch - self.textproperty.SetColor(colors.getColor(self.colors[s])) - bcc = numpy.array(colors.getColor(self.bcolors[s])) - self.textproperty.SetBackgroundColor(bcc) - if self.showframe: - self.textproperty.FrameOn() - self.textproperty.SetFrameWidth(self.framewidth) - self.textproperty.SetFrameColor(numpy.sqrt(bcc)) - - def switch(self): - """ - Change/cycle button status to the next defined status in states list. - """ - self._status = (self._status + 1) % len(self.states) - self.status(self._status) - - # ############################################################### Mouse Events def _mouse_enter(iren, event):