wikillm

Paper · 2025-10-30 · cs.RO

A Sliding-Window Filter for Online Continuous-Time Continuum Robot State Estimation

Estimates the pose, velocity and strain of a continuum robot along its length and over time by solving a factor-graph MAP problem on a short sliding window. The oldest time step is marginalised into a prior with the Schur complement. Author-reported: tip RMSE within 0.01 percentage points of the batch smoother on four of five trajectories, and 8.7 to 10.2 ms per step for a 0.1 s window.

Sliding-window estimationContinuous-time state estimationPose-graph optimizationstate-estimationcontinuum-robotssliding-windowcontinuous-timefactor-graph

arXivPDFCodeMarkdown16 min read

AI-compiled by claude-haiku-5-5 on 2026-11-01, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.

In plain words

A continuum robot is a thin, bendable arm with no joints, so its shape depends on how it is driven and pushed around. To know where its tip is, you combine a model of how it should bend with sensors that measure parts of it. There are two simple ways to do this. A filter only keeps the latest estimate, so it is fast but can be biased when its approximations accumulate. A batch smoother looks at the whole recording and is accurate, but it cannot run live.

This paper sits between the two. It keeps only a short recent window of time steps and re-solves the estimate over that window each time new data arrives. Older steps are folded into a single summary, so the problem never grows. The robot's shape is estimated continuously along its length, so the estimate can be queried between sensor sample times. The authors report accuracy close to the batch smoother while running live and faster than real time.

TL;DR

  • Sliding-window, factor-graph MAP estimator for a continuum robot's pose, velocity and strain along its arc length and over time §3.1 §3.2.
  • Each new time step adds states and factors. The window is re-solved with Gauss-Newton. The oldest step is marginalised into a prior with a Schur complement alg. 1 §3.3.2.
  • Author-reported on a real tendon-driven robot (five 10 s trajectories): tip position RMSE is 1.88% on Out-of-Bounds, against 2.04% for batch and 2.38% for filter, and within 0.01 points of batch on four of five trajectories tab. 1 §4.
  • Author-reported runtime is 8.7 to 10.2 ms per step with a 0.1 s window, and real time holds for windows up to 0.3 s tab. 1 §4.
  • Known weaknesses are the back-of-window latency, covariance discontinuities over time, and an assumption of zero-mean noise without bias, which the paper lists as a limitation §5.

Problem

Continuum robots are flexible and bend into nonlinear shapes, so open-loop models leave errors from unmodelled effects, material properties and external forces. Probabilistic fusion of noisy sensors is therefore needed §1. Filters run online but linearise at every step and can lose the Markov structure they rely on, which biases the posterior for nonlinear systems §1. Batch smoothers are more accurate but cannot run online §1. Before this paper, the sliding-window approaches for continuum robots either used a constant-curvature model without a probability distribution, or a rigid-link model tested only in simulation §2. Continuous-time batch methods for continuum robots existed but were offline [abstract] §1.

The sliding-window filter (SWF) is claimed to be the first stochastic sliding-window estimator for continuum robots §1 [abstract].

Method

Estimation framework §3.1. The state is the tip-to-base pose T(s, t) in SE(3), a velocity in R^6 and a strain in R^6, defined for every arc length 0 ≤ s ≤ L and time 0 ≤ t ≤ T. The paper reuses the prior and measurement factors from its earlier batch framework (cited as Teetaert2025 in the paper). The prior is built from an approximate Cosserat rod model with a white-noise-on-acceleration motion prior. The control inputs are zero, because the prior encodes the dynamics. The measurement factors are tip pose and gyroscope §3.1.

The estimate is the maximum a posteriori solution, which is the minimum of the summed negative log factors. Each factor is Gaussian in its error e_i(x) with covariance Σ_i §3.1. The problem is solved by Gauss-Newton: each iteration solves the linearised normal equations, and the iteration repeats until convergence. The paper names no convergence test. After convergence, the state covariance is the inverse of the information matrix at convergence, the Laplace approximation §3.1.

Pseudocode
# Gauss-Newton on the window  [§3.1]
# Input:  states x (K*N nodes, 18 values each), factors with errors e_i(x)
# Output: updated states x

def gauss_newton_step(states, factors):
    # Stack the Jacobians H and errors e of all factors at the current states
    # [§3.1]
    H, error = stack_linearisation(factors, states)
    # W is block diagonal in the factor covariances Sigma_i; W_inv is its inverse
    # [§3.1]
    W_inv = block_diagonal_inverse(factors)
    # Solve the normal equations for the update  [§3.1]
    lhs = H.T @ W_inv @ H
    rhs = -H.T @ W_inv @ error
    dx = solve(lhs, rhs)
    # Apply the full update (no line search in the shipped window config)
    # [code: assets/config/estimator/window.json#L13]
    return apply_update(states, dx)

