From 7ee1c2bed18aef8700193a53ca455795467f42af Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 5 Aug 2026 13:13:44 +0200 Subject: [PATCH 1/3] feat(video,calib3d): compile both modules without the standard library MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 (#85) concluded the exploration with "port both": the audit found no hard blockers — calib3d RANSAC uses its own self-contained LCG PRNG (not the std-gated thread-local RNG), and neither module uses HashMap/Instant/ threads. Un-gates `video` and `calib3d` (and their prelude re-exports). Refs #85 Co-Authored-By: Claude Fable 5 --- src/lib.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6f5564b..8db70a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,9 +47,8 @@ pub use alloc::format as __format; // Global modules // -// `core`, `imgproc`, and `version` build without `std`; the remaining modules -// are gated until their own no_std phases land (see issue #82). -#[cfg(feature = "std")] +// `core`, `imgproc`, `calib3d`, `video`, and `version` build without `std`; the +// `features`/`features2d` modules remain std-gated (see issue #82). pub mod calib3d; pub mod core; #[cfg(feature = "std")] @@ -58,12 +57,10 @@ pub mod features; pub mod features2d; pub mod imgproc; pub mod version; -#[cfg(feature = "std")] pub mod video; /// Prelude to easily import common structures pub mod prelude { - #[cfg(feature = "std")] pub use crate::calib3d::{ find_fundamental_mat, find_homography, init_undistort_rectify_map, rodrigues, solve_pnp, solve_pnp_ransac, FundamentalMatMethod, HomographyMethod, SolvePnPMethod, @@ -91,7 +88,6 @@ pub mod prelude { pub use crate::imgproc::{ cvt_color, remap, warp_perspective, ColorConversionCode, InterpolationFlags, }; - #[cfg(feature = "std")] pub use crate::video::optical_flow::{ build_optical_flow_pyramid, calc_optical_flow_pyramid_lk, OpticalFlowPyramid, OPTFLOW_LK_GET_MIN_EIGENVALS, OPTFLOW_USE_INITIAL_FLOW, From 863f41cdaf0e0a8aaf9cc2de95ed558e5e7173c3 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 5 Aug 2026 13:14:01 +0200 Subject: [PATCH 2/3] refactor(video,calib3d): use core::/alloc:: paths for no_std Mechanical std::->core:: conversion, alloc imports (vec/Vec/format/ToString), and num_traits::Float where concrete f32/f64 math is used. optical_flow's SIMD-only vec! import is gated behind the simd feature. No behavior change. Refs #85 Co-Authored-By: Claude Fable 5 --- src/calib3d/fundamental.rs | 4 ++++ src/calib3d/geometry.rs | 6 +++++- src/calib3d/homography.rs | 6 +++++- src/calib3d/linalg.rs | 8 ++++++-- src/calib3d/pose.rs | 4 ++++ src/calib3d/undistort.rs | 4 ++++ src/video/optical_flow.rs | 7 +++++++ src/video/simd.rs | 3 +++ 8 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/calib3d/fundamental.rs b/src/calib3d/fundamental.rs index 67bc198..8a4b58e 100644 --- a/src/calib3d/fundamental.rs +++ b/src/calib3d/fundamental.rs @@ -34,6 +34,10 @@ * */ +use alloc::{string::ToString, vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use super::linalg::{mat3_mul, null_space_vector, svd_3x3, Lcg}; use crate::core::error::{PureCvError, Result}; use crate::core::types::Point2f; diff --git a/src/calib3d/geometry.rs b/src/calib3d/geometry.rs index 41a8828..6361a66 100644 --- a/src/calib3d/geometry.rs +++ b/src/calib3d/geometry.rs @@ -39,6 +39,10 @@ //! Converts between a *rotation vector* (compact axis-angle representation, //! sometimes called an *Rodrigues vector*) and a 3×3 *rotation matrix*. +use alloc::{string::ToString, vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::Matrix; @@ -166,7 +170,7 @@ pub(super) fn rmat_to_rvec(m: &[f64; 9]) -> [f64; 3] { } // Near π the formula becomes numerically unstable; use an alternative. - if (theta - std::f64::consts::PI).abs() < 1e-4 { + if (theta - core::f64::consts::PI).abs() < 1e-4 { return rmat_to_rvec_near_pi(m, theta); } diff --git a/src/calib3d/homography.rs b/src/calib3d/homography.rs index 203f89e..936b04c 100644 --- a/src/calib3d/homography.rs +++ b/src/calib3d/homography.rs @@ -40,6 +40,10 @@ //! point normalization, plus an optional RANSAC wrapper for robustness //! against outliers. +use alloc::{format, string::ToString, vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::types::Point2f; use crate::core::Matrix; @@ -359,7 +363,7 @@ fn normalize_points(pts: &[Point2f]) -> (Vec, [f64; 9]) { let s = if mean_dist < 1e-12 { 1.0 } else { - std::f64::consts::SQRT_2 / mean_dist + core::f64::consts::SQRT_2 / mean_dist }; let mut out = vec![0.0f64; pts.len() * 2]; diff --git a/src/calib3d/linalg.rs b/src/calib3d/linalg.rs index 9c9f78e..48dbb29 100644 --- a/src/calib3d/linalg.rs +++ b/src/calib3d/linalg.rs @@ -38,6 +38,10 @@ //! //! All functions operate on flat, row-major `f64` slices/arrays. +use alloc::{vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + // --------------------------------------------------------------------------- // Matrix utilities // --------------------------------------------------------------------------- @@ -204,7 +208,7 @@ pub(super) fn null_space_vector(a: &[f64], rows: usize, cols: usize) -> Vec .min_by(|&i, &j| { ata[i * cols + i] .partial_cmp(&ata[j * cols + j]) - .unwrap_or(std::cmp::Ordering::Equal) + .unwrap_or(core::cmp::Ordering::Equal) }) .unwrap_or(cols - 1); @@ -241,7 +245,7 @@ pub(super) fn svd_3x3(a: &[f64; 9]) -> ([f64; 9], [f64; 3], [f64; 9]) { idx.sort_by(|&i, &j| { ata[j * 3 + j] .partial_cmp(&ata[i * 3 + i]) - .unwrap_or(std::cmp::Ordering::Equal) + .unwrap_or(core::cmp::Ordering::Equal) }); // Reorder V columns (right singular vectors). diff --git a/src/calib3d/pose.rs b/src/calib3d/pose.rs index 1532075..793b6bd 100644 --- a/src/calib3d/pose.rs +++ b/src/calib3d/pose.rs @@ -40,6 +40,10 @@ //! camera intrinsic matrix, these functions estimate the object pose //! (rotation and translation vectors) in the camera coordinate system. +use alloc::{format, string::ToString, vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::logging::tags; use crate::core::types::{Point2f, Point3f}; diff --git a/src/calib3d/undistort.rs b/src/calib3d/undistort.rs index 16ddd06..2c17787 100644 --- a/src/calib3d/undistort.rs +++ b/src/calib3d/undistort.rs @@ -34,6 +34,10 @@ * */ +use alloc::string::ToString; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::types::Size2i; use crate::core::Matrix; diff --git a/src/video/optical_flow.rs b/src/video/optical_flow.rs index 30ccc4b..55185e0 100644 --- a/src/video/optical_flow.rs +++ b/src/video/optical_flow.rs @@ -52,6 +52,13 @@ //! | `calcOpticalFlowPyrLK` `nextPts` is `InputOutputArray` | initial guess passed via `initial_next_pts: Option<&[Point2f]>` | //! | `tryReuseInputImage` optimisation flag | not implemented (correctness only) | +use alloc::{string::ToString, vec::Vec}; +// `vec!` is only used by the SIMD-only windowed kernel below. +#[cfg(feature = "simd")] +use alloc::vec; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::logging::tags; use crate::core::types::{BorderTypes, Point2f, Size2i, TermCriteria, TermType}; diff --git a/src/video/simd.rs b/src/video/simd.rs index 560380a..37cf19f 100644 --- a/src/video/simd.rs +++ b/src/video/simd.rs @@ -56,6 +56,9 @@ //! kernels operate *after* the gather, on the pre-collected contiguous `f32` //! buffers, where auto-vectorisation applies cleanly. +#[allow(unused_imports)] +use num_traits::Float; + // --------------------------------------------------------------------------- // H matrix accumulation // --------------------------------------------------------------------------- From f78cf13c4736623430cf640c5ed25c23311944f6 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 5 Aug 2026 13:14:02 +0200 Subject: [PATCH 3/3] test(video,calib3d): smoke-test both modules + document no_std support Adds rodrigues (calib3d) and build_optical_flow_pyramid (video) to the no_std smoke crate, extends the CI job name, and documents the per-module no_std support matrix in the README. Refs #85 Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 2 +- README.md | 25 ++++++++++++++++++++++--- crates/no-std-smoke/src/lib.rs | 24 ++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0056ea..82cd9b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: run: cargo test --workspace --features ndarray no-std-build: - name: no_std Build (core + imgproc, issues #83/#84) + name: no_std Build (core + imgproc + calib3d + video, issues #83/#84/#85) runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 59aaf56..e11fcfe 100644 --- a/README.md +++ b/README.md @@ -91,12 +91,31 @@ purecv = "0.5" | Flag | Default | Description | |------|---------|-------------| -| `std` | ✅ | Standard library support | -| `parallel` | ✅ | Multi-core parallelism via **Rayon** | +| `std` | ✅ | Standard library support (disable for `no_std` — see below) | +| `parallel` | ✅ | Multi-core parallelism via **Rayon** (implies `std`) | | `ndarray` | ❌ | Interop with the `ndarray` crate (zero-cost views & ownership transfers) | -| `simd` | ❌ | SIMD acceleration via [`pulp`](https://crates.io/crates/pulp) (x86 SSE/AVX, ARM NEON, WASM `simd128`) | +| `simd` | ❌ | SIMD acceleration via [`pulp`](https://crates.io/crates/pulp) (x86 SSE/AVX, ARM NEON, WASM `simd128`) — implies `std` | | `wasm` | ❌ | WebAssembly-specific optimizations | +### `no_std` / embedded support + +Build with `--no-default-features` to run on bare-metal targets such as the +ESP32 (`purecv = { version = "0.6", default-features = false }`). Only `core` +and `alloc` are required (an allocator must be provided by the target). + +| Module | `no_std` | Notes | +|--------|----------|-------| +| `core` | ✅ | Full support. `get_tick_count`/`get_tick_frequency` and the thread-local RNG (`randu`/`randn`/`rand_shuffle`) require `std`. | +| `imgproc` | ✅ | Scalar fallbacks. `hough_lines_p` requires `std` (uses the thread-local RNG); `hough_lines` works without. | +| `calib3d` | ✅ | Full support (RANSAC uses a self-contained PRNG). | +| `video` | ✅ | Full support. Optical-flow pyramids are heap-heavy — size images for your device's RAM. | +| `features2d` | ❌ | Requires `std` for now. | + +`parallel`, `simd`, `fft`, and `ndarray` require `std`; disabling default +features gives the scalar, single-threaded code paths. See +[`webarkit/purecv-esp32-examples`](https://github.com/webarkit/purecv-esp32-examples) +for runnable ESP32-S3 demos. + To enable the `ndarray` feature: ```toml diff --git a/crates/no-std-smoke/src/lib.rs b/crates/no-std-smoke/src/lib.rs index 329ef09..d3df2a8 100644 --- a/crates/no-std-smoke/src/lib.rs +++ b/crates/no-std-smoke/src/lib.rs @@ -49,10 +49,12 @@ extern crate alloc; use alloc::vec; +use purecv::calib3d::rodrigues; use purecv::core::error::Result; use purecv::core::types::{BorderTypes, Size2i}; use purecv::core::{add, determinant, mean, Matrix}; use purecv::imgproc::gaussian_blur; +use purecv::video::build_optical_flow_pyramid; /// Exercises matrix construction, arithmetic, statistics, and linear algebra. pub fn smoke() -> Result { @@ -83,3 +85,25 @@ pub fn smoke_imgproc() -> Result { )?; Ok(blurred.data[4]) } + +/// Exercises the Phase 3 calib3d path: `rodrigues` (rotation vector -> matrix). +pub fn smoke_calib3d() -> Result { + let rvec = Matrix::::from_vec(3, 1, 1, vec![0.1, 0.2, 0.3]); + let mut rmat = Matrix::::new(3, 3, 1); + rodrigues(&rvec, &mut rmat)?; + Ok(rmat.data[0]) +} + +/// Exercises the Phase 3 video path: build a Lucas-Kanade optical-flow pyramid. +pub fn smoke_video() -> Result { + let img = Matrix::::from_vec(8, 8, 1, vec![0u8; 64]); + let pyr = build_optical_flow_pyramid( + &img, + Size2i::new(3, 3), + 1, + false, + BorderTypes::Reflect101, + BorderTypes::Reflect101, + )?; + Ok(pyr.levels.len()) +}