From 4bc86fede3c02ed1520198e297579ca340f1c4d5 Mon Sep 17 00:00:00 2001 From: Quildra Date: Fri, 1 Aug 2025 07:16:54 +0100 Subject: [PATCH] refactor: organize debug tools and clean up excessive logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move debug scripts to tools/debug_scripts/ directory with README - Create requirements.txt for Python debug environment setup - Remove excessive debug logging from ScreenCaptureService - Disable verbose detection logging in YOLOOnnxDetector - Update .gitignore to exclude debug artifacts - Preserve core BGR/RGB fix while cleaning up temporary code ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 7 ++ .../pokegoalshelper/ScreenCaptureService.kt | 117 +----------------- .../pokegoalshelper/YOLOOnnxDetector.kt | 10 +- tools/debug_scripts/README.md | 51 ++++++++ .../debug_scripts/debug_model_comparison.py | 0 tools/debug_scripts/export_model_variants.py | 78 ++++++++++++ tools/debug_scripts/inspect_onnx_model.py | 61 +++++++++ tools/debug_scripts/requirements.txt | 6 + .../debug_scripts/test_static_onnx.py | 0 9 files changed, 209 insertions(+), 121 deletions(-) create mode 100644 tools/debug_scripts/README.md rename debug_model_comparison.py => tools/debug_scripts/debug_model_comparison.py (100%) create mode 100644 tools/debug_scripts/export_model_variants.py create mode 100644 tools/debug_scripts/inspect_onnx_model.py create mode 100644 tools/debug_scripts/requirements.txt rename test_static_onnx.py => tools/debug_scripts/test_static_onnx.py (100%) diff --git a/.gitignore b/.gitignore index 758d1a4..38d60c0 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,10 @@ fastlane/readme.md # Android Profiling *.hprof + +model_export_env/ + +# Debug tools and temporary files +tools/debug_scripts/debug_env/ +raw_models/exports/ +package-lock.json \ No newline at end of file diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt index d956c8d..abb3f99 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ScreenCaptureService.kt @@ -1114,9 +1114,6 @@ class ScreenCaptureService : Service() { val rowStride = planes[0].rowStride val rowPadding = rowStride - pixelStride * screenWidth - Log.d(TAG, "๐Ÿ–ผ๏ธ MANUAL CAPTURE DEBUG: pixelStride=$pixelStride, rowStride=$rowStride, rowPadding=$rowPadding") - Log.d(TAG, "๐Ÿ–ผ๏ธ MANUAL CAPTURE DEBUG: screenSize=${screenWidth}x${screenHeight}") - // Create bitmap from image val bitmap = Bitmap.createBitmap( screenWidth + rowPadding / pixelStride, @@ -1125,66 +1122,23 @@ class ScreenCaptureService : Service() { ) bitmap.copyPixelsFromBuffer(buffer) - Log.d(TAG, "๐Ÿ–ผ๏ธ MANUAL CAPTURE DEBUG: created bitmap=${bitmap.width}x${bitmap.height}") - // Crop bitmap to remove padding if needed val croppedBitmap = if (rowPadding == 0) { - Log.d(TAG, "๐Ÿ–ผ๏ธ MANUAL CAPTURE DEBUG: No padding, using original bitmap") bitmap } else { - Log.d(TAG, "๐Ÿ–ผ๏ธ MANUAL CAPTURE DEBUG: Cropping bitmap from ${bitmap.width}x${bitmap.height} to ${screenWidth}x${screenHeight}") val cropped = Bitmap.createBitmap(bitmap, 0, 0, screenWidth, screenHeight) bitmap.recycle() // Clean up original cropped } - Log.d(TAG, "๐Ÿ–ผ๏ธ MANUAL CAPTURE DEBUG: final bitmap=${croppedBitmap.width}x${croppedBitmap.height}") - // Convert bitmap to Mat val mat = Mat() Utils.bitmapToMat(croppedBitmap, mat) - Log.d(TAG, "๐ŸŽจ MANUAL COLOR DEBUG: Mat type=${mat.type()}, channels=${mat.channels()}, size=${mat.cols()}x${mat.rows()}") - - // Sample specific pixels to check color values - if (mat.rows() > 0 && mat.cols() > 0) { - // Sample center pixel - val centerY = mat.rows() / 2 - val centerX = mat.cols() / 2 - val centerPixel = mat.get(centerY, centerX) - if (centerPixel != null && centerPixel.size >= 3) { - val b = centerPixel[0].toInt() - val g = centerPixel[1].toInt() - val r = centerPixel[2].toInt() - val a = if (centerPixel.size >= 4) centerPixel[3].toInt() else 255 - Log.d(TAG, "๐ŸŽจ MANUAL COLOR DEBUG: Center pixel (${centerX},${centerY}) BGRA=($b,$g,$r,$a) -> RGBA=($r,$g,$b,$a)") - Log.d(TAG, "๐ŸŽจ MANUAL COLOR DEBUG: Center pixel hex = #${String.format("%02x%02x%02x", r, g, b)}") - } - - // Sample shiny icon pixel at x=155, y=1087 - val shinyX = 155 - val shinyY = 1087 - if (shinyX < mat.cols() && shinyY < mat.rows()) { - val shinyPixel = mat.get(shinyY, shinyX) - if (shinyPixel != null && shinyPixel.size >= 3) { - val b = shinyPixel[0].toInt() - val g = shinyPixel[1].toInt() - val r = shinyPixel[2].toInt() - val a = if (shinyPixel.size >= 4) shinyPixel[3].toInt() else 255 - Log.d(TAG, "โœจ SHINY PIXEL DEBUG: Shiny icon pixel (${shinyX},${shinyY}) BGRA=($b,$g,$r,$a) -> RGBA=($r,$g,$b,$a)") - Log.d(TAG, "โœจ SHINY PIXEL DEBUG: Shiny icon pixel hex = #${String.format("%02x%02x%02x", r, g, b)}") - } - } else { - Log.w(TAG, "โš ๏ธ SHINY PIXEL DEBUG: Coordinates (${shinyX},${shinyY}) out of bounds for ${mat.cols()}x${mat.rows()} image") - } - } - - // Convert from RGBA to BGR (OpenCV format, then YOLO preprocessing will handle RGB conversion) + // Convert from RGBA to BGR (OpenCV format for proper color channel handling) val bgrMat = Mat() Imgproc.cvtColor(mat, bgrMat, Imgproc.COLOR_RGBA2BGR) - Log.d(TAG, "๐ŸŽจ COLOR FIX: Converted RGBA to BGR format for OpenCV compatibility") - // Clean up mat.release() croppedBitmap.recycle() @@ -1205,11 +1159,6 @@ class ScreenCaptureService : Service() { val mat = convertImageToMat(image) if (mat != null) { - // DEBUG: Save captured image for comparison with working test image - saveDebugImage(mat, "captured_screen_${System.currentTimeMillis()}") - - // Also test this captured image through ONNX pipeline directly - testCapturedImageThroughONNX(mat) // Use controller to process detection (this will notify UI via callbacks) val detections = detectionController.processDetection(mat) @@ -1238,70 +1187,6 @@ class ScreenCaptureService : Service() { } } - /** - * Save debug image to external storage for comparison - */ - private fun saveDebugImage(mat: Mat, filename: String) { - try { - val debugDir = File(getExternalFilesDir(null), "debug_images") - if (!debugDir.exists()) { - debugDir.mkdirs() - } - - val imageFile = File(debugDir, "$filename.jpg") - val success = Imgcodecs.imwrite(imageFile.absolutePath, mat) - - if (success) { - Log.d(TAG, "๐Ÿ–ผ๏ธ DEBUG: Saved captured image to ${imageFile.absolutePath}") - Log.d(TAG, "๐Ÿ–ผ๏ธ DEBUG: Image properties - Size: ${mat.cols()}x${mat.rows()}, Type: ${mat.type()}, Channels: ${mat.channels()}") - } else { - Log.e(TAG, "โŒ DEBUG: Failed to save image") - } - } catch (e: Exception) { - Log.e(TAG, "โŒ DEBUG: Error saving image", e) - } - } - - /** - * Test captured image directly through ONNX pipeline to isolate issues - */ - private fun testCapturedImageThroughONNX(mat: Mat) { - try { - Log.d(TAG, "๐Ÿงช DEBUG: Testing captured image through ONNX pipeline") - Log.d(TAG, "๐Ÿงช DEBUG: Input image - Size: ${mat.cols()}x${mat.rows()}, Type: ${mat.type()}") - - // Test with existing initialized ONNX detector - val currentDetector = yoloDetector - val detections = if (currentDetector is YOLOOnnxDetector) { - Log.d(TAG, "๐Ÿงช DEBUG: Using existing ONNX detector") - currentDetector.detect(mat) - } else { - Log.d(TAG, "๐Ÿงช DEBUG: Creating new ONNX detector for test") - val testDetector = YOLOOnnxDetector(this) - testDetector.detect(mat) - } - - Log.d(TAG, "๐Ÿงช DEBUG: Direct ONNX test found ${detections.size} detections") - - // Check specifically for shiny icons (class 50) - val shinyDetections = detections.filter { detection -> detection.classId == 50 } - if (shinyDetections.isNotEmpty()) { - Log.d(TAG, "โœจ DEBUG: FOUND ${shinyDetections.size} SHINY ICONS in captured image!") - shinyDetections.forEach { detection -> - Log.d(TAG, "โœจ DEBUG: Shiny detection - conf: ${detection.confidence}, box: [${detection.boundingBox.x}, ${detection.boundingBox.y}, ${detection.boundingBox.width}, ${detection.boundingBox.height}]") - } - } else { - Log.d(TAG, "โŒ DEBUG: NO SHINY ICONS found in captured image") - } - - // Log all detections for comparison - val classGroups = detections.groupBy { detection -> detection.classId } - Log.d(TAG, "๐Ÿงช DEBUG: Detection classes found: ${classGroups.keys.sorted()}") - - } catch (e: Exception) { - Log.e(TAG, "โŒ DEBUG: Error testing captured image", e) - } - } override fun onDestroy() { super.onDestroy() diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/YOLOOnnxDetector.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/YOLOOnnxDetector.kt index 817e0eb..0b54a85 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/YOLOOnnxDetector.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/YOLOOnnxDetector.kt @@ -39,8 +39,8 @@ class YOLOOnnxDetector(private val context: Context) { var DEBUG_CLASS_FILTER: String? = null // Set to class name to show only that class var SHOW_ALL_CONFIDENCES = false // Show all detections with their confidences - // Special debug mode for shiny icon investigation - var DEBUG_SHINY_DETECTION = true // Enable detailed shiny icon debugging + // Debug flag for troubleshooting detection issues (disable in production) + private const val DEBUG_DETECTION = false fun setCoordinateMode(mode: String) { COORD_TRANSFORM_MODE = mode @@ -406,7 +406,7 @@ class YOLOOnnxDetector(private val context: Context) { val flatOutput = outputTensor[0].flatMap { it.asIterable() }.toFloatArray() // Debug: Log raw output statistics when looking for shiny icons - if (DEBUG_SHINY_DETECTION) { + if (DEBUG_DETECTION) { val maxVal = flatOutput.maxOrNull() ?: 0f val nonZeroCount = flatOutput.count { it > 0.01f } Log.w(TAG, "๐Ÿ”ฌ [RAW OUTPUT] Method: $method, FlatOutput size: ${flatOutput.size}, Max value: %.4f, Non-zero (>0.01): $nonZeroCount".format(maxVal)) @@ -894,7 +894,7 @@ class YOLOOnnxDetector(private val context: Context) { } // Special debug logging for shiny icon (class 50) - if (DEBUG_SHINY_DETECTION && (classId == 50 || className == "shiny_icon") && mappedConfidence > 0.05f) { + if (DEBUG_DETECTION && (classId == 50 || className == "shiny_icon") && mappedConfidence > 0.05f) { Log.w(TAG, "โœจ [SHINY DEBUG] Found shiny_icon candidate! ClassID: $classId, Confidence: %.4f (mapped: %.4f), Coords: [%.1f,%.1f,%.1f,%.1f]".format(confidence, mappedConfidence, x1, y1, x2, y2)) } @@ -1231,7 +1231,7 @@ class YOLOOnnxDetector(private val context: Context) { } // Special debug logging for shiny icon (class 50) - if (DEBUG_SHINY_DETECTION && (classId == 50 || className == "shiny_icon") && mappedConfidence > 0.05f) { + if (DEBUG_DETECTION && (classId == 50 || className == "shiny_icon") && mappedConfidence > 0.05f) { Log.w(TAG, "โœจ [SHINY DEBUG] Found shiny_icon candidate! ClassID: $classId, Confidence: %.4f (mapped: %.4f), Coords: [%.1f,%.1f,%.1f,%.1f]".format(confidence, mappedConfidence, x1, y1, x2, y2)) } diff --git a/tools/debug_scripts/README.md b/tools/debug_scripts/README.md new file mode 100644 index 0000000..85221a0 --- /dev/null +++ b/tools/debug_scripts/README.md @@ -0,0 +1,51 @@ +# Debug Scripts for YOLO ONNX Detection + +This directory contains debugging tools for troubleshooting YOLO object detection issues. + +## Setup + +1. Create a Python virtual environment: +```bash +python -m venv debug_env +source debug_env/bin/activate # On Windows: debug_env\Scripts\activate +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +## Scripts + +### `debug_model_comparison.py` +Compares .pt model predictions with ONNX model outputs on the same static test image. +- Tests both PyTorch and ONNX models side-by-side +- Provides detailed debug output including preprocessing steps +- Useful for identifying model export issues + +### `test_static_onnx.py` +Tests ONNX model against static images to isolate Android capture issues. +- Bypasses Android screen capture pipeline +- Tests multiple ONNX model variants +- Good for validating model functionality + +### `export_model_variants.py` +Exports YOLO model variants with different NMS settings. +- Creates models with different confidence/IoU thresholds +- Useful for debugging detection sensitivity issues + +### `inspect_onnx_model.py` +Inspects ONNX model structure and metadata. +- Verifies class mappings and model architecture +- Helpful for debugging model export problems + +## Usage + +Place test images in `../../test_images/` and ensure model files are in `../../raw_models/`. + +Example: +```bash +cd tools/debug_scripts +source debug_env/bin/activate +python debug_model_comparison.py +``` \ No newline at end of file diff --git a/debug_model_comparison.py b/tools/debug_scripts/debug_model_comparison.py similarity index 100% rename from debug_model_comparison.py rename to tools/debug_scripts/debug_model_comparison.py diff --git a/tools/debug_scripts/export_model_variants.py b/tools/debug_scripts/export_model_variants.py new file mode 100644 index 0000000..e9b5291 --- /dev/null +++ b/tools/debug_scripts/export_model_variants.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +""" +Export YOLO model variants with different NMS settings for shiny icon debugging +""" + +from ultralytics import YOLO +import os + +def export_model_variants(): + model_path = "./raw_models/best.pt" + output_dir = "./raw_models/exports" + + # Create output directory + os.makedirs(output_dir, exist_ok=True) + + print(f"Loading model from: {model_path}") + model = YOLO(model_path) + + # Export configurations to test + configs = [ + { + "name": "no_nms", + "nms": False, + "simplify": True, + "description": "Raw model output without NMS - for debugging shiny detection" + }, + { + "name": "nms_relaxed", + "nms": True, + "max_det": 500, # Increase from default 300 + "conf": 0.1, # Lower confidence threshold + "simplify": True, + "description": "NMS with more detections and lower confidence" + }, + { + "name": "nms_very_relaxed", + "nms": True, + "max_det": 1000, # Even more detections + "conf": 0.05, # Very low confidence + "simplify": True, + "description": "NMS with maximum detections for rare classes" + } + ] + + for config in configs: + try: + print(f"\n๐Ÿš€ Exporting {config['name']}: {config['description']}") + + # Extract export parameters + export_params = {k: v for k, v in config.items() + if k not in ['name', 'description']} + + # Export model + exported_path = model.export( + format='onnx', + **export_params + ) + + # Move to organized location + output_file = os.path.join(output_dir, f"best_{config['name']}.onnx") + if os.path.exists(exported_path): + os.rename(exported_path, output_file) + print(f"โœ… Exported: {output_file}") + else: + print(f"โŒ Export failed for {config['name']}") + + except Exception as e: + print(f"โŒ Error exporting {config['name']}: {e}") + + print(f"\n๐Ÿ“ All exports saved to: {output_dir}") + print("\n๐Ÿ“‹ Summary:") + print("- best_no_nms.onnx: Raw 8400x99 output for debugging") + print("- best_nms_relaxed.onnx: NMS with 500 max detections") + print("- best_nms_very_relaxed.onnx: NMS with 1000 max detections") + print("\nNext: Copy desired model to app/src/main/assets/ as best.onnx") + +if __name__ == "__main__": + export_model_variants() \ No newline at end of file diff --git a/tools/debug_scripts/inspect_onnx_model.py b/tools/debug_scripts/inspect_onnx_model.py new file mode 100644 index 0000000..5cda823 --- /dev/null +++ b/tools/debug_scripts/inspect_onnx_model.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Inspect ONNX model structure to verify class mappings +""" + +import onnx +import numpy as np + +def inspect_onnx_model(model_path): + print(f"Inspecting ONNX model: {model_path}") + + try: + # Load the model + model = onnx.load(model_path) + + print(f"\n๐Ÿ“‹ Model Info:") + print(f"IR Version: {model.ir_version}") + print(f"Producer: {model.producer_name} {model.producer_version}") + + # Check inputs + print(f"\n๐Ÿ“ฅ Inputs:") + for input_info in model.graph.input: + print(f" {input_info.name}: {[d.dim_value for d in input_info.type.tensor_type.shape.dim]}") + + # Check outputs + print(f"\n๐Ÿ“ค Outputs:") + for output_info in model.graph.output: + shape = [d.dim_value for d in output_info.type.tensor_type.shape.dim] + print(f" {output_info.name}: {shape}") + + # For NMS models, try to interpret the output format + if len(shape) == 3 and shape[2] == 6: + print(f" โ†’ NMS format: [batch, {shape[1]} detections, 6 values (x,y,w,h,conf,class)]") + elif len(shape) == 3 and shape[1] > 90: + print(f" โ†’ Raw format: [batch, {shape[1]} channels, {shape[2]} anchors]") + print(f" โ†’ Channels: 4 coords + {shape[1]-4} classes") + + # Check for any metadata about classes + print(f"\n๐Ÿท๏ธ Metadata:") + for prop in model.metadata_props: + print(f" {prop.key}: {prop.value}") + + print(f"\n๐Ÿ” Model Summary: {len(model.graph.node)} nodes, {len(model.graph.initializer)} initializers") + + except Exception as e: + print(f"โŒ Error inspecting model: {e}") + +if __name__ == "__main__": + models_to_check = [ + "app/src/main/assets/best.onnx", + "raw_models/exports/best_no_nms.onnx", + "raw_models/exports/best_nms_relaxed.onnx", + "raw_models/exports/best_nms_very_relaxed.onnx" + ] + + for model_path in models_to_check: + try: + inspect_onnx_model(model_path) + print("\n" + "="*60 + "\n") + except FileNotFoundError: + print(f"โš ๏ธ Model not found: {model_path}\n") \ No newline at end of file diff --git a/tools/debug_scripts/requirements.txt b/tools/debug_scripts/requirements.txt new file mode 100644 index 0000000..8cd6af7 --- /dev/null +++ b/tools/debug_scripts/requirements.txt @@ -0,0 +1,6 @@ +ultralytics>=8.0.0 +opencv-python>=4.5.0 +onnxruntime>=1.15.0 +onnx>=1.14.0 +numpy>=1.21.0 +Pillow>=8.0.0 \ No newline at end of file diff --git a/test_static_onnx.py b/tools/debug_scripts/test_static_onnx.py similarity index 100% rename from test_static_onnx.py rename to tools/debug_scripts/test_static_onnx.py