Contours, Clipped Surfaces and Line Projection and Slices

The following methods use a common definition for

  • a vector direction, and
  • a distance

to control surface clipping and line construction. Often there is no ‘one-way’ of creating a particular plot. Complex visualizations are a result of sequential applications of these methods. Numerous Lines and Surface Contours examples demonstrate that various combinations of these methods may be used to produce a similar visualization.

In this section, line refers to an object of, or derived from, the class ColorLine3DCollection.

Surface Contours

Planar contour line objects which intersect a surface object with a plane are constructed using the surface object method:

line = surface.contourLines(*dist,**kwargs)

Multiple planes in the direction may be passed using the *dist distance parameter. The intersection plane is defined by the distance from the origin to the plane in the direction of the plane normal. The keyword arguments are:

key assignment values control
direction 3 element list or tuple intersection surface orientation
coor integer or string intersection surface type
color Matplotlib format color default to surface color
name string identifier

where direction is a normal vector of the intersection plane. When direction is None, the default direction is normal to the x-y plane, pointing in the z direction.

../../_images/contour_plane_1.png

In this figure, the blue vector is normal to the cyan intersection plane. The intersection plane distance from the origin is indicated by the red point. The red line is the intersecting contour of the surface object and the plane.

A contour line is composed of line segments, each one intersecting surface faces. As a result, the resolution of the contour line is a direct result of the surface rez from which the line is constructed.

Construction of planar contours on a conical surface is shown in the Surface Contours example.

In addition to planar contours, radial contours may be constructed by intersecting a surface object with a cylinder or sphere of a given radial distance using the same method. The additional named argument coor, indicates the type of intersection surface, as listed in the following table. When the direction is not specified, the default value is used.

Intersection Surface coor value direction (default) contour On
planar (default) 0, ‘p’, ‘P’, ‘planar’,’xyz’ [ 0, 0, 1 ] plane
cylindrical 1, ‘c’, ‘C’, ‘cylinder’, ‘cylinderical’ [ 0, 0, 1 ] vertical cylinder
spherical 2, ‘s’, ‘S’, ‘sphere’, ‘spherical’ not used sphere at origin
y-z plane 3, ‘x’, ‘X’ [ 1, 0, 0 ] constant x plane
x-z plane 4, ‘y’, ‘Y’ [ 0, 1, 0 ] constant y plane
x-y plane 5, ‘z’, ‘Z’ [ 0, 0, 1 ] constant z plane

Example radial contours are shown in the following figure, including the intersecting surfaces. The orientation of the cylindrical intersecting surface is indicated by the blue vector.

../../_images/contour_plane_2.png

For radial and cylindrical contours, the dist is a positive number indicating the radial distance to the intersection.

Evenly space contours on a surface object along a direction are constructed using the method:

line = surface.contourLineSet(numb, **kwargs)

where the parameter numb is an integer for the number of contours to be constructed. The spacing of the intersection planes are set between the minimum to maximum surface coordinates along the direction. The following figure shows a set of 5 planar contours for the example surface.

../../_images/contour_plane_3.png

Construction of the three types of contour sets on a icosahedron is shown in the Planar and Radial Contours example.

Contours take the color of the surface from which they are constructed. Hence, contour object colors can be set by first coloring the surface, or alternatively, may be set by coloring the resulting line object.

Surface Clipping

Surface objects may be clipped using several object methods based on:

  • Functional boolean criteria of face position.
  • Color channel value of face color
  • Surface boundary intersection of a 2D plane, cylinder or sphere.

Clipping a surface removes faces from the surface. The restore_from_clip surface method will set the surface geometry and color prior to clipping, negating any operations on the surface after clipping. Clipping can result in jagged surface edges which are the edge faces of the remaining surface. This jagged appearance is reduced by increasing the rez of the surface.

Functional

A function of surface coordinates may be used to clip the surface faces using the surface object method:

surface.clip(operation, usexyz)

where ‘operation’ is a function of the surface native coordinates which returns a boolean value. If xyz Cartesian coordinates are to be passed to the function, the boolean parameter usexyz is set to True. The default for usexyz is False.

Functional clipping is shown in the examples Clipping a Surface and Texture Surface with Geometric Mapping. Also, Earth Interior demonstrates using clipping for an animation.

Color Opacity

The surface face color can be used to indicate faces to be clipped using the method:

surface.clip_alpha(alphaCut,useval)

For colored surfaces with variations in the alpha color channel, alphaCut is a number between 0 and 1 indicating faces with alpha values below alphaCut which are to be removed. Alternatively, the boolean useval is set to True if, instead of using alpha value of the face color, the HSV V-value is used to remove surface faces. The default for useval is False.

