DimoCardOcrInstrumentedTest.kt 94 lignes · 4208 octets
package fr.ebii.card2vcf.ocr

import android.graphics.BitmapFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import fr.ebii.card2vcf.scan.ImageOrientation
import fr.ebii.card2vcf.scan.OpenCvScanEngine
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith

/**
 * Test instrumenté générique : JPEG brut + EXIF → orientation → scan → OCR.
 * Couvre le chemin réel utilisateur (photo téléphone avec EXIF Orientation=6).
 */
@RunWith(AndroidJUnit4::class)
class DimoCardOcrInstrumentedTest {

    @Test
    fun scanEtOcrAvecCorrectionExif() {
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        OpenCvScanEngine.ensureOpenCv()
        val assets = InstrumentationRegistry.getInstrumentation().context.assets

        // JPEG brut (avec EXIF 6), pas la version déjà orientée.
        val jpeg = assets.open("cards/dimo_genoux.jpg").use { it.readBytes() }
        val oriented = ImageOrientation.fromJpegBytes(jpeg)!!
        assertTrue("EXIF doit corriger la photo dimo", oriented.fromExif)
        assertEquals(90, oriented.appliedRotationDegrees)

        val scanned = OpenCvScanEngine().scan(oriented.bitmap)
        val ocr = TesseractOcrEngine(context).recognize(scanned.bitmap)

        println("=== OCR RAW ===\n${ocr.rawText}")
        println("=== phones=${ocr.phones}")
        println("=== emails=${ocr.emails}")
        println("=== urls=${ocr.urls}")

        assertTrue("texte OCR trop court: ${ocr.rawText.length}", ocr.rawText.length > 20)
        assertTrue(
            "email attendu absent: ${ocr.emails}",
            ocr.emails.any { it.contains("dimosoftware", ignoreCase = true) }
                || ocr.rawText.contains("dimosoftware", ignoreCase = true),
        )

        // Extraction spatiale (ResultIterator) : lignes avec boîtes cohérentes.
        assertTrue("spatialLines attendues", ocr.spatialLines.isNotEmpty())
        assertTrue(
            "boîtes invalides: ${ocr.spatialLines.map { it.box }}",
            ocr.spatialLines.all { it.box.height > 0 && it.box.width > 0 },
        )
        assertTrue(
            "confiances hors bornes",
            ocr.spatialLines.all { it.confidence in 0f..100f },
        )
    }

    /** Comparaison A/B des modes de segmentation — lire les scores dans le stdout du test. */
    @Test
    fun comparePsmAutoEtSparseText() {
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        OpenCvScanEngine.ensureOpenCv()
        val assets = InstrumentationRegistry.getInstrumentation().context.assets
        val jpeg = assets.open("cards/dimo_genoux.jpg").use { it.readBytes() }
        val oriented = ImageOrientation.fromJpegBytes(jpeg)!!
        val scanned = OpenCvScanEngine().scan(oriented.bitmap)

        val modes = listOf(
            "PSM_AUTO" to com.googlecode.tesseract.android.TessBaseAPI.PageSegMode.PSM_AUTO,
            "PSM_SPARSE_TEXT" to com.googlecode.tesseract.android.TessBaseAPI.PageSegMode.PSM_SPARSE_TEXT,
        )
        for ((nom, mode) in modes) {
            val t0 = android.os.SystemClock.elapsedRealtime()
            val ocr = TesseractOcrEngine(context, pageSegMode = mode).recognize(scanned.bitmap)
            val duree = android.os.SystemClock.elapsedRealtime() - t0
            println(
                "=== $nom : score=${OcrOrientation.score(ocr)} durée=${duree}ms " +
                    "phones=${ocr.phones} emails=${ocr.emails} lignes=${ocr.lines.size}",
            )
        }
    }

    @Test
    fun fallbackCameraXSiExifNormal() {
        val assets = InstrumentationRegistry.getInstrumentation().context.assets
        // Version déjà orientée : EXIF normal → pas de rotation EXIF
        val jpeg = assets.open("cards/dimo_genoux_oriented.jpg").use { it.readBytes() }
        val withCam = ImageOrientation.fromJpegBytes(jpeg, cameraRotationDegrees = 0)!!
        assertEquals(0, withCam.appliedRotationDegrees)
        val bmp = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.size)!!
        assertEquals(bmp.width, withCam.bitmap.width)
    }
}