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

Add options to show RMSE and SSE #294

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
- [X] SSIM
- [X] MSSSIM
- [X] CIEDE2000
- [X] MSE
- [X] RMSE

## Installation

Expand Down
63 changes: 62 additions & 1 deletion av_metrics/src/video/psnr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,32 @@ pub fn calculate_video_psnr<D: Decoder, F: Fn(usize) + Send>(
Ok(metrics.psnr)
}

/// Calculates the MSE for two Videos. Lower is better
///
/// MSE is Mean Square Error for a video.
pub fn calculate_video_mse<D: Decoder, F: Fn(usize) + Send>(
decoder1: &mut D,
decoder2: &mut D,
frame_limit: Option<usize>,
progress_callback: F,
) -> Result<PlanarMetrics, Box<dyn Error>> {
let metrics = Psnr.process_video(decoder1, decoder2, frame_limit, progress_callback)?;
Ok(metrics.mse)
}

/// Calculates the RMSE for two Videos. Lower is better
///
/// Root Mean Square MSE is Root of Mean Square Error for a video.
pub fn calculate_video_rmse<D: Decoder, F: Fn(usize) + Send>(
decoder1: &mut D,
decoder2: &mut D,
frame_limit: Option<usize>,
progress_callback: F,
) -> Result<PlanarMetrics, Box<dyn Error>> {
let metrics = Psnr.process_video(decoder1, decoder2, frame_limit, progress_callback)?;
Ok(metrics.rmse)
}

/// Calculates the APSNR for two videos. Higher is better.
///
/// APSNR is capped at 100 in order to avoid skewed statistics
Expand Down Expand Up @@ -66,6 +92,8 @@ pub fn calculate_frame_psnr<T: Pixel>(
struct PsnrResults {
psnr: PlanarMetrics,
apsnr: PlanarMetrics,
mse: PlanarMetrics,
rmse: PlanarMetrics,
}

struct Psnr;
Expand Down Expand Up @@ -121,7 +149,24 @@ impl VideoMetric for Psnr {
.sum::<f64>()
/ metrics.len() as f64,
};
Ok(PsnrResults { psnr, apsnr })
let mse = PlanarMetrics {
y: return_summed_mse(&metrics.iter().map(|m| m[0]).collect::<Vec<_>>()),
u: return_summed_mse(&metrics.iter().map(|m| m[1]).collect::<Vec<_>>()),
v: return_summed_mse(&metrics.iter().map(|m| m[2]).collect::<Vec<_>>()),
avg: return_summed_mse(&metrics.iter().flatten().copied().collect::<Vec<_>>()),
};
let rmse = PlanarMetrics {
y: mse.y.sqrt(),
u: mse.u.sqrt(),
v: mse.v.sqrt(),
avg: mse.avg.sqrt(),
};
Ok(PsnrResults {
psnr,
apsnr,
mse,
rmse,
})
}
}

Expand All @@ -144,6 +189,18 @@ fn calculate_summed_psnr(metrics: &[PsnrMetrics]) -> f64 {
)
}

fn return_summed_mse(metrics: &[PsnrMetrics]) -> f64 {
return_mse(
metrics
.iter()
.fold(PsnrMetrics::default(), |acc, plane| PsnrMetrics {
sq_err: acc.sq_err + plane.sq_err,
sample_max: plane.sample_max,
n_pixels: acc.n_pixels + plane.n_pixels,
}),
)
}

/// Calculate the PSNR metrics for a `Plane` by comparing the original (uncompressed) to
/// the compressed version.
fn calculate_plane_psnr_metrics<T: Pixel>(
Expand All @@ -168,6 +225,10 @@ fn calculate_psnr(metrics: PsnrMetrics) -> f64 {
- metrics.sq_err.log10())
}

fn return_mse(metrics: PsnrMetrics) -> f64 {
metrics.sq_err
}

