zune_imageprocs/
spatial_ops.rs

1/*
2 * Copyright (c) 2023.
3 *
4 * This software is free software;
5 *
6 * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license
7 */
8//! Simple spatial operations implemented for images
9use std::fmt::Debug;
10use std::ops::{Add, Div, Sub};
11
12use crate::pad::{pad, PadMethod};
13use crate::spatial::spatial;
14use crate::traits::NumOps;
15
16/// Spatial operations implemented for images
17#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
18pub enum SpatialOperations {
19    /// (max-min)/(max+min)
20    Contrast,
21    /// max
22    Maximum,
23    /// max-min
24    Gradient,
25    /// min
26    Minimum,
27    /// sum(pix)/len
28    Mean
29}
30
31impl SpatialOperations {
32    pub fn from_string_result(input: &str) -> Result<Self, String> {
33        match input
34        {
35            "contrast" => Ok(Self::Contrast),
36            "maximum" | "max" => Ok(Self::Maximum),
37            "gradient" => Ok(Self::Gradient),
38            "minimum" | "min" => Ok(Self::Minimum),
39            "mean" | "avg" => Ok(Self::Mean),
40            _ => Err(
41                "Unknown statistic type,accepted values are contrast,(maximum|max),gradient,(minimum|min),mean"
42                    .to_string()
43            )
44        }
45    }
46}
47
48fn find_min<T: PartialOrd + Default + Copy + NumOps<T>>(data: &[T]) -> T {
49    let mut minimum = T::max_val();
50
51    for datum in data {
52        if *datum < minimum {
53            minimum = *datum;
54        }
55    }
56    minimum
57}
58
59fn find_contrast<
60    T: PartialOrd + Default + Copy + NumOps<T> + Sub<Output = T> + Add<Output = T> + Div<Output = T>
61>(
62    data: &[T]
63) -> T {
64    let mut minimum = T::max_val();
65    let mut maximum = T::min_val();
66
67    for datum in data {
68        if *datum < minimum {
69            minimum = *datum;
70        }
71        if *datum > maximum {
72            maximum = *datum;
73        }
74    }
75    let num = maximum - minimum;
76    let div = (maximum + minimum).saturating_add(T::one()); // do not allow division by zero
77
78    num / div
79}
80
81fn find_gradient<
82    T: PartialOrd + Default + Copy + NumOps<T> + Sub<Output = T> + Add<Output = T> + Div<Output = T>
83>(
84    data: &[T]
85) -> T {
86    let mut minimum = T::max_val();
87    let mut maximum = T::min_val();
88
89    for datum in data {
90        if *datum < minimum {
91            minimum = *datum;
92        }
93        if *datum > maximum {
94            maximum = *datum;
95        }
96    }
97
98    maximum - minimum
99}
100
101#[inline(always)]
102fn find_max<T: PartialOrd + Copy + NumOps<T>>(data: &[T]) -> T {
103    let mut maximum = T::min_val();
104
105    for datum in data {
106        if *datum > maximum {
107            maximum = *datum;
108        }
109    }
110    maximum
111}
112
113#[allow(clippy::cast_possible_truncation)]
114fn find_mean<T>(data: &[T]) -> T
115where
116    T: Default + Copy + NumOps<T> + Add<Output = T> + Div<Output = T>,
117    u32: std::convert::From<T>
118{
119    //https://godbolt.org/z/6Y8ncehd5
120    let mut maximum = u32::default();
121    let len = data.len() as u32;
122
123    for datum in data {
124        maximum += u32::from(*datum);
125    }
126    T::from_u32(maximum / len)
127}
128
129/// Run spatial operations on a pixel
130///
131/// # Arguments
132///
133/// * `in_channel`:  Input channel.
134/// * `out_channel`: Output channels
135/// * `radius`:  Radius for the spatial function
136/// * `width`:  Image width
137/// * `height`:  Image height
138/// * `operations`:  Enum operation to run
139///
140///
141pub fn spatial_ops<T>(
142    in_channel: &[T], out_channel: &mut [T], radius: usize, width: usize, height: usize,
143    operations: SpatialOperations
144) where
145    T: PartialOrd
146        + Default
147        + Copy
148        + NumOps<T>
149        + Sub<Output = T>
150        + Add<Output = T>
151        + Div<Output = T>,
152    u32: std::convert::From<T>
153{
154    //pad here
155    let padded_input = pad(
156        in_channel,
157        width,
158        height,
159        radius,
160        radius,
161        PadMethod::Replicate
162    );
163
164    // Note: It's faster to do it like this,
165    // Because of our tied and tested enemy called cache misses
166    //
167    // i.e using fn pointers
168    //
169    //   55,526,220,319   L1-dcache-loads:u         #    3.601 G/sec                    (75.02%)
170    //   746,710,874      L1-dcache-load-misses:u   #    1.34% of all L1-dcache accesses  (75.03%)
171    //
172    // Manual code for each statistic:
173    //
174    //   40,616,989,582   L1-dcache-loads:u         #    1.451 G/sec                    (75.03%)
175    //   103,089,305      L1-dcache-load-misses:u   #    0.25% of all L1-dcache accesses  (75.01%)
176    //
177    //
178    // Fn pointers have it 2x faster , yea tell me that we understand computers.
179    let ptr = match operations {
180        SpatialOperations::Contrast => find_contrast::<T>,
181        SpatialOperations::Maximum => find_max::<T>,
182        SpatialOperations::Gradient => find_gradient::<T>,
183        SpatialOperations::Minimum => find_min::<T>,
184        SpatialOperations::Mean => find_mean::<T>
185    };
186
187    spatial(&padded_input, out_channel, radius, width, height, ptr);
188}
189
190#[cfg(feature = "benchmarks")]
191#[cfg(test)]
192mod benchmarks {
193    extern crate test;
194
195    use crate::spatial_ops::{spatial_ops, SpatialOperations};
196
197    #[bench]
198    fn bench_spatial_mean(b: &mut test::Bencher) {
199        let width = 800;
200        let height = 800;
201        let dimensions = width * height;
202
203        let in_vec = vec![255_u16; dimensions];
204        let mut out_vec = vec![255_u16; dimensions];
205
206        let radius = 3;
207
208        b.iter(|| {
209            spatial_ops(
210                &in_vec,
211                &mut out_vec,
212                radius,
213                width,
214                height,
215                SpatialOperations::Mean
216            );
217        });
218    }
219
220    #[bench]
221    fn bench_spatial_min(b: &mut test::Bencher) {
222        let width = 800;
223        let height = 800;
224        let dimensions = width * height;
225
226        let in_vec = vec![255_u16; dimensions];
227        let mut out_vec = vec![255_u16; dimensions];
228
229        let radius = 3;
230
231        b.iter(|| {
232            spatial_ops(
233                &in_vec,
234                &mut out_vec,
235                radius,
236                width,
237                height,
238                SpatialOperations::Minimum
239            );
240        });
241    }
242}