Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace anyhow with concrete error type (via thiserror) #16

Merged
merged 3 commits into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "ssimulacra2"
version = "0.4.0"
edition = "2021"
rust-version = "1.61.0"
rust-version = "1.65.0"
description = "Rust implementation of the SSIMULACRA2 metric"
repository = "https://github.com/rust-av/ssimulacra2"
homepage = "https://github.com/rust-av/ssimulacra2"
Expand All @@ -15,10 +15,10 @@ default = ["rayon"]

[dependencies]
aligned = "0.4.1"
anyhow = "1.0.0"
nalgebra = "0.32.2"
num-traits = "0.2.15"
rayon = { version = "1.5.3", optional = true }
thiserror = "1.0.56"
wide = "0.7.5"
yuvxyb = "0.3.0"

Expand Down
15 changes: 15 additions & 0 deletions src/blur.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ use std::f64::consts::PI;
use aligned::{Aligned, A16};
use nalgebra::base::{Matrix3, Matrix3x1};

/// Structure handling image blur.
///
/// This struct contains the necessary buffers and the kernel used for blurring
/// (currently a recursive approximation of the Gaussian filter).
///
/// Note that the width and height of the image passed to [blur][Self::blur] needs to exactly
/// match the width and height of this instance. If you reduce the image size (e.g. via
/// downscaling), [`shrink_to`][Self::shrink_to] can be used to resize the internal buffers.
pub struct Blur {
kernel: RecursiveGaussian,
temp: Vec<f32>,
Expand All @@ -11,6 +19,8 @@ pub struct Blur {
}

impl Blur {
/// Create a new [Blur] for images of the given width and height.
/// This pre-allocates the necessary buffers.
#[must_use]
pub fn new(width: usize, height: usize) -> Self {
Blur {
Expand All @@ -21,12 +31,17 @@ impl Blur {
}
}

/// Truncates the internal buffers to fit images of the given width and height.
///
/// This will [truncate][Vec::truncate] the internal buffers
/// without affecting the allocated memory.
pub fn shrink_to(&mut self, width: usize, height: usize) {
self.temp.truncate(width * height);
self.width = width;
self.height = height;
}

/// Blur the given image.
pub fn blur(&mut self, img: &[Vec<f32>; 3]) -> [Vec<f32>; 3] {
[
self.blur_plane(&img[0]),
Expand Down
47 changes: 34 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,37 +41,58 @@

mod blur;

use anyhow::{bail, Result};
pub use blur::Blur;
pub use yuvxyb::{CastFromPrimitive, Frame, LinearRgb, Pixel, Plane, Rgb, Xyb, Yuv};
pub use yuvxyb::{ColorPrimaries, MatrixCoefficients, TransferCharacteristic, YuvConfig};

// How often to downscale and score the input images.
// Each scaling step will downscale by a factor of two.
const NUM_SCALES: usize = 6;

/// Errors which can occur when attempting to calculate a SSIMULACRA2 score from two input images.
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum Ssimulacra2Error {
/// The conversion from input image to [LinearRgb] (via [TryFrom]) returned an [Err].
/// Note that the conversion from LinearRgb to [Xyb] cannot fail, which means that
/// this is the only point of failure regarding image conversion.
#[error("Failed to convert input image to linear RGB")]
LinearRgbConversionFailed,

/// The two input images do not have the same width and height.
#[error("Source and distorted image width and height must be equal")]
NonMatchingImageDimensions,

/// One of the input images has a width and/or height of less than 8 pixels.
/// This is not currently supported by the SSIMULACRA2 metric.
#[error("Images must be at least 8x8 pixels")]
InvalidImageSize,
}

/// Computes the SSIMULACRA2 score for a given input frame and the distorted
/// version of that frame.
///
/// # Errors
/// - If the source and distorted image width and height do not match
/// - If the source or distorted image cannot be converted to XYB successfully
/// - If the image is smaller than 8x8 pixels
pub fn compute_frame_ssimulacra2<T: TryInto<LinearRgb>, U: TryInto<LinearRgb>>(
source: T,
distorted: U,
) -> Result<f64> {
let mut img1: LinearRgb = source
.try_into()
.map_err(|_e| anyhow::anyhow!("Failed to convert to Linear Rgb"))?;
let mut img2: LinearRgb = distorted
.try_into()
.map_err(|_e| anyhow::anyhow!("Failed to convert to Linear Rgb"))?;
pub fn compute_frame_ssimulacra2<T, U>(source: T, distorted: U) -> Result<f64, Ssimulacra2Error>
where
LinearRgb: TryFrom<T> + TryFrom<U>,
{
let Ok(mut img1) = LinearRgb::try_from(source) else {
return Err(Ssimulacra2Error::LinearRgbConversionFailed);
};

let Ok(mut img2) = LinearRgb::try_from(distorted) else {
return Err(Ssimulacra2Error::LinearRgbConversionFailed);
};

if img1.width() != img2.width() || img1.height() != img2.height() {
bail!("Source and distorted image width and height must be equal");
return Err(Ssimulacra2Error::NonMatchingImageDimensions);
}

if img1.width() < 8 || img1.height() < 8 {
bail!("Images must be at least 8x8 pixels");
return Err(Ssimulacra2Error::InvalidImageSize);
}

let mut width = img1.width();
Expand Down
Loading