use std::marker::PhantomData;
pub use nv_flip_sys::{pixels_per_degree, DEFAULT_PIXELS_PER_DEGREE};
pub struct FlipImageRgb8 {
inner: *mut nv_flip_sys::FlipImageColor3,
width: u32,
height: u32,
}
unsafe impl Send for FlipImageRgb8 {}
unsafe impl Sync for FlipImageRgb8 {}
impl Clone for FlipImageRgb8 {
fn clone(&self) -> Self {
let inner = unsafe { nv_flip_sys::flip_image_color3_clone(self.inner) };
assert!(!inner.is_null());
Self {
inner,
width: self.width,
height: self.height,
}
}
}
impl FlipImageRgb8 {
pub fn new(width: u32, height: u32) -> Self {
let inner = unsafe { nv_flip_sys::flip_image_color3_new(width, height, std::ptr::null()) };
assert!(!inner.is_null());
Self {
inner,
width,
height,
}
}
pub fn with_data(width: u32, height: u32, data: &[u8]) -> Self {
assert!(data.len() >= (width * height * 3) as usize);
let inner = unsafe { nv_flip_sys::flip_image_color3_new(width, height, data.as_ptr()) };
assert!(!inner.is_null());
Self {
inner,
width,
height,
}
}
pub fn to_vec(&self) -> Vec<u8> {
let mut data = vec![0u8; (self.width * self.height * 3) as usize];
unsafe {
nv_flip_sys::flip_image_color3_get_data(self.inner, data.as_mut_ptr());
}
data
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
}
impl Drop for FlipImageRgb8 {
fn drop(&mut self) {
unsafe {
nv_flip_sys::flip_image_color3_free(self.inner);
}
}
}
pub struct FlipImageFloat {
inner: *mut nv_flip_sys::FlipImageFloat,
width: u32,
height: u32,
}
unsafe impl Send for FlipImageFloat {}
unsafe impl Sync for FlipImageFloat {}
impl Clone for FlipImageFloat {
fn clone(&self) -> Self {
let inner = unsafe { nv_flip_sys::flip_image_float_clone(self.inner) };
assert!(!inner.is_null());
Self {
inner,
width: self.width,
height: self.height,
}
}
}
impl FlipImageFloat {
pub fn new(width: u32, height: u32) -> Self {
let inner = unsafe { nv_flip_sys::flip_image_float_new(width, height, std::ptr::null()) };
assert!(!inner.is_null());
Self {
inner,
width,
height,
}
}
pub fn with_data(width: u32, height: u32, data: &[f32]) -> Self {
assert!(data.len() >= (width * height) as usize);
let inner = unsafe { nv_flip_sys::flip_image_float_new(width, height, data.as_ptr()) };
assert!(!inner.is_null());
Self {
inner,
width,
height,
}
}
pub fn apply_color_lut(&self, value_mapping: &FlipImageRgb8) -> FlipImageRgb8 {
let output = FlipImageRgb8::new(self.width, self.height);
unsafe {
nv_flip_sys::flip_image_color3_color_map(output.inner, self.inner, value_mapping.inner);
}
output
}
pub fn to_color3(&self) -> FlipImageRgb8 {
let color3 = FlipImageRgb8::new(self.width, self.height);
unsafe {
nv_flip_sys::flip_image_float_copy_float_to_color3(self.inner, color3.inner);
}
color3
}
pub fn to_vec(&self) -> Vec<f32> {
let mut data = vec![0f32; (self.width * self.height) as usize];
unsafe {
nv_flip_sys::flip_image_float_get_data(self.inner, data.as_mut_ptr());
}
data
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
}
impl Drop for FlipImageFloat {
fn drop(&mut self) {
unsafe {
nv_flip_sys::flip_image_float_free(self.inner);
}
}
}
pub fn magma_lut() -> FlipImageRgb8 {
let inner = unsafe { nv_flip_sys::flip_image_color3_magma_map() };
assert!(!inner.is_null());
FlipImageRgb8 {
inner,
width: 256,
height: 1,
}
}
pub fn flip(
reference_image: FlipImageRgb8,
test_image: FlipImageRgb8,
pixels_per_degree: f32,
) -> FlipImageFloat {
assert_eq!(
reference_image.width(),
test_image.width(),
"Width mismatch between reference and test image"
);
assert_eq!(
reference_image.height(),
test_image.height(),
"Height mismatch between reference and test image"
);
let error_map = FlipImageFloat::new(reference_image.width(), reference_image.height());
unsafe {
nv_flip_sys::flip_image_float_flip(
error_map.inner,
reference_image.inner,
test_image.inner,
pixels_per_degree,
);
}
error_map
}
pub struct FlipHistogram<'a> {
inner: *mut nv_flip_sys::FlipImageHistogramRef,
_phantom: PhantomData<&'a ()>,
}
unsafe impl Send for FlipHistogram<'_> {}
unsafe impl Sync for FlipHistogram<'_> {}
impl<'a> FlipHistogram<'a> {
pub fn bucket_size(&self) -> usize {
unsafe { nv_flip_sys::flip_image_histogram_ref_get_bucket_size(self.inner) }
}
pub fn bucket_id_min(&self) -> Option<usize> {
let value = unsafe { nv_flip_sys::flip_image_histogram_ref_get_bucket_id_min(self.inner) };
if value == usize::MAX {
None
} else {
Some(value)
}
}
pub fn bucket_id_max(&self) -> usize {
unsafe { nv_flip_sys::flip_image_histogram_ref_get_bucket_id_max(self.inner) }
}
pub fn bucket_value_count(&self, bucket_id: usize) -> usize {
assert!(bucket_id < self.bucket_count());
unsafe { nv_flip_sys::flip_image_histogram_ref_get_bucket_value(self.inner, bucket_id) }
}
pub fn bucket_count(&self) -> usize {
unsafe { nv_flip_sys::flip_image_histogram_ref_size(self.inner) }
}
pub fn minimum_allowed_value(&self) -> f32 {
unsafe { nv_flip_sys::flip_image_histogram_ref_get_min_value(self.inner) }
}
pub fn maximum_allowed_value(&self) -> f32 {
unsafe { nv_flip_sys::flip_image_histogram_ref_get_max_value(self.inner) }
}
pub unsafe fn clear(&mut self) {
unsafe {
nv_flip_sys::flip_image_histogram_ref_clear(self.inner);
}
}
pub unsafe fn resize(&mut self, bucket_size: usize) {
unsafe {
nv_flip_sys::flip_image_histogram_ref_resize(self.inner, bucket_size);
}
}
pub fn bucket_id(&self, value: f32) -> usize {
unsafe { nv_flip_sys::flip_image_histogram_ref_value_bucket_id(self.inner, value) }
}
pub unsafe fn include_value(&mut self, value: f32, count: usize) {
unsafe {
nv_flip_sys::flip_image_histogram_ref_inc_value(self.inner, value, count);
}
}
pub unsafe fn include_image(&mut self, image: &FlipImageFloat) {
unsafe {
nv_flip_sys::flip_image_histogram_ref_inc_image(self.inner, image.inner);
}
}
}
impl Drop for FlipHistogram<'_> {
fn drop(&mut self) {
unsafe {
nv_flip_sys::flip_image_histogram_ref_free(self.inner);
}
}
}
pub struct FlipPool {
inner: *mut nv_flip_sys::FlipImagePool,
values_added: usize,
}
unsafe impl Send for FlipPool {}
unsafe impl Sync for FlipPool {}
impl FlipPool {
pub fn new() -> Self {
Self::with_buckets(100)
}
pub fn with_buckets(bucket_count: usize) -> Self {
let inner = unsafe { nv_flip_sys::flip_image_pool_new(bucket_count) };
assert!(!inner.is_null());
Self {
inner,
values_added: 0,
}
}
pub fn from_image(image: &FlipImageFloat) -> Self {
let mut pool = Self::new();
pool.update_with_image(image);
pool
}
pub fn histogram(&mut self) -> FlipHistogram<'_> {
let inner = unsafe { nv_flip_sys::flip_image_pool_get_histogram(self.inner) };
assert!(!inner.is_null());
FlipHistogram {
inner,
_phantom: PhantomData,
}
}
pub fn min_value(&self) -> f32 {
if self.values_added == 0 {
return 0.0;
}
unsafe { nv_flip_sys::flip_image_pool_get_min_value(self.inner) }
}
pub fn max_value(&self) -> f32 {
if self.values_added == 0 {
return 0.0;
}
unsafe { nv_flip_sys::flip_image_pool_get_max_value(self.inner) }
}
pub fn mean(&self) -> f32 {
if self.values_added == 0 {
return 0.0;
}
unsafe { nv_flip_sys::flip_image_pool_get_mean(self.inner) }
}
pub fn get_weighted_percentile(&self, percentile: f64) -> f64 {
if self.values_added == 0 {
return 0.0;
}
let bounds_percentile = f64::clamp(percentile, 0.0, next_f64_down(1.0));
unsafe {
nv_flip_sys::flip_image_pool_get_weighted_percentile(self.inner, bounds_percentile)
}
}
pub fn get_percentile(&mut self, percentile: f32, weighted: bool) -> f32 {
if self.values_added == 0 {
return 0.0;
}
let bounds_percentile =
f32::clamp(percentile, 0.0, 1.0 - (self.values_added as f32).recip());
debug_assert!(
(f32::ceil(bounds_percentile * self.values_added as f32) as usize) < self.values_added
);
unsafe {
nv_flip_sys::flip_image_pool_get_percentile(self.inner, bounds_percentile, weighted)
}
}
pub fn update_with_image(&mut self, image: &FlipImageFloat) {
unsafe {
nv_flip_sys::flip_image_pool_update_image(self.inner, image.inner);
}
self.values_added += image.width() as usize * image.height() as usize;
}
pub fn clear(&mut self) {
unsafe {
nv_flip_sys::flip_image_pool_clear(self.inner);
}
self.values_added = 0;
}
}
impl Default for FlipPool {
fn default() -> Self {
Self::new()
}
}
impl Drop for FlipPool {
fn drop(&mut self) {
unsafe {
nv_flip_sys::flip_image_pool_free(self.inner);
}
}
}
fn next_f64_down(value: f64) -> f64 {
f64::from_bits(value.to_bits() - 1)
}
#[cfg(test)]
mod tests {
pub use super::*;
use float_eq::assert_float_eq;
#[test]
fn zeroed_init() {
assert_eq!(FlipImageRgb8::new(10, 10).to_vec(), vec![0u8; 10 * 10 * 3]);
assert_eq!(FlipImageFloat::new(10, 10).to_vec(), vec![0.0f32; 10 * 10]);
}
#[test]
fn zero_size_pool_ops() {
let mut pool = FlipPool::new();
assert_eq!(pool.min_value(), 0.0);
assert_eq!(pool.max_value(), 0.0);
assert_eq!(pool.mean(), 0.0);
assert_eq!(pool.get_percentile(0.0, false), 0.0);
assert_eq!(pool.get_percentile(0.0, true), 0.0);
assert_eq!(pool.get_weighted_percentile(0.0), 0.0);
}
#[test]
fn end_to_end() {
let reference_image = image::open("../etc/tree-ref.png").unwrap().into_rgb8();
let reference_image = FlipImageRgb8::with_data(
reference_image.width(),
reference_image.height(),
&reference_image,
);
let test_image = image::open("../etc/tree-test.png").unwrap().into_rgb8();
let test_image =
FlipImageRgb8::with_data(test_image.width(), test_image.height(), &test_image);
let error_map = flip(reference_image, test_image, DEFAULT_PIXELS_PER_DEGREE);
let mut pool = FlipPool::from_image(&error_map);
let magma_lut = magma_lut();
let color = error_map.apply_color_lut(&magma_lut);
let image =
image::RgbImage::from_raw(color.width(), color.height(), color.to_vec()).unwrap();
let reference = image::open("../etc/tree-comparison-cli.png")
.unwrap()
.into_rgb8();
for (a, b) in image.pixels().zip(reference.pixels()) {
assert!(a.0[0].abs_diff(b.0[0]) <= 3);
assert!(a.0[1].abs_diff(b.0[1]) <= 3);
assert!(a.0[2].abs_diff(b.0[2]) <= 3);
}
const TOLERENCE: f32 = 0.000_1;
assert_float_eq!(pool.mean(), 0.133285, abs <= TOLERENCE);
assert_float_eq!(pool.get_percentile(0.25, true), 0.184924, abs <= TOLERENCE);
assert_float_eq!(pool.get_percentile(0.50, true), 0.333241, abs <= TOLERENCE);
assert_float_eq!(pool.get_percentile(0.75, true), 0.503441, abs <= TOLERENCE);
assert_float_eq!(pool.min_value(), 0.000000, abs <= TOLERENCE);
assert_float_eq!(pool.get_percentile(0.0, true), 0.000000, abs <= 0.001);
assert_float_eq!(pool.max_value(), 0.983044, abs <= TOLERENCE);
assert_float_eq!(pool.get_percentile(1.0, true), 0.983044, abs <= 0.001);
assert_float_eq!(
pool.get_weighted_percentile(0.25),
0.184586,
abs <= TOLERENCE as f64
);
assert_float_eq!(
pool.get_weighted_percentile(0.50),
0.333096,
abs <= TOLERENCE as f64
);
assert_float_eq!(
pool.get_weighted_percentile(0.75),
0.503230,
abs <= TOLERENCE as f64
);
let histogram = pool.histogram();
assert_float_eq!(histogram.minimum_allowed_value(), 0.0, abs <= TOLERENCE);
assert_float_eq!(histogram.maximum_allowed_value(), 1.0, abs <= TOLERENCE);
drop(histogram);
assert_float_eq!(pool.get_percentile(-10000.0, false), 0.0, abs <= TOLERENCE);
assert_float_eq!(pool.get_percentile(-10000.0, true), 0.0, abs <= TOLERENCE);
assert_float_eq!(
pool.get_percentile(10000.0, false),
0.983044,
abs <= TOLERENCE
);
assert_float_eq!(
pool.get_percentile(10000.0, true),
0.983044,
abs <= TOLERENCE
);
assert_float_eq!(
pool.get_weighted_percentile(-10000.0),
0.0,
abs <= TOLERENCE as _
);
assert_float_eq!(
pool.get_weighted_percentile(10000.0),
0.989999,
abs <= TOLERENCE as _
);
}
}