Skip to main content

xq_vision/
lib.rs

1//! High-performance Chinese chessboard vision primitives and ONNX inference.
2//!
3//! The crate exposes a compact end-to-end API through [`XqVision`] while keeping
4//! the board detector, piece recognizer, and board warp operation reusable for
5//! callers that need finer control.
6//!
7//! ```no_run
8//! use xq_vision::{ModelSource, XqVision};
9//!
10//! # fn main() -> xq_vision::Result<()> {
11//! let mut vision = XqVision::builder()
12//!     .board_model(ModelSource::file("models/board.onnx"))
13//!     .piece_model(ModelSource::file("models/piece.onnx"))
14//!     .build()?;
15//!
16//! let image = image::open("board.jpg")?.to_rgb8();
17//! let result = vision.recognize(&image)?;
18//! println!("{}", result.to_fen());
19//! # Ok(())
20//! # }
21//! ```
22//!
23//! # Concurrency
24//!
25//! [`XqVision`] (along with [`BoardDetector`] and [`PieceRecognizer`]) is `Send`
26//! but **intentionally not `Sync`**. `recognize` takes `&mut self` because it
27//! reuses an internal tensor scratch buffer between calls, so an instance is
28//! owned by exactly one task at a time.
29//!
30//! **Recommended async model** — give each worker its own `XqVision`:
31//!
32//! ```no_run
33//! # use xq_vision::{ModelSource, XqVision};
34//! # async fn run() -> xq_vision::Result<()> {
35//! // Share model bytes cheaply via Arc; each task builds its own session.
36//! let board_bytes: std::sync::Arc<[u8]> = std::fs::read("models/board.onnx")?.into();
37//! let piece_bytes: std::sync::Arc<[u8]> = std::fs::read("models/piece.onnx")?.into();
38//!
39//! let mut vision = XqVision::builder()
40//!     .board_model(ModelSource::Memory(board_bytes.clone()))
41//!     .piece_model(ModelSource::Memory(piece_bytes.clone()))
42//!     .build()?;
43//! // ...use `vision` from a single task...
44//! # let _ = &mut vision;
45//! # Ok(())
46//! # }
47//! ```
48//!
49//! If a single instance must be shared, wrap it in `tokio::sync::Mutex<XqVision>`
50//! or `std::sync::Mutex<XqVision>` — inference will be serialized.
51
52mod board;
53mod config;
54mod error;
55mod fast_path;
56mod geometry;
57mod image_ops;
58mod pieces;
59mod session;
60mod types;
61mod vision;
62
63pub use board::BOARD_CORNER_NAMES;
64pub use board::BoardDetection;
65pub use board::BoardDetector;
66pub use board::BoardDetectorConfig;
67pub use board::warp_board;
68pub use config::GraphOptimization;
69pub use config::ModelSource;
70pub use config::SessionConfig;
71pub use error::Result;
72pub use error::XqVisionError;
73pub use pieces::PIECE_SHORT;
74pub use pieces::PieceRecognition;
75pub use pieces::PieceRecognizer;
76pub use pieces::PieceRecognizerConfig;
77pub use pieces::PieceSnapshot;
78pub use session::ExecutionProvider;
79pub use session::ProviderFailure;
80pub use types::BoardCoord;
81pub use types::BoardCorners;
82pub use types::BoardImage;
83pub use types::CellPrediction;
84pub use types::PieceKind;
85pub use types::Point2f;
86pub use types::RecognitionResult;
87pub use types::RectF;
88pub use types::Side;
89pub use vision::XqVision;
90pub use vision::XqVisionBuilder;
91
92#[cfg(feature = "bench-support")]
93#[doc(hidden)]
94pub mod bench_support {
95    use image::RgbImage;
96
97    use crate::Result;
98    use crate::fast_path::normalize_rgb_to_chw;
99    use crate::pieces::PieceRecognition;
100    use crate::pieces::PieceRecognizer;
101    use crate::types::BOARD_CELLS;
102    use crate::types::PIECE_CLASSES;
103
104    #[must_use]
105    pub fn normalize_for_bench(image: &RgbImage) -> Vec<f32> {
106        let mut out = Vec::new();
107        normalize_rgb_to_chw(image.as_raw(), image.width() as usize, image.height() as usize, &mut out);
108        out
109    }
110
111    pub fn decode_logits_for_bench(logits: &[f32]) -> Result<PieceRecognition> {
112        PieceRecognizer::decode_logits(logits)
113    }
114
115    #[must_use]
116    pub fn zero_logits_for_bench() -> Vec<f32> { vec![0.0; BOARD_CELLS * PIECE_CLASSES] }
117}
118
119const _: fn() = || {
120    fn assert_send<T: Send>() {}
121    assert_send::<XqVision>();
122    assert_send::<BoardDetector>();
123    assert_send::<PieceRecognizer>();
124};
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn public_builder_default_providers_end_with_cpu() {
132        let builder = XqVision::builder()
133            .board_model(ModelSource::memory([1_u8, 2, 3]))
134            .piece_model(ModelSource::memory([4_u8, 5, 6]));
135        let providers = builder.session_config().execution_providers();
136        assert_eq!(providers.last(), Some(&ExecutionProvider::Cpu), "Cpu must always be the final fallback provider");
137    }
138}