refactor(core): 提炼公共类型

- 将 AxisDim、TensorInfo 等公共类型下沉至 ddddocr_core::types
- 项目结构优化
This commit is contained in:
2026-07-30 16:55:58 +08:00
parent 7d159c5702
commit a3c4614574
22 changed files with 349 additions and 408 deletions

View File

@@ -1,26 +1,11 @@
use crate::det::executor::Detector;
// use ddddocr_tract::det::session::DetSession;
use crate::DetEngine;
pub struct DetBuilder {
use_gpu: bool,
device_id: u8,
}
use crate::traits::DetEngine;
#[derive(Default)]
pub struct DetBuilder;
impl DetBuilder {
fn use_gpu(mut self) -> Self {
self.use_gpu = true;
self
}
fn device_id(mut self, device_id: u8) -> Self {
self.device_id = device_id;
self
}
fn build<E: DetEngine>(self, session: &E) -> Detector<'_> {
Detector {
session,
use_gpu: self.use_gpu,
device_id: self.device_id,
}
Detector { session }
}
}

View File

@@ -5,7 +5,8 @@ use std::fmt;
// use tract_onnx::prelude::{Tensor};
// use ddddocr_tract::det::session::DetSession;
use crate::{DetEngine, DetOutput};
use crate::{DetBuilder, DetOutput, OcrBuilder};
use crate::traits::DetEngine;
#[derive(Debug, Clone, Copy)]
pub struct DetectionResult {
pub x1: i32,
@@ -29,21 +30,17 @@ impl fmt::Display for DetectionResult {
pub struct Detector<'a> {
pub(crate) session: &'a dyn DetEngine,
#[allow(dead_code)]
pub(crate) use_gpu: bool,
#[allow(dead_code)]
pub(crate) device_id: u8,
}
impl<'a> Detector<'a> {
pub fn new(session: &'a dyn DetEngine) -> Self {
Detector {
session,
use_gpu: false,
device_id: 0,
}
Detector { session }
}
pub fn builder() -> DetBuilder {
DetBuilder::default()
}
}
impl<'a> Detector<'a> {
pub fn predict(&self, image: &DynamicImage) -> Result<Vec<DetectionResult>> {
// Rust 中通常在调用层处理文件/PIL转换这里直接进入核心逻辑
Ok(self.get_bbox(image)?)

View File

@@ -3,14 +3,14 @@ pub mod error;
mod ocr;
mod slide;
pub mod utils;
use error::{Result, TensorError};
use std::path::Path;
pub mod types;
pub mod traits;
pub use crate::det::{DetBuilder, DetectionResult, Detector};
pub use crate::ocr::{Charset, ModelMetadata, Normalization, Ocr, OcrBuilder, OcrResult, Resize};
pub use crate::slide::{SlideResult, Slider};
// DetSession
pub enum OcrOutput {
@@ -22,24 +22,3 @@ pub enum DetOutput {
Detection(ndarray::Array3<f32>), // 拥有完整所有权的 2维矩阵可任意传递和返回
}
/// 核心层定义的统一推理引擎接口。
/// 未来的 ddddocr-tract 和 ddddocr-ort 都必须实现这个 Trait
pub trait InferenceEngine {
/// 关联类型:具体的 Session 需要声明自己到底产出什么枚举
type Output;
fn inference(&self, input_array: ndarray::Array4<f32>) -> Result<Self::Output, TensorError>;
}
pub trait OcrEngine: InferenceEngine<Output = OcrOutput> {
fn metadata(&self) -> &ModelMetadata;
}
pub trait DetEngine: InferenceEngine<Output = DetOutput> {}
pub trait ModelBuilder {
type Session;
type Error;
fn model_for_path<P: AsRef<Path>>(&self,model_path: P) -> Result<Self::Session, Self::Error>;
fn model_from_bytes(&self,model_bytes: &[u8]) -> Result<Self::Session, Self::Error>;
}

View File

@@ -1,9 +1,9 @@
use crate::ocr::executor::Ocr;
// use ddddocr_tract::session::OcrSession;
use crate::OcrEngine;
use crate::traits::OcrEngine;
use crate::ocr::color_filter::ColorFilter;
use crate::ocr::token_filter::TokenFilter;
#[derive(Default)]
pub struct OcrBuilder {
/// 是否修复PNG格式问题
png_fix: bool,
@@ -49,14 +49,14 @@ impl OcrBuilder {
self.charset_restrict = Some(Box::new(restrict));
self
}
pub fn build<E: OcrEngine>(self, session: &E) -> Ocr<'_> {
pub fn runner<E: OcrEngine>(self, runtime: &E) -> Ocr<'_> {
// 1. 原地解析颜色过滤器
let final_color_ranges = match &self.color_filter {
Some(filter) => filter.collect_to_vec(),
None => Ok(None),
};
// 2. 原地解析字符集过滤
let tokens = &session.metadata().charset.tokens;
let tokens = &runtime.metadata().charset.tokens;
let final_charset_indices = match &self.charset_restrict {
Some(restrict) => restrict.apply_to_charset(tokens),
None => None,
@@ -64,7 +64,7 @@ impl OcrBuilder {
// Ocr::new(session, self)
Ocr {
session,
runtime,
png_fix: self.png_fix, // 原地解构出来
probability: self.probability,
final_color_ranges,

View File

@@ -16,7 +16,8 @@ use ndarray::ArrayView2;
// Logits(ndarray::Array2<f32>), // 拥有完整所有权的 2维矩阵可任意传递和返回
// }
use crate::error::{ImagePreprocessError, Result, TensorError};
use crate::{OcrEngine, OcrOutput};
use crate::{OcrBuilder, OcrOutput};
use crate::traits::OcrEngine;
use tracing::{ warn};
#[derive(Debug, Clone)]
pub enum OcrResult {
@@ -103,7 +104,7 @@ impl fmt::Display for OcrResult {
}
pub struct Ocr<'a> {
pub(crate) session: &'a dyn OcrEngine,
pub(crate) runtime: &'a dyn OcrEngine,
pub(crate) png_fix: bool,
pub(crate) probability: bool,
/// 颜色过滤:保留的颜色列表
@@ -116,15 +117,19 @@ pub struct Ocr<'a> {
impl<'a> Ocr<'a> {
// 初始化任务,设置默认参数
pub fn new(session: &'a dyn OcrEngine) -> Self {
pub fn new(runtime: &'a dyn OcrEngine) -> Self {
Ocr {
session,
runtime,
png_fix: false, // 默认值
probability: false,
final_color_ranges: Ok(None),
final_charset_indices: None,
}
}
pub fn builder() -> OcrBuilder {
OcrBuilder::default()
}
}
impl<'a> Ocr<'a> {
pub fn predict(&self, image: &DynamicImage) -> Result<OcrResult> {
@@ -157,7 +162,7 @@ impl<'a> Ocr<'a> {
};
let tensor = self.preprocess_image(&img_cow)?;
let raw_tensor = self.session.inference(tensor)?;
let raw_tensor = self.runtime.inference(tensor)?;
// 3. 后处理分流:直接返回 OcrResult
// let ocr_output = match raw_tensor.datum_type() {
@@ -178,7 +183,7 @@ impl<'a> Ocr<'a> {
/// 负责:透明背景修复 -> 灰度化 -> 按比例 Resize -> 归一化 -> 4维张量转换
fn preprocess_image(&self, img: &DynamicImage) -> Result<ndarray::Array4<f32>,ImagePreprocessError> {
// 1. 获取模型元数据配置
let meta = self.session.metadata();
let meta = self.runtime.metadata();
let norm = &meta.normalization; // 获取归一化器
// A. 修复 PNG 透明背景 (内部逻辑你之前已实现)
@@ -326,7 +331,7 @@ impl<'a> Ocr<'a> {
}
impl<'a> Ocr<'a> {
fn is_valid_indices(&self, idx: usize) -> bool {
if idx >= self.session.metadata().charset.size() {
if idx >= self.runtime.metadata().charset.size() {
return false;
}
@@ -338,7 +343,7 @@ impl<'a> Ocr<'a> {
/// 【按需延迟打印】:当用户真的需要“知道当前有哪些限制字符”时,一秒反查并打印
/// 这里的 &str 完美借用了自 tokens依然是彻底的零拷贝
pub fn valid_tokens(&self) -> Vec<&str> {
let charset = &self.session.metadata().charset;
let charset = &self.runtime.metadata().charset;
let tokens = &charset.tokens;
match &self.final_charset_indices {
Some(indices) => indices
@@ -352,7 +357,7 @@ impl<'a> Ocr<'a> {
pub fn valid_size(&self) -> usize {
match &self.final_charset_indices {
Some(indices) => indices.len(),
None => self.session.metadata().charset.tokens.len(),
None => self.runtime.metadata().charset.tokens.len(),
}
}
/// 变体 B 核心处理器:单次遍历 2D 视图,融合计算 Softmax、Argmax、置信度并输出概率大包
@@ -500,7 +505,7 @@ impl<'a> Ocr<'a> {
/// 获取有效字符索引列表 (用于外部验证或过滤)
fn ctc_decode_to_string(&self, predicted_indices: &[i64]) -> String {
println!("indices模型输出原始数据: {:?}", predicted_indices);
let charset = &self.session.metadata().charset;
let charset = &self.runtime.metadata().charset;
let tokens = &charset.tokens;
// let valid_indices = &charset.valid_indices;

View File

@@ -0,0 +1,42 @@
use crate::error::TensorError;
use crate::types::{ModelInfo, TensorInfo};
use crate::{DetOutput, ModelMetadata, OcrOutput};
use std::path::Path;
pub trait Info {
fn input_info(&self) -> crate::error::Result<Vec<TensorInfo>>;
fn output_info(&self) -> crate::error::Result<Vec<TensorInfo>>;
fn model_info(&self) -> crate::error::Result<ModelInfo>;
}
/// 核心层定义的统一推理引擎接口。
/// 未来的 ddddocr-tract 和 ddddocr-ort 都必须实现这个 Trait
pub trait InferenceEngine {
/// 关联类型:具体的 Session 需要声明自己到底产出什么枚举
type Output;
fn inference(
&self,
input_array: ndarray::Array4<f32>,
) -> crate::error::Result<Self::Output, TensorError>;
}
pub trait OcrEngine: InferenceEngine<Output = OcrOutput> + Info {
fn metadata(&self) -> &ModelMetadata;
}
pub trait DetEngine: InferenceEngine<Output = DetOutput> {}
pub trait Loader {
type Session;
type Error;
fn build_for_path<P: AsRef<Path>>(
&self,
model_path: P,
) -> crate::error::Result<Self::Session, Self::Error>;
fn build_from_bytes(
&self,
model_bytes: &[u8],
) -> crate::error::Result<Self::Session, Self::Error>;
}

46
ddddocr-core/src/types.rs Normal file
View File

@@ -0,0 +1,46 @@
#[derive(Debug,Clone)]
pub enum TensorType{
F32,
I64,
Other
}
/// 明确命名为 AxisDim代表模型某一个轴的维度特征
#[derive(Clone, PartialEq, Eq)]
pub enum AxisDim {
/// 静态固定维度(如通道数固定为 1高度固定为 64
Static(usize),
/// 动态符号维度(如宽度是动态的 "image_width"
Dynamic(String),
}
impl AxisDim {
/// 便捷方法:判断是否为动态维度
pub fn is_dynamic(&self) -> bool {
matches!(self, AxisDim::Dynamic(_))
}
}
/// 自定义 Debug 格式化输出,彻底融化套娃外壳,保证日志干净漂亮
impl std::fmt::Debug for AxisDim {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AxisDim::Static(size) => write!(f, "{}", size),
AxisDim::Dynamic(expr) => write!(f, "Dynamic(\"{}\")", expr),
}
}
}
/// 模拟 Python 的 input_info 和 output_info 结构
#[derive(Debug, Clone)]
pub struct TensorInfo {
pub name: String,
pub shape: Vec<AxisDim>, // 既包含 Fixed 静态维度,也包含 Dynamic 动态符号
pub tensor_type: TensorType, // 对应 Python 的 type
}
/// 最终返回的模型完整信息
#[derive(Debug, Clone)]
pub struct ModelInfo {
pub inputs: Vec<TensorInfo>,
pub outputs: Vec<TensorInfo>,
/// 硬件执行提供者(采用 Option 兼容不同底层的推理引擎)
pub providers: Option<Vec<String>>,
}