Using Arrlib from Python

  • Version : Scorpion Vision Version X
  • Date: 12apr2016
  • Written by: Arne Sommerfelt and Thor Vollset

Introduction

ArrLib is a library written in c for handling numerical arrays in an efficient way.

Included is also a large number of basic and advanced functions for numeric-, geometric- and image-processing. The Scorpion Vision Software relies on Arrlib and includes it in the form of a dynamically linked library - ArrLib.dll.

In order to give expert users easy access to ArrLib we have created the Python module called arrlibct.

The module is available from Scorpion Vision X. This module gives access to the full (more or less) set of functions and data types found in arrlib.

In addition arrlibct provides convenient ways to convert data to and from Numpy’s array type.

The arrlibct module is based on the standard library module ctypes in Python,- from which we have the ‘ct’ suffix of our module name.

Revision History:

  • 12apr2016, TV: * all arlibct samples
  • 29jan2016, TV: C
    • Modify layout - copyright notice
    • Add Script samples section
  • 08nov2012, AS: B - First release version

arrlibct versus pyArrlib

The Scorpion platform also supports an older binding to ArrLib.

This python module is imported simply as arrlib, but we usually refer to it as pyArrlib in order to distinguish it from the c-library itself. We realize that some confusion may arise from supporting two very similar python bindings to the same underlying library.

Our motivation for creating a new arrlibct python binding are the following:

1. Maintainability

  • arrlibct is a very direct mapping onto the underlying functions and data structures of arrlib. This allows us to automatically generate large portions of the interface.
  • As a result, more functionality is available for manipulating geometric objects than in pyArrlib, and it is constantly growing together with the underlying library.
  • arrlibct can be used to do fast prototyping and testing of new c-functions in arrlib. The resulting python code will have many similarities with the eventual pure C implementation.
  • With arrlibct it is possible to extend arrlib with user functions in C, compiled into separate DLL’s. These may be linked with arrlib to give a c-programming environment that is rich in functions and data types.
  • arrlibct only relies on the arrlib.dll, as opposed to pyArrlib which uses an intermediate python DLL (pyArrlib.pyd) as interface to the arrlib DLL.
  • Documentation for the c-library may be used directly since data types and functions have the same names.

2. Flexibility

  • arrlibct is more complete and consistent in in the way data types are accessed and manipulated.
  • Memory management is not tied to python, thus arrays created with arrlibct are not strictly owned python. They may be passed around and stored in user defined ways.
  • arrlibct has built in functions for conversion of data to and from pyArrlib and numpy so you can always switch from one to the other.

Our recommendation is that arrlibct is used for all new projects, and that arrays are converted to pyArrlib format whenever they are passed as parameters to Scorpion.

import arrlibct as al

# Assuming a 3D-camera like Sick IVP, this call will return a vector of XYZf:
pyPoints = GetImageMatr(...)

# The al.Arr constructor creates an arrlibct object giving access to
# the points in pyPoints. Note that pyPoints still owns the data.
# It must not be deleted until you have finished using
# the points from arrlibct. Otherwise an error will result.
points = al.Arr(pyPoints) # Conversion to arrlibct,- data are not copied.
p2 = points*Q # Do your transformations
p2 = p2.toArrlib() # Convert back to pyArrlib
SetImageMatr(p2)

# Plot 3D points
#Replace '3DModel' with your own image name below:
view=GetImageView().getImageViewer('3DModel')
view.pointColor='yellow'
view.addPoints(p2)

How to get help on arrlibct

The standard python help function will provide a description of the parameters and their type for all functions:

import arrlibct as al
help(al.nearestPntOnLin3d)

output

Help on function nearestPntOnLin3d in module arrlibct:
nearestPntOnLin3d(p, l)
    p: XYZd
    l: Lin3d
    returns: double

In addition to the standard help we have created a simple topic search function:

al.info('nearest')

output

Help on function nearestPntOnLin2d in module arrlibct:
nearestPntOnLin2d(p, l)
    p: XYd
    l: Lin2d
    returns: double
Help on function nearestPntOnLin2f in module arrlibct:
nearestPntOnLin2f(p, l)
    p: XYf
    l: Lin2f
    returns: float
