Generated banner for Matlab Fuse Inertial Sensors
Generated logo for Matlab Fuse Inertial Sensors

Matlab Fuse Inertial Sensors

MathWorksOfficialCreated by MathWorks

  • Skill
  • Tool integration
  • Science and research
  • BSD-2-Clause
  • No issues found

From the author

Analyzes sensor configurations and creates inertial fusion filters in MATLAB Navigation Toolbox. Manages filter selection (imufilter, ahrsfilter, complementaryFilter, insfilterMARG, insfilterAsync, insfilterNonholonomic, insfilterErrorState, insEKF, insCF), construction, tuning, and fusion loops. Use when fusing IMU/AHRS/INS/GPS+IMU data, estimating orientation or pose, or choosing a filter. Do NOT use for vision-only SLAM, Simulink fusion, or IMU simulation.
— the description by MathWorks

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to selecting, constructing, and tuning inertial fusion filters within the MATLAB Navigation Toolbox. It covers the full lifecycle of sensor fusion, from choosing between configured filters (like imufilter or ahrsfilter) and flexible filters (like insEKF) to data preparation, real-time or batch fusion loops, and parameter tuning. It emphasizes correct sensor configuration, handling of sample rates, and proper use of tuning signatures to ensure accurate orientation, pose, and velocity estimation.

What it does

  • Guides selection of appropriate inertial fusion filters based on sensor availability and motion models.
  • Provides implementation patterns for real-time and batch fusion workflows.
  • Details data preparation steps including timestamp conversion and handling of time gaps.
  • Explains how to tune filter noise properties using both in-place and struct-based signatures.
  • Offers guidance on visualizing tuning progress and extracting state estimates.
  • Defines conventions for reference frames, orientation outputs, and sensor-specific properties.

When to use it

  • When you need to fuse IMU, AHRS, INS, or GPS+IMU data for orientation or pose estimation.
  • When choosing the right filter object from the MATLAB Navigation Toolbox for a specific sensor suite.
  • When implementing or tuning a filter for real-time streaming or batch processing.
  • When troubleshooting filter performance or tuning noise parameters.

Advantages

  • Provides a clear decision tree for selecting the optimal filter for specific sensor combinations.
  • Clarifies the distinction between different tuning signatures to prevent common implementation errors.
  • Offers robust patterns for handling real-world data issues like non-uniform sample rates and time gaps.
  • Centralizes configuration and reference frame conventions for multiple filter types.

Usage examples

“How do I choose a filter for an IMU with accelerometer, gyroscope, and magnetometer?”
The skill guides you to use ahrsfilter for orientation estimation, or insfilterMARG if you also require position and velocity output.
“How do I tune the noise parameters for an insfilterMARG object?”
It explains using the Signature B tuning pattern: call tunernoise(filt) to get the struct, update the noise fields, and pass it to the tune function.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/matlab/matlab-fuse-inertial-sensors/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip matlab-fuse-inertial-sensors.zip -d .agents/skills

Documentation by MathWorks

When to Use

Select, implement, or tune an inertial sensor fusion filter in MATLAB Navigation Toolbox.

When NOT to Use

  • Vision-only or LiDAR-only SLAM (no inertial sensor): use nav SLAM functions instead

  • Sensor simulation or data generation only: no filter needed

  • Simulink model-based sensor fusion


Choose Your Filter

Step 1 — Configured or Flexible?

Configured filters cover these sensor combinations exactly:

  • Accel + Mag only (no gyro)

  • Accel + Gyro (no mag, no GPS)

  • Accel + Gyro + Mag

  • Accel + Gyro + Mag + Altimeter

  • Accel + Gyro + GPS (ground vehicle, no mag)

  • Accel + Gyro + GPS + monocular visual odometry (ground vehicle)

  • Accel + Gyro + Mag + GPS (synchronous rates)

  • Accel + Gyro + Mag + GPS (mixed rates or dropped samples)

Is your sensor set one of the above?
        │ No ─────────────────────────────────────────────────────► Step 2B (Flexible)
        │ Yes
        ▼
