import 'dart:math' as math; import '../data/models/target_type.dart'; import 'image_processing_service.dart'; export 'image_processing_service.dart' show ImpactDetectionSettings, ReferenceImpact, ImpactCharacteristics; class TargetDetectionResult { final double centerX; // Relative (0-1) final double centerY; // Relative (0-1) final double radius; // Relative (0-1) final List impacts; final bool success; final String? errorMessage; TargetDetectionResult({ required this.centerX, required this.centerY, required this.radius, required this.impacts, this.success = true, this.errorMessage, }); factory TargetDetectionResult.error(String message) { return TargetDetectionResult( centerX: 0.5, centerY: 0.5, radius: 0.4, impacts: [], success: false, errorMessage: message, ); } } class DetectedImpactResult { final double x; // Relative (0-1) final double y; // Relative (0-1) final double radius; // Absolute pixels final int suggestedScore; DetectedImpactResult({ required this.x, required this.y, required this.radius, required this.suggestedScore, }); } class TargetDetectionService { final ImageProcessingService _imageProcessingService; TargetDetectionService({ ImageProcessingService? imageProcessingService, }) : _imageProcessingService = imageProcessingService ?? ImageProcessingService(); /// Detect target and impacts from an image file TargetDetectionResult detectTarget( String imagePath, TargetType targetType, ) { try { // Detect main target final mainTarget = _imageProcessingService.detectMainTarget(imagePath); double centerX = 0.5; double centerY = 0.5; double radius = 0.4; if (mainTarget != null) { centerX = mainTarget.centerX; centerY = mainTarget.centerY; radius = mainTarget.radius; } // Detect impacts final impacts = _imageProcessingService.detectImpacts(imagePath); // Convert impacts to relative coordinates and calculate scores final detectedImpacts = impacts.map((impact) { final score = targetType == TargetType.concentric ? _calculateConcentricScore(impact.x, impact.y, centerX, centerY, radius) : _calculateSilhouetteScore(impact.x, impact.y, centerX, centerY); return DetectedImpactResult( x: impact.x, y: impact.y, radius: impact.radius, suggestedScore: score, ); }).toList(); return TargetDetectionResult( centerX: centerX, centerY: centerY, radius: radius, impacts: detectedImpacts, ); } catch (e) { return TargetDetectionResult.error('Erreur de detection: $e'); } } int _calculateConcentricScore( double impactX, double impactY, double centerX, double centerY, double targetRadius, ) { // Calculate distance from center (normalized to target radius) final dx = impactX - centerX; final dy = impactY - centerY; final distance = math.sqrt(dx * dx + dy * dy) / targetRadius; // Score zones (10 zones) if (distance <= 0.1) return 10; if (distance <= 0.2) return 9; if (distance <= 0.3) return 8; if (distance <= 0.4) return 7; if (distance <= 0.5) return 6; if (distance <= 0.6) return 5; if (distance <= 0.7) return 4; if (distance <= 0.8) return 3; if (distance <= 0.9) return 2; if (distance <= 1.0) return 1; return 0; // Outside target } int _calculateSilhouetteScore( double impactX, double impactY, double centerX, double centerY, ) { // For silhouettes, scoring is typically based on zones // Head and center mass = 5, body = 4, lower = 3 final dx = (impactX - centerX).abs(); final dy = impactY - centerY; // Check if within silhouette bounds (approximate) if (dx > 0.15) return 0; // Too far left/right // Vertical zones if (dy < -0.25) return 5; // Head zone (top) if (dy < 0.0) return 5; // Center mass (upper body) if (dy < 0.15) return 4; // Body if (dy < 0.35) return 3; // Lower body return 0; // Outside target } /// Detect only impacts with custom settings (doesn't affect target position) List detectImpactsOnly( String imagePath, TargetType targetType, double centerX, double centerY, double radius, int ringCount, ImpactDetectionSettings settings, ) { try { // Detect impacts with custom settings final impacts = _imageProcessingService.detectImpactsWithSettings( imagePath, settings, ); // Convert impacts to relative coordinates and calculate scores return impacts.map((impact) { final score = targetType == TargetType.concentric ? _calculateConcentricScoreWithRings( impact.x, impact.y, centerX, centerY, radius, ringCount) : _calculateSilhouetteScore(impact.x, impact.y, centerX, centerY); return DetectedImpactResult( x: impact.x, y: impact.y, radius: impact.radius, suggestedScore: score, ); }).toList(); } catch (e) { return []; } } int _calculateConcentricScoreWithRings( double impactX, double impactY, double centerX, double centerY, double targetRadius, int ringCount, ) { // Calculate distance from center (normalized to target radius) final dx = impactX - centerX; final dy = impactY - centerY; final distance = math.sqrt(dx * dx + dy * dy) / targetRadius; // Score zones based on ringCount for (int i = 0; i < ringCount; i++) { final zoneRadius = (i + 1) / ringCount; if (distance <= zoneRadius) { return 10 - i; } } return 0; // Outside target } /// Analyze reference impacts to learn their characteristics ImpactCharacteristics? analyzeReferenceImpacts( String imagePath, List references, ) { return _imageProcessingService.analyzeReferenceImpacts(imagePath, references); } /// Detect impacts based on reference characteristics (calibrated detection) List detectImpactsFromReferences( String imagePath, TargetType targetType, double centerX, double centerY, double radius, int ringCount, ImpactCharacteristics characteristics, { double tolerance = 2.0, }) { try { final impacts = _imageProcessingService.detectImpactsFromReferences( imagePath, characteristics, tolerance: tolerance, ); return impacts.map((impact) { final score = targetType == TargetType.concentric ? _calculateConcentricScoreWithRings( impact.x, impact.y, centerX, centerY, radius, ringCount) : _calculateSilhouetteScore(impact.x, impact.y, centerX, centerY); return DetectedImpactResult( x: impact.x, y: impact.y, radius: impact.radius, suggestedScore: score, ); }).toList(); } catch (e) { return []; } } }