Help on function nearestPntOnLin3d in module arrlibct:
nearestPntOnLin3d(p, l)
    p: XYZd
    l: Lin3d
    returns: double
Help on function nearestPntOnLin3f in module arrlibct:
nearestPntOnLin3f(p, l)
    p: XYZf
    l: Lin3f
    returns: float

Data types in ArrLib and arrlibct

Arrlib defines an extensive set of basic and geometric data types. Below we have listed the types with their original c-names. The corresponding arrlibct name is constructed simply by prepending c_. This is in accordance with the convention used by the ctypes module. Note that all help text containing type names is given without the c_ prefix, the user must remember to always add this when using the type names in python.

Fundamental types

Note that many of these types are identical to standard C-types.

  • Some of the types like uint, are just convenient shorthands for standard C-types.
  • The types with a number postfix are intended for use when it is important to control the word length and representation. For instance when transferring files or data to other platforms.
Type Description
char Standard C-type, platform and compiler dependant
short Standard C-type, platform and compiler dependant
int Standard C-type, platform and compiler dependant
long Standard C-type, platform and compiler dependant
uchar unsigned char, platform and compiler dependant
ushort unsigned short, platform and compiler dependant
uint unsigned int, platform and compiler dependant
ulong unsigned long, platform and compiler dependant
int8 Two’s complement signed 8 bit integer
int16 Two’s complement signed 16 bit integer
int32 Two’s complement signed 32 bit integer
int64 Two’s complement signed 64 bit integer
uint8 Unsigned 8 bit integer
uint16 Unsigned 16 bit integer
uint32 Unsigned 32 bit integer
uint64 Unsigned 64 bit integer
float Standard C-type, platform and compiler dependant
double Standard C-type, platform and compiler dependant
ldouble long double, platform and compiler dependant
float32 IEEE-754 standard 32 bit floating point number
float64 IEEE-754 standard 64 bit floating point number
Complexf Complex number: struct{float r,i;}
Complexd Complex number: struct{double r,i;}
Complex32 IEEE-754 standard 2x32 bit complex floating point number
Complex64 IEEE-754 standard 2x64 bit complex floating point number
ptr Generic pointer, same as void*, platform dependant
arr Generic array pointer, platform dependant
bits A chunk of bits. Used in bit-set arrays, Same as uint
fix An integer used for fast fixed-point calculations. Binary point in the middle.

Small vectors

Note that corresponding vector types exist for double, int and fix. Their names are found by simply exchanging the type suffix ‘f’ with one of ‘d’, ‘i’ or ‘x’ respectively.

Type struct Description
XYf struct{float x,y;} General-purpose 2-element float struct
XYZf struct{float x,y,z;} General-purpose 3-element float struct
XYZWf struct{float x,y,z,w;} General-purpose 4-element float struct
XYZWVf struct{float x,y,z,w,v;} General-purpose 5-element float struct

Supported operations

The following are supported expressions for small vectors of float and double:

Expression Left operand Right operand Result
a+b Small vector Same type as a Vector sum
a-b   Same type as a Vector difference
-a   Small vector Negate vector
a*b Small vector Same type as a Scalar product
a*b Small vector Scalar Vector scaling
a*b Scalar Small vector Vector scaling
a*b Small vector Small matrix Matrix multiplication
a*b Small vector Pose Rigid-body transform
a/b Small vector Pose Inverse rigid-body transform
a*~b Small vector Pose Inverse rigid-body transform
abs(a)   Small vector Length of a

Small matrices

The matrix types exist for f=float and d=double.

  • M**nk**f
  • M**nk**d

Where n and k are from 2 to 4.

Type struct
M22f struct{ float m11,m12,m21,m22;}
M33f struct{ float m11,m12,m13,m21,m22,m23,m31,m32,m33;}
M43f struct{ float m11,m12,m13,m21,m22,m23,m31,m32,m33,m41,m42,m43;}
M44f struct{ float m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44;}
M22d struct{ double m11,m12,m21,m22;}
M33d struct{ double m11,m12,m13,m21,m22,m23,m31,m32,m33;}
M43d struct{ double m11,m12,m13,m21,m22,m23,m31,m32,m33,m41,m42,m43;}
M44d struct{ double m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44;}

