///|
/// PP-OCR character dictionary. Class 0 is the CTC blank and is not stored.
/// Class `k` is line `k - 1`.
pub struct Charset {
chars : Array[String]
} derive(Debug, Eq)
///|
pub extend Charset with Eq::{not_equal, equal}
///|
pub extend Charset with @debug.Debug::{to_repr}
///|
fn strip_cr(line : String) -> String {
if line.length() > 0 && line[line.length() - 1] == '\r' {
line[:line.length() - 1].to_owned()
} else {
line
}
}
///|
/// Parse a dictionary file. A trailing newline does not add an empty class.
pub fn Charset::from_text(text : String) -> Charset {
let collected : Array[String] = []
for part in text.split("\n") {
collected.push(strip_cr(part.to_owned()))
}
let mut limit = collected.length()
if limit > 0 && collected[limit - 1].length() == 0 {
limit = limit - 1
}
let chars : Array[String] = []
for index in 0.. Charset {
let parsed = Charset::from_text(text)
let count = parsed.chars.length()
if count > 0 && parsed.chars[count - 1] == " " {
parsed
} else {
parsed.chars.push(" ")
parsed
}
}
///|
/// Map a CTC class to its character. Blank and out-of-range classes are `None`.
pub fn Charset::glyph(self : Charset, class : Int32) -> String? {
if class <= 0 {
None
} else {
let index = class - 1
if index >= self.chars.length() {
None
} else {
Some(self.chars[index])
}
}
}
///|
/// Greedy CTC text and the mean probability of the kept characters.
pub(all) struct CtcText {
text : String
confidence : Double
} derive(Debug, Eq, ToJson)
///|
pub extend CtcText with Eq::{not_equal, equal}
///|
pub extend CtcText with ToJson::{to_json}
///|
pub extend CtcText with @debug.Debug::{to_repr}
///|
fn step_confidence(
logits : ArrayView[Float],
offset : Int32,
classes : Int32,
best : Int32,
) -> Double {
let mut sum = 0.0
let mut negative = false
let mut peak = logits[offset].to_double()
for class in 0.. peak {
peak = value
}
sum = sum + value
}
let chosen = logits[offset + best].to_double()
if !negative && (if sum > 1.0 { sum - 1.0 } else { 1.0 - sum }) <= 0.05 {
chosen
} else {
let mut denom = 0.0
for class in 0.. String raise OcrError {
ctc_decode(logits, time~, classes~, charset~).text
}
///|
/// Greedy CTC decode with a confidence in `0.0..=1.0` when the row is a
/// probability distribution or a softmax of logits.
pub fn ctc_decode(
logits : ArrayView[Float],
time~ : Int32,
classes~ : Int32,
charset~ : Charset,
) -> CtcText raise OcrError {
if time < 0 || classes <= 0 {
raise OcrError::Model("ctc shape is invalid")
}
if logits.length() != time * classes {
raise OcrError::Model("ctc logits length does not match time*classes")
}
let builder = StringBuilder()
let mut previous = -1
let mut score_sum = 0.0
let mut score_count = 0
for step in 0..