> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/opentrack/opentrack/llms.txt
> Use this file to discover all available pages before exploring further.

# Filter Configuration

> Smooth and stabilize head tracking with OpenTrack's filtering system

Filters process raw tracking data to reduce noise, smooth movement, and improve tracking quality. OpenTrack includes several filter types optimized for different use cases.

## Filter System Overview

Filters implement the IFilter interface:

```cpp theme={null}
struct OTR_API_EXPORT IFilter : module_status_mixin {
    IFilter();
    
    // Perform filtering step
    virtual void filter(const double *input, double *output) = 0;
    
    // Optionally reset filter state when centering
    virtual void center() {}
};
```

### Module Selection

```cpp theme={null}
struct module_settings {
    bundle b { make_bundle("modules") };
    value<QString> filter_dll { b, "filter-dll", "accela" };
};
```

## Available Filters

<Tabs>
  <Tab title="Accela">
    ### Accela Filter (Recommended)

    Adaptive smoothing based on head velocity. Provides strong smoothing at rest and minimal lag during fast movements.

    **Best for**: General gaming and simulation

    **Implementation**:

    ```cpp theme={null}
    struct accela : IFilter {
        void filter(const double* input, double *output) override;
        void center() override { first_run = true; }
        
        spline spline_rot;   // Rotation smoothing curve
        spline spline_pos;   // Translation smoothing curve
        
    private:
        double last_output[6] {};
        double deltas[6] {};
        Timer t;
        bool first_run = true;
    };
    ```

    **How it works**:

    1. Calculate velocity (change per frame)
    2. Map velocity through spline curve to get smoothing factor
    3. Apply exponential smoothing based on factor

    **Advantages**:

    * Strong smoothing at rest (reduces jitter)
    * Minimal lag during fast movements
    * Separate curves for rotation and translation
    * Highly configurable
  </Tab>

  <Tab title="EWMA">
    ### Exponentially Weighted Moving Average

    Simple low-pass filter with constant smoothing.

    **Best for**: Quick setup, moderate smoothing

    **Configuration**:

    ```cpp theme={null}
    // Alpha factor: 0.0 (max smooth) to 1.0 (no smooth)
    // Typical value: 0.3-0.7
    ```

    **Algorithm**:

    ```cpp theme={null}
    output = alpha * input + (1 - alpha) * prev_output;
    ```

    **Advantages**:

    * Simple and predictable
    * Low CPU usage
    * Easy to tune

    **Disadvantages**:

    * Fixed lag regardless of movement speed
    * Trade-off between smoothness and responsiveness
  </Tab>

  <Tab title="Hamilton">
    ### Hamilton Kalman Filter

    Uses quaternions for rotation and Kalman filtering for optimal estimation.

    **Best for**: High precision applications, VR

    **Implementation**:

    ```cpp theme={null}
    struct hamilton : IFilter {
        void filter(const double* input, double *output) override;
        
    private:
        dquat rot_state;     // Rotation state
        vec3 pos_state;      // Position state
        // Kalman filter matrices
    };
    ```

    **Advantages**:

    * Mathematically optimal for Gaussian noise
    * Excellent for rotation (no gimbal lock)
    * Good prediction of position

    **Disadvantages**:

    * More complex to configure
    * Higher CPU usage
    * Requires understanding of Kalman filter parameters
  </Tab>

  <Tab title="None">
    ### No Filter

    Pass data through unchanged.

    **Best for**:

    * High-quality trackers with low noise
    * Testing and debugging
    * When game/application has built-in filtering
  </Tab>
</Tabs>

## Filter Processing Pipeline

Filters are applied in the tracking pipeline:

```cpp theme={null}
void pipeline::logic() {
    // ... get tracker data ...
    
    // Apply filter
    if (libs.pFilter) {
        Pose tmp(value);
        libs.pFilter->filter(value, tmp);
        value = tmp;
    }
    
    logger.write_pose(value); // "filtered"
    
    // ... continue processing ...
}
```

