imeka / ndarray-ndimage Goto Github PK
View Code? Open in Web Editor NEWMultidimensional image processing for ndarray
License: Apache License 2.0
Multidimensional image processing for ndarray
License: Apache License 2.0
We're currently using the Kernel3d
enum (Star and Full) as the only choices for kernels.
The advantage is that we're quite fast because we know exactly where the "weights" are. This makes it possible to hardcode the conditions. For example, we're around 5 times faster than SciPy in morphology operations.
While this is nice, the problem is that the user can't choose it's own special kernel. I have personally never seen a non-start or non-full kernel choice in the wild, but it could happen.
Ideas:
Other(arr)
. We would know when it's a special kernel and we would be able to take a special path. It creates no useless allocation. I'm not a fan of templated enum however.In brief, I want to keep being faster than SciPy and add all kernel choices. I tested an algo that offers more choices.
// Indices for the Star kernel
let indices = vec![(0, 1, 1), (1, 0, 1), (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 2, 1), (2, 1, 1)];
// Erosion
Zip::from(mask.windows((3, 3, 3)))
.map_assign_into(zone, |w| !indices.iter().any(|&idx| !w[idx]));
// Dilation
Zip::from(mask.windows((3, 3, 3)))
.map_assign_into(zone, |w| indices.iter().any(|&idx| w[idx]));
It's not too bad. We're still a little faster than SciPy and we're accepting all kernels. I just wonder if there's a way to keep our advantages :)
I'm not sure if it's possible to achieve the same effect of resampling a 3D image?
e.g.
In [249]: data = np.array([[1.,2.,3.],[4.,5.,6.],[7.,8.,9.]])
In [250]: from scipy.ndimage import zoom
In [251]: zoom(data, 2.0, mode='nearest')
Out[251]:
array([[1.0, 1.29710751, 1.74855376, 2.25144624, 2.70289249, 3.0],
[1.89132253, 2.18843004, 2.63987629, 3.14276878, 3.59421502, 3.89132253],
[3.24566127, 3.54276878, 3.99421502, 4.49710751, 4.94855376, 5.24566127],
[4.75433873, 5.05144624, 5.50289249, 6.00578498, 6.45723122, 6.75433873],
[6.10867747, 6.40578498, 6.85723122, 7.36012371, 7.81156996, 8.10867747],
[7.0 , 7.29710751, 7.74855376, 8.25144624, 8.70289249, 9.0]])
I am trying to convolve over the 2D arrays in an image cube and noticed the following mismatched types error.
pub fn denoise(hsi: &mut Array3<f32>) {
let corner_kernel = arr2(&[[0.25f32, 0., 0.25], [0., 0., 0.], [0.25, 0., 0.25]]);
hsi.outer_iter_mut().into_par_iter().for_each(|bnd| {
let corner_average =
ndarray_ndimage::convolve(&bnd, &corner_kernel.view(), BorderMode::Mirror, ORIGIN);
});
// --snip--
}
Error:
error[E0308]: mismatched types
--> src/lib.rs:93:45
|
93 | ndarray_ndimage::convolve(&bnd, &corner_kernel.view(), BorderMode::Mirror, ORIGIN);
| ------------------------- ^^^^^^^^^^^^^^^^^^^^^ types differ in mutability
| |
| arguments to this function are incorrect
|
= note: expected reference `&ArrayBase<ViewRepr<&mut f32>, Dim<[usize; 2]>>`
found reference `&ArrayBase<ViewRepr<&f32>, Dim<[usize; 2]>>`
I belive this is because of convolve
's signature. Since both data
and weights
have the same generic parameters, the compiler expects them to both have the exact same types, which, I suspect, is probably not necessary?
pub fn convolve<S, A, D>(
data: &ArrayBase<S, D>,
weights: &ArrayBase<S, D>,
mode: BorderMode<A>,
origin: isize
) -> Array<A, D>
Most functions are using Array
when they should use ArrayBase
. Thus it's impossible to call them with non-owned arrays (views).
Looking at the code it looks like it would be pretty simple to implement the par_iter()
methods from ndarray into the convolution iterators to allow for multithreading in various processing routines
There's a problem with the border management in the morphological erosion. Currently we are doing:
The consequence of our current method can be seen in the following image.
ImageMath
result.I read more and I realized that it's not the right behavior: "Pixels beyond the image border are assigned the maximum value afforded by the data type." Thus all the padded voxels should be set to 1.
Our current method (working with windows to avoid padding) can't work. We can keep the window but we must pad with 1 if we want to avoid border effects.
The person who contributed uniform_filter
wrote this
It's not going to be as quite as fast as scipy, because scipy's implementation hardcodes the fact that the weights are constant, but I really don't want to duplicate the border modes implementation.
Based on the code (https://github.com/scipy/scipy/blob/main/scipy/ndimage/src/ni_filters.c#L359-L362), scipy is using a single accumulator as it moves along lines, and just adds the new value and subtracts the oldest value each time the filter moves to a new sample. Then it just divides by the filter size, which should be a lot more efficient than a correlation. Looking again at the inner_collerate1d code, it should be possible to do something similar here, but it probably makes sense to do that in a subsequent PR for simplicity. I might have a go at that, it's made me curious now ๐.
No benchmark have been done/provided, but we can guess that uniform_filter
can be optimized.
Compared to NumPy, pad
is slow. correlate1d
is less slow but we still need to make it faster. Those functions are particularly important because they are the basis for several other functions:
correlate1d
: convolve1d
, gaussian_filter/1d
, prewitt
, sobel
pad
: correlate1d
, min/max filters
I'm not sure what NumPy does to be so fast. Of course, Cython is used, so it's compiled, optimized and there's no bound checks. but still, we're around 2-6x slower, depending in the mode. IMO, we should be able to be as fast, if not faster. It's a mystery to me how NumPy manages to be so fast (around 10x) on the reflect
, symmetric
and wrap
modes.
To replicate the results, please use
use std::time::Instant;
use ndarray::{Array1, Array3, ShapeBuilder};
use ndarray_ndimage::{pad, PadMode};
fn main() {
println!("C order");
let data_c = (0..200 * 200 * 200)
.map(|v| v as u32)
.collect::<Array1<_>>()
.into_shape((200, 200, 200))
.unwrap();
test(&data_c);
println!("\nF order");
let mut data_f = Array3::zeros((200, 200, 200).f());
data_f.assign(&data_c);
test(&data_f);
}
fn test(data: &Array3<u32>) {
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Constant(0));
let elapsed = now.elapsed();
println!(
"Constant 0 {}s {}ms {}",
elapsed.as_secs(),
elapsed.subsec_millis(),
padded[(0, 0, 0)]
);
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Constant(42));
let elapsed = now.elapsed();
println!(
"Constant 42 {}s {}ms {}",
elapsed.as_secs(),
elapsed.subsec_millis(),
padded[(0, 0, 0)]
);
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Edge);
let elapsed = now.elapsed();
println!("Edge {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Maximum);
let elapsed = now.elapsed();
println!("Maximum {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Mean);
let elapsed = now.elapsed();
println!("Mean {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Median);
let elapsed = now.elapsed();
println!("Median {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Minimum);
let elapsed = now.elapsed();
println!("Minimum {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Reflect);
let elapsed = now.elapsed();
println!("Reflect {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Symmetric);
let elapsed = now.elapsed();
println!("Symmetric {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
let now = Instant::now();
let padded = pad(&data, &[[1, 1]], PadMode::Wrap);
let elapsed = now.elapsed();
println!("Wrap {}s {}ms {:?}", elapsed.as_secs(), elapsed.subsec_millis(), padded.dim());
}
And for Python
from datetime import datetime
import time
import numpy as np
def test(a):
start = datetime.now()
np.pad(a, 1, mode='constant', constant_values=42)
print(' constant', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='edge')
print(' edge', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='maximum')
print(' maximum', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='mean')
print(' mean', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='median')
print(' median', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='minimum')
print(' minimum', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='reflect')
print(' reflect', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='symmetric')
print('symmetric', datetime.now() - start)
start = datetime.now()
np.pad(a, 1, mode='wrap')
print(' wrap', datetime.now() - start)
print('C order')
a = np.arange(200*200*200).reshape((200, 200, 200)).astype(np.uint32)
test(a)
print('\nF order')
a = np.asfortranarray(a)
test(a)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.