Skip to content

Commit 2e72956

Browse files
author
Pradeep Ranganathan
committed
Implement angle range normalization correctly. Ignore anchor constraints in chi2 evaluation
1 parent 87c81fc commit 2e72956

File tree

3 files changed

+30
-32
lines changed

3 files changed

+30
-32
lines changed

graph.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import pyximport; pyximport.install()
77
from perfx import XYTConstraint_residual, XYTConstraint_jacobians
8-
from perfx import cycle_2PI_towards_zero
8+
from perfx import normalize_angle_range
99

1010

1111

@@ -103,7 +103,7 @@ def __init__(self, v, gaussian):
103103

104104
def residual(self, aggregate_state=None):
105105
r = self._gaussian.mu - self._vx[0].state
106-
r[2] = cycle_2PI_towards_zero(r[2])
106+
r[2] = normalize_angle_range(r[2])
107107
return r
108108

109109
def chi2(self):
@@ -118,11 +118,11 @@ def jacobian(self, roff=0, eps=1e-5):
118118
Compute the jacobian matrix of the residual error function
119119
evaluated at the current states of the connected vertices.
120120
121-
Returns a (dok format) sparse matrix since the jacobian of an
122-
edge constraint is sparse. The `graph_state_length` parameter is
123-
required to fix the column dimension of this sparse matrix.
124-
Thus, the sparse matrix has `graph_state_length` columns and
125-
`len(self.residual())` rows.
121+
returns the sparse Jacobian matrix entries in triplet format
122+
(i,j,v). The row index of the entries is offset by `roff`.
123+
124+
It is useful to specify `roff` when this Jacobian matrix is
125+
computed as a sub-matrix of the graph Jacobian.
126126
"""
127127
if self._jacobian_ijv_cache is None:
128128
J = -np.eye(3)
@@ -152,15 +152,19 @@ def __init__(self, vertices, edges):
152152
v._graph_state_ndx = i
153153

154154
def anchor_first_vertex(self):
155-
v0 = self.vertices[0]
155+
if hasattr(self, '_anchor') == False:
156+
v0 = self.vertices[0]
156157

157-
mu = v0.state.copy()
158-
P = 1000. * np.eye(len(mu))
159-
self._anchor = AnchorConstraint(v0, MultiVariateGaussian(mu, P))
158+
mu = v0.state.copy()
159+
P = 1000. * np.eye(len(mu))
160+
self._anchor = AnchorConstraint(v0, MultiVariateGaussian(mu, P))
160161

161-
self.edges.append(self._anchor)
162+
self.edges.append(self._anchor)
162163

163164
def get_stats(self):
164-
DOF = sum(e._DOF for e in self.edges) - len(self.state)
165-
chi2 = sum(e.chi2() for e in self.edges)
165+
original_edges = [ e for e in self.edges if e is not self._anchor ]
166+
167+
DOF = sum(e._DOF for e in original_edges) - len(self.state)
168+
chi2 = sum(e.chi2() for e in original_edges)
169+
166170
return GraphStats(chi2, chi2/DOF, DOF)

perfx.pyx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,16 @@ import numpy as np
22
cimport numpy as np
33
cimport cython
44

5-
from libc.math cimport sin, cos, fabs, M_PI
5+
from libc.math cimport sin, cos, fmod, M_PI
66

77

88

9-
cpdef inline double cycle_2PI_towards_zero(double t):
10-
"""
11-
Return the closest to zero amongst `t`, `t+2*PI`, `t-2*PI`
12-
"""
13-
cdef double _2PI = 2*M_PI
14-
cdef double best
15-
best = t if (fabs(t) < fabs(t + _2PI)) else (t + _2PI)
16-
best = best if (fabs(best) < fabs(t - _2PI)) else (t - _2PI)
17-
return best
9+
cpdef inline double normalize_angle_range(double t):
10+
""" Normalize angle to be in [-PI, +PI] """
11+
if t > 0:
12+
return fmod(t+M_PI, 2.0*M_PI) - M_PI
13+
else:
14+
return fmod(t-M_PI, 2.0*M_PI) + M_PI
1815

1916

2017
@cython.boundscheck(False)
@@ -39,7 +36,7 @@ cpdef XYTConstraint_residual(
3936
observed[1] - ainvb[1],
4037
observed[2] - ainvb[2] ]
4138

42-
r[2] = cycle_2PI_towards_zero(r[2])
39+
r[2] = normalize_angle_range(r[2])
4340

4441
return np.array(r)
4542

slam_solver.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,19 +88,19 @@ def _get_state_update(self):
8888

8989

9090
def solve(self, verbose=False, tol=1e-6, maxiter=1000, callback=None):
91-
current_stats = None
91+
current_stats = self._graph.get_stats()
9292

9393
for iter_ in xrange(maxiter):
94+
print ' chi2: %.6f chi2 normalized: %.6f' % current_stats[:2]
95+
9496
delta = self._get_state_update()
9597
self._graph.state -= delta
9698

9799
new_stats = self._graph.get_stats()
98-
if ( current_stats is not None and
99-
abs(new_stats.chi2_N - current_stats.chi2_N) < tol ):
100+
if abs(new_stats.chi2_N - current_stats.chi2_N) < tol:
100101
break
101102

102103
current_stats = new_stats
103-
print current_stats
104104

105105

106106
def main():
@@ -109,9 +109,6 @@ def main():
109109
g = load_graph(sys.argv[1] if len(sys.argv) > 1 else 'datasets/MITb.g2o')
110110
print 'graph has %d vertices, %d edges' % ( len(g.vertices), len(g.edges) )
111111

112-
# from mmath import xyt_inv_mult
113-
# print [ xyt_inv_mult(e._vx[0].state, e._vx[1].state) for e in g.edges ]
114-
115112
g.anchor_first_vertex()
116113

117114
solver = SparseCholeskySolver(g)

0 commit comments

Comments
 (0)