1#![cfg_attr(not(feature = "std"), no_std)]
5
6use machine_vision_formats::{
10 ImageMutData, iter::HasRowChunksExact, iter::HasRowChunksExactMut, pixel_format::Mono8,
11};
12
13#[inline]
17pub fn spatial_moment_00<IM>(im: &IM) -> f32
18where
19 IM: HasRowChunksExact<Mono8>,
20{
21 let mut accum: f64 = 0.0;
22
23 let chunk_iter = im.rowchunks_exact();
24
25 for rowdata in chunk_iter {
26 let rowdata = &rowdata[..im.width() as usize];
28
29 let (head, body, tail): (&[u8], &[wide::u8x16], &[u8]) =
30 wide::AlignTo::simd_align_to(rowdata);
31
32 for x in head {
33 accum += *x as f64;
34 }
35
36 let mut tmpsum: wide::u16x16 = wide::u16x16::ZERO;
37 for (i, x) in body.iter().enumerate() {
38 if i % 256 == 0 {
39 for xi in tmpsum.as_array() {
41 accum += *xi as f64;
42 }
43 tmpsum = wide::u16x16::ZERO;
44 }
45 let wide_x = wide::u16x16::from(*x);
46 tmpsum += wide_x;
47 }
48 for xi in tmpsum.as_array() {
49 accum += *xi as f64;
50 }
51
52 for x in tail {
53 accum += *x as f64;
54 }
55 }
56 accum as f32
57}
58
59#[inline]
63pub fn spatial_moment_01<IM>(im: &IM) -> f32
64where
65 IM: HasRowChunksExact<Mono8>,
66{
67 let mut accum: f64 = 0.0;
68 use wide::f32x8;
69
70 let chunk_iter = im.rowchunks_exact();
71
72 for (row, rowdata) in chunk_iter.enumerate() {
73 let rowdata = &rowdata[..im.width() as usize];
75
76 let mut row_chunk_iter = rowdata.chunks_exact(8);
77
78 let mut rowsum = f32x8::splat(0.0);
79 let rowvec = f32x8::splat(row as f32);
80 for x in &mut row_chunk_iter {
81 let x = f32x8::new([
82 x[0] as f32,
83 x[1] as f32,
84 x[2] as f32,
85 x[3] as f32,
86 x[4] as f32,
87 x[5] as f32,
88 x[6] as f32,
89 x[7] as f32,
90 ]);
91 rowsum += x * rowvec;
92 }
93 accum += rowsum.reduce_add() as f64;
94
95 for x in row_chunk_iter.remainder() {
96 accum += *x as f64 * row as f64;
97 }
98 }
99 accum as f32
100}
101
102#[inline]
106pub fn spatial_moment_10<IM>(im: &IM) -> f32
107where
108 IM: HasRowChunksExact<Mono8>,
109{
110 let mut accum: f64 = 0.0;
111 use wide::f64x4;
112
113 let col_offset = f64x4::new([0.0, 1.0, 2.0, 3.0]);
114
115 let chunk_iter = im.rowchunks_exact();
116
117 let start_idx = im.width() as usize / 4 * 4;
118
119 for rowdata in chunk_iter {
120 let rowdata = &rowdata[..im.width() as usize];
122
123 let mut row_chunk_iter = rowdata.chunks_exact(4);
124
125 let mut rowsum = f64x4::splat(0.0);
126 for (col_div_4, x) in (&mut row_chunk_iter).enumerate() {
127 let x = f64x4::new([x[0] as f64, x[1] as f64, x[2] as f64, x[3] as f64]);
128 let col = f64x4::splat((col_div_4 * 4) as f64) + col_offset;
129 rowsum += x * col;
130 }
131
132 accum += rowsum.reduce_add();
133
134 for (i, x) in row_chunk_iter.remainder().iter().enumerate() {
135 let col = i + start_idx;
136 accum += *x as f64 * col as f64;
137 }
138 }
139 accum as f32
140}
141
142#[derive(Debug)]
143pub struct Moments {
144 pub centroid_x: f32,
145 pub centroid_y: f32,
146
147 pub m00: f32,
148 pub m01: f32,
149 pub m10: f32,
150 pub u11: f32,
151 pub u02: f32,
152 pub u20: f32,
153}
154
155pub fn calculate_moments<IM>(im: &IM) -> Moments
156where
157 IM: HasRowChunksExact<Mono8>,
158{
159 let width = im.width() as usize;
171
172 let mut m00 = 0.0f64;
173 let mut m01 = 0.0f64;
174 let mut m10 = 0.0f64;
175 let mut m11 = 0.0f64;
176 let mut m02 = 0.0f64;
177 let mut m20 = 0.0f64;
178
179 for (row, rowdata) in im.rowchunks_exact().enumerate() {
180 let rowdata = &rowdata[..width];
182
183 let mut rs0: u64 = 0;
184 let mut rs_x: u64 = 0;
185 let mut rs_xx: u64 = 0;
186 for (col, element) in rowdata.iter().enumerate() {
187 let v = *element as u64;
188 let x = col as u64;
189 rs0 += v;
190 rs_x += x * v;
191 rs_xx += x * x * v;
192 }
193
194 let y = row as f64;
195 let rs0 = rs0 as f64;
196 let rs_x = rs_x as f64;
197 m00 += rs0;
198 m01 += y * rs0;
199 m02 += y * y * rs0;
200 m10 += rs_x;
201 m11 += y * rs_x;
202 m20 += rs_xx as f64;
203 }
204
205 let m00 = m00 as f32;
206 let m01 = m01 as f32;
207 let m10 = m10 as f32;
208 let m11 = m11 as f32;
209 let m02 = m02 as f32;
210 let m20 = m20 as f32;
211
212 let centroid_x = m01 / m00;
213 let centroid_y = m10 / m00;
214
215 let u11 = m11 - centroid_x * m10;
216 let u02 = m02 - centroid_x * m01;
217 let u20 = m20 - centroid_y * m10;
218
219 Moments {
222 m00,
223 m01,
224 m10,
225 centroid_x,
226 centroid_y,
227 u11,
228 u02,
229 u20,
230 }
231}
232
233#[inline]
239pub fn clip_low<IM>(mut im: IM, low: u8) -> IM
240where
241 IM: HasRowChunksExact<Mono8> + ImageMutData<Mono8>,
242{
243 let width = im.width() as usize;
244
245 let chunk_iter = im.rowchunks_exact_mut();
246
247 #[inline]
248 fn scalar_clip_low(scalar_data: &mut [u8], low: u8) {
249 for element in scalar_data.iter_mut() {
250 if *element < low {
251 *element = low;
252 }
253 }
254 }
255
256 {
257 use wide::u8x32;
258
259 let low_vec = u8x32::splat(low);
260
261 for rowdata in chunk_iter {
262 let rowdata = &mut rowdata[..width];
264
265 let (head, body, tail): (&mut [u8], &mut [wide::u8x32], &mut [u8]) =
266 wide::AlignTo::simd_align_to_mut(rowdata);
267
268 scalar_clip_low(head, low);
269
270 for y in body {
271 *y = u8x32::max(*y, low_vec);
272 }
273
274 scalar_clip_low(tail, low);
275 }
276 }
277
278 im
279}
280
281#[derive(Debug, Clone, Copy)]
282pub enum CmpOp {
283 LessThan,
284 LessEqual,
285 Equal,
286 GreaterEqual,
287 GreaterThan,
288}
289
290#[inline]
297pub fn threshold<IM>(mut im: IM, op: CmpOp, thresh: u8, a: u8, b: u8) -> IM
298where
299 IM: HasRowChunksExact<Mono8> + ImageMutData<Mono8>,
300{
301 let width = im.width() as usize;
302 let chunk_iter = im.rowchunks_exact_mut();
303
304 #[inline]
305 fn scalar_cmp(scalar_data: &mut [u8], thresh: u8, a: u8, b: u8, op: CmpOp) {
306 for x in scalar_data {
307 match op {
308 CmpOp::LessThan => {
309 *x = if *x < thresh { a } else { b };
310 }
311 CmpOp::LessEqual => {
312 *x = if *x <= thresh { a } else { b };
313 }
314 CmpOp::Equal => {
315 *x = if *x == thresh { a } else { b };
316 }
317 CmpOp::GreaterEqual => {
318 *x = if *x >= thresh { a } else { b };
319 }
320 CmpOp::GreaterThan => {
321 *x = if *x > thresh { a } else { b };
322 }
323 }
324 }
325 }
326
327 use wide::u8x32;
328
329 let avec = u8x32::splat(a);
330 let bvec = u8x32::splat(b);
331 let thresh_vec = u8x32::splat(thresh);
332
333 for rowdata in chunk_iter {
334 let rowdata = &mut rowdata[..width];
336
337 let (head, body, tail): (&mut [u8], &mut [wide::u8x32], &mut [u8]) =
338 wide::AlignTo::simd_align_to_mut(rowdata);
339
340 scalar_cmp(head, thresh, a, b, op);
341
342 for y in body.iter_mut() {
343 let indicator = match op {
344 CmpOp::LessThan => y.simd_lt(thresh_vec),
345 CmpOp::LessEqual => y.simd_le(thresh_vec),
346 CmpOp::Equal => y.simd_eq(thresh_vec),
347 CmpOp::GreaterEqual => y.simd_ge(thresh_vec),
348 CmpOp::GreaterThan => y.simd_gt(thresh_vec),
349 };
350 *y = indicator.blend(avec, bvec);
351 }
352
353 scalar_cmp(tail, thresh, a, b, op);
354 }
355
356 im
357}
358
359#[cfg(feature = "std")]
360#[cfg(test)]
361mod tests {
362
363 use super::*;
364
365 #[cfg(test)]
369 #[derive(Clone, Copy, Debug, PartialEq)]
370 enum Power {
371 Zero,
372 One,
373 Two,
374 }
375
376 #[cfg(test)]
377 #[inline]
378 fn mypow(x: u32, exp: Power) -> f64 {
379 match exp {
380 Power::Zero => 1.0,
381 Power::One => x as f64,
382 Power::Two => x as f64 * x as f64,
383 }
384 }
385
386 #[cfg(test)]
387 impl From<u8> for Power {
388 fn from(orig: u8) -> Self {
389 match orig {
390 0 => Power::Zero,
391 1 => Power::One,
392 2 => Power::Two,
393 _ => {
394 unimplemented!();
395 }
396 }
397 }
398 }
399
400 #[cfg(test)]
401 fn spatial_moment<IM>(im: &IM, m_ord: Power, n_ord: Power) -> f32
402 where
403 IM: HasRowChunksExact<Mono8>,
404 {
405 let mut accum: f64 = 0.0;
406
407 let chunk_iter = im.rowchunks_exact();
408
409 for (row, rowdata) in chunk_iter.enumerate() {
410 for (col, element) in rowdata.iter().enumerate() {
411 accum += mypow(row as u32, n_ord) * mypow(col as u32, m_ord) * *element as f64;
412 }
413 }
414 accum as f32
415 }
416
417 #[test]
418 fn test_clip_low() {
419 const STRIDE: usize = 24;
420 const W: usize = 20;
421 const H: usize = 20;
422 const ALLOC_H: usize = 25;
423 let mut image_data = vec![0u8; STRIDE * ALLOC_H];
424 image_data[4 * STRIDE + 3] = 43;
425 image_data[5 * STRIDE + 3] = 1;
426 image_data[5 * STRIDE + 4] = 1;
427 image_data[6 * STRIDE + 4] = 1;
428
429 image_data[4 * STRIDE + 23] = 1;
432 image_data[5 * STRIDE + 23] = 1;
433 image_data[H * STRIDE + 4] = 1;
434 image_data[(H + 1) * STRIDE + 6] = 1;
435
436 let im = machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
437 .unwrap();
438
439 let im = clip_low(im, 42);
440
441 let image_data: Vec<u8> = im.into();
442
443 for row in 0..ALLOC_H {
444 print!("row {row:2}: ");
445 for col in 0..STRIDE {
446 print!("{:3} ", image_data[row * STRIDE + col]);
447 }
448 println!();
449 }
450
451 assert_eq!(image_data[0], 42);
452 assert_eq!(image_data[(H - 1) * STRIDE + (W - 1)], 42);
453 assert_eq!(image_data[4 * STRIDE + 3], 43);
454 assert_eq!(image_data[4 * STRIDE + 23], 1);
455 assert_eq!(image_data[H * STRIDE + 4], 1);
456 assert_eq!(image_data[(H + 1) * STRIDE + 6], 1);
457 }
458
459 macro_rules! gen_threshold_test {
460 ($name:ident, $orig:expr_2021, $op:path, $thresh:expr_2021, $expected:expr_2021) => {
461 #[test]
462 fn $name() {
463 const W: usize = 33; let im = machine_vision_formats::owned::OImage::new(W as u32, 1, W, vec![$orig; W])
466 .unwrap();
467
468 let im = threshold(im, $op, $thresh, 0, 255);
469 let image_data: Vec<u8> = im.into();
470 assert_eq!(image_data[0], $expected);
471 assert_eq!(image_data[W - 1], $expected);
472 }
473 };
474 }
475
476 gen_threshold_test!(test_lt_1, 10, CmpOp::LessThan, 42, 0);
477 gen_threshold_test!(test_lt_2, 10, CmpOp::LessThan, 10, 255);
478 gen_threshold_test!(test_lt_3, 10, CmpOp::LessThan, 9, 255);
479
480 gen_threshold_test!(test_le_1, 10, CmpOp::LessEqual, 42, 0);
481 gen_threshold_test!(test_le_2, 10, CmpOp::LessEqual, 10, 0);
482 gen_threshold_test!(test_le_3, 10, CmpOp::LessEqual, 9, 255);
483
484 gen_threshold_test!(test_eq_1, 10, CmpOp::Equal, 42, 255);
485 gen_threshold_test!(test_eq_2, 10, CmpOp::Equal, 10, 0);
486 gen_threshold_test!(test_eq_3, 10, CmpOp::Equal, 9, 255);
487
488 gen_threshold_test!(test_ge_1, 10, CmpOp::GreaterEqual, 42, 255);
489 gen_threshold_test!(test_ge_2, 10, CmpOp::GreaterEqual, 10, 0);
490 gen_threshold_test!(test_ge_3, 10, CmpOp::GreaterEqual, 9, 0);
491
492 gen_threshold_test!(test_gt_1, 10, CmpOp::GreaterThan, 42, 255);
493 gen_threshold_test!(test_gt_2, 10, CmpOp::GreaterThan, 10, 255);
494 gen_threshold_test!(test_gt_3, 10, CmpOp::GreaterThan, 9, 0);
495
496 #[test]
497 fn test_threshold_less_than() {
498 const STRIDE: usize = 24;
499 const W: usize = 20;
500 const H: usize = 20;
501 const ALLOC_H: usize = 25;
502 let mut image_data = vec![2u8; STRIDE * ALLOC_H];
503 image_data[4 * STRIDE + 3] = 43;
504 image_data[4 * STRIDE + 4] = 42;
505 image_data[4 * STRIDE + 5] = 41;
506 image_data[5 * STRIDE + 3] = 1;
507 image_data[5 * STRIDE + 4] = 1;
508 image_data[6 * STRIDE + 4] = 1;
509
510 image_data[4 * STRIDE + 23] = 1;
513 image_data[5 * STRIDE + 23] = 1;
514 image_data[H * STRIDE + 4] = 1;
515 image_data[(H + 1) * STRIDE + 6] = 1;
516
517 let im = machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
518 .unwrap();
519
520 let im = threshold(im, CmpOp::LessThan, 42, 0, 255);
521
522 let image_data: Vec<u8> = im.into();
523
524 assert_eq!(image_data[0], 0);
525 assert_eq!(image_data[(H - 1) * STRIDE + (W - 1)], 0);
526 assert_eq!(image_data[4 * STRIDE + 3], 255);
527 assert_eq!(image_data[4 * STRIDE + 4], 255);
528 assert_eq!(image_data[4 * STRIDE + 5], 0);
529 assert_eq!(image_data[4 * STRIDE + 23], 1);
530 assert_eq!(image_data[4 * STRIDE + 22], 2);
531 assert_eq!(image_data[H * STRIDE + 4], 1);
532 assert_eq!(image_data[(H + 1) * STRIDE + 6], 1);
533 }
534
535 #[test]
536 fn test_central_moments() {
537 const STRIDE: usize = 20;
538 const W: usize = 20;
539 const H: usize = 20;
540 const ALLOC_H: usize = 20;
541 let mut image_data = vec![0u8; STRIDE * ALLOC_H];
542
543 image_data[4 * STRIDE + 3] = 1;
544 image_data[5 * STRIDE + 3] = 1;
545 image_data[5 * STRIDE + 4] = 1;
546 image_data[6 * STRIDE + 4] = 1;
547
548 let im = machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
549 .unwrap();
550
551 let mr = calculate_moments(&im);
552 assert_eq!(mr.u11, 1.0);
553 assert_eq!(mr.u20, 1.0);
554 assert_eq!(mr.u02, 2.0);
555 }
556
557 #[test]
558 fn test_image_moments() {
559 const STRIDE: usize = 24;
560 const W: usize = 20;
561 const H: usize = 20;
562 const ALLOC_H: usize = 25;
563 let mut image_data = vec![0u8; STRIDE * ALLOC_H];
564 image_data[4 * STRIDE + 3] = 1;
565 image_data[5 * STRIDE + 3] = 1;
566 image_data[5 * STRIDE + 4] = 1;
567 image_data[6 * STRIDE + 4] = 1;
568
569 image_data[4 * STRIDE + 23] = 255;
572 image_data[5 * STRIDE + 23] = 255;
573 image_data[H * STRIDE + 4] = 255;
574 image_data[(H + 1) * STRIDE + 6] = 255;
575
576 let im = machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
577 .unwrap();
578
579 assert_eq!(spatial_moment_00(&im), 4.0);
580 assert_eq!(spatial_moment_10(&im), 14.0);
581 assert_eq!(spatial_moment_01(&im), 20.0);
582 }
583
584 #[test]
585 fn test_image_moments_remainder() {
586 const STRIDE: usize = 24;
591 const W: usize = 20;
592 const H: usize = 20;
593 let mut image_data = vec![0u8; STRIDE * H];
594 image_data[4 * STRIDE + 3] = 20;
595 image_data[5 * STRIDE + 3] = 21;
596 image_data[5 * STRIDE + 4] = 22;
597 image_data[6 * STRIDE + 4] = 23;
598
599 image_data[4 * STRIDE + 19] = 1;
600 image_data[5 * STRIDE + 19] = 1;
601 image_data[6 * STRIDE + 19] = 1;
602
603 image_data[4 * STRIDE + 23] = 255;
606 image_data[5 * STRIDE + 23] = 255;
607
608 let im = machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
609 .unwrap();
610
611 assert_eq!(spatial_moment_00(&im), 89.0);
612 assert_eq!(spatial_moment_01(&im), 448.0);
613 assert_eq!(spatial_moment_10(&im), 360.0);
614 }
615
616 #[test]
617 fn test_wide_image_moments_simd() {
618 const STRIDE: usize = 10000;
620 const W: usize = 9000;
621 const H: usize = 20;
622 let mut image_data = vec![u8::MAX; STRIDE * H];
624
625 image_data[W + 23] = 0;
628 image_data[W + 24] = 0;
629
630 let im = machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
631 .unwrap();
632
633 let expected: f64 = H as f64 * u8::MAX as f64 * W as f64;
635 assert_eq!(
636 spatial_moment(&im, Power::Zero, Power::Zero) as f64,
637 expected
638 );
639 assert_eq!(
640 spatial_moment_00(&im),
641 spatial_moment(&im, Power::Zero, Power::Zero)
642 );
643 assert_eq!(spatial_moment_00(&im) as f64, expected);
644 assert_eq!(
645 spatial_moment_01(&im),
646 spatial_moment(&im, Power::Zero, Power::One)
647 );
648 assert_eq!(
649 spatial_moment_10(&im),
650 spatial_moment(&im, Power::One, Power::Zero)
651 );
652 }
653
654 #[test]
655 fn test_tall_image_moments_simd() {
656 const STRIDE: usize = 32;
658 const W: usize = 20;
659 const H: usize = 10000;
660 let mut image_data = vec![u8::MAX; STRIDE * H];
662
663 image_data[W + 3] = 0;
666 image_data[W + 4] = 0;
667
668 let im = machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
669 .unwrap();
670
671 let expected: f64 = H as f64 * u8::MAX as f64 * W as f64;
673 assert_eq!(
674 spatial_moment(&im, Power::Zero, Power::Zero) as f64,
675 expected
676 );
677 assert_eq!(
678 spatial_moment_00(&im),
679 spatial_moment(&im, Power::Zero, Power::Zero)
680 );
681 assert_eq!(spatial_moment_00(&im) as f64, expected);
682 assert_eq!(
683 spatial_moment_01(&im),
684 spatial_moment(&im, Power::Zero, Power::One)
685 );
686 assert_eq!(
687 spatial_moment_10(&im),
688 spatial_moment(&im, Power::One, Power::Zero)
689 );
690 }
691
692 fn oracle_moments<IM>(im: &IM) -> Moments
697 where
698 IM: HasRowChunksExact<Mono8>,
699 {
700 let m00 = spatial_moment(im, Power::Zero, Power::Zero);
701 let m01 = spatial_moment(im, Power::Zero, Power::One);
702 let m10 = spatial_moment(im, Power::One, Power::Zero);
703 let m11 = spatial_moment(im, Power::One, Power::One);
704 let m02 = spatial_moment(im, Power::Zero, Power::Two);
705 let m20 = spatial_moment(im, Power::Two, Power::Zero);
706
707 let centroid_x = m01 / m00;
708 let centroid_y = m10 / m00;
709
710 Moments {
711 centroid_x,
712 centroid_y,
713 m00,
714 m01,
715 m10,
716 u11: m11 - centroid_x * m10,
717 u02: m02 - centroid_x * m01,
718 u20: m20 - centroid_y * m10,
719 }
720 }
721
722 fn assert_moments_eq(got: &Moments, want: &Moments) {
723 assert_eq!(got.m00, want.m00, "m00");
730 assert_eq!(got.m01, want.m01, "m01");
731 assert_eq!(got.m10, want.m10, "m10");
732 assert_eq!(got.centroid_x, want.centroid_x, "centroid_x");
733 assert_eq!(got.centroid_y, want.centroid_y, "centroid_y");
734 assert_eq!(got.u11, want.u11, "u11");
735 assert_eq!(got.u02, want.u02, "u02");
736 assert_eq!(got.u20, want.u20, "u20");
737 }
738
739 #[test]
743 fn test_calculate_moments_matches_oracle() {
744 {
748 const STRIDE: usize = 24;
749 const W: usize = 20;
750 const H: usize = 20;
751 let mut image_data = vec![0u8; STRIDE * H];
752 image_data[4 * STRIDE + 3] = 20;
753 image_data[5 * STRIDE + 3] = 21;
754 image_data[5 * STRIDE + 4] = 22;
755 image_data[6 * STRIDE + 4] = 23;
756 image_data[4 * STRIDE + 19] = 1;
757 image_data[5 * STRIDE + 19] = 1;
758 image_data[6 * STRIDE + 19] = 1;
759 image_data[4 * STRIDE + 23] = 255;
761 image_data[5 * STRIDE + 23] = 255;
762
763 let im =
764 machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
765 .unwrap();
766 assert_moments_eq(&calculate_moments(&im), &oracle_moments(&im));
767 }
768
769 {
773 const STRIDE: usize = 10000;
774 const W: usize = 9000;
775 const H: usize = 20;
776 let mut image_data = vec![u8::MAX; STRIDE * H];
777 image_data[W + 23] = 0;
778 image_data[W + 24] = 0;
779
780 let im =
781 machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
782 .unwrap();
783 assert_moments_eq(&calculate_moments(&im), &oracle_moments(&im));
784 }
785
786 {
789 const STRIDE: usize = 32;
790 const W: usize = 20;
791 const H: usize = 10000;
792 let mut image_data = vec![u8::MAX; STRIDE * H];
793 image_data[W + 3] = 0;
794 image_data[W + 4] = 0;
795
796 let im =
797 machine_vision_formats::owned::OImage::new(W as u32, H as u32, STRIDE, image_data)
798 .unwrap();
799 assert_moments_eq(&calculate_moments(&im), &oracle_moments(&im));
800 }
801 }
802}