<Steps>
  <Step title="Input">
    Receive corrected pose data (after camera offset and centering):

    ```cpp theme={null}
    // input[0-5]: TX, TY, TZ, Yaw, Pitch, Roll
    ```
  </Step>

  <Step title="Filter Processing">
    Filter processes each frame:

    ```cpp theme={null}
    void filter(const double *input, double *output) {
        const double dt = timer.elapsed_seconds();
        timer.start();
        
        for (int i = 0; i < 6; i++) {
            output[i] = process_axis(input[i], i, dt);
        }
    }
    ```

    <Note>
      Filters must track their own timing using a timer. Frame rate is not guaranteed to be constant.
    </Note>
  </Step>

  <Step title="State Management">
    Filters maintain internal state:

    ```cpp theme={null}
    private:
        double last_output[6] {};  // Previous output
        double velocity[6] {};     // Current velocity
        Timer t;                   // Frame timing
        bool first_run = true;     // Skip first frame
    ```
  </Step>

  <Step title="Center Reset">
    When user centers, filter can reset:

    ```cpp theme={null}
    void center() override {
        first_run = true;
        // Clear accumulated state
        memset(last_output, 0, sizeof(last_output));
    }
    ```
  </Step>
</Steps>

## Accela Filter Configuration

The Accela filter is the most powerful and commonly used:

### Smoothing Curves

Accela uses two spline curves to map velocity to smoothing:

```cpp theme={null}
spline spline_rot;    // Rotation axes (Yaw, Pitch, Roll)
spline spline_pos;    // Translation axes (X, Y, Z)
```

### Curve Design

<Steps>
  <Step title="X-axis: Velocity">
    Input velocity in degrees/second (rotation) or cm/second (translation)

    * **0**: At rest (maximum smoothing)
    * **50-100**: Moderate movement
    * **200+**: Fast movement (minimal smoothing)
  </Step>

  <Step title="Y-axis: Smoothing Factor">
    Output smoothing strength:

    * **0.0**: No smoothing (instant response)
    * **0.5**: Moderate smoothing
    * **0.95**: Strong smoothing (slow response)
  </Step>

  <Step title="Typical Curve">
    ```
    Velocity (°/s)  →  Smoothing Factor
    ─────────────────────────────────
         0          →      0.90      (strong smooth)
        50          →      0.70      (moderate)
       100          →      0.40      (light)
       200          →      0.10      (minimal)
    ```

    This provides strong smoothing when still, but reduces lag during fast movements.
  </Step>
</Steps>

### Rotation vs Translation

<Tabs>
  <Tab title="Rotation Curve">
    Typically needs less aggressive smoothing:

    ```cpp theme={null}
    // Example rotation curve points
    { 0,   0.85 },   // At rest: strong smooth
    { 45,  0.60 },   // Slow turn
    { 90,  0.35 },   // Normal turn
    { 180, 0.15 },   // Fast turn
    { 300, 0.05 }    // Very fast: minimal lag
    ```

    Head rotation is generally smoother than position tracking.
  </Tab>

  <Tab title="Translation Curve">
    Often needs more aggressive smoothing:

    ```cpp theme={null}
    // Example translation curve points
    { 0,  0.92 },    // At rest: very strong smooth
    { 2,  0.75 },    // Slow movement (2 cm/s)
    { 5,  0.50 },    // Normal movement
    { 10, 0.25 },    // Fast movement
    { 20, 0.10 }     // Very fast
    ```

    Position tracking typically has more noise than rotation.
  </Tab>
</Tabs>

<Warning>
  High smoothing values (>0.95) can cause significant lag. Balance smoothness with responsiveness based on your tracker quality.
</Warning>

## EWMA Filter Configuration

Simpler than Accela, with fixed smoothing:

```cpp theme={null}
// Configuration
struct ewma_settings {
    value<slider_value> alpha_rot;    // Rotation smoothing
    value<slider_value> alpha_pos;    // Position smoothing
};

// Filter implementation
void ewma::filter(const double *input, double *output) {
    const double dt = t.elapsed_seconds();
    t.start();
    
    for (int i = 0; i < 6; i++) {
        const double alpha = (i < 3) ? s.alpha_pos : s.alpha_rot;
        output[i] = alpha * input[i] + (1 - alpha) * last_output[i];
        last_output[i] = output[i];
    }
}
```

### Tuning EWMA

* **Alpha = 0.1**: Maximum smoothing, high lag
* **Alpha = 0.5**: Balanced
* **Alpha = 0.9**: Minimal smoothing, minimal lag

<Note>
  Start with 0.5 and adjust. Lower values = smoother but more lag.
</Note>

## Filter Dialog Interface