Supported operations

The following are supported expressions for small matrices of float and double:

Expression Left operand Right operand Result
a+b Small matrix Same type as a Matrix sum
a-b   Same type as a Matrix difference
-a   Small vector Negate matrix
a*b Small matrix Same type as a Matrix multiplication
a*b Small matrix Scalar Matrix scaling
a*b Scalar Small matrix Matrix scaling
a*b Small vector Small matrix Matrix multiplication
a*b Small vector Matrix extra row Homogeneous multiplication
a/b Small vector Square matrix Matrix Division
a*~b Small vector Square matrix same as a/b
abs(a)   Square matrix Determinant

Poses

These are similar to the 4x3 matrices above, but are meant to represent rigid body transformations.

These are supported by a set of functions that exploit the fact that the matrix is unitary and thus trivial and singularity free to invert.

Type struct Description
Pose3f struct{M33f U; XYZf t;} 3D rigid body transformation

Supported operations

The following are supported expressions for poses of float and double:

Expression Left operand Right operand Result
a*b Pose Same type as a Rigid-body transform multiply
a/b Pose Same type as a Inverse rigid-body multiply
~a   Pose Inverse
a*b Small vector Pose Rigid-body transform multiply
a/b Small vector Pose Inverse rigid-body transform.
a*~b Small vector Pose same as a/b
a*b Line Pose Rigid-body transform multiply
a/b Line Pose Inverse rigid-body transform.
a*~b Line Pose same as a/b

Ranges

Note that corresponding range types exist for double, int and fix. Their names are found by simply exchanging the type suffix ‘f’ with one of ‘d’, ‘i’ or ‘x’ respectively.

Type struct Description
Box1f struct{float min, max;} 1D range
Box2f struct{XYf min, max;} 2D range
Box3f struct{XYZf min, max;} 3D range

Parametric lines

Note that corresponding line types exist for double. Their names are found by simply exchanging the type suffix ‘f’ with ‘d’.

Type struct Description
Lin2f struct{ XYf p,v;} 2D parameterized line with starting point p and direction vector v.
Lin3f struct{ XYZf p,v;} 3D parameterized line with starting point p and direction vector v.

Some functions in Arrlib operate on 2D homogeneous lines. These are represented as small 3D vectors as listed above.

Supported operations

The following are supported expressions for lines of float and double:

Expression Left operand Right operand Result
a+b Line Scalar Parametric point at b on a - same as a.p+a.v*b
a-b Line Scalar Parametric point at -b on a - same as a.p-a.v*b
-a   Line Reverse direction vector
a*b Line Scalar Scale direction vector
a*b Line Small matrix Matrix multiply as though line’s end points were separately multiplied.
a*b Line Pose Rigid-body transform multiply
a/b Line Pose Inverse rigid-body transform. Same as a*~b
abs(a)   Line Length of line, same as abs(a.v)

Circles and spheres

The following datatypes are defined.

Type struct Description
Cir2f struct{XYf c; float r;} A circle
Cir3f struct{XYZf c; float r;} A sphere

Color pixels

The following datatypes are defined.

Type struct Description
RGB struct{uint8 b,g,r,pad;} Red, green and blue color
HSI struct{uint8 h,s; int16 i;} Hue, saturation and intensity.

RGB is compliant with with Windows bitmaps - 32 bit including 8 bit padding.

Important note on garbage collection

There is no automatic recycling of allocated arrays behind the scenes in ArrLib. This is unlike python where objects are automatically deleted when they become unreferenced.

ArrLib uses a simple and semi-automatic scheme that needs some attention by the programmer.

The basic principles are:

  • All arrays are by default linked into a chain when they are allocated.
  • ArrLib maintains a stack of these chains. Only topmost chain is used at any particular time.
  • In a local scope, typically at the start of a function we start a new chain.
  • At the end of the function (or scope) we mark the arrays we want to keep and delete all others in the chain

The typical code pattern is as follows:

al.newArrChain()
...
grid = al.gridPoints(a,b,False)
keepArr(grid)
deleteUnkept(1)

