Department of Photogrammetry, University of Bonn
Tutorial
This is the tutorial for the Java-library SUGR.
 
Overview
In this first steps you learn some basics about the java-library SUGR.
  1. Introduction
  2. Construction of certain 2D-Points and 2D-Lines
  3. Normalizations implemented in SUGR
  4. Construction of uncertain Entities
  5. Construct 3D-Entities; 3D-Line Introduction
  6. Gauss-Helmert-Estimation to construct an entity
  7. Transformations and ProjectiveCameras
 
Introduction back to overview
An entity is a line, point or a plane and in SUGR this entities are named like this:
  • Point_2D
  • Line_2D
  • Point_3D
  • Plane_3D
  • Line_3D

SUGR uses the framework of homogeneous coordinates, thus 2-dimensional entities are represented by 3-dimensional vectors. This vectors consist of a homogeneous and an euclidean part. If the homogeneous part of a 2D point, as the third entry of the corresponding vector, has a value of "1", you have euclidean coordinates in the first two entries.
 
Construction of certain 2D-Points and 2D-Lines back to overview
First you learn to construct points and lines in 2D in different ways. At first you give the values, later you construct one entity with the help of two others. Afterwards you will create an output for these entities.

At first we construct a point in 2-dimensional space.
//use constructor Point_2D(double,double)
//arguments: euclidean part
//homogeneous part ist set to "1"
Point_2D point1 = new Point_2D(2,3);
//this point is represented by the homogeneous vector (2,3,1)
Given two points, one can make a line easily.
//use a different constructor:
//Point_2D(double,double,double)
//first two arguments: euclidean part
//third argument: homogeneous part
Point_2D point2 = new Point_2D(4,0,1);
//use constructor Line_2D(Point_2D,Point_2D)
Line_2D line1 = new Line_2D(point1,point2);
With a second line given, you can construct the corresponding point of intersection. To construct a line, you should know, that the three entries of the 3-dimensional vector of a line are composed like this:
If the homogeneous subvector, containing of the first two elements, is normalized to "1", it is the normal of the line and the euclidean part (third entry) is the euclidean distance to the origin.
//use constructor:
//line_2D(double,double,double) using homogeneous coordinates
//first two arguments: homogeneous part
//third argument: euclidean part
Line_2D line2 = new Line_2D(3,6,1);
//use constructor:
//Point_2D(Line_2D,Line_2D);
Point_2D intersection = new Point_2D(line1,line2);
At last we want an output of this last intersection. In order to do this, we use the default output function toString.
//giving the Entity we want to be shown as argument
System.out.println("The calculated intersection is:\n" +intersection);
Following output is shown:
The calculated intersection is:
Vector:

74.0
-39.0
12.0

Covariance:

0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
 
Normalizations implemented in SUGR back to overview
If you are not familiar with homogeneous coordinates, you surely had problems with this output. To get a better understanding, you can normalize this point. If you use euclidean normalization, you get a vector with the normal euclidean point in the first two arguments and a "1" in the homogeneous part.
//normalize the point of intersection, we had as result before
intersection.normalizeEuclidean();
//generate output again
System.out.println("The normalized intersection is:\n" + intersection);
It should create this:
The normalized intersection is:
Vector:

6.166666666666666
-3.25
1.0

Covariance:

0.0 0.0 0.0
0.0 0.0 0.0
0.0 0.0 0.0
There are other normalizations implemented in SUGR. For the sake of completeness here are all of them together in one scheme:
  • normalizeEuclidean
  • normalizeEuclideanComparable
  • normalizeSphere
  • normalizeSphereComparable
The euclidean normalization calculates the length of the euclidean subvector and muliplies it's reciprocal with the vector. Afterwards, the subvector has the length of "1".

The spherical normalization divides the vector by it's length, normalizing it to a point on the corresponding standard sphere.

The two "comparable" versions of the normalizations change the sign of the vector, if the biggest absolute value of all entries is negative.
 
Construction of uncertain Entities back to overview
As the strength of SUGR is not to handle certain entities, but uncertain ones, this part is about uncertainty 2-dimensional entities, their construction, some calculations and a statistical test.