/// Calculate the squared error for a `Plane` by comparing the original (uncompressed)
/// to the compressed version.
fn calculate_plane_total_squared_error<T: Pixel>(plane1: &Plane<T>, plane2: &Plane<T>) -> f64 {
Expand Down
71 changes: 63 additions & 8 deletions av_metrics_tool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ fn main() -> Result<(), String> {
.takes_value(true)
.possible_value("psnr")
.possible_value("apsnr")
.possible_value("mse")
.possible_value("rmse")
.possible_value("psnrhvs")
.possible_value("ssim")
.possible_value("msssim")
Expand Down Expand Up @@ -186,6 +188,10 @@ struct MetricsResults {
#[serde(skip_serializing_if = "Option::is_none")]
psnr: Option<PlanarMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
mse: Option<PlanarMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
rmse: Option<PlanarMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
apsnr: Option<PlanarMetrics>,
#[serde(skip_serializing_if = "Option::is_none")]
psnr_hvs: Option<PlanarMetrics>,
Expand Down Expand Up @@ -243,6 +249,18 @@ fn run_video_metrics(
results.psnr = Psnr::run(input1, input2, progress_fn);
}

if metric.is_none() || metric == Some("mse") {
progress.set_prefix("Computing MSE");
progress.reset();
results.mse = Mse::run(input1, input2, progress_fn);
}

if metric.is_none() || metric == Some("rmse") {
progress.set_prefix("Computing RMSE");
progress.reset();
results.rmse = Rmse::run(input1, input2, progress_fn);
}

if metric.is_none() || metric == Some("apsnr") {
progress.set_prefix("Computing APSNR");
progress.reset();
Expand Down Expand Up @@ -312,41 +330,48 @@ impl Report<'_> {
.map_err(|err| err.to_string())?;
}
OutputType::CSV(w) => {
writeln!(w, "filename,psnr,apsnr,psnr_hvs,ssim,msssim,ciede2000")
.map_err(|err| err.to_string())?;
writeln!(
w,
"filename,psnr,apsnr,psnr_hvs,ssim,msssim,ciede2000,mse,rmse"
)
.map_err(|err| err.to_string())?;
for cmp in self.comparisons.iter() {
writeln!(
w,
"{},{},{},{},{},{},{}",
"{},{},{},{},{},{},{},{},{}",
cmp.filename,
cmp.psnr.map(|v| v.avg).unwrap_or(-0.0),
cmp.apsnr.map(|v| v.avg).unwrap_or(-0.0),
cmp.psnr_hvs.map(|v| v.avg).unwrap_or(-0.0),
cmp.ssim.map(|v| v.avg).unwrap_or(-0.0),
cmp.msssim.map(|v| v.avg).unwrap_or(-0.0),
cmp.ciede2000.unwrap_or(-0.0)
cmp.ciede2000.unwrap_or(-0.0),
cmp.mse.map(|v| v.avg).unwrap_or(-0.0),
cmp.rmse.map(|v| v.avg).unwrap_or(-0.0)
)
.map_err(|err| err.to_string())?;
}
}
OutputType::Markdown(w) => {
writeln!(
w,
"|filename|psnr|apsnr|psnr_hvs|ssim|msssim|ciede2000|\n\
|-|-|-|-|-|-|-|"
"|filename|psnr|apsnr|psnr_hvs|ssim|msssim|ciede2000|mse|rmse|\n\
|-|-|-|-|-|-|-|-|-|"
)
.map_err(|err| err.to_string())?;
for cmp in self.comparisons.iter() {
writeln!(
w,
"|{}|{}|{}|{}|{}|{}|{}|",
"|{}|{}|{}|{}|{}|{}|{}|{}|{}|",
cmp.filename,
cmp.psnr.map(|v| v.avg).unwrap_or(-0.0),
cmp.apsnr.map(|v| v.avg).unwrap_or(-0.0),
cmp.psnr_hvs.map(|v| v.avg).unwrap_or(-0.0),
cmp.ssim.map(|v| v.avg).unwrap_or(-0.0),
cmp.msssim.map(|v| v.avg).unwrap_or(-0.0),
cmp.ciede2000.unwrap_or(-0.0)
cmp.ciede2000.unwrap_or(-0.0),
cmp.mse.map(|v| v.avg).unwrap_or(-0.0),
cmp.rmse.map(|v| v.avg).unwrap_or(-0.0)
)
.map_err(|err| err.to_string())?;
}
Expand All @@ -368,6 +393,8 @@ impl Report<'_> {
Text::print_result(writer, "SSIM", cmp.ssim)?;
Text::print_result(writer, "MSSSIM", cmp.msssim)?;
Text::print_result(writer, "CIEDE2000", cmp.ciede2000)?;
Text::print_result(writer, "MSE", cmp.mse)?;
Text::print_result(writer, "RMSE", cmp.rmse)?;
}
}
}
Expand Down Expand Up @@ -440,6 +467,34 @@ impl CliMetric for Psnr {
}
}

struct Mse;

impl CliMetric for Mse {
type VideoResult = PlanarMetrics;

fn calculate_video_metric<D: Decoder, F: Fn(usize) + Send>(
dec1: &mut D,
dec2: &mut D,
progress_callback: F,
) -> Result<Self::VideoResult, Box<dyn Error>> {
psnr::calculate_video_mse(dec1, dec2, None, progress_callback)
}
}

struct Rmse;

impl CliMetric for Rmse {
type VideoResult = PlanarMetrics;

fn calculate_video_metric<D: Decoder, F: Fn(usize) + Send>(
dec1: &mut D,
dec2: &mut D,
progress_callback: F,
) -> Result<Self::VideoResult, Box<dyn Error>> {
psnr::calculate_video_rmse(dec1, dec2, None, progress_callback)
}
}

struct APsnr;

impl CliMetric for APsnr {
Expand Down