Need a custom motion model (constant-velocity, bicycle, etc.)?
        │ Yes ────────────────────────────────────────────────────► Step 2B (Flexible)
        │ No
        ▼
Need RTS smoothing, StateCovariance output, or optimizer-based tune()?
        │ Yes ────────────────────────────────────────────────────► Step 2B (Flexible)
        │ No
        ▼
Batch-only processing (no real-time predict/fuse loop needed)?
        │ Yes ────────────────────────────────────────────────────► Step 2B (Flexible)
        │ No
        ▼
        Step 2A (Configured)

Step 2A — Pick a configured filter
What output do you need?
│
├─ Orientation only (no position)
│   │
│   ├─ Accel + Mag only (no gyro)
│   │   └──► ecompass   [function, not object; single-shot; useful for filter init]
│   │
│   ├─ Accel + Gyro (no mag)
│   │   └──► imufilter   [heading drifts over time without mag]
│   │
│   ├─ Accel + Gyro + Mag
│   │   ├──► ahrsfilter   [default; KF-based, statistically tunable]
│   │   └── lowest compute cost needed?
│   │       └──► complementaryFilter   [gain-based only; no KF, no tune()]
│   │
│   └─ Accel + Gyro + Mag + Altimeter
│       └──► ahrs10filter   [adds altitude + vertical velocity output]
│
└─ Full pose (orientation + position + velocity)
    │
    ├─ No motion constraints (aerial, or ground vehicle without side-slip constraint)
    │   ├─ Accel + Gyro + Mag + GPS, all sensors sync
    │   │   └──► insfilterMARG
    │   └─ Accel + Gyro + Mag + GPS, mixed rates or dropped samples
    │       └──► insfilterAsync
    │
    └─ Ground vehicle with zero side-slip constraint
        ├─ Accel + Gyro + GPS
        │   └──► insfilterNonholonomic
        └─ Accel + Gyro + GPS + monocular visual odometry
            └──► insfilterErrorState

Step 2B — Pick a flexible filter
Need real-time fusion, StateCovariance output, or optimizer-based tune()?
        │ Yes ──► insEKF   [production use; full KF; real-time and batch APIs]
        │ No
        ▼
Batch-only processing, no covariance needed, quick setup (R2026a+)?
        └──► insCF   [complementary filter; gain-based; not for production]

Data Preparation

Sample Rate

SampleRate (Group A filters) and IMUSampleRate (Group B filters) must match the actual data rate — a mismatch directly scales the noise model and corrupts all filter outputs. insfilterAsync, insEKF, and insCF are timestamp-driven and have no rate property.

Convert timestamps to seconds first, then infer the rate:

tSec = rawTimestamp / 1e6;   % µs → s; divide by 1e3 for ms, or skip if already seconds
fs = 1 / median(diff(tSec));

If timestamps are unusable (all identical, or non-finite result):

if numel(unique(tSec)) < 2 || ~isfinite(fs)
    fs = 100;   % use the stated or datasheet rate
end
Time Gaps

All stateful filters accumulate error across a recording gap (a large jump in timestamps). Split the data at each gap and reset the filter at the start of each segment.

gapThreshold = 5 / fs;                   % 5× the expected sample interval
gapIdx = find(diff(tSec) > gapThreshold);
segments = [1; gapIdx+1];                % start index of each segment

Group A System object filters (imufilter, ahrsfilter, complementaryFilter): call release(filt) between segments — resets state while preserving all tuned noise properties.

Group B and other filters: re-construct the filter object at the start of each segment.


Implementation

Consult the reference file for the filter you chose:


Tuning

Two tune signatures exist — mixing them up is a common error:

  • Signature A (imufilter, ahrsfilter) — modifies filter in-place: tune(filt, sensorData, groundTruth)

  • Signature B (ahrs10filter, insfilterMARG, insfilterAsync, insfilterNonholonomic, insfilterErrorState, insEKF) — returns noise struct: tunedNoise = tune(filt, tunernoise(filt), sensorData, groundTruth)