First we want to make some constructions very similar to those we saw before, but we include a covariance matrix to store uncertainties.
//sigma of first and second euclidean coordinate
double sigma=0.01;
//covariance between the first two arguments; there's a
//correlation between the two coordinates
double covar=0.0001;
double[][] covArray={{sigma, covar , 0}, {covar, sigma, 0}, {0, 0, 0}};
//because we want to create a point, who's euclidean coordinates are uncertain, the last coloumn and row are zero.
Matrix cov=LinearAlgebraFactory.getMatrix(covArray);
//use constructor Point_2D(double,double,double,Matrix)
//first three arguments: homogeneous coordinates of the point
//as we want to construct a point with euclidean coordinates (2,3), the homogeneous part is set to 1.
//last argument: Matrix(3x3) with uncertainties
Point_2D point1=new Point_2D(2,3,1,cov);
As you saw in this example, we used the class Matrix from the sugr.linalg library. If you want to handle matrices or vectors in SUGR, you have to use this library. Every necessary construction can be made with the class LinearAlgebraFactory.
If you only have accuracies for the 2 coordinates of the 2D-point (and no covariances, that means that there are no correlations modeled), you can also simply use the constructor Point_2D(x,y, sigma_x, sigma_y).

Next we construct a second point, which we use to construct a connecting line between the two points.
//constructor for Point_2D as above
//we use the above construction of the Matrix cov
Point_2D point2=new Point_2D(6,9,1);
//construct Line_2D through intersection of the two points
Line_2D intersection=new Line_2D(point1,point2);
It is important to know, that SUGR calculates the resulting covariance-Matrix for the line using error propagation.

With the given euclidean coordinates of the two points (2,3) and (6,9) you can easily see, that the point (4,6) is on the connecting line, but with the given uncertainties, it is not that sure, that the point (4.001,6.0001) with the same uncertainties is NOT on the line. We need to do a statistical test.
//first construct point, that is to be tested
Point_2D ToBeTested=new Point_2D(4.001,6.001,1,cov);
//construct a relation
//Relation(Entity,Entity,Class);
//first two arguments: Entities to be related in the decision
//third argument: Relation Class
Relation r=new Relation(ToBeTested,intersection,Incident.class);
//Print decision
System.out.println("Decision of the test: "+r.decision());
System.out.println("Used alpha : "+r.getAlpha());
System.out.println("Testvalue : "+r.testValue());
System.out.println("Test threshold : "+r.testThreshold());
Resulting in the following output:
Decision of the test: true
Used alpha : 0.95
Testvalue : 6.212134904271456E-6
Test threshold : 3.7479846752137687
Here is a list of all possible relations, that SUGR can handle:
  • Equal
  • Incident
  • Parallel
  • Orthogonal
 
Construct 3D-Entities; 3D-Line Introduction back to overview
3D-Points are represented very similar to the 2-dimensional case. The first three entries of the corresponding vector are the euclidean part and the last entry is the homogeneous part. If the homogeneous part is normalized to the value of "1", the euclidean part is the 3-vector in euclidean coordinates.

The representation of 3D-Planes is similar to the representation of 2D-Lines. The first three components are the homogeneous part, and if this part is normalized to the value of "1", the last entry (the euclidean part of the plane) is the euclidean distance to the origin and the homogeneous part is the normal of the plane.

In SUGR we use Plücker coordinates to represent 3D-lines. So the homogeneous and the euclidean part each consist of a tree dimensional vector. As not every 6-vector is a line, the Plücker vector has to fulfill the Plücker constraint, which means that the cross-product between the homogeneous and the euclidean part has to vanish. The best way to construct 3D-lines is to use other entities for construction, as a line is the join of two points, or the intersection of two planes. Use planes as input only in case you have generated them using either three points (this is a construction) or by fitting a plane through given entities using the Gauß-Helmert-estimation procedure (see below). This is because it is difficult to generate realistic covariance matrices for planes manually.
//constructing Matrix as covariance for points
//as before, the homogeneous part of points is certain
double[][] covPointsArray={{0.001,0.00001,0.00001,0}, {0.00001,0.001,0.00001,0}, {0.00001,0.00001,0.001,0}, {0,0,0,0}};
Matrix covPoints=LinearAlgebraFactory.getMatrix(covPointsArray);
//using constructor Point_3D(double,double,double,double)
//first three arguments: euclidean part
//fourth argument: homogeneous part, set to "1"
//last argument: Matrix of covariances
Point_3D point1=new Point_3D(3,2,5,1,covPoints);
Point_3D point2=new Point_3D(4,4,2,1,covPoints);
//constructing Matrix as covariance for planes
double[][] covPlanesArray={{0.001,0.00001,0.00001,0.00001}, {0.00001,0.001,0.00001,0.00001}, {0.00001,0.00001,0.001,0.00001}, {0.00001,0.00001,0.00001,0.001}};
Matrix covPlanes=LinearAlgebraFactory.getMatrix(covPlanesArray);
//using constructor Plane_3D(double,double,double,double)
//first three arguments: homogeneous part
//fourth argument: euclidean part
Plane_3D plane1=new Plane_3D(3,4,2,1,covPlanes);
Plane_3D plane2=new Plane_3D(5,2,2,1,covPlanes);
//using constructor Line_3D(Point_3D,Point_3D);
Line_3D lineFromPoints=new Line_3D(point1,point2);
System.out.println("Line from points:\n" + lineFromPoints);
//using constructor Line_3D(Plane_3D,Plane_3D);
Line_3D lineFromPlanes=new Line_3D(plane1,plane2);
System.out.println("Line from planes:\n" + lineFromPlanes);
Through this constructions, the lines have been given uncertainties through error propagation from the points or planes, that were used during construction.
Line from points: Vector:

