Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 12 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name = "purecv"
version = "0.6.1"
authors = ["Walter Perdan <https://github.com/kalwalt>"]
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"
Expand All @@ -16,23 +17,24 @@ 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"
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]]
Expand Down Expand Up @@ -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"
Expand Down
68 changes: 58 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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.

Expand Down Expand Up @@ -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::<f32>::from_vec(2, 2, 1, vec![1.0, 2.0, 3.0, 4.0]);
let b = Matrix::<f32>::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
Expand Down Expand Up @@ -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`).
Expand Down Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions crates/no-std-smoke/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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]
109 changes: 109 additions & 0 deletions crates/no-std-smoke/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
* 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<f64> {
let a = Matrix::<f32>::from_vec(2, 2, 1, vec![1.0, 2.0, 3.0, 4.0]);
let b = Matrix::<f32>::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<f32> {
let src = Matrix::<f32>::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<f64> {
let rvec = Matrix::<f64>::from_vec(3, 1, 1, vec![0.1, 0.2, 0.3]);
let mut rmat = Matrix::<f64>::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<usize> {
let img = Matrix::<u8>::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())
}
4 changes: 3 additions & 1 deletion crates/wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
4 changes: 4 additions & 0 deletions src/calib3d/fundamental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion src/calib3d/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}

Expand Down
Loading