mirror of
https://github.com/ruvnet/RuView
synced 2026-07-31 18:51:42 +00:00
Implement CSI processing and phase sanitization modules; add unit tests for DensePose and modality translation networks
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
"""CSI (Channel State Information) processor for WiFi-DensePose system."""
|
||||
|
||||
import numpy as np
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class CSIProcessor:
|
||||
"""Processes raw CSI data for neural network input."""
|
||||
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||
"""Initialize CSI processor with configuration.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary with processing parameters
|
||||
"""
|
||||
self.config = config or {}
|
||||
self.sample_rate = self.config.get('sample_rate', 1000)
|
||||
self.num_subcarriers = self.config.get('num_subcarriers', 56)
|
||||
self.num_antennas = self.config.get('num_antennas', 3)
|
||||
|
||||
def process_raw_csi(self, raw_data: np.ndarray) -> np.ndarray:
|
||||
"""Process raw CSI data into normalized format.
|
||||
|
||||
Args:
|
||||
raw_data: Raw CSI data array
|
||||
|
||||
Returns:
|
||||
Processed CSI data ready for neural network input
|
||||
"""
|
||||
if raw_data.size == 0:
|
||||
raise ValueError("Raw CSI data cannot be empty")
|
||||
|
||||
# Basic processing: normalize and reshape
|
||||
processed = raw_data.astype(np.float32)
|
||||
|
||||
# Handle NaN values by replacing with mean of non-NaN values
|
||||
if np.isnan(processed).any():
|
||||
nan_mask = np.isnan(processed)
|
||||
non_nan_mean = np.nanmean(processed)
|
||||
processed[nan_mask] = non_nan_mean
|
||||
|
||||
# Simple normalization
|
||||
if processed.std() > 0:
|
||||
processed = (processed - processed.mean()) / processed.std()
|
||||
|
||||
return processed
|
||||
@@ -0,0 +1,108 @@
|
||||
"""Phase sanitizer for WiFi-DensePose CSI phase data processing."""
|
||||
|
||||
import numpy as np
|
||||
from typing import Optional
|
||||
from scipy import signal
|
||||
|
||||
|
||||
class PhaseSanitizer:
|
||||
"""Sanitizes phase data by unwrapping, removing outliers, and smoothing."""
|
||||
|
||||
def __init__(self, outlier_threshold: float = 3.0, smoothing_window: int = 5):
|
||||
"""Initialize phase sanitizer with configuration.
|
||||
|
||||
Args:
|
||||
outlier_threshold: Standard deviations for outlier detection
|
||||
smoothing_window: Window size for smoothing filter
|
||||
"""
|
||||
self.outlier_threshold = outlier_threshold
|
||||
self.smoothing_window = smoothing_window
|
||||
|
||||
def unwrap_phase(self, phase_data: np.ndarray) -> np.ndarray:
|
||||
"""Unwrap phase data to remove 2π discontinuities.
|
||||
|
||||
Args:
|
||||
phase_data: Raw phase data array
|
||||
|
||||
Returns:
|
||||
Unwrapped phase data
|
||||
"""
|
||||
if phase_data.size == 0:
|
||||
raise ValueError("Phase data cannot be empty")
|
||||
|
||||
# Apply unwrapping along the last axis (temporal dimension)
|
||||
unwrapped = np.unwrap(phase_data, axis=-1)
|
||||
return unwrapped.astype(np.float32)
|
||||
|
||||
def remove_outliers(self, phase_data: np.ndarray) -> np.ndarray:
|
||||
"""Remove outliers from phase data using statistical thresholding.
|
||||
|
||||
Args:
|
||||
phase_data: Phase data array
|
||||
|
||||
Returns:
|
||||
Phase data with outliers replaced
|
||||
"""
|
||||
if phase_data.size == 0:
|
||||
raise ValueError("Phase data cannot be empty")
|
||||
|
||||
result = phase_data.copy().astype(np.float32)
|
||||
|
||||
# Calculate statistics for outlier detection
|
||||
mean_val = np.mean(result)
|
||||
std_val = np.std(result)
|
||||
|
||||
# Identify outliers
|
||||
outlier_mask = np.abs(result - mean_val) > (self.outlier_threshold * std_val)
|
||||
|
||||
# Replace outliers with mean value
|
||||
result[outlier_mask] = mean_val
|
||||
|
||||
return result
|
||||
|
||||
def smooth_phase(self, phase_data: np.ndarray) -> np.ndarray:
|
||||
"""Apply smoothing filter to reduce noise in phase data.
|
||||
|
||||
Args:
|
||||
phase_data: Phase data array
|
||||
|
||||
Returns:
|
||||
Smoothed phase data
|
||||
"""
|
||||
if phase_data.size == 0:
|
||||
raise ValueError("Phase data cannot be empty")
|
||||
|
||||
result = phase_data.copy().astype(np.float32)
|
||||
|
||||
# Apply simple moving average filter along temporal dimension
|
||||
if result.ndim > 1:
|
||||
for i in range(result.shape[0]):
|
||||
if result.shape[-1] >= self.smoothing_window:
|
||||
# Apply 1D smoothing along the last axis
|
||||
kernel = np.ones(self.smoothing_window) / self.smoothing_window
|
||||
result[i] = np.convolve(result[i], kernel, mode='same')
|
||||
else:
|
||||
if result.shape[0] >= self.smoothing_window:
|
||||
kernel = np.ones(self.smoothing_window) / self.smoothing_window
|
||||
result = np.convolve(result, kernel, mode='same')
|
||||
|
||||
return result
|
||||
|
||||
def sanitize(self, phase_data: np.ndarray) -> np.ndarray:
|
||||
"""Apply full sanitization pipeline to phase data.
|
||||
|
||||
Args:
|
||||
phase_data: Raw phase data array
|
||||
|
||||
Returns:
|
||||
Fully sanitized phase data
|
||||
"""
|
||||
if phase_data.size == 0:
|
||||
raise ValueError("Phase data cannot be empty")
|
||||
|
||||
# Apply sanitization pipeline
|
||||
result = self.unwrap_phase(phase_data)
|
||||
result = self.remove_outliers(result)
|
||||
result = self.smooth_phase(result)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,114 @@
|
||||
"""Modality translation network for WiFi-DensePose system."""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class ModalityTranslationNetwork(nn.Module):
|
||||
"""Neural network for translating CSI data to visual feature space."""
|
||||
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
"""Initialize modality translation network.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary with network parameters
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.input_channels = config['input_channels']
|
||||
self.hidden_dim = config['hidden_dim']
|
||||
self.output_dim = config['output_dim']
|
||||
self.num_layers = config['num_layers']
|
||||
self.dropout_rate = config['dropout_rate']
|
||||
|
||||
# Encoder: CSI -> Feature space
|
||||
self.encoder = self._build_encoder()
|
||||
|
||||
# Decoder: Feature space -> Visual-like features
|
||||
self.decoder = self._build_decoder()
|
||||
|
||||
# Initialize weights
|
||||
self._initialize_weights()
|
||||
|
||||
def _build_encoder(self) -> nn.Module:
|
||||
"""Build encoder network."""
|
||||
layers = []
|
||||
|
||||
# Initial convolution
|
||||
layers.append(nn.Conv2d(self.input_channels, 64, kernel_size=3, padding=1))
|
||||
layers.append(nn.BatchNorm2d(64))
|
||||
layers.append(nn.ReLU(inplace=True))
|
||||
layers.append(nn.Dropout2d(self.dropout_rate))
|
||||
|
||||
# Progressive downsampling
|
||||
in_channels = 64
|
||||
for i in range(self.num_layers - 1):
|
||||
out_channels = min(in_channels * 2, self.hidden_dim)
|
||||
layers.extend([
|
||||
nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=2, padding=1),
|
||||
nn.BatchNorm2d(out_channels),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Dropout2d(self.dropout_rate)
|
||||
])
|
||||
in_channels = out_channels
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _build_decoder(self) -> nn.Module:
|
||||
"""Build decoder network."""
|
||||
layers = []
|
||||
|
||||
# Get the actual output channels from encoder (should be hidden_dim)
|
||||
encoder_out_channels = self.hidden_dim
|
||||
|
||||
# Progressive upsampling
|
||||
in_channels = encoder_out_channels
|
||||
for i in range(self.num_layers - 1):
|
||||
out_channels = max(in_channels // 2, 64)
|
||||
layers.extend([
|
||||
nn.ConvTranspose2d(in_channels, out_channels, kernel_size=3, stride=2, padding=1, output_padding=1),
|
||||
nn.BatchNorm2d(out_channels),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Dropout2d(self.dropout_rate)
|
||||
])
|
||||
in_channels = out_channels
|
||||
|
||||
# Final output layer
|
||||
layers.append(nn.Conv2d(in_channels, self.output_dim, kernel_size=3, padding=1))
|
||||
layers.append(nn.Tanh()) # Normalize output
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def _initialize_weights(self):
|
||||
"""Initialize network weights."""
|
||||
for m in self.modules():
|
||||
if isinstance(m, (nn.Conv2d, nn.ConvTranspose2d)):
|
||||
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
||||
if m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
"""Forward pass through the network.
|
||||
|
||||
Args:
|
||||
x: Input CSI tensor of shape (batch_size, channels, height, width)
|
||||
|
||||
Returns:
|
||||
Translated features tensor
|
||||
"""
|
||||
# Validate input shape
|
||||
if x.shape[1] != self.input_channels:
|
||||
raise RuntimeError(f"Expected {self.input_channels} input channels, got {x.shape[1]}")
|
||||
|
||||
# Encode CSI data
|
||||
encoded = self.encoder(x)
|
||||
|
||||
# Decode to visual-like features
|
||||
decoded = self.decoder(encoded)
|
||||
|
||||
return decoded
|
||||
Reference in New Issue
Block a user