# How to Process Raw CSI Data Frames in Rust: A Complete Guide to the wifi-densepose Pipeline

> Learn to process raw CSI data frames in Rust using the wifi-densepose pipeline. This guide covers noise removal, normalization, and feature extraction for DensePose estimation.

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

---

**The wifi-densepose Rust crate processes raw CSI data frames by constructing a `CsiData` record via the builder pattern, executing configurable preprocessing (noise removal, Hamming windowing, and normalization) through `CsiProcessor`, and extracting high-level descriptors like `AmplitudeFeatures` and `PhaseFeatures` for downstream DensePose estimation.**

Processing raw Channel State Information (CSI) frames from Wi-Fi hardware requires a robust pipeline to convert noisy radio signals into usable features for machine learning models. The **ruvnet/wifi-densepose** repository provides a production-ready Rust implementation that demonstrates how to process raw CSI data frames in Rust, offering configurable preprocessing, temporal smoothing, and comprehensive feature extraction. This guide walks through the architectural components, configuration options, and practical code examples needed to integrate CSI processing into your own Rust applications.

## Understanding the CSI Processing Architecture

The pipeline centers on three core abstractions defined in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/csi_processor.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/csi_processor.rs) and [`features.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/features.rs):

- **CsiData**: The fundamental container holding amplitude matrices, phase matrices, and metadata (timestamp, frequency, bandwidth, SNR).
- **CsiProcessor**: The orchestrator that applies preprocessing steps, maintains frame history, and tracks processing statistics.
- **FeatureExtractor**: Transforms preprocessed CSI into structured descriptors including `AmplitudeFeatures`, `PhaseFeatures`, `CorrelationFeatures`, and `DopplerFeatures`.

## Step 1: Building CsiData from Raw Frames

Raw CSI frames from Wi-Fi drivers typically arrive as flat binary buffers containing subcarrier amplitude and phase values. Before processing, you must construct a `CsiData` instance using the builder pattern implemented in [`csi_processor.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/csi_processor.rs).

### Using the CsiDataBuilder

The `CsiDataBuilder` requires at minimum an amplitude matrix (`Array2<f64>`), a phase matrix (`Array2<f64>`), and a timestamp. Additional metadata such as center frequency, bandwidth, and SNR improve the quality of downstream feature extraction.

```rust
use chrono::Utc;
use ndarray::Array2;
use wifi_densepose_signal::CsiData;

fn parse_raw_csi(bytes: &[u8]) -> Result<CsiData, Box<dyn std::error::Error>> {
    // Example: parse 4 antennas × 64 subcarriers from a binary layout
    let amplitude_flat: Vec<f64> = bytes[0..2048]
        .chunks(8)
        .map(|b| f64::from_le_bytes(b.try_into().unwrap()))
        .collect();
    let phase_flat: Vec<f64> = bytes[2048..4096]
        .chunks(8)
        .map(|b| f64::from_le_bytes(b.try_into().unwrap()))
        .collect();

    let amplitude = Array2::from_shape_vec((4, 64), amplitude_flat)?;
    let phase = Array2::from_shape_vec((4, 64), phase_flat)?;

    Ok(CsiData::builder()
        .timestamp(Utc::now())
        .amplitude(amplitude)
        .phase(phase)
        .frequency(5.0e9)   // 5 GHz
        .bandwidth(20.0e6)  // 20 MHz
        .snr(28.0)
        .build()?)
}

```

