What Are Doppler Features in CSI and Why They Matter for Wi-Fi Sensing

Doppler features in Channel State Information (CSI) encode the frequency shifts caused by motion, enabling Wi-Fi signals to detect velocity, direction, and even micro-movements like breathing.

In the ruvnet/wifi-densepose repository, Doppler features transform raw Wi-Fi packets into motion-aware representations that power human detection, pose estimation, and vital-signs monitoring. While static CSI captures environmental geometry, Doppler features specifically track how the phase of each sub-carrier changes across successive packets, revealing the dynamic components of the channel.

Understanding Doppler Features in Channel State Information

Channel State Information (CSI) provides complex-valued amplitude and phase measurements for every Wi-Fi sub-carrier. In static environments, these values remain relatively constant, reflecting the geometry of walls, furniture, and other fixed objects.

Doppler features extract the temporal variation from this CSI stream. When objects move—whether a person walking, a hand gesturing, or a chest expanding during breathing—they induce minute frequency shifts in the reflected Wi-Fi signals. By analyzing how phase changes across consecutive packets, the system calculates the Doppler shift for each sub-carrier, yielding a velocity-sensitive feature vector.

How Doppler Features Are Extracted in WiFi-DensePose

The wifi-densepose pipeline implements a four-stage extraction process that converts raw CSI histories into compact Doppler descriptors.

Temporal Stacking and FFT Processing

The system first collects CSI amplitude or phase values for each sub-carrier over a short temporal window. This creates a time-series matrix where rows represent sub-carriers and columns represent consecutive packets.

A Fast Fourier Transform (FFT) is then applied across the time axis for each sub-carrier. This converts the temporal variations into a frequency spectrum, where peaks correspond to periodic motion patterns.

Peak Detection and Feature Summarization

Following the FFT, the algorithm identifies the peak Doppler frequency for each sub-carrier by locating the frequency bin with the largest magnitude, excluding the DC (zero-frequency) component.

The system then populates a DopplerFeatures structure with statistical summaries:

  • Mean magnitude across all sub-carriers
  • Spread (standard deviation) indicating motion diversity
  • Per-sub-carrier shift vector capturing individual frequency offsets

Implementation Locations

In v1/src/core/csi_processor.py, the _extract_doppler_features method handles Python-side extraction (referenced around lines 169-170). The Rust implementation in rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/features.rs provides the DopplerFeatures::from_csi_history function (lines 323-388) for high-performance signal processing.

Why Doppler Features Are Critical for Wi-Fi Sensing

Doppler features provide capabilities that static CSI measurements cannot achieve alone, making them essential for advanced Wi-Fi sensing applications.

Motion Detection and Velocity Estimation

The primary advantage of Doppler analysis is direct velocity measurement. Moving objects induce frequency shifts proportional to their speed and direction relative to the transceiver. This enables the detect_human_presence function in csi_processor.py to distinguish between stationary environments and moving humans, even when amplitude changes are subtle.

Micro-Doppler for Vital Signs Monitoring

Micro-Doppler refers to minute frequency shifts caused by tiny movements—specifically chest displacements during breathing (0.1–0.5 Hz) and heartbeats (0.8–2 Hz). The WiFi-Mat disaster-response module described in the repository's README.md leverages these micro-Doppler signatures to detect vital signs through walls, enabling search-and-rescue operations without visual contact.

Robustness to Multipath Environments

Static CSI suffers in environments with heavy multipath reflections, where fixed objects create complex interference patterns. Doppler features are inherently robust to static multipath because they measure frequency changes rather than absolute phase values. Static reflections contribute to the DC component (which is excluded during peak detection), while dynamic targets produce distinct frequency peaks, improving discrimination of moving targets against cluttered backgrounds.

Machine Learning Input for Pose Estimation

The DensePose head in the system consumes CSIFeatures structures that include Doppler vectors alongside amplitude, phase, and correlation features. Doppler provides a compact, physics-based motion descriptor that neural networks can interpret as velocity cues, improving the accuracy of human pose estimation from Wi-Fi signals alone.

