// sensor_fusion.cpp - Implementation of Unified Sensor Fusion Engine for AstraVoxel #include "sensor_fusion.hpp" #include #include #include #include #include #include namespace astraxel { //============================================================================== // Math Utilities Implementation //============================================================================== namespace astraxel_math { Matrix3d Matrix3d::rotation_x(double angle_rad) { Matrix3d mat; double c = std::cos(angle_rad); double s = std::sin(angle_rad); mat.m[0] = 1.0; mat.m[1] = 0.0; mat.m[2] = 0.0; mat.m[3] = 0.0; mat.m[4] = c; mat.m[5] = -s; mat.m[6] = 0.0; mat.m[7] = s; mat.m[8] = c; return mat; } Matrix3d Matrix3d::rotation_y(double angle_rad) { Matrix3d mat; double c = std::cos(angle_rad); double s = std::sin(angle_rad); mat.m[0] = c; mat.m[1] = 0.0; mat.m[2] = s; mat.m[3] = 0.0; mat.m[4] = 1.0; mat.m[5] = 0.0; mat.m[6] = -s; mat.m[7] = 0.0; mat.m[8] = c; return mat; } Matrix3d Matrix3d::rotation_z(double angle_rad) { Matrix3d mat; double c = std::cos(angle_rad); double s = std::sin(angle_rad); mat.m[0] = c; mat.m[1] = -s; mat.m[2] = 0.0; mat.m[3] = s; mat.m[4] = c; mat.m[5] = 0.0; mat.m[6] = 0.0; mat.m[7] = 0.0; mat.m[8] = 1.0; return mat; } Matrix3d Matrix3d::operator*(const Matrix3d& other) const { Matrix3d result; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { result.m[i * 3 + j] = 0.0; for (int k = 0; k < 3; ++k) { result.m[i * 3 + j] += m[i * 3 + k] * other.m[k * 3 + j]; } } } return result; } Quaternion Quaternion::from_euler(double yaw, double pitch, double roll) { // Convert Euler angles to quaternion double cy = std::cos(yaw * 0.5); double sy = std::sin(yaw * 0.5); double cp = std::cos(pitch * 0.5); double sp = std::sin(pitch * 0.5); double cr = std::cos(roll * 0.5); double sr = std::sin(roll * 0.5); Quaternion q; q.w = cr * cp * cy + sr * sp * sy; q.x = sr * cp * cy - cr * sp * sy; q.y = cr * sp * cy + sr * cp * sy; q.z = cr * cp * sy - sr * sp * cy; return q; } Matrix3d Quaternion::to_rotation_matrix() const { Matrix3d mat; double xx = x * x, xy = x * y, xz = x * z, xw = x * w; double yy = y * y, yz = y * z, yw = y * w; double zz = z * z, zw = z * w; mat.m[0] = 1.0 - 2.0 * (yy + zz); mat.m[1] = 2.0 * (xy - zw); mat.m[2] = 2.0 * (xz + yw); mat.m[3] = 2.0 * (xy + zw); mat.m[4] = 1.0 - 2.0 * (xx + zz); mat.m[5] = 2.0 * (yz - xw); mat.m[6] = 2.0 * (xz - yw); mat.m[7] = 2.0 * (yz + xw); mat.m[8] = 1.0 - 2.0 * (xx + yy); return mat; } Quaternion Quaternion::normalized() const { double norm = std::sqrt(w*w + x*x + y*y + z*z); if (norm < 1e-12) return {1.0, 0.0, 0.0, 0.0}; return {w/norm, x/norm, y/norm, z/norm}; } } // namespace astraxel_math //============================================================================== // SensorCalibration Implementation //============================================================================== nlohmann::json SensorCalibration::to_json() const { nlohmann::json j; j["sensor_id"] = sensor_id; j["sensor_type"] = sensor_type; j["coordinate_system"] = coordinate_system; j["position"] = {position.x, position.y, position.z}; j["orientation"] = {orientation.w, orientation.x, orientation.y, orientation.z}; j["intrinsic_matrix"] = {{ intrinsic_matrix.m[0], intrinsic_matrix.m[1], intrinsic_matrix.m[2], intrinsic_matrix.m[3], intrinsic_matrix.m[4], intrinsic_matrix.m[5], intrinsic_matrix.m[6], intrinsic_matrix.m[7], intrinsic_matrix.m[8] }}; j["field_of_view_rad"] = field_of_view_rad; j["min_range"] = min_range; j["max_range"] = max_range; j["calibration_uncertainty"] = calibration_uncertainty; j["angular_uncertainty"] = angular_uncertainty; j["is_calibrated"] = is_calibrated; return j; } SensorCalibration SensorCalibration::from_json(const nlohmann::json& j) { SensorCalibration cal; cal.sensor_id = j.value("sensor_id", 0); cal.sensor_type = j.value("sensor_type", "unknown"); cal.coordinate_system = j.value("coordinate_system", "cartesian"); auto pos = j.value("position", std::vector{0,0,0}); if (pos.size() >= 3) { cal.position = {pos[0], pos[1], pos[2]}; } auto ori = j.value("orientation", std::vector{1,0,0,0}); if (ori.size() >= 4) { cal.orientation = {ori[0], ori[1], ori[2], ori[3]}; } auto intrinsic = j.value("intrinsic_matrix", std::vector>{ {1,0,0}, {0,1,0}, {0,0,1} }); if (intrinsic.size() >= 3 && intrinsic[0].size() >= 3) { for (int i = 0; i < 3; ++i) { for (int k = 0; k < 3; ++k) { cal.intrinsic_matrix.m[i*3 + k] = intrinsic[i][k]; } } } cal.field_of_view_rad = j.value("field_of_view_rad", 1.0); cal.min_range = j.value("min_range", 0.0); cal.max_range = j.value("max_range", 1000.0); cal.calibration_uncertainty = j.value("calibration_uncertainty", 0.1); cal.angular_uncertainty = j.value("angular_uncertainty", 0.01); cal.is_calibrated = j.value("is_calibrated", false); return cal; } //============================================================================== // VoxelGrid Implementation //============================================================================== VoxelGrid::VoxelGrid(const astraxel_math::Vector3d& origin, const astraxel_math::Vector3d& resolution, const Vector3i& dimensions) : origin_(origin), resolution_(resolution), dimensions_(dimensions) { initialize_grid(); } void VoxelGrid::initialize_grid() { size_t total_size = static_cast(dimensions_.x) * static_cast(dimensions_.y) * static_cast(dimensions_.z); evidence_grid_.resize(total_size, 0.0f); intensity_grid_.resize(total_size, 0.0f); observation_counts_.resize(total_size, 0); } bool VoxelGrid::accumulate_point(const astraxel_math::Vector3d& point, float evidence, float intensity) { // Convert world point to voxel index size_t index = point_to_voxel_index(point); if (index >= evidence_grid_.size()) { return false; } // Accumulate evidence and intensity evidence_grid_[index] += evidence; intensity_grid_[index] += intensity; observation_counts_[index]++; return true; } bool VoxelGrid::get_voxel_data(int x, int y, int z, float& evidence, float& intensity, int& count) const { if (!is_valid_voxel(x, y, z)) { return false; } size_t index = static_cast(x) + static_cast(y) * dimensions_.x + static_cast(z) * dimensions_.x * dimensions_.y; evidence = evidence_grid_[index]; intensity = intensity_grid_[index]; count = observation_counts_[index]; return true; } bool VoxelGrid::reset() { std::fill(evidence_grid_.begin(), evidence_grid_.end(), 0.0f); std::fill(intensity_grid_.begin(), intensity_grid_.end(), 0.0f); std::fill(observation_counts_.begin(), observation_counts_.end(), 0); return true; } size_t VoxelGrid::point_to_voxel_index(const astraxel_math::Vector3d& point) const { // Convert world coordinates to voxel indices astraxel_math::Vector3d relative_point = point - origin_; int x_idx = static_cast(relative_point.x / resolution_.x); int y_idx = static_cast(relative_point.y / resolution_.y); int z_idx = static_cast(relative_point.z / resolution_.z); // Check bounds if (x_idx < 0 || x_idx >= dimensions_.x || y_idx < 0 || y_idx >= dimensions_.y || z_idx < 0 || z_idx >= dimensions_.z) { return evidence_grid_.size(); // Return invalid index } return static_cast(x_idx) + static_cast(y_idx) * dimensions_.x + static_cast(z_idx) * dimensions_.x * dimensions_.y; } bool VoxelGrid::is_valid_voxel(int x, int y, int z) const { return x >= 0 && x < dimensions_.x && y >= 0 && y < dimensions_.y && z >= 0 && z < dimensions_.z; } //============================================================================== // BayesianFusion Implementation //============================================================================== FusionResult BayesianFusion::fuse_measurements( const std::vector& measurements, const std::vector& calibrations) { FusionResult result; if (measurements.empty()) { return result; } result.timestamp_ns = measurements[0].timestamp_ns; result.sensor_ids.reserve(measurements.size()); for (const auto& measurement : measurements) { result.sensor_ids.push_back(measurement.sensor_id); } // Simple evidence accumulation for now // In a full implementation, this would use proper Bayesian inference std::vector fused_points; std::vector weights; for (size_t i = 0; i < measurements.size(); ++i) { const auto& measurement = measurements[i]; const auto& calibration = calibrations[i]; // For simplicity, assume each measurement has a weight based on confidence float weight = measurement.confidence_level; // Mock point cloud generation (would be sensor-specific in real implementation) astraxel_math::Vector3d mock_point = { static_cast(measurement.sensor_id * 10.0), static_cast(i * 5.0), static_cast(measurement.timestamp_ns % 1000) }; fused_points.push_back(mock_point); weights.push_back(weight); } // Compute weighted centroid astraxel_math::Vector3d centroid = {0, 0, 0}; float total_weight = 0.0f; for (size_t i = 0; i < fused_points.size(); ++i) { centroid = centroid + (fused_points[i] * weights[i]); total_weight += weights[i]; } if (total_weight > 0.0f) { centroid = centroid * (1.0f / total_weight); result.object_detected = true; result.centroid = centroid; result.object_position = centroid; result.point_cloud = fused_points; result.fusion_confidence = std::min(1.0f, total_weight / measurements.size()); } return result; } void BayesianFusion::update_parameters(double performance_score) { // Adaptive parameter adjustment based on performance if (performance_score > 0.8) { prior_weight_ = std::min(0.9, prior_weight_ + 0.01); } else if (performance_score < 0.6) { prior_weight_ = std::max(0.1, prior_weight_ - 0.01); } } nlohmann::json BayesianFusion::get_parameters() const { return { {"fusion_type", "bayesian"}, {"prior_weight", prior_weight_}, {"likelihood_threshold", likelihood_threshold_}, {"correlation_enabled", use_correlation_} }; } //============================================================================== // EvidenceAccumulationFusion Implementation //============================================================================== FusionResult EvidenceAccumulationFusion::fuse_measurements( const std::vector& measurements, const std::vector& calibrations) { FusionResult result; if (measurements.empty()) { return result; } result.timestamp_ns = measurements[0].timestamp_ns; result.sensor_ids.reserve(measurements.size()); for (const auto& measurement : measurements) { result.sensor_ids.push_back(measurement.sensor_id); } // Evidence accumulation with decay std::map evidence_map; for (size_t i = 0; i < measurements.size(); ++i) { const auto& measurement = measurements[i]; // Use sensor confidence for evidence weight float evidence_weight = measurement.confidence_level * evidence_threshold_; // Mock spatial points (would be extracted from sensor data) astraxel_math::Vector3d evidence_point = { static_cast(measurement.sensor_id * 5.0), static_cast(i * 2.0), static_cast(measurement.timestamp_ns % 500) }; // Apply decay to existing evidence for (auto& [point, evidence] : evidence_map) { evidence *= decay_factor_; } // Add new evidence evidence_map[evidence_point] += evidence_weight; } // Extract strongest evidence points std::vector strong_points; float max_evidence = 0.0f; for (const auto& [point, evidence] : evidence_map) { if (evidence > evidence_threshold_) { strong_points.push_back(point); result.evidences.push_back(evidence); max_evidence = std::max(max_evidence, evidence); } } result.point_cloud = strong_points; result.fusion_confidence = normalize_evidence_ ? max_evidence : max_evidence; if (!strong_points.empty()) { // Compute centroid of strong evidence points result.centroid = {0, 0, 0}; for (const auto& point : strong_points) { result.centroid = result.centroid + point; } result.centroid = result.centroid * (1.0 / strong_points.size()); result.object_detected = true; result.object_position = result.centroid; } return result; } void EvidenceAccumulationFusion::update_parameters(double performance_score) { // Adjust accumulation parameters based on performance if (performance_score > 0.7) { evidence_threshold_ = std::max(0.05f, evidence_threshold_ - 0.01f); } else { evidence_threshold_ = std::min(0.5f, evidence_threshold_ + 0.01f); } } nlohmann::json EvidenceAccumulationFusion::get_parameters() const { return { {"fusion_type", "evidence_accumulation"}, {"evidence_threshold", evidence_threshold_}, {"decay_factor", decay_factor_}, {"normalize_evidence", normalize_evidence_} }; } //============================================================================== // QualityAssurance Implementation //============================================================================== QualityAssurance::QualityMetrics QualityAssurance::assess_fusion_quality( const FusionResult& result, const std::vector& measurements, const std::vector& calibrations) { QualityMetrics metrics; // Data completeness based on coverage metrics.data_completeness = std::min(1.0, static_cast(measurements.size()) / 4.0); // Assume 4 sensors ideal // Temporal consistency (variation in timestamps) if (measurements.size() > 1) { std::vector timestamps; for (const auto& m : measurements) timestamps.push_back(m.timestamp_ns); uint64_t mean_ts = std::accumulate(timestamps.begin(), timestamps.end(), uint64_t(0)) / timestamps.size(); double variance = 0.0; for (uint64_t ts : timestamps) { double diff = static_cast(ts) - mean_ts; variance += diff * diff; } variance /= timestamps.size(); metrics.temporal_consistency = std::exp(-variance / 1e12); // Time consistency metric } else { metrics.temporal_consistency = 0.5; } // Spatial consistency (agreement between sensors) if (measurements.size() >= 2) { double avg_confidence = 0.0; for (const auto& m : measurements) avg_confidence += m.confidence_level; avg_confidence /= measurements.size(); metrics.spatial_consistency = avg_confidence; } else { metrics.spatial_consistency = 0.5; } // Sensor diversity std::set sensor_types; for (const auto& cal : calibrations) { if (cal.is_calibrated) sensor_types.insert(cal.sensor_id); } metrics.sensor_diversity = std::min(1.0, static_cast(sensor_types.size()) / 3.0); // Overall confidence score metrics.confidence_score = (metrics.data_completeness * 0.3 + metrics.temporal_consistency * 0.2 + metrics.spatial_consistency * 0.3 + metrics.sensor_diversity * 0.2); return metrics; } std::vector QualityAssurance::generate_quality_report( const QualityMetrics& metrics, const std::vector& measurements) { std::vector report; char buffer[256]; sprintf(buffer, "Fusion Quality Report:"); report.push_back(buffer); sprintf(buffer, " Overall Confidence: %.2f", metrics.confidence_score); report.push_back(buffer); sprintf(buffer, " Data Completeness: %.2f", metrics.data_completeness); report.push_back(buffer); sprintf(buffer, " Temporal Consistency: %.2f", metrics.temporal_consistency); report.push_back(buffer); sprintf(buffer, " Spatial Consistency: %.2f", metrics.spatial_consistency); report.push_back(buffer); sprintf(buffer, " Sensor Diversity: %.2f", metrics.sensor_diversity); report.push_back(buffer); sprintf(buffer, " Measurements Processed: %zu", measurements.size()); report.push_back(buffer); // Generate recommendations if (metrics.confidence_score < 0.6) { report.push_back(" WARNING: Low confidence - consider sensor calibration"); } if (metrics.data_completeness < 0.8) { report.push_back(" RECOMMENDATION: Add more sensor coverage"); } if (metrics.temporal_consistency < 0.7) { report.push_back(" ISSUE: High timestamp variation detected"); } return report; } //============================================================================== // AstraVoxelFusionEngine Implementation //============================================================================== AstraVoxelFusionEngine::AstraVoxelFusionEngine() : fusion_strategy_(std::make_unique()), voxel_grid_(nullptr), initialized_(false) { } AstraVoxelFusionEngine::~AstraVoxelFusionEngine() { stop_fusion(); } bool AstraVoxelFusionEngine::initialize(const nlohmann::json& config) { if (config.contains("fusion_rate_hz")) { fusion_rate_hz_ = config.value("fusion_rate_hz", 10.0); } if (config.contains("real_time_mode")) { real_time_mode_ = config.value("real_time_mode", true); } // Create voxel grid with default parameters astraxel_math::Vector3d grid_origin(-100.0, -100.0, -100.0); astraxel_math::Vector3d grid_resolution(1.0, 1.0, 1.0); Vector3i grid_dimensions{200, 200, 200}; if (config.contains("voxel_grid")) { auto& grid_config = config["voxel_grid"]; if (grid_config.contains("origin")) { auto origin = grid_config["origin"]; grid_origin = {origin[0], origin[1], origin[2]}; } if (grid_config.contains("resolution")) { auto res = grid_config["resolution"]; grid_resolution = {res[0], res[1], res[2]}; } if (grid_config.contains("dimensions")) { auto dims = grid_config["dimensions"]; grid_dimensions = {dims[0], dims[1], dims[2]}; } } voxel_grid_ = std::make_unique(grid_origin, grid_resolution, grid_dimensions); initialized_ = true; return true; } int AstraVoxelFusionEngine::add_sensor(std::unique_ptr sensor) { if (!sensor) return -1; int sensor_id = static_cast(sensors_.size()); sensor->get_calibration().sensor_id = sensor_id; sensors_.push_back(std::move(sensor)); return sensor_id; } bool AstraVoxelFusionEngine::remove_sensor(int sensor_id) { if (sensor_id < 0 || sensor_id >= static_cast(sensors_.size())) { return false; } sensors_.erase(sensors_.begin() + sensor_id); return true; } bool AstraVoxelFusionEngine::start_fusion() { if (!initialized_ || sensors_.empty()) { return false; } // Implementation would start background fusion thread // For now, just mark as initialized return true; } void AstraVoxelFusionEngine::stop_fusion() { // Implementation would stop background fusion thread // For now, just cleanup if (voxel_grid_) { voxel_grid_->reset(); } } FusionResult AstraVoxelFusionEngine::fuse_data() { if (!initialized_ || sensors_.empty()) { return create_empty_result(); } std::vector measurements; std::vector calibrations; // Collect measurements from all sensors for (auto& sensor : sensors_) { if (sensor->is_connected()) { auto measurement = sensor->capture_measurement(); if (measurement.valid) { measurements.push_back(measurement); calibrations.push_back(sensor->get_calibration()); } } } if (measurements.empty()) { return create_empty_result(); } // Perform fusion FusionResult result = fusion_strategy_->fuse_measurements(measurements, calibrations); // Update performance tracking successful_fusions_++; result.timestamp_ns = std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()).count(); // Store recent results recent_results_.push_back(result); if (recent_results_.size() > 100) { // Keep last 100 results recent_results_.erase(recent_results_.begin()); } return result; } nlohmann::json AstraVoxelFusionEngine::get_system_status() const { return { {"initialized", initialized_}, {"sensor_count", sensors_.size()}, {"fusion_rate_hz", fusion_rate_hz_}, {"real_time_mode", real_time_mode_}, {"successful_fusions", successful_fusions_}, {"failed_fusions", failed_fusions_}, {"voxel_grid_size", voxel_grid_ ? voxel_grid_->total_voxels() : 0} }; } bool AstraVoxelFusionEngine::export_results(const std::string& format) { // Mock implementation - would export actual results return true; } void AstraVoxelFusionEngine::configure_fusion_parameters(const nlohmann::json& params) { if (params.contains("strategy")) { std::string strategy = params["strategy"]; if (strategy == "evidence_accumulation") { fusion_strategy_ = std::make_unique(); } else { fusion_strategy_ = std::make_unique(); } } fusion_strategy_->update_parameters(params.value("performance_feedback", 0.8)); } bool AstraVoxelFusionEngine::validate_sensor_configurations() const { for (const auto& sensor : sensors_) { const auto& cal = sensor->get_calibration(); if (!cal.is_calibrated) { return false; } // Add more validation as needed } return true; } void AstraVoxelFusionEngine::update_performance_metrics(double processing_time_ms) { average_processing_time_ms_ = 0.9 * average_processing_time_ms_ + 0.1 * processing_time_ms; } FusionResult AstraVoxelFusionEngine::create_empty_result() const { FusionResult result; result.timestamp_ns = std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch()).count(); result.object_detected = false; result.fusion_confidence = 0.0; return result; } void AstraVoxelFusionEngine::cleanup_old_results() { auto now = std::chrono::high_resolution_clock::now(); auto cutoff = now - std::chrono::seconds(300); // Keep last 5 minutes // Remove old results recent_results_.erase( std::remove_if(recent_results_.begin(), recent_results_.end(), [cutoff](const FusionResult& result) { return result.timestamp_ns < cutoff.time_since_epoch().count(); }), recent_results_.end()); } } // namespace astraxel