Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e1c58d9
doc: record the release workflow and merge policy in CLAUDE.md
kalwalt Aug 11, 2026
d125e44
chore(ci): pin stable toolchain and allow the new chunks_exact_to_as_…
kalwalt Aug 29, 2026
313038c
refactor(imgproc): use as_chunks instead of allowing the new clippy lint
kalwalt Aug 29, 2026
7583fa0
feat(imgproc): add histogram module
XiaoPengYouCode Aug 23, 2026
8896d98
fix(imgproc): address review comments for histogram module
XiaoPengYouCode Aug 29, 2026
433d3be
feat(imgproc): add parallel support to histogram module
kalwalt Aug 30, 2026
393b160
fix(imgproc): guard calc_back_project against zero-width images
kalwalt Aug 30, 2026
eb7673f
perf(imgproc): SIMD-accelerate compare_hist
kalwalt Aug 30, 2026
7d8d47c
Revert "perf(imgproc): SIMD-accelerate compare_hist"
kalwalt Aug 30, 2026
1207ac1
perf(imgproc): SIMD-accelerate compare_hist
kalwalt Aug 30, 2026
24cb5e2
fix(imgproc): address Qodo review comments for SIMD compare_hist
kalwalt Aug 31, 2026
435a978
feat(wasm): expose histogram module bindings
kalwalt Aug 31, 2026
1636271
fix(wasm): address Qodo review findings on histogram bindings
kalwalt Aug 31, 2026
a2f3b1f
doc: add histogram/CLAHE examples (Rust + WASM) and update READMEs
kalwalt Sep 1, 2026
861f2ea
fix: address Qodo review findings on histogram examples
kalwalt Sep 1, 2026
1aff3b0
perf(ci): run the sequential (non-parallel) test suite in CI
kalwalt Sep 2, 2026
991cdaf
test(imgproc): move histogram module tests into src/imgproc/tests.rs
kalwalt Sep 2, 2026
e03c938
chore(release): prepare for v0.8.0
kalwalt Sep 2, 2026
e3f9bb6
fix(imgproc): CLAHE edge-pixel extrapolation and calc_back_project ra…
kalwalt Sep 2, 2026
e3e3613
fix(imgproc): guard validate_hist_ranges against usize overflow
kalwalt Sep 2, 2026
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
11 changes: 7 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.98.0
with:
components: rustfmt, clippy

Expand All @@ -37,6 +37,9 @@ jobs:
- name: Run tests
run: cargo test --workspace

- name: Run tests (sequential, parallel disabled)
run: cargo test --workspace --no-default-features --features std

- name: Run tests (with parallel feature)
run: cargo test --workspace --features parallel

Expand All @@ -54,7 +57,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.98.0
with:
components: clippy
targets: thumbv7em-none-eabihf
Expand Down Expand Up @@ -82,7 +85,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.98.0
with:
targets: wasm32-unknown-unknown

Expand All @@ -107,7 +110,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.98.0

- name: Rust Cache
uses: Swatinem/rust-cache@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.98.0
- name: Publish to Crates.io
run: cargo publish --token ${{ secrets.CRATES_TOKEN }} -p purecv

Expand All @@ -114,7 +114,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@1.98.0
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Build and Publish to NPM
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,48 @@

All notable changes to this project will be documented in this file.

## [0.8.0] - 2026-09-02

### ⚙️ Miscellaneous Tasks

- *(ci)* Pin stable toolchain and allow the new chunks_exact_to_as_chunks lint

### ⚡ Performance

- *(imgproc)* SIMD-accelerate compare_hist
- *(imgproc)* SIMD-accelerate compare_hist
- *(ci)* Run the sequential (non-parallel) test suite in CI

### 🐛 Bug Fixes

- *(imgproc)* Address review comments for histogram module
- *(imgproc)* Guard calc_back_project against zero-width images
- *(imgproc)* Address Qodo review comments for SIMD compare_hist
- *(wasm)* Address Qodo review findings on histogram bindings
- Address Qodo review findings on histogram examples

### 📚 Documentation

- Record the release workflow and merge policy in CLAUDE.md
- Add histogram/CLAHE examples (Rust + WASM) and update READMEs

### 🕸️ WebAssembly & Emscripten

- *(wasm)* Expose histogram module bindings

### 🚀 Features

- *(imgproc)* Add histogram module
- *(imgproc)* Add parallel support to histogram module

### 🚜 Refactor

- *(imgproc)* Use as_chunks instead of allowing the new clippy lint

### 🧪 Testing

- *(imgproc)* Move histogram module tests into src/imgproc/tests.rs

## [0.7.1] - 2026-08-10

### ⚙️ Miscellaneous Tasks
Expand Down
23 changes: 23 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ Preferred scopes: `(core)` `(imgproc)` `(simd)` `(wasm)` `(parallel)`
- PRs start from and target the `dev` branch (not `main`).
- Keep PRs focused; one feature or fix per PR.