1.0
2.0
-3.0
-16.0
14.0
4.0

Covariance:

0.0020 2.0E-5 2.0E-5 -9.999999999999999E-6 0.00693 -0.0059299999999999995
2.0E-5 0.0020 2.0E-5 -0.00694 -3.3881317890172014E-21 0.00694
2.0E-5 2.0E-5 0.0020 0.00593 -0.00693 1.0000000000000003E-5
-9.999999999999999E-6 -0.00694 0.00593 0.04864 -0.021880000000000004 -0.0228
0.00693 -3.3881317890172014E-21 -0.00693 -0.021880000000000004 0.053540000000000004 -0.017800000000000003
-0.0059299999999999995 0.00694 1.0000000000000003E-5 -0.0228 -0.0178 0.04456


Line from planes: Vector:

-4.0
-4.0
14.0
-2.0
2.0
0.0

Covariance:

0.02776 -0.0218 -0.01586 -4.000000000000001E-5 -0.00402 0.00592
-0.0218 0.041679999999999995 -0.011960000000000002 0.0041 6.000000000000001E-5 -0.00788
-0.01586 -0.011960000000000002 0.053559999999999997 -0.006040000000000001 0.00792 -2.0E-5
-4.000000000000001E-5 0.0040999999999999995 -0.00604 0.035840000000000004 0.021880000000000004 0.0159
-0.00402 6.000000000000001E-5 0.00792 0.021880000000000004 0.02188 0.01192
0.00592 -0.00788 -2.0E-5 0.0159 0.01192 0.00992
 
Gauss-Helmert-Estimation to construct an entity back to overview
There is another way for constructing entities in SUGR besides using a plain constructor and using geometric reasoning.
SUGR can estimate entities and even transformations for you, if redundancy is greater than zero.

What do you need to do, to achive that?
Well, let's say you have a bunch of points in 2D and want to lay a best fitting line through them.
So you construct a line, using the standard constructor without parameters.
//default constructor delivers an uninitialised line
//meaning, that Vector and Covariance are "null"
Line_2D bestFit = new Line_2D();
That line object has a function estimate(RelationalProperty[] rprops), that we will call later on to estimate the line. This function is inherited from Element, which indicates that you can estimate almost everything SUGR nows about.

So let's take a look at the parameter "rprops". When you browse a little through the SUGR Api Documentation you will eventually find the RelationalProperties interface. There are two implementations: BiRelationalProperty and TriRelationalProperty. We will use BiRelationalProperty here, more exactly a subtype of it, because BiRelationalProperty is abstract. As you may guess, these subtypes of BiRelationalProperty stand for relations between two entities.
There are four of these subtypes as mentioned before at Construction of uncertain Entities as relations, we can use for statistical tests. Be sure to take a closer look on them.

Now, what do we need to do for our estimation problem? We define an array of Incident objects, each containing one of our points, and tell them, that the point should be incident to a Line_2D object:
Point_2D somepoints = new Point_2D[nr_obs];

//somehow fill "somepoints" with points here.

Incident[] observations = new Incident[nr_obs];
for(int i = 0; i < nr_obs; i++){
observations[i] = new Incident(somepoints[i],Line_2D.class);
}
And with that array we call estimate on our line object:
bestFit.estimate(observations);
Now, what does that code do? We just tell SUGR, that we want all the points from "somepoints" to be incident to a Line_2D object. And now SUGR uses a Gauss Helmert Model to find the line which does that best.
If the iteration converged you have the best fitting line in your Line_2D object "bestFit" from above and can use it.


Maybe you don't have points lying on a line, but are close together and you need to calculate the center of gravity? With SUGR, that's easy, too.
As you may imagine we now use Equal instead of Incident. Let's change the example from above:
Point_2D centerOfGravity = new Point_2D(); //Point_2D that will hold the center of gravity.

Point_2D somepoints = new Point_2D[nr_obs];

