# Temporal Smoothing Techniques for CSI Phase Trajectories in WiFi‑DensePose

> Explore temporal smoothing techniques like moving-average and Butterworth filtering used in WiFi-DensePose to stabilize CSI phase trajectories for accurate pose estimation.

- Repository: [rUv/wifi-densepose](https://github.com/ruvnet/wifi-densepose)
- Tags: deep-dive
- Published: 2026-02-19

---

**WiFi‑DensePose employs three distinct temporal smoothing mechanisms—moving‑average filtering, Butterworth low‑pass filtering, and exponential moving‑average (EMA) on detection confidence—to stabilize noisy CSI phase trajectories before pose estimation.**

The `ruvnet/wifi-densepose` repository processes raw Channel State Information (CSI) through a dedicated sanitization pipeline that applies these temporal smoothing techniques for CSI phase trajectories to suppress high‑frequency jitter while preserving essential motion cues. Understanding these filtering stages is critical for configuring the trade‑off between noise reduction and motion fidelity in dense pose reconstruction systems.

## Moving‑Average Smoothing for Phase Trajectories

The first smoothing layer operates inside `PhaseSanitizer` within [`v1/src/core/phase_sanitizer.py`](https://github.com/ruvnet/wifi-densepose/blob/main/v1/src/core/phase_sanitizer.py). The **`smooth_phase`** method delegates to **`_apply_moving_average`**, which implements a symmetric sliding window mean.

### Implementation Details

The method accepts a configurable **`smoothing_window`** parameter. If the supplied window size is even, the implementation automatically increments it to the next odd integer, ensuring a symmetric neighborhood around each sample. This prevents phase shifts that would otherwise distort the temporal alignment of the CSI data.

```python
from v1.src.core.phase_sanitizer import PhaseSanitizer

config = {
    'unwrapping_method': 'numpy',
    'outlier_threshold': 3.0,
    'smoothing_window': 5,          # Must be odd; auto-corrected if even

    'enable_smoothing': True,
    'enable_noise_filtering': True,
    'noise_threshold': 0.05         # Butterworth cutoff (fraction of Nyquist)

}
sanitizer = PhaseSanitizer(config)

raw_phase = ...                     # np.ndarray shape (antennas, samples)

clean_phase = sanitizer.sanitize_phase(raw_phase)

```

## Butterworth Low‑Pass Filtering

Following the moving‑average stage, the pipeline applies a **4th‑order Butterworth low‑pass filter** via the **`filter_noise`** method, which calls **`_apply_low_pass_filter`**. This stage targets residual high‑frequency artifacts that survive the initial averaging.

### Zero‑Phase Filtering

The cutoff frequency is proportional to the **`noise_threshold`** parameter, expressed as a fraction of the Nyquist rate. The implementation uses **`scipy.signal.filtfilt`** to apply the filter forward and backward, eliminating group delay and ensuring zero phase distortion. This preserves the temporal alignment of phase trajectories critical for accurate time‑of‑flight calculations.

## Exponential Moving Average for Detection Confidence

Beyond the phase trajectory itself, WiFi‑DensePose stabilizes binary human‑presence decisions using an **exponential moving‑average (EMA)** implemented in [`v1/src/core/csi_processor.py`](https://github.com/ruvnet/wifi-densepose/blob/main/v1/src/core/csi_processor.py). The **`CSIProcessor._apply_temporal_smoothing`** method smooths scalar confidence scores across successive CSI frames.

### EMA Formula and Configuration

The smoothing follows the recurrence relation:

```text
smoothed = α · previous + (1 − α) · raw

```

Here, **α** corresponds to the configurable **`smoothing_factor`** (typically near 0.9). This technique prevents rapid oscillations between detection states when noise temporarily perturbs the signal energy.

```python
from v1.src.core.csi_processor import CSIProcessor

proc_cfg = {
    'sampling_rate': 1000,
    'window_size': 256,
    'overlap': 0.5,
    'noise_threshold': 0.1,
    'smoothing_factor': 0.9       # EMA coefficient α

}
processor = CSIProcessor(proc_cfg)

csi_data = ...                    # CSIData instance

result = await processor.process_csi_data(csi_data)

print(f"Human detected: {result.human_detected}, confidence (smoothed): {result.confidence}")

```

## Configuration and Implementation Examples

All three temporal smoothing techniques for CSI phase trajectories expose hyperparameters through the configuration dictionaries passed to `PhaseSanitizer` and `CSIProcessor`. When tuning these values:

- **Increase** `smoothing_window` to aggressively suppress jitter, but expect increased latency and potential smearing of rapid motion.
- **Decrease** `noise_threshold` to raise the Butterworth cutoff and preserve high‑frequency motion details.
- **Adjust** `smoothing_factor` closer to 1.0 for stronger confidence stabilization, or closer to 0.0 for faster reaction to appearance/disappearance events.

## Summary

- **Moving‑average smoothing** in [`v1/src/core/phase_sanitizer.py`](https://github.com/ruvnet/wifi-densepose/blob/main/v1/src/core/phase_sanitizer.py) applies a symmetric sliding window mean with automatic odd‑window correction via `_apply_moving_average`.
- **Butterworth low‑pass filtering** uses a 4th‑order design with `scipy.signal.filtfilt` for zero‑phase distortion, configured by `noise_threshold`.
- **Exponential moving‑average** in [`v1/src/core/csi_processor.py`](https://github.com/ruvnet/wifi-densepose/blob/main/v1/src/core/csi_processor.py) stabilizes detection confidence scores using `_apply_temporal_smoothing` with configurable coefficient α.
- These stages execute sequentially within `sanitize_phase()` and `process_csi_data()` to yield temporally coherent phase trajectories suitable for dense pose estimation.

## Frequently Asked Questions

### What is the default window size for moving‑average smoothing in WiFi‑DensePose?

The default configuration typically sets `smoothing_window` to 5 samples, though the implementation accepts any positive integer. If you supply an even number, the `_apply_moving_average` method automatically increments it to the nearest odd integer to maintain symmetry.

### Why does the Butterworth filter use `filtfilt` instead of `lfilter`?

The `_apply_low_pass_filter` method uses `scipy.signal.filtfilt` to process the signal both forward and backward. This zero‑phase filtering technique cancels out the group delay inherent in causal IIR filters, ensuring that peaks and troughs in the CSI phase trajectory remain temporally aligned with the original raw data.

### How does the EMA smoothing factor affect human detection latency?

A high `smoothing_factor` (e.g., 0.95) makes the EMA in `_apply_temporal_smoothing` heavily weight historical confidence values, reducing false‑positive flicker but introducing lag when a human actually enters the scene. Conversely, a low factor (e.g., 0.5) makes the system responsive but more susceptible to noise‑induced state changes.

### Can I disable temporal smoothing if I need raw phase data?

Yes. Setting `enable_smoothing` to `False` in the `PhaseSanitizer` configuration skips the moving‑average stage, and setting `enable_noise_filtering` to `False` bypasses the Butterworth filter. However, the EMA in `CSIProcessor` is typically always active unless you modify the source code to skip the `_apply_temporal_smoothing` call.