All arrays allocated after the call to newChain will be deleted by deleteUnkept except arrays that have been explicitly been marked for keeping by a call to keepArr, in this case only the array ‘grid’ will survive.

Geometric recipes

The operator overloadings described in the previous section lead to a number of simple solutions to basic geometric problems in the following we have tried to make a practical collection of examples.

Planar geometry

Assume that p,q,v are 2D points (or vectors) and l,l1,l2,m are 2D lines and M a 2x2 matrix. We use the float version of the data types in these examples.

Make a point

p = c_XYf(1,2)
p = c_XYf([1,2])

Vector from p to q

v = q-p

Make a line starting at p with direction v

l = c_Lin2f(p,v)
l = c_Lin2f([(1,2),(3,4)])

Make line from p to q

l = c_Lin2f(p,q-p)
l = pntToLin2f(p,q)

Unit vector from vector

u = unitVec2f(v)

Length of vector

s = vecLen2f(v)

Distance between two points

s = pntDistance2f(p,q)

Starting point on a line

p = l.p
p = l+0

Endpoint of line

q = l+1

Midpoint on line

p = l+0.5

Changing the endpoints of a line

l = resizeLin2f(l,start,end)

Make direction vector a unit vector

l = normalizeLin2f(l)

Middle normal to line

m = c_Lin2d(l+0.5,normalVec2f(l.v))
m = middleNormal2f(l)

Translate a point by addition

p +=  q

Translate a point by homogeneous transformation matrix

p = p*xlat2f(q)

Rotate and translate a point by homogeneous transformation matrix

p = p*rot2f(pi/4)*xlat2f(q)

Scaling a vector by homogeneous transformation matrix

p = p*scal2f(c_XYf(1,2))

Constructing 3x3 transformation matrices

M = c_M33f(1,2,3,4,5,6,7,8,9)
M = c_M33f([1,2,3,4,5,6,7,8,9])
M = c_M33f([[1,2,3],[4,5,6],[7,8,9]])

Special matrix constructions

print c_M33f(1)

'''
1.00 0.00 0.00
0.00 1.00 0.00
0.00 0.00 1.00
'''
print c_M33f(1,2,3)

'''
1.00 0.00 0.00
0.00 2.00 0.00
0.00 0.00 3.00
'''

Rotation matrix constructed from the angle of a vector

M = vecRot2f(c_XYf(1,2))

Intersection of two lines

t = c_XYf() # Allocate return variable for solution
d = linIntersectLin2f(l1,l2,t)
# The returned value is the determinant of the
# underlying equation system
# It will be non-zero if an intersection is found, i.e.
# when lines are not parallel:
if d:
  # t.x is the parameter position of the intersection on l1
  # t.y is the parameter position of the intersection on l2
  # When the parameter is in the range [0,1] the intersection
  # point is inside the line segment. Otherwise it is beyond
  # its ends. In any case the intersection point can be
  # computed by:
  p = l1+t.x # The intersection point

Nearest point on line

t = nearestPntOnLin2f(p, l) # Finds parametric position on l
q = l+t # The nearest point itself

Finding the intersection points of a circle and a line

t1 = c_float() # Allocate variables
t2 = c_float() # to be returned by reference
d = intersectLinCir2f(l, cir, t1, t2)
# d<0 : no intersection
# d=0 : line is tangent and t1=t2
# d>0 : two intersections at l+t1 and l+t2

Working with homogeneous lines and points in the plane

The use of homogeneous coordinates provides a elegant and alternative way to work with points and lines in the plane. In homogeneous coordinates a 2D point (x,y) is represented by a 3D vector (x,y,1). Homogeneous lines have, unlike parametric lines, no notion of starting point or end point and are represented by a 3D vector (a,b,c) such that any point (x,y) on the line satisfies ax+by+c=0

Conversion between homogeneous and ordinary coordinates:

p = hom2f(p) # Make 3D homogeneous vector from 2D point
p = unhom3f(p) # Project 3D homogeneous vector back to 2D
               # NOTE: That if p.z=0 an error is generated
               # because p is at infinity.

Computing the homogeneous line through two points

