Susi Server

Inside-Out Fusion

Node ID: insideOutFusion · Role: Filter · Realtime config: no

Description

Complementary filter that fuses inside-out tracking (e.g. VR headset SLAM) with external optical tracking (e.g. OptiTrack) to produce a globally referenced, high-frequency pose.

Algorithm notes

What this node estimates

This is a complementary (blending) filter, not a Kalman filter. It has no dynamic model and no covariance; it maintains a small set of running averages plus one alignment rotation. The estimated quantity is a single 6-DOF pose: a position and an orientation expressed in the frame of the external optical reference (global, room-fixed). The internal state consists of three smoothed position accumulators (slow inside-out position, smoothed fast inside-out motion, slow optical position), a rotation that maps the inside-out frame into the global frame, and an optional residual rotation offset produced by auto-calibration.

The idea is a frequency split: the external reference supplies absolute, drift-free low-frequency position, while the inside-out source supplies smooth, low-latency high-frequency motion. Orientation is taken from the inside-out source, rotated into the global frame, and slowly pulled toward the optical reference.

How the two streams are used

Both inputs arrive as pose streams; the inside-out source is identified by its sender id, and any other pose stream is treated as the external absolute reference. Only inside-out samples produce output, so the node’s output rate equals the inside-out rate. External samples merely refresh the stored reference and nudge the alignment.

At startup, no alignment exists. Until the first external sample has been seen, output orientation is passed through unchanged and position is clamped to a fixed standing height; after external data begins arriving but before alignment converges, the raw inside-out position is passed through. Alignment is initialized exactly on the first external sample that follows at least one inside-out sample, then refined by a small rotational step per external sample, so orientation converges over seconds rather than instantly. The position accumulators seed themselves on the first sample pair where both positions are non-zero, meaning the fused position is briefly biased toward the seeding values and settles according to the low-pass weights.

Auto-calibration runs only after alignment exists and after the configured startup delay. It gathers paired orientations in a sliding window, forms a mean residual rotation once at least a quarter of the window (minimum ten samples) is filled, and thereafter blends new estimates in with the forgetting factor. It can be persisted and reloaded so restarts begin warm.

Parameter groups

Frame alignment. The static optical orientation and position offsets pre-transform incoming optical data. Supplying either of them switches the node into a fully static mode: online alignment and auto-calibration are locked out, and the offsets become the sole means of reconciling the two frames. Use these when you have a hand-eye calibration; otherwise leave them at identity and let the online path work.

Position blending. The two low-pass weights and the high-pass smoothing weight set the crossover between reference and inside-out motion. All three are per-sample, so their effective time constants scale with input rate. The two axis switches change the topology rather than the smoothing: taking vertical from inside-out bypasses blending entirely for height, and disabling the horizontal contribution makes horizontal position follow the raw optical reference with no inside-out motion at all.

Orientation. The blend weight sets how fast the alignment tracks the optical reference. Auto-calibration settings (delay, window, forgetting factor, stationary gate and its motion threshold, persistence) govern the slower residual correction.

Latency. The prediction look-ahead is published as a command to the pose source rather than applied inside this node.

Tuning and failure modes

Start with defaults, verify that alignment settles before judging position quality, and change one group at a time. Raise the orientation blend weight if heading drift is visible; lower it if optical jitter appears in the output. Raise the inside-out low-pass weight if slow real motion is being absorbed by the reference instead of appearing promptly.

Known failure modes: if the external reference drops out, its last value is held indefinitely and the fused position slowly parks on stale data; if alignment is established while the inside-out source is still warming up, the initial rotation can be badly wrong and only recovers at the blend rate; unequal latency between the two streams biases auto-calibration during motion, which the stationary gate mitigates. Note that the motion threshold is computed from per-sample position change and therefore scales with input rate, so treat its value as empirical rather than as a true speed. Finally, a persisted calibration reloaded after a physical remount will be wrong until it is deleted or re-converged.

Inputs / Outputs

Config aliases

InsideOutFusion, insideOutFusion

Required feature

imu_optical_fusion

Properties

The following fields are exposed in the node’s Properties panel in the UI. Types map to UI widgets (e.g. string → text input, number → numeric, boolean → toggle, select → dropdown, json → JSON editor).