### Releases

Releases go `dev` → PR to `main` → merge → tag `vX.Y.Z` on `main`. Pushing the tag
triggers `release.yml`, which publishes to crates.io **and** npm — irreversible.

**Merge release PRs with a merge commit, not "Rebase and merge" or "Squash and
merge".** Rebasing replays dev's commits as new objects on `main`, so `dev` stops
being an ancestor of `main` and the two diverge with identical content but different
SHAs. The next release PR then replays every old commit again. This happened with
v0.7.1 (#95) and had to be repaired by resetting `dev` to `main`.

Release prep steps (see the v0.7.1 commit for a worked example):

1. Bump the version in `Cargo.toml` (**two places** — `[package]` and
`[workspace.package]`) and in the root `package.json`.
2. Run `npm run build` — this regenerates `crates/wasm/pkg/package.json`, which is
tracked and otherwise silently drifts.
3. `npx git-cliff --config cliff.toml --tag vX.Y.Z --unreleased --prepend CHANGELOG.md`,
then add the blank line `--prepend` omits before the previous version heading.
4. Commit as `chore(release): prepare for vX.Y.Z` — `cliff.toml` skips this message
from the changelog, so land any other changes in their own commits *first* or they
will not appear.

## Module Structure Convention

Each top-level module (`core/`, `imgproc/`, `features2d/`, etc.) must contain its own:
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "purecv"
version = "0.7.1"
version = "0.8.0"
authors = ["Walter Perdan <https://github.com/kalwalt>"]
edition = "2021"
rust-version = "1.88"
Expand Down Expand Up @@ -86,7 +86,7 @@ members = ["crates/wasm"]
exclude = ["crates/no-std-smoke"]

[workspace.package]
version = "0.7.1"
version = "0.8.0"
authors = ["Walter Perdan <https://github.com/kalwalt>"]
edition = "2021"
description = "A pure Rust, high-performance computer vision library focused on safety and portability."
Expand Down
6 changes: 5 additions & 1 deletion MAINTAINERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ Publishing a new version requires a mix of manual changelog curation and automat
2. Verify that all CI checks (Formatting, Clippy, Tests for `parallel` and `simd`) are passing on the latest commit.

### Step 2: Bump the Version
Update the version number in the `Cargo.toml` file of the workspace in [package] and [workspace.package] sections.
Update the version number in the `Cargo.toml` file of the workspace in [package] and [workspace.package] sections, and in the root `package.json`.

Also check `README.md` and `crates/wasm/README.md` for hardcoded version strings in
installation snippets (e.g. `purecv = "0.6"`) — these don't update automatically and
are easy to miss. Search for the old version number across both files before moving on.

### Step 3: Generate the Local Changelog
We use `git-cliff` to parse the conventional commits and update the historical changelog. Run the following command in the root directory:
Expand Down
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Unlike existing wrappers, **PureCV** is a native rewrite. It aims to provide:
- **Hough Transform:** Standard (`hough_lines`) and Probabilistic (`hough_lines_p`) line detection, plus Hough Circle Transform (`hough_circles`) using internally computed Sobel gradients. Fully parallelized via the `parallel` feature.
- **Resizing:** `resize` function utilizing high-performance bilinear interpolation, fully compatible with `parallel` Rayon multi-threading.
- **Geometric Transformations:** `remap` (with bilinear and nearest-neighbor interpolation) and `warp_perspective` (perspective transformations) fully parallelized and SIMD-accelerated.
- **Histograms & Contrast:** `calc_hist` (multi-dimensional, uniform or non-uniform bins, optional mask and accumulation) and `calc_back_project` for histogram back-projection; `compare_hist` with all 6 OpenCV comparison methods (`Correl`, `ChiSqr`, `ChiSqrAlt`, `Intersection`, `Bhattacharyya`, `KullbackLeibler`), SIMD-accelerated via the `simd` feature; `equalize_hist` for global histogram equalization; and `Clahe` (Contrast Limited Adaptive Histogram Equalization) for `u8`/`u16` images. `calc_hist`, `calc_back_project`, `equalize_hist`, and `Clahe` are all parallelized via the `parallel` feature.

### `purecv-features2d`
- **FAST Feature Detector:** Real-time corner detector (`FastFeatureDetector`) supporting Type 5_8, 7_12, and 9_16 neighborhood configurations, plus optional non-maximum suppression.
Expand All @@ -86,7 +87,7 @@ Add the following to your `Cargo.toml`:

```toml
[dependencies]
purecv = "0.6"
purecv = "0.8"
```

PureCV's minimum supported Rust version (MSRV) is **1.88**.
Expand All @@ -104,7 +105,7 @@ PureCV's minimum supported Rust version (MSRV) is **1.88**.
### `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`
ESP32 (`purecv = { version = "0.8", default-features = false }`). Only `core`
and `alloc` are required (an allocator must be provided by the target).

| Module | `no_std` | Notes |
Expand All @@ -120,7 +121,7 @@ features gives the scalar, single-threaded code paths.

```toml
[dependencies]
purecv = { version = "0.6", default-features = false }
purecv = { version = "0.8", default-features = false }
```

```rust
Expand Down Expand Up @@ -149,14 +150,14 @@ To enable the `ndarray` feature:

```toml
[dependencies]
purecv = { version = "0.6", features = ["ndarray"] }
purecv = { version = "0.8", features = ["ndarray"] }
```

To enable SIMD + Parallel for maximum performance:

```toml
[dependencies]
purecv = { version = "0.6", features = ["parallel", "simd"] }
purecv = { version = "0.8", features = ["parallel", "simd"] }
```

### Usage Example
Expand Down Expand Up @@ -312,6 +313,9 @@ cargo run --example morphology
# Gaussian pyramids (pyr_down, pyr_up)
cargo run --example pyramids

# Histograms & contrast (calc_hist, calc_back_project, compare_hist, equalize_hist, CLAHE)
cargo run --example histogram

# Hough Transform (Lines and Circles detection)
cargo run --example hough_transform

Expand Down Expand Up @@ -344,10 +348,10 @@ 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 **308 unit tests** (plus **40 doc-tests**) covering:
PureCV uses a comprehensive suite of unit tests to ensure correctness and parity with OpenCV. The test suite currently includes **342 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`).
- **Imgproc module:** Filters, derivatives, edge detection, color conversions (including gray-to-RGB/BGR/RGBA/BGRA), thresholding, morphology (`erode`, `dilate`), pyramids (`pyr_down`, `pyr_up`), kernel helpers (`get_gaussian_kernel`, `get_sobel_kernels`), and histograms/CLAHE (`calc_hist`, `calc_back_project`, `compare_hist`, `equalize_hist`, `Clahe`).
- **Features2d module:** Keypoint structures (`KeyPoint`), FAST corner detection (`FastFeatureDetector`), scale pyramids, and ORB feature extraction & BRIEF descriptor extraction (`Orb`).
- **Video module:** Tracking and optical flow capabilities including `calc_optical_flow_pyr_lk` and `build_optical_flow_pyramid` implementations.
- **Calib3d module:** SVD, homography estimation, pose estimation (`solve_pnp`), and `rodrigues`.
Expand Down
95 changes: 95 additions & 0 deletions benches/imgproc_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use purecv::imgproc::derivatives::{laplacian, scharr, sobel};
use purecv::imgproc::edge::canny;
use purecv::imgproc::feature::corner_harris;
use purecv::imgproc::filter::{bilateral_filter, box_filter, gaussian_blur};
use purecv::imgproc::histogram::{calc_back_project, calc_hist, equalize_hist, Clahe, RangeSpec};
use purecv::imgproc::hough::{hough_circles, hough_lines, hough_lines_p};
use purecv::imgproc::threshold::{threshold, ThresholdTypes};
use purecv::imgproc::{cvt_color, ColorConversionCode};
Expand Down Expand Up @@ -254,6 +255,100 @@ fn bench_imgproc(c: &mut Criterion) {
.unwrap()
})
});