*Source*: [[`csi_processor.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/csi_processor.rs) – builder implementation (lines 87‑106)](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/csi_processor.rs#L87-L106)

## Step 2: Configuring the Processor Pipeline

The `CsiProcessor` behavior is governed by `CsiProcessorConfig`, which exposes tuning parameters for sampling rate, window dimensions, noise floors, and temporal smoothing. Instantiating the processor validates the configuration and allocates internal buffers.

### Tuning CsiProcessorConfig

Key parameters include:
- **sampling_rate**: Wi-Fi sampling frequency in Hz (typically 1‑2 kHz for CSI tools).
- **window_size**: FFT window length (power of two, e.g., 256).
- **overlap**: Fractional overlap between consecutive windows (0.0‑1.0).
- **noise_threshold**: Amplitude values below this dB threshold are zeroed.
- **smoothing_factor**: Exponential moving average coefficient for temporal smoothing.
- **max_history_size**: Capacity of the sliding window for historical frames.

```rust
use wifi_densepose_signal::{CsiProcessor, CsiProcessorConfig};

fn initialize_processor() -> Result<CsiProcessor, Box<dyn std::error::Error>> {
    let config = CsiProcessorConfig::builder()
        .sampling_rate(2000.0)          // 2 kHz
        .window_size(256)
        .overlap(0.5)                   // 50% overlap
        .noise_threshold(-35.0)         // dB
        .smoothing_factor(0.9)
        .max_history_size(500)
        .build()?;
    
    CsiProcessor::new(config)
        .map_err(|e| e.into())
}

```

*Source*: [[`csi_processor.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/csi_processor.rs) – config builder (lines 88‑111)](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/csi_processor.rs#L88-L111)

## Step 3: Preprocessing Raw CSI Data

Preprocessing transforms noisy raw frames into clean signals suitable for feature extraction. The `CsiProcessor::preprocess` method orchestrates three configurable stages: noise removal, windowing, and normalization.

### Noise Removal and Windowing

The preprocessor first applies a noise floor threshold to eliminate low-amplitude artifacts. It then multiplies the signal by a Hamming window to reduce spectral leakage before FFT operations.

*Source*: [[`csi_processor.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/csi_processor.rs) – preprocessing implementation (lines 96‑126)](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/csi_processor.rs#L96-L126)

### Amplitude Normalization

Normalization scales amplitude values to a consistent range (typically 0‑1 or dB relative to maximum), compensating for hardware gain variations across different Wi-Fi chipsets.

```rust
use wifi_densepose_signal::{CsiData, CsiProcessor};

fn process_single_frame(
    processor: &mut CsiProcessor,
    raw: &CsiData,
) -> Result<CsiData, Box<dyn std::error::Error>> {
    // Run noise removal, windowing, and normalization
    let cleaned = processor.preprocess(raw)?;
    
    println!("Preprocessed SNR: {:.2} dB", cleaned.metadata.snr);
    println!("Amplitude range: {:.3} to {:.3}", 
        cleaned.amplitude.min()?, 
        cleaned.amplitude.max()?);
    
    Ok(cleaned)
}

```

## Step 4: Extracting Features from CSI Frames

Feature extraction converts preprocessed CSI into compact descriptors that capture spatial and temporal characteristics of wireless channels. The `FeatureExtractor` generates five distinct feature categories defined in [`features.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/features.rs).

### Amplitude and Phase Features

`AmplitudeFeatures` compute statistical moments (mean, variance, peak, kurtosis) across subcarriers and antennas. `PhaseFeatures` capture coherence metrics and unwrap phase differences to estimate path lengths.

*Source*: [[`features.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/features.rs) – feature structs (lines 13‑74)](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/features.rs#L13-L74)

### Correlation and Doppler Descriptors

`CorrelationFeatures` measure spatial consistency across antenna pairs, while `DopplerFeatures` estimate velocity through frequency shifts across consecutive frames.

```rust
use wifi_densepose_signal::{CsiData, FeatureExtractor};

fn analyze_frame(csi: &CsiData) {
    let extractor = FeatureExtractor::new();
    let features = extractor.extract(csi);
    
    // Amplitude statistics
    println!("Peak amplitude: {:.3}", features.amplitude.peak);
    println!("Amplitude variance: {:.3}", features.amplitude.variance);
    
    // Phase coherence indicates multipath stability
    println!("Phase coherence: {:.3}", features.phase.coherence);
    
    // Doppler shift suggests motion
    println!("Doppler velocity: {:.3} m/s", features.doppler.velocity);
}

```

*Source*: [[`features.rs`](https://github.com/ruvnet/wifi-densepose/blob/main/features.rs) – extractor entry point (lines 580‑607)](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/src/features.rs#L580-L607)

## Managing Frame History and Temporal Smoothing

Real-world CSI streams require handling multiple consecutive frames to stabilize detection results. The `CsiProcessor` maintains a sliding window of historical frames and applies exponential moving average smoothing to detection confidences.

The `add_to_history` method inserts preprocessed frames into a ring buffer with configurable capacity (`max_history_size`), while `apply_temporal_smoothing` reduces false positives caused by transient noise spikes.

```rust
use wifi_densepose_signal::{CsiData, CsiProcessor, CsiProcessorConfig};

fn batch_process_frames(frames: Vec<CsiData>) -> Result<(), Box<dyn std::error::Error>> {
    let config = CsiProcessorConfig::builder()
        .max_history_size(10)
        .smoothing_factor(0.85)
        .build()?;
    
    let mut processor = CsiProcessor::new(config)?;
    let extractor = wifi_densepose_signal::FeatureExtractor::new();

    for (idx, raw) in frames.iter().enumerate() {
        let preprocessed = processor.preprocess(raw)?;
        processor.add_to_history(preprocessed.clone());
        
        let features = extractor.extract(&preprocessed);
        
        // Simulate downstream detection confidence (e.g., from DensePose NN)
        let raw_conf = if features.amplitude.peak > 1.5 { 0.9 } else { 0.2 };
        let smoothed = processor.apply_temporal_smoothing(raw_conf);
        
        println!("Frame {}: smoothed confidence = {:.3}", idx, smoothed);
    }

    let stats = processor.get_statistics();
    println!(
        "Completed: {} frames processed, {} detections, error rate {:.2}%",
        stats.total_processed,
        stats.human_detections,
        stats.error_rate() * 100.0
    );

    Ok(())
}

```

## Key Dependencies and Build Configuration

The CSI processing pipeline relies on several foundational crates declared in [`rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/Cargo.toml`](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/Cargo.toml):

- **ndarray**: N-dimensional arrays for amplitude and phase matrices.
- **num-complex**: Complex number operations for IQ data handling.
- **chrono**: Timestamp metadata for temporal analysis.
- **serde**: Serialization support for feature vectors and configuration.
- **thiserror**: Structured error handling across the pipeline.
- **rustfft**: Fast Fourier Transform operations for spectral analysis.

*Source*: [[`Cargo.toml`](https://github.com/ruvnet/wifi-densepose/blob/main/Cargo.toml) – dependency declarations](https://github.com/ruvnet/wifi-densepose/blob/main/rust-port/wifi-densepose-rs/crates/wifi-densepose-signal/Cargo.toml)

## Summary

Processing raw CSI data frames in Rust using the **wifi-densepose** crate involves four essential stages:

- **Construction**: Use `CsiDataBuilder` to wrap raw amplitude and phase matrices with essential metadata (timestamp, frequency, bandwidth, SNR).
- **Configuration**: Tune `CsiProcessorConfig` to match your Wi-Fi hardware specifications, setting sampling rates, window sizes, noise thresholds, and history limits.
- **Preprocessing**: Execute `CsiProcessor::preprocess()` to apply noise removal, Hamming windowing, and amplitude normalization, producing clean signals for analysis.
- **Extraction**: Invoke `FeatureExtractor::extract()` to generate `AmplitudeFeatures`, `PhaseFeatures`, and motion descriptors that feed directly into DensePose estimation models.

The processor's built-in history management and temporal smoothing capabilities ensure stable detection across consecutive frames, while comprehensive statistics enable production monitoring.

## Frequently Asked Questions

### What is the minimum Rust version required to compile the wifi-densepose CSI processor?

The crate requires Rust 1.70 or later to support the `ndarray` 0.15+ features and `chrono` serialization used in `CsiData` metadata handling. Ensure your toolchain is updated via `rustup update` before building the `wifi-densepose-signal` crate.

### How do I handle different Wi-Fi hardware configurations with varying subcarrier counts?

Adjust the `CsiProcessorConfig` fields to match your hardware specifications. Set `sampling_rate` to your driver's capture frequency (typically 1–2 kHz for Intel 5300 CSI tools), and ensure your raw amplitude and phase matrices passed to `CsiDataBuilder` match the antenna and subcarrier dimensions (e.g., 3×30 for 3 antennas and 30 subcarriers). The pipeline handles arbitrary matrix shapes as long as amplitude and phase dimensions match.

### Can I disable specific preprocessing steps like noise removal or normalization?

Yes. While `CsiProcessor::preprocess()` runs the full pipeline by default, you can construct `CsiData` directly and bypass the processor for custom workflows, or modify the `CsiProcessorConfig` noise threshold to effectively disable noise removal by setting it to a very low value (e.g., -100.0 dB). For complete control over individual stages, use the underlying `CsiPreprocessor` methods directly as exposed in the internal API.

### How does temporal smoothing affect real-time performance?

The `apply_temporal_smoothing` method applies an exponential moving average using the configured `smoothing_factor`, introducing minimal computational overhead per frame. The `max_history_size` parameter controls the sliding window memory buffer; larger values improve temporal consistency but increase RAM usage linearly with the number of stored frames. For real-time DensePose estimation, balance these settings against your hardware memory constraints and latency requirements.