KeyLabelTypeDefaultDescription / Notes
insideOutSenderIdInside-Out Sender IDstringinsideOutSender id marking which incoming optical stream is the inside-out (SLAM/headset) source. The other optical stream is treated as the external absolute reference. Both inputs arrive as OpticalData and are distinguished by this id.
ioLpWeightIO Low-Pass Weightnumber0.999Low-pass smoothing factor (0-1) used to estimate the slow drift of the inside-out position. The estimate is subtracted to isolate the high-frequency motion. Closer to 1 = slower drift estimate, so more of the inside-out signal counts as fast motion.
ioHpWeightIO High-Pass Weightnumber0.85Smoothing factor (0-1) applied to the high-pass (fast-motion) component of the inside-out position before it is added to the optical reference. Higher values smooth the fast motion more.
optLpWeightOptical Low-Pass Weightnumber0.999Low-pass smoothing factor (0-1) on the external optical position, which provides the drift-free absolute reference. The fused position is this low-passed optical plus the high-passed inside-out motion. Closer to 1 = smoother, slower-responding absolute reference.
oriBlendWeightOrientation Blend Weightnumber0.02Per-update slerp weight pulling the inside-out alignment quaternion toward the optical reference orientation. Small values (0.02) correct orientation drift slowly and smoothly; larger values follow the optical reference faster but pass through more jitter.
useIOHeightUse IO HeightbooleantrueTake the vertical (height) component of position from the inside-out source directly instead of the complementary blend. Useful when the inside-out tracker has good vertical stability and the optical reference does not.
useIOHorizontalUse IO HorizontalbooleantrueBlend the inside-out high-frequency motion into the horizontal position. When off, horizontal position follows the external optical reference directly with no inside-out contribution.
predictionTimeModifierPrediction Time Modifiernumber0.02Look-ahead time in seconds used to forward-predict the pose, compensating for downstream latency. 0 disables prediction.
autoCalibEnabledEnable Auto-CalibrationbooleantrueContinuously estimate the rotational alignment between the inside-out and optical frames online. Disable to keep the alignment fixed (e.g. when a static Optical Orientation Offset is supplied).
autoCalibDelaySecAuto-Calib Delay (s)number5.0Delay in seconds after startup before auto-calibration begins, allowing both tracking sources to stabilize first.
autoCalibWindowSizeAuto-Calib Window Sizenumber500Number of recent pose-pair samples retained for the alignment estimate. Larger windows give a more stable calibration but adapt more slowly to changes.
autoCalibForgetFactorAuto-Calib Forget Factornumber0.001Exponential forgetting factor for the recursive alignment estimate. Higher values weight recent samples more heavily, adapting faster at the cost of stability.
autoCalibOnlyStationaryAuto-Calib Only When StationarybooleanfalseUpdate the alignment estimate only while the tracked body is below the motion threshold. Avoids corrupting the calibration with dynamics, at the cost of slower convergence.
autoCalibMotionThresholdAuto-Calib Motion Threshold (m/s)number0.05Speed in m/s below which the body is considered stationary for the Auto-Calib Only When Stationary gate.
autoCalibSaveAuto-Calib Save/LoadbooleanfalsePersist the converged alignment calibration to disk and reload it on the next start, so the filter resumes warm instead of recalibrating from scratch.
opticalOrientationOffsetOptical Orientation Offsetquaternion{"w":1.0,"x":0.0,"y":0.0,"z":0.0}Static rotation applied to incoming optical pose before fusion. Set from a hand-eye calibration so the optical reference (e.g. DTrack) lines up with the inside-out frame (e.g. ALVR native pose). When non-identity, replaces the online auto-calibration: the orientation alignment and the auto-calib rotation offset are both locked to identity.
opticalPositionOffsetOptical Position Offsetvector3{"x":0.0,"y":0.0,"z":0.0}Static translation applied to incoming optical pose after the orientation offset (p’ = q_off * p + t_off). Paired with opticalOrientationOffset to bring the optical reference into the inside-out frame.

Example node definition

A full node definition in config.json looks like the block below. Paste it under the sinks key of your config, keyed by the node’s instance name (any identifier; it doesn’t have to match the node ID).

{
  "sinks": {
    "insideOutFusion": {
      "dataEndpoint": "inproc://insideOutFusion_data",
      "inputEndpoints": [
        "inproc://fusedPose_data",
        "inproc://optical_data"
      ],
      "inputDataFilter": [
        "FusedPose",
        "Optical"
      ],
      "settings": {
        "autoCalibDelaySec": 5.0,
        "autoCalibEnabled": true,
        "autoCalibForgetFactor": 0.001,
        "autoCalibMotionThreshold": 0.05,
        "autoCalibOnlyStationary": false,
        "autoCalibSave": false,
        "autoCalibWindowSize": 500,
        "insideOutSenderId": "insideOut",
        "ioHpWeight": 0.85,
        "ioLpWeight": 0.999,
        "optLpWeight": 0.999,
        "opticalOrientationOffset": {
          "w": 1.0,
          "x": 0.0,
          "y": 0.0,
          "z": 0.0
        },
        "opticalPositionOffset": {
          "x": 0.0,
          "y": 0.0,
          "z": 0.0
        },
        "oriBlendWeight": 0.02,
        "predictionTimeModifier": 0.02,
        "useIOHeight": true,
        "useIOHorizontal": true
      }
    }
  }
}

Field reference

FieldPurpose
dataEndpointEndpoint this node binds to publish its output. Omit to let the runtime generate one.
inputEndpointsList of upstream endpoints this node connects to (outEndpoint / dataEndpoint values from upstream nodes).
inputDataFilterOptional whitelist of data types. Messages whose type isn’t in the list are dropped at the subscriber.
settingsAll user-configurable fields — see the Properties table above.

Connections can also be declared at the top-level connections array when settings.explicitConnections is true, as an alternative to filling inputEndpoints on each node.

Loading documentation…