use crate::{
DType, Error, Image, ImageLayout, Matrix, OpAttribute, Result,
runtime::{
BufferBinding, ComputeDispatch, ImageSemanticDispatch, ImageSemanticInput, KernelId,
PushConstant,
},
};
use super::BorderMode;
pub fn convolve_2d(
input: &Image,
kernel: &Matrix,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_kernel(input, kernel, false, "image::convolve_2d")?;
finite(border_value, "image::convolve_2d border value")?;
let contract = crate::core::operation::image::CONVOLVE_2D;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(kernel.storage()),
BufferBinding::read(kernel.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(0),
PushConstant::U32(border.code()),
PushConstant::U32(kernel.shape()[1] as u32),
PushConstant::U32(kernel.shape()[0] as u32),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageConvolve2dF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[
ImageSemanticInput::Image(input),
ImageSemanticInput::Matrix(kernel),
],
outputs: &[&output],
attributes: &[
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn separable_convolve_2d(
input: &Image,
kernel_x: &Matrix,
kernel_y: &Matrix,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_kernel(input, kernel_x, true, "image::separable_convolve_2d")?;
validate_kernel(input, kernel_y, true, "image::separable_convolve_2d")?;
finite(border_value, "image::separable_convolve_2d border value")?;
let contract = crate::core::operation::image::SEPARABLE_CONVOLVE_2D;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(kernel_x.storage()),
BufferBinding::read(kernel_y.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(1),
PushConstant::U32(border.code()),
PushConstant::U32(kernel_x.num_elements() as u32),
PushConstant::U32(kernel_y.num_elements() as u32),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageSeparableConvolve2dF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[
ImageSemanticInput::Image(input),
ImageSemanticInput::Matrix(kernel_x),
ImageSemanticInput::Matrix(kernel_y),
],
outputs: &[&output],
attributes: &[
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn average_blur(input: &Image, width: u32, height: u32, border: BorderMode) -> Result<Image> {
validate_morphology(width, height, 31, "image::average_blur")?;
let contract = crate::core::operation::image::AVERAGE_BLUR;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(2),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageAverageBlurF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
pub fn sobel(input: &Image, dx: u32, dy: u32, border: BorderMode) -> Result<Image> {
validate_derivative(dx, dy, "image::sobel")?;
let contract = crate::core::operation::image::SOBEL;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(3),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(dx),
PushConstant::U32(dy),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageSobelF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("dx", dx),
unsigned("dy", dy),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
pub fn scharr(input: &Image, dx: u32, dy: u32, border: BorderMode) -> Result<Image> {
validate_derivative(dx, dy, "image::scharr")?;
let contract = crate::core::operation::image::SCHARR;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(4),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(dx),
PushConstant::U32(dy),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageScharrF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("dx", dx),
unsigned("dy", dy),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
pub fn laplacian(input: &Image, border: BorderMode) -> Result<Image> {
let contract = crate::core::operation::image::LAPLACIAN;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(5),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageLaplacianF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[enumeration("border", border.token())],
},
)?;
Ok(output)
}
pub fn erode(
input: &Image,
width: u32,
height: u32,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_morphology(
width,
height,
31,
crate::core::operation::image::ERODE.name(),
)?;
finite(border_value, "morphology border value")?;
let contract = crate::core::operation::image::ERODE;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(6),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageErodeF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn dilate(
input: &Image,
width: u32,
height: u32,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_morphology(
width,
height,
31,
crate::core::operation::image::DILATE.name(),
)?;
finite(border_value, "morphology border value")?;
let contract = crate::core::operation::image::DILATE;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(7),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageDilateF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn morphology_open(
input: &Image,
width: u32,
height: u32,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_morphology(
width,
height,
31,
crate::core::operation::image::MORPHOLOGY_OPEN.name(),
)?;
finite(border_value, "morphology border value")?;
let contract = crate::core::operation::image::MORPHOLOGY_OPEN;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(8),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageMorphologyOpenF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn morphology_close(
input: &Image,
width: u32,
height: u32,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_morphology(
width,
height,
31,
crate::core::operation::image::MORPHOLOGY_CLOSE.name(),
)?;
finite(border_value, "morphology border value")?;
let contract = crate::core::operation::image::MORPHOLOGY_CLOSE;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(9),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageMorphologyCloseF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn morphology_gradient(
input: &Image,
width: u32,
height: u32,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_morphology(
width,
height,
31,
crate::core::operation::image::MORPHOLOGY_GRADIENT.name(),
)?;
finite(border_value, "morphology border value")?;
let contract = crate::core::operation::image::MORPHOLOGY_GRADIENT;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(10),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageMorphologyGradientF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn gaussian_blur(input: &Image, sigma: f32, kernel_size: u32) -> Result<Image> {
finite(sigma, "image::gaussian_blur sigma")?;
if sigma <= 0.0 || kernel_size != 0 && (kernel_size > 31 || kernel_size.is_multiple_of(2)) {
return Err(Error::invalid_argument(
"image::gaussian_blur requires positive sigma and odd kernel <=31 or zero",
));
}
let size = if kernel_size == 0 {
((3.0 * sigma).ceil() as u32).min(15) * 2 + 1
} else {
kernel_size
};
let contract = crate::core::operation::image::GAUSSIAN_BLUR;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(11),
PushConstant::U32(BorderMode::Replicate.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(size),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(sigma),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageGaussianBlurF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[float("sigma", sigma), unsigned("kernel_size", kernel_size)],
},
)?;
Ok(output)
}
pub fn sharpen(input: &Image, amount: f32, border: BorderMode) -> Result<Image> {
finite(amount, "image::sharpen amount")?;
if amount < 0.0 {
return Err(Error::invalid_argument(
"image::sharpen amount must be nonnegative",
));
}
let contract = crate::core::operation::image::SHARPEN;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(12),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(amount),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageSharpenF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
float("amount", amount),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
pub fn median_blur(input: &Image, kernel_size: u32, border: BorderMode) -> Result<Image> {
validate_neighborhood(kernel_size, "image::median_blur")?;
let contract = crate::core::operation::image::MEDIAN_BLUR;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(13),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(kernel_size),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageMedianBlurF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_size", kernel_size),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
pub fn bilateral_filter(
input: &Image,
kernel_size: u32,
sigma_color: f32,
sigma_space: f32,
border: BorderMode,
) -> Result<Image> {
validate_neighborhood(kernel_size, "image::bilateral_filter")?;
finite(sigma_color, "image::bilateral_filter sigma_color")?;
finite(sigma_space, "image::bilateral_filter sigma_space")?;
if sigma_color <= 0.0 || sigma_space <= 0.0 {
return Err(Error::invalid_argument(
"image::bilateral_filter sigmas must be positive",
));
}
let contract = crate::core::operation::image::BILATERAL_FILTER;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(14),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(kernel_size),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(sigma_color),
PushConstant::F32(sigma_space),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageBilateralFilterF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_size", kernel_size),
float("sigma_color", sigma_color),
float("sigma_space", sigma_space),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
pub fn unsharp_mask(input: &Image, sigma: f32, amount: f32, kernel_size: u32) -> Result<Image> {
finite(sigma, "image::unsharp_mask sigma")?;
finite(amount, "image::unsharp_mask amount")?;
if sigma <= 0.0 || kernel_size != 0 && (kernel_size > 31 || kernel_size.is_multiple_of(2)) {
return Err(Error::invalid_argument(
"image::unsharp_mask requires positive sigma and odd kernel <=31 or zero",
));
}
let size = if kernel_size == 0 {
((3.0 * sigma).ceil() as u32).min(15) * 2 + 1
} else {
kernel_size
};
let contract = crate::core::operation::image::UNSHARP_MASK;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(15),
PushConstant::U32(BorderMode::Replicate.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(size),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(sigma),
PushConstant::F32(amount),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageUnsharpMaskF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
float("sigma", sigma),
float("amount", amount),
unsigned("kernel_size", kernel_size),
],
},
)?;
Ok(output)
}
pub fn morphology_top_hat(
input: &Image,
width: u32,
height: u32,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_morphology(
width,
height,
31,
crate::core::operation::image::MORPHOLOGY_TOP_HAT.name(),
)?;
finite(border_value, "morphology border value")?;
let contract = crate::core::operation::image::MORPHOLOGY_TOP_HAT;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(16),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageMorphologyTopHatF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn morphology_black_hat(
input: &Image,
width: u32,
height: u32,
border: BorderMode,
border_value: f32,
) -> Result<Image> {
validate_morphology(
width,
height,
31,
crate::core::operation::image::MORPHOLOGY_BLACK_HAT.name(),
)?;
finite(border_value, "morphology border value")?;
let contract = crate::core::operation::image::MORPHOLOGY_BLACK_HAT;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(17),
PushConstant::U32(border.code()),
PushConstant::U32(width),
PushConstant::U32(height),
PushConstant::U32(1),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(border_value),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageMorphologyBlackHatF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_width", width),
unsigned("kernel_height", height),
enumeration("border", border.token()),
float("border_value", border_value),
],
},
)?;
Ok(output)
}
pub fn adaptive_threshold_mean(
input: &Image,
kernel_size: u32,
c: f32,
max_value: f32,
border: BorderMode,
) -> Result<Image> {
validate_neighborhood(kernel_size, "image::adaptive_threshold_mean")?;
finite(c, "image::adaptive_threshold_mean c")?;
finite(max_value, "image::adaptive_threshold_mean max_value")?;
let contract = crate::core::operation::image::ADAPTIVE_THRESHOLD_MEAN;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(18),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(kernel_size),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(c),
PushConstant::F32(max_value),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageAdaptiveThresholdMeanF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_size", kernel_size),
float("c", c),
float("max_value", max_value),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
pub fn adaptive_threshold_gaussian(
input: &Image,
kernel_size: u32,
c: f32,
max_value: f32,
sigma: f32,
border: BorderMode,
) -> Result<Image> {
validate_neighborhood(kernel_size, "image::adaptive_threshold_gaussian")?;
for (value, name) in [(c, "c"), (max_value, "max_value"), (sigma, "sigma")] {
finite(value, name)?;
}
if sigma < 0.0 {
return Err(Error::invalid_argument(
"image::adaptive_threshold_gaussian sigma must be nonnegative",
));
}
let resolved_sigma = if sigma > 0.0 {
sigma
} else {
0.3 * ((kernel_size as f32 - 1.0) * 0.5 - 1.0) + 0.8
};
let contract = crate::core::operation::image::ADAPTIVE_THRESHOLD_GAUSSIAN;
let (batch, channels, image_height, image_width) = image_extent(input, contract.name())?;
let output = allocate_like(input, contract.name())?;
let buffers = [
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::read(input.storage()),
BufferBinding::write(output.storage()),
];
let push_constants = [
PushConstant::U32(batch),
PushConstant::U32(channels),
PushConstant::U32(image_height),
PushConstant::U32(image_width),
PushConstant::U32(19),
PushConstant::U32(border.code()),
PushConstant::U32(1),
PushConstant::U32(1),
PushConstant::U32(kernel_size),
PushConstant::U32(0),
PushConstant::U32(0),
PushConstant::F32(c),
PushConstant::F32(max_value),
PushConstant::F32(resolved_sigma),
PushConstant::F32(0.0),
PushConstant::F32(0.0),
];
input.engine_handle().record_image_semantic(
ComputeDispatch {
kernel: KernelId::ImageAdaptiveThresholdGaussianF32,
buffers: &buffers,
push_constants: &push_constants,
workgroups: [
image_width.div_ceil(16),
image_height.div_ceil(16),
batch * channels,
],
},
ImageSemanticDispatch {
contract,
inputs: &[ImageSemanticInput::Image(input)],
outputs: &[&output],
attributes: &[
unsigned("kernel_size", kernel_size),
float("c", c),
float("max_value", max_value),
float("sigma", sigma),
enumeration("border", border.token()),
],
},
)?;
Ok(output)
}
fn validate_kernel(input: &Image, kernel: &Matrix, vector: bool, operation: &str) -> Result<()> {
image_extent(input, operation)?;
let valid_shape = if vector {
kernel.shape().len() == 1
|| kernel.shape().len() == 2 && (kernel.shape()[0] == 1 || kernel.shape()[1] == 1)
} else {
kernel.shape().len() == 2
};
let elements = kernel.num_elements();
let valid_extent = valid_shape
&& if vector {
elements > 0 && elements <= 31 && elements % 2 == 1
} else {
kernel.shape()[0] > 0
&& kernel.shape()[1] > 0
&& kernel.shape()[0] <= 31
&& kernel.shape()[1] <= 31
&& kernel.shape()[0] % 2 == 1
&& kernel.shape()[1] % 2 == 1
};
if !valid_shape
|| !valid_extent
|| kernel.dtype() != DType::F32
|| !input.engine_handle().same_as(kernel.engine_handle())
{
return Err(Error::invalid_argument(format!(
"{operation} requires a same-engine odd FP32 kernel with each extent <=31"
)));
}
Ok(())
}
fn validate_derivative(dx: u32, dy: u32, operation: &str) -> Result<()> {
if matches!((dx, dy), (1, 0) | (0, 1)) {
Ok(())
} else {
Err(Error::invalid_argument(format!(
"{operation} derivative must be (1,0) or (0,1)"
)))
}
}
fn validate_morphology(width: u32, height: u32, maximum: u32, operation: &str) -> Result<()> {
if width > 0
&& height > 0
&& width <= maximum
&& height <= maximum
&& width % 2 == 1
&& height % 2 == 1
{
Ok(())
} else {
Err(Error::invalid_argument(format!(
"{operation} kernel dimensions must be odd and in 1..={maximum}"
)))
}
}
fn validate_neighborhood(size: u32, operation: &str) -> Result<()> {
validate_morphology(size, size, 15, operation)
}
fn image_extent(input: &Image, operation: &str) -> Result<(u32, u32, u32, u32)> {
if input.dtype() != DType::F32 || !matches!(input.layout(), ImageLayout::Nchw | ImageLayout::Chw)
{
return Err(Error::invalid_argument(format!(
"{operation} requires an FP32 NCHW or CHW image"
)));
}
let values = [
input.batch_size(),
input.channels(),
input.height(),
input.width(),
];
if values.contains(&0) {
return Err(Error::invalid_argument(format!(
"{operation} requires a nonempty image"
)));
}
Ok((
u32::try_from(values[0]).map_err(|_| Error::out_of_range("image batch exceeds u32"))?,
u32::try_from(values[1]).map_err(|_| Error::out_of_range("image channels exceed u32"))?,
u32::try_from(values[2]).map_err(|_| Error::out_of_range("image height exceeds u32"))?,
u32::try_from(values[3]).map_err(|_| Error::out_of_range("image width exceeds u32"))?,
))
}
fn allocate_like(input: &Image, operation: &str) -> Result<Image> {
let elements = input.as_matrix().num_elements();
u32::try_from(elements)
.map_err(|_| Error::out_of_range(format!("{operation} output size exceeds u32")))?;
let matrix = Matrix::allocate(
input.engine_handle(),
input.as_matrix().shape().to_vec(),
elements,
DType::F32,
)?;
Image::new(matrix, input.layout(), input.format())
}
fn finite(value: f32, label: &str) -> Result<()> {
if value.is_finite() {
Ok(())
} else {
Err(Error::invalid_argument(format!("{label} must be finite")))
}
}
fn float(name: &str, value: f32) -> OpAttribute {
OpAttribute::Float {
name: name.into(),
value: f64::from(value),
}
}
fn unsigned(name: &str, value: u32) -> OpAttribute {
OpAttribute::UnsignedInteger {
name: name.into(),
value: u64::from(value),
}
}
fn enumeration(name: &str, value: &str) -> OpAttribute {
OpAttribute::Enum {
name: name.into(),
value: value.into(),
}
}