Skip to content

Commit 363e581

Browse files
authored
ADFA-3757 | Fix fuzzy attribute parsing and OCR text assembly (#1233)
* fix(ocr): improve fuzzy attribute parsing and text assembly Fix dimension truncation, enhance drawable parsing, add fuzzy aliases, and use tolerance-based text joining to prevent attribute mangling. * refactor: add validations to the attribute keys and improve code readability
1 parent e3cba50 commit 363e581

3 files changed

Lines changed: 69 additions & 19 deletions

File tree

‎cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/FuzzyAttributeParser.kt‎

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ object FuzzyAttributeParser {
2626
TEXT("android:text", listOf("text")),
2727
HINT("android:hint", listOf("hint")),
2828
BACKGROUND("android:background", listOf("background", "bg"), ValueType.COLOR),
29-
BACKGROUND_TINT("app:backgroundTint", listOf("backgroundtint", "background_tint"), ValueType.COLOR),
30-
SRC("android:src", listOf("src", "scr"), ValueType.DRAWABLE),
29+
BACKGROUND_TINT("app:backgroundTint", listOf("backgroundtint", "background_tint", "bg_tint"), ValueType.COLOR),
30+
SRC("android:src", listOf("src", "scr", "sre", "5rc"), ValueType.DRAWABLE),
3131
CONTENT_DESCRIPTION("android:contentDescription", listOf("contentdescription", "content_description")),
3232

3333
TEXT_SIZE("android:textSize", listOf("textsize", "text_size"), ValueType.SP_DIMENSION),
@@ -74,7 +74,7 @@ object FuzzyAttributeParser {
7474
LAYOUT_MARGIN_RIGHT("android:layout_marginRight", listOf("layout_marginright", "layout_margin_right", "margin_right"), ValueType.DIMENSION),
7575

7676
LAYOUT_WEIGHT("android:layout_weight", listOf("layout_weight", "weight"), ValueType.FLOAT),
77-
LAYOUT_GRAVITY("android:layout_gravity", listOf("layout_gravity")),
77+
LAYOUT_GRAVITY("android:layout_gravity", listOf("layout_gravity", "layaut_gravity")),
7878
GRAVITY("android:gravity", listOf("gravity")),
7979
ORIENTATION("android:orientation", listOf("orientation")),
8080

@@ -150,6 +150,7 @@ object FuzzyAttributeParser {
150150
private val ocrLetterZToTwoRegex = Regex("[zZ]")
151151
private val ocrLetterSToFiveRegex = Regex("[sS]")
152152
private val ocrLetterBToSixRegex = Regex("[bB]")
153+
private val ocrDenoiseRegex = Regex("inm|rn|wm|nm")
153154

154155
private val matchKeywords = setOf("match", "parent")
155156
private val wrapKeywords = setOf("wrap", "content", "wrapcan")
@@ -419,7 +420,7 @@ object FuzzyAttributeParser {
419420
.replace(Regex("op$"), "dp")
420421
.replace(Regex("olp$"), "dp")
421422

422-
val numericString = fixedUnit.replace(Regex("[a-z]+$"), "")
423+
val numericString = fixedUnit.replace("_", "")
423424
val numericPart = extractOcrNumber(numericString)
424425

425426
if (numericPart != null) return "${numericPart}dp"
@@ -432,7 +433,7 @@ object FuzzyAttributeParser {
432433
.replace(" ", "")
433434
.replace(Regex("5p$"), "sp")
434435

435-
val numericString = fixedUnit.replace(Regex("[a-z]+$"), "")
436+
val numericString = fixedUnit.replace("_", "")
436437
val numericPart = extractOcrNumber(numericString)
437438

438439
if (numericPart != null) return "${numericPart}sp"
@@ -457,22 +458,30 @@ object FuzzyAttributeParser {
457458
}
458459

459460
private fun cleanId(value: String): String {
460-
return value.lowercase()
461+
return denoiseOcrIdentifier(value.lowercase())
461462
.replace(nonAlphanumericRegex, "_")
462463
.replace(multipleUnderscoresRegex, "_")
463464
.trimEnd('_')
464465
.trimStart('_')
465466
}
466467

467-
private fun denoiseOcrIdentifier(value: String): String =
468-
value.replace("rn", "m")
469-
.replace("wm", "m")
468+
private fun denoiseOcrIdentifier(value: String): String {
469+
return value.replace(ocrDenoiseRegex) { matchResult ->
470+
when (matchResult.value) {
471+
"inm" -> "im"
472+
else -> "m"
473+
}
474+
}
475+
}
470476

471477
private fun cleanDrawable(value: String): String {
472478
if (value.startsWith("@drawable/")) return value
473-
val cleaned = value.replace(Regex("[^a-zA-Z0-9_.]"), "")
474-
.substringBeforeLast('.')
475-
.lowercase()
479+
480+
var cleaned = value.lowercase()
481+
.replace(Regex("\\.(png|jpg|jpeg|webp|xml|svg)$"), "")
482+
483+
cleaned = cleaned.replace(Regex("[^a-z0-9_]"), "")
484+
476485
return "@drawable/${denoiseOcrIdentifier(cleaned)}"
477486
}
478487

‎cv-image-to-xml/src/main/java/org/appdevforall/codeonthego/computervision/domain/RegionOcrProcessor.kt‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlinx.coroutines.coroutineScope
99
import org.appdevforall.codeonthego.computervision.data.source.OcrSource
1010
import org.appdevforall.codeonthego.computervision.domain.model.DetectionResult
1111
import org.appdevforall.codeonthego.computervision.utils.BitmapUtils
12+
import org.appdevforall.codeonthego.computervision.utils.OcrTextAssembler
1213

1314
class RegionOcrProcessor(
1415
private val ocrSource: OcrSource,
@@ -69,12 +70,8 @@ class RegionOcrProcessor(
6970
try {
7071
crop = BitmapUtils.cropRegion(bitmap, component.boundingBox, componentPadding)
7172
preprocessed = BitmapUtils.preprocessForOcr(crop)
72-
val text = ocrSource.recognizeText(preprocessed)
73-
.getOrNull()
74-
?.joinToString(" ") { it.text }
75-
?.replace("\n", " ")
76-
?.trim()
77-
?: ""
73+
val textBlocks = ocrSource.recognizeText(preprocessed).getOrNull()
74+
val text = textBlocks?.let { OcrTextAssembler.extractTextWithTolerance(it) } ?: ""
7875
component.copy(text = text)
7976
} finally {
8077
preprocessed?.recycle()
@@ -124,7 +121,7 @@ class RegionOcrProcessor(
124121
),
125122
label = "text",
126123
score = 0.99f,
127-
text = line.text.trim(),
124+
text = OcrTextAssembler.joinElementsWithTolerance(line),
128125
isYolo = false
129126
)
130127
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.appdevforall.codeonthego.computervision.utils
2+
3+
import com.google.mlkit.vision.text.Text
4+
5+
object OcrTextAssembler {
6+
const val DEFAULT_SPACE_GAP_TOLERANCE_PX = 15f
7+
8+
fun extractTextWithTolerance(
9+
textBlocks: List<Text.TextBlock>,
10+
maxSpaceGap: Float = DEFAULT_SPACE_GAP_TOLERANCE_PX
11+
): String {
12+
return textBlocks.joinToString(" ") { block ->
13+
block.lines.joinToString(" ") { line ->
14+
joinElementsWithTolerance(line, maxSpaceGap)
15+
}
16+
}.trim()
17+
}
18+
19+
fun joinElementsWithTolerance(line: Text.Line, maxSpaceGap: Float = DEFAULT_SPACE_GAP_TOLERANCE_PX): String {
20+
val elements = line.elements.sortedBy { it.boundingBox?.left ?: 0 }
21+
if (elements.isEmpty()) return ""
22+
23+
val builder = StringBuilder()
24+
var prevRight = elements.first().boundingBox?.right ?: 0
25+
builder.append(elements.first().text)
26+
27+
for (i in 1 until elements.size) {
28+
val current = elements[i]
29+
val currentBox = current.boundingBox
30+
31+
if (currentBox != null) {
32+
val gap = currentBox.left - prevRight
33+
if (gap > maxSpaceGap) {
34+
builder.append(" ")
35+
}
36+
prevRight = currentBox.right
37+
} else {
38+
builder.append(" ")
39+
}
40+
builder.append(current.text)
41+
}
42+
return builder.toString().trim()
43+
}
44+
}

0 commit comments

Comments
 (0)