bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
294 lines
10 KiB
Python
294 lines
10 KiB
Python
"""
|
|
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()
|