Build a Graph#

The NavAbilitySDK provides variables and factors useful to robotics. We start with a Pose2 variable, i.e. position and orientation for a vehicle traveling on a flat XY plane.

As before, let’s setup a new client-context to talk with the NavAbility platform:

from navability.entities import DFGClient, VariableType, PriorPose2, FullNormal
from navability.services import addVariable, ls, addFactor, lsf
import uuid, random, string

userLabel = "guest@navability.io"
robotLabel = "TestRobot"
sessionLabel = "TestPy"

# also create a client connection
fgclient = DFGClient(userLabel, robotLabel, sessionLabel)

Adding Variables#

Warning

SDK.py@v0.6.0 currently does not support creating new sessions from “guest”, but variables and factors can be added to an existing session.

Let’s start with addVariable

by adding a randomly named variable an existing graph. Here we call addVariable as type VariableTypes.Pose2. Pose2 here refers to position an orientation on a flat 2D plane – i.e. the variable represents the estimation problem to find the state of variable with freedoms [x,y,theta]:

# generate a random variable label
vlabel = 'x_'+''.join(random.choices(string.ascii_letters + string.digits, k=4))

# add the variable to the existing session under guest
v = addVariable(fgclient, vlabel, VariableType.Pose2)
# result_v0 = await addVariable(fgclient, "x0", VariableType.Pose2)

Tip

Note that asynchronous tasks could increase client side upload performance. Each of these events are queued on the server for processing. While the variable is being created.

Adding Factors#

Next, we can also add a factor. Have a look at the addFactor function followed by how a user defines the factor measurement and observation model in the next section.

A Zero-Prior#

Let’s first add a Prior. Priors are absolute information about variables.

Tip

NavAbility factor graph solutions at this time require a gauge to exist for the graph being solved, hence enough prior information must be included in the graph to constrain the solution (bundles/orbits/locii) to a single solution. Note that this does not imply solutions are necessarily unimodal (Gaussian).

In this example, we’ll use the estimated starting point of our robot. We use unary factors called priors to represent absolute information to be introduced. In this case we use PriorPose2, as our variable type is also Pose2. Since factors represent a probabilistic interaction between variables, we need to specify the distribution our factor will represent. Here we use FullNormal which is a multivariate normal distribution.

Let’s create a PriorPose2 unary factor with zero mean and a covariance matrix of (diagm([0.05,0.05,0.01].^2)):

prior_distribution = FullNormal(mu=np.zeros(3), cov=np.power(np.diag([0.1, 0.1, 0.1]),2))
result_f0 = addFactor(fgclient, ["x0"], PriorPose2(Z=prior_distribution)) 
# result_f0 = await addFactorAsync(fgclient, ["x0"], PriorPose2(Z=prior_distribution)) 

Odometry Factor#

An odometry factor connects two consecutive robot poses x0 and x1 together to form a chain. Here we use a relative factor of type Pose2Pose2 with a measurement from pose x0 to x1 of (x=1.0,y=0.0,θ=pi/2); the robot drove 1 unit forward (in the x direction). Similarly to the prior we added above, we use a FullNormal distribution to represent the odometry with mean and covariance:

result_v = addVariable(fgclient, "x1", VariableType.Pose2)
print(f"Added x1 with result ID {result_v}")

odo_distribution = FullNormal(mu=[1.0, 0.0, np.pi/2], cov=np.power(np.diag([0.1, 0.1, 0.01]),2))
result_fac_1 = addFactor(fgclient, ["x0", "x1"], Pose2Pose2(Z=odo_distribution)) 
print(f"Added factor with result ID {result_fac_1}")

Adding Different Sensors#

So far we worked with the Pose2 factor type. Among others, NavAbilitySDK also provides the Point2 variable and Pose2Point2BearingRange factor types, which we will use to represent a landmark sighting in our factor graph. We will add a landmark l1 with bearing range measurement of bearing=(mu=0,sigma=0.03) range=(mu=0.5,sigma=0.1) and continue our robot trajectory by driving around in a square.

addVariable(fgclient, "l1", VariableType.Point2)
addVariable(fgclient, "x2", VariableType.Pose2)
addVariable(fgclient, "x3", VariableType.Pose2)
addVariable(fgclient, "x4", VariableType.Pose2)

addFactor(fgclient, ["x0", "l1"], Pose2Point2BearingRange(Normal(0.0,0.03), Normal(0.5,0.1)))
addFactor(fgclient, ["x1", "x2"], Pose2Pose2(odo_distribution))
addFactor(fgclient, ["x2", "x3"], Pose2Pose2(odo_distribution))
addFactor(fgclient, ["x3", "x4"], Pose2Pose2(odo_distribution))

One Loop-Closure Example#

The robot continued its square trajectory to end off where it started. To illustrate a loop closure, we add another bearing range sighting to from pose x4 to landmark l1, solve the graph and plot the new results:

result_factor = addFactor(fgclient, ["x4", "l1"], Pose2Point2BearingRange(Normal(0.0,0.03), Normal(0.5,0.1)))