From b001537103af607630dc28b69d005487064fbd67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Ckamon?= <“kamon.sourabie@imt-atlantique.net”>
Date: Tue, 7 Jan 2025 01:22:02 +0100
Subject: [PATCH] amelioration de l'affichage des cadres et noms des especes
 avec un leger bug dans le cas d'especes non reconnues

---
 .idea/compiler.xml                            |   2 +-
 .idea/deploymentTargetSelector.xml            |   4 +-
 .idea/misc.xml                                |   3 +-
 .../example/myapplication/MainActivity.java   |  39 ---
 .../myapplication/TestModeleTflite.java       | 225 ++++++++++--------
 5 files changed, 132 insertions(+), 141 deletions(-)

diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b86273d..b589d56 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="21" />
+    <bytecodeTargetLevel target="17" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index bcc86d0..7762630 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,10 +4,10 @@
     <selectionStates>
       <SelectionState runConfigName="app">
         <option name="selectionMode" value="DROPDOWN" />
-        <DropdownSelection timestamp="2025-01-04T02:38:58.283128700Z">
+        <DropdownSelection timestamp="2025-01-07T00:07:51.508741600Z">
           <Target type="DEFAULT_BOOT">
             <handle>
-              <DeviceId pluginId="PhysicalDevice" identifier="serial=R39M30F5B2T" />
+              <DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\Prosp\.android\avd\Pixel_8a_API_35.avd" />
             </handle>
           </Target>
         </DropdownSelection>
