use crate::features::{Corner, Detector, DetectorCapacity, DetectorImage, Grid, Threshold};
use crate::float_image::FloatImageView;
use crate::utils::float_gradients::scharr_f32_into;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScoreThreshold {
absolute: f32,
relative: f32,
}
impl ScoreThreshold {
#[must_use]
pub fn absolute(min_score: f32) -> Self {
assert!(
min_score.is_finite() && min_score >= 0.0,
"absolute score threshold must be finite and non-negative, got {min_score}"
);
Self {
absolute: min_score,
relative: 0.0,
}
}
#[must_use]
pub fn with_relative(self, quality_level: f32) -> Self {
assert!(
(0.0..=1.0).contains(&quality_level),
"relative score threshold must be within 0..=1, got {quality_level}"
);
Self {
relative: quality_level,
..self
}
}
pub fn absolute_part(&self) -> f32 {
self.absolute
}
pub fn relative_part(&self) -> f32 {
self.relative
}
fn threshold(self) -> Threshold {
Threshold {
absolute: self.absolute,
relative: self.relative,
}
}
}
#[derive(Default)]
pub struct FloatDetectorContext {
detector: Detector<f64>,
}
impl FloatDetectorContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: DetectorCapacity) -> Self {
let mut context = Self::default();
context.reserve(capacity);
context
}
pub fn reserve(&mut self, capacity: DetectorCapacity) {
self.detector.reserve(&capacity);
}
pub fn detect(
&mut self,
image: FloatImageView<'_>,
threshold: ScoreThreshold,
min_distance: u32,
) -> &[Corner] {
self.detector
.detect(image, threshold.threshold(), min_distance)
}
pub fn detect_grid(
&mut self,
image: FloatImageView<'_>,
grid: Grid,
threshold: ScoreThreshold,
min_distance: u32,
existing_points: &[(f32, f32)],
) -> &[Corner] {
self.detector.detect_grid(
image,
grid,
threshold.threshold(),
min_distance,
existing_points,
)
}
}
impl DetectorImage for FloatImageView<'_> {
type Sum = f64;
fn dimensions(self) -> (u32, u32) {
(self.width(), self.height())
}
#[inline(always)]
fn gradients_into(self, grad_x: &mut [f32], grad_y: &mut [f32]) {
let (width, height) = (self.width() as usize, self.height() as usize);
scharr_f32_into(self.pixels(), width, height, grad_x, grad_y);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn threshold_passes_both_parts_to_the_detector() {
let absolute = ScoreThreshold::absolute(2.0);
assert_eq!(absolute.relative_part(), 0.0);
let both = absolute.with_relative(0.1);
assert_eq!((both.absolute_part(), both.relative_part()), (2.0, 0.1));
assert_eq!(
both.threshold(),
Threshold {
absolute: 2.0,
relative: 0.1
}
);
}
#[test]
#[should_panic(expected = "finite and non-negative")]
fn negative_absolute_threshold_panics() {
let _ = ScoreThreshold::absolute(-1e-9);
}
#[test]
#[should_panic(expected = "finite and non-negative")]
fn nan_absolute_threshold_panics() {
let _ = ScoreThreshold::absolute(f32::NAN);
}
#[test]
#[should_panic(expected = "within 0..=1")]
fn relative_threshold_above_one_panics() {
let _ = ScoreThreshold::absolute(0.0).with_relative(1.5);
}
#[test]
#[should_panic(expected = "within 0..=1")]
fn nan_relative_threshold_panics() {
let _ = ScoreThreshold::absolute(0.0).with_relative(f32::NAN);
}
#[test]
fn extreme_permitted_values_keep_scores_finite() {
let max = FloatImageView::MAX_ABS_VALUE;
let (width, height) = (8u32, 8u32);
let pixels: Vec<f32> = (0..width * height)
.map(|i| {
if (i / width + i % width) % 2 == 0 {
max
} else {
-max
}
})
.collect();
let checker: Vec<f32> = (0..width * height)
.map(|i| {
if (i / width < 4) == (i % width < 4) {
max
} else {
-max
}
})
.collect();
for pixels in [pixels, checker] {
let view = FloatImageView::new(&pixels, width, height).unwrap();
let mut context = FloatDetectorContext::new();
context.detect(view, ScoreThreshold::absolute(0.0), 0);
assert!(
context
.detector
.scores()
.iter()
.all(|s| s.is_finite() && *s >= 0.0)
);
}
}
}