refactor(load,tract):将 ModelMetadata JSON 加载逻辑解耦至 ddddocr-tract, 优化 Error 枚举结构与错误透传
- 在 load 模块中精简 Error 与 Result 别名定义 - 增加 ParseError 子类型区分路径与字节流加载失败 - 支持通过 #[from] 自动转换 Tract 引擎底层错误 - 移出 core 中的 serde 依赖,保持核心库纯洁 - 在 tract 中实现 TractModelMetadata 扩展 trait 加载解析配置
This commit is contained in:
@@ -1,78 +1,11 @@
|
||||
use crate::error::{ Result};
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// ==========================================
|
||||
// 3. 字符集核心结构体 (重命名为 Charset)
|
||||
// ==========================================
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Charset {
|
||||
// 使用 Cow 统一静态切片和动态读取的 Vec<String>,内部实现真正的零拷贝
|
||||
pub tokens: Vec<Cow<'static, str>>,
|
||||
// 反向查找表,保证字符转索引为 O(1)
|
||||
pub char_to_idx: HashMap<Cow<'static, str>, usize>,
|
||||
// 当前处于激活状态的有效索引缓存 (用于 CTC 解码前的过滤加速)
|
||||
// pub valid_indices: HashSet<usize>,
|
||||
}
|
||||
|
||||
impl Charset {
|
||||
// 内部底层统一收拢构造
|
||||
pub fn new(tokens: Vec<Cow<'static, str>>) -> Self {
|
||||
let mut char_to_idx = HashMap::with_capacity(tokens.len());
|
||||
for (idx, token) in tokens.iter().enumerate() {
|
||||
char_to_idx.entry(token.clone()).or_insert(idx);
|
||||
// 如果字符集有重复,保留第一个遇到的索引 (符合 Python .index 逻辑)
|
||||
// char_to_idx.entry(token.to_string()).or_insert(idx);
|
||||
}
|
||||
|
||||
Self {
|
||||
tokens,
|
||||
char_to_idx,
|
||||
}
|
||||
}
|
||||
|
||||
// --- 业务策略方法 ---
|
||||
|
||||
/// 将字符转为索引,不存在返回 -1 (保持与原 Python 库行为一致)
|
||||
pub fn char_to_index(&self, char_str: &str) -> i32 {
|
||||
if let Some(&idx) = self.char_to_idx.get(char_str) {
|
||||
idx as i32
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
/// 将索引转为字符引用,零拷贝。若越界返回 None
|
||||
pub fn index_to_char_ref(&self, index: usize) -> Option<&str> {
|
||||
self.tokens.get(index).map(|cow| cow.as_ref())
|
||||
}
|
||||
|
||||
pub fn is_valid_char(&self, char_str: &str) -> bool {
|
||||
self.char_to_idx.get(char_str).is_some()
|
||||
}
|
||||
pub fn size(&self) -> usize {
|
||||
self.tokens.len()
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 4. 标准 Display 接口实现 (对应 __str__)
|
||||
// ==========================================
|
||||
impl std::fmt::Display for Charset {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Charset [Total Size: {}", self.size(),)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// 1. 辅助定义的枚举与结构体
|
||||
// =====================================================================
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")] // 支持 json 中写 "zero_to_one" 或 "minus_one_to_one"
|
||||
use crate::ocr::Charset;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Normalization {
|
||||
/// 映射到 [0.0, 1.0] -> pixel / 255.0
|
||||
ZeroToOne,
|
||||
@@ -102,23 +35,6 @@ pub enum Resize {
|
||||
Square(u32),
|
||||
}
|
||||
|
||||
/// 仅用于反序列化 JSON 的中间临时结构体(DTO)
|
||||
#[derive(Deserialize)]
|
||||
struct ModelMetadataDto {
|
||||
charset: Vec<String>,
|
||||
word: bool,
|
||||
#[serde(alias = "image")]
|
||||
resize: Vec<i32>,
|
||||
channel: u8,
|
||||
/// 新增:允许在配置文件中指定归一化策略。
|
||||
/// 使用 serde(default) 可以在不配置时提供一个默认值(比如默认 ZeroToOne)
|
||||
#[serde(default = "default_normalization")]
|
||||
normalization: Normalization,
|
||||
}
|
||||
fn default_normalization() -> Normalization {
|
||||
Normalization::ZeroToOne
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModelMetadata {
|
||||
/// 字符集管理器
|
||||
@@ -134,6 +50,21 @@ pub struct ModelMetadata {
|
||||
}
|
||||
|
||||
impl ModelMetadata {
|
||||
pub fn new(
|
||||
charset: Charset,
|
||||
word: bool,
|
||||
resize: Resize,
|
||||
channel: u8,
|
||||
normalization: Normalization,
|
||||
) -> Self {
|
||||
Self {
|
||||
charset,
|
||||
word,
|
||||
resize,
|
||||
channel,
|
||||
normalization,
|
||||
}
|
||||
}
|
||||
// --- 优雅的工厂模式构造器 ---
|
||||
/// 通用的静态切片转换构造器
|
||||
pub fn from_static_slice(
|
||||
@@ -152,49 +83,4 @@ impl ModelMetadata {
|
||||
normalization,
|
||||
}
|
||||
}
|
||||
pub fn from_json_str(json_str: &str) -> Result<Self> {
|
||||
let dto: ModelMetadataDto = serde_json::from_str(json_str)
|
||||
.map_err(|e| anyhow!("JSON 反序列化失败,请检查字段是否完整: {}", e))?;
|
||||
|
||||
// 1. 将 DTO 的字符串数组转化为强类型的 Charset
|
||||
let tokens: Vec<Cow<'static, str>> =
|
||||
dto.charset.into_iter().map(|s| Cow::Owned(s)).collect();
|
||||
let charset = Charset::new(tokens);
|
||||
|
||||
// 2. 解析 resize 策略(重现 Python 的复杂条件判断)
|
||||
if dto.resize.len() != 2 {
|
||||
return Err(anyhow!(
|
||||
"'resize (or image)' 字段必须是包含两个元素的数组,例如 [-1, 64]"
|
||||
));
|
||||
}
|
||||
let r0 = dto.resize[0];
|
||||
let r1 = dto.resize[1];
|
||||
|
||||
let resize = if r0 == -1 {
|
||||
if dto.word {
|
||||
// 如果 word 为 true,且包含 -1,Python 里是 resize 为 (r1, r1) 的正方形
|
||||
Resize::Square(r1 as u32)
|
||||
} else {
|
||||
// 如果 word 为 false,且包含 -1,Python 里是高度固定为 r1,宽度按原图比例缩放
|
||||
Resize::DynamicWidth(r1 as u32)
|
||||
}
|
||||
} else {
|
||||
// 正常的固定宽高
|
||||
Resize::Fixed(r0 as u32, r1 as u32)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
charset,
|
||||
word: dto.word,
|
||||
resize,
|
||||
channel: dto.channel,
|
||||
normalization: dto.normalization,
|
||||
})
|
||||
}
|
||||
/// 机制 2:从内存字节流加载(极大地方便 include_bytes! 或网络下载)
|
||||
pub fn from_json_bytes(bytes: &[u8]) -> Result<Self> {
|
||||
let json_str = std::str::from_utf8(bytes)
|
||||
.map_err(|e| anyhow!("JSON 字节流不是合法的 UTF-8 编码: {}", e))?;
|
||||
Self::from_json_str(json_str)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user