Skip to content

Commit 8a15687

Browse files
Merge branch 'develop'
2 parents 1f60d32 + 823bf5a commit 8a15687

File tree

6 files changed

+86
-28
lines changed

6 files changed

+86
-28
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
### Added
55

66
### Changed
7+
- Fixed orientation of sobel filters
8+
- Fixed remove limit on magnitude in sobel magnitude calculation
79

810
### Removed
911

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ndarray-vision"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
authors = ["xd009642 <[email protected]>"]
55
description = "A computer vision library built on top of ndarray"
66
repository = "https://github.com/xd009642/ndarray-vision"
@@ -26,9 +26,11 @@ noisy_float = { version = "0.2", default-features = false }
2626
num-traits = { version = "0.2", default-features = false }
2727

2828
[dev-dependencies]
29+
ndarray = { version = "0.15", features = ["approx"] }
2930
ndarray-rand = "0.14.0"
3031
rand = "0.8"
3132
assert_approx_eq = "1.1.0"
33+
approx = "0.4"
3234
noisy_float = "0.2"
3335
png = "0.16"
3436
ndarray-linalg = { version = "0.14", features = ["intel-mkl"] }

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ See the examples and tests for basic usage.
2424
# Performance
2525

2626
Not a lot of work has been put towards performance yet but a rudimentary
27-
benchmarking project exists [here](https://github.com/xd009642/ndarray-vision-benchmarking)
27+
benchmarking project exists [here](https://github.com/rust-cv/ndarray-vision-benchmarking)
2828
for comparative benchmarks against other image processing libraries in rust.

src/core/colour_models.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -867,7 +867,7 @@ impl ColourModel for RGBA {
867867
mod tests {
868868
use super::*;
869869
use ndarray::s;
870-
use ndarray_rand::{RandomExt, F32};
870+
use ndarray_rand::RandomExt;
871871
use ndarray_stats::QuantileExt;
872872
use rand::distributions::Uniform;
873873

@@ -920,7 +920,7 @@ mod tests {
920920
#[test]
921921
fn basic_xyz_rgb_checks() {
922922
let mut image = Image::<f32, RGB>::new(100, 100);
923-
let new_data = Array3::<f32>::random(image.data.dim(), F32(Uniform::new(0.0, 1.0)));
923+
let new_data = Array3::<f32>::random(image.data.dim(), Uniform::new(0.0, 1.0));
924924
image.data = new_data;
925925

926926
let xyz = Image::<f32, CIEXYZ>::from(image.clone());
@@ -937,7 +937,7 @@ mod tests {
937937
#[test]
938938
fn generic3_checks() {
939939
let mut image = Image::<f32, RGB>::new(100, 100);
940-
let new_data = Array3::<f32>::random(image.data.dim(), F32(Uniform::new(0.0, 1.0)));
940+
let new_data = Array3::<f32>::random(image.data.dim(), Uniform::new(0.0, 1.0));
941941
image.data = new_data;
942942
let gen = Image::<_, Generic3>::from(image.clone());
943943
// Normally don't check floats with equality but data shouldn't have
@@ -987,7 +987,7 @@ mod tests {
987987
#[test]
988988
fn generic_model_expand() {
989989
let mut image = Image::<f32, Generic1>::new(100, 100);
990-
let new_data = Array3::<f32>::random(image.data.dim(), F32(Uniform::new(0.0, 1.0)));
990+
let new_data = Array3::<f32>::random(image.data.dim(), Uniform::new(0.0, 1.0));
991991
image.data = new_data;
992992

993993
let large = Image::<_, Generic5>::from(image.clone());

src/processing/kernels.rs

+15-14
Original file line numberDiff line numberDiff line change
@@ -220,15 +220,16 @@ where
220220
/// Build a fixed size kernel with the given parameters
221221
fn build_with_params(p: Self::Params) -> Result<Array3<T>, Error> {
222222
let two = T::from_i8(2).ok_or_else(|| Error::NumericError)?;
223-
224-
let vert_sobel = arr2(&[
225-
[-T::one(), T::zero(), T::one()],
226-
[-two, T::zero(), two],
227-
[-T::one(), T::zero(), T::one()],
223+
// Gets the gradient along the horizontal axis
224+
#[rustfmt::skip]
225+
let horz_sobel = arr2(&[
226+
[T::one(), T::zero(), -T::one()],
227+
[two, T::zero(), -two],
228+
[T::one(), T::zero(), -T::one()],
228229
]);
229230
let sobel = match p {
230-
Orientation::Vertical => vert_sobel,
231-
Orientation::Horizontal => vert_sobel.t().to_owned(),
231+
Orientation::Vertical => horz_sobel.t().to_owned(),
232+
Orientation::Horizontal => horz_sobel,
232233
};
233234
Ok(sobel.insert_axis(Axis(2)))
234235
}
@@ -253,25 +254,25 @@ mod tests {
253254
fn test_sobel_filter() {
254255
// As sobel works with integer numbers I'm going to ignore the perils of
255256
// floating point comparisons... for now.
256-
let filter: Array3<f32> = SobelFilter::build_with_params(Orientation::Vertical).unwrap();
257+
let filter: Array3<f32> = SobelFilter::build_with_params(Orientation::Horizontal).unwrap();
257258

258259
assert_eq!(
259260
filter,
260261
arr3(&[
261-
[[-1.0f32], [0.0f32], [1.0f32]],
262-
[[-2.0f32], [0.0f32], [2.0f32]],
263-
[[-1.0f32], [0.0f32], [1.0f32]]
262+
[[1.0f32], [0.0f32], [-1.0f32]],
263+
[[2.0f32], [0.0f32], [-2.0f32]],
264+
[[1.0f32], [0.0f32], [-1.0f32]]
264265
])
265266
);
266267

267-
let filter: Array3<f32> = SobelFilter::build_with_params(Orientation::Horizontal).unwrap();
268+
let filter: Array3<f32> = SobelFilter::build_with_params(Orientation::Vertical).unwrap();
268269

269270
assert_eq!(
270271
filter,
271272
arr3(&[
272-
[[-1.0f32], [-2.0f32], [-1.0f32]],
273+
[[1.0f32], [2.0f32], [1.0f32]],
273274
[[0.0f32], [0.0f32], [0.0f32]],
274-
[[1.0f32], [2.0f32], [1.0f32]]
275+
[[-1.0f32], [-2.0f32], [-1.0f32]]
275276
])
276277
)
277278
}

src/processing/sobel.rs

+61-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::core::*;
22
use crate::processing::*;
33
use core::mem::MaybeUninit;
44
use core::ops::Neg;
5-
use ndarray::{prelude::*, s, DataMut, OwnedRepr};
5+
use ndarray::{prelude::*, s, DataMut, OwnedRepr, Zip};
66
use num_traits::{cast::FromPrimitive, real::Real, Num, NumAssignOps};
77
use std::marker::Sized;
88

@@ -61,12 +61,9 @@ where
6161
for r in 0..res_shape.0 {
6262
for c in 0..res_shape.1 {
6363
for channel in 0..res_shape.2 {
64-
let mut temp = (h_deriv[[r, c, channel]].powi(2)
64+
let temp = (h_deriv[[r, c, channel]].powi(2)
6565
+ v_deriv[[r, c, channel]].powi(2))
6666
.sqrt();
67-
if temp > T::one() {
68-
temp = T::one();
69-
}
7067
unsafe {
7168
*result.uget_mut([r, c, channel]) = MaybeUninit::new(temp);
7269
}
@@ -81,10 +78,15 @@ where
8178

8279
let mut magnitude = h_deriv.mapv(|x| x.powi(2)) + v_deriv.mapv(|x| x.powi(2));
8380
magnitude.mapv_inplace(|x| x.sqrt());
84-
magnitude.mapv_inplace(|x| if x > T::one() { T::one() } else { x });
8581

86-
let mut rotation = v_deriv / h_deriv;
87-
rotation.mapv_inplace(|x| x.atan());
82+
let dim = h_deriv.dim();
83+
let mut rotation = Array3::uninit((dim.0, dim.1, dim.2));
84+
Zip::from(&mut rotation)
85+
.and(&h_deriv)
86+
.and(&v_deriv)
87+
.for_each(|r, &h, &v| *r = MaybeUninit::new(h.atan2(v)));
88+
89+
let rotation = unsafe { rotation.assume_init() };
8890

8991
Ok((magnitude, rotation))
9092
}
@@ -109,3 +111,54 @@ where
109111
.map(|(m, r)| (Image::from_data(m), Image::from_data(r)))
110112
}
111113
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
use super::*;
118+
use approx::*;
119+
120+
#[test]
121+
fn simple() {
122+
let mut image: Image<f64, Gray> = ImageBase::new(11, 11);
123+
image.data.slice_mut(s![4..7, 4..7, ..]).fill(1.0);
124+
image.data.slice_mut(s![3..8, 5, ..]).fill(1.0);
125+
image.data.slice_mut(s![5, 3..8, ..]).fill(1.0);
126+
127+
let sobel = image.full_sobel().unwrap();
128+
129+
// Did a calculation of sobel_mag[1..9, 1..9, ..] in a spreadsheet
130+
#[rustfmt::skip]
131+
let mag = vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
132+
0.0, 0.0, 0.0, 1.41421356237301, 2.0, 1.41421356237301, 0.0, 0.0, 0.0,
133+
0.0, 0.0, 1.41421356237301, 4.24264068711929, 4.0, 4.24264068711929, 1.4142135623731, 0.0, 0.0,
134+
0.0, 1.4142135623731, 4.24264068711929, 4.24264068711929, 2.0, 4.24264068711929, 4.24264068711929, 1.4142135623731, 0.0,
135+
0.0, 2.0, 4.0, 2.0, 0.0, 2.0, 4.0, 2.0, 0.0,
136+
0.0, 1.4142135623731, 4.24264068711929, 4.24264068711929, 2.0, 4.24264068711929, 4.24264068711929, 1.4142135623731, 0.0,
137+
0.0, 0.0, 1.4142135623731, 4.24264068711929, 4.0, 4.24264068711929, 1.4142135623731, 0.0,
138+
0.0, 0.0, 0.0, 0.0, 1.4142135623731, 2.0, 1.4142135623731, 0.0, 0.0,
139+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
140+
];
141+
142+
let mag = Array::from_shape_vec((9, 9), mag).unwrap();
143+
144+
assert_abs_diff_eq!(sobel.0.data.slice(s![1..10, 1..10, 0]), mag, epsilon = 1e-5);
145+
146+
let only_mag = image.apply_sobel().unwrap();
147+
assert_abs_diff_eq!(sobel.0.data, only_mag.data);
148+
149+
// Did a calculation of sobel_rot[1..9, 1..9, ..] in a spreadsheet
150+
#[rustfmt::skip]
151+
let rot = vec![0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,
152+
0.00000000000000,0.00000000000000,0.00000000000000,-2.35619449019234,3.14159265358979,2.35619449019234,0.00000000000000,0.00000000000000,0.00000000000000,
153+
0.00000000000000,0.00000000000000,-2.35619449019234,-2.35619449019234,3.14159265358979,2.35619449019234,2.35619449019234,0.00000000000000,0.00000000000000,
154+
0.00000000000000,-2.35619449019234,-2.35619449019234,-2.35619449019234,3.14159265358979,2.35619449019234,2.35619449019234,2.35619449019234,0.00000000000000,
155+
0.00000000000000,-1.57079632679490,-1.57079632679490,-1.57079632679490,0.00000000000000,1.57079632679490,1.57079632679490,1.57079632679490,0.00000000000000,
156+
0.00000000000000,-0.78539816339745,-0.78539816339745,-0.78539816339745,0.00000000000000,0.78539816339745,0.78539816339745,0.78539816339745,0.00000000000000,
157+
0.00000000000000,0.00000000000000,-0.78539816339745,-0.78539816339745,0.00000000000000,0.78539816339745,0.78539816339745,0.00000000000000,0.00000000000000,
158+
0.00000000000000,0.00000000000000,0.00000000000000,-0.78539816339745,0.00000000000000,0.78539816339745,0.00000000000000,0.00000000000000,0.00000000000000,
159+
0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000,0.00000000000000];
160+
let rot = Array::from_shape_vec((9, 9), rot).unwrap();
161+
162+
assert_abs_diff_eq!(sobel.1.data.slice(s![1..10, 1..10, 0]), rot, epsilon = 1e-5);
163+
}
164+
}

0 commit comments

Comments
 (0)