///|
/// NCHW float tensor without the leading batch axis. `values` is channel-major
/// RGB, length `3 * height * width`.
pub(all) struct ChwTensor {
height : Int32
width : Int32
values : Array[Float]
} derive(Eq, Debug)
///|
pub extend ChwTensor with Eq::{not_equal, equal}
///|
pub extend ChwTensor with @debug.Debug::{to_repr}
///|
/// Detection input plus the scale from the resized map back to the source.
pub(all) struct DetTensor {
tensor : ChwTensor
ratio_w : Double
ratio_h : Double
} derive(Debug, Eq)
///|
pub extend DetTensor with Eq::{not_equal, equal}
///|
pub extend DetTensor with @debug.Debug::{to_repr}
///|
priv struct Norm {
r : Double
g : Double
b : Double
sr : Double
sg : Double
sb : Double
}
///|
let det_norm : Norm = {
r: 0.485,
g: 0.456,
b: 0.406,
sr: 0.229,
sg: 0.224,
sb: 0.225,
}
///|
let rec_norm : Norm = { r: 0.5, g: 0.5, b: 0.5, sr: 0.5, sg: 0.5, sb: 0.5, }
///|
fn trunc_int(value : Double) -> Int32 {
value.trunc().to_int()
}
///|
fn align32(size : Int32) -> Int32 {
let aligned = (size.to_double() / 32.0).round().to_int() * 32
if aligned < 32 {
32
} else {
aligned
}
}
///|
/// Resize size used by PP-OCR detection: limit the long side, then round both
/// sides to a multiple of 32.
///
/// # Example
/// ```mbt check
/// test {
/// let (width, height) = @paddleocr.limit_side_size(10, 20, limit=960)
/// inspect(width, content="32")
/// inspect(height, content="32")
/// }
/// ```
pub fn limit_side_size(
width : Int32,
height : Int32,
limit~ : Int32,
) -> (Int32, Int32) raise OcrError {
if width <= 0 || height <= 0 {
raise OcrError::Image("image size must be positive")
}
if limit < 32 {
raise OcrError::Image("limit_side_len must be at least 32")
}
let longer = if width > height { width } else { height }
let ratio = if longer > limit {
limit.to_double() / longer.to_double()
} else {
1.0
}
let resized_w = trunc_int(width.to_double() * ratio)
let resized_h = trunc_int(height.to_double() * ratio)
let safe_w = if resized_w < 1 { 1 } else { resized_w }
let safe_h = if resized_h < 1 { 1 } else { resized_h }
(align32(safe_w), align32(safe_h))
}
///|
fn resize_rgba(
image : Rgba,
width : Int32,
height : Int32,
) -> Rgba raise OcrError {
let resized = @codec.resize(
{ width: image.width, height: image.height, data: image.pixels, },
width,
height,
@codec.Bilinear,
) catch {
error => raise OcrError::Image(error.to_string())
}
Rgba::from_rgba(resized.width, resized.height, resized.data)
}
///|
fn normalize_rgb(image : Rgba, norm : Norm) -> ChwTensor raise OcrError {
if norm.sr == 0.0 || norm.sg == 0.0 || norm.sb == 0.0 {
raise OcrError::Image("normalization std is zero")
}
let plane = image.width * image.height
let values : Array[Float] = Array::make(plane * 3, 0.0)
for y in 0.. DetTensor raise OcrError {
let (width, height) = limit_side_size(
image.width,
image.height,
limit=limit_side_len,
)
let resized = resize_rgba(image, width, height)
{
tensor: normalize_rgb(resized, det_norm),
ratio_w: width.to_double() / image.width.to_double(),
ratio_h: height.to_double() / image.height.to_double(),
}
}
///|
fn blit_pad(source : ChwTensor, width : Int32) -> ChwTensor {
if source.width == width {
return source
}
let plane = width * source.height
let values : Array[Float] = Array::make(plane * 3, 0.0)
for channel in 0..<3 {
for y in 0.. ChwTensor raise OcrError {
if image.width <= 0 || image.height <= 0 {
raise OcrError::Image("image size must be positive")
}
if height < 1 || max_width < 1 {
raise OcrError::Image("recognition size must be positive")
}
let cap = match fixed_width {
Some(width) =>
if width < 1 {
raise OcrError::Image("recognition width must be positive")
} else {
width
}
None => max_width
}
let aspect = image.width.to_double() / image.height.to_double()
let mut resized_w = (height.to_double() * aspect).ceil().to_int()
if resized_w > cap {
resized_w = cap
}
if resized_w < 1 {
resized_w = 1
}
let normalized = normalize_rgb(
resize_rgba(image, resized_w, height),
rec_norm,
)
match fixed_width {
Some(_) => blit_pad(normalized, cap)
None => normalized
}
}