use nalgebra::{DMatrix, Scalar};
use num_traits::{self, cast::AsPrimitive, NumCast};
use rand::Rng;
use std::ops::{Add, Div, Mul};
use crate::core::multires;
use crate::misc::helper::div_rem;
use crate::misc::type_aliases::Float;
pub trait Number<T>:
Scalar
+ Ord
+ NumCast
+ Add<T, Output = T>
+ Div<T, Output = T>
+ Mul<T, Output = T>
+ AsPrimitive<Float>
+ std::fmt::Display
{
}
impl Number<u16> for u16 {}
pub type Picked = u8;
pub struct RegionConfig<T> {
pub size: usize,
pub threshold_coefs: (Float, T),
}
#[derive(Copy, Clone)]
pub struct BlockConfig {
pub base_size: usize,
pub nb_levels: usize,
pub threshold_factor: Float,
}
pub struct RecursiveConfig {
pub nb_iterations_left: usize,
pub low_thresh: Float,
pub high_thresh: Float,
pub random_thresh: Float,
}
pub const DEFAULT_REGION_CONFIG: RegionConfig<u16> = RegionConfig {
size: 32,
threshold_coefs: (1.0, 3), };
pub const DEFAULT_BLOCK_CONFIG: BlockConfig = BlockConfig {
base_size: 4,
nb_levels: 3,
threshold_factor: 0.5,
};
pub const DEFAULT_RECURSIVE_CONFIG: RecursiveConfig = RecursiveConfig {
nb_iterations_left: 1,
low_thresh: 0.8,
high_thresh: 4.0,
random_thresh: 1.1,
};
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_precision_loss)]
pub fn select<T: Number<T>>(
gradients: &DMatrix<T>,
region_config: RegionConfig<T>,
block_config: BlockConfig,
recursive_config: RecursiveConfig,
nb_target: usize,
) -> DMatrix<bool> {
let median_gradients = region_median_gradients(gradients, region_config.size);
let regions_thresholds = region_thresholds(&median_gradients, region_config.threshold_coefs);
let (vec_nb_candidates, picked) = pick_all_block_candidates(
block_config,
region_config.size,
®ions_thresholds,
gradients,
);
let nb_candidates: usize = vec_nb_candidates.iter().sum();
let candidates_ratio = nb_candidates as Float / nb_target as Float;
let target_size = candidates_ratio.sqrt() * (block_config.base_size as Float + 1.0) - 1.0;
let target_size = std::cmp::max(1, target_size.round() as i32) as usize;
if candidates_ratio < recursive_config.low_thresh
|| candidates_ratio > recursive_config.high_thresh
{
if target_size != block_config.base_size && recursive_config.nb_iterations_left > 0 {
let mut b_config = block_config;
b_config.base_size = target_size;
let mut rec_config = recursive_config;
rec_config.nb_iterations_left -= 1;
select(gradients, region_config, b_config, rec_config, nb_target)
} else {
to_mask(&picked)
}
} else if candidates_ratio > recursive_config.random_thresh {
let mut rng = rand::thread_rng();
picked.map(|p| p > 0 && rng.gen::<u8>() <= (255.0 / candidates_ratio) as u8)
} else {
to_mask(&picked)
}
}
fn to_mask(picked: &DMatrix<u8>) -> DMatrix<bool> {
picked.map(|p| p > 0)
}
#[allow(clippy::cast_possible_truncation)]
fn pick_all_block_candidates<T: Number<T>>(
block_config: BlockConfig,
regions_size: usize,
regions_thresholds: &DMatrix<T>,
gradients: &DMatrix<T>,
) -> (Vec<usize>, DMatrix<Picked>) {
let (nb_rows, nb_cols) = gradients.shape();
let max_gradients_0 = init_max_gradients(gradients, block_config.base_size);
let max_gradients_multires =
multires::limited_sequence(block_config.nb_levels, max_gradients_0, |m| {
multires::halve(m, max_of_four_gradients)
});
let mut threshold_level_coef = 1.0;
let mut nb_picked = Vec::new();
let (blocks_rows, blocks_cols) = max_gradients_multires[0].shape();
let mut mask = DMatrix::repeat(blocks_rows, blocks_cols, true);
let mut candidates = DMatrix::repeat(nb_rows, nb_cols, 0);
for (level, max_gradients_level) in max_gradients_multires.iter().enumerate() {
let (nb_picked_level, mask_next_level, new_candidates) = pick_level_block_candidates(
threshold_level_coef,
(level + 1) as u8,
regions_size,
regions_thresholds,
max_gradients_level,
&mask,
candidates,
);
nb_picked.push(nb_picked_level);
mask = mask_next_level;
candidates = new_candidates;
threshold_level_coef *= block_config.threshold_factor;
}
(nb_picked, candidates)
}
fn init_max_gradients<T: Number<T>>(
gradients: &DMatrix<T>,
block_size: usize,
) -> DMatrix<(T, usize, usize)> {
let (nb_rows, nb_cols) = gradients.shape();
let nb_rows_blocks = match div_rem(nb_rows, block_size) {
(quot, 0) => quot,
(quot, _) => quot + 1,
};
let nb_cols_blocks = match div_rem(nb_cols, block_size) {
(quot, 0) => quot,
(quot, _) => quot + 1,
};
DMatrix::from_fn(nb_rows_blocks, nb_cols_blocks, |bi, bj| {
let start_i = bi * block_size;
let start_j = bj * block_size;
let end_i = std::cmp::min(start_i + block_size, nb_rows);
let end_j = std::cmp::min(start_j + block_size, nb_cols);
let mut tmp_max = (gradients[(start_i, start_j)], start_i, start_j);
for j in start_j..end_j {
for i in start_i..end_i {
let g = gradients[(i, j)];
if g > tmp_max.0 {
tmp_max = (g, i, j);
}
}
}
tmp_max
})
}
fn max_of_four_gradients<T: Number<T>>(
g1: (T, usize, usize),
g2: (T, usize, usize),
g3: (T, usize, usize),
g4: (T, usize, usize),
) -> (T, usize, usize) {
let g_max = |g_m1: (T, usize, usize), g_m2: (T, usize, usize)| {
if g_m1.0 < g_m2.0 {
g_m2
} else {
g_m1
}
};
g_max(g1, g_max(g2, g_max(g3, g4)))
}
fn pick_level_block_candidates<T: Number<T>>(
threshold_level_coef: Float,
level: Picked,
regions_size: usize,
regions_thresholds: &DMatrix<T>,
max_gradients: &DMatrix<(T, usize, usize)>,
mask: &DMatrix<bool>,
candidates: DMatrix<Picked>,
) -> (usize, DMatrix<bool>, DMatrix<Picked>) {
let (mask_height, mask_width) = mask.shape();
let mut mask_next_level = DMatrix::repeat(mask_height / 2, mask_width / 2, true);
let mut candidates = candidates;
let mut nb_picked = 0;
for j in 0..(mask_width / 2 * 2) {
for i in 0..(mask_height / 2 * 2) {
if mask[(i, j)] {
let (g2, i_g, j_g) = max_gradients[(i, j)];
let threshold = regions_thresholds[(i_g / regions_size, j_g / regions_size)];
if g2.as_() >= threshold_level_coef * threshold.as_() {
mask_next_level[(i / 2, j / 2)] = false;
candidates[(i_g, j_g)] = level;
nb_picked += 1;
}
} else {
mask_next_level[(i / 2, j / 2)] = false;
}
}
}
(nb_picked, mask_next_level, candidates)
}
#[allow(clippy::cast_precision_loss)]
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
fn region_thresholds<T: Number<T>>(median_gradients: &DMatrix<T>, coefs: (Float, T)) -> DMatrix<T> {
let (nb_rows, nb_cols) = median_gradients.shape();
DMatrix::from_fn(nb_rows, nb_cols, |i, j| {
let start_i = std::cmp::max(0, i as i32 - 1) as usize;
let start_j = std::cmp::max(0, j as i32 - 1) as usize;
let end_i = std::cmp::min(nb_rows, i + 2);
let end_j = std::cmp::min(nb_cols, j + 2);
let mut sum: T = num_traits::cast(0).unwrap();
let mut nb_elements = 0;
for j in start_j..end_j {
for i in start_i..end_i {
sum = sum + median_gradients[(i, j)];
nb_elements += 1;
}
}
let (a, b) = coefs;
let thresh_tmp = sum.as_() / nb_elements as Float + b.as_();
num_traits::cast(a * thresh_tmp * thresh_tmp).expect("woops")
})
}
fn region_median_gradients<T: Number<T>>(gradients: &DMatrix<T>, size: usize) -> DMatrix<T> {
let (nb_rows, nb_cols) = gradients.shape();
let nb_rows_regions = match div_rem(nb_rows, size) {
(quot, 0) => quot,
(quot, _) => quot + 1,
};
let nb_cols_regions = match div_rem(nb_cols, size) {
(quot, 0) => quot,
(quot, _) => quot + 1,
};
DMatrix::from_fn(nb_rows_regions, nb_cols_regions, |i, j| {
let height = std::cmp::min(size, nb_rows - i * size);
let width = std::cmp::min(size, nb_cols - j * size);
let region_slice = gradients.slice((i * size, j * size), (height, width));
let mut region_cloned: Vec<T> = region_slice.iter().cloned().collect();
region_cloned.sort_unstable();
region_cloned[region_cloned.len() / 2]
})
}