Sliding-window formulation §3.2. A window holds w time steps and states x_{a:b} with b = a + w. The factors are: a prior on the state before the window, motion factors between consecutive time steps, spatial factors along the arc length, boundary priors at the base, and time-interpolated measurement factors §3.2. The state preceding the window is marginalised out, which leaves one effective prior on the first in-window state. The window then depends only on that prior and on its own measurements. The paper argues this keeps the Markov property approximately valid, because each old state is integrated out rather than dropped §3.2.

Algorithm alg. 1. The paper gives one algorithm. For each new measurement it adds a state and its factors, solves the MAP problem over the window, and when the window is larger than w it integrates out the oldest state into a new prior. The loop below restates it in our own words.

Pseudocode
# Sliding-window filter main loop  [alg. 1] [§3.2] [§3.3]
# Input:  prior on the first state, window length w, measurement stream
# Output: one tip estimate per new time step

def sliding_window_filter(prior, w, measurement_stream):
    # Start with the marginal prior and an empty window  [alg. 1]
    factors = [prior]
    window = []
    for measurement in measurement_stream:
        # Append the new time step, initialised from its neighbours [§3.3.1]
        window = expand_window(window, measurement.time)
        # Add motion, spatial, boundary and measurement factors [alg. 1] [§3.2]
        factors = add_factors(factors, window, measurement)
        # Solve the MAP problem for the current window  [§3.1] [alg. 1]
        window = solve_map(factors, window)
        # Report the state at the back of the window  [§3.3.3]
        estimate = window.back_state
        # When the window is too long, fold the oldest step into the prior
        # [alg. 1] [§3.3.2]
        if window.num_time_steps > w:
            prior, factors = marginalise_oldest(factors, window)
            window = drop_oldest(window)
        yield estimate

Window expansion §3.3.1. Each new time step adds N states along the robot. They are linked to each other by spatial factors and to the previous time step by temporal factors. The initial mean of each new state is the average of two predictions: one from the spatial prior and one from the temporal prior §3.3.1. Vector values are averaged directly. Pose values use an SE(3) average, written in the paper as

T_{avg} = T_1 \exp\left(\tfrac{1}{2} \log\left(T_1^{-1} T_2\right)^{\vee}\right)

as stated in §3.3.1. This is the geodesic midpoint of the two poses.

Pseudocode
# Initial pose of a new node: midpoint of two predictions  [§3.3.1]
# Input:  pose_space (from the spatial prior), pose_time (temporal prior)
# Output: initial pose in SE(3)

def initial_pose(pose_space, pose_time):
    # Relative motion from the first prediction to the second  [§3.3.1]
    delta = log(inverse(pose_space) @ pose_time)
    # Walk half of that motion from the first prediction  [§3.3.1]
    return pose_space @ exp(0.5 * delta)

Marginalisation §3.3.2. Old states are integrated out. The paper works in information form, with information matrix H = Σ^{-1} and information vector h = Σ^{-1} μ. Keeping the r states and marginalising the m states gives the Schur complement on H and h §3.3.2:

H_r = H_{rr} - H_{rm} H_{mm}^{-1} H_{mr}

h_r = h_r - H_{rm} H_{mm}^{-1} h_m

The information of locked states is carried forward without re-iteration, because it does not change as the window slides §3.3.2.

Pseudocode
# Marginalise a block of states out of the information system  [§3.3.2]
# Input:  H (information matrix), h (information vector), keep and drop indices
# Output: H_r, h_r on the kept states only

def marginalise(H, h, keep, drop):
    # Blocks of the information matrix  [§3.3.2]
    H_rr = H[keep, keep]
    H_rm = H[keep, drop]
    H_mr = H[drop, keep]
    H_mm = H[drop, drop]
    # Schur complement on the information matrix and vector  [§3.3.2]
    H_r = H_rr - H_rm @ inverse(H_mm) @ H_mr
    h_r = h[keep] - H_rm @ inverse(H_mm) @ h[drop]
    return H_r, h_r

State extraction §3.3.3. The state is read from the back of the window, which holds all measurements in the window but adds a delay up to the window length. Reading from the front gives a live estimate, but the paper reports no accuracy gain for it §5. Covariance is handled differently from the state: the joint covariance between neighbouring time steps is stored when a step is locked, so the variance at a time step can come from two sources that do not agree exactly. The paper reports that this causes small discontinuities in the covariance over time §3.3.3.