Not tunable via tune:

  • ecompass, complementaryFilter — no tune method; adjust properties manually

  • insCF — use gainparts(filt, sensorName, value) instead

insEKF only: tunerconfig requires a filter instance — tunerconfig(filt), not tunerconfig('insEKF')

tunerconfig
PropertyDefaultDescription
MaxIterations20Stop after this many iterations
ObjectiveLimit0.1Stop when cost drops below this value
Display"iter""iter" prints progress each iteration; "none" suppresses output
Cost"RMS""RMS" minimizes RMS error; "Custom" uses CustomCostFcn
CustomCostFcn[]Function handle; active only when Cost = "Custom"
tunernoise Field Names by Filter

Signature B filters require tunernoise(filt) before calling tune. Signature A filters (imufilter, ahrsfilter) do not use tunernoise — tune modifies their noise properties directly in-place.

FilterFields returned by tunernoise
ahrs10filterMagnetometerNoise, AltimeterNoise
insfilterMARGMagnetometerNoise, GPSPositionNoise, GPSVelocityNoise
insfilterAsyncAccelerometerNoise, GyroscopeNoise, MagnetometerNoise, GPSPositionNoise, GPSVelocityNoise
insfilterNonholonomicGPSPositionNoise, GPSVelocityNoise
insfilterErrorStateMVOOrientationNoise, MVOPositionNoise, GPSPositionNoise, GPSVelocityNoise
insEKFNamed by filt.SensorNames + Noise suffix (e.g. AccelerometerNoise, GPSNoise)

Fields use the ...Noise suffix — omitting it is a common mistake.

Visualizing Results

tunerPlotPose is an OutputFcn callback — pass it to tunerconfig to plot pose estimates live during each tuning iteration. It is not a standalone post-hoc plotting function; calling it directly with filter and data arguments will error ("Too many input arguments").

config = tunerconfig(filt);
config.OutputFcn = @tunerPlotPose;
tunedNoise = tune(filt, tunernoise(filt), sensorData, groundTruth, config);

For post-tuning visualization, extract the filter state and plot against your ground truth manually. Configured filters (ahrs10filter, insfilterMARG, insfilterAsync, insfilterNonholonomic, insfilterErrorState) have a pose() method — use it. insEKF and insCF have no pose() method — use stateparts(filt, 'Orientation'), stateparts(filt, 'Position'), etc. instead. For batch workflows, plot from the estimateStates output timetable.

For advanced patterns (controlling TunableParameters, custom cost functions, OutputFcn for logging/early stopping), see references/insekf.md and references/navigation-filters.md.


Conventions

  • Reference frame: default is "NED". To use ENU:

    • Configured filter objects (imufilter, ahrsfilter, complementaryFilter, ahrs10filter, insfilterMARG, insfilterAsync, insfilterNonholonomic, insfilterErrorState): filt.ReferenceFrame = "ENU"

    • ecompass (a function, not an object): the orientationFormat positional arg is required before any name-value pairs — ecompass(a, m, 'quaternion', 'ReferenceFrame', 'ENU'). Omitting it errors: "Expected input number 3, FORMAT..."

    • insEKF/insCF: ReferenceFrame is read-only after construction; set via insOptions/insCFOptions at construction time

  • Orientation output: quaternion object by default.

  • GPS position input: LLA vector in degrees / degrees / meters.

  • Sample rate property:

    • imufilter, ahrsfilter, complementaryFilter: SampleRate

    • ahrs10filter, insfilterMARG, insfilterNonholonomic, insfilterErrorState: IMUSampleRate

    • insfilterAsync, insEKF, insCF: no sample rate property (sensors carry their own timestamps)

  • ReferenceLocation: must be set on insfilterMARG, insfilterAsync, insfilterNonholonomic, and insfilterErrorState before the fusion loop. For insEKF/insCF, set it on the insGPS/insCFGPS sensor object instead.

Skill Feedback

If this skill's guidance was wrong, incomplete, or didn't work, emit a plain-text feedback marker in your response, then continue with the task.

