From 1722c338423abb23517ce48ce3517f10024252a7 Mon Sep 17 00:00:00 2001 From: Quildra Date: Sat, 2 Aug 2025 14:52:30 +0100 Subject: [PATCH] feat: implement centralized logging and standardized error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created PGHLog utility with "PGH - " prefix for easy log filtering - Enhanced MLResult with specific error types (MLErrorType enum) - Updated MLInferenceEngine interface to use MLResult pattern - Converted all Log.* calls to PGHLog.* in core components - Added type-specific error recovery strategies with onErrorType() - Improved error propagation and debugging capabilities ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../pokegoalshelper/MainActivity.kt | 33 +-- .../pokegoalshelper/ScreenCaptureService.kt | 242 +++++++++--------- .../controllers/DetectionController.kt | 36 ++- .../pokegoalshelper/ml/MLInferenceEngine.kt | 8 +- .../pokegoalshelper/ml/MLResult.kt | 60 ++++- .../pokegoalshelper/ml/YOLOInferenceEngine.kt | 123 ++++----- .../pokegoalshelper/utils/PGHLog.kt | 132 ++++++++++ 7 files changed, 413 insertions(+), 221 deletions(-) create mode 100644 app/src/main/java/com/quillstudios/pokegoalshelper/utils/PGHLog.kt diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/MainActivity.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/MainActivity.kt index a72ef34..1fa7625 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/MainActivity.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/MainActivity.kt @@ -11,6 +11,7 @@ import android.os.Build import android.os.Bundle import android.provider.Settings import android.util.Log +import com.quillstudios.pokegoalshelper.utils.PGHLog import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.activity.ComponentActivity @@ -43,7 +44,7 @@ class MainActivity : ComponentActivity() { private val screenCapturePermissionLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() ) { result -> - Log.d(TAG, "Screen capture permission result: ${result.resultCode}") + PGHLog.d(TAG, "Screen capture permission result: ${result.resultCode}") if (result.resultCode == Activity.RESULT_OK) { val data = result.data if (data != null) { @@ -51,41 +52,41 @@ class MainActivity : ComponentActivity() { startScreenCaptureService(data) // FloatingUI now handled by service overlay isCapturing = true - Log.d(TAG, "Screen capture service started successfully") + PGHLog.d(TAG, "Screen capture service started successfully") } catch (e: Exception) { - Log.e(TAG, "Failed to start screen capture service", e) + PGHLog.e(TAG, "Failed to start screen capture service", e) isCapturing = false } } else { - Log.e(TAG, "Screen capture permission granted but no data received") + PGHLog.e(TAG, "Screen capture permission granted but no data received") } } else { - Log.e(TAG, "Screen capture permission denied with result code: ${result.resultCode}") + PGHLog.e(TAG, "Screen capture permission denied with result code: ${result.resultCode}") } } private fun initializeOpenCV() { if (OpenCVLoader.initLocal()) { - Log.d(TAG, "OpenCV loaded successfully") + PGHLog.d(TAG, "OpenCV loaded successfully") // Test OpenCV val testMat = Mat(100, 100, CvType.CV_8UC3) - Log.d(TAG, "Mat created: ${testMat.rows()}x${testMat.cols()}") + PGHLog.d(TAG, "Mat created: ${testMat.rows()}x${testMat.cols()}") // Initialize ONNX YOLO detector for testing - now using MLInferenceEngine in service // yoloDetector = YOLOOnnxDetector(this) // if (yoloDetector!!.initialize()) { - // Log.d(TAG, "โœ… ONNX YOLO detector initialized successfully") + // PGHLog.d(TAG, "โœ… ONNX YOLO detector initialized successfully") // } else { - // Log.e(TAG, "โŒ ONNX YOLO detector initialization failed") + // PGHLog.e(TAG, "โŒ ONNX YOLO detector initialization failed") // } - Log.d(TAG, "โœ… Using new MLInferenceEngine architecture in ScreenCaptureService") + PGHLog.d(TAG, "โœ… Using new MLInferenceEngine architecture in ScreenCaptureService") } else { - Log.e(TAG, "OpenCV initialization failed") + PGHLog.e(TAG, "OpenCV initialization failed") } } private fun testYOLODetection() { - Log.i(TAG, "๐Ÿงช YOLO testing now handled by MLInferenceEngine in ScreenCaptureService") + PGHLog.i(TAG, "๐Ÿงช YOLO testing now handled by MLInferenceEngine in ScreenCaptureService") // yoloDetector?.testWithStaticImage() } @@ -121,10 +122,10 @@ class MainActivity : ComponentActivity() { ActivityResultContracts.RequestPermission() ) { isGranted -> if (isGranted) { - Log.d(TAG, "Notification permission granted, checking overlay permission") + PGHLog.d(TAG, "Notification permission granted, checking overlay permission") requestScreenCapturePermission() // Re-check other permissions } else { - Log.e(TAG, "Notification permission denied") + PGHLog.e(TAG, "Notification permission denied") } } @@ -133,10 +134,10 @@ class MainActivity : ComponentActivity() { ) { _ -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Settings.canDrawOverlays(this)) { - Log.d(TAG, "Overlay permission granted, proceeding with screen capture") + PGHLog.d(TAG, "Overlay permission granted, proceeding with screen capture") proceedWithScreenCapture() } else { - Log.e(TAG, "Overlay permission denied") + PGHLog.e(TAG, "Overlay permission denied") } } } diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt index 8ed5284..f5d1f4e 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt @@ -16,6 +16,8 @@ import android.media.projection.MediaProjectionManager import android.os.* import android.util.DisplayMetrics import android.util.Log +import com.quillstudios.pokegoalshelper.utils.PGHLog +import com.quillstudios.pokegoalshelper.ml.MLErrorType import android.view.WindowManager import android.view.View import android.view.Gravity @@ -148,11 +150,13 @@ class ScreenCaptureService : Service() { mlInferenceEngine = YOLOInferenceEngine(this) Thread { runBlocking { - if (!mlInferenceEngine!!.initialize()) { - Log.e(TAG, "โŒ Failed to initialize ML inference engine") - } else { - Log.i(TAG, "โœ… ML inference engine initialized for screen capture") - } + mlInferenceEngine!!.initialize() + .onSuccess { + PGHLog.i(TAG, "โœ… ML inference engine initialized for screen capture") + } + .onError { errorType, exception, message -> + PGHLog.e(TAG, "โŒ Failed to initialize ML inference engine: $message (${errorType.name})", exception) + } } }.start() @@ -169,7 +173,7 @@ class ScreenCaptureService : Service() { onClose = { stopSelf() } ) - Log.d(TAG, "โœ… MVC architecture initialized") + PGHLog.d(TAG, "โœ… MVC architecture initialized") } override fun onBind(intent: Intent?): IBinder = binder @@ -238,7 +242,7 @@ class ScreenCaptureService : Service() { } private fun startScreenCapture(resultData: Intent) { - Log.d(TAG, "Starting screen capture") + PGHLog.d(TAG, "Starting screen capture") try { val notification = NotificationCompat.Builder(this, CHANNEL_ID) @@ -260,28 +264,28 @@ class ScreenCaptureService : Service() { ) .build() - Log.d(TAG, "Starting foreground service") + PGHLog.d(TAG, "Starting foreground service") startForeground(NOTIFICATION_ID, notification) // Use the screen capture manager to start capture if (!screenCaptureManager.startCapture(resultData)) { - Log.e(TAG, "Failed to start screen capture via manager") + PGHLog.e(TAG, "Failed to start screen capture via manager") stopSelf() return } - Log.d(TAG, "Screen capture setup complete") + PGHLog.d(TAG, "Screen capture setup complete") // Show floating overlay enhancedFloatingFAB?.show() } catch (e: Exception) { - Log.e(TAG, "Error starting screen capture", e) + PGHLog.e(TAG, "Error starting screen capture", e) stopSelf() } } private fun stopScreenCapture() { - Log.d(TAG, "Stopping screen capture") + PGHLog.d(TAG, "Stopping screen capture") handler.removeCallbacks(captureRunnable) hideDetectionOverlay() @@ -311,7 +315,7 @@ class ScreenCaptureService : Service() { // Don't close the image yet - it will be closed in triggerManualDetection } } catch (e: Exception) { - Log.e(TAG, "Error handling captured image", e) + PGHLog.e(TAG, "Error handling captured image", e) } } @@ -320,7 +324,7 @@ class ScreenCaptureService : Service() { private fun captureScreen() { // Trigger image capture by reading from the ImageReader // The onImageAvailableListener will handle the actual processing - Log.d(TAG, "Triggering screen capture...") + PGHLog.d(TAG, "Triggering screen capture...") } private fun processImage(image: Image) { @@ -331,7 +335,7 @@ class ScreenCaptureService : Service() { // Get screen dimensions from the manager val screenDimensions = screenCaptureManager.getScreenDimensions() if (screenDimensions == null) { - Log.e(TAG, "Screen dimensions not available from manager") + PGHLog.e(TAG, "Screen dimensions not available from manager") return } val (screenWidth, screenHeight) = screenDimensions @@ -342,8 +346,8 @@ class ScreenCaptureService : Service() { val rowStride = planes[0].rowStride val rowPadding = rowStride - pixelStride * screenWidth - Log.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: pixelStride=$pixelStride, rowStride=$rowStride, rowPadding=$rowPadding") - Log.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: screenSize=${screenWidth}x${screenHeight}, expected bitmap=${screenWidth + rowPadding / pixelStride}x${screenHeight}") + PGHLog.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: pixelStride=$pixelStride, rowStride=$rowStride, rowPadding=$rowPadding") + PGHLog.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: screenSize=${screenWidth}x${screenHeight}, expected bitmap=${screenWidth + rowPadding / pixelStride}x${screenHeight}") // Create bitmap from image bitmap = Bitmap.createBitmap( @@ -353,26 +357,26 @@ class ScreenCaptureService : Service() { ) bitmap.copyPixelsFromBuffer(buffer) - Log.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: created bitmap=${bitmap.width}x${bitmap.height}") + PGHLog.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: created bitmap=${bitmap.width}x${bitmap.height}") // Convert to cropped bitmap if needed croppedBitmap = if (rowPadding == 0) { - Log.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: No padding, using original bitmap") + PGHLog.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: No padding, using original bitmap") bitmap } else { - Log.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: Cropping bitmap from ${bitmap.width}x${bitmap.height} to ${screenWidth}x${screenHeight}") + PGHLog.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: Cropping bitmap from ${bitmap.width}x${bitmap.height} to ${screenWidth}x${screenHeight}") Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight) } - Log.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: final bitmap=${croppedBitmap.width}x${croppedBitmap.height}") + PGHLog.d(TAG, "๐Ÿ–ผ๏ธ CAPTURE DEBUG: final bitmap=${croppedBitmap.width}x${croppedBitmap.height}") // Convert to OpenCV Mat for analysis - with proper resource management Mat().use { mat -> Utils.bitmapToMat(croppedBitmap, mat) // DEBUG: Check color conversion - Log.d(TAG, "๐ŸŽจ COLOR DEBUG: Mat type=${mat.type()}, channels=${mat.channels()}") - Log.d(TAG, "๐ŸŽจ COLOR DEBUG: OpenCV expects BGR, Android Bitmap is ARGB") + PGHLog.d(TAG, "๐ŸŽจ COLOR DEBUG: Mat type=${mat.type()}, channels=${mat.channels()}") + PGHLog.d(TAG, "๐ŸŽจ COLOR DEBUG: OpenCV expects BGR, Android Bitmap is ARGB") // Sample a center pixel to check color values if (mat.rows() > 0 && mat.cols() > 0) { @@ -383,8 +387,8 @@ class ScreenCaptureService : Service() { val b = pixel[0].toInt() val g = pixel[1].toInt() val r = pixel[2].toInt() - Log.d(TAG, "๐ŸŽจ COLOR DEBUG: Center pixel (${centerX},${centerY}) BGR=($b,$g,$r) -> RGB=(${r},${g},${b})") - Log.d(TAG, "๐ŸŽจ COLOR DEBUG: Center pixel hex = #${String.format("%02x%02x%02x", r, g, b)}") + PGHLog.d(TAG, "๐ŸŽจ COLOR DEBUG: Center pixel (${centerX},${centerY}) BGR=($b,$g,$r) -> RGB=(${r},${g},${b})") + PGHLog.d(TAG, "๐ŸŽจ COLOR DEBUG: Center pixel hex = #${String.format("%02x%02x%02x", r, g, b)}") } } @@ -393,7 +397,7 @@ class ScreenCaptureService : Service() { } } catch (e: Exception) { - Log.e(TAG, "Error processing image", e) + PGHLog.e(TAG, "Error processing image", e) } finally { // Always clean up bitmaps in finally block to prevent leaks if (croppedBitmap != null && croppedBitmap != bitmap) { @@ -404,24 +408,24 @@ class ScreenCaptureService : Service() { } private fun analyzePokemonScreen(mat: Mat) { - Log.i(TAG, "๐Ÿ“ฑ ANALYZING SCREEN: ${mat.cols()}x${mat.rows()}") + PGHLog.i(TAG, "๐Ÿ“ฑ ANALYZING SCREEN: ${mat.cols()}x${mat.rows()}") // Check if analysis has been stuck for too long (30 seconds max) val currentTime = System.currentTimeMillis() if (isAnalyzing && (currentTime - analysisStartTime) > 30000) { - Log.w(TAG, "โš ๏ธ Analysis stuck for >30s, resetting flag") + PGHLog.w(TAG, "โš ๏ธ Analysis stuck for >30s, resetting flag") isAnalyzing = false } // Skip if already analyzing if (isAnalyzing) { - Log.d(TAG, "โญ๏ธ Skipping analysis - previous cycle still in progress") + PGHLog.d(TAG, "โญ๏ธ Skipping analysis - previous cycle still in progress") return } isAnalyzing = true analysisStartTime = currentTime - Log.d(TAG, "๐Ÿ”„ Starting new analysis cycle") + PGHLog.d(TAG, "๐Ÿ”„ Starting new analysis cycle") try { // Run ML inference first @@ -429,25 +433,35 @@ class ScreenCaptureService : Service() { Utils.matToBitmap(mat, bitmap) val detections: List = runBlocking { - mlInferenceEngine?.detect(bitmap) ?: emptyList() + mlInferenceEngine?.detect(bitmap) + ?.onErrorType(MLErrorType.MEMORY_ERROR) { exception, message -> + PGHLog.w(TAG, "โš ๏ธ Memory error during detection, may retry with smaller image: $message") + } + ?.onErrorType(MLErrorType.TIMEOUT_ERROR) { exception, message -> + PGHLog.w(TAG, "โš ๏ธ Detection timed out, analysis may be too complex: $message") + } + ?.onErrorType(MLErrorType.INVALID_INPUT) { exception, message -> + PGHLog.e(TAG, "โŒ Invalid input to ML engine: $message") + } + ?.getOrDefault(emptyList()) ?: emptyList() } if (detections.isEmpty()) { - Log.i(TAG, "๐Ÿ” No Pokemon UI elements detected by ONNX YOLO") + PGHLog.i(TAG, "๐Ÿ” No Pokemon UI elements detected by ONNX YOLO") isAnalyzing = false return } - Log.i(TAG, "๐ŸŽฏ ONNX YOLO detected ${detections.size} UI elements") + PGHLog.i(TAG, "๐ŸŽฏ ONNX YOLO detected ${detections.size} UI elements") // Log ALL detections for debugging detections.forEachIndexed { index, detection -> - Log.i(TAG, " $index: ${detection.className} (${String.format("%.3f", detection.confidence)}) at [${detection.boundingBox.left}, ${detection.boundingBox.top}, ${detection.boundingBox.width}, ${detection.boundingBox.height}]") + PGHLog.i(TAG, " $index: ${detection.className} (${String.format("%.3f", detection.confidence)}) at [${detection.boundingBox.left}, ${detection.boundingBox.top}, ${detection.boundingBox.width}, ${detection.boundingBox.height}]") } // Show breakdown by type val detectionCounts = detections.groupBy { it.className }.mapValues { it.value.size } - Log.i(TAG, "๐Ÿ” Detection counts by type: $detectionCounts") + PGHLog.i(TAG, "๐Ÿ” Detection counts by type: $detectionCounts") // Check for commonly missing elements val expectedElements = listOf("pokemon_level", "attack_value", "sp_def_value", "shiny_icon", @@ -456,18 +470,18 @@ class ScreenCaptureService : Service() { detections.none { detection -> detection.className.startsWith(expected.split("_").take(2).joinToString("_")) } } if (missingElements.isNotEmpty()) { - Log.w(TAG, "โš ๏ธ Missing expected elements: $missingElements") + PGHLog.w(TAG, "โš ๏ธ Missing expected elements: $missingElements") } // Show detection overlay IMMEDIATELY (no OCR blocking) showYOLODetectionOverlay(detections) - Log.i(TAG, "๐Ÿ“บ Overlay displayed with ${detections.size} detections") + PGHLog.i(TAG, "๐Ÿ“บ Overlay displayed with ${detections.size} detections") // Extract Pokemon info using YOLO detections in background extractPokemonInfoFromYOLOAsync(mat, detections) } catch (e: Exception) { - Log.e(TAG, "Error analyzing Pokemon screen", e) + PGHLog.e(TAG, "Error analyzing Pokemon screen", e) isAnalyzing = false } } @@ -486,18 +500,18 @@ class ScreenCaptureService : Service() { handler.post { try { if (pokemonInfo != null) { - Log.i(TAG, "๐Ÿ”ฅ POKEMON DATA EXTRACTED SUCCESSFULLY!") + PGHLog.i(TAG, "๐Ÿ”ฅ POKEMON DATA EXTRACTED SUCCESSFULLY!") logPokemonInfo(pokemonInfo) // TODO: Send to your API // sendToAPI(pokemonInfo) } else { - Log.i(TAG, "โŒ Could not extract complete Pokemon info") + PGHLog.i(TAG, "โŒ Could not extract complete Pokemon info") } } finally { // Analysis cycle complete, allow next one isAnalyzing = false val duration = System.currentTimeMillis() - analysisStartTime - Log.d(TAG, "โœ… Analysis cycle complete after ${duration}ms - ready for next") + PGHLog.d(TAG, "โœ… Analysis cycle complete after ${duration}ms - ready for next") } } @@ -505,13 +519,13 @@ class ScreenCaptureService : Service() { matCopy.release() } catch (e: Exception) { - Log.e(TAG, "Error in async Pokemon extraction", e) + PGHLog.e(TAG, "Error in async Pokemon extraction", e) matCopy.release() // Clear flag on error too handler.post { isAnalyzing = false - Log.d(TAG, "โŒ Analysis cycle failed - ready for next") + PGHLog.d(TAG, "โŒ Analysis cycle failed - ready for next") } } } @@ -519,7 +533,7 @@ class ScreenCaptureService : Service() { private fun extractPokemonInfoFromYOLO(mat: Mat, detections: List): PokemonInfo? { try { - Log.i(TAG, "๐ŸŽฏ Extracting Pokemon info from ${detections.size} YOLO detections") + PGHLog.i(TAG, "๐ŸŽฏ Extracting Pokemon info from ${detections.size} YOLO detections") // Group detections by type val detectionMap = detections.groupBy { it.className } @@ -542,7 +556,7 @@ class ScreenCaptureService : Service() { // Wait for all OCR tasks to complete (max 10 seconds total) val completed = latch.await(10, TimeUnit.SECONDS) if (!completed) { - Log.w(TAG, "โฑ๏ธ Some OCR tasks timed out after 10 seconds") + PGHLog.w(TAG, "โฑ๏ธ Some OCR tasks timed out after 10 seconds") } // Extract results @@ -579,20 +593,20 @@ class ScreenCaptureService : Service() { // Detect tera type val teraType = detectTeraTypeFromDetections(detectionMap) - Log.i(TAG, "๐Ÿ“Š YOLO extraction summary:") - Log.i(TAG, " Nickname: '${nickname ?: "null"}'") - Log.i(TAG, " Level: ${level ?: "null"}") - Log.i(TAG, " Species: '${species ?: "null"}'") - Log.i(TAG, " Nature: '${nature ?: "null"}'") - Log.i(TAG, " Ability: '${ability ?: "null"}'") - Log.i(TAG, " Gender: '${gender ?: "null"}'") - Log.i(TAG, " Pokeball: '${pokeballType ?: "null"}'") - Log.i(TAG, " Types: ${types}") - Log.i(TAG, " Tera: '${teraType ?: "null"}'") - Log.i(TAG, " Shiny: $isShiny") + PGHLog.i(TAG, "๐Ÿ“Š YOLO extraction summary:") + PGHLog.i(TAG, " Nickname: '${nickname ?: "null"}'") + PGHLog.i(TAG, " Level: ${level ?: "null"}") + PGHLog.i(TAG, " Species: '${species ?: "null"}'") + PGHLog.i(TAG, " Nature: '${nature ?: "null"}'") + PGHLog.i(TAG, " Ability: '${ability ?: "null"}'") + PGHLog.i(TAG, " Gender: '${gender ?: "null"}'") + PGHLog.i(TAG, " Pokeball: '${pokeballType ?: "null"}'") + PGHLog.i(TAG, " Types: ${types}") + PGHLog.i(TAG, " Tera: '${teraType ?: "null"}'") + PGHLog.i(TAG, " Shiny: $isShiny") if (nickname.isNullOrBlank() && species.isNullOrBlank() && level == null) { - Log.w(TAG, "โš ๏ธ No essential Pokemon data found with YOLO detection") + PGHLog.w(TAG, "โš ๏ธ No essential Pokemon data found with YOLO detection") return null } @@ -621,7 +635,7 @@ class ScreenCaptureService : Service() { ) } catch (e: Exception) { - Log.e(TAG, "Error extracting Pokemon info from YOLO detections", e) + PGHLog.e(TAG, "Error extracting Pokemon info from YOLO detections", e) return null } } @@ -634,7 +648,7 @@ class ScreenCaptureService : Service() { results[key] = text } } catch (e: Exception) { - Log.e(TAG, "Error in OCR task for $key", e) + PGHLog.e(TAG, "Error in OCR task for $key", e) synchronized(results) { results[key] = null } @@ -653,7 +667,7 @@ class ScreenCaptureService : Service() { results[key] = level } } catch (e: Exception) { - Log.e(TAG, "Error in level OCR task", e) + PGHLog.e(TAG, "Error in level OCR task", e) synchronized(results) { results[key] = null } @@ -689,9 +703,9 @@ class ScreenCaptureService : Service() { val safeBbox = Rect(clippedX, clippedY, clippedWidth, clippedHeight) // Debug logging for bounding box transformations - Log.d(TAG, "๐Ÿ“ Expanded bbox for ${detection.className}: [${bbox.left},${bbox.top},${bbox.width},${bbox.height}] โ†’ [${expandedBbox.x},${expandedBbox.y},${expandedBbox.width},${expandedBbox.height}]") + PGHLog.d(TAG, "๐Ÿ“ Expanded bbox for ${detection.className}: [${bbox.left},${bbox.top},${bbox.width},${bbox.height}] โ†’ [${expandedBbox.x},${expandedBbox.y},${expandedBbox.width},${expandedBbox.height}]") if (safeBbox.x != expandedBbox.x || safeBbox.y != expandedBbox.y || safeBbox.width != expandedBbox.width || safeBbox.height != expandedBbox.height) { - Log.w(TAG, "โš ๏ธ Clipped bbox for ${detection.className}: expanded=[${expandedBbox.x},${expandedBbox.y},${expandedBbox.width},${expandedBbox.height}] โ†’ safe=[${safeBbox.x},${safeBbox.y},${safeBbox.width},${safeBbox.height}] (image: ${mat.cols()}x${mat.rows()})") + PGHLog.w(TAG, "โš ๏ธ Clipped bbox for ${detection.className}: expanded=[${expandedBbox.x},${expandedBbox.y},${expandedBbox.width},${expandedBbox.height}] โ†’ safe=[${safeBbox.x},${safeBbox.y},${safeBbox.width},${safeBbox.height}] (image: ${mat.cols()}x${mat.rows()})") } // Extract region of interest using safe bounding box @@ -717,14 +731,14 @@ class ScreenCaptureService : Service() { processedRoi.release() if (extractedText != null) { - Log.i(TAG, "โœ… YOLO SUCCESS: ${detection.className} = '$extractedText' (conf: ${String.format("%.2f", detection.confidence)})") + PGHLog.i(TAG, "โœ… YOLO SUCCESS: ${detection.className} = '$extractedText' (conf: ${String.format("%.2f", detection.confidence)})") } else { - Log.w(TAG, "โŒ YOLO FAILED: ${detection.className} - no text found (conf: ${String.format("%.2f", detection.confidence)})") + PGHLog.w(TAG, "โŒ YOLO FAILED: ${detection.className} - no text found (conf: ${String.format("%.2f", detection.confidence)})") } return extractedText } catch (e: Exception) { - Log.e(TAG, "Error extracting text from YOLO detection ${detection.className}", e) + PGHLog.e(TAG, "Error extracting text from YOLO detection ${detection.className}", e) return null } } @@ -874,10 +888,10 @@ class ScreenCaptureService : Service() { private fun showYOLODetectionOverlay(detections: List) { try { - Log.i(TAG, "๐ŸŽจ Creating YOLO detection overlay for ${detections.size} detections") + PGHLog.i(TAG, "๐ŸŽจ Creating YOLO detection overlay for ${detections.size} detections") if (detectionOverlay == null) { - Log.i(TAG, "๐Ÿ†• Creating new DetectionOverlay instance") + PGHLog.i(TAG, "๐Ÿ†• Creating new DetectionOverlay instance") detectionOverlay = DetectionOverlay(this) } @@ -893,12 +907,12 @@ class ScreenCaptureService : Service() { ) }.toMap() - Log.i(TAG, "๐Ÿ“บ Showing YOLO overlay with ${regions.size} regions...") + PGHLog.i(TAG, "๐Ÿ“บ Showing YOLO overlay with ${regions.size} regions...") detectionOverlay?.showOverlay(regions) - Log.i(TAG, "โœ… YOLO overlay show command sent") + PGHLog.i(TAG, "โœ… YOLO overlay show command sent") } catch (e: Exception) { - Log.e(TAG, "โŒ Error showing YOLO detection overlay", e) + PGHLog.e(TAG, "โŒ Error showing YOLO detection overlay", e) } } @@ -908,7 +922,7 @@ class ScreenCaptureService : Service() { private fun performOCR(bitmap: Bitmap, purpose: String): String? { try { - Log.d(TAG, "๐Ÿ” Starting OCR for $purpose - bitmap: ${bitmap.width}x${bitmap.height}") + PGHLog.d(TAG, "๐Ÿ” Starting OCR for $purpose - bitmap: ${bitmap.width}x${bitmap.height}") // Create InputImage for ML Kit val image = InputImage.fromBitmap(bitmap, 0) @@ -922,18 +936,18 @@ class ScreenCaptureService : Service() { recognizer.process(image) .addOnSuccessListener { visionText -> result = visionText.text.trim() - Log.d(TAG, "๐Ÿ” Raw OCR result for $purpose: '${visionText.text}' (blocks: ${visionText.textBlocks.size})") + PGHLog.d(TAG, "๐Ÿ” Raw OCR result for $purpose: '${visionText.text}' (blocks: ${visionText.textBlocks.size})") if (result.isNullOrBlank()) { - Log.w(TAG, "โš ๏ธ OCR for $purpose: NO TEXT DETECTED - ML Kit found ${visionText.textBlocks.size} text blocks but text is empty") + PGHLog.w(TAG, "โš ๏ธ OCR for $purpose: NO TEXT DETECTED - ML Kit found ${visionText.textBlocks.size} text blocks but text is empty") } else { - Log.i(TAG, "โœ… OCR SUCCESS for $purpose: '${result}' (${result!!.length} chars)") + PGHLog.i(TAG, "โœ… OCR SUCCESS for $purpose: '${result}' (${result!!.length} chars)") } latch.countDown() } .addOnFailureListener { e -> ocrError = e - Log.e(TAG, "โŒ OCR failed for $purpose: ${e.message}", e) + PGHLog.e(TAG, "โŒ OCR failed for $purpose: ${e.message}", e) latch.countDown() } @@ -941,22 +955,22 @@ class ScreenCaptureService : Service() { val completed = latch.await(5, TimeUnit.SECONDS) if (!completed) { - Log.e(TAG, "โฑ๏ธ OCR timeout for $purpose after 5 seconds") + PGHLog.e(TAG, "โฑ๏ธ OCR timeout for $purpose after 5 seconds") return null } if (ocrError != null) { - Log.e(TAG, "โŒ OCR error for $purpose: ${ocrError!!.message}") + PGHLog.e(TAG, "โŒ OCR error for $purpose: ${ocrError!!.message}") return null } // Clean and process the result val cleanedResult = cleanOCRResult(result, purpose) - Log.d(TAG, "๐Ÿง™ Cleaned result for $purpose: '${cleanedResult}'") + PGHLog.d(TAG, "๐Ÿง™ Cleaned result for $purpose: '${cleanedResult}'") return cleanedResult } catch (e: Exception) { - Log.e(TAG, "โŒ Error in OCR for $purpose", e) + PGHLog.e(TAG, "โŒ Error in OCR for $purpose", e) return null } } @@ -1014,7 +1028,7 @@ class ScreenCaptureService : Service() { val resized = Mat() Imgproc.resize(roi, resized, Size(roi.width() * scale, roi.height() * scale)) - Log.d(TAG, "๐Ÿ” Upscaled OCR region from ${roi.width()}x${roi.height()} to ${resized.width()}x${resized.height()}") + PGHLog.d(TAG, "๐Ÿ” Upscaled OCR region from ${roi.width()}x${roi.height()} to ${resized.width()}x${resized.height()}") resized } else { val copy = Mat() @@ -1057,7 +1071,7 @@ class ScreenCaptureService : Service() { return result } catch (e: Exception) { - Log.e(TAG, "Error preprocessing image, using original", e) + PGHLog.e(TAG, "Error preprocessing image, using original", e) // Return copy of original if preprocessing fails val result = Mat() roi.copyTo(result) @@ -1082,32 +1096,32 @@ class ScreenCaptureService : Service() { } private fun logPokemonInfo(pokemonInfo: PokemonInfo) { - Log.i(TAG, "====== POKEMON INFO EXTRACTED ======") - Log.i(TAG, "๐ŸŽพ Pokeball: ${pokemonInfo.pokeballType}") - Log.i(TAG, "๐Ÿ“› Nickname: ${pokemonInfo.nickname}") - Log.i(TAG, "โšค Gender: ${pokemonInfo.gender}") - Log.i(TAG, "๐Ÿ“Š Level: ${pokemonInfo.level}") - Log.i(TAG, "๐ŸŒ Language: ${pokemonInfo.language}") - Log.i(TAG, "๐ŸŽฎ Game Source: ${pokemonInfo.gameSource}") - Log.i(TAG, "โญ Favorited: ${pokemonInfo.isFavorited}") - Log.i(TAG, "๐Ÿ”ข Dex #: ${pokemonInfo.nationalDexNumber}") - Log.i(TAG, "๐Ÿพ Species: ${pokemonInfo.species}") - Log.i(TAG, "๐Ÿท๏ธ Type 1: ${pokemonInfo.primaryType}") - Log.i(TAG, "๐Ÿท๏ธ Type 2: ${pokemonInfo.secondaryType}") - Log.i(TAG, "๐Ÿ† Stamps: ${pokemonInfo.stamps}") - Log.i(TAG, "๐Ÿท๏ธ Labels: ${pokemonInfo.labels}") - Log.i(TAG, "โœ… Marks: ${pokemonInfo.marks}") + PGHLog.i(TAG, "====== POKEMON INFO EXTRACTED ======") + PGHLog.i(TAG, "๐ŸŽพ Pokeball: ${pokemonInfo.pokeballType}") + PGHLog.i(TAG, "๐Ÿ“› Nickname: ${pokemonInfo.nickname}") + PGHLog.i(TAG, "โšค Gender: ${pokemonInfo.gender}") + PGHLog.i(TAG, "๐Ÿ“Š Level: ${pokemonInfo.level}") + PGHLog.i(TAG, "๐ŸŒ Language: ${pokemonInfo.language}") + PGHLog.i(TAG, "๐ŸŽฎ Game Source: ${pokemonInfo.gameSource}") + PGHLog.i(TAG, "โญ Favorited: ${pokemonInfo.isFavorited}") + PGHLog.i(TAG, "๐Ÿ”ข Dex #: ${pokemonInfo.nationalDexNumber}") + PGHLog.i(TAG, "๐Ÿพ Species: ${pokemonInfo.species}") + PGHLog.i(TAG, "๐Ÿท๏ธ Type 1: ${pokemonInfo.primaryType}") + PGHLog.i(TAG, "๐Ÿท๏ธ Type 2: ${pokemonInfo.secondaryType}") + PGHLog.i(TAG, "๐Ÿ† Stamps: ${pokemonInfo.stamps}") + PGHLog.i(TAG, "๐Ÿท๏ธ Labels: ${pokemonInfo.labels}") + PGHLog.i(TAG, "โœ… Marks: ${pokemonInfo.marks}") if (pokemonInfo.stats != null) { - Log.i(TAG, "๐Ÿ“ˆ Stats: HP:${pokemonInfo.stats.hp} ATK:${pokemonInfo.stats.attack} DEF:${pokemonInfo.stats.defense}") - Log.i(TAG, " SP.ATK:${pokemonInfo.stats.spAttack} SP.DEF:${pokemonInfo.stats.spDefense} SPD:${pokemonInfo.stats.speed}") + PGHLog.i(TAG, "๐Ÿ“ˆ Stats: HP:${pokemonInfo.stats.hp} ATK:${pokemonInfo.stats.attack} DEF:${pokemonInfo.stats.defense}") + PGHLog.i(TAG, " SP.ATK:${pokemonInfo.stats.spAttack} SP.DEF:${pokemonInfo.stats.spDefense} SPD:${pokemonInfo.stats.speed}") } - Log.i(TAG, "โš”๏ธ Moves: ${pokemonInfo.moves}") - Log.i(TAG, "๐Ÿ’ช Ability: ${pokemonInfo.ability}") - Log.i(TAG, "๐ŸŽญ Nature: ${pokemonInfo.nature}") - Log.i(TAG, "๐Ÿ‘ค OT: ${pokemonInfo.originalTrainerName}") - Log.i(TAG, "๐Ÿ”ข ID: ${pokemonInfo.originalTrainerId}") - Log.i(TAG, "๐ŸŽฏ Confidence: ${String.format("%.2f", pokemonInfo.extractionConfidence)}") - Log.i(TAG, "====================================") + PGHLog.i(TAG, "โš”๏ธ Moves: ${pokemonInfo.moves}") + PGHLog.i(TAG, "๐Ÿ’ช Ability: ${pokemonInfo.ability}") + PGHLog.i(TAG, "๐ŸŽญ Nature: ${pokemonInfo.nature}") + PGHLog.i(TAG, "๐Ÿ‘ค OT: ${pokemonInfo.originalTrainerName}") + PGHLog.i(TAG, "๐Ÿ”ข ID: ${pokemonInfo.originalTrainerId}") + PGHLog.i(TAG, "๐ŸŽฏ Confidence: ${String.format("%.2f", pokemonInfo.extractionConfidence)}") + PGHLog.i(TAG, "====================================") } private fun convertImageToMat(image: Image): Mat? { @@ -1119,7 +1133,7 @@ class ScreenCaptureService : Service() { // Get screen dimensions from the manager val screenDimensions = screenCaptureManager.getScreenDimensions() if (screenDimensions == null) { - Log.e(TAG, "Screen dimensions not available from manager") + PGHLog.e(TAG, "Screen dimensions not available from manager") return null } val (screenWidth, screenHeight) = screenDimensions @@ -1162,7 +1176,7 @@ class ScreenCaptureService : Service() { bgrMat } catch (e: Exception) { - Log.e(TAG, "โŒ Error converting image to Mat", e) + PGHLog.e(TAG, "โŒ Error converting image to Mat", e) // Clean up on error mat?.release() if (croppedBitmap != null && croppedBitmap != bitmap) { @@ -1174,7 +1188,7 @@ class ScreenCaptureService : Service() { } private fun triggerManualDetection() { - Log.d(TAG, "๐Ÿ” Manual detection triggered via MVC!") + PGHLog.d(TAG, "๐Ÿ” Manual detection triggered via MVC!") latestImage?.let { image -> try { @@ -1187,7 +1201,7 @@ class ScreenCaptureService : Service() { Utils.matToBitmap(mat, bitmap) val detections: List = runBlocking { - mlInferenceEngine?.detect(bitmap) ?: emptyList() + mlInferenceEngine?.detect(bitmap)?.getOrDefault(emptyList()) ?: emptyList() } // Show detection overlay with results @@ -1200,7 +1214,7 @@ class ScreenCaptureService : Service() { mat.release() } else { - Log.e(TAG, "โŒ Failed to convert image to Mat") + PGHLog.e(TAG, "โŒ Failed to convert image to Mat") } // Close the image after processing to free the buffer @@ -1208,10 +1222,10 @@ class ScreenCaptureService : Service() { latestImage = null } catch (e: Exception) { - Log.e(TAG, "โŒ Error in manual detection", e) + PGHLog.e(TAG, "โŒ Error in manual detection", e) } } ?: run { - Log.w(TAG, "โš ๏ธ No image available for detection") + PGHLog.w(TAG, "โš ๏ธ No image available for detection") } } @@ -1233,11 +1247,11 @@ class ScreenCaptureService : Service() { ocrExecutor.shutdown() try { if (!ocrExecutor.awaitTermination(5, TimeUnit.SECONDS)) { - Log.w(TAG, "OCR executor did not terminate gracefully, forcing shutdown") + PGHLog.w(TAG, "OCR executor did not terminate gracefully, forcing shutdown") ocrExecutor.shutdownNow() } } catch (e: InterruptedException) { - Log.w(TAG, "Interrupted while waiting for OCR executor termination") + PGHLog.w(TAG, "Interrupted while waiting for OCR executor termination") ocrExecutor.shutdownNow() Thread.currentThread().interrupt() } diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/controllers/DetectionController.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/controllers/DetectionController.kt index 927778e..b2f9408 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/controllers/DetectionController.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/controllers/DetectionController.kt @@ -1,6 +1,7 @@ package com.quillstudios.pokegoalshelper.controllers import android.util.Log +import com.quillstudios.pokegoalshelper.utils.PGHLog // import com.quillstudios.pokegoalshelper.YOLOOnnxDetector // import com.quillstudios.pokegoalshelper.Detection import com.quillstudios.pokegoalshelper.ml.MLInferenceEngine @@ -41,7 +42,7 @@ class DetectionController( // === DetectionUIEvents Implementation === override fun onDetectionRequested() { - Log.d(TAG, "๐Ÿ” Detection requested via controller") + PGHLog.d(TAG, "๐Ÿ” Detection requested via controller") // The actual detection will be triggered by the service layer // This event will be handled by the service which has access to the image detectionRequestCallback?.invoke() @@ -58,7 +59,7 @@ class DetectionController( } override fun onClassFilterChanged(className: String?) { - Log.i(TAG, "๐Ÿ” Class filter changed to: ${className ?: "ALL CLASSES"}") + PGHLog.i(TAG, "๐Ÿ” Class filter changed to: ${className ?: "ALL CLASSES"}") currentSettings.classFilter = className @@ -75,7 +76,7 @@ class DetectionController( override fun onDebugModeToggled() { currentSettings.debugMode = !currentSettings.debugMode - Log.i(TAG, "๐Ÿ“Š Debug mode toggled: ${currentSettings.debugMode}") + PGHLog.i(TAG, "๐Ÿ“Š Debug mode toggled: ${currentSettings.debugMode}") // Apply debug mode to ML inference engine // mlInferenceEngine.toggleShowAllConfidences() // Not implemented yet @@ -89,7 +90,7 @@ class DetectionController( } override fun onCoordinateModeChanged(mode: String) { - Log.i(TAG, "๐Ÿ”ง Coordinate mode changed to: $mode") + PGHLog.i(TAG, "๐Ÿ”ง Coordinate mode changed to: $mode") currentSettings.coordinateMode = mode @@ -111,21 +112,18 @@ class DetectionController( * This will be called by the service layer */ suspend fun processDetection(inputBitmap: android.graphics.Bitmap): List { - return try { - uiCallbacks?.onDetectionStarted() - - val detections = mlInferenceEngine.detect(inputBitmap) - val detectionCount = detections.size - - Log.i(TAG, "โœ… Detection completed: $detectionCount objects found") - uiCallbacks?.onDetectionCompleted(detectionCount) - - detections - } catch (e: Exception) { - Log.e(TAG, "โŒ Detection failed", e) - uiCallbacks?.onDetectionFailed(e.message ?: "Unknown error") - emptyList() - } + uiCallbacks?.onDetectionStarted() + + return mlInferenceEngine.detect(inputBitmap) + .onSuccess { detections -> + PGHLog.i(TAG, "โœ… Detection completed: ${detections.size} objects found") + uiCallbacks?.onDetectionCompleted(detections.size) + } + .onError { errorType, exception, message -> + PGHLog.e(TAG, "โŒ Detection failed: $message (${errorType.name})", exception) + uiCallbacks?.onDetectionFailed(message) + } + .getOrDefault(emptyList()) } /** diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLInferenceEngine.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLInferenceEngine.kt index 82cb2aa..f33ce72 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLInferenceEngine.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLInferenceEngine.kt @@ -10,16 +10,16 @@ interface MLInferenceEngine { /** * Initialize the ML model and prepare for inference. - * @return true if initialization was successful, false otherwise + * @return MLResult indicating success or specific failure type */ - suspend fun initialize(): Boolean + suspend fun initialize(): MLResult /** * Perform object detection on the provided image. * @param image The bitmap image to analyze - * @return List of detected objects, empty if no objects found + * @return MLResult containing detected objects or error information */ - suspend fun detect(image: Bitmap): List + suspend fun detect(image: Bitmap): MLResult> /** * Set the confidence threshold for detections. diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLResult.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLResult.kt index 5989d2f..9440cc1 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLResult.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/MLResult.kt @@ -1,12 +1,29 @@ package com.quillstudios.pokegoalshelper.ml +/** + * Categories of ML/CV operation errors for better error handling + */ +enum class MLErrorType(val description: String) +{ + INITIALIZATION_FAILED("Model initialization failed"), + INFERENCE_FAILED("Model inference failed"), + PREPROCESSING_FAILED("Image preprocessing failed"), + POSTPROCESSING_FAILED("Result postprocessing failed"), + MEMORY_ERROR("Memory allocation error"), + INVALID_INPUT("Invalid input data"), + TIMEOUT_ERROR("Operation timed out"), + RESOURCE_ERROR("Resource management error"), + CONFIGURATION_ERROR("Configuration error"), + UNKNOWN_ERROR("Unknown error occurred") +} + /** * Result wrapper for ML operations to standardize error handling */ sealed class MLResult { data class Success(val data: T) : MLResult() - data class Error(val exception: Throwable, val message: String) : MLResult() + data class Error(val errorType: MLErrorType, val exception: Throwable, val message: String) : MLResult() /** * Returns the data if successful, or null if error @@ -38,9 +55,18 @@ sealed class MLResult /** * Execute block if error */ - inline fun onError(block: (Throwable, String) -> Unit): MLResult + inline fun onError(block: (MLErrorType, Throwable, String) -> Unit): MLResult + { + if (this is Error) block(errorType, exception, message) + return this + } + + /** + * Execute block if specific error type + */ + inline fun onErrorType(errorType: MLErrorType, block: (Throwable, String) -> Unit): MLResult { - if (this is Error) block(exception, message) + if (this is Error && this.errorType == errorType) block(exception, message) return this } @@ -50,7 +76,7 @@ sealed class MLResult inline fun map(transform: (T) -> R): MLResult = when (this) { is Success -> try { Success(transform(data)) } - catch (e: Exception) { Error(e, "Transform failed: ${e.message}") } + catch (e: Exception) { Error(MLErrorType.UNKNOWN_ERROR, e, "Transform failed: ${e.message}") } is Error -> this } } @@ -58,7 +84,7 @@ sealed class MLResult /** * Helper functions for creating results */ -inline fun mlTry(operation: () -> T): MLResult +inline fun mlTry(errorType: MLErrorType, operation: () -> T): MLResult { return try { @@ -66,11 +92,11 @@ inline fun mlTry(operation: () -> T): MLResult } catch (e: Exception) { - MLResult.Error(e, "Operation failed: ${e.message}") + MLResult.Error(errorType, e, "${errorType.description}: ${e.message}") } } -inline fun mlTryWithMessage(message: String, operation: () -> T): MLResult +inline fun mlTryWithMessage(errorType: MLErrorType, message: String, operation: () -> T): MLResult { return try { @@ -78,6 +104,24 @@ inline fun mlTryWithMessage(message: String, operation: () -> T): MLResult mlError(errorType: MLErrorType, message: String, cause: Throwable? = null): MLResult +{ + val exception = cause ?: RuntimeException(message) + return MLResult.Error(errorType, exception, message) +} + +/** + * Create an error result from exception + */ +fun mlErrorFromException(errorType: MLErrorType, exception: Throwable, customMessage: String? = null): MLResult +{ + val message = customMessage ?: "${errorType.description}: ${exception.message}" + return MLResult.Error(errorType, exception, message) } \ No newline at end of file diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt index 02aa9c3..00c0a29 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ml/YOLOInferenceEngine.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.Log +import com.quillstudios.pokegoalshelper.utils.PGHLog import org.opencv.android.Utils import org.opencv.core.* import org.opencv.imgproc.Imgproc @@ -68,13 +69,13 @@ class YOLOInferenceEngine( fun setCoordinateMode(mode: String) { COORD_TRANSFORM_MODE = mode - Log.i(TAG, "๐Ÿ”ง Coordinate transform mode changed to: $mode") + PGHLog.i(TAG, "๐Ÿ”ง Coordinate transform mode changed to: $mode") } fun toggleShowAllConfidences() { SHOW_ALL_CONFIDENCES = !SHOW_ALL_CONFIDENCES - Log.i(TAG, "๐Ÿ“Š Show all confidences: $SHOW_ALL_CONFIDENCES") + PGHLog.i(TAG, "๐Ÿ“Š Show all confidences: $SHOW_ALL_CONFIDENCES") } fun setClassFilter(className: String?) @@ -82,11 +83,11 @@ class YOLOInferenceEngine( DEBUG_CLASS_FILTER = className if (className != null) { - Log.i(TAG, "๐Ÿ” Class filter set to: '$className' (ID will be shown in debug output)") + PGHLog.i(TAG, "๐Ÿ” Class filter set to: '$className' (ID will be shown in debug output)") } else { - Log.i(TAG, "๐Ÿ” Class filter set to: ALL CLASSES") + PGHLog.i(TAG, "๐Ÿ” Class filter set to: ALL CLASSES") } } } @@ -206,58 +207,60 @@ class YOLOInferenceEngine( 95 to "ball_icon_ultraball_husui" ) - override suspend fun initialize(): Boolean = withContext(Dispatchers.IO) + override suspend fun initialize(): MLResult = withContext(Dispatchers.IO) { - if (isInitialized) return@withContext true + if (isInitialized) return@withContext MLResult.Success(Unit) - try - { - Log.i(TAG, "๐Ÿค– Initializing ONNX YOLO detector...") + mlTry(MLErrorType.INITIALIZATION_FAILED) { + PGHLog.i(TAG, "๐Ÿค– Initializing ONNX YOLO detector...") // Initialize ONNX Runtime environment ortEnvironment = OrtEnvironment.getEnvironment() // Copy model from assets to internal storage - Log.i(TAG, "๐Ÿ“‚ Copying model file: ${config.modelFile}") + PGHLog.i(TAG, "๐Ÿ“‚ Copying model file: ${config.modelFile}") val model_path = copyAssetToInternalStorage(config.modelFile) - if (model_path == null) - { - Log.e(TAG, "โŒ Failed to copy ONNX model from assets") - return@withContext false - } - Log.i(TAG, "โœ… Model copied to: $model_path") + ?: throw RuntimeException("Failed to copy ONNX model from assets") + + PGHLog.i(TAG, "โœ… Model copied to: $model_path") // Create ONNX session - Log.i(TAG, "๐Ÿ“ฅ Loading ONNX model from: $model_path") + PGHLog.i(TAG, "๐Ÿ“ฅ Loading ONNX model from: $model_path") val session_options = OrtSession.SessionOptions() session_options.setOptimizationLevel(OrtSession.SessionOptions.OptLevel.ALL_OPT) ortSession = ortEnvironment?.createSession(model_path, session_options) + ?: throw RuntimeException("Failed to create ONNX session") - Log.i(TAG, "โœ… ONNX YOLO detector initialized successfully") + PGHLog.i(TAG, "โœ… ONNX YOLO detector initialized successfully") isInitialized = true - true - } - catch (e: Exception) - { - Log.e(TAG, "โŒ Failed to initialize ONNX YOLO detector", e) - false + }.onError { errorType, exception, message -> + PGHLog.e(TAG, "โŒ $message", exception) } } - override suspend fun detect(image: Bitmap): List = withContext(Dispatchers.IO) + override suspend fun detect(image: Bitmap): MLResult> = withContext(Dispatchers.IO) { - // Convert Bitmap to Mat first to maintain compatibility with original logic - val input_mat = Mat() - Utils.bitmapToMat(image, input_mat) - - try + if (image.isRecycled) { - detectWithMat(input_mat) + return@withContext mlError(MLErrorType.INVALID_INPUT, "Bitmap is recycled") } - finally - { - input_mat.release() + + mlTry(MLErrorType.INFERENCE_FAILED) { + // Convert Bitmap to Mat first to maintain compatibility with original logic + val input_mat = Mat() + Utils.bitmapToMat(image, input_mat) + + try + { + detectWithMat(input_mat) + } + finally + { + input_mat.release() + } + }.onError { errorType, exception, message -> + PGHLog.e(TAG, "โŒ Detection failed: $message", exception) } } @@ -268,7 +271,7 @@ class YOLOInferenceEngine( { if (!isInitialized || ortSession == null) { - Log.w(TAG, "โš ๏ธ ONNX detector not initialized") + PGHLog.w(TAG, "โš ๏ธ ONNX detector not initialized") return emptyList() } @@ -276,7 +279,7 @@ class YOLOInferenceEngine( return try { - Log.d(TAG, "๐ŸŽฏ Starting ONNX YOLO detection...") + PGHLog.d(TAG, "๐ŸŽฏ Starting ONNX YOLO detection...") val detections = if (ENABLE_MULTI_PREPROCESSING) { @@ -294,13 +297,13 @@ class YOLOInferenceEngine( totalInferenceTime += lastInferenceTime inferenceCount++ - Log.d(TAG, "๐ŸŽฏ Detection completed: ${detections.size} objects found in ${lastInferenceTime}ms") + PGHLog.d(TAG, "๐ŸŽฏ Detection completed: ${detections.size} objects found in ${lastInferenceTime}ms") detections } catch (e: Exception) { - Log.e(TAG, "โŒ Error during enhanced ONNX YOLO detection", e) + PGHLog.e(TAG, "โŒ Error during enhanced ONNX YOLO detection", e) emptyList() } } @@ -344,7 +347,7 @@ class YOLOInferenceEngine( } catch (e: Exception) { - Log.w(TAG, "โš ๏ธ Preprocessing method timed out or failed", e) + PGHLog.w(TAG, "โš ๏ธ Preprocessing method timed out or failed", e) } } @@ -353,7 +356,7 @@ class YOLOInferenceEngine( } catch (e: Exception) { - Log.e(TAG, "โŒ Error in multiple preprocessing", e) + PGHLog.e(TAG, "โŒ Error in multiple preprocessing", e) return emptyList() } } @@ -382,7 +385,7 @@ class YOLOInferenceEngine( } catch (e: Exception) { - Log.e(TAG, "โŒ Error in preprocessing method '$method'", e) + PGHLog.e(TAG, "โŒ Error in preprocessing method '$method'", e) return emptyList() } } @@ -414,14 +417,14 @@ class YOLOInferenceEngine( override fun setConfidenceThreshold(threshold: Float) { confidenceThreshold = threshold.coerceIn(0.0f, 1.0f) - Log.d(TAG, "๐ŸŽš๏ธ Confidence threshold set to: $confidenceThreshold") + PGHLog.d(TAG, "๐ŸŽš๏ธ Confidence threshold set to: $confidenceThreshold") } override fun setClassFilter(className: String?) { classFilter = className DEBUG_CLASS_FILTER = className - Log.d(TAG, "๐Ÿ” Class filter set to: ${className ?: "none"}") + PGHLog.d(TAG, "๐Ÿ” Class filter set to: ${className ?: "none"}") } override fun isReady(): Boolean = isInitialized @@ -434,7 +437,7 @@ class YOLOInferenceEngine( override fun cleanup() { - Log.d(TAG, "๐Ÿงน Cleaning up YOLO Inference Engine") + PGHLog.d(TAG, "๐Ÿงน Cleaning up YOLO Inference Engine") try { @@ -442,7 +445,7 @@ class YOLOInferenceEngine( preprocessingExecutor.shutdown() if (!preprocessingExecutor.awaitTermination(2, TimeUnit.SECONDS)) { - Log.w(TAG, "โš ๏ธ Thread pool shutdown timeout, forcing shutdown") + PGHLog.w(TAG, "โš ๏ธ Thread pool shutdown timeout, forcing shutdown") preprocessingExecutor.shutdownNow() } @@ -451,7 +454,7 @@ class YOLOInferenceEngine( } catch (e: Exception) { - Log.e(TAG, "Error during cleanup", e) + PGHLog.e(TAG, "Error during cleanup", e) } finally { @@ -481,7 +484,7 @@ class YOLOInferenceEngine( } catch (e: IOException) { - Log.e(TAG, "Failed to copy asset to internal storage", e) + PGHLog.e(TAG, "Failed to copy asset to internal storage", e) null } } @@ -493,7 +496,7 @@ class YOLOInferenceEngine( { try { - Log.d(TAG, "๐Ÿ”ง Ultralytics preprocessing: input ${inputMat.cols()}x${inputMat.rows()}, type=${inputMat.type()}") + PGHLog.d(TAG, "๐Ÿ”ง Ultralytics preprocessing: input ${inputMat.cols()}x${inputMat.rows()}, type=${inputMat.type()}") // Step 1: Letterbox resize (preserves aspect ratio with padding) val letterboxed = letterboxResize(inputMat, config.inputSize, config.inputSize) @@ -510,19 +513,19 @@ class YOLOInferenceEngine( // Use Gaussian blur as a more reliable alternative to bilateral filter Imgproc.GaussianBlur(processed_mat, denoised, Size(3.0, 3.0), 0.5) processed_mat.release() - Log.d(TAG, "โœ… Ultralytics preprocessing complete with Gaussian smoothing") + PGHLog.d(TAG, "โœ… Ultralytics preprocessing complete with Gaussian smoothing") return denoised } else { - Log.w(TAG, "โš ๏ธ Smoothing skipped - unsupported image type: ${processed_mat.type()}") + PGHLog.w(TAG, "โš ๏ธ Smoothing skipped - unsupported image type: ${processed_mat.type()}") denoised.release() return processed_mat } } catch (e: Exception) { - Log.e(TAG, "โŒ Error in Ultralytics preprocessing", e) + PGHLog.e(TAG, "โŒ Error in Ultralytics preprocessing", e) // Return a copy instead of the original to avoid memory issues val safe_copy = Mat() inputMat.copyTo(safe_copy) @@ -573,7 +576,7 @@ class YOLOInferenceEngine( } catch (e: Exception) { - Log.e(TAG, "โŒ Error enhancing image", e) + PGHLog.e(TAG, "โŒ Error enhancing image", e) inputMat.copyTo(enhanced) } return enhanced @@ -598,7 +601,7 @@ class YOLOInferenceEngine( } catch (e: Exception) { - Log.e(TAG, "โŒ Error sharpening image", e) + PGHLog.e(TAG, "โŒ Error sharpening image", e) inputMat.copyTo(sharpened) } return sharpened @@ -750,7 +753,7 @@ class YOLOInferenceEngine( } catch (e: Exception) { - Log.e(TAG, "โŒ $errorMessage", e) + PGHLog.e(TAG, "โŒ $errorMessage", e) fallback() } } @@ -892,7 +895,7 @@ class YOLOInferenceEngine( val num_detections = 300 // From model output [1, 300, 6] val features_per_detection = 6 // [x1, y1, x2, y2, confidence, class_id] - Log.d(TAG, "๐Ÿ” Parsing NMS output: 300 post-processed detections") + PGHLog.d(TAG, "๐Ÿ” Parsing NMS output: 300 post-processed detections") var valid_detections = 0 @@ -937,7 +940,7 @@ class YOLOInferenceEngine( // Debug logging for all detections if enabled if (SHOW_ALL_CONFIDENCES && mapped_confidence > 0.1f) { - Log.d(TAG, "๐Ÿ” [DEBUG] Class: $class_name (ID: $class_id), Confidence: %.3f, Original: %.3f".format(mapped_confidence, confidence)) + PGHLog.d(TAG, "๐Ÿ” [DEBUG] Class: $class_name (ID: $class_id), Confidence: %.3f, Original: %.3f".format(mapped_confidence, confidence)) } // Apply class filter if set @@ -968,13 +971,13 @@ class YOLOInferenceEngine( if (valid_detections <= 3) { - Log.d(TAG, "โœ… Valid NMS detection: class=$class_id ($class_name), conf=${String.format("%.4f", mapped_confidence)}") + PGHLog.d(TAG, "โœ… Valid NMS detection: class=$class_id ($class_name), conf=${String.format("%.4f", mapped_confidence)}") } } } } - Log.d(TAG, "๐ŸŽฏ NMS parsing complete: $valid_detections valid detections") + PGHLog.d(TAG, "๐ŸŽฏ NMS parsing complete: $valid_detections valid detections") return detections.sortedByDescending { it.confidence } } @@ -1101,7 +1104,7 @@ class YOLOInferenceEngine( ) final_confidence = (total_weight / overlapping_boxes.size).toFloat() - Log.d(TAG, "๐Ÿ”— Merged ${overlapping_boxes.size} overlapping detections, final conf: ${String.format("%.3f", final_confidence)}") + PGHLog.d(TAG, "๐Ÿ”— Merged ${overlapping_boxes.size} overlapping detections, final conf: ${String.format("%.3f", final_confidence)}") } results.add( @@ -1163,7 +1166,7 @@ class YOLOInferenceEngine( best_detection = other_detection } suppressed[j] = true - Log.d(TAG, "๐Ÿ”— Cross-class NMS: merged ${current_detection.className} with ${other_detection.className}") + PGHLog.d(TAG, "๐Ÿ”— Cross-class NMS: merged ${current_detection.className} with ${other_detection.className}") } } } diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/utils/PGHLog.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/utils/PGHLog.kt new file mode 100644 index 0000000..7d3622b --- /dev/null +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/utils/PGHLog.kt @@ -0,0 +1,132 @@ +package com.quillstudios.pokegoalshelper.utils + +import android.util.Log + +/** + * Centralized logging utility for PokeGoalsHelper + * Prefixes all logs with "PGH - " for easy filtering + */ +object PGHLog +{ + private const val PREFIX = "PGH - " + + /** + * Send a VERBOSE log message + */ + fun v(tag: String, msg: String): Int + { + return Log.v(tag, "$PREFIX$msg") + } + + /** + * Send a VERBOSE log message with throwable + */ + fun v(tag: String, msg: String, tr: Throwable): Int + { + return Log.v(tag, "$PREFIX$msg", tr) + } + + /** + * Send a DEBUG log message + */ + fun d(tag: String, msg: String): Int + { + return Log.d(tag, "$PREFIX$msg") + } + + /** + * Send a DEBUG log message with throwable + */ + fun d(tag: String, msg: String, tr: Throwable): Int + { + return Log.d(tag, "$PREFIX$msg", tr) + } + + /** + * Send an INFO log message + */ + fun i(tag: String, msg: String): Int + { + return Log.i(tag, "$PREFIX$msg") + } + + /** + * Send an INFO log message with throwable + */ + fun i(tag: String, msg: String, tr: Throwable): Int + { + return Log.i(tag, "$PREFIX$msg", tr) + } + + /** + * Send a WARN log message + */ + fun w(tag: String, msg: String): Int + { + return Log.w(tag, "$PREFIX$msg") + } + + /** + * Send a WARN log message with throwable + */ + fun w(tag: String, msg: String, tr: Throwable): Int + { + return Log.w(tag, "$PREFIX$msg", tr) + } + + /** + * Send a WARN log message with throwable only + */ + fun w(tag: String, tr: Throwable): Int + { + return Log.w(tag, PREFIX, tr) + } + + /** + * Send an ERROR log message + */ + fun e(tag: String, msg: String): Int + { + return Log.e(tag, "$PREFIX$msg") + } + + /** + * Send an ERROR log message with throwable + */ + fun e(tag: String, msg: String, tr: Throwable): Int + { + return Log.e(tag, "$PREFIX$msg", tr) + } + + /** + * What a Terrible Failure: Report a condition that should never happen + */ + fun wtf(tag: String, msg: String): Int + { + return Log.wtf(tag, "$PREFIX$msg") + } + + /** + * What a Terrible Failure: Report a condition that should never happen with throwable + */ + fun wtf(tag: String, msg: String, tr: Throwable): Int + { + return Log.wtf(tag, "$PREFIX$msg", tr) + } + + /** + * What a Terrible Failure: Report a condition that should never happen with throwable only + */ + fun wtf(tag: String, tr: Throwable): Int + { + return Log.wtf(tag, PREFIX, tr) + } + + /** + * Checks to see whether or not a log for the specified tag is loggable at the specified level + */ + fun isLoggable(tag: String, level: Int): Boolean + { + return Log.isLoggable(tag, level) + } +} \ No newline at end of file