""" Confidence Model Probabilistic confidence estimation for geolocation """ from typing import Dict, List, Optional from dataclasses import dataclass import math @dataclass class ConfidenceReport: """Confidence report for geolocation estimate""" overall_confidence: float position_probability: Dict # Probability distribution over area uncertainty_radius: float # meters supporting_evidence: List[Dict] contradicting_evidence: List[Dict] unknown_factors: List[str] class ConfidenceModel: """ Probabilistic confidence model for geolocation Instead of just returning a coordinate, the model reports: - Probability distribution over position - Uncertainty radius - Supporting evidence - Contradicting evidence - Unknown factors """ def __init__(self): self.evidence_weights = { "gps": 0.9, "visual": 0.7, "semantic": 0.6, "geometric": 0.5, "temporal": 0.4, "map": 0.6, "similarity": 0.5 } def calculate_confidence(self, estimate, evidence_package) -> ConfidenceReport: """ Calculate comprehensive confidence report Args: estimate: GeolocationEstimate evidence_package: EvidencePackage Returns: ConfidenceReport with full probabilistic analysis """ # Analyze supporting evidence supporting = self._find_supporting_evidence(evidence_package, estimate) # Analyze contradicting evidence contradicting = self._find_contradicting_evidence(evidence_package, estimate) # Identify unknown factors unknown = self._identify_unknown_factors(evidence_package) # Calculate position probability probability = self._calculate_position_probability(estimate, evidence_package) # Calculate uncertainty radius uncertainty = self._calculate_uncertainty(estimate, evidence_package) # Calculate overall confidence overall = self._calculate_overall_confidence( estimate, supporting, contradicting, unknown ) return ConfidenceReport( overall_confidence=overall, position_probability=probability, uncertainty_radius=uncertainty, supporting_evidence=supporting, contradicting_evidence=contradicting, unknown_factors=unknown ) def _find_supporting_evidence(self, evidence, estimate) -> List[Dict]: """Find evidence that supports the estimate""" supporting = [] # GPS evidence if evidence.metadata.gps_lat and evidence.metadata.gps_lng: distance = self._haversine_distance( evidence.metadata.gps_lat, evidence.metadata.gps_lng, estimate.lat, estimate.lng ) if distance < 100: # Within 100m supporting.append({ "type": "gps", "description": f"GPS coordinates match within {distance:.1f}m", "confidence": 0.9, "distance": distance }) # Visual evidence for obj in evidence.visual_objects: if obj.confidence > 0.7: supporting.append({ "type": "visual", "description": f"Detected {obj.label} with {obj.confidence:.2f} confidence", "confidence": obj.confidence }) # Semantic evidence for text in evidence.text_detections: if text.confidence > 0.8: supporting.append({ "type": "semantic", "description": f"Detected text: '{text.text}'", "confidence": text.confidence }) return supporting def _find_contradicting_evidence(self, evidence, estimate) -> List[Dict]: """Find evidence that contradicts the estimate""" contradicting = [] # Check if GPS contradicts other methods if evidence.metadata.gps_lat and evidence.metadata.gps_lng: gps_distance = self._haversine_distance( evidence.metadata.gps_lat, evidence.metadata.gps_lng, estimate.lat, estimate.lng ) if gps_distance > 1000: # More than 1km difference contradicting.append({ "type": "gps_mismatch", "description": f"GPS and estimate differ by {gps_distance:.0f}m", "severity": "high" }) # Check for conflicting visual evidence # (e.g., detected objects not expected at location) return contradicting def _identify_unknown_factors(self, evidence) -> List[str]: """Identify factors that are unknown or uncertain""" unknown = [] if not evidence.metadata.gps_lat: unknown.append("GPS coordinates missing") if not evidence.metadata.compass_heading: unknown.append("Camera orientation unknown") if not evidence.metadata.timestamp: unknown.append("Timestamp missing") if len(evidence.visual_objects) == 0: unknown.append("No visual objects detected") if len(evidence.text_detections) == 0: unknown.append("No text detected") return unknown def _calculate_position_probability(self, estimate, evidence) -> Dict: """Calculate probability distribution over position""" # Simplified: Gaussian distribution around estimate # In production, use full Bayesian inference return { "type": "gaussian", "center": {"lat": estimate.lat, "lng": estimate.lng}, "std_dev": estimate.accuracy / 2, # 95% CI ≈ 2*std_dev "confidence_interval": { "95_percent": estimate.accuracy, "99_percent": estimate.accuracy * 1.5 } } def _calculate_uncertainty(self, estimate, evidence) -> float: """Calculate uncertainty radius in meters""" base_uncertainty = estimate.accuracy # Increase uncertainty if few evidence sources evidence_count = len(evidence.visual_objects) + len(evidence.text_detections) if evidence_count < 3: base_uncertainty *= 1.5 # Decrease uncertainty if GPS available if evidence.metadata.gps_lat: base_uncertainty *= 0.8 return base_uncertainty def _calculate_overall_confidence(self, estimate, supporting, contradicting, unknown) -> float: """Calculate overall confidence score""" # Base confidence from estimate base = estimate.confidence # Boost from supporting evidence support_boost = sum(s.get("confidence", 0.5) * 0.1 for s in supporting) # Penalty from contradicting evidence contradict_penalty = sum(0.2 for c in contradicting if c.get("severity") == "high") contradict_penalty += sum(0.1 for c in contradicting if c.get("severity") == "medium") # Penalty from unknown factors unknown_penalty = len(unknown) * 0.05 # Calculate final confidence = base + support_boost - contradict_penalty - unknown_penalty # Clamp to [0, 1] return max(0.0, min(1.0, confidence)) def _haversine_distance(self, lat1, lng1, lat2, lng2) -> float: """Calculate distance between two coordinates in meters""" R = 6371000 # Earth radius in meters phi1 = math.radians(lat1) phi2 = math.radians(lat2) delta_phi = math.radians(lat2 - lat1) delta_lambda = math.radians(lng2 - lng1) a = math.sin(delta_phi / 2) ** 2 + \ math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2 c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) return R * c # Example usage def example_confidence(): """Example: Confidence calculation""" from evidence_extractor import EvidencePackage, ImageMetadata, VisualObject, TextDetection from geolocation_engine import GeolocationEstimate from datetime import datetime model = ConfidenceModel() # Create sample evidence evidence = EvidencePackage( image_id="demo_001", timestamp=datetime.now(), metadata=ImageMetadata( gps_lat=13.7563, gps_lng=100.5018, altitude=10.0, compass_heading=90.0 ), visual_objects=[ VisualObject(label="street_light", confidence=0.85, bbox=[100, 200, 50, 150]), VisualObject(label="building", confidence=0.92, bbox=[0, 0, 640, 480]) ], semantic_objects=[], text_detections=[ TextDetection(text="Bangkok", confidence=0.95, bbox=[200, 100, 100, 50]) ], geometric_features=[], environmental_signals=[], temporal_signals={} ) # Create sample estimate estimate = GeolocationEstimate( lat=13.7563, lng=100.5018, accuracy=10.0, confidence=0.9, method="gps", evidence={} ) # Calculate confidence report = model.calculate_confidence(estimate, evidence) print(f"Confidence Report:") print(f" Overall confidence: {report.overall_confidence:.2f}") print(f" Uncertainty radius: {report.uncertainty_radius:.1f}m") print(f" Supporting evidence: {len(report.supporting_evidence)}") for evidence in report.supporting_evidence: print(f" - {evidence['type']}: {evidence['description']}") print(f" Contradicting evidence: {len(report.contradicting_evidence)}") print(f" Unknown factors: {len(report.unknown_factors)}") for factor in report.unknown_factors: print(f" - {factor}") return report if __name__ == '__main__': example_confidence()