Format: NAV-SKILL-FEEDBACK [matlab-fuse-inertial-sensors] [category]: what failed; what you did instead Categories: wrong-command (incorrect/outdated), missing-guidance (omitted step), discovery-failure (suggested approach didn't work), platform-issue (environment-specific)

Copyright 2026 The MathWorks, Inc.

Files

references/insekf.md

insEKF Reference

insEKF is a continuous-discrete EKF: state prediction runs continuously, measurement corrections are discrete. It composes a filter from motion and sensor model objects, giving full control over states, sensor types, and fusion logic.


Construction

Pass sensor model objects, a motion model, and (optionally) insOptions to insEKF(). Order of arguments does not matter except that insOptions must be last.

% Orientation only (default motion model)
filt = insEKF(insAccelerometer, insGyroscope, insMotionOrientation);

% Full pose: orientation + position + velocity
filt = insEKF(insAccelerometer, insGyroscope, insGPS, insMotionPose);

% Full pose with magnetometer
filt = insEKF(insAccelerometer, insGyroscope, insGPS, insMagnetometer, insMotionPose);

% Custom sensor alongside built-in sensors
customSensor = MyCustomSensorModel;   % subclass positioning.INSSensorModel
filt = insEKF(insAccelerometer, insGyroscope, customSensor, insMotionPose);

Default (no arguments): insMotionOrientation + {insAccelerometer, insGyroscope}.

Setting GPS reference location

insEKF has no ReferenceLocation property. When using GPS, set ReferenceLocation on the insGPS sensor object before passing it to the constructor. This anchors the GPS→NED coordinate conversion:

gpsSensor = insGPS;
gpsSensor.ReferenceLocation = [42.3, -71.1, 100];   % [lat deg, lon deg, alt m]
filt = insEKF(insAccelerometer, insGyroscope, gpsSensor, insMotionPose);

The default is [0, 0, 0]. If your GPS data uses real LLA coordinates (e.g., latitude 42°N), omitting this produces incorrect position output.

Setting options at construction time

ReferenceFrame and custom sensor names are read-only after construction. Set them via insOptions before calling insEKF():

opts = insOptions(ReferenceFrame="ENU", ...
                  SensorNamesSource="Property", ...
                  SensorNames={'GPS1','GPS2','Accel'});
filt = insEKF(insGPS, insGPS, insAccelerometer, insMotionPose, opts);

Inspect State Layout

Always call stateinfo before accessing states — the index layout depends on the motion model and sensor set:

stateinfo(filt)                             % print all states and their indices
% To query a specific sensor's state index, pass the SAME instance used at construction:
%   accSensor = insAccelerometer;
%   filt = insEKF(accSensor, insGyroscope, insMotionPose);
%   stateinfo(filt, accSensor, 'Bias')      % works because accSensor is the construction instance

Fusion Loop (real-time)

accSensor  = insAccelerometer;
gyroSensor = insGyroscope;
gpsSensor  = insGPS;
filt = insEKF(accSensor, gyroSensor, gpsSensor, insMotionPose);

% Initialize state
stateparts(filt, 'Orientation', compact(initOrient));
statecovparts(filt, 'Orientation', 1e-2);

dt = 0.01;   % seconds between IMU samples
for i = 1:N
    predict(filt, dt);

    filt.fuse(accSensor,  accel(i,:), Raccel);   % [3x3] noise covariance
    filt.fuse(gyroSensor, gyro(i,:),  Rgyro);

    if gpsAvailable(i)
        filt.fuse(gpsSensor, lla(i,:), Rgps);    % lla: [lat_deg, lon_deg, alt_m]
    end
end

orientation = quaternion(stateparts(filt, 'Orientation'));
position    = stateparts(filt, 'Position');
velocity    = stateparts(filt, 'Velocity');

Prefer dot notation: filt.fuse(sensor, meas, R) is recommended over fuse(filt, sensor, meas, R). Both forms work, but Navigation Toolbox also contains a standalone fuse function; if a variable named fuse exists in your workspace, it will shadow the method call and cause an error.


State Access

Always use stateparts/statecovparts — never access filt.State directly. State indices depend on the motion model and sensors present, so hard-coded indices will break if the filter configuration changes.

Motion model states (by name)
stateparts(filt, 'Orientation')             % returns 1x4 double [w x y z]; wrap with quaternion() for a quaternion object
stateparts(filt, 'Orientation', q)          % set orientation (1x4 compact quaternion)
stateparts(filt, 'Position')                % get position [x y z]
stateparts(filt, 'Velocity')                % get velocity [vx vy vz]
statecovparts(filt, 'Orientation', val)     % set orientation covariance
Sensor states (by sensor handle)

For sensor-specific states (e.g., bias), pass the same sensor object instance used at construction:

accSensor  = insAccelerometer;
gyroSensor = insGyroscope;
filt = insEKF(accSensor, gyroSensor, insMotionPose);

% Read sensor bias
accBias  = stateparts(filt, accSensor, 'Bias');    % 1x3 accelerometer bias
gyroBias = stateparts(filt, gyroSensor, 'Bias');   % 1x3 gyroscope bias

% Set sensor bias
stateparts(filt, accSensor, 'Bias', [0.01 -0.02 0.005]);

insEKF has no pose() method. To retrieve the current navigation state after a fusion loop, use:

orientation = quaternion(stateparts(filt, 'Orientation'));
position    = stateparts(filt, 'Position');
velocity    = stateparts(filt, 'Velocity');

Batch Fusion (offline)

estimateStates runs forward filtering over a timetable, and optionally applies an RTS smoother:

% sensorData: timetable with columns named by filt.SensorNames
% measureNoise: struct from tunernoise(filt), or a manually constructed struct
estimates = estimateStates(filt, sensorData, measureNoise);

% Request smoothed estimates (RTS smoother — more memory and compute)
[estimates, smoothEstimates] = estimateStates(filt, sensorData, measureNoise);

Both outputs are timetables of states (same format as filt.SensorNames). smoothEstimates is the RTS-smoothed result; use it when you need the best offline accuracy.

Heading-unconstrained configurations (6-axis IMU, no magnetometer): Default tunernoise parameters give large heading errors in batch estimation because heading drift is unconstrained. Tune noise before calling estimateStates:

measureNoise = tunernoise(filt);
cfg = tunerconfig(filt, MaxIterations=50);
measureNoise = tune(filt, measureNoise, sensorData, groundTruth, cfg);
estimates = estimateStates(filt, sensorData, measureNoise);

Without tuning, heading error from estimateStates can be large on 6-axis IMU data.


Tuning

measureNoise = tunernoise(filt);              % fields named {SensorName}Noise: AccelerometerNoise, GyroscopeNoise, GPSNoise, etc.
cfg = tunerconfig(filt, MaxIterations=200);   % requires filter INSTANCE, not string

% Reinitialize before tuning
stateparts(filt, 'Orientation', compact(initOrient));
statecovparts(filt, 'Orientation', 1e-2);

tunedNoise = tune(filt, measureNoise, sensorData, groundTruth, cfg);

% Apply tuned noise in batch estimate
tunedStates = estimateStates(filt, sensorData, tunedNoise);
Controlling Which Parameters Get Tuned

TunableParameters defaults to a cell array for insEKF. Each cell is either a string (tune all elements together) or a {propertyName, indices} pair (tune specific diagonal elements):

config = tunerconfig(filt);

% Inspect defaults — AdditiveProcessNoise is tuned element-by-element (diagonals)
config.TunableParameters
% {{'AdditiveProcessNoise', [1 15 29 ...]}, 'AccelerometerNoise', 'GyroscopeNoise'}

% Tune only AccelerometerNoise and GyroscopeNoise (drop process noise)
config.TunableParameters = {'AccelerometerNoise', 'GyroscopeNoise'};

tunedNoise = tune(filt, tunernoise(filt), sensorData, groundTruth, config);
Custom Cost Function

Use when RMS error on orientation/position is not the right objective (e.g. you care more about velocity accuracy). Use createTunerCostTemplate to get a starter function signature:

createTunerCostTemplate(filt)

Then wire it in via tunerconfig:

config = tunerconfig(filt);
config.Cost = 'Custom';
config.CustomCostFcn = @myCostFcn;

tunedNoise = tune(filt, tunernoise(filt), sensorData, groundTruth, config);

% CustomCostFcn receives 3 inputs: (filter, measNoise, sensorData)
% groundTruth is NOT passed — load/access your own reference inside the function
function cost = myCostFcn(filter, measNoise, sensorData)
    states = estimateStates(filter, sensorData, measNoise);
    velError = states.Velocity - myGroundTruthVelocity;
    cost = rms(vecnorm(velError, 2, 2));
end
OutputFcn — Logging and Early Stopping

OutputFcn is called after each iteration. Return true to stop early. Applies to all tunable filters, not just insEKF.

config = tunerconfig(filt);
config.OutputFcn = @(params, info) logAndStop(params, info);

function stop = logAndStop(params, info)
    fprintf('Iter %d: cost = %.4f\n', info.Iteration, info.Cost);
    stop = info.Cost < 0.05;   % stop early if good enough
end

info fields: Iteration, SensorData, GroundTruth, Configuration, Cost.


Motion Models

ModelStates addedUse when
insMotionOrientationOrientation, AngularVelocityAttitude only; no position needed
insMotionPoseOrientation, AngularVelocity, Position, Velocity, AccelerationFull navigation (position + velocity + attitude)
positioning.INSMotionModelUser-definedCustom dynamics (e.g. constant-velocity, bicycle model)

Sensor Models

ModelMeasuresNotes
insAccelerometerSpecific force + biasModels gravity + acceleration when in state
insGyroscopeAngular velocity + bias
insMagnetometerGeomagnetic vector + bias
insGPSLLA position (+ velocity if fused)Multiple GPS objects supported; auto-named GPS, GPS_1, ...
positioning.INSSensorModelUser-definedSubclass; must implement measurement method

Custom Motion Model

Subclass positioning.INSMotionModel and implement modelstates and stateTransition:

classdef MyMotionModel < positioning.INSMotionModel
    methods
        function states = modelstates(obj)
            % Return Nx1 string array of state names
            states = ["X"; "Y"; "Heading"];
        end
        function [state, F] = stateTransition(obj, state, dt, varargin)
            % Propagate state forward; return updated state and Jacobian F
        end
        function jac = stateTransitionJacobian(obj, filter, dt, varargin)
            % Return S-by-N Jacobian matrix for the state transition
        end
    end
end

Gotchas Summary

SituationWrongCorrect
Getting orientation from statefilt.State(1:4)stateparts(filt, 'Orientation')
Getting pose after fusion looppose(filt) or [pos, orient] = pose(filt)insEKF has no pose() method — use stateparts(filt, 'Position'), stateparts(filt, 'Orientation'), stateparts(filt, 'Velocity')
Accessing sensor biasfilt.State(idx:idx+2)stateparts(filt, sensorHandle, 'Bias') — pass the same object instance used at construction
Accessing a state not in motion modelstateparts(filt, 'Position') when constructed with insMotionOrientationCall stateinfo(filt) first — insMotionOrientation has no Position/Velocity states; use insMotionPose if you need them
Creating tuner configtunerconfig('insEKF')tunerconfig(filt)
Setting ENU reference framefilt.ReferenceFrame = "ENU"insOptions(ReferenceFrame="ENU") at construction
Setting GPS reference locationfilt.ReferenceLocation = lla0gpsSensor.ReferenceLocation = lla0 on the insGPS object
Noise struct field namesmeasureNoise.Accelerometer = ...measureNoise.AccelerometerNoise = ... (suffix Noise)

Copyright 2026 The MathWorks, Inc.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

The author says: R2022a+

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
MathWorks
Publisher
MathWorks (Official)
Source
matlab/matlab-agentic-toolkit · skills-catalog/robotics-and-autonomous-systems/matlab-fuse-inertial-sensors · commit 2a7f215
Licence
BSD 2-Clause License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by MathWorks.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-fuse-inertial-sensors.zip