Move SSD learned guess selection to typed Kotlin
ober
c887b5d9a1721826f35a60677f41e7f182d7fcee
--- a/templates/ssd-review.ss +++ b/templates/ssd-review.ss @@ -2107,6 +2107,68 @@ #t) #f)))) + (typed-kotlin-file "com/sfb/ssdreview/LearnedGuessSelect.kt" + (kotlin-imports (org json JSONArray) (org json JSONObject)) + (typed-library (com sfb ssdreview) + (export make-LearnedGuessMatch LearnedGuessMatch? + LearnedGuessMatch-example LearnedGuessMatch-score + bestLearnedGuessForGroup) + (type JSONArray) + (type JSONObject) + (type Float32) + (type Int32) + (record LearnedGuessMatch + ((example : JSONObject) + (score : Float32))) + (def (betterLearnedGuess (candidate : LearnedGuessMatch) + (current : (Nullable LearnedGuessMatch))) : (Nullable LearnedGuessMatch) + (if (nullable-null? current) + (nullable-some candidate) + (if (< (LearnedGuessMatch-score candidate) + (LearnedGuessMatch-score (nullable-get current))) + (nullable-some candidate) + current))) + (def (learnedGuessCandidate (session : SsdSession) + (group : SsdGroup) + (features : JSONObject) + (example : JSONObject)) : (Nullable LearnedGuessMatch) + (let ((label (json-object-opt-string example "label"))) + (if (or (equal? (json-object-opt-string example "source_key") + (SsdSession-sourceKey session)) + (or (string-blank? label) + (or (suppressedLearnedExample session example) + (suppressedLearnedForGroup group example)))) + (nullable-none LearnedGuessMatch) + (let ((candidateFeatures (json-object-opt-json-object example "features"))) + (if (nullable-null? candidateFeatures) + (nullable-none LearnedGuessMatch) + (nullable-some + (make-LearnedGuessMatch + example + (featureDistance features (nullable-get candidateFeatures))))))))) + (def (bestLearnedGuessForGroup (session : SsdSession) + (examples : JSONArray) + (group : SsdGroup)) : (Nullable LearnedGuessMatch) + (let ((features + (groupFeaturesFromGroup + group + (SsdSession-imageWidth session) + (SsdSession-imageHeight session)))) + (for/fold ((best (nullable-none LearnedGuessMatch))) + ((i (in-range (int32 0) (json-array-length examples)))) + (let ((example (json-array-opt-json-object examples i))) + (if (nullable-null? example) + best + (let ((candidate + (learnedGuessCandidate + session + group + features + (nullable-get example)))) + (if (nullable-null? candidate) + best + (betterLearnedGuess (nullable-get candidate) best)))))))))) + (typed-kotlin-file "com/sfb/ssdreview/OcrGeometry.kt" (typed-library (com sfb ssdreview) (export floatArrayGetOrZero @@ -6406,25 +6468,12 @@ " var applied = 0" " session.groups.forEach { group ->" " if (group.label.isNotBlank() && !group.label.endsWith(\"?\")) return@forEach" - " val features = groupFeaturesFromGroup(group, session.imageWidth, session.imageHeight)" - " var best: JSONObject? = null" - " var bestScore = Float.POSITIVE_INFINITY" - " for (i in 0 until examples.length()) {" - " val example = examples.getJSONObject(i)" - " if (example.optString(\"source_key\") == session.sourceKey) continue" - " val label = example.optString(\"label\")" - " if (label.isBlank() || suppressedLearnedExample(session, example) || suppressedLearnedForGroup(group, example)) continue" - " val score = featureDistance(features, example.optJSONObject(\"features\") ?: continue)" - " if (score < bestScore) {" - " bestScore = score" - " best = example" - " }" - " }" - " val match = best" - " if (match != null && bestScore <= 0.22f) {" - " group.label = \"${match.optString(\"label\")}?\"" - " group.boxTypeId = match.optString(\"box_type_id\", group.boxTypeId)" - " group.notes = \"Learned guess from ${match.optString(\"truth_path\")} score %.3f\".format(bestScore)" + " val match = bestLearnedGuessForGroup(session, examples, group)" + " if (match != null && match.score <= 0.22f) {" + " val example = match.example" + " group.label = \"${example.optString(\"label\")}?\"" + " group.boxTypeId = example.optString(\"box_type_id\", group.boxTypeId)" + " group.notes = \"Learned guess from ${example.optString(\"truth_path\")} score %.3f\".format(match.score)" " applied += 1" " }" " }"