//somehow fill "somepoints" with points here.

Equal[] observations = new Equal[nr_obs];
for(int i = 0; i < nr_obs; i++){
observations[i] = new Equal(somepoints[i],Point_2D.class);
}
Just like before we need some empty point, which will hold the center of gravity in the end. Then we fill an array of RelationalProperties. This time, we use Equal instead of Incident. That means we tell SUGR that all our points should be equal to another Point_2D and that it should find the best point for that, which is the center of gravity. That was not much different, eh?
Experiment a little bit. Estimation in SUGR is quite easy to use and very usefull.

Ah, not to forget, that you have to call the method estimate() on the centerOfGravity object again, just as before, but I think you got that.
If you want to look at the uncertainties of the estimated object, just generate an output as learned before at Construction of certain 2D-Points and 2D-Lines.
At last there is to mention, that you can mix relations by adding more observations with other subtypes of BiRelationalProperty before calling the method estimate() as done before.

One hint: You may have a problem where more than one homogenoues entity should be estimated in a single adjustment step, because the unknown entities depend on each other due to the observations. SUGR is not able to estimate more than one entity simultaneously, but there's a Matlab package which can do that available here.

Have fun with estimation!
 
Transformations and ProjectiveCameras back to overview
Transformations, as they are used in SUGR, are line preserving mappings from 3D to 3D or 2D to 2D.
There is also a class modelling a projective camera which can be used to construct a line preserving mapping from 3D to 2D. In addition, it can be used for shooting a viewing ray into 3D, making 3D-lines from 2D-points.

Transformations, in general, are represented as homogeneous transformation matrices. In SUGR this matrices are implemented as a column-vector with a corresponding covariance matrix as described before at Construction of uncertain Entities. From 3D to 3D, we have a vector with 16 entries, from 2D to 2D, it has 9 entries. A vector, representing a projective camera, has 12 entries, representing a 3x4 Matrix

3D-Transformations are:
  • Translation_3D
  • Rotation_3D
  • Motion_3D
  • ScaledMotion_3D
  • Homography_3D
2D-Transformations are:
  • Translation_2D
  • Rotation_2D
  • Motion_2D
  • ScaledMotion_2D
  • Homography_2D
At last there is the class ProjectiveCamera. The first example shows you the construction of a general 3D-Homography with covariance matrix and the transformation of a point.
//preconstructing point
double sigma=0.01;
double corr=0.0001;
double[][] covArray={{sigma, corr, corr, 0}, {corr, sigma, corr, 0}, {corr, corr, sigma, 0}, {0, 0, 0, 0}};
Matrix cov=LinearAlgebraFactory.getMatrix(covArray);
Point_3D point=new Point_3D(2,3,5,1,cov);
//Vector for Homography
double[] homography_array={1,5,3,11,1,6,2,8,4,3,2,5,4,0,1,2};
Vector homography_vector=LinearAlgebraFactory.getVector();
//covarince for homography
//using method getDiagMatrix(dim,b);
//this method generates a Matrix with dimension dim and value b on the diagonal and the 0 as every other entry.
Matrix homography_covariance=LinearAlgebraFactory.getDiagMatrix(16,0.001);
//using constructor Homography_3D(Vector,Matrix)
Homography_3D homography=new Homography_3D(homography_vector,homography_covariance);
//using preconstructed entities for transformation
Point_3D point_after_homography = homography.transform(point);
The second example transforms a 2D point via ProjectiveCamera to a 3D line.
//preconstructing point with corresponding covariance matrix
double sigma=0.01;
double corr=0.0001;
double[][] covArray={{sigma, corr, 0}, {corr, sigma, 0}, {0, 0, 0}};
Matrix cov=LinearAlgebraFactory.getMatrix(covArray);
Point_2D pointTwoD=new Point_2D(5,7,1,cov);
//covarince for ProjectiveCamera
Matrix cam_covariance=LinearAlgebraFactory.getDiagMatrix(9,0.001);
//using constructor ProjectiveCamera(double,double,double)
//the first argument is the focal length or principal distance of the camera
//the second and third argument are the coordinates of the principal point
ProjectiveCamera cam=new ProjectiveCamera(10,0.1,0.2,cam_covariance);
//using preconstructed entities for transformation
Line_3D point_after_cam = cam.transform(point);
It is also possible to estimate transformations. To collect all your observations, use TriRelationalProperty instead of BiRelationalProperty, because you have a relation between a tranformation and two entities, so 3 elements are involved.

Well, now you're through with the tutorial. If you need further help, look at the SUGR Api Documentation. Have much fun using SUGR.