The use of alpha clipping based on an image HSV values is shown in the Image Value Clipping example. Alpha clipping using colormapping is shown in the Geometric and Alpha Clipping example, which also illustrates using functional clipping.

Surface

Planar clipping uses a plane similarly defined as contour definitions. The method for plane clipping is:

surface.clip_plane(dist, **kwargs)

where the keyword arguments are:

key control
direction intersection surface orientation
coor projection surface

Consider the following example of contours on a sphere defined by the dist and direction values shown in following figure.

../../_images/clip_demo_1.png

The combination of direction and distance defines the contour. For example, a ‘negative’ direction and ‘negative’ distance define an identical plane with both ‘positive’ direction and distance. For example, the following two definitions of direction and distance are equivalent to define a plane for contours:

direction, dist = [  1.0,  0.5,  1.0 ] ,  0.35
direction, dist = [ -1.0, -0.5, -1.0 ] , -0.35

However, for clipping, the sign of these values defines not only the clipping plane, but which side of the surface is clipped. When the dist is positive, the side near the origin is retained.

../../_images/clip_demo_2.png

Planar surface clipping is shown in the Clipped Surface example.

As with planar contours, coor values of 1 or 2 will use cylindrical or spherical clipping, respectively. Examples of the three types of coordinate clipping are shown below.

../../_images/clip_plane.png

Note

When the dist is assigned a value beyond the domain of the surface boundary, an error will be thrown.

Line Clipping

Line objects may be clipped using line object methods based on:

  • Functional boolean criteria of the line segment positions.
  • Segment intersection of a 2D plane, cylinder or sphere.

Clipping a line object removes line segments. Line clipping is similar to surface clipping, with object methods:

line.clip(operation)

Note that the line object native coordinate system is Cartesian. As a result, unlike the functional clipping of a surface, the line clip method does not have the argument usexyz.

As with surface clip_plane method, clipping may be based on a plane, a cylinder or sphere by using the coor argument.:

line.clip_plane(dist,**kwargs)

which is similar to the surface plane clipping, where the keyword arguments are:

key control
direction intersection surface orientation
coor projection surface

Functional line clipping of contours lines, along with surface clipping, is shown in the Clipping Surface and Contours example.

Line Projection

Any line object may be projected onto a plane, cylinder or sphere using the line method:

line.map_to_plane(dist,**kwargs)

where the arguments are similar to the plane clip_plane method argument definitions.

Projections of lines to a plane and sphere are shown in the Contour Projections and Earth Elevation Projected Contours examples.

Line Slices

Line slices are contours in x-z or y-z planes created using ParametricLine object methods.

Visualizations may be similar in appearance to planar surface contours in directions [1,0,0] and [0,1,0] however:

  1. Line slices may only be in planes normal to [1,0,0] and [0,1,0].
  2. Line slice Z coordinates must be uniquely defined by the x,y coordinate, z=f(x,y) .
  3. Functions, not surfaces define the contour, as a result
    • resolution is controlled by the line object, not a surface.
    • line color is not a default surface color. Since slices are a line object, all coloring methods may be applied to the slices.
    • line ‘shading’ is dependent on alignment with light direction, not surface normals (line normals are not unique)

The advantage of using a line slice compared to a surface contour is that a higher resolutions can be used without creating a high rez planar surface. This is particularly useful for datagrids with a large resolution.

Starting by instantiating a ParametricLine object, with the operation=None, the object is mapped to slices as:

line.map_xySlice_from_op(operation, **kargs)

where operation is a function that takes one 3XN array of x,y,z coordinates (z value is ignored) and returns a 3XN array of coordinate values (only z value is used). This definition argument and return value is consistent with the use of operations for geometric mapping of surfaces.

The keyword arguments set the lines in the line collection that are constructed:

key value meaning
xplane x line of constant x
yplane y line of constant y
xset N N number of lines of constant x from the min to max X domain
yset N N number of lines of constant y from the min to max Y domain
xlim [min,max] X domain of operation ( default: [-1,1] )
ylim [min,max] Y domain of operation ( default: [-1,1] )

The xlim and ylim arguments may be a single float value to set domains centered at the origin.

Slices are constructed using the method:

line.map_xySlice_from_dadtagrid(datagrid, **kargs)

where the datagrid sets the Z values of the lines. The kargs is similar to the above with the exception that the xlim and ylim keys are not available. Similar to that for surface, datagrid slices may be scaled using the transform method.

