1#![cfg_attr(
2 test,
3 allow(
4 clippy::expect_used,
5 clippy::unwrap_used,
6 clippy::disallowed_methods,
7 clippy::float_cmp,
8 clippy::panic
9 )
10)]
11mod buf;
31mod color;
32mod conv;
33mod error;
34mod histogram;
35mod morphology;
36mod resize;
37
38#[cfg(test)]
39mod tests;
40
41pub use buf::{DType, ImageBuf};
42pub use color::{connected_components, hsv_to_rgb, rgb_to_gray, rgb_to_hsv};
43pub use conv::{
44 canny, canny_rgb, conv2d, gaussian_blur, gradient_magnitude, separable_conv2d, sobel,
45 BorderMode,
46};
47pub use error::ImageError;
48pub use histogram::{cumulative_histogram, equalize, histogram};
49pub use morphology::{closing, dilate, erode, opening};
50pub use resize::{resize, Interpolation};
51
52pub trait ImageOps {
57 fn apply_conv2d(
59 &self,
60 kernel: &[f32],
61 kw: usize,
62 kh: usize,
63 border: BorderMode,
64 ) -> Result<ImageBuf, ImageError>;
65
66 fn blur(&self, sigma: f32) -> Result<ImageBuf, ImageError>;
68
69 fn sobel_gradients(&self) -> Result<(ImageBuf, ImageBuf), ImageError>;
71
72 fn canny_edges(&self, sigma: f32, low: f32, high: f32) -> Result<ImageBuf, ImageError>;
74
75 fn apply_dilate(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError>;
77
78 fn apply_erode(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError>;
80
81 fn apply_resize(
83 &self,
84 new_w: usize,
85 new_h: usize,
86 interp: Interpolation,
87 ) -> Result<ImageBuf, ImageError>;
88
89 fn to_gray(&self) -> Result<ImageBuf, ImageError>;
91
92 fn to_hsv(&self) -> Result<ImageBuf, ImageError>;
94
95 fn compute_histogram(&self, bins: usize) -> Result<Vec<u32>, ImageError>;
97
98 fn label_components(&self) -> Result<(Vec<u32>, u32), ImageError>;
100}
101
102impl ImageOps for ImageBuf {
103 fn apply_conv2d(
104 &self,
105 kernel: &[f32],
106 kw: usize,
107 kh: usize,
108 border: BorderMode,
109 ) -> Result<ImageBuf, ImageError> {
110 if self.channels() == 1 {
111 let data = conv2d(
112 self.data(),
113 self.width(),
114 self.height(),
115 kernel,
116 kw,
117 kh,
118 border,
119 )?;
120 ImageBuf::new(data, self.width(), self.height(), 1)
121 } else {
122 let npix = self.width() * self.height();
123 let mut out = vec![0.0_f32; npix * self.channels()];
124 for c in 0..self.channels() {
125 let ch = self.channel(c)?;
126 let filtered = conv2d(
127 ch.data(),
128 self.width(),
129 self.height(),
130 kernel,
131 kw,
132 kh,
133 border,
134 )?;
135 for i in 0..npix {
136 out[i * self.channels() + c] = filtered[i];
137 }
138 }
139 ImageBuf::new(out, self.width(), self.height(), self.channels())
140 }
141 }
142
143 fn blur(&self, sigma: f32) -> Result<ImageBuf, ImageError> {
144 if self.channels() == 1 {
145 let data = gaussian_blur(self.data(), self.width(), self.height(), sigma)?;
146 ImageBuf::new(data, self.width(), self.height(), 1)
147 } else {
148 let npix = self.width() * self.height();
149 let mut out = vec![0.0_f32; npix * self.channels()];
150 for c in 0..self.channels() {
151 let ch = self.channel(c)?;
152 let blurred = gaussian_blur(ch.data(), self.width(), self.height(), sigma)?;
153 for i in 0..npix {
154 out[i * self.channels() + c] = blurred[i];
155 }
156 }
157 ImageBuf::new(out, self.width(), self.height(), self.channels())
158 }
159 }
160
161 fn sobel_gradients(&self) -> Result<(ImageBuf, ImageBuf), ImageError> {
162 let gray = if self.channels() == 1 {
163 self.data().to_vec()
164 } else {
165 rgb_to_gray(self.data(), self.width(), self.height())?
166 };
167 let (gx, gy) = sobel(&gray, self.width(), self.height())?;
168 Ok((
169 ImageBuf::new(gx, self.width(), self.height(), 1)?,
170 ImageBuf::new(gy, self.width(), self.height(), 1)?,
171 ))
172 }
173
174 fn canny_edges(&self, sigma: f32, low: f32, high: f32) -> Result<ImageBuf, ImageError> {
175 let edges = canny_rgb(
176 self.data(),
177 self.width(),
178 self.height(),
179 self.channels(),
180 sigma,
181 low,
182 high,
183 )?;
184 ImageBuf::new(edges, self.width(), self.height(), 1)
185 }
186
187 fn apply_dilate(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError> {
188 if self.channels() == 1 {
189 let data = dilate(self.data(), self.width(), self.height(), se, sw, sh)?;
190 ImageBuf::new(data, self.width(), self.height(), 1)
191 } else {
192 let npix = self.width() * self.height();
193 let mut out = vec![0.0_f32; npix * self.channels()];
194 for c in 0..self.channels() {
195 let ch = self.channel(c)?;
196 let d = dilate(ch.data(), self.width(), self.height(), se, sw, sh)?;
197 for i in 0..npix {
198 out[i * self.channels() + c] = d[i];
199 }
200 }
201 ImageBuf::new(out, self.width(), self.height(), self.channels())
202 }
203 }
204
205 fn apply_erode(&self, se: &[f32], sw: usize, sh: usize) -> Result<ImageBuf, ImageError> {
206 if self.channels() == 1 {
207 let data = erode(self.data(), self.width(), self.height(), se, sw, sh)?;
208 ImageBuf::new(data, self.width(), self.height(), 1)
209 } else {
210 let npix = self.width() * self.height();
211 let mut out = vec![0.0_f32; npix * self.channels()];
212 for c in 0..self.channels() {
213 let ch = self.channel(c)?;
214 let e = erode(ch.data(), self.width(), self.height(), se, sw, sh)?;
215 for i in 0..npix {
216 out[i * self.channels() + c] = e[i];
217 }
218 }
219 ImageBuf::new(out, self.width(), self.height(), self.channels())
220 }
221 }
222
223 fn apply_resize(
224 &self,
225 new_w: usize,
226 new_h: usize,
227 interp: Interpolation,
228 ) -> Result<ImageBuf, ImageError> {
229 if self.channels() == 1 {
230 let data = resize(
231 self.data(),
232 self.width(),
233 self.height(),
234 new_w,
235 new_h,
236 interp,
237 )?;
238 ImageBuf::new(data, new_w, new_h, 1)
239 } else {
240 let new_npix = new_w * new_h;
241 let mut out = vec![0.0_f32; new_npix * self.channels()];
242 for c in 0..self.channels() {
243 let ch = self.channel(c)?;
244 let resized = resize(ch.data(), self.width(), self.height(), new_w, new_h, interp)?;
245 for i in 0..new_npix {
246 out[i * self.channels() + c] = resized[i];
247 }
248 }
249 ImageBuf::new(out, new_w, new_h, self.channels())
250 }
251 }
252
253 fn to_gray(&self) -> Result<ImageBuf, ImageError> {
254 if self.channels() == 1 {
255 return Ok(self.clone());
256 }
257 let gray = rgb_to_gray(self.data(), self.width(), self.height())?;
258 ImageBuf::new(gray, self.width(), self.height(), 1)
259 }
260
261 fn to_hsv(&self) -> Result<ImageBuf, ImageError> {
262 let hsv = rgb_to_hsv(self.data(), self.width(), self.height())?;
263 ImageBuf::new(hsv, self.width(), self.height(), self.channels())
264 }
265
266 fn compute_histogram(&self, bins: usize) -> Result<Vec<u32>, ImageError> {
267 let gray = if self.channels() == 1 {
268 self.data().to_vec()
269 } else {
270 rgb_to_gray(self.data(), self.width(), self.height())?
271 };
272 histogram(&gray, self.width(), self.height(), bins)
273 }
274
275 fn label_components(&self) -> Result<(Vec<u32>, u32), ImageError> {
276 let gray = if self.channels() == 1 {
277 self.data().to_vec()
278 } else {
279 rgb_to_gray(self.data(), self.width(), self.height())?
280 };
281 let labels = connected_components(&gray, self.width(), self.height())?;
282 let max_label = labels.iter().copied().max().unwrap_or(0);
283 Ok((labels, max_label))
284 }
285}