// calc_hist benchmark setup
let mut img_hist = Matrix::<u8>::new(size, size, 1);
for (i, p) in img_hist.data.iter_mut().enumerate() {
*p = (i % 256) as u8;
}
let hist_ranges = [RangeSpec::Uniform(0.0, 256.0)];

c.bench_function("calc_hist_1024x1024", |b| {
b.iter(|| {
calc_hist(
black_box(&[&img_hist]),
&[0],
None,
&[256],
&hist_ranges,
false,
None,
)
.unwrap()
})
});

// calc_back_project benchmark setup
let hist_for_backproj =
calc_hist(&[&img_hist], &[0], None, &[256], &hist_ranges, false, None).unwrap();

c.bench_function("calc_back_project_1024x1024", |b| {
b.iter(|| {
calc_back_project(
black_box(&[&img_hist]),
&[0],
&[256],
&hist_for_backproj,
&hist_ranges,
1.0,
)
.unwrap()
})
});

// equalize_hist benchmark setup
c.bench_function("equalize_hist_1024x1024", |b| {
b.iter(|| equalize_hist(black_box(&img_hist)).unwrap())
});

// Clahe::apply_u8 benchmark setup
let clahe = Clahe::new(2.0, Size2i::new(8, 8));

c.bench_function("clahe_apply_u8_1024x1024", |b| {
b.iter(|| clahe.apply_u8(black_box(&img_hist)).unwrap())
});
// compare_hist benchmark setup
let mut hist1 = Matrix::<f32>::new(256, 1, 1);
let mut hist2 = Matrix::<f32>::new(256, 1, 1);
for (i, p) in hist1.data.iter_mut().enumerate() {
*p = (i as f32 * 0.1).sin().abs();
}
for (i, p) in hist2.data.iter_mut().enumerate() {
*p = (i as f32 * 0.1).cos().abs();
}