Implementation Examples

Python – Extracting Doppler with the Processor

The CsiProcessor class provides a high-level interface for Doppler extraction:

from v1.src.core.csi_processor import CsiProcessor
import numpy as np

# Mock CSI frame: 10 sub-carriers, single antenna

csi_frame = np.random.rand(10)

processor = CsiProcessor(enable_feature_extraction=True)
features = processor.extract_features(csi_frame)

# Doppler shift is a NumPy array of length = #sub-carriers

print("Doppler shifts (Hz):", features.doppler_shift)   # → shape (10,)

This extraction occurs in _extract_doppler_features within v1/src/core/csi_processor.py (lines 169-170).

Rust – Building DopplerFeatures from CSI History

For performance-critical applications, the Rust implementation processes CSI histories directly:

use wifi_densepose_signal::features::DopplerFeatures;
use wifi_densepose_signal::types::CsiData;

// History must contain at least two consecutive CSI frames
let doppler = DopplerFeatures::from_csi_history(&history, sampling_rate);

// Access per-sub-carrier shifts
println!("Shifts per sub-carrier: {:?}", doppler.shifts);
println!("Peak frequency: {:.2} Hz", doppler.peak_frequency);

The implementation resides in rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/features.rs (lines 323-388).

Unit Test Validation

The repository includes tests verifying Doppler extraction accuracy:

def test_doppler_shape(sample_features):
    assert sample_features.doppler_shift.shape == (10,)

This test is located in v1/tests/unit/test_csi_processor_tdd.py (line 437).

Summary

  • Doppler features in CSI encode motion-induced frequency shifts by analyzing phase changes across consecutive Wi-Fi packets.
  • The extraction pipeline involves temporal stacking, FFT processing, and peak detection, implemented in both Python (csi_processor.py) and Rust (features.rs).
  • These features enable velocity estimation, micro-Doppler vital-signs detection, and robust human presence sensing in multipath environments.
  • Doppler vectors provide physics-based motion descriptors that improve machine learning models for human pose estimation in the DensePose pipeline.

Frequently Asked Questions

How do Doppler features differ from regular CSI amplitude and phase measurements?

While standard CSI captures static geometric information about the environment through amplitude and absolute phase values, Doppler features specifically measure the rate of change in these signals over time. Static CSI reflects fixed objects like walls and furniture, whereas Doppler features isolate dynamic components caused by motion, making them insensitive to static multipath interference while highly responsive to moving targets.

What is the minimum temporal resolution required to extract meaningful Doppler features?

The wifi-densepose system requires at least two consecutive CSI frames to calculate initial Doppler shifts, though practical implementations use short temporal windows of multiple packets to improve spectral resolution. For detecting human motion (walking, gestures), sampling rates of 100-1000 Hz provide sufficient resolution, while micro-Doppler applications like heartbeat detection (0.8-2 Hz) require longer observation windows to resolve sub-Hz frequency shifts.

Why are Doppler features more robust to multipath effects than static CSI measurements?

Static CSI suffers in environments with heavy multipath because fixed reflections create complex interference patterns that obscure target signatures. Doppler features filter out the DC (zero-frequency) component, which represents static multipath, and focus exclusively on frequency shifts caused by motion. Since static objects produce no frequency shift, they effectively disappear from the Doppler spectrum, allowing the system to discriminate dynamic human targets against cluttered backgrounds with greater reliability.

How does the Rust implementation of Doppler extraction differ from the Python version?

The Rust implementation in features.rs (lines 323-388) provides a high-performance, memory-safe implementation through DopplerFeatures::from_csi_history, making it suitable for real-time processing and embedded deployments. The Python implementation in csi_processor.py (around lines 169-170) through _extract_doppler_features offers greater flexibility for research and rapid prototyping, integrating seamlessly with NumPy-based machine learning pipelines. Both implementations follow the same mathematical pipeline—temporal stacking, FFT, and peak detection—but the Rust version optimizes for throughput while Python prioritizes ease of experimentation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →