feat: Rust hardware adapters return errors instead of silent empty data, add changelog

- densepose.rs: forward() returns NnError when no weights loaded instead of zeros
- translator.rs: forward/encode/decode require loaded weights, error otherwise
- fusion.rs: remove rand_range() RNG, RSSI reads return empty with warning log
- hardware_adapter.rs: ESP32/Intel/Atheros/UDP/PCAP adapters return AdapterError
  explaining hardware not connected instead of silent empty readings
- csi_receiver.rs: PicoScenes parser returns error instead of empty amplitudes
- README.md: add v2.1.0 changelog with all recent changes

https://claude.ai/code/session_01Ki7pvEZtJDvqJkmyn6B714
This commit is contained in:
Claude
2026-02-28 06:31:11 +00:00
parent a8ac309258
commit 6e0e539443
6 changed files with 118 additions and 179 deletions

View File

@@ -233,15 +233,17 @@ impl DensePoseHead {
///
/// This performs inference using loaded weights. For ONNX-based inference,
/// use the ONNX backend directly.
///
/// # Errors
/// Returns an error if no model weights are loaded. Load weights with
/// `with_weights()` before calling forward(). Use `forward_mock()` in tests.
pub fn forward(&self, input: &Tensor) -> NnResult<DensePoseOutput> {
self.validate_input(input)?;
// If we have native weights, use them
if let Some(ref _weights) = self.weights {
self.forward_native(input)
} else {
// Return mock output for testing when no weights are loaded
self.forward_mock(input)
Err(NnError::inference("No model weights loaded. Load weights with with_weights() before calling forward(). Use MockBackend for testing."))
}
}
@@ -283,6 +285,7 @@ impl DensePoseHead {
}
/// Mock forward pass for testing
#[cfg(test)]
fn forward_mock(&self, input: &Tensor) -> NnResult<DensePoseOutput> {
let shape = input.shape();
let batch = shape.dim(0).unwrap_or(1);
@@ -551,13 +554,24 @@ mod tests {
assert!(!head.has_weights());
}
#[test]
fn test_forward_without_weights_errors() {
let config = DensePoseConfig::new(256, 24, 2);
let head = DensePoseHead::new(config).unwrap();
let input = Tensor::zeros_4d([1, 256, 64, 64]);
let result = head.forward(&input);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No model weights loaded"));
}
#[test]
fn test_mock_forward_pass() {
let config = DensePoseConfig::new(256, 24, 2);
let head = DensePoseHead::new(config).unwrap();
let input = Tensor::zeros_4d([1, 256, 64, 64]);
let output = head.forward(&input).unwrap();
let output = head.forward_mock(&input).unwrap();
// Check output shapes
assert_eq!(output.segmentation.shape().dim(1), Some(25)); // 24 + 1 background

View File

@@ -282,47 +282,49 @@ impl ModalityTranslator {
}
/// Forward pass through the translator
///
/// # Errors
/// Returns an error if no model weights are loaded. Load weights with
/// `with_weights()` before calling forward(). Use `forward_mock()` in tests.
pub fn forward(&self, input: &Tensor) -> NnResult<TranslatorOutput> {
self.validate_input(input)?;
if let Some(ref _weights) = self.weights {
self.forward_native(input)
} else {
self.forward_mock(input)
Err(NnError::inference("No model weights loaded. Load weights with with_weights() before calling forward(). Use MockBackend for testing."))
}
}
/// Encode input to latent space
///
/// # Errors
/// Returns an error if no model weights are loaded.
pub fn encode(&self, input: &Tensor) -> NnResult<Vec<Tensor>> {
self.validate_input(input)?;
let shape = input.shape();
let batch = shape.dim(0).unwrap_or(1);
let height = shape.dim(2).unwrap_or(64);
let width = shape.dim(3).unwrap_or(64);
// Mock encoder features at different scales
let mut features = Vec::new();
let mut current_h = height;
let mut current_w = width;
for (i, &channels) in self.config.hidden_channels.iter().enumerate() {
if i > 0 {
current_h /= 2;
current_w /= 2;
}
let feat = Tensor::zeros_4d([batch, channels, current_h.max(1), current_w.max(1)]);
features.push(feat);
if self.weights.is_none() {
return Err(NnError::inference("No model weights loaded. Cannot encode without weights."));
}
Ok(features)
// Real encoding through the encoder path of forward_native
let output = self.forward_native(input)?;
output.encoder_features.ok_or_else(|| {
NnError::inference("Encoder features not available from forward pass")
})
}
/// Decode from latent space
///
/// # Errors
/// Returns an error if no model weights are loaded or if encoded features are empty.
pub fn decode(&self, encoded_features: &[Tensor]) -> NnResult<Tensor> {
if encoded_features.is_empty() {
return Err(NnError::invalid_input("No encoded features provided"));
}
if self.weights.is_none() {
return Err(NnError::inference("No model weights loaded. Cannot decode without weights."));
}
let last_feat = encoded_features.last().unwrap();
let shape = last_feat.shape();
@@ -385,6 +387,7 @@ impl ModalityTranslator {
}
/// Mock forward pass for testing
#[cfg(test)]
fn forward_mock(&self, input: &Tensor) -> NnResult<TranslatorOutput> {
let shape = input.shape();
let batch = shape.dim(0).unwrap_or(1);
@@ -669,28 +672,48 @@ mod tests {
assert!(!translator.has_weights());
}
#[test]
fn test_forward_without_weights_errors() {
let config = TranslatorConfig::new(128, vec![256, 512, 256], 256);
let translator = ModalityTranslator::new(config).unwrap();
let input = Tensor::zeros_4d([1, 128, 64, 64]);
let result = translator.forward(&input);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No model weights loaded"));
}
#[test]
fn test_mock_forward() {
let config = TranslatorConfig::new(128, vec![256, 512, 256], 256);
let translator = ModalityTranslator::new(config).unwrap();
let input = Tensor::zeros_4d([1, 128, 64, 64]);
let output = translator.forward(&input).unwrap();
let output = translator.forward_mock(&input).unwrap();
assert_eq!(output.features.shape().dim(1), Some(256));
}
#[test]
fn test_encode_decode() {
fn test_encode_without_weights_errors() {
let config = TranslatorConfig::new(128, vec![256, 512], 256);
let translator = ModalityTranslator::new(config).unwrap();
let input = Tensor::zeros_4d([1, 128, 64, 64]);
let encoded = translator.encode(&input).unwrap();
assert_eq!(encoded.len(), 2);
let result = translator.encode(&input);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No model weights loaded"));
}
let decoded = translator.decode(&encoded).unwrap();
assert_eq!(decoded.shape().dim(1), Some(256));
#[test]
fn test_decode_without_weights_errors() {
let config = TranslatorConfig::new(128, vec![256, 512], 256);
let translator = ModalityTranslator::new(config).unwrap();
let features = vec![Tensor::zeros_4d([1, 512, 32, 32])];
let result = translator.decode(&features);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("No model weights loaded"));
}
#[test]