Filters can provide live configuration:

```cpp theme={null}
struct OTR_API_EXPORT IFilterDialog : public BaseDialog {
    virtual void register_filter(IFilter* filter) = 0;
    virtual void unregister_filter() = 0;
};
```

### Live Updates

```cpp theme={null}
class dialog_accela : public IFilterDialog {
    void register_filter(IFilter* f) override {
        filter = static_cast<accela*>(f);
        // Can now access filter splines for live preview
    }
    
    void unregister_filter() override {
        filter = nullptr;
    }
    
private:
    accela* filter = nullptr;
};
```

## Custom Filter Development

<Steps>
  <Step title="Implement IFilter">
    ```cpp theme={null}
    class MyFilter : public IFilter {
    public:
        module_status initialize() override {
            return status_ok();
        }
        
        void filter(const double *input, double *output) override {
            // Your filtering logic
        }
        
        void center() override {
            // Reset filter state
        }
    };
    ```
  </Step>

  <Step title="Handle Timing">
    ```cpp theme={null}
    private:
        Timer t;
        bool first_run = true;
        
    void filter(const double *input, double *output) override {
        if (first_run) {
            first_run = false;
            memcpy(output, input, sizeof(double) * 6);
            return;
        }
        
        const double dt = t.elapsed_seconds();
        t.start();
        
        // Use dt for time-based filtering
    }
    ```
  </Step>

  <Step title="Register Plugin">
    ```cpp theme={null}
    class MyFilterMeta : public Metadata {
        QString name() override { return "My Filter"; }
        QIcon icon() override { return QIcon(":/filter.png"); }
    };

    OPENTRACK_DECLARE_FILTER(
        MyFilter,
        MyFilterDialog,
        MyFilterMeta
    )
    ```
  </Step>
</Steps>

## Best Practices

### Do's

<Check>
  **Use time-based filtering** - Account for variable frame rate with dt
</Check>

<Check>
  **Handle first frame** - Skip or initialize carefully on first run
</Check>

<Check>
  **Reset on center** - Clear accumulated state when user centers
</Check>

<Check>
  **Separate rotation/translation** - Different axes need different treatment
</Check>

### Don'ts

<Warning>
  **Don't assume constant frame rate** - Pipeline runs at \~250Hz but varies
</Warning>

<Warning>
  **Don't introduce NaN/Infinity** - Check for invalid math operations
</Warning>

<Warning>
  **Don't block** - Filter runs in tracking thread, keep it fast
</Warning>

## Troubleshooting

<Accordion title="Too much lag">
  1. Reduce smoothing factor (Accela: lower curve values)
  2. Make curve steeper (faster response at higher velocities)
  3. Try EWMA with higher alpha (0.7-0.9)
  4. Check if game has additional filtering
</Accordion>

<Accordion title="Still too jittery">
  1. Increase smoothing at low velocities
  2. Check tracker quality and lighting
  3. Try Hamilton filter for better noise handling
  4. Verify tracker is running at stable frame rate
</Accordion>

<Accordion title="Filter not applying">
  1. Verify filter is selected in modules
  2. Check filter initialization didn't fail
  3. Restart tracking after changing filter
  4. Check OpenTrack logs for errors
</Accordion>

<Accordion title="Different axes feel wrong">
  Configure separate smoothing per axis:

  ```cpp theme={null}
  // In custom filter
  for (int i = 0; i < 6; i++) {
      const double factor = smoothing_factors[i];
      output[i] = factor * input[i] + (1-factor) * last[i];
  }
  ```
</Accordion>

## Performance Impact

Filter CPU usage comparison (approximate):

* **None**: 0% overhead
* **EWMA**: \<0.1% CPU
* **Accela**: \<0.5% CPU (includes spline evaluation)
* **Hamilton**: 1-2% CPU (quaternion and matrix math)

All filters are designed to run efficiently at 250Hz.

## Next Steps

<CardGroup cols={2}>
  <Card title="Mapping Curves" icon="chart-line" href="/guides/mapping-curves">
    Fine-tune response after filtering
  </Card>

  <Card title="Configuration" icon="gear" href="/guides/configuration">
    Advanced tracking settings
  </Card>

  <Card title="Tracker Setup" icon="camera" href="/guides/tracker-setup">
    Improve tracking quality at the source
  </Card>
</CardGroup>
