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

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 and 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.

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.

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 – 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.
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 – 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 – 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.

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.

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 – 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.

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 – 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.

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:

  • 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 – 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.

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 →