Window size §3.3.4. A window of one time step gives an iterated filter. A window covering the whole interval gives the batch method. Larger windows allow continuous-time covariance queries after the fact, while the one-step filter does not §3.3.4.

Experimental setup §3.4. Each estimator uses N = 5 states along the robot and time nodes at 30 Hz. The window sizes compared run from 0 s (the filter) to 0.2 s, and the batch baseline covers 10 s §3.4.1 §4. Noise parameters are kept the same across all methods. Their values are not stated in the paper §3.4.1. Hardware was an Intel i7-13850HX at 3.80 GHz with 64 GB of RAM §3.4.1.

Dataset §3.4.2. The robot is a 3D-printed tendon-driven continuum robot, 46.6 cm long, with a 3.6 cm outer diameter. It has two six-degree-of-freedom electromagnetic pose sensors (Aurora v3) at the tip and base, and two gyroscope units at the midpoint and the tip. Ground truth comes from a motion-capture system at five points. The five trajectories are 10 s each and are named in the table tab. 1; each covers varying motion and contact. Sensor timestamps are asynchronous §3.4.2.

Metrics §3.5. Tip pose RMSE against ground truth. Position is normalised by the robot length. The estimates are interpolated to the ground-truth times. The average normalised estimation error squared (NEES) measures consistency, with an optimal value of 6, the number of pose degrees of freedom §3.5 tab. 1 note. Runtime is the average per time step §3.5.

Hyperparameters

Values stated in the paper:

NameValueWhereNotes
Robot discretisation N5 states along the length§3.4.1Increasing N did not change accuracy much but raised cost §3.4.1
Time-node rate30 Hz§3.4.1
Primary window size0.1 s§4Chosen after the sweep; diminishing returns beyond about 0.1 s §4
Window sizes compared0 s to 0.2 s in §3.4.1; 0 s to 10 s in the §4 sweep§3.4.1 §410 s is the batch baseline §4
Noise parameterssame across methods; values not stated§3.4.1See the code values below
Convergence test and iteration capnot stated§3.1See Paper vs code
Trajectory length10 s each§3.4.2
Robot length46.6 cm§3.4.2
Outer diameter3.6 cm§3.4.2
Hardwarei7-13850HX, 3.80 GHz, 64 GB§3.4.1

Values taken from the code, which the paper does not state. These are the shipped defaults for the TDCR robot used in the paper, so they are useful as a starting point but are not the paper's own settings:

NameValueWhereNotes
P0 (prior on first node), diagonal[8e-5, 8e-5, 8e-5, 1e-3, 1e-3, 1e-3, 1e-2, 1e-2, 1e-2, 1e3, 1e3, 1e-4, 1e2, 1e2, 1e2, 1e2, 1e2, 1e2]robot2.jsonScaled by prior_factor
Q1 (time, velocity), diagonal[3e1, 3e1, 3e1, 8e3, 8e3, 8e3]robot2.jsonScaled by prior_factor
Q2 (space, strain), diagonal[8e-1, 8e-1, 8e-1, 1e3, 1e3, 1e3]robot2.jsonScaled by prior_factor
Q3 (strain in time), diagonal[8e-1, 8e-1, 8e-1, 1e4, 1e4, 1e4]robot2.jsonScaled by prior_factor
prior_factor0.15robot2.jsonMultiplies P0, Q1, Q2, Q3 data_loader.hpp
R_pose, diagonal covariance[2e-5, 2e-5, 2e-5, 2.5e-3, 2.5e-3, 2.5e-3]robot2.jsonInverted into the weight Factor.hpp
R_gyro, diagonal covariance[1e-6, 1e-6, 1e-6]robot2.jsonSame inversion
Robot length, N, rate0.466 m, 5, 30robot2.json
Window dt0.1 swindow.json
Window convergence threshold, iteration cap1e-1, 13window.json
Window line searchoffwindow.json
Window extraction endbackwindow.json
Window marginalisationonwindow.json
Filter dt1/30 sfilter.json
Filter convergence threshold, iteration cap1e-2, 10filter.json

Results

Author-reported. Setup: five trajectories on the tendon-driven robot described in §3.4.2. Baselines are a 10 s batch smoother and a one-step filter. The window is 0.1 s. Position is a percentage of robot length. Rotation is in radians. NEES is consistency, where 6 is the optimum. Runtime is per time step.

