Localization and mapping
These classes support simulation of vehicle and map estimation in a simple planar world with point landmarks.
State estimation
Two state estimators are included.
Extended Kalman filter
The EKF is capable of vehicle localization, map estimation or SLAM.
- class EKF(robot, sensor=None, map=None, P0=None, x_est=None, joseph=True, animate=True, x0=(0, 0, 0), verbose=False, history=True, workspace=None)[source]
Bases:
objectExtended Kalman filter for vehicle localization, map estimation and SLAM
- __init__(robot, sensor=None, map=None, P0=None, x_est=None, joseph=True, animate=True, x0=(0, 0, 0), verbose=False, history=True, workspace=None)[source]
Extended Kalman filter
- Parameters:
robot (
tuple[VehicleBase,ndarray|None]) – robot motion modelsensor (
tuple[RangeBearingSensor,ndarray] |None) – vehicle mounted sensor model, defaults to Nonemap (
LandmarkMap|None) – landmark map, defaults to NoneP0 (
ndarray[Tuple[Literal[3],Literal[3]],dtype[floating]] |None) – initial covariance matrix, defaults to Nonex_est (
ndarray|None) – initial state estimate, defaults to Nonejoseph (
bool) – use Joseph update of covariance, defaults to Trueanimate (
bool) – show animation of vehicle motion, defaults to Truex0 (
Union[List[float],Tuple[float,float,float],ndarray[Tuple[Literal[3]],dtype[floating]]]) – initial vehicle state \((x, y, \\theta)\), defaults to (0, 0, 0)verbose (
bool) – display extra debug information, defaults to Falsehistory (
bool) – retain step-by-step history, defaults to Trueworkspace (
Union[float,ndarray,Sequence[float],HasWorkspace,None]) – dimension of workspace, seeexpand_dims()
This class solves several classical robotic estimation problems, which are selected according to the arguments:
Problem
len(x)
robotsensormapP0Dead reckoning
3
(veh,V)
None
None
P0
Map-based localization
3
(veh,V)
(smodel,W)
yes
P0
Map creation
2N
(veh,None)
(smodel,W)
None
None
SLAM
3+2N
(veh,V)
(smodel,W)
None
P0
where:
vehmodels the robotic vehicle kinematics and odometry and is aVehicleBasesubclassVis the estimated odometry (process) noise covariance as an ndarray(2,2)smodelmodels the robot mounted sensor and is aSensorBasesubclassWis the estimated sensor (measurement) noise covariance as an ndarray(2,2)
The state vector has different lengths depending on the particular estimation problem, see below.
At each iteration of the EKF:
invoke the step method of the
robotobtains the next control input from the driver agent, and apply it as the vehicle control input
the vehicle returns a noisy odometry estimate
the state prediction is computed
the true pose is used to determine a noisy sensor observation
the state is corrected, new landmarks are added to the map
The working area of the robot is defined by
workspaceor inherited from the landmark map attached to thesensor(seeexpand_dims()):workspacex-range
y-range
A (scalar)
-A:A
-A:A
[A, B]
A:B
A:B
[A, B, C, D]
A:B
C:D
Dead-reckoning localization
The state \(\vec{x} = (x, y, \theta)\) is the estimated vehicle configuration.
Create a vehicle with odometry covariance
V, add a driver to it, run the Kalman filter with estimated covariancesVand initial vehicle state covarianceP0:V = np.diag([0.02, np.radians(0.5)]) ** 2; robot = Bicycle(covar=V) robot.control = RandomPath(workspace=10) x_sdev = [0.05, 0.05, np.radians(0.5)] P0 = np.diag(x_sdev) ** 2 ekf = EKF(robot=(robot, V), P0=P0) ekf.run(T=20) # run the simulation for 20 seconds robot.plot_xy(color="b") # plot the true vehicle path ekf.plot_xy(color="r") # overlay the estimated path ekf.plot_ellipse(filled=True, facecolor="g", alpha=0.3) # overlay uncertainty ellipses # plot the covariance against time t = ekf.get_t(); pn = ekf.get_Pnorm() plt.plot(t, pn);
Map-based vehicle localization
The state \(\vec{x} = (x, y, \theta)\) is the estimated vehicle configuration.
Create a vehicle with odometry covariance
V, add a driver to it, create a map with 20 point landmarks, create a sensor that uses the map and vehicle state to estimate landmark range and bearing with covarianceW, the Kalman filter with estimated covariancesVandWand initial vehicle state covarianceP0:V = np.diag([0.02, np.radians(0.5)]) ** 2; robot = Bicycle(covar=V) robot.control = RandomPath(workspace=10) map = LandmarkMap(20, workspace=10) W = np.diag([0.1, np.radians(1)]) ** 2 sensor = RangeBearingSensor(robot=robot, map=map, covar=W, angle=[-np.pi/2, np.pi/2], range=4, plot=True) x_sdev = [0.05, 0.05, np.radians(0.5)] P0 = np.diag(x_sdev) ** 2 ekf = EKF(robot=(robot, V), P0=P0, map=map, sensor=(sensor, W)) ekf.run(T=20) # run the simulation for 20 seconds map.plot() # plot the map robot.plot_xy(color="b") # plot the true vehicle path ekf.plot_xy(color="r") # overlay the estimated path ekf.plot_ellipse() # overlay uncertainty ellipses # plot the covariance against time t = ekf.get_t(); pn = ekf.get_Pnorm() plt.plot(t, pn);
Vehicle-based map making
The state \(\vec{x} = (x_0, y_0, \dots, x_{N-1}, y_{N-1})\) is the estimated landmark positions where \(N\) is the number of landmarks. The state vector is initially empty, and is extended by 2 elements every time a new landmark is observed.
Create a vehicle with perfect odometry (no covariance), add a driver to it, create a sensor that uses the map and vehicle state to estimate landmark range and bearing with covariance
W, the Kalman filter with estimated sensor covarianceW, then run the filter for N time steps:robot = Bicycle() robot.add_driver(RandomPath(workspace=10)) map = LandmarkMap(20, workspace=10, seed=0) W = np.diag([0.1, np.radians(1)]) ** 2 sensor = RangeBearingSensor(robot, map, covar=W) ekf = EKF(robot=(robot, None), sensor=(sensor, W)) ekf.run(T=20) # run the simulation for 20 seconds map.plot() # plot the map robot.plot_xy(color="b") # plot the true vehicle path
Simultaneous localization and mapping (SLAM)
The state \(\vec{x} = (x, y, \theta, x_0, y_0, \dots, x_{N-1}, y_{N-1})\) is the estimated vehicle configuration followed by the estimated landmark positions where \(N\) is the number of landmarks. The state vector is initially of length 3, and is extended by 2 elements every time a new landmark is observed.
Create a vehicle with odometry covariance
V, add a driver to it, create a map with 20 point landmarks, create a sensor that uses the map and vehicle state to estimate landmark range and bearing with covarianceW, the Kalman filter with estimated covariancesVandWand initial state covarianceP0, then run the filter to estimate the vehicle state at each time step and the map:V = np.diag([0.02, np.radians(0.5)]) ** 2; robot = Bicycle(covar=V) robot.control = RandomPath(workspace=10) map = LandmarkMap(20, workspace=10) W = np.diag([0.1, np.radians(1)]) ** 2 sensor = RangeBearingSensor(robot=robot, map=map, covar=W, angle=[-np.pi/2, np.pi/2], range=4, plot=True) x_sdev = [0.05, 0.05, np.radians(0.5)] P0 = np.diag(x_sdev) ** 2 ekf = EKF(robot=(robot, V), P0=P0, sensor=(sensor, W)) ekf.run(T=20) # run the simulation for 20 seconds map.plot(); # plot true map ekf.plot_map(); # plot estimated landmark position robot.plot_xy(); # plot true path ekf.plot_xy(); # plot estimated robot path ekf.plot_ellipse(); # plot estimated covariance # plot the covariance against time t = ekf.get_t(); pn = ekf.get_Pnorm() plt.plot(t, pn);
See also
- property x_est: ndarray
Get EKF state
- Returns:
state vector
- Return type:
ndarray(n)
Returns the value of the estimated state vector at the end of simulation. The dimensions depend on the problem being solved.
- property P_est: ndarray
Get EKF covariance
Returns the value of the estimated covariance matrix at the end of simulation. The dimensions depend on the problem being solved.
- property P0: ndarray | None
Get initial EKF covariance
Returns the value of the covariance matrix passed to the constructor.
- property V_est: ndarray | None
Get estimated odometry covariance
- Returns:
odometry covariance
- Return type:
ndarray(2,2)
Returns the value of the estimated odometry covariance matrix passed to the constructor
- property W_est: ndarray | None
Get estimated sensor covariance
- Returns:
sensor covariance
- Return type:
ndarray(2,2)
Returns the value of the estimated sensor covariance matrix passed to the constructor
- property robot: VehicleBase
Get robot object
- Returns:
robot used in simulation
- Return type:
VehicleBasesubclass
- property sensor: RangeBearingSensor | None
Get sensor object
- Returns:
sensor used in simulation
- Return type:
SensorBasesubclass
- property map: LandmarkMap | None
Get map object
- Returns:
map used in simulation
- Return type:
LandmarkMapsubclass
- property history: list[EKFLog]
Get EKF simulation history
- Returns:
simulation history
- Return type:
list of namedtuples
At each simulation timestep a namedtuple of is appended to the history list. It contains, for that time step, estimated state and covariance, and sensor observation.
See also
- property workspace: ndarray | None
Size of robot workspace
- Returns:
workspace bounds [xmin, xmax, ymin, ymax]
- Return type:
ndarray(4)
Returns the bounds of the workspace as specified by the constructor option
workspace
- property landmarks: dict[int, list[int]]
Get landmark information
- Returns:
landmark information
- Return type:
The dictionary is indexed by the landmark id and gives a 3-tuple:
order in which landmark was seen
number of times seen
The order in which the landmark was first seen. The first observed landmark has order 0 and so on.
See also
- landmark(id: int) tuple[int, int][source]
Landmark information
- Parameters:
id (
int) – landmark index- Returns:
order in which it was first seen, number of times seen
- Return type:
The first observed landmark has order 0 and so on.
See also
- landmark_index(id: int) int[source]
Landmark index in complete state vector
The return value
jis the index of the x-coordinate of the landmark in the EKF state vector, andj+1is the index of the y-coordinate.See also
- landmark_mindex(id: int) int[source]
Landmark index in map state vector
The return value
jis the index of the x-coordinate of the landmark in the map vector, andj+1is the index of the y-coordinate.See also
- landmark_x(id: int) ndarray[source]
Landmark position
Returns the landmark position from the current state vector.
- init() None[source]
Reset the filter
Resets the filter state and covariance to their initial values, and clears the landmarks and history.
- Return type:
- run(T: float, animate: bool = False) None[source]
Run the EKF simulation
- Parameters:
- Return type:
Simulates the motion of a vehicle (under the control of a driving agent) and the EKF estimator. The steps are:
initialize the filter, vehicle and vehicle driver agent, sensor
for each time step:
step the vehicle and its driver agent, obtain odometry
take a sensor reading
execute the EKF
save information as a namedtuple to the history list for later display
- run_animation(T: float = 10, x0: ndarray | Sequence[float] | None = None, control: Any = None, format: str | None = None, file: str | None = None) Any[source]
Run the EKF simulation
- Parameters:
- Returns:
Matplotlib animation object
- Return type:
Simulates the motion of a vehicle (under the control of a driving agent) and the EKF estimator for
Tseconds and returns an animation in various formats:``format`` ``file`` description ============ ========= ============================ ``"html"`` str, None return HTML5 video ``"jshtml"`` str, None return JS+HTML video ``"gif"`` str return animated GIF ``"mp4"`` str return MP4/H264 video ``None`` return a ``FuncAnimation`` object
If
filecan beNonethen return the video as a string, otherwise it must be a filename.The simulation steps are:
initialize the filter, vehicle and vehicle driver agent, sensor
for each time step:
step the vehicle and its driver agent, obtain odometry
take a sensor reading
execute the EKF
save information as a namedtuple to the history list for later display
- get_t() ndarray[source]
Get time from simulation
- Returns:
simulation time vector
- Return type:
Return simulation time vector, starts at zero. The timestep is an attribute of the
robotobject.
- plot_xy(*args: Any, block: bool | None = None, **kwargs: Any) None[source]
Plot estimated vehicle position
- Parameters:
- Return type:
Plot the estimated vehicle path in the xy-plane.
See also
get_xyt()plot_error()plot_ellipse()plot_Prun()history()
- plot_ellipse(confidence: float = 0.95, N: int = 10, block: bool | None = None, **kwargs: Any) None[source]
Plot uncertainty ellipses
- Parameters:
confidence (
float) – ellipse confidence interval, defaults to 0.95N (
int) – number of ellipses to plot, defaults to 10block (
bool|None) –Noneleaves showing the figure to the environment (noplt.showcall),Falseshows it and returns at once,Trueshows it and waits until it is closed; ignored by notebook backends, defaults to Nonekwargs (
Any) – arguments passed tospatialmath.base.graphics.plot_ellipse()
- Return type:
Plot
Nuncertainty ellipses spaced evenly along the trajectory.
- plot_error(bgcolor: str = 'r', confidence: float | None = 0.95, ax: Any = None, block: bool | None = None, **kwargs: Any) None[source]
Plot error with uncertainty bounds
- Parameters:
bgcolor (
str) – color of the confidence bound polygons, defaults to ‘r’confidence (
float|None) – confidence interval, defaults to 0.95, None for no boundsax (
Any) – axes to plot into, one for each of \((x, y, \theta)\), defaults to a new figure with three stacked axesblock (
bool|None) –Noneleaves showing the figure to the environment (noplt.showcall),Falseshows it and returns at once,Trueshows it and waits until it is closed; ignored by notebook backends, defaults to None
- Raises:
ValueError – if the vehicle pose is not part of the estimated state
- Return type:
Plot the error between actual and estimated vehicle path \((x, y, \theta)\) versus time as three stacked plots. Heading error is wrapped into the range \([-\pi,\pi)\)
Behind each line draw a shaded polygon
bgcolorshowing the specifiedconfidencebounds based on the covariance at each time step. Ideally the lines should be within the shaded polygonconfidenceof the time.Note
Observations will decrease the uncertainty while periods of dead-reckoning increase it.
Changed in version 1.5.0: - The
confidencebounds are the interval that contains that fraction of a one-dimensional Gaussian, \(\pm 1.96\sigma\) for 0.95. Earlier versions used the radius of a two-dimensional confidence region, \(\pm 2.45\sigma\) for 0.95, which made the shaded region about 25% wider than its label. Plots produced with earlier versions show wider bounds. - The error is plotted against time. Earlier versions plotted it against sample number on axes limited to the time range, so only the start of a run was visible. -bgcoloris used, it was previously ignored. - Works for SLAM, where the state also holds the landmarks, and raisesValueErrorif the vehicle pose is not estimated.
- get_map() ndarray[source]
Get estimated map
- Returns:
landmark coordinates \((x, y)\)
- Return type:
Landmarks are returned in the order they were first observed.
See also
- plot_map(marker_style: dict[str, Any] | None = None, ellipse_style: dict[str, Any] | None = None, confidence: float = 0.95, block: bool | None = None, *, marker: dict[str, Any] | None = None, ellipse: dict[str, Any] | None = None) None[source]
Plot estimated landmarks
- Parameters:
marker_style (
dict[str,Any] |None) – Matplotlib options for the landmark marker, defaults to a red+ellipse_style (
dict[str,Any] |None) – options passed toplot_ellipse(), ellipses are drawn only if this is givenconfidence (
float) – ellipse confidence interval, defaults to 0.95block (
bool|None) –Noneleaves showing the figure to the environment (noplt.showcall),Falseshows it and returns at once,Trueshows it and waits until it is closed; ignored by notebook backends, defaults to Nonemarker (
dict[str,Any] |None) – deprecated, usemarker_styleellipse (
dict[str,Any] |None) – deprecated, useellipse_style
- Return type:
Plot a marker and, if
ellipse_styleis given, a covariance ellipse for each estimated landmark.Deprecated since version 1.5.0:
markerandellipseare deprecated, usemarker_styleandellipse_style.Changed in version 1.5.0: Each ellipse is now drawn with the covariance of its own landmark. Previously the covariance was taken from the wrong place in the state covariance matrix, so that the ellipses after the first were wrong, and in SLAM were not landmark covariances at all.
- get_P(k: int | None = None) ndarray | list[ndarray][source]
Get covariance matrices from simulation
- Parameters:
- Returns:
covariance matrix
- Return type:
If
kis given return covariance from simulation timestepk, else return a list of all covariance matrices.See also
- get_Pnorm(k: int | None = None) float | ndarray[source]
Get covariance norm from simulation
- Parameters:
- Returns:
covariance matrix norm
- Return type:
If
kis given return covariance norm from simulation timestepk, else return all covariance norms as a 1D NumPy array.
- disp_P(P: ndarray, colorbar: bool | dict[str, Any] = False) None[source]
Display covariance matrix
- Parameters:
- Type:
- Return type:
Plot the elements of the covariance matrix as an image. If
colorbaris True add a color bar, ifcolorbaris a dict add a color bar with these options passed to colorbar.Note
A log scale is used.
See also
- get_transform(map: dict[int, ndarray | Sequence[float]] | ndarray) SE2[source]
Transformation from estimated map to true map frame
- Parameters:
map (
dict[int,ndarray|Sequence[float]] |ndarray) – known landmark positions- Returns:
transform from
mapto estimated map frame- Return type:
Uses a least squares technique to find the transform between the landmark is world frame and the estimated landmarks in the SLAM reference frame.
See also
Particle filter
The particle filter is capable of map-based vehicle localization.
- class ParticleFilter(robot, sensor, R, L, nparticles=500, seed=0, x0=None, verbose=False, animate=False, history=True, workspace=None)[source]
Bases:
objectParticle filter for vehicle localization
- __init__(robot, sensor, R, L, nparticles=500, seed=0, x0=None, verbose=False, animate=False, history=True, workspace=None)[source]
Particle filter
- Parameters:
robot (
VehicleBase) – robot motion modelsensor (
RangeBearingSensor) – vehicle mounted sensor modelR (
ndarray) – covariance of the zero-mean Gaussian noise added to the particles at each step (diffusion)L (
ndarray) – covariance used in the sensor likelihood modelnparticles (
int) – number of particles, defaults to 500x0 (
ndarray|Sequence[float] |None) – initial particle states, defaults to None which means the particles are distributed uniformly randomly over the workspace, with random headingsverbose (
bool) – display extra debug information, defaults to Falsehistory (
bool) – retain step-by-step history, defaults to Trueworkspace (
Union[float,ndarray,Sequence[float],HasWorkspace,None]) – dimension of workspace, seeexpand_dims()
This class implements a Monte-Carlo estimator or particle filter for vehicle state, based on odometry, a landmark map, and landmark observations. The state of each particle is a possible vehicle configuration \((x,y,\theta)\). Bootstrap particle resampling is used.
The working area is defined by
workspaceor inherited from the landmark map attached to thesensor(seeexpand_dims()):workspacex-range
y-range
A (scalar)
-A:A
-A:A
[A, B]
A:B
A:B
[A, B, C, D]
A:B
C:D
Particles are initially distributed uniform randomly over this area, unless
x0is given:x0initial particles
Noneuniformly random over the workspace
\((x, y, \theta)\)
all particles at this state
ndarray(nparticles, 3)
a row \((x, y, \theta)\) for each particle
Particles that start at the same state are spread apart by the noise
Radded at each step. The array is copied, it is not changed by the filter. This is the default for thex0argument ofrun().Example:
V = np.diag([0.02, np.radians(0.5)]) ** 2 robot = Bicycle(covar=V, animation="car", workspace=10) robot.control = RandomPath(workspace=robot) map = LandmarkMap(20, workspace=robot.workspace) W = np.diag([0.1, np.radians(1)]) ** 2 sensor = RangeBearingSensor(robot, map, covar=W, plot=True) R = np.diag([0.1, 0.1, np.radians(1)]) ** 2 L = np.diag([0.1, 0.1]) pf = ParticleFilter(robot, sensor, R, L, nparticles=1000) pf.run(T=10) map.plot() robot.plot_xy() pf.plot_xy() plt.plot(pf.get_std()[:100,:])
Note
Set
seed=Noneto get different behaviour from run to run.See also
- property robot: VehicleBase
Get robot object
- Returns:
robot used in simulation
- Return type:
VehicleBasesubclass
- property sensor: RangeBearingSensor
Get sensor object
- Returns:
sensor used in simulation
- Return type:
SensorBasesubclass
- property map: LandmarkMap
Get map object
- Returns:
map used in simulation
- Return type:
LandmarkMapsubclass
- property history: list[PFlog]
Get EKF simulation history
- Returns:
simulation history
- Return type:
list of namedtuples
At each simulation timestep a namedtuple of is appended to the history list. It contains, for that time step, estimated state and covariance, and sensor observation.
- property workspace: ndarray | None
Size of robot workspace
- Returns:
workspace bounds [xmin, xmax, ymin, ymax]
- Return type:
ndarray(4)
Returns the bounds of the workspace as specified by constructor option
workspace
- property random: Generator
Get private random number generator
- Returns:
NumPy random number generator
- Return type:
Has methods including:
integers(low, high, size, endpoint)random(size)uniformnormal(mean, std, size)multivariate_normal(mean, covar, size)
The generator is initialized with the seed provided at constructor time every time
initis called.
- run(T: float = 10, x0: ndarray | Sequence[float] | None = None) None[source]
Run the particle filter simulation
- Parameters:
- Return type:
Simulates the motion of a vehicle (under the control of a driving agent) and the particle-filter estimator. The steps are:
initialize the filter, vehicle and vehicle driver agent, sensor
for each time step:
step the vehicle and its driver agent, obtain odometry
take a sensor reading
execute the particle filter
save information as a namedtuple to the history list for later display
- run_animation(T: float = 10, x0: ndarray | Sequence[float] | None = None, format: str | None = None, file: str | None = None) Any[source]
Run the particle filter simulation
- Parameters:
- Returns:
Matplotlib animation object
- Return type:
Simulates the motion of a vehicle (under the control of a driving agent) and the particle-filter estimator and returns an animation in various formats:
``format`` ``file`` description ============ ========= ============================ ``"html"`` str, None return HTML5 video ``"jshtml"`` str, None return JS+HTML video ``"gif"`` str return animated GIF ``"mp4"`` str return MP4/H264 video ``None`` return a ``FuncAnimation`` object
The allowables types for
fileare given in the second column. A str value is the file name. IfNoneis an option then return the video as a string.For the last case, a reference to the animation object must be held if used for animation in a Jupyter cell:
anim = robot.run_animation(T=20)
The steps are:
initialize the filter, vehicle and vehicle driver agent, sensor
for each time step:
step the vehicle and its driver agent, obtain odometry
take a sensor reading
execute the particle filter
save information as a namedtuple to the history list for later display
- plot_pdf() None[source]
Plot particle PDF
Displays a discrete PDF of vehicle position. Creates a 3D plot where the x- and y-axes are the estimated vehicle position and the z-axis is the particle weight. Each particle is represented by a a vertical line segment of height equal to particle weight.
- Return type:
- get_t() ndarray[source]
Get time from simulation
- Returns:
simulation time vector
- Return type:
Return simulation time vector, starts at zero. The timestep is an attribute of the
robotobject.
- get_xyt() ndarray[source]
Get estimated vehicle trajectory
- Returns:
vehicle trajectory where each row is configuration \((x, y, \theta)\)
- Return type:
Changed in version 1.5.0: Returns the heading as well as the position, as documented and as
EKF.get_xyt()does. Previously the result had only the \((x, y)\) columns,xyt[:, :2]is that.
- get_std() ndarray[source]
Get standard deviation of particles
- Returns:
dispersion of position and heading estimates, shape (n, 3)
- Return type:
Return the population standard deviation of position and circular standard deviation of heading \((\sigma_x, \sigma_y, \sigma_\theta)\) of the equally weighted particle cloud at each time step. Heading dispersion is in radians and is unbounded as the mean resultant tends to zero.
See also
- plot_xy(block: bool | None = None, **kwargs: Any) None[source]
Plot estimated vehicle position
- Parameters:
- Return type:
Plot the estimated vehicle path in the xy-plane.
See also
Sensor models
- class RangeBearingSensor(robot, map, line_style=None, poly_style=None, covar=None, range=None, angle=None, plot=False, seed=0, **kwargs)[source]
Bases:
SensorBaseRange and bearing angle sensor
- __init__(robot, map, line_style=None, poly_style=None, covar=None, range=None, angle=None, plot=False, seed=0, **kwargs)[source]
Range and bearing angle sensor
- Parameters:
robot (
Any) – model of robot carrying the sensormap (
Any) – map of landmarksline_style (
dict[str,Any] |None) – style of the line to the observed landmark, defaults to Nonepoly_style (
dict[str,Any] |None) – style for the sensing region polygon, defaults to Nonecovar (
ndarray|Sequence[float] |None) – covariance matrix for sensor readings, or the variance of each of range and bearing, defaults to None (noise free)range (
float|ndarray|Sequence[float] |None) – maximum range \(r_{max}\) or range span \([r_{min}, r_{max}]\), defaults to Noneangle (
float|ndarray|Sequence[float] |None) – angular field of view, either \(\theta\) for the span \([-\theta, \theta]\) or the span \([\theta_{min}, \theta_{max}]\), defaults to Noneplot (
bool) – plot the map and a line to each observed landmark, defaults to Falsekwargs (
Any) – arguments passed toSensorBase
Sensor object that returns the range and bearing angle \((r, \beta)\) to a point landmark from a robot-mounted sensor. The sensor measurements are corrupted with zero-mean Gaussian noise with covariance
covar.The sensor can have a maximum range, or a minimum and maximum range. The sensor can also have a restricted angular field of view.
If
plotis True the landmark map is displayed and a line is drawn from the robot to the landmark observed each timereading()is called.line_stylestyles the landmark beam andpoly_stylestyles the sensing region whenplot=True. The region is shown when both range and angle limits are supplied.>>> from roboticstoolbox import Bicycle, LandmarkMap, RangeBearingSensor >>> from math import pi >>> robot = Bicycle() >>> map = LandmarkMap(20) >>> sensor = RangeBearingSensor(robot, map, range=(0.5, 20), angle=pi/4) >>> print(sensor) RangeBearingSensor sensor class LandmarkMap object with 20 landmarks, workspace=(-10.0: 10.0, -10.0: 10.0) W = [ 0, 0 | 0, 0 ] sampled every 1 samples range: (0.5: 20.0) angle: (-0.785: 0.785)
See also
LandmarkMapEKFDeprecated since version 1.4.0: The
animatekeyword, useplotinstead.
- init()[source]
Initialize sensor
reseed the random number generator
reset the counter for handling the
everyandfailoptionsreset the landmark log
initialize plots
See also
- Return type:
- property W: ndarray
Get sensor covariance
- Returns:
sensor covariance
- Return type:
ndarray(2,2)
Returns the value of the sensor covariance matrix passed to the constructor.
- reading()[source]
Choose landmark and return observation
- Returns:
range and bearing angle to a landmark, and landmark id
- Return type:
Returns an observation of a random visible landmark (range, bearing) and the
idof that landmark. The landmark is chosen randomly from the set of all visible landmarks, those within the angular field of view and range limit.If constructor argument
everyis set then only return a valid reading on everyeverycalls.If constructor argument
failis set then do not return a reading during that specified time interval.If no valid reading is available then return (None, None)
>>> from roboticstoolbox import Bicycle, LandmarkMap, RangeBearingSensor >>> from math import pi >>> robot = Bicycle() >>> map = LandmarkMap(20) >>> sensor = RangeBearingSensor(robot, map, range=(0.5, 20), angle=pi/4) >>> print(sensor.reading()) (array([ 7.2691, -0.039 ]), 16)
Note
Noise with covariance
W(set by constructor) is added to the readingIf
animateoption is set then show a line from the vehicle to the landmarkIf
animateoption set and the angular and distance limits are set then display the sensor field of view as a polygon.
See also
- visible()[source]
List of all visible landmarks
Return a list of the id of all landmarks that are visible, that is, it lies with the sensing range and field of view of the sensor at the robot’s current configuration.
See also
- isvisible(id)[source]
Test if landmark is visible
The landmark
idis visible if it lies with the sensing range and field of view of the sensor at the robot’s current configuration.
- h(x, landmark=None)[source]
Landmark observation function
- Parameters:
- Returns:
range and bearing angle to landmark \((r,\beta)\)
- Return type:
Return the range and bearing to a landmark:
.h(x)is range and bearing to all landmarks, one row per landmark, including maps with just one landmark.h(x, id)is range and bearing to landmarkid.h(x, p)is range and bearing to landmark with coordinatesp
For an explicit landmark, a single vehicle pose returns a vector of two elements. An ndarray of vehicle poses returns one row per pose, including a batch containing just one pose.
>>> from roboticstoolbox import Bicycle, LandmarkMap, RangeBearingSensor >>> from math import pi >>> robot = Bicycle() >>> map = LandmarkMap(20) >>> sensor = RangeBearingSensor(robot, map, range=(0.5, 20), angle=pi/4) >>> z = sensor.h((1, 2, pi/2), 3) >>> print(z) [10.7111 1.4826]
Note
Noise with covariance (property
W) is added to each row ofz.Performs fast vectorized operation where
xis an ndarray(n,3).The landmark is assumed to be visible, field of view and range limits are not applied.
- Hx(x, landmark)[source]
Jacobian dh/dx
- Parameters:
- Returns:
Jacobian matrix
- Return type:
Compute the Jacobian of the observation function with respect to vehicle configuration \(\partial h/\partial x\)
sensor.Hx(q, id)is Jacobian for landmarkidsensor.h(q, p)is Jacobian for landmark with coordinatesp
- Hp(x, landmark)[source]
Jacobian dh/dp
- Parameters:
- Returns:
Jacobian matrix
- Return type:
Compute the Jacobian of the observation function with respect to landmark position \(\partial h/\partial p\)
sensor.Hp(x, id)is Jacobian for landmarkidsensor.Hp(x, p)is Jacobian for landmark with coordinatesp
- Hw(x, landmark)[source]
Jacobian dh/dw
- Parameters:
- Returns:
Jacobian matrix
- Return type:
Compute the Jacobian of the observation function with respect to sensor noise \(\partial h/\partial w\)
sensor.Hw(x, id)is Jacobian for landmarkidsensor.Hw(x, p)is Jacobian for landmark with coordinatesp
Note
xandlandmarkare not used to compute this.
- property map: Any
Landmark map associated with sensor (superclass)
- Returns:
robot
- Return type:
LandmarkMap
- property random: Generator
Get private random number generator (superclass)
- Returns:
NumPy random number generator
- Return type:
Has methods including:
The generator is initialized with the seed provided at constructor time every time
init()is called.See also
- property robot: Any
Robot associated with sensor (superclass)
- Returns:
robot
- Return type:
VehicleBasesubclass
- g(x, z)[source]
Landmark position from sensor observation
- Parameters:
- Returns:
landmark position \((x, y)\)
- Return type:
Compute the world coordinate of a landmark given the observation
zfrom a vehicle state withx.
- Gx(x, z)[source]
Jacobian dg/dx
- Parameters:
- Returns:
Jacobian matrix
- Return type:
Compute the Jacobian of the landmark position function with respect to landmark position \(\partial g/\partial x\)
See also
- class SensorBase(robot, map, every=1, fail=[], plot=False, delay=0.1, seed=0, verbose=False, **kwargs)[source]
Bases:
ABCSuperclass for sensors
- __init__(robot, map, every=1, fail=[], plot=False, delay=0.1, seed=0, verbose=False, **kwargs)[source]
Sensor.Sensor Sensor object constructor % # S = Sensor(VEHICLE, MAP, OPTIONS) is a sensor mounted on a vehicle # described by the Vehicle subclass object VEHICLE and observing landmarks # in a map described by the LandmarkMap class object self. % # Options:: # ‘animate’ animate the action of the laser scanner # ‘ls’,LS laser scan lines drawn with style ls (default ‘r-‘) # ‘skip’, I return a valid reading on every I’th call # ‘fail’,T sensor simulates failure between timesteps T=[TMIN,TMAX] % # Notes:: # - Animation shows a ray from the vehicle position to the selected # landmark.
- init()[source]
Initialize sensor (superclass)
reseed the random number generator
reset the counter for handling the
everyandfailoptions
- Return type:
- __str__()[source]
Convert sensor parameters to a string (superclass) % # s = self.char() is a string showing sensor parameters in # a compact human readable format.
- Return type:
- property robot: Any
Robot associated with sensor (superclass)
- Returns:
robot
- Return type:
VehicleBasesubclass
- property map: Any
Landmark map associated with sensor (superclass)
- Returns:
robot
- Return type:
LandmarkMap
Map models
- LANDMARK_STYLE: dict[str, Any] = {'color': 'black', 'linestyle': 'None', 'linewidth': 0, 'marker': 'x'}
default Matplotlib style of the landmarks, a user’s options are merged over this
- class LandmarkMap(landmarks=None, workspace=10, seed=0, verbose=None, map=None)[source]
Bases:
objectMap of planar point landmarks
A LandmarkMap object represents a rectangular 2D environment with a number of point landmarks.
The landmarks can be specified explicitly or be uniform randomly positioned inside a region defined by the workspace. The workspace can be numeric:
workspacex-range
y-range
A (scalar)
-A:A
-A:A
[A, B]
A:B
A:B
[A, B, C, D]
A:B
C:D
or any object that has a
workspaceattribute, see The workspace.Example:
>>> from roboticstoolbox import LandmarkMap >>> map = LandmarkMap(20) >>> print(map) LandmarkMap object with 20 landmarks, workspace=(-10.0: 10.0, -10.0: 10.0) >>> print(map[3]) # coordinates of landmark 3 [-9.6694 2.9438]
The object is an iterator that returns consecutive landmark coordinates.
- Reference:
Robotics, Vision & Control in Python, 3rd edition, Chap 6, Peter Corke, Springer 2023
See also
- __init__(landmarks=None, workspace=10, seed=0, verbose=None, map=None)[source]
Create a landmark map
- Parameters:
landmarks (
NDArray|int|None) – the landmark coordinates, one column per landmark, or the number of landmarks to place at random in the workspaceworkspace (
Union[float,ndarray,Sequence[float],HasWorkspace]) – workspace or map bounds, defaults to 10seed (
int|None) – random number seed used when placing landmarks at random, defaults to 0
- Raises:
ValueError –
landmarksis not given, or is not a 2xN array or an integer
Deprecated since version 1.5.0:
verbosehas no effect andmapis renamedlandmarks.
- __str__()[source]
Compact string description of the map
- Return type:
- Returns:
number of landmarks and the workspace
- property landmarks: NDArray
xy-coordinates of all landmarks
- Returns:
xy-coordinates for landmark points, one column per landmark
- Return type:
ndarray(2, n)
- property workspace: ndarray[Tuple[Literal[4]], dtype[floating]]
Size of map workspace
- Returns:
workspace bounds [xmin, xmax, ymin, ymax]
Returns the bounds of the workspace as specified by constructor option
workspace.
- plot(labels=False, block=None, **kwargs)[source]
Plot landmark map
- Parameters:
labels (
bool) – number the points on the plot, defaults to Falseblock (
bool|None) –Noneleaves showing the figure to the environment (noplt.showcall),Falseshows it and returns at once,Trueshows it and waits until it is closed; ignored by notebook backends, defaults to Nonekwargs (
Any) –plot()options, merged overLANDMARK_STYLE
- Return type:
Plot landmark points using Matplotlib options. Default style is black crosses, and any options given override the corresponding default.
Pose graph optimization
A pose graph is the other approach to SLAM: the poses of the vehicle are the vertices of a graph, and the measurements between them are the edges. Optimizing the graph finds the poses that agree best with all of the measurements.
- class PoseGraph(filename, lidar=False, verbose=False)[source]
Bases:
objectPose graph optimization for SLAM
- Parameters:
- Raises:
RuntimeError – file syntax error
ValueError – an edge references an unknown vertex, or a lidar record precedes any vertex
A pose graph has a vertex for each robot pose \((x, y, \theta)\) and an edge for each relative pose measurement between two poses. Optimization adjusts the poses to best satisfy all the measurements.
The vertices are numbered in the order they appear in the file, and
pose(),scan()andtime()take this number, not the vertex name which is the identifier in the file.Example:
- References:
Robotics, Vision & Control for Python, §6.5, P. Corke, Springer 2023.
A Tutorial on Graph-Based SLAM, G. Grisetti, R. Kümmerle, C. Stachniss and W. Burgard, in IEEE Intelligent Transportation Systems Magazine, vol. 2, no. 4, pp. 31-43, winter 2010, doi: 10.1109/MITS.2010.939925.
See also
- scan(i: int) tuple[ndarray, ndarray][source]
Lidar scan at a vertex
- Parameters:
i (
int) – vertex number- Raises:
IndexError – there is no such vertex
ValueError – the vertex has no lidar scan
- Returns:
range and bearing of each beam
- Return type:
See also
- scanxy(i: int) ndarray[source]
Lidar scan at a vertex as points in the sensor frame
- Parameters:
i (
int) – vertex number- Raises:
IndexError – there is no such vertex
ValueError – the vertex has no lidar scan
- Returns:
points, one per column, NaN for beams that returned no range less than the maximum range of the sensor
- Return type:
See also
- plot_scan(n: int | ndarray | Sequence[float], pause: float = 1, **kwargs: Any) None[source]
Plot lidar scans
- Parameters:
- Return type:
Each scan is plotted in the sensor frame, in turn.
See also
- pose(i: int) ndarray[source]
Pose of a vertex
- Parameters:
i (
int) – vertex number- Raises:
IndexError – there is no such vertex
- Returns:
pose \((x, y, \theta)\), or position \((x, y)\) for a landmark, as stored in the graph so that it reflects any optimization
- Return type:
- time(i)[source]
Timestamp of the lidar scan at a vertex
- Parameters:
i (
int) – vertex number- Raises:
IndexError – there is no such vertex
ValueError – the vertex has no lidar scan
- Return type:
- Returns:
timestamp, as a Unix time
- plot(vertex_style: dict[str, Any] | None = None, edge_style: dict[str, Any] | None = None, *, vopt: dict[str, Any] | None = None, eopt: dict[str, Any] | None = None, **kwargs: Any) None[source]
Plot the pose graph
- Parameters:
- Return type:
Deprecated since version 1.5.0:
voptandeoptare deprecated, usevertex_styleandedge_style.See also
- scanmatch(s1: int, s2: int) SE2[source]
Relative pose between two scans by scan matching
- Parameters:
- Raises:
IndexError – there is no such vertex
ValueError – a vertex has no lidar scan
- Return type:
- Returns:
relative pose, found by iterative closest point
See also
spatialmath.base.ICP2d()
- scanmap(occgrid: BaseOccupancyGrid, maxrange: float | None = None, step: int = 5) None[source]
Build an occupancy grid from the lidar scans
- Parameters:
occgrid (
BaseOccupancyGrid) – occupancy grid to updatemaxrange (
float|None) – ignore range readings beyond this distance, defaults to None for allstep (
int) – use everystep-th scan, defaults to 5
- Raises:
ValueError – the graph has no lidar scans
- Return type:
Each scan is placed at the pose of its vertex, and the cells along each ray are incremented as free, and the cell it ends in is decremented as occupied. Afterwards cells are -1 (occupied), +1 (free) or 0 (unknown).
Note
Scans are taken in vertex order, so the result depends on the current vertex poses, and is best made after
optimize().See also
- optimize(iterations: int = 10, animate: bool = False, retain: bool = True, vertex_style: dict[str, Any] | None = None, edge_style: dict[str, Any] | None = None, **kwargs: Any) None[source]
Optimize the pose graph
- Parameters:
iterations (
int) – maximum number of iterations, defaults to 10animate (
bool) – show the graph after each iteration, defaults to Falseretain (
bool) – retain the graph of each iteration in the animation, in increasingly dark gray, defaults to Truevertex_style (
dict[str,Any] |None) – Matplotlib options for the vertices when animatingedge_style (
dict[str,Any] |None) – Matplotlib options for the edges when animating, the color is set byretainkwargs (
Any) – options passed topgraph.UGraph.plotwhen animating
- Raises:
NotImplementedError – the graph has landmark vertices
- Return type:
Deprecated since version 1.5.0:
voptandeoptare deprecated, usevertex_styleandedge_style.Each iteration solves the linearized least-squares problem for a correction to all the poses, which are updated in place. Iteration stops early if the total error does not decrease.
See also
- linearize_and_solve() float[source]
One iteration of pose graph optimization
- Return type:
- Returns:
total cost, the sum of squared errors of all the edges, before the poses are updated
If the graph is
verbosethe cost and the time taken are printed.The first vertex is anchored, since the measurements are all relative. The system matrix is assembled from sparse 3-by-3 blocks, so assembly storage grows with the graph’s vertices and edges rather than the square of its vertex count.
See also