Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
732a15b
test(video): pin build_optical_flow_pyramid to Scharr derivatives (#130)
kalwalt Sep 13, 2026
36ac7e8
fix(video): use Scharr derivatives in build_optical_flow_pyramid (#130)
kalwalt Sep 13, 2026
7b78a12
test(video): pin calc_optical_flow_pyramid_lk to Scharr derivatives (…
kalwalt Sep 13, 2026
b568a69
fix(video): use Scharr derivatives in calc_optical_flow_pyramid_lk (#…
kalwalt Sep 13, 2026
2d9a0c5
test(video): use relative tolerance for f32 min-eigen comparison (#130)
kalwalt Sep 13, 2026
676fa53
doc(video): correct stale Sobel references after Scharr switch (#130)
kalwalt Sep 13, 2026
afa8869
doc: update stale unit test count in README (342 -> 348)
kalwalt Sep 13, 2026
e20a77a
doc(video): address Copilot review findings on PR #137
kalwalt Sep 13, 2026
3900822
fix(video): match OpenCV LK eigenvalue scale
Voyagerroc-Code Sep 13, 2026
c3808f3
fix(video): correct LK eigenvalue divisor and pin OpenCV's scale
kalwalt Sep 14, 2026
516ef26
doc(video): correct the LK threshold migration factor
kalwalt Sep 14, 2026
033caa8
refactor(video): extract lk_iterate from lk_single_level (#131)
kalwalt Sep 14, 2026
ce4d1bf
test(video): pin lk_iterate's oscillation half-step fallback (#131)
kalwalt Sep 14, 2026
b6165f9
fix(video): add OpenCV's oscillation half-step fallback to LK (#131)
kalwalt Sep 14, 2026
be66ecb
chore(video): fix formatting/lint from oscillation half-step change (…
kalwalt Sep 14, 2026
de245a9
doc(video): fix dangling trace reference and comment alignment (#131)
kalwalt Sep 14, 2026
d12118d
doc: fix cross-fix staleness found in pre-merge review (#130, #131, #…
kalwalt Sep 18, 2026
24c93e3
test(video): pin near-degenerate LK window rejection to OpenCV's scal…
kalwalt Sep 19, 2026
ee42b93
fix(video): scale the LK determinant guard to match OpenCV (#142)
kalwalt Sep 19, 2026
061daf8
test(video): correct effective threshold value in test doc comment (#…
kalwalt Sep 19, 2026
6718ee8
doc: address final-review findings for #142 (README count, doc polish)
kalwalt Sep 19, 2026
83169cb
fix(video): use OpenCV's signed determinant test in the LK guard (#142)
kalwalt Sep 23, 2026
9b2004b
test(video): pin the LK determinant guard from the accept side (#142)
kalwalt Sep 23, 2026
c9f6ea1
test(video): LK must survive a degenerate coarse pyramid level (#145)
kalwalt Sep 23, 2026
0f95120
fix(video): only lose an LK point when level 0 is degenerate (#145)
kalwalt Sep 23, 2026
bbd801e
test(video): make the #145 propagated-flow test step-size independent
kalwalt Sep 23, 2026
c20b2ba
test(video): pin LK window sampling for even and non-square win_size …
kalwalt Sep 23, 2026
f6a20b9
fix(video): sample exactly win_size pixels in the LK window, like Ope…
kalwalt Sep 23, 2026
0344edc
test(video): pin the LK Newton step to the full Gauss-Newton step (#149)
kalwalt Sep 23, 2026
0d6582a
fix(video): scale LK's temporal difference to the Scharr gain (#149)
kalwalt Sep 23, 2026
94215ce
fix(ci): publish to npm via trusted publishing (OIDC) instead of NPM_…
kalwalt Sep 26, 2026
ef13ed8
chore(release): prepare for v0.9.0
kalwalt Sep 27, 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
20 changes: 12 additions & 8 deletions .agents/MIRI_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,18 @@ algorithms (RANSAC, ORB, Lucas-Kanade), and **none of them contain or reach
| `features2d::tests::test_orb_pyramid_dimensions` | 30.2s | `src/features2d/tests.rs` |

**Why so few exclusions suffice.** The distribution is extremely skewed: the top 5
tests are 82% of total runtime, while the remaining **293 tests complete in 139s
combined**. Nine annotations take the suite from 41 minutes to roughly 4.
tests are 82% of total runtime, while the remaining **341 tests complete in well
under two minutes combined**. Nine annotations take the suite from 41 minutes to roughly 4.

**Coverage check on the one borderline case.** `test_build_pyramid_with_derivatives`
exercises the Sobel path, which under `simd` reaches the `unsafe` in
`derivatives.rs:346,349`. That coverage is **not** lost: `imgproc::tests::test_sobel`
calls `sobel(&src_f32, 1, 0, 3, …)` (`src/imgproc/tests.rs:138`), matching
`fast_deriv_3x3`'s `TypeId == f32 && ksize == 3` trigger, and runs in 0.8s under Miri.
exercises the Scharr path (Sobel before #130, switched to Scharr by that fix), which
under `simd` reaches the `unsafe` in `derivatives.rs:346,349`. That coverage is
**not** lost: `imgproc::tests::test_scharr` calls `scharr(&src, 1, 0, …)`
(`src/imgproc/tests.rs:154`), matching `fast_deriv_3x3`'s `TypeId == f32 &&
kernel-length == 3` trigger (true for both Sobel `ksize=3` and Scharr `ksize=-1`,
since `get_deriv_kernel` returns a 3-element kernel for both), and runs in ~0.75s
under Miri — same order of magnitude as `test_sobel`, which still covers the
Sobel path used elsewhere (`canny`, `hough_circles`, …).

### Annotation convention

Expand Down Expand Up @@ -483,7 +487,7 @@ This turns a claim Miri would contradict into one Miri actively backs.
| # | Decision | Alternatives considered | Rationale |
|---|----------|-------------------------|-----------|
| 10 | Set `MIRIFLAGS: -Zmiri-deterministic-floats` — **revises #4** | `#[cfg_attr(miri, ignore)]` on `test_randn_determinism`; leave the failure | #4 said "no flags", but measurement found a genuine need. The flag keeps the RNG determinism contract under test instead of disabling it, and targets a documented Miri behaviour rather than a real defect |
| 11 | Exclude the 9 tests over 30s — **implements #2** | Exclude >10s (15 tests); shrink inputs under `cfg(miri)`; exclude nothing and raise the timeout | Measurement showed an extreme skew: 5 tests = 82% of runtime, 293 tests = 139s. Nine annotations buy a 10× speedup; none of the nine touch `unsafe`, so no UB coverage is lost |
| 11 | Exclude the 9 tests over 30s — **implements #2** | Exclude >10s (15 tests); shrink inputs under `cfg(miri)`; exclude nothing and raise the timeout | Measurement showed an extreme skew: 5 tests = 82% of runtime, the rest (341 tests as of #131) well under two minutes combined. Nine annotations buy a 10× speedup; none of the nine touch `unsafe`, so no UB coverage is lost |

---

Expand All @@ -493,5 +497,5 @@ This turns a claim Miri would contradict into one Miri actively backs.
|-----------------------|--------------|
| Miri CI job passes on `dev` | ✅ Both legs green locally (§9). Pending confirmation on `ubuntu-latest`. |
| All existing unsafe verified UB-free, or documented exceptions | ✅ 6 of 6 reachable production blocks verified clean. Exception: the `parallel`-only pair, documented in §5. |
| Incompatible tests annotated `#[cfg_attr(miri, ignore)]` | ✅ 9 tests, each with a reason comment (§4). Excluded for runtime, not incompatibility — nothing in the suite proved Miri-incompatible. |
| Incompatible tests annotated `#[cfg_attr(miri, ignore)]` | ✅ 9 tests, each with a reason comment (§4). Excluded for runtime, not incompatibility — nothing in the suite proved Miri-incompatible. #130 and #138 together added three more tests exercising the same unsafe Scharr fast path, but all three measured well under the 30s threshold (§4) and are not excluded. |
| Plan document identifying included/excluded code with rationale | ✅ This document, tracked in git via a `.gitignore` exception. |
24 changes: 22 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,38 @@ jobs:
- name: Publish to Crates.io
run: cargo publish --token ${{ secrets.CRATES_TOKEN }} -p purecv

# Publishes @webarkit/purecv-wasm via npm Trusted Publishing (OIDC), not a
# long-lived NPM_TOKEN (#129). Requires a Trusted Publisher configured on
# npmjs.com for this package: owner `webarkit`, repository `purecv`,
# workflow `release.yml`, no environment.
publish-npm:
needs: [release]
runs-on: ubuntu-latest
# Job-level permissions replace the workflow-level block for this job:
# checkout only needs to read, and the OIDC token is what npm exchanges
# for a short-lived publish credential (and signs provenance with).
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- name: Install Rust
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: Set up Node
uses: actions/setup-node@v7
with:
node-version: '24'
# Trusted publishing needs npm >= 11.5.1, and Node 24's bundled npm is
# not guaranteed to be new enough (24.18.0 ships npm 10.9.4, see #129).
- name: Ensure npm >= 11.5.1
run: |
npm install -g npm@11
npm --version
node -e 'const [a,b,c]=process.argv[1].split(".").map(Number); if (a<11 || (a===11 && (b<5 || (b===5 && c<1)))) { console.error(`npm ${process.argv[1]} < 11.5.1`); process.exit(1); }' "$(npm --version)"
- name: Build and Publish to NPM
run: |
npm run build:wasm
cd crates/wasm/pkg
npm config set //registry.npmjs.org/:_authToken ${{ secrets.NPM_TOKEN }}
npm publish --access public
npm publish --access public --provenance
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,54 @@

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

## [0.9.0] - 2026-09-27

### ⚙️ Miscellaneous Tasks

- *(video)* Fix formatting/lint from oscillation half-step change (#131)

### 🐛 Bug Fixes

- *(video)* Use Scharr derivatives in build_optical_flow_pyramid (#130)
- *(video)* Use Scharr derivatives in calc_optical_flow_pyramid_lk (#130)
- *(video)* Match OpenCV LK eigenvalue scale
- *(video)* [**breaking**] Correct LK eigenvalue divisor and pin OpenCV's scale
- *(video)* Add OpenCV's oscillation half-step fallback to LK (#131)
- *(video)* Scale the LK determinant guard to match OpenCV (#142)
- *(video)* Use OpenCV's signed determinant test in the LK guard (#142)
- *(video)* Only lose an LK point when level 0 is degenerate (#145)
- *(video)* Sample exactly win_size pixels in the LK window, like OpenCV (#144)
- *(video)* Scale LK's temporal difference to the Scharr gain (#149)
- *(ci)* Publish to npm via trusted publishing (OIDC) instead of NPM_TOKEN (#129)

### 📚 Documentation

- *(video)* Correct stale Sobel references after Scharr switch (#130)
- Update stale unit test count in README (342 -> 348)
- *(video)* Address Copilot review findings on PR #137
- *(video)* Correct the LK threshold migration factor
- *(video)* Fix dangling trace reference and comment alignment (#131)
- Fix cross-fix staleness found in pre-merge review (#130, #131, #138)
- Address final-review findings for #142 (README count, doc polish)

### 🚜 Refactor

- *(video)* Extract lk_iterate from lk_single_level (#131)

### 🧪 Testing

- *(video)* Pin build_optical_flow_pyramid to Scharr derivatives (#130)
- *(video)* Pin calc_optical_flow_pyramid_lk to Scharr derivatives (#130)
- *(video)* Use relative tolerance for f32 min-eigen comparison (#130)
- *(video)* Pin lk_iterate's oscillation half-step fallback (#131)
- *(video)* Pin near-degenerate LK window rejection to OpenCV's scale (#142)
- *(video)* Correct effective threshold value in test doc comment (#142)
- *(video)* Pin the LK determinant guard from the accept side (#142)
- *(video)* LK must survive a degenerate coarse pyramid level (#145)
- *(video)* Make the #145 propagated-flow test step-size independent
- *(video)* Pin LK window sampling for even and non-square win_size (#144)
- *(video)* Pin the LK Newton step to the full Gauss-Newton step (#149)

## [0.8.0] - 2026-09-02

### ⚙️ Miscellaneous Tasks
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.8.0"
version = "0.9.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.8.0"
version = "0.9.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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Add the following to your `Cargo.toml`:

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

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

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

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

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

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

To enable SIMD + Parallel for maximum performance:

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

### Usage Example
Expand Down Expand Up @@ -348,7 +348,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 **342 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 **359 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`), kernel helpers (`get_gaussian_kernel`, `get_sobel_kernels`), and histograms/CLAHE (`calc_hist`, `calc_back_project`, `compare_hist`, `equalize_hist`, `Clahe`).
Expand Down
11 changes: 7 additions & 4 deletions benches/benchmark_results.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,13 @@ Parallel + SIMD achieves the best throughput.

#### `build_optical_flow_pyramid` with derivatives

When `with_derivatives = true`, the per-level Sobel (Ix, Iy) passes are
independent across pyramid levels and run concurrently via Rayon with the
`parallel` feature. For a 4-level pyramid the expected speedup is up to
4× (level count) times the per-level Sobel speedup. In practice the
When `with_derivatives = true`, the per-level Scharr (Ix, Iy) passes
(Sobel before #130, switched to Scharr to match OpenCV) are independent
across pyramid levels and run concurrently via Rayon with the `parallel`
feature. Scharr shares the same `fast_deriv_3x3` code path as Sobel, so
the performance characteristics below are unchanged by that switch. For
a 4-level pyramid the expected speedup is up to 4× (level count) times
the per-level Scharr speedup. In practice the
coarser levels are very small so the scaling is sub-linear, but a 2–3×
wall-clock gain is typical.

Expand Down
2 changes: 1 addition & 1 deletion benches/video_bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ fn make_grid_points(size: usize, step: usize) -> Vec<Point2f> {
///
/// Benchmark names:
/// * `build_optical_flow_pyramid/no_deriv` — pyramid only
/// * `build_optical_flow_pyramid/with_deriv` — pyramid + Sobel Ix, Iy per level
/// * `build_optical_flow_pyramid/with_deriv` — pyramid + Scharr Ix, Iy per level
/// (this is where the `parallel` feature gives the most gain in this function)
fn bench_build_pyramid(c: &mut Criterion) {
let size = 512usize;
Expand Down
2 changes: 1 addition & 1 deletion crates/wasm/pkg/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"Walter Perdan \u003chttps://github.com/kalwalt\u003e"
],
"description": "A pure Rust, high-performance computer vision library focused on safety and portability.",
"version": "0.8.0",
"version": "0.9.0",
"license": "LGPL-2.1-or-later",
"repository": {
"type": "git",
Expand Down
12 changes: 10 additions & 2 deletions crates/wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1966,7 +1966,7 @@ pub fn morph_blackhat() -> i32 {
///
/// * `win_w`, `win_h` – Tracking window size.
/// * `max_level` – Maximum number of additional pyramid levels.
/// * `with_derivatives`– Compute Sobel derivatives alongside the pyramid.
/// * `with_derivatives`– Compute Scharr derivatives alongside the pyramid.
/// * `pyr_border` – Border interpolation for downsampling.
/// * `deriv_border` – Border interpolation for derivatives.
///
Expand Down Expand Up @@ -2038,7 +2038,15 @@ pub fn wasm_build_optical_flow_pyramid(
/// * `epsilon` – Convergence threshold.
/// * `flags` – Combine `OPTFLOW_USE_INITIAL_FLOW()` and/or
/// `OPTFLOW_LK_GET_MIN_EIGENVALS()`.
/// * `min_eigen_threshold` – Min eigenvalue below which a point is lost.
/// * `min_eigen_threshold` – Min eigenvalue below which a point is lost. The
/// gradient matrix is built from Scharr derivatives and normalized by
/// `FLT_SCALE = 2^-20` and the window area, matching
/// `cv::calcOpticalFlowPyrLK` (see #130 and #138), so this value is on the
/// same scale as OpenCV's and its `1e-4` default transfers directly.
/// Thresholds tuned against a purecv build predating both fixes must be
/// retuned: Scharr multiplies the gradient matrix by 16 relative to the
/// Sobel derivatives used then, and `FLT_SCALE` divides by `2^20`, so such
/// thresholds read about `2^20 / 16 = 65536` times larger on this scale.
///
/// ```js
/// const gray0 = Mat.fromU8Data(h, w, 1, frameData0);
Expand Down
2 changes: 1 addition & 1 deletion examples/optical_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ fn draw_line(
/// The output has one header row followed by one data row per point:
/// ```text
/// idx,prev_x,prev_y,next_x,next_y,flow_x,flow_y,status,min_eigen
/// 0,158.000,124.000,162.010,127.000,4.010,3.000,1,88873.757813
/// 0,158.000,124.000,161.922,126.883,3.922,2.883,1,1.470835
/// …
/// ```
///
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "purecv",
"version": "0.8.0",
"version": "0.9.0",
"description": "A pure Rust, high-performance computer vision library focused on safety and portability.",
"private": true,
"scripts": {
Expand Down
Loading
Loading