Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ocrs for openSUSE:Factory checked in at 2024-06-05 17:42:49 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ocrs (Old) and /work/SRC/openSUSE:Factory/.ocrs.new.24587 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ocrs" Wed Jun 5 17:42:49 2024 rev:3 rq:1178678 version:0.8.0 Changes: -------- --- /work/SRC/openSUSE:Factory/ocrs/ocrs.changes 2024-05-20 18:14:24.640417779 +0200 +++ /work/SRC/openSUSE:Factory/.ocrs.new.24587/ocrs.changes 2024-06-05 17:43:29.920505706 +0200 @@ -1,0 +2,11 @@ +Wed Jun 5 07:53:14 UTC 2024 - Muhammad Akbar Yanuar Mantari <[email protected]> + +- Update to version 0.8.0 + * Updated rten to v0.10.0: This improves performance when + recognizing long lines of text and improves efficiency by + setting the number of threads to match the number of physical + cores. + * Errors that occur when running the text recognition model are + now propagated to the caller instead of causing a panic. + +------------------------------------------------------------------- Old: ---- ocrs-ocrs-v0.7.0.tar.gz New: ---- ocrs-ocrs-v0.8.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ocrs.spec ++++++ --- /var/tmp/diff_new_pack.GJtFtM/_old 2024-06-05 17:43:30.872540377 +0200 +++ /var/tmp/diff_new_pack.GJtFtM/_new 2024-06-05 17:43:30.876540523 +0200 @@ -22,7 +22,7 @@ %bcond_without test %endif Name: ocrs -Version: 0.7.0 +Version: 0.8.0 Release: 0 Summary: A modern OCR engine written in Rust License: Apache-2.0 AND MIT ++++++ ocrs-ocrs-v0.7.0.tar.gz -> ocrs-ocrs-v0.8.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/CHANGELOG.md new/ocrs-ocrs-v0.8.0/CHANGELOG.md --- old/ocrs-ocrs-v0.7.0/CHANGELOG.md 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/CHANGELOG.md 2024-05-25 10:41:11.000000000 +0200 @@ -5,6 +5,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0] - 2024-05-25 + +### Breaking changes + +This release changes Ocrs's internal use of threads, which may affect consumers +that are using their own parallelism. Specifically Ocrs no longer uses the +global Rayon thread pool but instead a custom thread pool which is sized to +match the number of physical rather than logical cores. See +https://github.com/robertknight/ocrs/pull/79 for more details and information on +adapting. + +### Changes + +- Updated rten to v0.10.0. This improves performance when recognizing long lines + of text (https://github.com/robertknight/ocrs/pull/79) and improves efficiency + by setting the number of threads to match the number of physical cores. + +- Errors that occur when running the text recognition model are now propagated + to the caller instead of causing a panic (https://github.com/robertknight/ocrs/pull/77) + ## [0.7.0] - 2024-05-16 ### Breaking changes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/Cargo.lock new/ocrs-ocrs-v0.8.0/Cargo.lock --- old/ocrs-ocrs-v0.7.0/Cargo.lock 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/Cargo.lock 2024-05-25 10:41:11.000000000 +0200 @@ -173,6 +173,12 @@ ] [[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -266,8 +272,18 @@ ] [[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] name = "ocrs" -version = "0.7.0" +version = "0.8.0" dependencies = [ "anyhow", "fastrand", @@ -283,7 +299,7 @@ [[package]] name = "ocrs-cli" -version = "0.7.0" +version = "0.8.0" dependencies = [ "anyhow", "home", @@ -378,13 +394,15 @@ [[package]] name = "rten" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9d6d80601e57cab46f477955be6e3be1a4c92ed0aebb3376e1f19d24e83bb1" +checksum = "09c030cdf90e64c5eeeba389ca59da14b0a106b1b8366c15591251bb6a2e777f" dependencies = [ "flatbuffers", "libm", + "num_cpus", "rayon", + "rten-simd", "rten-tensor", "rten-vecmath", "rustc-hash", @@ -394,27 +412,36 @@ [[package]] name = "rten-imageproc" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529fdef25f8232ebb08fb6cfc785ec97a7fb268bebc4895e36e8750e2bbeaa51" +checksum = "5ba61077269b2b2c90445bfd55fb798dcd544b56e7fd78faaea51940b8e429ae" dependencies = [ "rten-tensor", ] [[package]] +name = "rten-simd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb16da64e0d08ce56dc17d8304ab2da541176ee30430c0b0e581a7841a660ae" + +[[package]] name = "rten-tensor" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffa78180a98337a43163e9da8f202120e9ae3b82366cccfb05a5a854e48cd581" +checksum = "52f5e53d2e43bb736e89e4ea41b707e024190f8ba47c3eddf5a3c2d022089909" dependencies = [ "smallvec", ] [[package]] name = "rten-vecmath" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495f48d459768d61ca37b418f79ac7aac3a707024c79fa49a14dd2c1ad8a2c0e" +checksum = "56eccc46a7e7a2df2cebb7ba95e613a01942a01e0f2f2f7d6122176ab7372e9f" +dependencies = [ + "rten-simd", +] [[package]] name = "rustc-hash" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/Cargo.toml new/ocrs-ocrs-v0.8.0/Cargo.toml --- old/ocrs-ocrs-v0.7.0/Cargo.toml 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/Cargo.toml 2024-05-25 10:41:11.000000000 +0200 @@ -4,3 +4,8 @@ "ocrs", "ocrs-cli", ] + +[workspace.dependencies] +rten = { version = "0.10.0" } +rten-imageproc = { version = "0.10.0" } +rten-tensor = { version = "0.10.0" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/Makefile new/ocrs-ocrs-v0.8.0/Makefile --- old/ocrs-ocrs-v0.7.0/Makefile 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/Makefile 2024-05-25 10:41:11.000000000 +0200 @@ -41,5 +41,12 @@ wasm-bindgen target/wasm32-unknown-unknown/release/ocrs.wasm --out-dir js/dist/ --target web --reference-types --weak-refs tools/optimize-wasm.sh js/dist/ocrs_bg.wasm +# Build Ocrs CLI for non-browser WebAssembly runtimes (eg. wasmtime). Run using: +# +# wasmtime --dir . target/wasm32-wasi/release/ocrs.wasm --detect-model text-detection.rten --rec-model text-recognition.rten ocrs-cli/test-data/why-rust.png +.PHONY: wasm-wasi +wasm-wasi: + RUSTFLAGS="-C target-feature=+simd128" cargo build --release --target wasm32-wasi --package ocrs-cli + .PHONY: wasm-all wasm-all: wasm wasm-nosimd diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/ocrs/Cargo.toml new/ocrs-ocrs-v0.8.0/ocrs/Cargo.toml --- old/ocrs-ocrs-v0.7.0/ocrs/Cargo.toml 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/ocrs/Cargo.toml 2024-05-25 10:41:11.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "ocrs" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = ["Robert Knight"] description = "OCR engine" @@ -11,9 +11,9 @@ [dependencies] anyhow = "1.0.80" rayon = "1.10.0" -rten = { version = "0.9.0" } -rten-imageproc = { version = "0.9.0" } -rten-tensor = { version = "0.9.0" } +rten = { workspace = true } +rten-imageproc = { workspace = true } +rten-tensor = { workspace = true } thiserror = "1.0.59" [target.'cfg(target_arch = "wasm32")'.dependencies] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/ocrs/src/errors.rs new/ocrs-ocrs-v0.8.0/ocrs/src/errors.rs --- old/ocrs-ocrs-v0.7.0/ocrs/src/errors.rs 1970-01-01 01:00:00.000000000 +0100 +++ new/ocrs-ocrs-v0.8.0/ocrs/src/errors.rs 2024-05-25 10:41:11.000000000 +0200 @@ -0,0 +1,25 @@ +use std::error::Error; +use std::fmt; + +/// The error type returned when running a machine learning model fails. +#[derive(Debug)] +pub enum ModelRunError { + /// Model execution failed. + RunFailed(Box<dyn Error + Send + Sync>), + + /// The model output had a different data type or shape than expected. + WrongOutput, +} + +impl fmt::Display for ModelRunError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + ModelRunError::RunFailed(err) => write!(f, "model run failed: {}", err), + ModelRunError::WrongOutput => { + write!(f, "model output had unexpected type or shape") + } + } + } +} + +impl Error for ModelRunError {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/ocrs/src/lib.rs new/ocrs-ocrs-v0.8.0/ocrs/src/lib.rs --- old/ocrs-ocrs-v0.7.0/ocrs/src/lib.rs 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/ocrs/src/lib.rs 2024-05-25 10:41:11.000000000 +0200 @@ -5,6 +5,7 @@ use rten_tensor::NdTensor; mod detection; +mod errors; mod geom_util; mod layout_analysis; mod log; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/ocrs/src/recognition.rs new/ocrs-ocrs-v0.8.0/ocrs/src/recognition.rs --- old/ocrs-ocrs-v0.7.0/ocrs/src/recognition.rs 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/ocrs/src/recognition.rs 2024-05-25 10:41:11.000000000 +0200 @@ -3,13 +3,14 @@ use anyhow::anyhow; use rayon::prelude::*; use rten::ctc::{CtcDecoder, CtcHypothesis}; -use rten::{Dimension, FloatOperators, Model, NodeId}; +use rten::{thread_pool, Dimension, FloatOperators, Model, NodeId}; use rten_imageproc::{ bounding_rect, BoundingRect, Line, Point, PointF, Polygon, Rect, RotatedRect, }; use rten_tensor::prelude::*; use rten_tensor::{NdTensor, NdTensorView, Tensor}; +use crate::errors::ModelRunError; use crate::geom_util::{downwards_line, leftmost_edge, rightmost_edge}; use crate::preprocess::BLACK_VALUE; use crate::text_items::{TextChar, TextLine}; @@ -359,12 +360,14 @@ /// Run text recognition on an NCHW batch of text line images, and return /// a `[batch, seq, label]` tensor of class probabilities. - fn run(&self, input: NdTensor<f32, 4>) -> anyhow::Result<NdTensor<f32, 3>> { + fn run(&self, input: NdTensor<f32, 4>) -> Result<NdTensor<f32, 3>, ModelRunError> { let input: Tensor<f32> = input.into(); - let [output] = - self.model - .run_n(&[(self.input_id, (&input).into())], [self.output_id], None)?; - let mut rec_sequence: NdTensor<f32, 3> = output.try_into()?; + let [output] = self + .model + .run_n(&[(self.input_id, (&input).into())], [self.output_id], None) + .map_err(|err| ModelRunError::RunFailed(err.into()))?; + let mut rec_sequence: NdTensor<f32, 3> = + output.try_into().map_err(|_| ModelRunError::WrongOutput)?; // Transpose from [seq, batch, class] => [batch, seq, class] rec_sequence.permute([1, 0, 2]); @@ -470,52 +473,59 @@ .collect(); // Run text recognition on batches of lines. - let mut line_rec_results: Vec<LineRecResult> = line_groups - .into_par_iter() - .flat_map(|(group_width, lines)| { - if debug { - println!( - "Processing group of {} lines of width {}", - lines.len(), - group_width, - ); - } - - let rec_input = prepare_text_line_batch( - &image, - &lines, - page_rect, - rec_img_height as usize, - group_width as usize, - ); - - // TODO - Propagate errors from recognition model to caller. - let rec_output = self.run(rec_input).expect("recognition failed"); - let ctc_input_len = rec_output.shape()[1]; - - // Apply CTC decoding to get the label sequence for each line. - lines - .into_iter() - .enumerate() - .map(|(group_line_index, line)| { - let decoder = CtcDecoder::new(); - let input_seq = rec_output.slice([group_line_index]); - let ctc_output = match decode_method { - DecodeMethod::Greedy => decoder.decode_greedy(input_seq), - DecodeMethod::BeamSearch { width } => { - decoder.decode_beam(input_seq, width) - } - }; - LineRecResult { - line, - rec_input_len: group_width as usize, - ctc_input_len, - ctc_output, + let batch_rec_results: Result<Vec<Vec<LineRecResult>>, ModelRunError> = + thread_pool().run(|| { + line_groups + .into_par_iter() + .map(|(group_width, lines)| { + if debug { + println!( + "Processing group of {} lines of width {}", + lines.len(), + group_width, + ); } + + let rec_input = prepare_text_line_batch( + &image, + &lines, + page_rect, + rec_img_height as usize, + group_width as usize, + ); + + let rec_output = self.run(rec_input)?; + let ctc_input_len = rec_output.shape()[1]; + + // Apply CTC decoding to get the label sequence for each line. + let line_rec_results = lines + .into_iter() + .enumerate() + .map(|(group_line_index, line)| { + let decoder = CtcDecoder::new(); + let input_seq = rec_output.slice([group_line_index]); + let ctc_output = match decode_method { + DecodeMethod::Greedy => decoder.decode_greedy(input_seq), + DecodeMethod::BeamSearch { width } => { + decoder.decode_beam(input_seq, width) + } + }; + LineRecResult { + line, + rec_input_len: group_width as usize, + ctc_input_len, + ctc_output, + } + }) + .collect::<Vec<_>>(); + + Ok(line_rec_results) }) - .collect::<Vec<_>>() - }) - .collect(); + .collect() + }); + + let mut line_rec_results: Vec<LineRecResult> = + batch_rec_results?.into_iter().flatten().collect(); // The recognition outputs are in a different order than the inputs due to // batching and parallel processing. Re-sort them into input order. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/ocrs-cli/Cargo.toml new/ocrs-ocrs-v0.8.0/ocrs-cli/Cargo.toml --- old/ocrs-ocrs-v0.7.0/ocrs-cli/Cargo.toml 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/ocrs-cli/Cargo.toml 2024-05-25 10:41:11.000000000 +0200 @@ -1,6 +1,6 @@ [package] name = "ocrs-cli" -version = "0.7.0" +version = "0.8.0" edition = "2021" authors = ["Robert Knight"] description = "OCR CLI tool for extracting text from images" @@ -12,16 +12,18 @@ image = { version = "0.25.1", default-features = false, features = ["png", "jpeg", "webp"] } png = "0.17.6" serde_json = "1.0.116" -rten = { version = "0.9.0" } -rten-imageproc = { version = "0.9.0" } -rten-tensor = { version = "0.9.0" } -ocrs = { path = "../ocrs", version = "0.7.0" } +rten = { workspace = true } +rten-imageproc = { workspace = true } +rten-tensor = { workspace = true } +ocrs = { path = "../ocrs", version = "0.8.0" } lexopt = "0.3.0" -ureq = "2.9.7" url = "2.4.0" -home = "0.5.9" anyhow = "1.0.79" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +ureq = "2.9.7" +home = "0.5.9" + [features] # Use AVX-512 instructions if available. Requires nightly Rust. avx512 = ["rten/avx512"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ocrs-ocrs-v0.7.0/ocrs-cli/src/models.rs new/ocrs-ocrs-v0.8.0/ocrs-cli/src/models.rs --- old/ocrs-ocrs-v0.7.0/ocrs-cli/src/models.rs 2024-05-16 10:41:41.000000000 +0200 +++ new/ocrs-ocrs-v0.8.0/ocrs-cli/src/models.rs 2024-05-25 10:41:11.000000000 +0200 @@ -1,13 +1,18 @@ use std::fmt; -use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; + +#[cfg(not(target_arch = "wasm32"))] +use std::{fs, path::Path}; use anyhow::anyhow; use rten::Model; + +#[cfg(not(target_arch = "wasm32"))] use url::Url; /// Return the path to the directory in which cached models etc. should be /// saved. +#[cfg(not(target_arch = "wasm32"))] fn cache_dir() -> Result<PathBuf, anyhow::Error> { let mut cache_dir: PathBuf = home::home_dir().ok_or(anyhow!("Failed to determine home directory"))?; @@ -22,6 +27,7 @@ /// Extract the last path segment from a URL. /// /// eg. "https://models.com/text-detection.rten" => "text-detection.rten". +#[cfg(not(target_arch = "wasm32"))] #[allow(rustdoc::bare_urls)] fn filename_from_url(url: &str) -> Option<String> { let parsed = Url::parse(url).ok()?; @@ -33,6 +39,7 @@ /// Download a file from `url` to a local cache, if not already fetched, and /// return the path to the local file. +#[cfg(not(target_arch = "wasm32"))] fn download_file(url: &str, filename: Option<&str>) -> Result<PathBuf, anyhow::Error> { let cache_dir = cache_dir()?; let filename = match filename { @@ -55,6 +62,13 @@ Ok(file_path) } +#[cfg(target_arch = "wasm32")] +fn download_file(_url: &str, _filename: Option<&str>) -> Result<PathBuf, anyhow::Error> { + Err(anyhow!( + "Downloading models from a URL is not supported on the current platform" + )) +} + /// Location that a model can be loaded from. #[derive(Clone, Copy)] pub enum ModelSource<'a> { ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/ocrs/vendor.tar.zst /work/SRC/openSUSE:Factory/.ocrs.new.24587/vendor.tar.zst differ: char 255581, line 1058