Numerous examples of slices are given in the Line Slices examples, including various comparisons to contours constructed from surfaces.

Source Code

Contours

The following is the script for generating the contour figures.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
import s3dlib.surface as s3d

#.. Guide Surface Contour Figures

# # 1. Define functions to examine ....................................

rez = 5
illum = [-.5,1,1]
direction, dirRot, dist = [0,.5,1], -26.565, 0.4
unitDir = np.divide( direction, np.linalg.norm(direction) )

def deflate(rtp) :
    r,t,p = rtp
    scale = 0.2
    Rz = np.cos(p)
    Rxys = (1-scale)*np.sin(p) + scale*np.cos(4*t)
    R = np.sqrt( Rz**2 + Rxys**2)
    return R,t,p

def cont_plane(xyz) :
    x,y,z = xyz
    u,v,w = np.divide( direction, np.linalg.norm(direction) )
    Z = ( dist - u*x - v*y ) / w
    return x,y,Z

def getSurface(rez) :
    surface = s3d.SphericalSurface(rez,basetype='octa',color=[1,.9,.7,0.35],lw=0)
    surface.map_geom_from_op(deflate)
    surface.transform(s3d.eulerRot(-10,35,25,useXconv=False))
    return surface

# Figure 1 : Planar Contour =============================================================

surface = getSurface(rez).shade(.0,illum)
plane = s3d.PlanarSurface(rez,color=[0,0.35,0.35,0.5],lw=0)
plane.map_geom_from_op(cont_plane)
composite = surface+plane
composite.set_linewidth(0)

contour = surface.contourLines(dist,direction=direction,color='r',coor='p')
contour.set_linewidth(3)

xyz= unitDir*dist
uvw=np.array( [direction])
vector = s3d.Vector3DCollection(xyz,uvw,alr=0.15,color='b')
line = s3d.SegmentLine([[0,0,0],xyz],color='k',lw=3)

fig = plt.figure(figsize=(3.5,3.5))
fig.text(0.95,0.01,'Figure 1', ha='right', fontsize='x-small')
ax = plt.axes(projection='3d')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
s3d.standardAxis( ax )
ax.set_title('Planar Contour')
ax.add_collection3d(composite)
ax.add_collection3d(contour.fade(0,30,30))
ax.add_collection3d(vector)
ax.add_collection3d(line)
ax.scatter(*xyz.T, c='r', edgecolor='k', s=50)

fig.tight_layout()

# Figure 2: Cylinderical & Spherical Contours ===========================================
cdist,sdist = 0.6, 0.8
uvw=np.array( [unitDir])*2
vector = s3d.Vector3DCollection(-uvw,2*uvw,alr=0.025,color='b')

planes = [None]*2
planes[0] = s3d.CylindricalSurface(rez,color=[0,0.5,0.5,0.5],lw=0 )
planes[0].transform(scale=[cdist,cdist,1])
planes[0].transform( s3d.eulerRot(0,dirRot))
planes[1] = s3d.SphericalSurface(rez,color=[0,0.5,0.5,0.5],lw=0).domain(sdist)

dist =       [cdist, sdist]
directions = [direction,None]
stype =      ['Cylindrical', 'Spherical']

fig = plt.figure(figsize=(7,3.5))
fig.text(0.95,0.01,'Figure 2', ha='right', fontsize='x-small')
for i,plane in enumerate(planes) :
    surface = getSurface(rez)
    composite = surface + plane
    composite.set_linewidth(0.01)
    contour = surface.contourLines(dist[i],direction=directions[i],coor=i+1,color='r')
    contour.set_linewidth(3)
    ax = fig.add_subplot(121+i, projection='3d')
    ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
    s3d.standardAxis( ax, offset=0.0 )
    ax.set_title(stype[i] + ' Contours')

    ax.add_collection3d(composite.shade(.0,illum))
    ax.add_collection3d(contour.fade(ax=ax))
    if i == 0 : ax.add_collection3d(vector)

fig.tight_layout()

# Figure 3 : Planar Contour Set =========================================================

surface = getSurface(rez)

uvw=np.array( [unitDir])*1.5
vector = s3d.Vector3DCollection([[0,0,0]],uvw,alr=0.15,color='b')

contour = surface.contourLineSet(rez,direction=direction, color='r', coor='p')

fig = plt.figure(figsize=(3.5,3.5))
fig.text(0.95,0.01,'Figure 3', ha='right', fontsize='x-small')
ax = plt.axes(projection='3d')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
s3d.standardAxis( ax )
ax.set_title('Planar Contour Set')

