CSI Phase Unwrapping Algorithm in WiFi-DensePose: Implementation Guide
The CSI phase unwrapping algorithm in WiFi-DensePose restores continuous phase values from wrapped [-π, π] measurements using either vectorized NumPy operations or a custom block-wise approach that respects hardware sampling boundaries.
The ruvnet/wifi-densepose repository implements a robust CSI phase unwrapping algorithm to convert raw Channel State Information into usable signals for dense human pose estimation. Before outlier removal or smoothing can occur, the system must resolve phase discontinuities caused by the inherent 2π periodicity of wireless measurements. This article examines the dual implementation strategy found in the codebase, covering both the high-performance vectorized approach and the domain-specific custom unwrapping logic.
Core Problem – Phase Wrap-Around in CSI Measurements
CSI phase measurements are inherently wrapped to the [-π, π] interval. When the true physical phase changes slowly across frequency or time, the measured value may abruptly jump from +π to -π (or vice-versa), creating artificial discontinuities. The CSI phase unwrapping algorithm detects these jumps—where the absolute difference between consecutive samples exceeds π—and restores the original monotonic progression by adding or subtracting multiples of 2π.
Vectorised CSI Phase Unwrapping with NumPy and SciPy
The primary implementation resides in v1/src/core/phase_sanitizer.py and offers a configurable interface that delegates to either NumPy or SciPy backends.
The unwrap_phase Entry Point
The PhaseSanitizer.unwrap_phase() method (lines 77-98) serves as the central dispatcher. It selects an underlying implementation based on the configuration flag unwrapping_method, which accepts "numpy", "scipy", or "custom".
NumPy Implementation Details
The _unwrap_numpy() method (lines 102-107) leverages np.unwrap for high-performance vectorized processing:
def _unwrap_numpy(self, phase_data: np.ndarray) -> np.ndarray:
"""Unwrap phase using numpy's unwrap function."""
if phase_data.size == 0:
raise ValueError("Cannot unwrap empty phase data")
return np.unwrap(phase_data, axis=1)
The axis=1 argument treats the frequency axis (sub-carrier index) as the dimension to unwrap, leaving the sample-time axis untouched. The algorithm computes differences between successive samples and applies 2π corrections whenever the absolute difference exceeds the default discontinuity threshold of π.
SciPy Wrapper
The _unwrap_scipy() method (lines 108-113) currently functions as a thin wrapper around the NumPy implementation. This design preserves API consistency and allows for future migration to SciPy's signal.unwrap without modifying external calling code.
Custom Block-Wise CSI Phase Unwrapping Algorithm
For scenarios where hardware sampling boundaries must be respected, the repository provides a domain-specific implementation in references/script_5.py (lines 20-41).
Handling Hardware Sampling Boundaries
CSI acquisition in WiFi-DensePose organizes data into blocks of 30 consecutive sub-carriers, with five such blocks comprising a full capture of 150 frequency samples. The hardware may introduce independent phase offsets between these blocks, making a pure vectorized unwrap across the entire frequency axis potentially destructive.
Group-Wise Implementation
The custom algorithm iterates over each 30-subcarrier block and each transmit-receive antenna pair, explicitly correcting jumps greater than π or less than -π:
for sample_group in range(5): # five blocks of 30 sub-carriers
start_idx = sample_group * 30
end_idx = start_idx + 30
for tx in range(3):
for rx in range(3):
for i in range(start_idx + 1, end_idx):
diff = unwrapped[i, tx, rx] - unwrapped[i-1, tx, rx]
if diff > np.pi:
unwrapped[i, tx, rx] = unwrapped[i-1, tx, rx] + diff - 2*np.pi
elif diff < -np.pi:
unwrapped[i, tx, rx] = unwrapped[i-1, tx, rx] + diff + 2*np.pi
This approach preserves physical antenna path independence while preventing error propagation across hardware-defined block boundaries.
Integration into the Sanitization Pipeline
The PhaseSanitizer.sanitize_phase() method (lines 66-90) orchestrates the complete preprocessing workflow. Unwrapping occurs as the first deterministic transformation, ensuring that subsequent modules—outlier removal, smoothing, and noise filtering—operate on physically plausible continuous phase surfaces rather than wrapped discontinuities.
Configuration and Method Selection
The algorithm behavior is controlled through a configuration dictionary passed to PhaseSanitizer.__init__:
{
"unwrapping_method": "numpy",
"outlier_threshold": 3.0,
"smoothing_window": 5,
"enable_noise_filtering": false
}
Valid options for "unwrapping_method" include "numpy", "scipy", and "custom". Changing this parameter swaps the underlying implementation without requiring modifications to downstream code, facilitating rapid experimentation between vectorized speed and block-wise accuracy.
Practical Code Examples
Using PhaseSanitizer with NumPy
import numpy as np
from v1.src.core.phase_sanitizer import PhaseSanitizer
# Configuration for vectorized NumPy unwrapping
cfg = {
"unwrapping_method": "numpy",
"outlier_threshold": 3.0,
"smoothing_window": 5,
"enable_outlier_removal": True,
"enable_smoothing": True,
"enable_noise_filtering": False,
}
sanitizer = PhaseSanitizer(cfg)
# Simulated wrapped CSI phase (samples × 150 frequencies × 3 tx × 3 rx)
wrapped_phase = np.random.uniform(-np.pi, np.pi, (2, 150, 3, 3))
# Execute full sanitization pipeline
clean_phase = sanitizer.sanitize_phase(wrapped_phase)
Direct Block-Wise Unwrapping
from references.script_5 import CSIPhaseProcessor
import numpy as np
processor = CSIPhaseProcessor(num_subcarriers=30)
# Shape: (150 frequency samples, 3 transmitters, 3 receivers)
wrapped = np.random.uniform(-np.pi, np.pi, (150, 3, 3))
# Apply custom unwrap respecting 5 blocks of 30 sub-carriers
unwrapped = processor.unwrap_phase(wrapped)
Switching to Custom Implementation
# Reconfigure sanitizer to use block-wise logic
cfg["unwrapping_method"] = "custom"
sanitizer = PhaseSanitizer(cfg)
# Now delegates to bespoke group-wise algorithm
clean_phase = sanitizer.unwrap_phase(wrapped_phase)
Key Source Files
v1/src/core/phase_sanitizer.py– Central sanitization class implementingPhaseSanitizer.unwrap_phase(),_unwrap_numpy(),_unwrap_scipy(), andsanitize_phase(). Contains the vectorized unwrapping logic at lines 77-98 and 102-113.references/script_5.py– Stand-alone demonstration script containing the custom block-wise CSI phase unwrapping algorithm (lines 20-41) that handles grouped sub-carrier blocks.v1/tests/unit/test_phase_sanitizer.py– Unit tests verifying correct unwrapping behavior for both NumPy and custom methods.v1/src/services/pose_service.py– Service layer consuming sanitized phase data for downstream dense pose prediction.
Summary
- The CSI phase unwrapping algorithm resolves [-π, π] discontinuities by detecting jumps exceeding π and correcting them with 2π multiples.
- Two implementations coexist: a vectorized NumPy approach (
np.unwrapon axis=1) for speed, and a custom block-wise method respecting 30-subcarrier hardware blocks. - The
PhaseSanitizerclass inv1/src/core/phase_sanitizer.pyorchestrates unwrapping as the first step in a pipeline that includes outlier removal and smoothing. - Configuration via
unwrapping_methodallows switching between"numpy","scipy", and"custom"without code changes.
Frequently Asked Questions
What causes phase wrapping in CSI measurements?
Phase wrapping occurs because Channel State Information is measured modulo 2π, constraining values to the [-π, π] interval. When the true physical phase changes gradually across frequency or time, the measured signal appears to jump discontinuously from +π to -π (or vice versa), creating artificial discontinuities that must be corrected before further signal processing.
How does the NumPy unwrapping method handle multi-antenna CSI data?
The _unwrap_numpy() method in v1/src/core/phase_sanitizer.py applies np.unwrap(phase_data, axis=1), treating axis 1 as the frequency dimension (sub-carrier index) while preserving the sample-time and antenna dimensions. This vectorized approach simultaneously processes all transmit-receive pairs across the time axis, making it efficient for batched CSI tensors with shape (samples, 150, 3, 3).
When should I use the custom block-wise unwrapping instead of the NumPy method?
Use the custom block-wise implementation—found in references/script_5.py—when your CSI acquisition hardware introduces independent phase offsets between sampling blocks. The WiFi-DensePose system acquires data in five blocks of 30 sub-carriers each; the custom algorithm unwraps within each block separately to prevent error propagation across block boundaries, whereas the standard NumPy method would treat the entire 150-subcarrier range as continuous.
Where is the unwrapping algorithm configured in the WiFi-DensePose pipeline?
Configuration occurs in the PhaseSanitizer class constructor via a dictionary parameter. Set "unwrapping_method" to "numpy", "scipy", or "custom" to select the implementation. This configuration is typically defined in the service initialization or experiment script and passed to sanitize_phase(), which orchestrates the full preprocessing pipeline including unwrapping, outlier removal, and smoothing.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →