1use thiserror::Error;
7
8pub type Result<T> = core::result::Result<T, MlError>;
10
11#[derive(Debug, Error)]
13pub enum MlError {
14 #[error("Model error: {0}")]
16 Model(#[from] ModelError),
17
18 #[error("Inference error: {0}")]
20 Inference(#[from] InferenceError),
21
22 #[error("Preprocessing error: {0}")]
24 Preprocessing(#[from] PreprocessingError),
25
26 #[error("Postprocessing error: {0}")]
28 Postprocessing(#[from] PostprocessingError),
29
30 #[error("OxiGDAL error: {0}")]
32 OxiGdal(#[from] oxigdal_core::OxiGdalError),
33
34 #[error("ONNX Runtime error: {0}")]
36 Ort(String),
37
38 #[error("I/O error: {0}")]
40 Io(#[from] std::io::Error),
41
42 #[error("Serialization error: {0}")]
44 Serialization(#[from] serde_json::Error),
45
46 #[error("Invalid configuration: {0}")]
48 InvalidConfig(String),
49
50 #[error("Feature not available: {feature}. Enable with feature flag: {flag}")]
52 FeatureNotAvailable {
53 feature: String,
55 flag: String,
57 },
58}
59
60#[derive(Debug, Error)]
62pub enum ModelError {
63 #[error("Model file not found: {path}")]
65 NotFound {
66 path: String,
68 },
69
70 #[error("Failed to load model: {reason}")]
72 LoadFailed {
73 reason: String,
75 },
76
77 #[error("Invalid model format: {message}")]
79 InvalidFormat {
80 message: String,
82 },
83
84 #[error("Model initialization failed: {reason}")]
86 InitializationFailed {
87 reason: String,
89 },
90
91 #[error("Incompatible model version: expected {expected}, got {actual}")]
93 IncompatibleVersion {
94 expected: String,
96 actual: String,
98 },
99
100 #[error("Missing required model input: {input_name}")]
102 MissingInput {
103 input_name: String,
105 },
106
107 #[error("Missing required model output: {output_name}")]
109 MissingOutput {
110 output_name: String,
112 },
113}
114
115#[derive(Debug, Error)]
117pub enum InferenceError {
118 #[error("Invalid input shape: expected {expected:?}, got {actual:?}")]
120 InvalidInputShape {
121 expected: Vec<usize>,
123 actual: Vec<usize>,
125 },
126
127 #[error("Invalid input type: expected {expected}, got {actual}")]
129 InvalidInputType {
130 expected: String,
132 actual: String,
134 },
135
136 #[error(
143 "Band count mismatch: model expects {expected} channel(s) but the input buffer supplies {actual}"
144 )]
145 InvalidBandCount {
146 expected: usize,
148 actual: usize,
150 },
151
152 #[error("Batch size mismatch: expected {expected}, got {actual}")]
154 BatchSizeMismatch {
155 expected: usize,
157 actual: usize,
159 },
160
161 #[error("Inference failed: {reason}")]
163 Failed {
164 reason: String,
166 },
167
168 #[error("Failed to parse output: {reason}")]
170 OutputParsingFailed {
171 reason: String,
173 },
174
175 #[error("GPU acceleration requested but not available: {message}")]
177 GpuNotAvailable {
178 message: String,
180 },
181}
182
183#[derive(Debug, Error)]
185pub enum PreprocessingError {
186 #[error("Invalid normalization parameters: {message}")]
188 InvalidNormalization {
189 message: String,
191 },
192
193 #[error("Tiling failed: {reason}")]
195 TilingFailed {
196 reason: String,
198 },
199
200 #[error("Padding failed: {reason}")]
202 PaddingFailed {
203 reason: String,
205 },
206
207 #[error("Invalid tile size: width={width}, height={height}")]
209 InvalidTileSize {
210 width: usize,
212 height: usize,
214 },
215
216 #[error("Channel mismatch: expected {expected}, got {actual}")]
218 ChannelMismatch {
219 expected: usize,
221 actual: usize,
223 },
224
225 #[error("Data augmentation failed: {reason}")]
227 AugmentationFailed {
228 reason: String,
230 },
231}
232
233#[derive(Debug, Error)]
235pub enum PostprocessingError {
236 #[error("Tile merging failed: {reason}")]
238 MergingFailed {
239 reason: String,
241 },
242
243 #[error("Threshold out of range: must be between 0.0 and 1.0, got {value}")]
245 InvalidThreshold {
246 value: f32,
248 },
249
250 #[error("Polygon conversion failed: {reason}")]
252 PolygonConversionFailed {
253 reason: String,
255 },
256
257 #[error("Non-maximum suppression failed: {reason}")]
259 NmsFailed {
260 reason: String,
262 },
263
264 #[error("Export failed: {reason}")]
266 ExportFailed {
267 reason: String,
269 },
270
271 #[error("Invalid class ID: {class_id}")]
273 InvalidClassId {
274 class_id: usize,
276 },
277}
278
279impl From<oxionnx::OnnxError> for MlError {
280 fn from(err: oxionnx::OnnxError) -> Self {
281 MlError::Ort(err.to_string())
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn test_error_display() {
291 let err = ModelError::NotFound {
292 path: "/path/to/model.onnx".to_string(),
293 };
294 assert!(err.to_string().contains("Model file not found"));
295 assert!(err.to_string().contains("/path/to/model.onnx"));
296 }
297
298 #[test]
299 fn test_error_conversion() {
300 let model_err = ModelError::LoadFailed {
301 reason: "test".to_string(),
302 };
303 let ml_err: MlError = model_err.into();
304 assert!(matches!(ml_err, MlError::Model(_)));
305 }
306
307 #[test]
308 fn test_invalid_threshold() {
309 let err = PostprocessingError::InvalidThreshold { value: 1.5 };
310 assert!(err.to_string().contains("1.5"));
311 }
312}