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) } }
GitRust