BlockGrouper.kt
40 lignes · 1631 octets
package fr.ebii.card2vcf.ocr /** * Regroupe les lignes OCR en blocs spatiaux : une ligne rejoint le bloc le plus * proche qui la chevauche horizontalement (même colonne) si l'écart vertical * reste sous [GAP_FACTOR] × hauteur de ligne médiane. La médiane rend le seuil * robuste aux lignes géantes (logos). O(n × blocs), n ≈ 10-40 lignes. */ object BlockGrouper { private const val GAP_FACTOR = 1.8 fun group(lines: List<OcrLine>): List<OcrBlock> { if (lines.isEmpty()) return emptyList() val sorted = lines.sortedWith(compareBy({ it.box.top }, { it.box.left })) val maxGap = medianHeight(sorted) * GAP_FACTOR val blocks = mutableListOf<MutableList<OcrLine>>() for (line in sorted) { val candidate = blocks .filter { block -> val blockBox = block.map { it.box }.reduce(OcrBox::union) blockBox.overlapsHorizontally(line.box) && line.box.top - blockBox.bottom <= maxGap } .maxByOrNull { block -> block.maxOf { it.box.bottom } } if (candidate != null) candidate += line else blocks += mutableListOf(line) } return blocks .map { OcrBlock(it) } .sortedWith(compareBy({ it.box().top }, { it.box().left })) } private fun medianHeight(lines: List<OcrLine>): Double { val heights = lines.map { it.box.height }.sorted() val mid = heights.size / 2 return if (heights.size % 2 == 1) heights[mid].toDouble() else (heights[mid - 1] + heights[mid]) / 2.0 } }
GitRust