Magnetometer#
Magnetometer — 3-axis geomagnetic field sensor.
The
magnetometerclass models a strapdown 3-axis magnetometer. It measures the local geomagnetic field vector expressed in the vehicle body frame: a fixed reference field in the navigation frame, rotated into the body frame by the true attitude, then corrupted by soft-iron and hard-iron distortion and sample-to-sample white Gaussian noise.- Parameters:
noise_std (float or array_like, optional) – Standard deviation of the field measurement noise, per body axis, in the same units as
field_intensity(dimensionless when the field is left normalized). A scalar is broadcast to all three axes. Defaults to0.02.hard_iron (array_like, optional) – Constant additive bias
[bx, by, bz](hard-iron offset), in the same units asfield_intensity. Defaults to[0, 0, 0].soft_iron (array_like, optional) –
3 x 3soft-iron distortion matrix applied to the body-frame field before the hard-iron offset and the noise. Defaults to the identity.field_intensity (float, optional) – Total intensity \(F\) of the reference geomagnetic field. Defaults to
1.0(the field is returned normalized). Set a real value (e.g.50e-6T or50for µT) to work in physical units —noise_stdandhard_ironthen carry the same unit.inclination (float, optional) – Inclination (dip) angle \(I\) of the reference field, [rad], positive pointing down. Defaults to
np.pi / 3(\(60^\circ\), a typical mid-latitude value).declination (float, optional) – Declination angle \(D\) of the reference field, [rad], positive east of north. Defaults to
0.isideal (bool, optional) – If
True, overridesnoise_std/hard_iron/soft_ironto produce an ideal (noise-free, distortion-free) magnetometer. The reference field itself is unaffected. Defaults toFalse.frame ({'NED', 'ENU'}, optional) – Convention of the inertial frame the true state is expressed in:
'NED'(x north, y east, z down) or'ENU'(x east, y north, z up). It sets the components of the reference field \(m_{ref}\). Defaults to'NED'.
Functionality
The reference field is built from its total intensity, inclination and declination and held fixed in the inertial frame associated with the state’s 3-2-1 Euler angles. For
frame = 'NED'(xnorth,yeast,zdown):\[m_{ref} = F \cdot [\cos I \cos D,\ \cos I \sin D,\ \sin I]^T\]For
frame = 'ENU'(xeast,ynorth,zup), the same field has its north and east components swapped and its vertical component negated:\[m_{ref} = F \cdot [\cos I \sin D,\ \cos I \cos D,\ -\sin I]^T\]At each sample, given the 12-state vector
\[X = [x, y, z, v_x, v_y, v_z, \varphi, \theta, \psi, p, q, r]^T\]the reference field is rotated into the body frame by the true attitude and distorted:
\[ \begin{align}\begin{aligned}m_{body} = [BI](\varphi, \theta, \psi) \cdot m_{ref}\\z = S_i \cdot m_{body} + b_i + n\end{aligned}\end{align} \]where \([BI]\) is the body-from-inertial 3-2-1 DCM (
dcm321), \(S_i\) is the soft-iron matrix, \(b_i\) is the hard-iron offset, and \(n\) is a zero-mean Gaussian vector with per-axis standard deviationnoise_std.measurereturns the full 3-axis body-frame field vector. A consumer that needs a heading derives it from the horizontal components after de-rotating roll and pitch (tilt compensation); a filter that consumes the vector directly (seeekf) needs no yaw-wrapping, because the measurement is linear in the rotated field rather than in the angle itself. In a typical setup the magnetometer is sampled at a lower rate than the IMU, e.g. \(50\,Hz\).Errors Model
The magnetometer measurement is subject to three error sources:
Hard iron: a constant additive offset[bx, by, bz](magnetized material fixed to the body). Set through thehard_ironparameter and unchanged between measurements. Defaults to[0, 0, 0].Soft iron: a constant3 x 3linear distortion \(S_i\) (nearby ferrous material that reshapes the field). Set through thesoft_ironparameter. Defaults to the identity.Noise: a zero-mean Gaussian sample, drawn independently per axis at every call tomeasure, with standard deviationnoise_std.
The errors model can be disabled by passing
isideal = Trueat construction, which forcesnoise_std = 0,hard_iron = [0, 0, 0]andsoft_iron = Iregardless of the arguments. Unlike theseekermodel, the magnetometer does not generate a random bias during construction; the supplied distortion is deterministic for a given instance.Construction
A magnetometer instance is created by making a direct call to the constructor:
>>> mag_sensor = c4d.sensors.magnetometer()
The noise, the hard-iron / soft-iron distortion and the reference-field geometry can all be specified when creating the sensor.
Examples
Import required packages:
>>> import c4dynamics as c4d >>> import numpy as np
True attitude
For the examples below, build a 12-state vector and set its attitude entries (indices 6, 7, 8 = roll, pitch, yaw):
>>> x_true = np.zeros(12) >>> x_true[8] = 0.5 # true heading [rad]
Ideal magnetometer
An ideal magnetometer can be created by muting the errors model. Level and heading north, it reads the reference field directly — horizontal component forward, vertical component (the dip) down:
>>> mag_ideal = c4d.sensors.magnetometer(isideal=True) >>> mag_ideal.measure(np.zeros(12)) [0.5 0. 0.866]
Rotating to a heading of
0.5rad swings the horizontal field into the bodyyaxis while its magnitude and the vertical component are preserved:>>> mag_ideal.measure(x_true) [0.439 -0.24 0.866]
Frame convention
The default inertial frame is \(NED\) (z down). For a state in \(ENU\) (z up), set
frame = 'ENU'. Level with zero Euler angles, the bodyxaxis points east andzpoints up, so the horizontal field appears ony(north) and the dip points along-z:>>> mag_enu = c4d.sensors.magnetometer(isideal=True, frame='ENU') >>> mag_enu.measure(np.zeros(12)) [0. 0.5 -0.866]
Non-ideal magnetometer
A non-ideal magnetometer adds white measurement noise (and, optionally, hard-iron / soft-iron distortion). Set the random seed to make the example reproducible:
>>> np.random.seed(42) >>> mag_sensor = c4d.sensors.magnetometer(noise_std=0.02) >>> mag_sensor.measure(x_true) [0.449 -0.242 0.879]
Hard iron
The hard-iron offset is constant across all measurements. A magnetometer with a
[0.1, 0, 0]offset can be created as follows:>>> mag_hi = c4d.sensors.magnetometer(noise_std=0, hard_iron=[0.1, 0, 0]) >>> mag_ref = c4d.sensors.magnetometer(isideal=True) >>> mag_hi.measure(x_true) - mag_ref.measure(x_true) [0.1 0. 0.]
The difference between the measurement and the ideal field is the specified offset.
Soft iron
The soft-iron matrix scales / mixes the body-frame field. A diagonal
soft_ironwith a1.2gain on the bodyxaxis:>>> mag_si = c4d.sensors.magnetometer( ... noise_std=0, soft_iron=np.diag([1.2, 1.0, 1.0])) >>> mag_si.measure(np.zeros(12)) [0.6 0. 0.866]
Measurement noise
With no distortion, repeated measurements of the same state demonstrate the random per-axis noise generated at every call to
measure:>>> np.random.seed(1) >>> mag_noise = c4d.sensors.magnetometer(noise_std=0.02) >>> for _ in range(3): ... print(mag_noise.measure(x_true)) [0.471 -0.252 0.855] [0.417 -0.222 0.820] [0.474 -0.255 0.872]
Demo
The built-in
demo()method provides a compact demonstration of the magnetometer errors model and plots the true and measured body-frame field components through an attitude sweep:>>> fig = c4d.sensors.magnetometer.demo(show=True)
The same demonstration can be run without displaying the figure by using
show=False.Methods
magnetometer.measure(x_true)Measure the body-frame geomagnetic field.