ax.add_collection3d(surface.shade(.0,illum))
ax.add_collection3d(contour.fade(ax=ax))
ax.add_collection3d(vector)

fig.tight_layout()

# =======================================================================================
plt.show()

Clipping

The Following is the script for generating the clipping figures.

import numpy as np
import matplotlib.pyplot as plt
import s3dlib.surface as s3d
import s3dlib.cmap_utilities as cmu  

#.. Guide Surface Clipping Figures

# # 1. Define functions to examine ....................................
color=[1,.9,.7]

def deflate(rtp) :
    r,t,p = rtp
    scale = 0.2
    Rz = np.cos(p)
    Rxys = (1-scale)*np.sin(p) + scale*np.cos(4*t)
    R = np.sqrt( Rz**2 + Rxys**2)
    return R,t,p

def getSurface(rez) :
    surface = s3d.SphericalSurface(rez,basetype='octa',color=[1,.9,.7])
    surface.map_geom_from_op(deflate)
    surface.transform(s3d.eulerRot(-10,35,25,useXconv=False))
    return surface

# Figure A : Planar Contours ============================================================
rez=5
surface = s3d.SphericalSurface(rez,color=color,lw=0)
surface.shade().set_surface_alpha(0.3)
line1 = surface.contourLines(0.35,direction=[1,0.5,1], coor='p', color='r')
line2 = surface.contourLines(-.35,direction=[1,0.5,1], coor='p', color='b')

fig = plt.figure(figsize=(3.5,3.5))
fig.text(0.98,0.01,'Figure A', ha='right',fontsize='x-small')
ax = plt.axes(projection='3d')
ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
ax.set_title("[1, 0.5, 1]\n"+ r"$\pm$ 0.35",fontsize='medium')
ax.add_collection3d(surface)
ax.add_collection3d(line1.fade(0.2))
ax.add_collection3d(line2.fade(0.2))
fig.tight_layout()

# Figure B : Sphere Clipping ============================================================
rez, pv, nv, d = 6, [1,0.5,1] , [-1,-.5,-1] , 0.35
directions = [ pv, nv, pv, nv ]
distances =  [ d,  -d,  -d, d ]
minmax = (-0.7,0.7)
illum = [-.5,1,1]

fig = plt.figure(figsize=(4,4))
fig.text(0.98,0.01,'Figure B', ha='right', fontsize='x-small')
for i in range(4) :
    surface = s3d.SphericalSurface(rez,color=color)
    direction = directions[i]
    dist = distances[i]
    surface.clip_plane(dist,direction=direction)
    # ...................... 
    ax = fig.add_subplot(2,2,1+i, projection='3d')
    ax.set(xlim=minmax, ylim=minmax, zlim=minmax )
    fColor = 'r' if i<2 else 'b'
    ax.set_title( str(direction)+'\n'+str(dist), fontsize='small', color=fColor)
    ax.set_axis_off()
    ax.add_collection3d(surface.shade(ax=ax))
fig.tight_layout()

# Figure C : Three Types of Clipping Planes =============================================

rez = 7
direction, dirRot = [0,.5,1], -26.565
unitDir = np.divide( direction, np.linalg.norm(direction) )
negDir = [-direction[0],-direction[1],-direction[2]]
dist, cdist, sdist = 0.4, 0.6, 0.8
pvals = [  
    [ [direction, dist ] , [negDir,     -dist] ],
    [ [direction, cdist] , [direction, -cdist] ],
    [ [direction, sdist] , [direction, -sdist] ]
]
titleType = [ 'Planar','Cylindrical','Spherical']
fig = plt.figure(figsize=(6,9))
fig.text(0.98,0.01,'Figure C', ha='right', fontsize='x-small')
i = 0
for sys in range(3) :
    for pln in range(2) :
        ax = fig.add_subplot(321+i, projection='3d')
        i += 1
        ax.set(xlim=(-1,1), ylim=(-1,1), zlim=(-1,1))
        ax.view_init(30,30)
        ax.set_axis_off()
        c = pvals[sys][pln]
        cc = '' if sys == 2 else c[0]
        title = "{} Clipping\n{}   {}".format(titleType[sys],cc,c[1])
        ax.set_title(title,fontsize='small')
        surface = getSurface(rez).shade(direction=illum,ax=ax)
        surface.clip_plane(c[1],direction=c[0],coor=sys)

        ax.add_collection3d(surface)
    
fig.tight_layout(pad=0)

# =======================================================================================

plt.show()