Skip to content

Commit

Permalink
switch from quickcheck to proptest
Browse files Browse the repository at this point in the history
  • Loading branch information
ripytide committed Jun 24, 2024
1 parent 350512f commit cc2f371
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 271 deletions.
3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ exclude = [".github/*", "examples/*", "tests/*"]

[features]
default = ["rayon", "image/default"]
property-testing = ["quickcheck"]
display-window = ["sdl2"]
rayon = ["dep:rayon", "image/rayon"]

Expand All @@ -33,7 +32,6 @@ rand = { version = "0.8.5", default-features = false, features = [
] }
rand_distr = { version = "0.4.3", default-features = false }
rayon = { version = "1.8.0", optional = true, default-features = false }
quickcheck = { version = "1.0.3", optional = true, default-features = false }
sdl2 = { version = "0.36", optional = true, default-features = false, features = [
"bundled",
] }
Expand All @@ -45,7 +43,6 @@ getrandom = { version = "0.2", default-features = false, features = ["js"] }
[dev-dependencies]
assert_approx_eq = "1.1.0"
proptest = "1.4.0"
quickcheck = "1.0.3"
wasm-bindgen-test = "0.3.38"

[package.metadata.docs.rs]
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ from not using linear color spaces.
This library provides both single-threaded and multi-threaded variations of several functions
by using [rayon](https://github.com/rayon-rs/rayon).

Depending on image size and the amount of work performed per pixel the parallel versions may not
Depending on image size and the amount of work performed per pixel the parallel versions may not
always be faster - we recommend benchmarking for your specific use-case.

## Crate Features
Expand All @@ -52,13 +52,11 @@ always be faster - we recommend benchmarking for your specific use-case.

- `katexit`: enables latex in documentation via
[katexit](https://github.com/termoshtt/katexit)
- `property-testing`: enables `quickcheck`
- `quickcheck`: exposes helper types and methods to enable property testing
via [quickcheck](https://github.com/BurntSushi/quickcheck)
- `display-window`: enables `sdl2`
- `sdl2`: enables the displaying of images (using `imageproc::window`) with
[sdl2](https://github.com/Rust-SDL2/rust-sdl2)

## How to contribute

See [CONTRIBUTING.md](CONTRIBUTING.md).
See [CONTRIBUTING.md](CONTRIBUTING.md).

143 changes: 56 additions & 87 deletions src/distance_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,34 +443,12 @@ fn intersection<S: Source + ?Sized>(f: &S, p: usize, q: usize) -> f64 {
mod tests {
use super::*;
use crate::definitions::Image;
use crate::property_testing::GrayTestImage;
use crate::utils::pixel_diff_summary;
use crate::proptest_utils::arbitrary_image;
use image::{GrayImage, Luma};
use quickcheck::{quickcheck, Arbitrary, Gen, TestResult};
use proptest::prelude::*;
use std::cmp::max;
use std::f64;

/// Avoid generating garbage floats during certain calculations below.
#[derive(Debug, Clone)]
struct BoundedFloat(f64);

impl Arbitrary for BoundedFloat {
fn arbitrary(g: &mut Gen) -> Self {
let mut f;

loop {
f = f64::arbitrary(g);

if f.is_normal() {
f = f.clamp(-1_000_000.0, 1_000_000.0);
break;
}
}

BoundedFloat(f)
}
}

#[test]
fn test_distance_transform_saturation() {
// A single foreground pixel in the top-left
Expand All @@ -486,48 +464,22 @@ mod tests {
assert_pixels_eq!(distances, expected);
}

impl Sink for Vec<f64> {
fn put(&mut self, idx: usize, value: f64) {
self[idx] = value;
}
fn len(&self) -> usize {
self.len()
}
}

fn distance_transform_1d(f: &Vec<f64>) -> Vec<f64> {
let mut r = vec![0.0; f.len()];
let mut e = LowerEnvelope::new(f.len());
distance_transform_1d_mut(f, &mut r, &mut e);
r
}
proptest! {
#[test]
fn test_distance_transform_1d_matches_reference_implementation(f in proptest::collection::vec(-10_000_000.0..10_000_000.0, 0..50)) {
let actual = distance_transform_1d(&f);
let expected = distance_transform_1d_reference(&f);

#[test]
fn test_distance_transform_1d_constant() {
let f = vec![0.0, 0.0, 0.0];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[0.0, 0.0, 0.0]);
}

#[test]
fn test_distance_transform_1d_descending_gradient() {
let f = vec![7.0, 5.0, 3.0, 1.0];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[6.0, 4.0, 2.0, 1.0]);
}
assert_eq!(actual, expected);
}

#[test]
fn test_distance_transform_1d_ascending_gradient() {
let f = vec![1.0, 3.0, 5.0, 7.0];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[1.0, 2.0, 4.0, 6.0]);
}
#[test]
fn test_euclidean_squared_distance_transform_matches_reference_implementation(image in arbitrary_image::<Luma<u8>>(0..10, 0..10)) {
let expected = euclidean_squared_distance_transform_reference(&image);
let actual = euclidean_squared_distance_transform(&image);

#[test]
fn test_distance_transform_1d_with_infinities() {
let f = vec![f64::INFINITY, f64::INFINITY, 5.0, f64::INFINITY];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[9.0, 6.0, 5.0, 6.0]);
assert_eq!(actual, expected)
}
}

// Simple implementation of 1d distance transform which performs an
Expand All @@ -546,19 +498,6 @@ mod tests {
ret
}

#[cfg_attr(miri, ignore = "slow")]
#[test]
fn test_distance_transform_1d_matches_reference_implementation() {
fn prop(f: Vec<BoundedFloat>) -> bool {
let v: Vec<f64> = f.into_iter().map(|n| n.0).collect();
let expected = distance_transform_1d_reference(&v);
let actual = distance_transform_1d(&v);
expected == actual
}

quickcheck(prop as fn(Vec<BoundedFloat>) -> bool);
}

fn euclidean_squared_distance_transform_reference(image: &Image<Luma<u8>>) -> Image<Luma<f64>> {
let (width, height) = image.dimensions();

Expand Down Expand Up @@ -586,18 +525,48 @@ mod tests {
dists
}

#[cfg_attr(miri, ignore = "slow")]
#[test]
fn test_euclidean_squared_distance_transform_matches_reference_implementation() {
fn prop(image: GrayTestImage) -> TestResult {
let expected = euclidean_squared_distance_transform_reference(&image.0);
let actual = euclidean_squared_distance_transform(&image.0);
match pixel_diff_summary(&actual, &expected) {
None => TestResult::passed(),
Some(err) => TestResult::error(err),
}
impl Sink for Vec<f64> {
fn put(&mut self, idx: usize, value: f64) {
self[idx] = value;
}
quickcheck(prop as fn(GrayTestImage) -> TestResult);
fn len(&self) -> usize {
self.len()
}
}

fn distance_transform_1d(f: &Vec<f64>) -> Vec<f64> {
let mut r = vec![0.0; f.len()];
let mut e = LowerEnvelope::new(f.len());
distance_transform_1d_mut(f, &mut r, &mut e);
r
}

#[test]
fn test_distance_transform_1d_constant() {
let f = vec![0.0, 0.0, 0.0];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[0.0, 0.0, 0.0]);
}

#[test]
fn test_distance_transform_1d_descending_gradient() {
let f = vec![7.0, 5.0, 3.0, 1.0];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[6.0, 4.0, 2.0, 1.0]);
}

#[test]
fn test_distance_transform_1d_ascending_gradient() {
let f = vec![1.0, 3.0, 5.0, 7.0];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[1.0, 2.0, 4.0, 6.0]);
}

#[test]
fn test_distance_transform_1d_with_infinities() {
let f = vec![f64::INFINITY, f64::INFINITY, 5.0, f64::INFINITY];
let dists = distance_transform_1d(&f);
assert_eq!(dists, &[9.0, 6.0, 5.0, 6.0]);
}

#[test]
Expand Down
26 changes: 9 additions & 17 deletions src/filter/median.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,9 @@ mod benches {
#[cfg(test)]
mod tests {
use super::*;
use crate::property_testing::GrayTestImage;
use crate::utils::pixel_diff_summary;
use crate::proptest_utils::arbitrary_image;
use image::{GrayImage, Luma};
use quickcheck::{quickcheck, TestResult};
use proptest::prelude::*;
use std::cmp::{max, min};

// Reference implementation of median filter - written to be as simple as possible,
Expand Down Expand Up @@ -470,20 +469,13 @@ mod tests {
sorted[mid]
}

#[cfg_attr(miri, ignore = "slow")]
#[test]
fn test_median_filter_matches_reference_implementation() {
fn prop(image: GrayTestImage, x_radius: u32, y_radius: u32) -> TestResult {
let x_radius = x_radius % 5;
let y_radius = y_radius % 5;
let expected = reference_median_filter(&image.0, x_radius, y_radius);
let actual = median_filter(&image.0, x_radius, y_radius);

match pixel_diff_summary(&actual, &expected) {
None => TestResult::passed(),
Some(err) => TestResult::error(err),
}
proptest! {
#[test]
fn test_median_filter_matches_reference_implementation(image in arbitrary_image::<Luma<u8>>(0..10, 0..10), x_radius in 0_u32..5, y_radius in 0_u32..5) {
let expected = reference_median_filter(&image, x_radius, y_radius);
let actual = median_filter(&image, x_radius, y_radius);

assert_eq!(actual, expected);
}
quickcheck(prop as fn(GrayTestImage, u32, u32) -> TestResult);
}
}
2 changes: 1 addition & 1 deletion src/filter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ mod tests {
// I think the interesting edge cases here are determined entirely
// by the relative sizes of the kernel and the image side length, so
// I'm just enumerating over small values instead of generating random
// examples via quickcheck.
// examples via proptesting.
for height in 0..5 {
for width in 0..5 {
for kernel_length in 0..15 {
Expand Down
24 changes: 9 additions & 15 deletions src/integral_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,11 +464,9 @@ pub fn column_running_sum(image: &GrayImage, column: u32, buffer: &mut [u32], pa
#[cfg(test)]
mod tests {
use super::*;
use crate::definitions::Image;
use crate::property_testing::GrayTestImage;
use crate::utils::pixel_diff_summary;
use crate::{definitions::Image, proptest_utils::arbitrary_image};
use image::{GenericImage, Luma};
use quickcheck::{quickcheck, TestResult};
use proptest::prelude::*;

#[test]
fn test_integral_image_gray() {
Expand Down Expand Up @@ -580,18 +578,14 @@ mod tests {
out
}

#[cfg_attr(miri, ignore = "slow")]
#[test]
fn test_integral_image_matches_reference_implementation() {
fn prop(image: GrayTestImage) -> TestResult {
let expected = integral_image_ref(&image.0);
let actual = integral_image(&image.0);
match pixel_diff_summary(&actual, &expected) {
None => TestResult::passed(),
Some(err) => TestResult::error(err),
}
proptest! {
#[test]
fn test_integral_image_matches_reference_implementation(image in arbitrary_image::<Luma<u8>>(0..10, 0..10)) {
let expected = integral_image_ref(&image);
let actual = integral_image(&image);

assert_eq!(expected, actual);
}
quickcheck(prop as fn(GrayTestImage) -> TestResult);
}
}

Expand Down
2 changes: 0 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ pub mod morphology;
pub mod noise;
pub mod pixelops;
pub mod point;
#[cfg(any(feature = "property-testing", test))]
pub mod property_testing;
pub mod rect;
pub mod region_labelling;
pub mod seam_carving;
Expand Down
Loading

0 comments on commit cc2f371

Please sign in to comment.