Using Python Bindings

This page presents exemplary ways how to beneficially use the Python bindings of opals modules, partly in combination with other Python extensions. As a prerequisite, a basic understanding of the Python language and its standard library are necessary - otherwise reading Python in a minute is recommended. For instructions on how to debug Python code, see Debugging OPALS Python scripts.

Introduction

Aiming at fulfilling the needs of different users, every opals module is provided with 3 bindings:

  • executables,
  • shared libraries, and
  • Python modules.

While executables do not depend on any third-party software and may be run instantly, shared libraries offer the possibility for a tight integration into other software, but require compilation. As a drawback, usage of Python modules requires the availability of a Python interpreter. However, OPALS comes with an internal Python installation, and users may enjoy the rich set of language features and the typed, in-memory communication provided by Python bindings in contrast to the limited options available with executables, and the ease of a high-level, interpreted language as opposed to the need for compilation when using shared libraries.

Python (named after the BBC show Monty Python's Flying Circus) features outstanding characteristics, among which the following may be most important:

  • Python is an interpreted language, allowing for rapid prototyping,
  • can be run interactively,
  • comes with built-in, high-level data structures,
  • supports object-oriented programming with classes and multiple inheritance,
  • is at the same time dynamically and strongly typed, allowing for compact code that is easy to read, while still providing concise error messages via exceptions,
  • uses automatic memory management,
  • may be extended by modules implemented in other programming languages, e.g. allowing for expensive computations to be carried out in a compiled language (as done in OPALS modules),
  • may be embedded into other software (as done in OPALS),
  • runs on many platforms and operating systems, and
  • may be used, modified, included in (commercial) software and re-distributed free of charge.

This page aims at highlighting the benefits of using the Python bindings of OPALS modules. As a prerequisite, a one-minute-introduction to the Python language and its standard library are given in Python in a minute.

Working with OPALS modules

Parameter access

The Python bindings of OPALS modules reflect closely their C/C++ - counterparts: for each OPALS module, there exists a corresponding Python module which provides a Python class that derives from opals::IModuleBase's equivalent on the Python side ("opals.Base.Base"). Every such class comes with the respective module's specific parameters, as well as with all common and global parameters (see Parameter Categories). Every module parameter can be accessed as a Python property of that name. While specific parameters are accessible as properties of the class itself, common and global parameters are accessible as attributes of the attributes commons or globals, respectively.

Parameters as properties

from opals import Import
imp = Import.Import()
imp.commons.nbThreads = 12
imp.inFile = 'G111.las' # replacement for set_inFile
...
if imp.outFile is not None: # replacement for isSet_outFile
print( imp.outFile ) # replacement for get_outFile

Please note that the get_<parameterName>, set_<parameterName> and isSet_<parameterName> methods have been deprecated and removed in the builds following Sept. 25th, 2016.

Data type mapping

All data types exposed by OPALS modules are mapped to a certain Python data type. Mostly, this mapping is clear: e.g. C/C++'s int is mapped to Python's int, opals::String is mapped to Python's str, and opals::List is mapped to Python's list. For each enumeration, a custom data type is exposed on the Python-side, with constants for each enumerator.

import opals
from opals import GridFeature
grf = GridFeature.GridFeature()
grf.inFile = 'G111_z.tif'
grf.feature = [opals.Types.GridFeature.slpDeg] # in correspondence with \p opals::GridFeature

Not obvious is the mapping of C/C++ types that have no "natural" correspondence on the Python side. For some, but not all of these types, OPALS defines a custom data type on the Python side (e.g. opals::HistoStats), whose methods are thus accessible to the Python interpreter. C/C++'s std::pair, opals::Vector, and opals::Array are all mapped to Python's list. opals::Matrix, respectively, is mapped to a Python list of Python lists (which is easily converted to a numpy.matrix). Those types, for whom no special Python equivalent is defined, are represented as str in Python. Whether such a special type is defined, may be determined by usage of help.

Examples

In the following, two examples are presented that closely follow the ones presented in the example-sections for modules Module Bounds and Module Histo. As extensions to these examples that are conducted with OPALS modules only, simple visualizations are created here, using third-party modules. Kindly note that all needed third-party modules are included in the OPALS Python AddOns.

Strip overview

In this example, Module Import and Module Bounds are used to determine a tight outline of the 3 data sets G111.las, G112.las, and G113.las found in the OPALS demo-directory. Using these outlines, a combined plot is created using the Python module matplotlib, which provides basic 2D plotting functions. Package os comes with Python and provides basic operating system functionalities. Its sub-module path facilitates file path operations. As Module Bounds does not grant in-memory access to the resulting outline, but exports vector data files, these must be read in here. We choose the simple text-format xyz as export format, which stores the 3 coordinates of a point on each line, separated by white space. As Module Bounds exports a single, closed polyline, the topology is unambiguous. Module csv serves for reading in the xyz-file. The final result looks like this:

Overview of flight strips
import opals
from opals import Import, Bounds
import os, csv
# By convention, we abbreviate matplotlib as mpl
import matplotlib as mpl
# select the graphics kit to use
mpl.use("TkAgg")
# import does not work recursively,
# so we need to import the sub-module, too.
# By convention, we name it plt.
import matplotlib.pyplot as plt
# Change the working to the OPALS demo directory
os.chdir(os.path.join(opals.__path__[0], r'..\demo'))
hatches = ["/", "\\", "|", "-", "+", "x", "o"]
colors = ["b", "g", "r", "c", "m", "y", "k"]
# Loop over 3 lists at the same time.
# Built-in function 'zip' returns a list of tuples,
# where the i-th tuple contains the i-th element
# of each of the sequences passed as arguments to 'zip'.
# The shortest list determines the number of loops
for dataset, hatch, color in zip(["G111", "G112", "G113"], hatches, colors):
dataFn = dataset + ".las"
odmFn = dataset + ".odm"
boundsFn = dataset + "_bounds.xyz"
imp = Import.Import()
imp.inFile = dataFn
imp.run()
bnds = Bounds.Bounds()
bnds.inFile = odmFn
# Set the boundary type to result in a tight fit:
# using the enumerator 'alphaShape' of the enumeration 'BoundaryType'
bnds.boundsType = opals.Types.BoundaryType.alphaShape
bnds.outFile = boundsFn
bnds.run()
# csv.reader is an iterable object,
# reading a line of text on each iteration,
# and returning another object that
# iterates over the line split into tokens.
x, y = [], []
with open(boundsFn, 'rb') as fin:
reader = csv.reader(fin, delimiter=' ', skipinitialspace=True)
for line in reader:
x.append(float(line[0]))
y.append(float(line[1]))
# Plot a polygon using the lists x and y.
# No fill, but edges and a hatch.
plt.fill(x, y, hatch=hatch, color=color,
fill=False, linewidth=2, label=dataset)
# Label the axis
plt.title('Strip boundaries')
plt.xlabel('Easting [m]')
plt.ylabel('Northing [m]')
# Insert a legend. Use defaults i.e.
# all plot-handles of the axis,
# with labels as specified in plt.fill(..).
plt.legend()
# Ensure equal scales for both coordinates.
plt.axis('scaled')
# Actually draw the figure.
plt.show()

Augmented histogram

In this example, Module Import, Module Grid, Module Diff, Module Algebra, and Module Histo are used to generate a histogram of differences in height within the overlapping region of the 2 data sets strip19.las, and strip20.las found in the OPALS demo-directory. These overlapping regions are further restricted to smooth areas surrounded by point samples in every direction. For further details, see the respective example for Module Histo. As the OPALS-type opals::HistoStats exposes its data members to Python, the results are directly accessible in-memory. In addition to plotting the histogram itself, we overlay the normal probability density distribution derived from the mean and standard deviation returned by Module Histo. The final result looks like this:

Histogram of height differences, augmented with the corresponding normal probability density distribution. Obviously, the sample's curtosis is small.
import opals
from opals import Import, Grid, Diff, Algebra, Histo
import os, numpy as np, matplotlib as mpl
mpl.use("TkAgg")
import matplotlib.pyplot as plt
from matplotlib import mlab
os.chdir(os.path.join(opals.__path__[0], r'..\demo'))
datasets = ["strip21", "strip22"]
for ds in datasets:
imp = Import.Import()
imp.inFile = ds + ".laz"
imp.run()
for ds in datasets:
grid = Grid.Grid()
grid.inFile = ds + ".odm"
grid.interpolation = opals.Types.GridInterpolator.movingPlanes
grid.feature = [opals.Types.GridFeature.sigmaz,
opals.Types.GridFeature.excentricity]
grid.run()
inFns = []
for ds in datasets:
inFns.append(ds + "_z.tif")
inFns.append(ds + "_z_sigmaZ.tif")
inFns.append(ds + "_z_excen.tif")
fnDiff = "diff_21_22.tif"
alg = Algebra.Algebra()
alg.inFile = inFns
alg.outFile = fnDiff
alg.formula = #too long for print
r"r[0] and r[1]<0.1 and r[2]<0.8 and "\
"r[3] and r[4]<0.1 and r[5]<0.8 "\
"? r[0]-r[3]"\
": invalid"
alg.run()
hist = Histo.Histo()
hist.inFile = fnDiff
hist.sampleRange = [-0.15, 0.15]
hist.run()
histo = hist.histogram[0]
bins = histo.getBins()
diff, frequ = zip(*bins)
wid = diff[1] - diff[0]
plt.bar(diff, frequ, width=wid)
plt.xlabel(r'$\Delta z$')
plt.ylabel('$\mathrm{Relative\ frequency}$')
plt.title(r'$\mathrm{Histogram\ of}\ \Delta z$')
diffArr = np.array(diff)
centers = diffArr + wid / 2
nd = mlab.normpdf(centers, histo.getMean(), histo.getStdDev()) * wid
plt.plot(centers, nd, 'r')
plt.show()

Working with OPALS package scripts

OPALS packages are found in $OPALS_ROOT/opals/workflows.

Author
wk,le,lwiniwar
Date
09.11.2011