Move SSD OCR candidate selection to typed Kotlin
ober
54e235b2c2e56f858110a22be153c5bfb8524f3a
--- a/.build.yml +++ b/.build.yml @@ -5,7 +5,7 @@ packages: - make=4.4.1-r4 sources: # Build dependency: full immutable commit, mirrored in dependencies.lock.json. - - "https://git.sr.ht/~lisp/jerboa#3b6f07f1a2810e03b9d6f489567f8819b54880b8" + - "https://git.sr.ht/~lisp/jerboa#d1dfda6682e7948e4f3e2c54029f01f69bd7109e" # The second source is the build subject selected by the SourceHut submitter. - https://git.sr.ht/~lisp/jerboa-android tasks: @@ -14,6 +14,6 @@ tasks: test "$(apk info -v chez-scheme)" = chez-scheme-10.3.0-r2 test "$(apk info -v git)" = git-2.54.0-r0 test "$(apk info -v make)" = make-4.4.1-r4 - test "$(git -C ../jerboa rev-parse HEAD)" = 3b6f07f1a2810e03b9d6f489567f8819b54880b8 - test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 8b3a95435b0fd0966103db550fb4fbe5a0bc5597 + test "$(git -C ../jerboa rev-parse HEAD)" = d1dfda6682e7948e4f3e2c54029f01f69bd7109e + test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 785fcb008694e534f1a7fb53dacd7893f5f4e04d JERBOA="chez --libdirs .:../jerboa/lib --script" make test --- a/dependencies.lock.json +++ b/dependencies.lock.json @@ -11,8 +11,8 @@ "generator_runtime": { "name": "jerboa", "repository": "https://git.sr.ht/~lisp/jerboa", - "commit": "3b6f07f1a2810e03b9d6f489567f8819b54880b8", - "tree": "8b3a95435b0fd0966103db550fb4fbe5a0bc5597" + "commit": "d1dfda6682e7948e4f3e2c54029f01f69bd7109e", + "tree": "785fcb008694e534f1a7fb53dacd7893f5f4e04d" }, "assurance_tools": { "osv_scanner": { --- a/scripts/verify-supply-chain.sh +++ b/scripts/verify-supply-chain.sh @@ -3,8 +3,8 @@ set -eu repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P) lock="$repo/dependencies.lock.json" -jerboa_commit=3b6f07f1a2810e03b9d6f489567f8819b54880b8 # gitsafe:ignore -jerboa_tree=8b3a95435b0fd0966103db550fb4fbe5a0bc5597 # gitsafe:ignore +jerboa_commit=d1dfda6682e7948e4f3e2c54029f01f69bd7109e # gitsafe:ignore +jerboa_tree=785fcb008694e534f1a7fb53dacd7893f5f4e04d # gitsafe:ignore gradle_sha=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 jdk_macos_sha=8fa1eff40bb637a33613b2ccb8b12c70dc3661cc22cf8e784943715769a05336 jdk_linux_sha=d8afc263758141a66e0e3aafc321e783f7016696f4eaea067d340a269037d331 --- a/templates/ssd-review.ss +++ b/templates/ssd-review.ss @@ -1447,6 +1447,78 @@ (and (equal? status "candidate") (string-starts-with? notes "Guess:"))))))))) + (typed-kotlin-file "com/sfb/ssdreview/OcrCandidateSelect.kt" + (kotlin-imports (android content Context)) + (typed-library (com sfb ssdreview) + (export bestOcrCandidate) + (type Context) + (type Int32) + (type Float32) + (extern (boxTypesResolve (context : Context) (raw : String)) : (Nullable BoxType) + (kotlin-call BoxTypes resolve)) + (def (ocrPatternMatches (pattern : OcrPattern) (text : String)) : Bool + (for/fold ((matched #f)) + ((i (in-range (int32 0) (list-size (OcrPattern-tokens pattern))))) + (or matched + (ocrTokenMatches text (list-ref (OcrPattern-tokens pattern) i))))) + (def (boxTypeNameOr (boxType : (Nullable BoxType)) (fallback : String)) : String + (if (nullable-null? boxType) + fallback + (BoxType-name (nullable-get boxType)))) + (def (boxTypeIdOrEmpty (boxType : (Nullable BoxType))) : String + (if (nullable-null? boxType) + "" + (BoxType-id (nullable-get boxType)))) + (def (ocrCandidateForPattern (context : Context) + (pattern : OcrPattern) + (text : String)) : OcrCandidate + (let ((resolved (boxTypesResolve context (OcrPattern-label pattern)))) + (make-OcrCandidate + (boxTypeNameOr resolved (OcrPattern-label pattern)) + (boxTypeIdOrEmpty resolved) + (OcrPattern-confidence pattern) + (string-append "nearby text: " (string-take text (int32 80)))))) + (def (betterOcrCandidate (current : (Nullable OcrCandidate)) + (candidate : OcrCandidate)) : (Nullable OcrCandidate) + (if (nullable-null? current) + (nullable-some candidate) + (if (> (OcrCandidate-confidence candidate) + (OcrCandidate-confidence (nullable-get current))) + (nullable-some candidate) + current))) + (def (considerOcrPattern (context : Context) + (text : String) + (race : String) + (seen : (MutableSet String)) + (best : (Nullable OcrCandidate)) + (pattern : OcrPattern)) : (Nullable OcrCandidate) + (if (or (not (ocrPatternMatches pattern text)) + (suppressedByRace race (OcrPattern-label pattern))) + best + (let ((candidate (ocrCandidateForPattern context pattern text))) + (let ((key (compact (OcrCandidate-label candidate)))) + (if (set-contains? seen key) + best + (begin + (mutable-set-add! seen key) + (betterOcrCandidate best candidate))))))) + (def (bestOcrCandidate (context : Context) + (patterns : (List OcrPattern)) + (text : String) + (race : String)) : (Nullable OcrCandidate) + (if (string-blank? text) + (nullable-none OcrCandidate) + (let ((seen (mutable-set-empty String))) + (for/fold ((best (nullable-none OcrCandidate))) + ((i (in-range (int32 0) (list-size patterns)))) + (considerOcrPattern + context + text + race + seen + best + (list-ref patterns i)))))))) + (typed-kotlin-file "com/sfb/ssdreview/LearnedSuppression.kt" (kotlin-imports (org json JSONObject)) (typed-library (com sfb ssdreview) @@ -4765,7 +4837,7 @@ " session.groups.forEach { group ->" " if (!shouldReplaceOcrLabel(group.label, group.status, group.notes)) return@forEach" " val text = nearbyText(session.ocrWords, group.bbox, session.imageWidth, session.imageHeight)" - " val best = ocrCandidates(context, text, race).firstOrNull() ?: return@forEach" + " val best = bestOcrCandidate(context, patterns, text, race) ?: return@forEach" " if (bankCompatible(group.label, group.boxTypeId) && !bankCompatible(best.label, best.boxTypeId)) return@forEach" " applyOcrCandidateToGroup(group, best)" " applied += 1" @@ -4773,23 +4845,6 @@ " return applied" " }" "" - " private fun ocrCandidates(context: Context, text: String, race: String): List<OcrCandidate> {" - " if (text.isBlank()) return emptyList()" - " val seen = mutableSetOf<String>()" - " val candidates = mutableListOf<OcrCandidate>()" - " patterns.forEach { pattern ->" - " if (!pattern.tokens.any { token -> ocrTokenMatches(text, token) }) return@forEach" - " if (suppressedByRace(race, pattern.label)) return@forEach" - " val resolved = BoxTypes.resolve(context, pattern.label)" - " val label = resolved?.name ?: pattern.label" - " val key = compact(label)" - " if (seen.add(key)) {" - " candidates.add(OcrCandidate(label, resolved?.id ?: \"\", pattern.confidence, \"nearby text: ${text.take(80)}\"))" - " }" - " }" - " return candidates.sortedByDescending { it.confidence }" - " }" - "" "}" )) (kotlin-file-lines "com/sfb/ssdreview/SsdReviewView.kt"