diff --git a/.idea/misc.xml b/.idea/misc.xml
index b2c751a..0ad17cb 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="ExternalStorageConfigurationManager" enabled="true" />
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
diff --git a/app/src/main/java/com/example/myapplication/MainActivity.java b/app/src/main/java/com/example/myapplication/MainActivity.java
index 12f9d38..2393421 100644
--- a/app/src/main/java/com/example/myapplication/MainActivity.java
+++ b/app/src/main/java/com/example/myapplication/MainActivity.java
@@ -30,7 +30,6 @@ public class MainActivity extends AppCompatActivity {
 
         setContentView(R.layout.activity_main);
 
-
         int delayMillis = 3000;
 
         new Handler().postDelayed(new Runnable() {
@@ -46,44 +45,6 @@ public class MainActivity extends AppCompatActivity {
 
     }
 
-    public void testPredict(View v){
-        TextView textView = findViewById(R.id.testInference);
-        TestModeleTflite testModeleTflite = null;
-        try {
-            testModeleTflite = new TestModeleTflite(this);
-            String currentDir = System.getProperty("user.dir");
-            String imagePath = "mouette_rieuse.jpg";
-            Bitmap inputImage = TestModeleTflite.loadImage(imagePath, this);
-
-            if (inputImage == null) {
-                System.out.println("Failed to load the image. Check the file path.");
-                return;
-            }
-
-            // Perform prediction
-            TensorBuffer outputBuffer = testModeleTflite.predict(inputImage);
-
-            // Process the output to get bounding boxes
-            List<BoundingBox> boxes = testModeleTflite.processOutput(outputBuffer);
-
-            if (boxes == null || boxes.isEmpty()) {
-                textView.setText("No objects detected in the image.");
-            } else {
-                // Output results to the console
-                for (BoundingBox box : boxes) {
-                    System.out.println("Class: " + box.clsName);
-                    System.out.println("Confidence: " + box.cnf);
-                    System.out.println("----------------------------");
-                    textView.setText("Class: " + box.clsName + " Confidence : " + box.cnf);
-                }
-
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-
-       //textView.setText("bonjour");
-    }
 
 
 }
diff --git a/app/src/main/java/com/example/myapplication/TestModeleTflite.java b/app/src/main/java/com/example/myapplication/TestModeleTflite.java
index 1cd882e..6b10aca 100644
--- a/app/src/main/java/com/example/myapplication/TestModeleTflite.java
+++ b/app/src/main/java/com/example/myapplication/TestModeleTflite.java
@@ -4,7 +4,6 @@ import static android.graphics.Paint.*;
 
 import android.content.Context;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -20,8 +19,6 @@ import org.tensorflow.lite.support.image.TensorImage;
 import org.tensorflow.lite.support.tensorbuffer.TensorBuffer;
 
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -31,6 +28,10 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * A class for performing species recognition using a TensorFlow Lite model.
+ * Handles model loading, inference, output processing, and annotation of input images.
+ */
 public class TestModeleTflite {
     private static final String MODEL_PATH = "model_float32.tflite";
     private static final String LABEL_PATH = "labels.txt";
@@ -50,16 +51,22 @@ public class TestModeleTflite {
     private List<String> labels = new ArrayList<>();
     private ImageProcessor imageProcessor;
 
+    /**
+     * Initializes the TensorFlow Lite model interpreter and loads associated resources.
+     *
+     * @param context The application context for accessing assets.
+     * @throws IOException If the model or label files cannot be loaded.
+     */
     public TestModeleTflite(Context context) throws IOException {
-        // Load the model
+        // Load the TensorFlow Lite model
         MappedByteBuffer model = FileUtil.loadMappedFile(context, MODEL_PATH);
 
-        // Set interpreter options
+        // Configure the interpreter with multithreading options
         Interpreter.Options options = new Interpreter.Options();
         options.setNumThreads(4);
         interpreter = new Interpreter(model, options);
 
-        // Get tensor shapes
+        // Extract tensor shapes from the model
         int[] inputShape = interpreter.getInputTensor(0).shape();
         int[] outputShape = interpreter.getOutputTensor(0).shape();
 
@@ -68,7 +75,7 @@ public class TestModeleTflite {
         numChannel = outputShape[1];
         numElements = outputShape[2];
 
-        // Load labels
+        // Load the label list
         try (InputStream inputStream = context.getAssets().open(LABEL_PATH);
              BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
             String line;
@@ -77,54 +84,95 @@ public class TestModeleTflite {
             }
         }
 
-        // Initialize image processor
+        // Configure preprocessing steps for the input image
         imageProcessor = new ImageProcessor.Builder()
                 .add(new NormalizeOp(INPUT_MEAN, INPUT_STANDARD_DEVIATION))
                 .add(new CastOp(INPUT_IMAGE_TYPE))
                 .build();
     }
 
+    /**
+     * Prepares an input image and performs inference using the TensorFlow Lite model.
+     *
+     * @param bitmap The input image to analyze.
+     * @return A TensorBuffer containing the model's raw output.
+     */
     public TensorBuffer predict(Bitmap bitmap) {
-        // Resize bitmap to model's input size
         Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, tensorWidth, tensorHeight, false);
 
-        // Preprocess input image
         TensorImage tensorImage = new TensorImage(DataType.FLOAT32);
         tensorImage.load(resizedBitmap);
         TensorImage processedImage = imageProcessor.process(tensorImage);
 
-        // Prepare output buffer
         TensorBuffer outputBuffer = TensorBuffer.createFixedSize(
                 new int[]{1, numChannel, numElements},
                 OUTPUT_IMAGE_TYPE
         );
 
-        // Run inference
         interpreter.run(processedImage.getBuffer(), outputBuffer.getBuffer());
-
         return outputBuffer;
     }
 
-    public List<String> getLabels() {
-        return labels;
-    }
-
-    public void close() {
-        if (interpreter != null) {
-            interpreter.close();
-            interpreter = null;
-        }
-    }
-
+    /**
+     * Processes the raw model output to generate bounding boxes for detected objects.
+     *
+     * @param outputBuffer The TensorBuffer containing raw model output.
+     * @return A list of bounding boxes for detected objects.
+     */
     public List<BoundingBox> processOutput(TensorBuffer outputBuffer) {
         float[] outputArray = outputBuffer.getFloatArray();
         return bestBox(outputArray);
     }
 
+    /**
+     * Annotates an input image with bounding boxes for detected objects.
+     *
+     * @param inputImage The original image to annotate.
+     * @param boxes The bounding boxes of detected objects.
+     * @return A new image with bounding boxes drawn.
+     */
     public Bitmap annotateImage(Bitmap inputImage, List<BoundingBox> boxes) {
         return drawBoundingBoxes(inputImage, boxes);
     }
 
+    /**
+     * Groups detections by class and constructs a structured output image and metadata.
+     *
+     * @param inputImage The input image for recognition.
+     * @return An OutputPredictedImage containing the annotated image and prediction results.
+     * @throws NoSpeciesRecognizedException If no objects are detected.
+     */
+    public OutputPredictedImage recognizeSpeciesClass(Bitmap inputImage) throws NoSpeciesRecognizedException {
+        Map<String, RecognizeSpecie> predictionResults = new HashMap<>();
+        TensorBuffer outputBuffer = predict(inputImage);
+        List<BoundingBox> boxes = processOutput(outputBuffer);
+
+        if (boxes != null) {
+            System.out.println("not empty");
+            for (BoundingBox box : boxes) {
+                predictionResults.computeIfAbsent(box.clsName, k -> new RecognizeSpecie(k, new ArrayList<>()))
+                        .getSpecieBoxes().add(box);
+            }
+            Bitmap annotatedImage = annotateImage(inputImage, boxes);
+            return new OutputPredictedImage(annotatedImage, predictionResults);
+        } else {
+            throw new NoSpeciesRecognizedException();
+        }
+    }
+
+    /**
+     * Releases the resources held by the TensorFlow Lite interpreter.
+     */
+    public void close() {
+        if (interpreter != null) {
+            interpreter.close();
+            interpreter = null;
+        }
+    }
+
+    /**
+     * Filters bounding boxes based on confidence and non-maximum suppression (NMS).
+     */
     private List<BoundingBox> bestBox(float[] array) {
         List<BoundingBox> boundingBoxes = new ArrayList<>();
 
@@ -161,26 +209,29 @@ public class TestModeleTflite {
             }
         }
 
-        if (boundingBoxes.isEmpty()) return null;
-
         return applyNMS(boundingBoxes);
     }
 
+    /**
+     * Applies non-maximum suppression (NMS) to eliminate overlapping boxes.
+     */
     private List<BoundingBox> applyNMS(List<BoundingBox> boxes) {
         List<BoundingBox> sortedBoxes = new ArrayList<>(boxes);
-        sortedBoxes.sort((b1, b2) -> Float.compare(b2.cnf, b1.cnf)); // Sort by confidence descending
+        sortedBoxes.sort((b1, b2) -> Float.compare(b2.cnf, b1.cnf));
         List<BoundingBox> selectedBoxes = new ArrayList<>();
 
         while (!sortedBoxes.isEmpty()) {
             BoundingBox first = sortedBoxes.remove(0);
             selectedBoxes.add(first);
-
             sortedBoxes.removeIf(nextBox -> calculateIoU(first, nextBox) >= IOU_THRESHOLD);
         }
 
         return selectedBoxes;
     }
 
+    /**
+     * Calculates the Intersection over Union (IoU) between two bounding boxes.
+     */
     private float calculateIoU(BoundingBox box1, BoundingBox box2) {
         float x1 = Math.max(box1.x1, box2.x1);
         float y1 = Math.max(box1.y1, box2.y1);
@@ -194,8 +245,10 @@ public class TestModeleTflite {
         return intersectionArea / (box1Area + box2Area - intersectionArea);
     }
 
-
-
+    /**
+     * Draws bounding boxes on an image.
+     */
+    /*
     private Bitmap drawBoundingBoxes(Bitmap bitmap, List<BoundingBox> boxes) {
         Bitmap mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
         Canvas canvas = new Canvas(mutableBitmap);
@@ -204,7 +257,7 @@ public class TestModeleTflite {
         paint.setColor(Color.RED);
         paint.setStyle(Style.STROKE);
         paint.setStrokeWidth(8f);
-
+        /*
         Paint textPaint = new Paint();
         textPaint.setColor(Color.WHITE);
         textPaint.setTextSize(40f);
@@ -218,89 +271,65 @@ public class TestModeleTflite {
                     box.y2 * mutableBitmap.getHeight()
             );
             canvas.drawRect(rect, paint);
-            canvas.drawText(box.clsName, rect.left, rect.bottom, textPaint);
-            //canvas.drawText(new StringBuilder().append("prob : ").append(String.format("%.2f", box.cnf)).toString(), rect.left, rect.top, textPaint);
+            //canvas.drawText(box.clsName, rect.left, rect.bottom, textPaint);
         }
 
         return mutableBitmap;
-    }
+    }*/
 
-    /**
-     * Loads an image from the specified file path and converts it to a Bitmap.
-     *
-     * @param fileName Path to the image file.
-     * @return Bitmap representation of the image or null if an error occurs.
-     */
-    public static Bitmap loadImage(String fileName, Context context) {
-        try (InputStream inputStream = context.getAssets().open(fileName)) {
-            return BitmapFactory.decodeStream(inputStream);
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
+    private Bitmap drawBoundingBoxes(Bitmap bitmap, List<BoundingBox> boxes) {
+        Bitmap mutableBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
+        Canvas canvas = new Canvas(mutableBitmap);
 
-    public Bitmap recognizeSpecies(Bitmap inputImage){
-        TensorBuffer outputBuffer = predict(inputImage);
-        List<BoundingBox> boxes = processOutput(outputBuffer);
-        return annotateImage(inputImage, boxes);
-    }
+        // Scale stroke width dynamically based on image resolution
+        float strokeWidth = Math.max(mutableBitmap.getWidth(), mutableBitmap.getHeight()) / 470.0f;
 
-    public OutputPredictedImage recognizeSpeciesClass(Bitmap inputImage) throws NoSpeciesRecognizedException {
-        //ArrayList<String> speciesNames = new ArrayList<>();
-        List<RecognizeSpecie> specieBoxes= new ArrayList<RecognizeSpecie>();
-        Map<String, RecognizeSpecie> predictionResults = new HashMap<>();
+        Paint paint = new Paint();
+        paint.setColor(Color.RED);
+        paint.setStyle(Style.STROKE);
+        paint.setStrokeWidth(strokeWidth); // Dynamically set stroke width
 
-        TensorBuffer outputBuffer = predict(inputImage);
-        List<BoundingBox> boxes = processOutput(outputBuffer);
-        if(boxes != null){
-            for(BoundingBox box:boxes){
-                if(predictionResults.containsKey(box.clsName)){
-                    predictionResults.get(box.clsName).getSpecieBoxes().add(box);
-                }else{
-                    predictionResults.put(box.clsName, new RecognizeSpecie(box.clsName, new ArrayList<BoundingBox>()));
-                    predictionResults.get(box.clsName).getSpecieBoxes().add(box);
-                }
+        Paint textPaint = new Paint();
+        textPaint.setColor(Color.WHITE);
+        textPaint.setStyle(Style.FILL);
 
-            }
-            Bitmap annotatedImage =  annotateImage(inputImage, boxes);
-            return new OutputPredictedImage(annotatedImage, predictionResults);
-        }else throw new NoSpeciesRecognizedException();
-    }
+        for (BoundingBox box : boxes) {
+            RectF rect = new RectF(
+                    box.x1 * mutableBitmap.getWidth(),
+                    box.y1 * mutableBitmap.getHeight(),
+                    box.x2 * mutableBitmap.getWidth(),
+                    box.y2 * mutableBitmap.getHeight()
+            );
 
+            // Draw the bounding box
+            canvas.drawRect(rect, paint);
 
-    public static void main(String [] args){
-    /*
-        TestModeleTflite testModeleTflite = new TestModeleTflite(this);
-        String imagePath = "../../res/drawable/bernache_cravant.jpg";
-        Bitmap inputImage = TestModeleTflite.loadImage(imagePath);
+            // Dynamically adjust text size to fit within the box
+            float boxWidth = rect.width();
+            float boxHeight = rect.height();
+            float maxTextSize = boxHeight * 0.2f; // Limit text size to a fraction of the box height
+            textPaint.setTextSize(maxTextSize);
 
-        if (inputImage == null) {
-            System.out.println("Failed to load the image. Check the file path.");
-            return;
+            // Measure text width and adjust if necessary
+            float textWidth = textPaint.measureText(box.clsName);
+            if (textWidth > boxWidth) {
+                textPaint.setTextSize(maxTextSize * (boxWidth / textWidth));
+            }
+
+            // Draw the text at the bottom of the box
+            canvas.drawText(box.clsName, rect.left, rect.bottom - 5, textPaint); // Adjust -5 for padding
         }
 
-        // Perform prediction
-        TensorBuffer outputBuffer = testModeleTflite.predict(inputImage);
+        return mutableBitmap;
+    }
 
-        // Process the output to get bounding boxes
-        List<BoundingBox> boxes = testModeleTflite.processOutput(outputBuffer);
 
-        if (boxes == null || boxes.isEmpty()) {
-            System.out.println("No objects detected in the image.");
-        } else {
-            // Output results to the console
-            for (BoundingBox box : boxes) {
-                System.out.println("Class: " + box.clsName);
-                System.out.println("Confidence: " + box.cnf);
-                System.out.println("----------------------------");
-            }
-        }*/
+
+    /**
+     * Main method for debugging or testing purposes.
+     */
+    public static void main(String[] args) {
         String currentDir = System.getProperty("user.dir");
         System.out.println("Current working directory: " + currentDir);
     }
-
-
-
 }
-
-- 
GitLab