h = cross3f(p,q)

Computing the intersection point of two homogeneous lines

p = cross3f(h1,h2) # If lines are parallel then p.z=0

Distance from a point to a line

h = normalizeHomlin2f(h) # Makes (h.x,h.y) a unit vector
d = h*p # Distance is now a simple scalar product

Bisecting non parallel lines. The lines will generally form one acute and one obtuse angle we have created one function for each case

h = bisectAcuteHomlin2f(h1, h2)
h = bisectObtuseHomlin2f(h1, h2)

Conversion to/from parametric lines

l = homlinToLin2f(h)#Startpoint l.p is the point on h closest to origin
h = linToHomlin2f(l)

Finding the intersection points of a circle and a line

p1 = c_XYf() # Allocate variables
p2 = c_XYf() # to be returned by reference
d = intersectHomlinCir2f(hl, cir, p1, p2)
# d<0 : no intersection
# d=0 : line is tangent and p1=p2
# d>0 : two intersections p1,p2

Fitting points to a straight line

In the examples below assume that a set of points has been read as a list of tuples from Scorpion, and then converted to an ArrLib array. Note that we use the numpy module as our main tool for array handling. Assume that numpy has been imported as np:

import numpy as np
import arrlib as al
points = np.asarray(GetResultValue(‘mytool.points’)).astype(np.float32)
points  = al.Arr(points ,’XYf’) # Convert numpy array to Arrlib array.

Least squares fitting of a set of points to a homogeneous line

The rms parameter is optional, replace it with None when not needed.

rms = al.c_float() # Allocate variable to be returned by reference
h = al.fitHomlin(points, rms) # points is an array of XYf

Robust fitting of a set of points to a homogeneous line using the RANSAC method

The nGoodPoints parameter tells the function the minimum number points that may be assumed to be good (i.e. non-outliers).

Since the algorithm is based on random sampling of point pairs without trying all combinations, there is a risk that a solution is never found.

  • This is controlled by the risk parameter. Setting the risk to 0.001 means that the search will have an average failure rate of 1/1000.
  • Setting the risk low results in a longer search and slower execution of the function.
  • The fitTolerance determines threshold between points that are considered inlier or outlier.
  • Remember that when reducing the fitTolerance , nGoodPoints should be decreased accordingly based on your á priori knowledge of the distribution.
  • This will also in general lengthen the search in order to maintain the same risk of failure.
rms = al.c_float() # Allocate variable to be returned by reference
h = al.robustFitHomlin(points, nGoodPoints, risk, fitTolerance, None, rms)
if h.x==0 and h.y==0: # A null normal vector means failure
  print ‘No line found’

There is an optional parameter that can be used to get information about the set of inlier points that were fitted to the line.

  • iFit and returns an index vector with the indices of the inlier points.
iFit = al.c_arr() # Allocate variable that will return inlier indices
rms = al.c_float() # Allocate variable to be returned by reference
h = al.robustFitHomlin(points, nGoodPoints, risk, fitTolerance, iFit, rms)
if iFit.vecSize==0: # No points fitted means failure
  print 'rms',rms.value
else:
  inliers = al.indexedSubVec(p,iFit) # Make vector containing only inliers.

Transformation of normal vectors and homogeneous lines.

In general, to preserve perpendicularity of a normal vector n through a matrix multiplication it should be multiplied by the inverse transpose matrix.

n = n*~transp33f(M)

We have created special functions for normal vector transformations

n = muln2x33f(n, M)

Homogeneous lines are the projection of 3D plane normals, and should be transformed by the normal multiplication functions.

h = muln3x33f(h,M)

3D space geometry

Many of the planar (2D) line functions described above are available in 3D. Simply change part of the function name from ..2f to ..3f for point-functions and ..Lin2f to ..Lin3f for line functions.

A notable difference going from 2D to 3D is that there are no homogeneous lines in 3D. The closest analogy is planes, but unfortunately these do not support the same set of elegant and useful operations as homogeneous lines do.

3D point transformations

Below is an example on how to do basic transformations on 3D points using python and Scorpions built-in numerical library arrlib.

