diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 689c1a6..82cd9b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,34 @@ jobs: - name: Run tests (with ndarray feature) run: cargo test --workspace --features ndarray + no-std-build: + name: no_std Build (core + imgproc + calib3d + video, issues #83/#84/#85) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + targets: thumbv7em-none-eabihf + + - name: Rust Cache + uses: Swatinem/rust-cache@v2 + + - name: Build without default features (host) + run: cargo build --no-default-features + + - name: Lint without default features + run: cargo clippy --no-default-features -- -D warnings + + - name: Build for bare-metal target + run: cargo build --no-default-features --target thumbv7em-none-eabihf + + - name: Build no_std smoke-test consumer + run: cargo build --manifest-path crates/no-std-smoke/Cargo.toml --target thumbv7em-none-eabihf + wasm-build: name: WASM Dual Build runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 621b5eb..2b968da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "purecv" version = "0.6.1" authors = ["Walter Perdan "] edition = "2021" +rust-version = "1.88" description = "A pure Rust, high-performance computer vision library focused on safety and portability." license = "LGPL-2.1-or-later" repository = "https://github.com/webarkit/purecv" @@ -16,11 +17,11 @@ path = "src/lib.rs" [dependencies] rayon = { version = "1.10", optional = true } ndarray = { version = "0.17", optional = true } -num-traits = "0.2" +num-traits = { version = "0.2", default-features = false, features = ["libm"] } pulp = { version = "0.22", optional = true } rustfft = { version = "6", optional = true } num-complex = { version = "0.4", optional = true } -log = "0.4" +log = { version = "0.4", default-features = false } [dev-dependencies] image = "0.25" @@ -28,11 +29,12 @@ criterion = "0.8" [features] default = ["std", "parallel"] -std = [] -parallel = ["rayon"] -ndarray = ["dep:ndarray"] -simd = ["dep:pulp"] -fft = ["dep:rustfft", "dep:num-complex"] +std = ["num-traits/std"] +# The features below require `std` until their own no_std phases land (see issue #82). +parallel = ["dep:rayon", "std"] +ndarray = ["dep:ndarray", "std"] +simd = ["dep:pulp", "std"] +fft = ["dep:rustfft", "dep:num-complex", "std"] transforms = ["fft"] [[bench]] @@ -79,6 +81,9 @@ panic = "abort" [workspace] members = ["crates/wasm"] +# Built separately against bare-metal targets (see the no-std CI job); keeping it +# out of the workspace lets `cargo build --workspace` stay host-only. +exclude = ["crates/no-std-smoke"] [workspace.package] version = "0.6.1" diff --git a/README.md b/README.md index 59aaf56..4fa6042 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ [![GitHub Stars](https://img.shields.io/github/stars/webarkit/purecv.svg?style=social)](https://github.com/webarkit/purecv/stargazers) [![GitHub Forks](https://img.shields.io/github/forks/webarkit/purecv.svg?style=social)](https://github.com/webarkit/purecv/network/members) -A high-performance, **pure Rust** computer vision library focusing on the `core` and `imgproc` modules of OpenCV. **PureCV** is built from the ground up to be memory-safe, thread-safe, and highly portable without the overhead of C++ FFI. +A high-performance, **pure Rust** computer vision library reimplementing the `core`, `imgproc`, `features2d`, `video`, and `calib3d` modules of OpenCV. **PureCV** is built from the ground up to be memory-safe, thread-safe, and highly portable โ€” from desktop and WebAssembly down to `no_std` microcontrollers โ€” without the overhead of C++ FFI. -> This project is currently a **Work in Progress**. While most core and imgproc features have been implemented, the library is not yet stable, and bugs may occur. We are actively optimizing and expanding the feature set. +> This project is currently a **Work in Progress**. While most features across the core, imgproc, features2d, video, and calib3d modules have been implemented, the library is not yet stable, and bugs may occur. We are actively optimizing and expanding the feature set. ## ๐ŸŽฏ Philosophy @@ -21,6 +21,7 @@ Unlike existing wrappers, **PureCV** is a native rewrite. It aims to provide: * **Memory Safety:** Elimination of segmentation faults and buffer overflows via Rust's ownership model. * **Modern Parallelism:** Native integration with **Rayon** for effortless multi-core processing. * **Portable SIMD:** Optional SIMD acceleration via [`pulp`](https://crates.io/crates/pulp) โ€” auto-detects x86 SSE/AVX, ARM NEON, and WASM `simd128` at runtime. Zero `unsafe`, zero `#[cfg(target_arch)]`. +* **Embedded-ready:** Builds under `no_std` + `alloc` for bare-metal targets such as the ESP32 โ€” the `core`, `imgproc`, `calib3d`, and `video` modules run without the standard library ([see below](#no_std--embedded-support)). ## โœจ Features @@ -43,7 +44,7 @@ Unlike existing wrappers, **PureCV** is a native rewrite. It aims to provide: - **Channel Management:** `split`, `merge`, `mix_channels`. - **Utilities:** `add_weighted`, `check_range`, `absdiff`, `get_tick_count`, `get_tick_frequency`. - **Logging** (OpenCV-style): a `cv::utils::logging`-compatible facade over the [`log`](https://crates.io/crates/log) crate โ€” a 7-level `LogLevel` with `set_log_level`/`get_log_level`, per-subsystem `tags`, `cv_log_*!` macros, and `cv_bail!`/`cv_err!` log-and-return helpers used throughout `core` to report invalid input (wrong dimensions, channel mismatches, โ€ฆ). Bring your own backend (`env_logger`, `tracing`, โ€ฆ) or call `init_basic_logger()` for quick stdout output. -- **Mathematical Constants:** OpenCV-compatible constants โ€” `CV_PI`, `CV_PI_2`, `CV_2PI`, `CV_PI_4`, `CV_LOG2`, `CV_LN2`, `CV_E`, `CV_LN10`, `CV_SQRT2` โ€” backed by `std::f64::consts` for maximum precision. +- **Mathematical Constants:** OpenCV-compatible constants โ€” `CV_PI`, `CV_PI_2`, `CV_2PI`, `CV_PI_4`, `CV_LOG2`, `CV_LN2`, `CV_E`, `CV_LN10`, `CV_SQRT2` โ€” backed by `core::f64::consts` for maximum precision (available under `no_std`). - **ndarray Interop:** Optional, zero-cost conversions to/from `ndarray::Array3` via the `ndarray` feature flag. - **SIMD Acceleration** (`simd` feature): Trait-based dispatch via `pulp` for `f32`, `f64`, and `u8` types. Accelerated operations include `add`, `sub`, `mul`, `div`, `min`, `max`, `sqrt`, `dot`, `sum`, `add_weighted`, `convert_scale_abs`, `magnitude`, `simd_row_min_max`, `simd_min_max_col`, `simd_gaussian_5tap_h/v`, and `simd_remap_bilinear_row`/`simd_remap_nearest_row`. Falls back to scalar loops at zero cost when disabled. @@ -84,31 +85,77 @@ Add the following to your `Cargo.toml`: ```toml [dependencies] -purecv = "0.5" +purecv = "0.6" ``` +PureCV's minimum supported Rust version (MSRV) is **1.88**. + ### Feature Flags | 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. + +```toml +[dependencies] +purecv = { version = "0.6", default-features = false } +``` + +```rust +#![no_std] +extern crate alloc; // an allocator must be provided by your target + +use alloc::vec; +use purecv::core::{add, Matrix}; +use purecv::imgproc::gaussian_blur; +use purecv::core::types::{BorderTypes, Size2i}; + +// core arithmetic, no std +let a = Matrix::::from_vec(2, 2, 1, vec![1.0, 2.0, 3.0, 4.0]); +let b = Matrix::::from_vec(2, 2, 1, vec![5.0, 6.0, 7.0, 8.0]); +let sum = add(&a, &b)?; + +// imgproc under no_std (scalar fallback) +let blurred = gaussian_blur(&sum, Size2i::new(3, 3), 0.0, 0.0, BorderTypes::Reflect101)?; +``` + +See [`webarkit/purecv-esp32-examples`](https://github.com/webarkit/purecv-esp32-examples) +for runnable ESP32-S3 demos (matrix arithmetic, Gaussian blur, and `solve_pnp` +camera pose estimation). + To enable the `ndarray` feature: ```toml [dependencies] -purecv = { version = "0.5", features = ["ndarray"] } +purecv = { version = "0.6", features = ["ndarray"] } ``` To enable SIMD + Parallel for maximum performance: ```toml [dependencies] -purecv = { version = "0.5", features = ["parallel", "simd"] } +purecv = { version = "0.6", features = ["parallel", "simd"] } ``` ### Usage Example @@ -296,7 +343,7 @@ cargo run --example rectification ## ๐Ÿงช Testing & Benchmarking ### Running Tests -PureCV uses a comprehensive suite of unit tests to ensure correctness and parity with OpenCV. The test suite currently includes **281 unit tests** (plus **31 doc-tests**) covering: +PureCV uses a comprehensive suite of unit tests to ensure correctness and parity with OpenCV. The test suite currently includes **308 unit tests** (plus **40 doc-tests**) covering: - **Core module:** Matrix factories, scalar arithmetic variants, bitwise scalar ops, min/max, comparison ops (`compare`, `in_range`), reduction (`reduce`, `count_non_zero`), polar/cartesian conversions, linear algebra (`determinant`, `invert`, `solve`), channel ops (`extract_channel`, `insert_channel`), `DynamicMatrix`, transforms, sorting, clustering, and RNG. - **Imgproc module:** Filters, derivatives, edge detection, color conversions (including gray-to-RGB/BGR/RGBA/BGRA), thresholding, morphology (`erode`, `dilate`), pyramids (`pyr_down`, `pyr_up`), and kernel helpers (`get_gaussian_kernel`, `get_sobel_kernels`). @@ -350,6 +397,7 @@ RUSTFLAGS="-C target-cpu=native" cargo bench --features parallel ## ๐Ÿ—บ Roadmap - [x] [**Milestone 7: Geometric Rectification & Calibration**](https://github.com/webarkit/purecv/milestone/7) - Expand purecv to support camera intrinsic correction and geometric transformation, essential for robust 3D pose estimation and AR surface tracking. +- [x] **Embedded / `no_std` support** - The `core`, `imgproc`, `calib3d`, and `video` modules compile without the standard library for microcontrollers such as the ESP32. See [`purecv-esp32-examples`](https://github.com/webarkit/purecv-esp32-examples). ## ๐Ÿ“„ License diff --git a/crates/no-std-smoke/Cargo.toml b/crates/no-std-smoke/Cargo.toml new file mode 100644 index 0000000..208f97b --- /dev/null +++ b/crates/no-std-smoke/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "purecv-no-std-smoke" +version = "0.0.0" +edition = "2021" +publish = false +description = "Build-only smoke test: consumes the purecv public API from a no_std crate (see issue #83)." + +[dependencies] +purecv = { path = "../..", default-features = false } + +# Standalone workspace root: keeps this build-only crate out of both the +# purecv workspace and any enclosing one. +[workspace] diff --git a/crates/no-std-smoke/src/lib.rs b/crates/no-std-smoke/src/lib.rs new file mode 100644 index 0000000..d3df2a8 --- /dev/null +++ b/crates/no-std-smoke/src/lib.rs @@ -0,0 +1,109 @@ +/* + * lib.rs + * purecv + * + * This file is part of purecv - WebARKit. + * + * purecv is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * purecv is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with purecv. If not, see . + * + * As a special exception, the copyright holders of this library give you + * permission to link this library with independent modules to produce an + * executable, regardless of the license terms of these independent modules, and to + * copy and distribute the resulting executable under terms of your choice, + * provided that you also meet, for each linked independent module, the terms and + * conditions of the license of that module. An independent module is a module + * which is neither derived from nor based on this library. If you modify this + * library, you may extend this exception to your version of the library, but you + * are not obligated to do so. If you do not wish to do so, delete this exception + * statement from your version. + * + * Copyright 2026 WebARKit. + * + * Author(s): Walter Perdan @kalwalt https://github.com/kalwalt + * + */ + +//! Build-only `no_std` smoke test for `purecv` (issue #83). +//! +//! This crate never runs; compiling it for a bare-metal target such as +//! `thumbv7em-none-eabihf` proves that the `purecv` core API is usable +//! from a `no_std` + `alloc` consumer: +//! +//! ```sh +//! cargo build --target thumbv7em-none-eabihf +//! ``` + +#![no_std] + +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 { + let a = Matrix::::from_vec(2, 2, 1, vec![1.0, 2.0, 3.0, 4.0]); + let b = Matrix::::from_vec(2, 2, 1, vec![5.0, 6.0, 7.0, 8.0]); + + let sum = add(&a, &b)?; + let avg = mean(&sum); + let det = determinant(&sum); + + Ok(avg.v[0] + det) +} + +/// Exercises the Phase 2 imgproc path: a scalar `gaussian_blur` under no_std. +pub fn smoke_imgproc() -> Result { + let src = Matrix::::from_vec( + 3, + 3, + 1, + vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], + ); + let blurred = gaussian_blur( + &src, + Size2i::new(3, 3), + 0.0, + 0.0, + BorderTypes::Reflect101, + )?; + 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()) +} diff --git a/crates/wasm/Cargo.toml b/crates/wasm/Cargo.toml index 959ca8b..7c3dc05 100644 --- a/crates/wasm/Cargo.toml +++ b/crates/wasm/Cargo.toml @@ -22,7 +22,9 @@ web-sys = { version = "0.3.69", features = ["console"] } serde = { version = "1.0", features = ["derive"] } serde-wasm-bindgen = "0.6" num-traits = "0.2" -purecv = { path = "../../", default-features = false } +# default-features = false keeps rayon (parallel) out of the wasm build; +# `std` must be re-enabled explicitly now that it gates the non-core modules. +purecv = { path = "../../", default-features = false, features = ["std"] } [features] default = [] 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/core.rs b/src/core.rs index 13438b8..33afea0 100644 --- a/src/core.rs +++ b/src/core.rs @@ -109,6 +109,7 @@ pub use self::matrix::{ CV_8SC1, CV_8SC2, CV_8SC3, CV_8SC4, CV_8U, CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4, }; pub use self::metrics::{mahalanobis, psnr}; +#[cfg(feature = "std")] pub use self::rng::{rand_shuffle, randn, randu, set_rng_seed}; pub use self::solvers::{solve_cubic, solve_quadratic}; pub use self::types::{ @@ -119,6 +120,8 @@ pub use self::types::{ KMEANS_RANDOM_CENTERS, KMEANS_USE_INITIAL_LABELS, SORT_ASCENDING, SORT_DESCENDING, SORT_EVERY_COLUMN, SORT_EVERY_ROW, }; +pub use self::utils::border_interpolate; #[cfg(not(feature = "parallel"))] pub use self::utils::ParIterFallback; -pub use self::utils::{border_interpolate, get_tick_count, get_tick_frequency}; +#[cfg(feature = "std")] +pub use self::utils::{get_tick_count, get_tick_frequency}; diff --git a/src/core/arithm.rs b/src/core/arithm.rs index 2063634..05449a9 100644 --- a/src/core/arithm.rs +++ b/src/core/arithm.rs @@ -34,6 +34,13 @@ * */ +// `num_traits::Float` provides `sqrt`, `sin`, ... on `f32`/`f64` via libm +// when `std` is disabled; with `std` the inherent methods win, so the +// import is only "used" in no_std builds. +use alloc::{vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::constants::CV_2PI; use crate::core::error::Result; use crate::core::logging::tags; @@ -41,8 +48,8 @@ use crate::core::types::{CmpTypes, NormTypes, ReduceTypes, Scalar}; use crate::core::{DataType, Matrix}; use crate::cv_log_warning; use crate::{cv_bail, cv_err}; +use core::ops::{BitAnd, BitOr, BitXor, Not, Sub}; use num_traits::{Bounded, FromPrimitive, Num, SaturatingAdd, SaturatingSub, ToPrimitive}; -use std::ops::{BitAnd, BitOr, BitXor, Not, Sub}; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -67,7 +74,7 @@ macro_rules! binary_op { #[cfg(feature = "simd")] { // SIMD fast-path: only when dst type == src type and type has SIMD support - if std::any::TypeId::of::<$t_dst>() == std::any::TypeId::of::<$t_src>() + if core::any::TypeId::of::<$t_dst>() == core::any::TypeId::of::<$t_src>() && <$t_src as SimdElement>::has_simd() { // Try the SIMD kernel. If it returns false, fall back to scalar. @@ -76,7 +83,7 @@ macro_rules! binary_op { #[cfg(feature = "parallel")] { use rayon::prelude::*; - use std::sync::atomic::{AtomicBool, Ordering}; + use core::sync::atomic::{AtomicBool, Ordering}; let chunk_size = ($dst.data.len() / rayon::current_num_threads()).max(1024); let all_ok = AtomicBool::new(true); @@ -90,7 +97,7 @@ macro_rules! binary_op { // Transmute the dst chunk to $t_src for the SIMD call // This is safe because we verified $t_dst == $t_src above let dst_as_src: &mut [$t_src] = unsafe { - std::slice::from_raw_parts_mut( + core::slice::from_raw_parts_mut( dst_chunk.as_mut_ptr() as *mut $t_src, len, ) @@ -110,7 +117,7 @@ macro_rules! binary_op { #[cfg(not(feature = "parallel"))] { let dst_as_src: &mut [$t_src] = unsafe { - std::slice::from_raw_parts_mut( + core::slice::from_raw_parts_mut( $dst.data.as_mut_ptr() as *mut $t_src, $dst.data.len(), ) @@ -218,7 +225,7 @@ macro_rules! unary_op { ($dst:expr, $src:expr, $t_dst:ty, $t_src:ty, |$d:ident, $s:ident| $body:expr, simd: $simd_fn:ident) => { #[cfg(feature = "simd")] { - if std::any::TypeId::of::<$t_dst>() == std::any::TypeId::of::<$t_src>() + if core::any::TypeId::of::<$t_dst>() == core::any::TypeId::of::<$t_src>() && <$t_src as SimdElement>::has_simd() { // Try the SIMD kernel. If it returns false, fall back to scalar. @@ -227,7 +234,7 @@ macro_rules! unary_op { #[cfg(feature = "parallel")] { use rayon::prelude::*; - use std::sync::atomic::{AtomicBool, Ordering}; + use core::sync::atomic::{AtomicBool, Ordering}; let chunk_size = ($dst.data.len() / rayon::current_num_threads()).max(1024); let all_ok = AtomicBool::new(true); @@ -239,7 +246,7 @@ macro_rules! unary_op { let offset = idx * chunk_size; let len = dst_chunk.len(); let dst_as_src: &mut [$t_src] = unsafe { - std::slice::from_raw_parts_mut( + core::slice::from_raw_parts_mut( dst_chunk.as_mut_ptr() as *mut $t_src, len, ) @@ -258,7 +265,7 @@ macro_rules! unary_op { #[cfg(not(feature = "parallel"))] { let dst_as_src: &mut [$t_src] = unsafe { - std::slice::from_raw_parts_mut( + core::slice::from_raw_parts_mut( $dst.data.as_mut_ptr() as *mut $t_src, $dst.data.len(), ) @@ -1651,8 +1658,8 @@ where #[cfg(feature = "simd")] { // Only f32/f64 have simd_add_weighted; u8 and others use the scalar fallback. - if (std::any::TypeId::of::() == std::any::TypeId::of::() - || std::any::TypeId::of::() == std::any::TypeId::of::()) + if (core::any::TypeId::of::() == core::any::TypeId::of::() + || core::any::TypeId::of::() == core::any::TypeId::of::()) && ::has_simd() { ::simd_add_weighted(&mut dst.data, &src1.data, &src2.data, alpha, beta, gamma); @@ -1781,8 +1788,8 @@ where #[cfg(feature = "simd")] { // Only f32/f64 have simd_convert_scale_abs; others use the scalar fallback. - if (std::any::TypeId::of::() == std::any::TypeId::of::() - || std::any::TypeId::of::() == std::any::TypeId::of::()) + if (core::any::TypeId::of::() == core::any::TypeId::of::() + || core::any::TypeId::of::() == core::any::TypeId::of::()) && ::has_simd() { ::simd_convert_scale_abs(&mut dst.data, &src.data, alpha, beta); @@ -1956,7 +1963,7 @@ where T: Num + Copy + Send + Sync + Default + 'static, { mtx.data.fill(T::zero()); - let n = std::cmp::min(mtx.rows, mtx.cols); + let n = core::cmp::min(mtx.rows, mtx.cols); let channels = mtx.channels; let cols = mtx.cols; @@ -2034,8 +2041,8 @@ where #[cfg(feature = "simd")] { // Only f32/f64 have simd_dot; others use the scalar fallback. - if (std::any::TypeId::of::() == std::any::TypeId::of::() - || std::any::TypeId::of::() == std::any::TypeId::of::()) + if (core::any::TypeId::of::() == core::any::TypeId::of::() + || core::any::TypeId::of::() == core::any::TypeId::of::()) && ::has_simd() { if let Some(result) = ::simd_dot(&src1.data, &src2.data) { @@ -2143,7 +2150,7 @@ pub fn trace(src: &Matrix) -> Scalar where T: Num + Copy + Send + Sync + ToPrimitive + Default + 'static, { - let n = std::cmp::min(src.rows, src.cols); + let n = core::cmp::min(src.rows, src.cols); let channels = src.channels; let cols = src.cols; let mut sum = [0.0; 4]; @@ -2510,8 +2517,8 @@ where #[cfg(feature = "simd")] { // Only f32/f64 have simd_magnitude; others use the scalar fallback. - if (std::any::TypeId::of::() == std::any::TypeId::of::() - || std::any::TypeId::of::() == std::any::TypeId::of::()) + if (core::any::TypeId::of::() == core::any::TypeId::of::() + || core::any::TypeId::of::() == core::any::TypeId::of::()) && ::has_simd() { ::simd_magnitude(&mut dst.data, &x.data, &y.data); @@ -3210,7 +3217,7 @@ where let start = r * dst.cols; let end = start + dst.cols; let row = &mut dst.data[start..end]; - row.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + row.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)); if descending { row.reverse(); } @@ -3219,7 +3226,7 @@ where // Sort every column: extract column, sort, put back for c in 0..dst.cols { let mut col: Vec = (0..dst.rows).map(|r| dst.data[r * dst.cols + c]).collect(); - col.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + col.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)); if descending { col.reverse(); } @@ -3264,7 +3271,7 @@ where indices.sort_by(|&a, &b| { src.data[start + a] .partial_cmp(&src.data[start + b]) - .unwrap_or(std::cmp::Ordering::Equal) + .unwrap_or(core::cmp::Ordering::Equal) }); if descending { indices.reverse(); @@ -3279,7 +3286,7 @@ where indices.sort_by(|&a, &b| { src.data[a * src.cols + c] .partial_cmp(&src.data[b * src.cols + c]) - .unwrap_or(std::cmp::Ordering::Equal) + .unwrap_or(core::cmp::Ordering::Equal) }); if descending { indices.reverse(); diff --git a/src/core/constants.rs b/src/core/constants.rs index 2bc7df4..fe13fce 100644 --- a/src/core/constants.rs +++ b/src/core/constants.rs @@ -36,32 +36,32 @@ //! Mathematical constants mirroring OpenCV's C++ `CV_PI`, `CV_2PI`, etc. //! -//! All values are `pub const f64` backed by [`std::f64::consts`] for +//! All values are `pub const f64` backed by [`core::f64::consts`] for //! maximum precision and cross-platform reproducibility. -/// Pi (same as `std::f64::consts::PI`). -pub const CV_PI: f64 = std::f64::consts::PI; +/// Pi (same as `core::f64::consts::PI`). +pub const CV_PI: f64 = core::f64::consts::PI; -/// Pi divided by 2 (same as `std::f64::consts::FRAC_PI_2`). -pub const CV_PI_2: f64 = std::f64::consts::FRAC_PI_2; +/// Pi divided by 2 (same as `core::f64::consts::FRAC_PI_2`). +pub const CV_PI_2: f64 = core::f64::consts::FRAC_PI_2; /// 2 * Pi โ€” full circle in radians. -pub const CV_2PI: f64 = 2.0 * std::f64::consts::PI; +pub const CV_2PI: f64 = 2.0 * core::f64::consts::PI; -/// Pi divided by 4 (same as `std::f64::consts::FRAC_PI_4`). -pub const CV_PI_4: f64 = std::f64::consts::FRAC_PI_4; +/// Pi divided by 4 (same as `core::f64::consts::FRAC_PI_4`). +pub const CV_PI_4: f64 = core::f64::consts::FRAC_PI_4; -/// Log base 2 of e (same as `std::f64::consts::LOG2_E`). -pub const CV_LOG2: f64 = std::f64::consts::LOG2_E; +/// Log base 2 of e (same as `core::f64::consts::LOG2_E`). +pub const CV_LOG2: f64 = core::f64::consts::LOG2_E; -/// Natural logarithm of 2 (same as `std::f64::consts::LN_2`). -pub const CV_LN2: f64 = std::f64::consts::LN_2; +/// Natural logarithm of 2 (same as `core::f64::consts::LN_2`). +pub const CV_LN2: f64 = core::f64::consts::LN_2; -/// Square root of 2 (same as `std::f64::consts::SQRT_2`). -pub const CV_SQRT2: f64 = std::f64::consts::SQRT_2; +/// Square root of 2 (same as `core::f64::consts::SQRT_2`). +pub const CV_SQRT2: f64 = core::f64::consts::SQRT_2; -/// Euler's number (same as `std::f64::consts::E`). -pub const CV_E: f64 = std::f64::consts::E; +/// Euler's number (same as `core::f64::consts::E`). +pub const CV_E: f64 = core::f64::consts::E; -/// Natural logarithm of 10 (same as `std::f64::consts::LN_10`). -pub const CV_LN10: f64 = std::f64::consts::LN_10; +/// Natural logarithm of 10 (same as `core::f64::consts::LN_10`). +pub const CV_LN10: f64 = core::f64::consts::LN_10; diff --git a/src/core/dct.rs b/src/core/dct.rs index 80246ab..c8ce934 100644 --- a/src/core/dct.rs +++ b/src/core/dct.rs @@ -34,6 +34,8 @@ * */ +use alloc::vec; + use crate::core::constants::CV_PI; use crate::core::error::Result; use crate::core::logging::tags; diff --git a/src/core/dft.rs b/src/core/dft.rs index 9c3b240..9ea9a51 100644 --- a/src/core/dft.rs +++ b/src/core/dft.rs @@ -34,6 +34,8 @@ * */ +use alloc::vec; + use rustfft::num_complex::Complex; use rustfft::FftPlanner; diff --git a/src/core/dynamic.rs b/src/core/dynamic.rs index df03549..411cf2f 100644 --- a/src/core/dynamic.rs +++ b/src/core/dynamic.rs @@ -34,6 +34,8 @@ * */ +use alloc::{format, vec, vec::Vec}; + use crate::core::error::Result; use crate::core::logging::tags; use crate::core::matrix::{Depth, MatType, Matrix}; diff --git a/src/core/error.rs b/src/core/error.rs index b687d38..2f8a2c9 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -34,7 +34,9 @@ * */ -use std::fmt; +use alloc::string::String; + +use core::fmt; /// Custom error type for the purecv library. #[derive(Debug, Clone, PartialEq)] @@ -60,7 +62,7 @@ impl fmt::Display for PureCvError { } } -impl std::error::Error for PureCvError {} +impl core::error::Error for PureCvError {} /// Standard result type for purecv. -pub type Result = std::result::Result; +pub type Result = core::result::Result; diff --git a/src/core/logging.rs b/src/core/logging.rs index f4f36b7..1c987b2 100644 --- a/src/core/logging.rs +++ b/src/core/logging.rs @@ -477,7 +477,7 @@ macro_rules! cv_log_if_debug { #[macro_export] macro_rules! cv_bail { ($tag:expr, $variant:ident, $($arg:tt)+) => {{ - let __msg = format!($($arg)+); + let __msg = $crate::__format!($($arg)+); $crate::cv_log_warning!($tag, "{}", __msg); return Err($crate::core::error::PureCvError::$variant(__msg)); }}; @@ -489,7 +489,7 @@ macro_rules! cv_bail { #[macro_export] macro_rules! cv_err { ($tag:expr, $variant:ident, $($arg:tt)+) => {{ - let __msg = format!($($arg)+); + let __msg = $crate::__format!($($arg)+); $crate::cv_log_warning!($tag, "{}", __msg); $crate::core::error::PureCvError::$variant(__msg) }}; @@ -499,7 +499,7 @@ macro_rules! cv_err { #[macro_export] macro_rules! cv_bail_debug { ($tag:expr, $variant:ident, $($arg:tt)+) => {{ - let __msg = format!($($arg)+); + let __msg = $crate::__format!($($arg)+); $crate::cv_log_debug!($tag, "{}", __msg); return Err($crate::core::error::PureCvError::$variant(__msg)); }}; @@ -509,7 +509,7 @@ macro_rules! cv_bail_debug { #[macro_export] macro_rules! cv_err_debug { ($tag:expr, $variant:ident, $($arg:tt)+) => {{ - let __msg = format!($($arg)+); + let __msg = $crate::__format!($($arg)+); $crate::cv_log_debug!($tag, "{}", __msg); $crate::core::error::PureCvError::$variant(__msg) }}; diff --git a/src/core/matrix.rs b/src/core/matrix.rs index 0290e15..746423f 100644 --- a/src/core/matrix.rs +++ b/src/core/matrix.rs @@ -37,6 +37,7 @@ use crate::core::error::Result; use crate::core::logging::tags; use crate::core::types::Scalar; use crate::{cv_bail, cv_err}; +use alloc::{vec, vec::Vec}; /// Matrix depth: number of bits per element and its signedness/type. /// Follows OpenCV's depth conventions (CV_8U, CV_32F, etc.). @@ -562,7 +563,7 @@ impl Matrix { /// /// * `other` - The matrix with which to swap references logic correctly. pub fn swap(&mut self, other: &mut Self) { - std::mem::swap(self, other); + core::mem::swap(self, other); } /// Returns a raw immutable pointer to the start of the underlying data @@ -609,9 +610,14 @@ impl Matrix { pub fn copy_to(&self, dst: &mut Matrix) -> Result<()> { if self.rows != dst.rows || self.cols != dst.cols || self.channels != dst.channels { #[cfg(debug_assertions)] - eprintln!( + log::warn!( "[purecv::copy_to] dst resized from {}x{}x{} to {}x{}x{}", - dst.rows, dst.cols, dst.channels, self.rows, self.cols, self.channels + dst.rows, + dst.cols, + dst.channels, + self.rows, + self.cols, + self.channels ); dst.rows = self.rows; dst.cols = self.cols; @@ -693,7 +699,7 @@ impl Matrix { /// Following OpenCV, the diagonal has value 1 and others are 0. pub fn eye(rows: usize, cols: usize, channels: usize) -> Self { let mut mat = Self::zeros(rows, cols, channels); - let min_dim = std::cmp::min(rows, cols); + let min_dim = core::cmp::min(rows, cols); for i in 0..min_dim { for c in 0..channels { mat.set(i, i, c, T::one()); diff --git a/src/core/metrics.rs b/src/core/metrics.rs index a7a4789..eea58e6 100644 --- a/src/core/metrics.rs +++ b/src/core/metrics.rs @@ -34,6 +34,13 @@ * */ +// `num_traits::Float` provides `sqrt`, `sin`, ... on `f32`/`f64` via libm +// when `std` is disabled; with `std` the inherent methods win, so the +// import is only "used" in no_std builds. +use alloc::vec; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::Result; use crate::core::logging::tags; use crate::core::matrix::Matrix; diff --git a/src/core/rng.rs b/src/core/rng.rs index d459196..af4028f 100644 --- a/src/core/rng.rs +++ b/src/core/rng.rs @@ -34,14 +34,28 @@ * */ +// `num_traits::Float` provides `sqrt`, `sin`, ... on `f32`/`f64` via libm +// when `std` is disabled; with `std` the inherent methods win, so the +// import is only "used" in no_std builds. use crate::core::constants::CV_2PI; +// The RNG entry points (randu/randn/rand_shuffle) are std-only, so the error, +// logging, and matrix imports they pull in are gated behind `std` too. +#[cfg(feature = "std")] use crate::core::error::Result; +#[cfg(feature = "std")] use crate::core::logging::tags; +#[cfg(feature = "std")] use crate::core::types::Scalar; +#[cfg(feature = "std")] use crate::core::Matrix; +#[cfg(feature = "std")] use crate::cv_bail; +#[cfg(feature = "std")] +use core::cell::RefCell; +#[allow(unused_imports)] +use num_traits::Float; +#[cfg(feature = "std")] use num_traits::{FromPrimitive, ToPrimitive}; -use std::cell::RefCell; // --------------------------------------------------------------------------- // Xoshiro256** โ€” a fast, high-quality pure-Rust PRNG (public domain algorithm @@ -49,11 +63,16 @@ use std::cell::RefCell; // --------------------------------------------------------------------------- /// Internal state for the xoshiro256** generator. +// Without `std` the thread-local RNG below is compiled out, leaving this +// generator temporarily unused; a seedable no_std API is planned in #82. +#[cfg_attr(not(feature = "std"), allow(dead_code))] #[derive(Clone)] struct Xoshiro256 { s: [u64; 4], } +// See the note on `Xoshiro256`: without `std` these methods have no caller yet. +#[cfg_attr(not(feature = "std"), allow(dead_code))] impl Xoshiro256 { /// Creates a new generator seeded by expanding `seed` through SplitMix64. fn from_seed(seed: u64) -> Self { @@ -124,7 +143,8 @@ impl Xoshiro256 { // Thread-local RNG state // --------------------------------------------------------------------------- -thread_local! { +#[cfg(feature = "std")] +std::thread_local! { static THREAD_RNG: RefCell = RefCell::new(Xoshiro256::from_seed(0)); } @@ -142,6 +162,7 @@ thread_local! { /// use purecv::core::set_rng_seed; /// set_rng_seed(42); /// ``` +#[cfg(feature = "std")] pub fn set_rng_seed(seed: u64) { THREAD_RNG.with(|rng| { *rng.borrow_mut() = Xoshiro256::from_seed(seed); @@ -174,6 +195,7 @@ pub fn set_rng_seed(seed: u64) { /// let mut mat = Matrix::::new(100, 100, 1); /// randu(&mut mat, Scalar::all(0.0), Scalar::all(1.0)).unwrap(); /// ``` +#[cfg(feature = "std")] pub fn randu(dst: &mut Matrix, low: Scalar, high: Scalar) -> Result<()> where T: Default + Clone + FromPrimitive + ToPrimitive + Send + Sync, @@ -228,6 +250,7 @@ where /// let mut mat = Matrix::::new(100, 100, 1); /// randn(&mut mat, Scalar::all(0.0), Scalar::all(1.0)).unwrap(); /// ``` +#[cfg(feature = "std")] pub fn randn(dst: &mut Matrix, mean: Scalar, std_dev: Scalar) -> Result<()> where T: Default + Clone + FromPrimitive + ToPrimitive + Send + Sync, @@ -273,6 +296,7 @@ where /// /// # Arguments /// * `slice` - The slice to shuffle. +#[cfg(feature = "std")] pub fn rand_shuffle(slice: &mut [T]) { if slice.is_empty() { return; diff --git a/src/core/solvers.rs b/src/core/solvers.rs index 6b6a636..60dbca6 100644 --- a/src/core/solvers.rs +++ b/src/core/solvers.rs @@ -34,6 +34,13 @@ * */ +// `num_traits::Float` provides `sqrt`, `sin`, ... on `f32`/`f64` via libm +// when `std` is disabled; with `std` the inherent methods win, so the +// import is only "used" in no_std builds. +use alloc::{vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::constants::CV_PI; use crate::core::error::Result; diff --git a/src/core/structural.rs b/src/core/structural.rs index a9878cc..58fc89a 100644 --- a/src/core/structural.rs +++ b/src/core/structural.rs @@ -34,6 +34,8 @@ * */ +use alloc::vec::Vec; + use crate::core::error::Result; use crate::core::logging::tags; use crate::core::types::Scalar; diff --git a/src/core/types.rs b/src/core/types.rs index ff5a6bc..adc3a58 100644 --- a/src/core/types.rs +++ b/src/core/types.rs @@ -34,7 +34,7 @@ * */ -use std::ops::{Add, Div, Index, IndexMut, Mul, Sub}; +use core::ops::{Add, Div, Index, IndexMut, Mul, Sub}; use num_traits::{CheckedDiv, Zero}; @@ -643,7 +643,7 @@ impl VecN { /// numeric types and guarantees that each element is the additive identity. pub fn zeros() -> Self { Self { - val: std::array::from_fn(|_| T::zero()), + val: core::array::from_fn(|_| T::zero()), } } } @@ -652,7 +652,7 @@ impl VecN { /// Returns a vector with every element set to `v`. pub fn all(v: T) -> Self { Self { - val: std::array::from_fn(|_| v), + val: core::array::from_fn(|_| v), } } } @@ -690,7 +690,7 @@ impl, const N: usize> Add for VecN { type Output = Self; fn add(self, rhs: Self) -> Self { Self { - val: std::array::from_fn(|i| self.val[i] + rhs.val[i]), + val: core::array::from_fn(|i| self.val[i] + rhs.val[i]), } } } @@ -699,7 +699,7 @@ impl, const N: usize> Sub for VecN { type Output = Self; fn sub(self, rhs: Self) -> Self { Self { - val: std::array::from_fn(|i| self.val[i] - rhs.val[i]), + val: core::array::from_fn(|i| self.val[i] - rhs.val[i]), } } } @@ -712,7 +712,7 @@ impl, const N: usize> Add> for Vec type Output = Self; fn add(self, rhs: Scalar) -> Self { Self { - val: std::array::from_fn(|i| self.val[i] + rhs.channel_or_default(i)), + val: core::array::from_fn(|i| self.val[i] + rhs.channel_or_default(i)), } } } @@ -723,7 +723,7 @@ impl, const N: usize> Sub> for Vec type Output = Self; fn sub(self, rhs: Scalar) -> Self { Self { - val: std::array::from_fn(|i| self.val[i] - rhs.channel_or_default(i)), + val: core::array::from_fn(|i| self.val[i] - rhs.channel_or_default(i)), } } } @@ -733,7 +733,7 @@ impl, const N: usize> Mul for VecN { type Output = Self; fn mul(self, rhs: Self) -> Self { Self { - val: std::array::from_fn(|i| self.val[i] * rhs.val[i]), + val: core::array::from_fn(|i| self.val[i] * rhs.val[i]), } } } @@ -743,7 +743,7 @@ impl, const N: usize> Mul for VecN { type Output = Self; fn mul(self, rhs: T) -> Self { Self { - val: std::array::from_fn(|i| self.val[i] * rhs), + val: core::array::from_fn(|i| self.val[i] * rhs), } } } @@ -753,7 +753,7 @@ impl, const N: usize> Div for VecN { type Output = Self; fn div(self, rhs: T) -> Self { Self { - val: std::array::from_fn(|i| self.val[i] / rhs), + val: core::array::from_fn(|i| self.val[i] / rhs), } } } diff --git a/src/core/utils.rs b/src/core/utils.rs index b49f89a..21100b9 100644 --- a/src/core/utils.rs +++ b/src/core/utils.rs @@ -34,19 +34,29 @@ * */ +#[cfg(feature = "std")] use std::sync::OnceLock; +#[cfg(feature = "std")] use std::time::Instant; +#[cfg(feature = "std")] static START_TIME: OnceLock = OnceLock::new(); /// Returns the number of ticks. /// In this implementation, it returns the number of nanoseconds since the first call. +/// +/// Requires the `std` feature: bare-metal targets have no portable clock, +/// so embedded users should rely on their platform timer instead. +#[cfg(feature = "std")] pub fn get_tick_count() -> i64 { let start = *START_TIME.get_or_init(Instant::now); start.elapsed().as_nanos() as i64 } /// Returns the number of ticks per second. +/// +/// Requires the `std` feature (see [`get_tick_count`]). +#[cfg(feature = "std")] pub fn get_tick_frequency() -> f64 { 1_000_000_000.0 } @@ -151,13 +161,13 @@ pub trait ParIterFallback<'a, T: 'a> { fn par_iter(&'a self) -> Self::Iter; fn par_iter_mut(&'a mut self) -> Self::IterMut; - fn par_chunks_mut(&'a mut self, size: usize) -> std::slice::ChunksMut<'a, T>; + fn par_chunks_mut(&'a mut self, size: usize) -> core::slice::ChunksMut<'a, T>; } #[cfg(not(feature = "parallel"))] impl<'a, T: 'a> ParIterFallback<'a, T> for [T] { - type Iter = std::slice::Iter<'a, T>; - type IterMut = std::slice::IterMut<'a, T>; + type Iter = core::slice::Iter<'a, T>; + type IterMut = core::slice::IterMut<'a, T>; fn par_iter(&'a self) -> Self::Iter { self.iter() @@ -165,7 +175,7 @@ impl<'a, T: 'a> ParIterFallback<'a, T> for [T] { fn par_iter_mut(&'a mut self) -> Self::IterMut { self.iter_mut() } - fn par_chunks_mut(&'a mut self, size: usize) -> std::slice::ChunksMut<'a, T> { + fn par_chunks_mut(&'a mut self, size: usize) -> core::slice::ChunksMut<'a, T> { self.chunks_mut(size) } } diff --git a/src/imgproc/color.rs b/src/imgproc/color.rs index 1e441d7..650bd23 100644 --- a/src/imgproc/color.rs +++ b/src/imgproc/color.rs @@ -34,6 +34,9 @@ * */ +#[allow(unused_imports)] +use num_traits::Float; + #[cfg(feature = "parallel")] use rayon::prelude::*; diff --git a/src/imgproc/derivatives.rs b/src/imgproc/derivatives.rs index b8011c0..612db07 100644 --- a/src/imgproc/derivatives.rs +++ b/src/imgproc/derivatives.rs @@ -34,6 +34,8 @@ * */ +use alloc::{string::ToString, vec, vec::Vec}; + use crate::core::error::{PureCvError, Result}; use crate::core::utils::border_interpolate; use crate::core::{BorderTypes, Matrix}; @@ -337,15 +339,15 @@ where // and use the SIMD 3x3 kernel for interior rows. #[cfg(feature = "simd")] { - if std::any::TypeId::of::() == std::any::TypeId::of::() && rows > 2 && cols > 2 { + if core::any::TypeId::of::() == core::any::TypeId::of::() && rows > 2 && cols > 2 { let row_len = cols * channels; // Reinterpret src.data as &[f32] โ€” safe because T == f32 let src_f32: &[f32] = unsafe { - std::slice::from_raw_parts(src.data.as_ptr() as *const f32, src.data.len()) + core::slice::from_raw_parts(src.data.as_ptr() as *const f32, src.data.len()) }; let dst_f32: &mut [f32] = unsafe { - std::slice::from_raw_parts_mut(dst.data.as_mut_ptr() as *mut f32, dst.data.len()) + core::slice::from_raw_parts_mut(dst.data.as_mut_ptr() as *mut f32, dst.data.len()) }; // Process interior rows with SIMD diff --git a/src/imgproc/edge.rs b/src/imgproc/edge.rs index d1ba4cf..f44a2ea 100644 --- a/src/imgproc/edge.rs +++ b/src/imgproc/edge.rs @@ -34,6 +34,10 @@ * */ +use alloc::vec::Vec; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::types::BorderTypes; use crate::core::Matrix; @@ -117,7 +121,7 @@ where *m = magnitude as f32; if magnitude > 1e-5 { - let angle = gy_f.atan2(gx_f) * 180.0 / std::f64::consts::PI; + let angle = gy_f.atan2(gx_f) * 180.0 / core::f64::consts::PI; let normalized_angle = if angle < 0.0 { angle + 180.0 } else { angle }; if (0.0..22.5).contains(&normalized_angle) diff --git a/src/imgproc/feature.rs b/src/imgproc/feature.rs index 8e4b996..02d750b 100644 --- a/src/imgproc/feature.rs +++ b/src/imgproc/feature.rs @@ -52,6 +52,10 @@ //! //! `pre_corner_detect` is an independent simpler map using first + second derivatives. +use alloc::vec::Vec; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::types::{BorderTypes, Point2f, Size2i, TermCriteria, TermType}; use crate::core::Matrix; @@ -431,7 +435,7 @@ where } // Sort by response descending. - candidates.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal)); + candidates.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(core::cmp::Ordering::Equal)); // Greedily select corners with minimum distance constraint. let min_dist_sq = min_distance * min_distance; diff --git a/src/imgproc/filter.rs b/src/imgproc/filter.rs index 303acc0..6fbf0fa 100644 --- a/src/imgproc/filter.rs +++ b/src/imgproc/filter.rs @@ -34,13 +34,17 @@ * */ +use alloc::{format, string::ToString, vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::Result; use crate::core::types::BorderTypes; use crate::core::utils::border_interpolate; use crate::core::{Matrix, Point2i, PureCvError, Size2i}; +use core::any::TypeId; +use core::iter::Sum; use num_traits::{FromPrimitive, NumCast, ToPrimitive}; -use std::any::TypeId; -use std::iter::Sum; #[cfg(not(feature = "parallel"))] use crate::core::utils::ParIterFallback; @@ -337,7 +341,8 @@ where } } // Sort to find median - neighbors.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + neighbors + .sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)); let median = neighbors[neighbors.len() / 2]; *comp = median; } diff --git a/src/imgproc/geometric.rs b/src/imgproc/geometric.rs index f4742a8..f6d9606 100644 --- a/src/imgproc/geometric.rs +++ b/src/imgproc/geometric.rs @@ -34,6 +34,10 @@ * */ +use alloc::{string::ToString, vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::arithm::{invert, DecompTypes}; use crate::core::error::{PureCvError, Result}; use crate::core::simd::SimdElement; diff --git a/src/imgproc/hough.rs b/src/imgproc/hough.rs index 3849dc2..f1f5138 100644 --- a/src/imgproc/hough.rs +++ b/src/imgproc/hough.rs @@ -34,6 +34,10 @@ * */ +use alloc::{vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::constants::CV_PI; use crate::core::error::{PureCvError, Result}; use crate::core::types::BorderTypes; @@ -131,7 +135,7 @@ pub fn hough_lines( } // Stage 3. Sort the detected lines by accumulator value descending - sort_buf.sort_by_key(|b| std::cmp::Reverse(b.1)); + sort_buf.sort_by_key(|b| core::cmp::Reverse(b.1)); // Stage 4. Format output let mut lines = Vec::with_capacity(sort_buf.len()); @@ -160,6 +164,12 @@ pub fn hough_lines( /// /// # Returns /// A vector of `[i32; 4]` representing `(x1, y1, x2, y2)` for each segment. +/// +/// Requires the `std` feature: the probabilistic transform shuffles edge points +/// with the thread-local RNG ([`crate::core::rand_shuffle`]), which is std-only +/// until a no_std seedable RNG lands (see issue #82). The deterministic +/// [`hough_lines`] is available under `no_std`. +#[cfg(feature = "std")] pub fn hough_lines_p( image: &Matrix, rho: f64, @@ -452,7 +462,7 @@ pub fn hough_circles( } // Sort centers by votes - centers.sort_by_key(|b| std::cmp::Reverse(b.2)); + centers.sort_by_key(|b| core::cmp::Reverse(b.2)); let mut circles = Vec::new(); diff --git a/src/imgproc/morph.rs b/src/imgproc/morph.rs index 4a4985a..15fb2a9 100644 --- a/src/imgproc/morph.rs +++ b/src/imgproc/morph.rs @@ -34,6 +34,13 @@ * */ +use alloc::{string::ToString, vec::Vec}; +// `vec!` is only used by the SIMD-only separable kernel below. +#[cfg(feature = "simd")] +use alloc::vec; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::arithm; use crate::core::error::{PureCvError, Result}; use crate::core::simd::SimdElement; diff --git a/src/imgproc/pyramid.rs b/src/imgproc/pyramid.rs index 01d010f..5cd3587 100644 --- a/src/imgproc/pyramid.rs +++ b/src/imgproc/pyramid.rs @@ -39,6 +39,10 @@ //! These functions implement the classical Gaussian pyramid using the 5-tap //! kernel `[1, 4, 6, 4, 1]` (sum = 16, outer product sum = 256). +use alloc::{string::ToString, vec, vec::Vec}; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::simd::SimdElement; use crate::core::types::{BorderTypes, Size}; diff --git a/src/imgproc/resize.rs b/src/imgproc/resize.rs index 8d9cff8..6c3de1c 100644 --- a/src/imgproc/resize.rs +++ b/src/imgproc/resize.rs @@ -36,6 +36,10 @@ //! Image resizing operations: [`resize`]. +use alloc::string::ToString; +#[allow(unused_imports)] +use num_traits::Float; + use crate::core::error::{PureCvError, Result}; use crate::core::types::Size; use crate::core::Matrix; diff --git a/src/imgproc/threshold.rs b/src/imgproc/threshold.rs index c1291e3..0676d18 100644 --- a/src/imgproc/threshold.rs +++ b/src/imgproc/threshold.rs @@ -34,6 +34,8 @@ * */ +use alloc::string::ToString; + use crate::core::error::{PureCvError, Result}; use crate::core::simd::SimdElement; use crate::core::Matrix; diff --git a/src/lib.rs b/src/lib.rs index c253558..8db70a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,10 +34,26 @@ * */ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +/// Re-export of `alloc::format!` for use inside exported macros (e.g. `cv_bail!`). +/// +/// Referencing it as `$crate::__format!` keeps those macros working in external +/// crates that never declared `extern crate alloc` themselves. +#[doc(hidden)] +pub use alloc::format as __format; + // Global modules +// +// `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")] pub mod features; +#[cfg(feature = "std")] pub mod features2d; pub mod imgproc; pub mod version; @@ -56,6 +72,7 @@ pub mod prelude { Vec3s, Vec4b, Vec4d, Vec4f, Vec4i, Vec4s, Vec6d, Vec6f, VecN, }; pub use crate::core::Matrix; + #[cfg(feature = "std")] pub use crate::features2d::{ draw_keypoints, draw_matches, filter_matches, BFMatcher, DMatch, DescriptorMatcher, FastFeatureDetector, FastType, KeyPoint, NormType, Orb, 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 // ---------------------------------------------------------------------------