From 3499e89bf1f84f56a8c16829360dbc685d68915c Mon Sep 17 00:00:00 2001 From: CNWei Date: Mon, 20 Jul 2026 20:18:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor(error):=20=E8=A7=84=E8=8C=83=E5=8C=96?= =?UTF-8?q?=E5=88=86=E5=B1=82=E9=94=99=E8=AF=AF=E7=B1=BB=E5=9E=8B=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BC=82=E5=B8=B8=E6=8D=95=E6=8D=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 tracing 记录异常,移除不必要的 Result - 重构 错误处理架构 --- Cargo.toml | 1 + ddddocr-core/Cargo.toml | 2 +- ddddocr-core/src/det/executor.rs | 19 ++++-- ddddocr-core/src/error.rs | 76 +++++++++++++--------- ddddocr-core/src/ocr/color_filter.rs | 44 ++++++++----- ddddocr-core/src/ocr/executor.rs | 41 +++++++----- ddddocr-core/src/ocr/metadata.rs | 2 +- ddddocr-core/src/utils/image_convert.rs | 34 +++++----- ddddocr-core/src/utils/image_helper.rs | 4 +- ddddocr-core/src/utils/tensor_transform.rs | 14 ++-- ddddocr-tract/src/ocr/session.rs | 4 +- 11 files changed, 140 insertions(+), 101 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f07d8f1..fc83e21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.150" ndarray = "0.16.1" thiserror = "1.0" # 刚好可以开始接入你需要的标准库错误处理 +tracing = "0.1.44" # 埋入日志打点(后续需要继续优化,现在只是简单尝试) \ No newline at end of file diff --git a/ddddocr-core/Cargo.toml b/ddddocr-core/Cargo.toml index 2fc8cf3..5b5fcb7 100644 --- a/ddddocr-core/Cargo.toml +++ b/ddddocr-core/Cargo.toml @@ -13,5 +13,5 @@ serde = { workspace = true } serde_json = "1.0.150" ndarray = { workspace = true } # 继承自工作空间 thiserror = { workspace = true } # 刚好可以开始接入你需要的标准库错误处理 - +tracing={workspace = true} #serde = { workspace = true, features = ["derive"] } \ No newline at end of file diff --git a/ddddocr-core/src/det/executor.rs b/ddddocr-core/src/det/executor.rs index 54d5f8f..eea7ad8 100644 --- a/ddddocr-core/src/det/executor.rs +++ b/ddddocr-core/src/det/executor.rs @@ -1,7 +1,7 @@ -use anyhow::{Context, Result}; +use crate::error::{Result, TensorError}; use image::{imageops::FilterType, DynamicImage, GenericImageView}; -use std::fmt; use ndarray::{prelude::*, s, Array2, Array3, Array4, Axis}; +use std::fmt; // use tract_onnx::prelude::{Tensor}; @@ -51,7 +51,7 @@ impl<'a> Detector<'a> { self.get_bbox(image) } /// 2. preproc: 纯 Rust 实现 (替代 OpenCV) - fn preproc(&self, image: &DynamicImage, input_size: (u32, u32)) -> Result<(Array4, f32)> { + fn preproc(&self, image: &DynamicImage, input_size: (u32, u32)) -> (Array4, f32) { let (target_h, target_w) = input_size; let (img_w, img_h) = image.dimensions(); @@ -89,7 +89,7 @@ impl<'a> Detector<'a> { } } - Ok((array, r)) + (array, r) } /// 3. demo_postprocess (逻辑与 Python 一致) @@ -252,7 +252,7 @@ impl<'a> Detector<'a> { // let dynamic_img = image::load_from_memory(image_bytes).context("Failed to decode utils")?; let (orig_w, orig_h) = dynamic_img.dimensions(); - let (input_tensor, ratio) = self.preproc(dynamic_img, (416, 416))?; + let (input_tensor, ratio) = self.preproc(dynamic_img, (416, 416)); // tract 推理 // let outputs = self.session.session.run(tvec!(input_tensor.into()))?; @@ -268,8 +268,13 @@ impl<'a> Detector<'a> { let obj_conf = pred.slice(s![.., 4..5]); let cls_conf = pred.slice(s![.., 5..]); let obj_broadcast = obj_conf - .broadcast(cls_conf.dim()) - .context("ndarray broadcasting failed for scores calculation")?; + .broadcast(cls_conf.dim()).ok_or_else(|| { + TensorError::DimensionMismatch { + expected: format!("可广播至 cls_conf 形状 {:?}", cls_conf.shape()), + actual: obj_conf.shape().to_vec(), + } + })?; + // .context("ndarray broadcasting failed for scores calculation")?; let scores = &obj_broadcast * &cls_conf; // let scores = &pred.slice(s![.., 4..5]) * &pred.slice(s![.., 5..]); diff --git a/ddddocr-core/src/error.rs b/ddddocr-core/src/error.rs index 6d97359..d3be367 100644 --- a/ddddocr-core/src/error.rs +++ b/ddddocr-core/src/error.rs @@ -73,7 +73,8 @@ use thiserror::Error; // } /// 统一用我们自己的 DdddError 包装 Result -pub type Result = std::result::Result; +// pub type Result = std::result::Result; +pub type Result = std::result::Result; // ===================================================================== // 1. 顶层全局 Error 分流器 (去 anyhow 化,完全基于标准库/自定义类型) // ===================================================================== @@ -83,27 +84,26 @@ pub enum DdddError { // /// 系统文件、网络等原生 I/O 异常 (高优先级自动转换) // #[error("系统网络或文件 I/O 异常: {0}")] // Io(#[from] std::io::Error), - /// 图像预处理阶段发生异常 #[error("图像预处理失败: {0}")] - Preprocess(#[from] ImagePreprocessReason), + Preprocess(#[from] ImagePreprocessError), /// 推理引擎与张量操作阶段发生异常 #[error("推理与模型输入/输出张量异常: {0}")] - Inference(#[from] TensorErrorReason), + Inference(#[from] TensorError), /// 算法后处理解码阶段发生异常 #[error("后处理解码错误: {0}")] - Decode(#[from] DecodeReason), + Decode(#[from] DecodeError), /// 框架内部不可恢复的逻辑断言错误(例如解析节点 Fact 失败) #[error("内部严重逻辑错误: {0}")] Internal(String), - /// 【流派一核心】接替 anyhow::Error 的用户自定义扩展错误 + /// 【流派核心】接替 anyhow::Error 的用户自定义扩展错误 /// 承载任何第三方扩展、解密、特定预处理插件在执行时产生的自定义错误 #[error("用户自定义扩展错误: {0}")] - Other(Box), + Other(#[source]Box), } // ===================================================================== @@ -111,7 +111,7 @@ pub enum DdddError { // ===================================================================== #[derive(Error, Debug)] -pub enum ImagePreprocessReason { +pub enum ImagePreprocessError { // #[error("图片加载或解码失败: {0}")] // ImageIo(#[from] image::ImageError), // image_io @@ -119,14 +119,16 @@ pub enum ImagePreprocessReason { Ndarray(#[from] ndarray::ShapeError), // image_io #[error("图像矩阵维度不合规!预期: {expected},实际图像形状: {actual:?}")] - InvalidImageDimensions { + InvalidDimensions { expected: String, actual: Vec, }, // image_io /// 从 ndarray 原始数据构建图像缓冲区时,缓冲区长度与分辨率/通道数不匹配 - #[error("图像缓冲区长度不匹配!预期大小: {expected},实际大小: {actual} (分辨率: {width}x{height}, 通道数: {channels})")] + #[error( + "图像缓冲区长度不匹配!预期大小: {expected},实际大小: {actual} (分辨率: {width}x{height}, 通道数: {channels})" + )] BufferLengthMismatch { expected: usize, actual: usize, @@ -137,7 +139,18 @@ pub enum ImagePreprocessReason { // image_io #[error("不支持的图像通道数: {0} (仅支持单通道灰度L、3通道RGB、4通道RGBA)")] UnsupportedChannels(usize), + // ================= 新增:针对 HSV 和 Preset 的强类型错误 ================= + /// HSV 颜色区间非法 (例如 H > 180 或 lower > upper) + #[error("HSV 颜色区间参数非法: {0}")] + InvalidHsvRange (String ), + /// 不支持或未知的颜色预设名称 + #[error("不支持的颜色预设名称: {0}")] + UnknownColorPreset(String), + + /// 颜色过滤器/预处理规则配置非法导致失败 + #[error("颜色过滤器配置无效或初始化失败: {0}")] + FilterConfigInvalid(String), // #[error("Base64 解码失败: {0}")] // Base64(#[from] base64::DecodeError), // @@ -153,14 +166,14 @@ pub enum ImagePreprocessReason { // ===================================================================== #[derive(Error, Debug)] -pub enum TensorErrorReason { +pub enum TensorError { /// 替换原有的 anyhow::Error,明确将 Tract/ONNX 引擎底层报错序列化为干净的 String #[error("推理引擎内部发生异常: {0}")] - EngineError(String), + Engine(String), /// 模型张量维度不匹配 (原有的顶层 DimensionMismatch 被优雅地归入本模块) #[error("模型张量维度不匹配!预期: {expected},实际 Tensor 形状: {actual:?}")] - TensorDimensionMismatch { + DimensionMismatch { expected: String, actual: Vec, }, @@ -169,7 +182,7 @@ pub enum TensorErrorReason { /// 直接包装 ndarray::ShapeError,保留强类型,完美支持 match #[error("OCR Logits 矩阵变形失败: {0}")] LogitsDimensionMismatch(#[from] ndarray::ShapeError), - + /// 张量内存布局不是连续的 #[error("内存不连续,无法执行零拷贝操作")] NonContiguousMemory, @@ -184,10 +197,10 @@ pub enum TensorErrorReason { // ===================================================================== #[derive(Error, Debug)] -pub enum DecodeReason { +pub enum DecodeError { /// CTC 解码器解码过程中的逻辑报错 #[error("CTC 解码异常: {0}")] - CtcDecodeError(String), + Ctc(String), } // ===================================================================== @@ -223,7 +236,7 @@ impl DdddError { pub fn is_invalid_dimensions(&self) -> bool { matches!( self, - DdddError::Preprocess(ImagePreprocessReason::InvalidImageDimensions { .. }) + DdddError::Preprocess(ImagePreprocessError::InvalidDimensions { .. }) ) } @@ -231,22 +244,21 @@ impl DdddError { pub fn is_unsupported_channels(&self) -> bool { matches!( self, - DdddError::Preprocess(ImagePreprocessReason::UnsupportedChannels(_)) + DdddError::Preprocess(ImagePreprocessError::UnsupportedChannels(_)) ) } - /// 提取出底层最原始的那个错误(无论是 IO、预处理、推理、还是第三方扩展错误) - /// 方便外层统一打印更深层的 `source` 链条 - pub fn source_error(&self) -> Option<&(dyn std::error::Error + 'static)> { - use std::error::Error; - match self { - // DdddError::Io(err) => Some(err), - DdddError::Preprocess(err) => Some(err), - DdddError::Inference(err) => Some(err), - DdddError::Decode(err) => Some(err), - DdddError::Other(err) => Some(err.as_ref()), - DdddError::Internal(_) => None, // Internal 内部目前只有 String,没有底层的 Error source - } - } - + // 提取出底层最原始的那个错误(无论是 IO、预处理、推理、还是第三方扩展错误) + // 方便外层统一打印更深层的 `source` 链条 + // pub fn source_error(&self) -> Option<&(dyn std::error::Error + 'static)> { + // use std::error::Error; + // match self { + // // DdddError::Io(err) => Some(err), + // DdddError::Preprocess(err) => Some(err), + // DdddError::Inference(err) => Some(err), + // DdddError::Decode(err) => Some(err), + // DdddError::Other(err) => Some(err.as_ref()), + // DdddError::Internal(_) => None, // Internal 内部目前只有 String,没有底层的 Error source + // } + // } } diff --git a/ddddocr-core/src/ocr/color_filter.rs b/ddddocr-core/src/ocr/color_filter.rs index da18be5..f37884b 100644 --- a/ddddocr-core/src/ocr/color_filter.rs +++ b/ddddocr-core/src/ocr/color_filter.rs @@ -1,5 +1,5 @@ +use crate::error::{ImagePreprocessError, Result}; use crate::utils::image_processor::rgb_to_opencv_hsv; -use anyhow::anyhow; use image::{DynamicImage, ImageBuffer, Rgb}; use std::str::FromStr; @@ -18,12 +18,13 @@ fn is_pixel_matched(ranges: &[HsvRange], h: u8, s: u8, v: u8) -> bool { pub fn apply_to_image( image: &DynamicImage, hsv_ranges: &[HsvRange], -) -> anyhow::Result { +) -> Result { // 1. 统一转换为连续内存的 RGB8 缓冲区 (对应 Python 的 Image 到 RGB/BGR 数组转换) let rgb_img = image.to_rgb8(); let (width, height) = rgb_img.dimensions(); let mut raw_pixels = rgb_img.into_raw(); - + let actual_len = raw_pixels.len(); + let expected_len = (width as usize) * (height as usize) * 3; // 2. 密集计算核心:原地流式迭代修改 // 每次取出 3 个 u8 字节,分别代表 [R, G, B],无多余掩膜矩阵内存分配 for chunk in raw_pixels.chunks_exact_mut(3) { @@ -45,8 +46,14 @@ pub fn apply_to_image( // 3. 将扁平字节数组重新打包回 DynamicImage 容器 let filtered_buffer = ImageBuffer::, Vec>::from_raw(width, height, raw_pixels) - .ok_or_else(|| anyhow!("图像缓冲重新组装失败,维度与数据大小不匹配"))?; - + // .ok_or_else(|| anyhow!("图像缓冲重新组装失败,维度与数据大小不匹配"))?; + .ok_or_else(|| ImagePreprocessError::BufferLengthMismatch { + expected: expected_len, + actual: actual_len, + width, + height, + channels: 3, + })?; Ok(DynamicImage::ImageRgb8(filtered_buffer)) } @@ -64,16 +71,22 @@ impl HsvRange { impl HsvRange { /// 验证当前 HSV 范围是否合法 /// 对应 Python 逻辑:H 在 0-180,S/V 在 0-255,且下界 <= 上界 - pub fn validate(&self) -> Result<(), String> { + pub fn validate(&self) -> Result<(), ImagePreprocessError> { // 1. 校验 H 通道边界 (OpenCV 中 H 范围是 0-180) if self.lower.0 > 180 || self.upper.0 > 180 { - return Err("H通道值必须在 0-180 范围内".to_string()); + // return Err("H通道值必须在 0-180 范围内".to_string()); + return Err(ImagePreprocessError::InvalidHsvRange( + "H通道值必须在 0-180 范围内".to_string(), + )); } // 2. 校验下界不能大于上界 if self.lower.0 > self.upper.0 || self.lower.1 > self.upper.1 || self.lower.2 > self.upper.2 { - return Err("HSV范围下界不能大于上界".to_string()); + // return Err("HSV范围下界不能大于上界".to_string()); + return Err(ImagePreprocessError::InvalidHsvRange( + "HSV范围下界不能大于上界".to_string(), + )); } Ok(()) @@ -150,7 +163,7 @@ impl ColorPreset { } } /// 校验逻辑:在这里实现完美的“责任分离” - pub fn validate(&self) -> Result<(), String> { + pub fn validate(&self) -> Result<(), ImagePreprocessError> { match self { // 1. 快捷变体:完全绕过,根本不校验,0 运行时开销放行! ColorPreset::Custom(ranges) => { @@ -166,7 +179,7 @@ impl ColorPreset { } impl FromStr for ColorPreset { - type Err = String; + type Err = ImagePreprocessError; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "red" => Ok(ColorPreset::Red), @@ -179,7 +192,8 @@ impl FromStr for ColorPreset { "black" => Ok(ColorPreset::Black), "white" => Ok(ColorPreset::White), "gray" => Ok(ColorPreset::Gray), - _ => Err(format!("不支持的颜色预设: {}", s)), + // _ => Err(format!("不支持的颜色预设: {}", s)), + _ => Err(ImagePreprocessError::UnknownColorPreset(s.to_string())), } } } @@ -200,12 +214,12 @@ pub trait ColorFilter { fn estimated_count(&self) -> usize; /// 将自身的有效约束平铺追加到统一目标容器中 /// 验证当前过滤器是否合法,默认直接放行(Ok(())) - fn validate_self(&self) -> Result<(), String> { + fn validate_self(&self) -> Result<(), ImagePreprocessError> { Ok(()) } /// 【新扩展的架构方法】将自身安全的合并到已有的普通容器中,并完成去重和排序 /// 完美的责任分离:Builder 不再需要关心怎么分配内存、怎么排序去重 - fn collect_to_vec(&self) -> Result>, String> { + fn collect_to_vec(&self) -> Result>, ImagePreprocessError> { // 1. 触发自检 self.validate_self()?; @@ -237,7 +251,7 @@ impl ColorFilter for ColorPreset { // 直接获取切片长度 self.matches().len() } - fn validate_self(&self) -> Result<(), String> { + fn validate_self(&self) -> Result<(), ImagePreprocessError> { // 直接调用我们在第一步中为 ColorPreset 实现的精细化分流校验 // 快捷变体在这里会直接返回 Ok(()), 只有 Custom 才会去真正校验 self.validate() @@ -261,7 +275,7 @@ impl<'a> ColorFilter for MultiOrColorRestrict<'a> { self.filters.iter().map(|f| f.estimated_count()).sum() } - fn validate_self(&self) -> Result<(), String> { + fn validate_self(&self) -> Result<(), ImagePreprocessError> { // 递归政审:只要其中一个子过滤器校验失败(比如某个 Custom 变体非法),立刻熔断 for f in &self.filters { f.validate_self()?; diff --git a/ddddocr-core/src/ocr/executor.rs b/ddddocr-core/src/ocr/executor.rs index eeb0d6e..bde2df3 100644 --- a/ddddocr-core/src/ocr/executor.rs +++ b/ddddocr-core/src/ocr/executor.rs @@ -4,7 +4,6 @@ use crate::ocr::color_filter::{HsvRange, apply_to_image}; // use ddddocr_tract::session::{ModelOutput, OcrSession}; use crate::utils::image_convert::png_rgba_white_preprocess; use crate::utils::image_processor::{convert_to_grayscale, resize_image}; -use anyhow::Result; use image::DynamicImage; use serde::Serialize; use std::borrow::Cow; @@ -17,7 +16,9 @@ use ndarray::ArrayView2; // Indices(ndarray::Array1), // 拥有完整所有权的 1维数组,可任意传递和返回 // Logits(ndarray::Array2), // 拥有完整所有权的 2维矩阵,可任意传递和返回 // } +use crate::error::{ImagePreprocessError, Result, TensorError}; use crate::{OcrEngine, OcrOutput}; +use tracing::{ warn}; #[derive(Debug, Clone, Serialize)] pub enum OcrResult { /// 纯文本分支(对应 probability = false) @@ -107,7 +108,7 @@ pub struct Ocr<'a> { pub(crate) png_fix: bool, pub(crate) probability: bool, /// 颜色过滤:保留的颜色列表 - pub(crate) final_color_ranges: Result>, String>, + pub(crate) final_color_ranges: Result>, ImagePreprocessError>, /// 字符集范围 pub(crate) final_charset_indices: Option>, @@ -127,7 +128,7 @@ impl<'a> Ocr<'a> { } } impl<'a> Ocr<'a> { - pub fn predict(&self, image: &DynamicImage) -> anyhow::Result { + pub fn predict(&self, image: &DynamicImage) -> Result { println!("当前颜色过滤器状态: {:?}", self.final_color_ranges); // ===================================================================== @@ -137,10 +138,13 @@ impl<'a> Ocr<'a> { // ===================================================================== let img_cow = match &self.final_color_ranges { Err(err_msg) => { - return Err(anyhow::anyhow!( - "颜色过滤器初始化失败,全链路短路: {}", - err_msg - )); + // return Err(anyhow::anyhow!( + // "颜色过滤器初始化失败,全链路短路: {}", + // err_msg + // )); + return Err(ImagePreprocessError::FilterConfigInvalid( + err_msg.to_string(), + ))?; } Ok(None) => { // 核心优化点:直接借用原图,不发生任何克隆 @@ -168,12 +172,12 @@ impl<'a> Ocr<'a> { // let raw_indices = self.ocr.extract_indices_from_tensor(&raw_tensor)?; // // 步骤 2: 将索引切片 `&[i64]` 传给解码器进行 CTC 去重和字符映射 // let final_text = self.ctc_decode_to_string(&raw_indices); - let ocr_output = self.process_model_output(raw_tensor); - ocr_output + let ocr_output = self.process_model_output(raw_tensor)?; + Ok(ocr_output) } /// 对应 Python 的 _preprocess_image /// 负责:透明背景修复 -> 灰度化 -> 按比例 Resize -> 归一化 -> 4维张量转换 - fn preprocess_image(&self, img: &DynamicImage) -> anyhow::Result> { + fn preprocess_image(&self, img: &DynamicImage) -> Result,ImagePreprocessError> { // 1. 获取模型元数据配置 let meta = self.session.metadata(); let norm = &meta.normalization; // 获取归一化器 @@ -239,7 +243,12 @@ impl<'a> Ocr<'a> { array } - _ => return Err(anyhow::anyhow!("不支持的通道数配置: {}", meta.channel)), + // _ => return Err(anyhow::anyhow!("不支持的通道数配置: {}", meta.channel)), + _ => { + return Err(ImagePreprocessError::UnsupportedChannels( + meta.channel as usize, + )); + } }; Ok(array4) // Ok(tensor) @@ -265,13 +274,14 @@ impl<'a> Ocr<'a> { } // 这段代码未来直接放入 ddddocr-core - fn process_model_output(&self, output: OcrOutput) -> anyhow::Result { + fn process_model_output(&self, output: OcrOutput) -> Result { match output { OcrOutput::Indices(array1) => { // 对应你原来的 process_i64_tensor let slice = array1 .as_slice() - .ok_or_else(|| anyhow::anyhow!("内存不连续,无法执行零拷贝解码"))?; + // .ok_or_else(|| anyhow::anyhow!("内存不连续,无法执行零拷贝解码"))?; + .ok_or_else(|| TensorError::NonContiguousMemory)?; let final_text = self.ctc_decode_to_string(slice); if self.probability { @@ -528,8 +538,9 @@ impl<'a> Ocr<'a> { // 5. 字符映射 if let Some(char_str) = tokens.get(u_idx) { res.push_str(char_str); - } else { - eprintln!("警告: 预测索引 {} 超出字符集范围", u_idx); + } + else { + warn!("警告: 预测索引 {} 超出字符集范围", u_idx); } } res diff --git a/ddddocr-core/src/ocr/metadata.rs b/ddddocr-core/src/ocr/metadata.rs index cc4e61f..285399c 100644 --- a/ddddocr-core/src/ocr/metadata.rs +++ b/ddddocr-core/src/ocr/metadata.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use crate::error::{ Result}; use serde::Deserialize; use std::borrow::Cow; use std::collections::HashMap; diff --git a/ddddocr-core/src/utils/image_convert.rs b/ddddocr-core/src/utils/image_convert.rs index c4a0e81..30808ce 100644 --- a/ddddocr-core/src/utils/image_convert.rs +++ b/ddddocr-core/src/utils/image_convert.rs @@ -1,4 +1,4 @@ -use crate::error::{DdddError, ImagePreprocessReason, Result}; +use crate::error::{DdddError, ImagePreprocessError, Result}; use image::{DynamicImage, GenericImageView, ImageBuffer, Luma, Rgb, Rgba}; use ndarray::{Array3, ArrayViewD}; @@ -35,19 +35,16 @@ pub fn ndarray_to_hwc_image(array: ArrayViewD) -> Result { // 对应 Python: array.shape[2] == 4 (RGBA H, W, 4) 4 => ColorMode::RGBA, _ => { - return Err(DdddError::Preprocess( - ImagePreprocessReason::UnsupportedChannels(c), - )); + return Err(ImagePreprocessError::UnsupportedChannels(c))?; } } } _ => { - return Err(DdddError::Preprocess( - ImagePreprocessReason::InvalidImageDimensions { - expected: "2D (H,W) 或 3D (H,W,C)".to_string(), - actual: shape.to_vec(), - }, - )); + return Err(ImagePreprocessError::InvalidDimensions { + expected: "2D (H,W) 或 3D (H,W,C)".to_string(), + actual: shape.to_vec(), + } + .into()); } }; from_ndarray(array, color_mode) @@ -113,7 +110,7 @@ pub fn image_to_ndarray(image: &DynamicImage, mode: ColorMode) -> Result, mode: ColorMode) -> Result, mode: ColorMode) -> Result // 构造通用错误闭包,避免 match 分支中重复编写冗长的错误对象 let make_err = || { - DdddError::Preprocess(ImagePreprocessReason::BufferLengthMismatch { + ImagePreprocessError::BufferLengthMismatch { expected: expected_len, actual: raw_len, width, height, channels, - }) + } + .into() }; // 2. 重新解释内存并构建 ImageBuffer diff --git a/ddddocr-core/src/utils/image_helper.rs b/ddddocr-core/src/utils/image_helper.rs index 8d46de3..8cec67d 100644 --- a/ddddocr-core/src/utils/image_helper.rs +++ b/ddddocr-core/src/utils/image_helper.rs @@ -1,6 +1,6 @@ -use crate::error::{DdddError, ImagePreprocessReason, Result}; +use crate::error::{DdddError, Result}; use crate::utils::image_convert::ndarray_to_hwc_image; -use base64::{Engine as _, engine::general_purpose}; +use base64::{engine::general_purpose, Engine as _}; use image::DynamicImage; use ndarray::ArrayViewD; use std::fmt; diff --git a/ddddocr-core/src/utils/tensor_transform.rs b/ddddocr-core/src/utils/tensor_transform.rs index 268a26d..deec7c6 100644 --- a/ddddocr-core/src/utils/tensor_transform.rs +++ b/ddddocr-core/src/utils/tensor_transform.rs @@ -1,8 +1,8 @@ use crate::OcrOutput; -use crate::error::{DdddError, Result, TensorErrorReason}; +use crate::error::{DdddError, Result, TensorError}; use ndarray::s; /// 核心层复用资产:将异构的动态维度矩阵转化为标准 OCR 2D Logits 矩阵 -pub fn normalize_ocr_logits(array: ndarray::ArrayViewD, shape: &[usize]) -> Result { +pub fn normalize_ocr_logits(array: ndarray::ArrayViewD, shape: &[usize]) -> Result { let (steps, classes, data_dyn_view) = match shape.len() { 3 => { if shape[1] == 1 { @@ -24,12 +24,12 @@ pub fn normalize_ocr_logits(array: ndarray::ArrayViewD, shape: &[usize]) -> // 我们把它虚构成一个 [1, Classes] 的 2D 矩阵来复用后面的 argmax 逻辑 1 => (1, shape[0], array), _ => { - return Err(DdddError::Inference( - TensorErrorReason::TensorDimensionMismatch { + return Err( + TensorError::DimensionMismatch { expected: "1D, 2D, or 3D OCR Logits".to_string(), actual: shape.to_vec(), }, - )); + ); } }; @@ -39,10 +39,10 @@ pub fn normalize_ocr_logits(array: ndarray::ArrayViewD, shape: &[usize]) -> .map_err(|shape_err| { // 如果是因为切片导致不连续且无法进行零拷贝变换,抛出 NonContiguousMemory if !data_dyn_view.is_standard_layout() { - DdddError::Inference(TensorErrorReason::NonContiguousMemory) + TensorError::NonContiguousMemory } else { // 否则,说明是纯粹的数据元素数量不对(Shape 不匹配),抛出专属的强类型错误 - DdddError::Inference(TensorErrorReason::LogitsDimensionMismatch(shape_err)) + TensorError::LogitsDimensionMismatch(shape_err) } })? .to_owned(); diff --git a/ddddocr-tract/src/ocr/session.rs b/ddddocr-tract/src/ocr/session.rs index 311f83d..bb55dfc 100644 --- a/ddddocr-tract/src/ocr/session.rs +++ b/ddddocr-tract/src/ocr/session.rs @@ -1,6 +1,6 @@ use crate::loader::ModelLoader; use anyhow::Context; -use ddddocr_core::error::{DdddError, Result, TensorErrorReason}; +use ddddocr_core::error::{DdddError, Result, TensorError}; use ddddocr_core::utils::normalize_ocr_logits; use ddddocr_core::{InferenceEngine, ModelMetadata, OcrEngine, OcrOutput}; use ndarray::s; @@ -49,7 +49,7 @@ impl InferenceEngine for OcrSession { .session .run(tvec!(tensor.into())) .map_err(|_| { - DdddError::Inference(TensorErrorReason::EngineError( + DdddError::Inference(TensorError::EngineError( "执行模型推理失败".to_string(), )) })?;