1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use crate::{fed_tau, Akaze, GrayFloatImage};
use log::*;

#[derive(Debug)]
#[allow(non_snake_case)]
pub struct EvolutionStep {
    /// Evolution time
    pub etime: f64,
    /// Evolution sigma. For linear diffusion t = sigma^2 / 2
    pub esigma: f64,
    /// Image octave
    pub octave: u32,
    /// Image sublevel in each octave
    pub sublevel: u32,
    /// Integer sigma. For computing the feature detector responses
    pub sigma_size: u32,
    /// Evolution image
    pub Lt: GrayFloatImage,
    /// Smoothed image
    pub Lsmooth: GrayFloatImage,
    /// First order spatial derivative
    pub Lx: GrayFloatImage,
    /// First order spatial derivatives
    pub Ly: GrayFloatImage,
    /// Second order spatial derivative
    pub Lxx: GrayFloatImage,
    /// Second order spatial derivatives
    pub Lyy: GrayFloatImage,
    /// Second order spatial derivatives
    pub Lxy: GrayFloatImage,
    /// Diffusivity image
    pub Lflow: GrayFloatImage,
    /// Detector response
    pub Ldet: GrayFloatImage,
    /// fed_tau steps
    pub fed_tau_steps: Vec<f64>,
}

impl EvolutionStep {
    /// Construct a new EvolutionStep for a given octave and sublevel
    ///
    /// # Arguments
    /// * `octave` - The target octave.
    /// * `octave` - The target sublevel.
    /// * `options` - The options to use.
    fn new(octave: u32, sublevel: u32, options: &Akaze) -> EvolutionStep {
        let esigma = options.base_scale_offset
            * f64::powf(
                2.0f64,
                f64::from(sublevel) / f64::from(options.num_sublevels) + f64::from(octave),
            );
        let etime = 0.5 * (esigma * esigma);
        EvolutionStep {
            etime,
            esigma,
            octave,
            sublevel,
            sigma_size: esigma.round() as u32,
            Lt: GrayFloatImage::new(0, 0),
            Lsmooth: GrayFloatImage::new(0, 0),
            Lx: GrayFloatImage::new(0, 0),
            Ly: GrayFloatImage::new(0, 0),
            Lxx: GrayFloatImage::new(0, 0),
            Lyy: GrayFloatImage::new(0, 0),
            Lxy: GrayFloatImage::new(0, 0),
            Lflow: GrayFloatImage::new(0, 0),
            Ldet: GrayFloatImage::new(0, 0),
            fed_tau_steps: vec![],
        }
    }
}

impl Akaze {
    /// Allocate and calculate prerequisites to the construction of a scale space.
    ///
    /// # Arguments
    /// `width` - The width of the input image.
    /// `height` - The height of the input image.
    /// `options` - The configuration to use.
    pub fn allocate_evolutions(&self, width: u32, height: u32) -> Vec<EvolutionStep> {
        let mut evolutions: Vec<EvolutionStep> = (0..self.max_octave_evolution)
            .filter_map(|octave| {
                let rfactor = 2.0f64.powi(-(octave as i32));
                let level_height = (f64::from(height) * rfactor) as u32;
                let level_width = (f64::from(width) * rfactor) as u32;
                let smallest_dim = std::cmp::min(level_width, level_height);
                // If the smallest dim is less than 40, terminate as we cannot detect features
                // at a scale that small.
                if smallest_dim < 40 {
                    None
                } else {
                    // At a smallest dimension size between 80, only include one sublevel,
                    // as the amount of information in the image is limited.
                    let sublevels = if smallest_dim < 80 {
                        1
                    } else {
                        self.num_sublevels
                    };
                    // Return the sublevels.
                    Some(
                        (0..sublevels)
                            .map(move |sublevel| EvolutionStep::new(octave, sublevel, self)),
                    )
                }
            })
            .flatten()
            .collect();
        // We need to set the tau steps.
        // This is used to produce each evolution.
        // Each tau corresponds to one diffusion time step.
        // In FED (Fast Explicit Diffusion) earlier time steps are smaller
        // because they are more unstable. Once it becomes more stable, the time
        // steps become larger.
        for i in 1..evolutions.len() {
            // Comute the total difference in time between evolutions.
            let ttime = evolutions[i].etime - evolutions[i - 1].etime;
            // Compute the separate tau steps and assign it to the evolution.
            evolutions[i].fed_tau_steps = fed_tau::fed_tau_by_process_time(ttime, 1, 0.25, true);
            debug!(
                "{} steps in evolution {}.",
                evolutions[i].fed_tau_steps.len(),
                i
            );
        }
        evolutions
    }
}