import arrlibct as al # Import python bindings for arrlib
points = [(1.2,3.4,4.5),(6.7,8.9,9.0)] # some points
points = al.Arr(points,'XYZd') # Convert points to arrlib vector of (XYZd) point structures.

R = al.rotx33d(0.1)*al.roty33d(0.2)*al.rotz33d(0.3) # Create a rotation matrix from a series of primitive rotations
t = al.c_XYZd(10,20,30) # A translation vector
Q = al.c_Pose3d(R,t) # Construct a rigid-body transformation structure called a 'pose'


p2 = points*Q # Transform your points by post-multiplying with pose
p2.prt() # Print contents of resulting arrlib array

p3 = p2*~Q # Do reverse transformation
p3.prt() # Print contents of resulting arrlib array

print p3[1].x # The points may be accessed by index and field (x,y,z)
p4 = p3.toNumpy() # Convert arrlib array to numpy array
print p4

p5 = p4.tolist() # Convert from numpy array to python list
print p5

Merging two point clouds in Scorpion

import arrlibct as alc
# Mark beginning of garbage collection scope.
# To be used in Scorpion python script tools.
# (The newArrChain/try/finally/deleteUnkept pattern below
# is not needed in BaseTool scripts.)
alc.newArrChain()
try:
  # Get pointclouds from Scorpion 3D images as pyArrlib vectors
  A = GetImageMatr('3D-ImageA')
  B = GetImageMatr('3D-ImageB')
  # Convert to arrlibct arrays
  A = alc.Arr(A)
  B = alc.Arr(B)
  # If point cloud A needs to be aligned with B through a rigid-body
  # transformation this can be done as follows:
  # Create a rotation matrix from a series of primitive rotations
  R = alc.rotx33f(0.1)*alc.roty33f(0.2)*alc.rotz33f(0.3)
  t = alc.c_XYZf(10,20,30) # Make a translation vector
  Q = alc.c_Pose3f(R,t) # Make Pose Q,- a rigid-body transformation structure.
  A = A*Q # Transform point-cloud A by multiplying with the pose
  # Merge the points into a single vector
  # Make AB,- a new array with room for both clouds
  AB = alc.newVec('XYZf',len(A)+len(B))
  # Copy A and B into AB
  alc.copyToSubVec(A,AB,0)
  alc.copyToSubVec(B,AB,len(A))
  AB = AB.toArrlib() # Pass ownership of arrlibct array to pyArrlib
  # Put merged cloud back into a Scorpion 3D image.
  ok = SetImageMatr('3D-Merged',AB)
  print ('failed','ok')[ok]
finally:
  # Delete all arrlibct arrays allocated since
  # last call to newArrChain().
  # Arrays given to pyArrlib, like AB above are unaffected.
  deleteUnkept(1)

Note

that arrlibct arrays can be printed by A.prt() method, but make sure that you turn on arrlib messages in Scorpion under General/Options/Show Arrlib messages. Alternatively you may want to se just the header info,- then use A.prh().

Intersecting lines in 3D

In general 3D lines do not intersect, but each line has a unique point that is closest to the other line. In the following example we compute the closest point on each line and also the parametric line between them.

l1 = c_Lin3f((-2,0,0),(4,0,0))
l2 = c_Lin3f((1,1,1),(1,2,0))
t = c_XYf()
k = c_Lin3f()
print linIntersectLin3f(l1,l2,t,k), t , k

'''
64.0 (0.63 -0.50) ((0.50 0.00 0.00) (0.00 0.00 1.00))
'''

The number returned by linIntersectLin3f is non-zero when the lines are not parallel and a valid solution is found. Note that t returns the parametric position of the points on each line. This can be used to compute the closest point on l1 and l2 as follows:

print l1+t.x, l2+t.y

'''
(0.50 0.00 0.00) (0.50 0.00 1.00)
'''

The distance between the lines is computed by:

print abs(k)

'''
1.0
'''

Python Script Samples

Sample 1 - Data conversion - Scorpion - arrlib - numpy