TrialEstimatorTip pos RMSE (%)Tip rot RMSE (rad)Avg NEESAvg runtime (ms)
Out-of-BoundsFilter (0 s)2.380.045N/A4.4
Out-of-BoundsSWF (0.1 s)1.880.0426.9310.2
Out-of-BoundsBatch (10 s)2.040.0417.601359
Fast ContactFilter (0 s)1.750.048N/A4.0
Fast ContactSWF (0.1 s)1.290.0383.5610.2
Fast ContactBatch (10 s)1.290.0383.801379
Impulse 1Filter (0 s)1.800.053N/A3.9
Impulse 1SWF (0.1 s)1.700.0425.968.7
Impulse 1Batch (10 s)1.700.0426.541377
Impulse 2Filter (0 s)2.110.075N/A4.0
Impulse 2SWF (0.1 s)1.440.0504.998.9
Impulse 2Batch (10 s)1.430.0505.271380
Slow Free SpaceFilter (0 s)1.160.043N/A4.0
Slow Free SpaceSWF (0.1 s)1.160.0425.269.1
Slow Free SpaceBatch (10 s)1.160.0425.641380

Source: tab. 1.

Reading of the table, as the paper states it §4: on position, the SWF beats the filter on every trajectory except Slow Free Space, where they tie; the largest gap is on Impulse 2 (1.44% against 2.11%). It matches the batch smoother on position on three trajectories and within 0.01 points on Impulse 2, and it is 0.16 points better on Out-of-Bounds. On rotation, the batch is marginally better on Out-of-Bounds (0.041 against 0.042) and Impulse 2 is tied. The paper reports that larger windows improve accuracy with diminishing returns beyond 0.1 s, except on Out-of-Bounds, where position error rises at larger windows §4 §5. It reports that real-time operation holds up to 0.3 s windows, with per-window runtimes falling below the 33.3 ms budget for a 30 Hz rate §4 fig. runtime distribution, §4. The paper reports one further result: a variant that drops the continuous-time covariance runs under 5 ms per step, so over 200 Hz §5.

Paper vs code

