#![cfg_attr(
test,
allow(
clippy::expect_used,
clippy::unwrap_used,
clippy::disallowed_methods,
clippy::float_cmp,
clippy::panic
)
)]
mod buf;
mod color;
mod conv;
mod error;
mod histogram;
mod morphology;
mod resize;
#[cfg(test)]
mod tests;
pub use buf::{DType, ImageBuf};
pub use color::{connected_components, hsv_to_rgb, rgb_to_gray, rgb_to_hsv};
pub use conv::{
canny, canny_rgb, conv2d, gaussian_blur, gradient_magnitude, separable_conv2d, sobel,
BorderMode,
};
pub use error::ImageError;
pub use histogram::{cumulative_histogram, equalize, histogram};
pub use morphology::{closing, dilate, erode, opening};
pub use resize::{resize, Interpolation};
pub trait ImageOps {
fn apply_conv2d(
&self,
kernel: &[f32],
kw: usize,
kh: usize,
border: BorderMode,
) -> Result<ImageBuf, ImageError>;
fn blur(&self, sigma: f32) -> Result<ImageBuf, ImageError>;
fn sobel_gradients(&self) -> Result<(ImageBuf, ImageBuf), ImageError>;
fn canny_edges(&self, sigma: f32, low: f32, high: f32) -> Result<ImageBuf, ImageError>;
fn apply_dilate(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError>;
fn apply_erode(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError>;
fn apply_resize(
&self,
new_w: usize,
new_h: usize,
interp: Interpolation,
) -> Result<ImageBuf, ImageError>;
fn to_gray(&self) -> Result<ImageBuf, ImageError>;
fn to_hsv(&self) -> Result<ImageBuf, ImageError>;
fn compute_histogram(&self, bins: usize) -> Result<Vec<u32>, ImageError>;
fn label_components(&self) -> Result<(Vec<u32>, u32), ImageError>;
}
impl ImageOps for ImageBuf {
fn apply_conv2d(
&self,
kernel: &[f32],
kw: usize,
kh: usize,
border: BorderMode,
) -> Result<ImageBuf, ImageError> {
if self.channels() == 1 {
let data = conv2d(
self.data(),
self.width(),
self.height(),
kernel,
kw,
kh,
border,
)?;
ImageBuf::new(data, self.width(), self.height(), 1)
} else {
let npix = self.width() * self.height();
let mut out = vec![0.0_f32; npix * self.channels()];
for c in 0..self.channels() {
let ch = self.channel(c)?;
let filtered = conv2d(
ch.data(),
self.width(),
self.height(),
kernel,
kw,
kh,
border,
)?;
for i in 0..npix {
out[i * self.channels() + c] = filtered[i];
}
}
ImageBuf::new(out, self.width(), self.height(), self.channels())
}
}
fn blur(&self, sigma: f32) -> Result<ImageBuf, ImageError> {
if self.channels() == 1 {
let data = gaussian_blur(self.data(), self.width(), self.height(), sigma)?;
ImageBuf::new(data, self.width(), self.height(), 1)
} else {
let npix = self.width() * self.height();
let mut out = vec![0.0_f32; npix * self.channels()];
for c in 0..self.channels() {
let ch = self.channel(c)?;
let blurred = gaussian_blur(ch.data(), self.width(), self.height(), sigma)?;
for i in 0..npix {
out[i * self.channels() + c] = blurred[i];
}
}
ImageBuf::new(out, self.width(), self.height(), self.channels())
}
}
fn sobel_gradients(&self) -> Result<(ImageBuf, ImageBuf), ImageError> {
let gray = if self.channels() == 1 {
self.data().to_vec()
} else {
rgb_to_gray(self.data(), self.width(), self.height())?
};
let (gx, gy) = sobel(&gray, self.width(), self.height())?;
Ok((
ImageBuf::new(gx, self.width(), self.height(), 1)?,
ImageBuf::new(gy, self.width(), self.height(), 1)?,
))
}
fn canny_edges(&self, sigma: f32, low: f32, high: f32) -> Result<ImageBuf, ImageError> {
let edges = canny_rgb(
self.data(),
self.width(),
self.height(),
self.channels(),
sigma,
low,
high,
)?;
ImageBuf::new(edges, self.width(), self.height(), 1)
}
fn apply_dilate(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError> {
if self.channels() == 1 {
let data = dilate(self.data(), self.width(), self.height(), se, sw, sh)?;
ImageBuf::new(data, self.width(), self.height(), 1)
} else {
let npix = self.width() * self.height();
let mut out = vec![0.0_f32; npix * self.channels()];
for c in 0..self.channels() {
let ch = self.channel(c)?;
let d = dilate(ch.data(), self.width(), self.height(), se, sw, sh)?;
for i in 0..npix {
out[i * self.channels() + c] = d[i];
}
}
ImageBuf::new(out, self.width(), self.height(), self.channels())
}
}
fn apply_erode(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError> {
if self.channels() == 1 {
let data = erode(self.data(), self.width(), self.height(), se, sw, sh)?;
ImageBuf::new(data, self.width(), self.height(), 1)
} else {
let npix = self.width() * self.height();
let mut out = vec![0.0_f32; npix * self.channels()];
for c in 0..self.channels() {
let ch = self.channel(c)?;
let e = erode(ch.data(), self.width(), self.height(), se, sw, sh)?;
for i in 0..npix {
out[i * self.channels() + c] = e[i];
}
}
ImageBuf::new(out, self.width(), self.height(), self.channels())
}
}
fn apply_resize(
&self,
new_w: usize,
new_h: usize,
interp: Interpolation,
) -> Result<ImageBuf, ImageError> {
if self.channels() == 1 {
let data = resize(
self.data(),
self.width(),
self.height(),
new_w,
new_h,
interp,
)?;
ImageBuf::new(data, new_w, new_h, 1)
} else {
let new_npix = new_w * new_h;
let mut out = vec![0.0_f32; new_npix * self.channels()];
for c in 0..self.channels() {
let ch = self.channel(c)?;
let resized = resize(ch.data(), self.width(), self.height(), new_w, new_h, interp)?;
for i in 0..new_npix {
out[i * self.channels() + c] = resized[i];
}
}
ImageBuf::new(out, new_w, new_h, self.channels())
}
}
fn to_gray(&self) -> Result<ImageBuf, ImageError> {
if self.channels() == 1 {
return Ok(self.clone());
}
let gray = rgb_to_gray(self.data(), self.width(), self.height())?;
ImageBuf::new(gray, self.width(), self.height(), 1)
}
fn to_hsv(&self) -> Result<ImageBuf, ImageError> {
let hsv = rgb_to_hsv(self.data(), self.width(), self.height())?;
ImageBuf::new(hsv, self.width(), self.height(), self.channels())
}
fn compute_histogram(&self, bins: usize) -> Result<Vec<u32>, ImageError> {
let gray = if self.channels() == 1 {
self.data().to_vec()
} else {
rgb_to_gray(self.data(), self.width(), self.height())?
};
histogram(&gray, self.width(), self.height(), bins)
}
fn label_components(&self) -> Result<(Vec<u32>, u32), ImageError> {
let gray = if self.channels() == 1 {
self.data().to_vec()
} else {
rgb_to_gray(self.data(), self.width(), self.height())?
};
let labels = connected_components(&gray, self.width(), self.height())?;
let max_label = labels.iter().copied().max().unwrap_or(0);
Ok((labels, max_label))
}
}