import numpy
A = arrlibct.Arr(ImageA).toNumpy() # Conversion to numpy arrays via arrlibct
B = arrlibct.Arr(ImageB).toNumpy()
AB = np.concatenate([A,B],axis=0)
z = AB[:,2]
ABfiltered = AB[(zMin<z) & (z<zMax)]
# Convert via arrlibct back to pyArrlib
ABfiltered = arrlibct.Arr(ABfiltered,'XYf').toArrlib()

Sample 2 - Color Image to HSI

import arrlibct as al
im_pyarr=GetImageMatr('Image1')
rgb = al.Arr(im_pyarr)
hsi = al.rgb2hsiMatr(rgb,4) # Convert entire image

x=100
y=200

print hsi[x,y]
# or just convert a single pixel
print al.rgb2hsiFast(rgb[x,y])

Sample 3 - Convert 2d tuple to Arr ‘XYf’

import arrlibct as al
import numpy as np

if not isinstance(plg,al.Arr):
  plg = al.Arr(np.array(plg,np.float32),'XYf')

Sample 4 - Filtering items based on various property values

Let us assume that we are working in the environment of a BaseTool in Scorpion. Furthermore assume that some GUI-elements have been defined and linked to a set of tag values of the tool.

The tag names adhere to a naming convention as follows: Assume we have two properties called ‘length’ and ‘angle’.

We would then have defined the tag values:

  • lengthMin
  • lengthMax
  • lengthMinEnable
  • lengthMaxEnable
  • lengthInvert
  • angleMin
  • angleMax
  • angleMinEnable
  • angleMaxEnable
  • angleInvert
def getConstraints(propNames):
    '''
    Retrieves constraints parameters from BaseTool GUI
    in a format suitable for use by propertyFilter.
    Input a list of property names that must be found
    among the user defined tag values of the enclosing tool.
    For each property name ‘prop’ there must be two scalar tags
    propMin,propMax, and three boolean tags propMinEnable, propMaxEnable
    and propInvert.
    If the property value should be converted by a function
    (e.g. from degrees to radians) the property can be specified as
    a tuple giving the name and the function (‘prop’,func).
    '''
    constr = al.newVec('Box1f',len(propNames))
    enabl = al.newVec('XYu',len(propNames))
    invert = al.newVec('uint',len(propNames))
    for i,pn in enumerate(propNames):
      if isinstance(pn,tuple):
        pn,fn=pn
      else:
        fn = lambda x:x

      constr[i].min = fn(self.getDynamicFloat(pn+'Min',-np.inf))
      constr[i].max = fn(self.getDynamicFloat(pn+'Max',+np.inf))
      enabl[i].x = tool.getIntValue(pn+'MinEnable')
      enabl[i].y = tool.getIntValue(pn+'MaxEnable')
      invert[i] = tool.getIntValue(pn+'Invert')
    return constr,enabl,invert

Here is an example of the calling code

# Some artificial data for the purpose of this example:
lengths = al.vec_float(10,100,102,103,95) # Some properties in an array.
angles = al.vec_float(0.01,-0.02,0.5,0.04,-0.01) # Some more properties.


# Collect all properties in an array of arrays.
# Note: All arrays must have same length.
properties = al.vec_arr(lengths,angles)

# Get the constraints from the GUI
constr,enab,inv = getConstraints(('length',('angle',np.radians)))

# Do the filtering
I = al.propertyFilter(properties,constr,enab,inv)
# The result is an array of indices of the items that have survived
# the filtering. To apply the filter you may do:
lengths = lengths[I] # Keep only the
angles = angles[I]   # accepted items.

In the above case the data are grouped by property. In other cases the data a grouped by item, i.e. the properties of each item come in chunks. A common case is a 3D point of type XYZf.

Below is a slightly contrived code snipped exemplifying this:

prop =vec_XYZf((1,4,7),(2,5,8),(3,6,9)) # An array of three 3D points.
filt = vec_Box1f((1,3),(5,7),(7,10)) # Constraints for the x-, y- and z-coord.
enab = vec_XYu((1,1),(1,1),(1,1)) # Three pairs of enable switches.
inv = vec_uint(0,0,0) # No inversion. Setting inv=None does the same.
I = propertyFilter(prop,filt,enab,inv)

NOTE: That propertyFilter currently is only implemented for float data.