The official repository is utiasASRL/space_time_continuum at the commit in the frontmatter. The paper links it in the introduction §1. It is MIT licensed LICENSE. The shipped code contains three estimators in one binary: batch, sliding-window (SW) and filter. The window and filter estimators are different classes main.cpp.

  • Convergence. The paper gives no stopping rule §3.1. The code stops when the squared norm of the Gauss-Newton step falls below the threshold, which is 0.1 for the window and 0.01 for the filter, and caps the iterations at 13 and 10 Estimator.cpp Estimator.cpp window.json filter.json.
  • Which code made the filter column. The paper says the 0 s window is the filter §3.3.4 §4. In the code, the window estimator asserts at least two time nodes SWEstimator.hpp, and the filter is a separate class with one time node and dt = 1/30 s [code: scripts/main.cpp#L98-L101] filter.json. The paper does not say which code produced Table 1's filter row.
  • Window node count. The paper says the robot has N = 5 states and nodes at 30 Hz §3.4.1, but does not state the count per window. The code sets it to ceil(30 × T) + 1, so a 0.1 s window has 4 time nodes [code: scripts/main.cpp#L92-L93]. This is a code-derived value.
  • Gyroscope error. The paper does not write the gyroscope error §3.1. The code uses the measured angular rate plus the node's angular velocity component, with a plus sign GyroMeasurementFactor.hpp. The sign convention depends on how the prior defines velocity, which the paper does not state.
  • Pose error. The paper does not write the pose error either. The code computes the log of the node pose times the inverse measured pose, then masks components the sensor does not provide PoseMeasurementFactor.hpp.
  • SE(3) average. The paper's midpoint T1 exp(½ log(T1⁻¹T2)) and the code's exp(½ log(T2 T1⁻¹)) T1 types.hpp are the same pose by the adjoint identity. The code averages strain, velocity and time linearly.
  • Measurements outside the window. Measurements outside the window's time range are skipped for that solve Estimator.cpp. The paper does not state this.
  • Vicon data. The Vicon rows go to ground truth only. The code's line that would use them as a measurement is commented out [code: scripts/data_loader.hpp#L504-L507], which matches the paper's use of motion capture as ground truth §3.4.2.
  • Covariance extraction. The code extracts covariance for the first two time steps SWEstimator.hpp. The paper describes storing joint covariances between neighbouring steps when they are locked §3.3.3. These are consistent but the code does not show the storage step as described.
  • Marginalisation. The paper says Schur complement. The code forms a dense inverse of the 90 × 90 block (N × 18 for N = 5) with a Cholesky solve SWEstimator.cpp and computes the Schur terms from it SWEstimator.cpp SWEstimator.cpp.
  • Shipped config for the demo. The run script sets 8 OpenMP threads run_demo.sh. The paper gives no thread count.
  • Robot file. The demo uses robot2.json for the tendon-driven robot [code: README.md#L126]. Its length matches the paper (0.466 m) [code: assets/config/robot/robot2.json#L22]. It sets radius to 0.002 m, while the paper gives an outer diameter of 3.6 cm. The card does not interpret this field.
  • Repository note. The README says the code and configurations differ in small ways from the published results README.md.

Pitfalls

  • No numeric noise or prior values in the paper. The paper says the noise parameters were the same across estimators and gives no numbers §3.4.1. The code's values are in the table above, but they are the repository's own settings, not the paper's. Pick values for your sensors and check them against the innovation statistics.
  • The motion prior is not reproduced here. The prior is a Cosserat-rod model with a white-noise-on-acceleration term, defined in the earlier batch paper (Teetaert2025 in the paper's citations; the repo README lists arXiv:2510.01381 as a companion publication) §3.1 README.md. The transition matrices live in the code: getQt static_funcs.hpp and getPhiT static_funcs.hpp. Read the earlier paper before porting the prior.
  • Convergence is not stated. A fixed number of iterations and a step-size threshold both change the answer. The shipped window config uses a threshold of 0.1 on the squared step, with a cap of 13 iterations [code: assets/config/estimator/window.json#L11-L12]. Test that these stop the iteration before the cap on your data.
  • Marginalisation failure throws. The code logs that no marginalisation will be performed, then throws SWEstimator.cpp [code: src/SWEstimator.cpp#L330]. A numerically singular block stops the run rather than skipping the marginalisation. Plan for an exception.
  • Latency with back-of-window extraction. The state reported is from the back of the window, so it lags by up to the window length §3.3.3. For the dataset, that lag is at most 0.1 s §3.3.3. Front extraction showed no accuracy gain over the filter in the authors' experiments §5.
  • Covariance discontinuities. The variance at a time step has two sources that do not always agree. This is small in practice, but it can give visible jumps in the reported uncertainty §3.3.3.
  • Position error rises at large windows. On Out-of-Bounds, position error rises at larger windows. The authors suspect the pose sensor's boundary measurements degrade, so the batch overfits them. This is a hypothesis, not tested §5.
  • Assumptions the paper lists. Noise is zero-mean with no bias. The prior has large modelling approximations. Only one robot and one sensor configuration were tested. Small frame misalignment between the sensor and motion-capture frames adds error §5.
  • Sensor noise levels. The authors report pose noise of about 1 mm in position and 0.01 rad in rotation, with data at 30 to 50 Hz §5. The paper expects the filter to perform well with noisier sensors and more compliant robots, though its estimates become less certain, and expects larger windows with lower-rate, noisier sensors §5.
  • Runtime depends on threads and hardware. The paper's timings come from one i7-13850HX §3.4.1. The run script pins 8 OpenMP threads [code: run_demo.sh#L8]. Measure on your own machine.
  • Build dependencies. The README lists Eigen 3.4, VTK 9.3, Qt 6.8 and lgmath, and says lgmath will move to GTSAM in a later version README.md. Build with the headless flag if you do not need the Qt visualiser [code: scripts/main.cpp#L47-L49].
  • Single-run results. The table reports one number per trajectory and estimator. No variance across runs is reported.

Lineage

  • Builds on: the batch continuous-time framework for continuum robots, which the paper cites as Teetaert2025 §3.1. The README lists arXiv:2510.01381 as a companion publication README.md, but this card does not confirm that it is the cited work.
  • Related sliding-window work: a constant-curvature sliding-window estimator (Abdelaziz et al., 2023) and a rigid-link sliding-window estimator evaluated in simulation only (Bastos et al., 2024), both discussed in the paper's related work §2. The paper does not give arXiv ids for them, and this card does not use their content.
  • Compared against: a continuous-time batch smoother from the same group tab. 1 and a one-step filter §4.
  • Superseded by: no later work is known from content in this wiki.

Source and license

Adapted from “A Sliding-Window Filter for Online Continuous-Time Continuum Robot State Estimation” by Spencer Teetaert, Sven Lilge, Jessica Burgner-Kahrs, Timothy D. Barfoot (arXiv:2510.26623), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/utiasASRL/space_time_continuum at commit ad387fbd1213 (MIT). Any quoted code is shown under that license.