c.bench_function("compare_hist_correl_256", |b| {
b.iter(|| {
purecv::imgproc::histogram::compare_hist(
black_box(&hist1),
black_box(&hist2),
purecv::imgproc::histogram::HistCompMethods::Correl,
)
.unwrap()
})
});

c.bench_function("compare_hist_intersection_256", |b| {
b.iter(|| {
purecv::imgproc::histogram::compare_hist(
black_box(&hist1),
black_box(&hist2),
purecv::imgproc::histogram::HistCompMethods::Intersection,
)
.unwrap()
})
});

c.bench_function("compare_hist_kullback_256", |b| {
b.iter(|| {
purecv::imgproc::histogram::compare_hist(
black_box(&hist1),
black_box(&hist2),
purecv::imgproc::histogram::HistCompMethods::KullbackLeibler,
)
.unwrap()
})
});
}

criterion_group!(benches, bench_imgproc);
Expand Down
35 changes: 34 additions & 1 deletion crates/wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,41 @@ Because WebAssembly runs linearly in memory and holds pointers to Rust `Vec` obj
Right now we have covered a large majority of operations for `core` and `imgproc`, and have started on `calib3d` and `video`:

- **Core**: Arithmetic (`add`, `subtract`, `multiply`, `absdiff` etc.), Structural (`hconcat`, `vconcat`, `flip`), Geometry, constants etc.
- **ImgProc**: Filters (`blur`, `gaussian_blur`, `bilateral_filter`), Thresholding (`threshold`), Coloring (`cvt_color`), Edge Derivatives (`canny`, `sobel`, `laplacian`), Morphology (`erode`, `dilate`, `morphology_ex`, `get_structuring_element`), Pyramids (`pyr_down`, `pyr_up`, `build_pyramid`), Feature Detection (`good_features_to_track`, `corner_sub_pix`).
- **ImgProc**: Filters (`blur`, `gaussian_blur`, `bilateral_filter`), Thresholding (`threshold`), Coloring (`cvt_color`), Edge Derivatives (`canny`, `sobel`, `laplacian`), Morphology (`erode`, `dilate`, `morphology_ex`, `get_structuring_element`), Pyramids (`pyr_down`, `pyr_up`, `build_pyramid`), Feature Detection (`good_features_to_track`, `corner_sub_pix`), Histograms (`calcHistUniform`, `calcHistNonUniform`, `calcBackProjectUniform`, `calcBackProjectNonUniform`, `compareHist`, `equalizeHist`, `Clahe`).
- **Video**: Optical Flow (`calc_optical_flow_pyr_lk`).
- **Calib3d**: Pose Estimation (`solve_pnp`, `solve_pnp_ransac`), Homography (`find_homography`), and geometry (`rodrigues`).

### Histogram & Contrast Operations

```javascript
import { Mat, MatVector, calcHistUniform, equalizeHist, Clahe } from '@webarkit/purecv-wasm';

// 1. Equalize Histogram (8-bit grayscale only)
const eqMat = equalizeHist(grayMat);

// 2. CLAHE (Contrast Limited Adaptive Histogram Equalization)
const clahe = new Clahe(40.0, 8, 8);
const enhancedMat = clahe.apply(grayMat);

// 3. Dense Histogram with uniform bins
const images = new MatVector();
images.push(grayMat);
const channels = [0];
const histSize = [256];
const ranges = [0.0, 256.0]; // [min, max] per channel
const hist = calcHistUniform(images, channels, undefined, histSize, ranges, false, undefined);

// To accumulate onto a previous histogram, pass accumulate=true and the
// existing histogram Mat as the last argument instead of undefined:
// calcHistUniform(images, channels, undefined, histSize, ranges, true, hist);
```

*Note:* `equalizeHist` and `Clahe.apply` currently support single-channel 8-bit images (`CV_8UC1`). Support for 16-bit images (`CV_16UC1`) is planned for a future release.

*Note:* `calcBackProjectUniform`/`calcBackProjectNonUniform` take an explicit
`histSize` argument (right after `channels`) describing the shape the
histogram was built with — a flat histogram's bin count alone can't be
unambiguously reconstructed into a multi-dimensional shape (e.g. 8 bins
could be `[8]` or `[2, 4]`).

Note: To interface between JavaScript Typed Arrays and `purecv-wasm`, please use the available getter functions (`.data()`) which directly retrieve a Float32Array or Uint8Array view into WASM memory.
Loading