| // SPDX-License-Identifier: Apache-2.0 |
| // ---------------------------------------------------------------------------- |
| // Copyright 2011-2022 Arm Limited |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy |
| // of the License at: |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations |
| // under the License. |
| // ---------------------------------------------------------------------------- |
| |
| /** |
| * @brief Functions for computing image error metrics. |
| */ |
| |
| #include <cassert> |
| #include <cstdio> |
| |
| #include "astcenccli_internal.h" |
| |
| /** |
| * @brief An accumulator for errors. |
| */ |
| class error_accum4 |
| { |
| public: |
| /** @brief The running sum. */ |
| double sum_r { 0.0 }; |
| double sum_g { 0.0 }; |
| double sum_b { 0.0 }; |
| double sum_a { 0.0 }; |
| }; |
| |
| /** |
| * @brief Incremental addition operator for error accumulators. |
| * |
| * @param val The accumulator to increment |
| * @param inc The increment to apply |
| * |
| * @return The updated accumulator |
| */ |
| static error_accum4& operator+=( |
| error_accum4 &val, |
| vfloat4 inc |
| ) { |
| val.sum_r += static_cast<double>(inc.lane<0>()); |
| val.sum_g += static_cast<double>(inc.lane<1>()); |
| val.sum_b += static_cast<double>(inc.lane<2>()); |
| val.sum_a += static_cast<double>(inc.lane<3>()); |
| return val; |
| } |
| |
| /** |
| * @brief mPSNR tone-mapping operator for HDR images. |
| * |
| * @param val The color value to tone map |
| * @param fstop The exposure fstop; should be in range [-125, 125] |
| * |
| * @return The mapped color value in [0.0f, 255.0f] range |
| */ |
| static float mpsnr_operator( |
| float val, |
| int fstop |
| ) { |
| if32 p; |
| p.u = 0x3f800000 + (fstop << 23); // 0x3f800000 is 1.0f |
| val *= p.f; |
| val = powf(val, (1.0f / 2.2f)); |
| val *= 255.0f; |
| |
| return astc::clamp(val, 0.0f, 255.0f); |
| } |
| |
| /** |
| * @brief mPSNR difference between two values. |
| * |
| * Differences are given as "val1 - val2". |
| * |
| * @param val1 The first color value |
| * @param val2 The second color value |
| * @param fstop_lo The low exposure fstop; should be in range [-125, 125] |
| * @param fstop_hi The high exposure fstop; should be in range [-125, 125] |
| * |
| * @return The summed mPSNR difference across all active fstop levels |
| */ |
| static float mpsnr_sumdiff( |
| float val1, |
| float val2, |
| int fstop_lo, |
| int fstop_hi |
| ) { |
| float summa = 0.0f; |
| for (int i = fstop_lo; i <= fstop_hi; i++) |
| { |
| float mval1 = mpsnr_operator(val1, i); |
| float mval2 = mpsnr_operator(val2, i); |
| float mdiff = mval1 - mval2; |
| summa += mdiff * mdiff; |
| } |
| return summa; |
| } |
| |
| /* See header for documentation */ |
| void compute_error_metrics( |
| bool compute_hdr_metrics, |
| bool compute_normal_metrics, |
| int input_components, |
| const astcenc_image* img1, |
| const astcenc_image* img2, |
| int fstop_lo, |
| int fstop_hi |
| ) { |
| static const int componentmasks[5] { 0x00, 0x07, 0x0C, 0x07, 0x0F }; |
| int componentmask = componentmasks[input_components]; |
| |
| error_accum4 errorsum; |
| error_accum4 alpha_scaled_errorsum; |
| error_accum4 log_errorsum; |
| error_accum4 mpsnr_errorsum; |
| double mean_angular_errorsum = 0.0; |
| double worst_angular_errorsum = 0.0; |
| |
| unsigned int dim_x = astc::min(img1->dim_x, img2->dim_x); |
| unsigned int dim_y = astc::min(img1->dim_y, img2->dim_y); |
| unsigned int dim_z = astc::min(img1->dim_z, img2->dim_z); |
| |
| if (img1->dim_x != img2->dim_x || |
| img1->dim_y != img2->dim_y || |
| img1->dim_z != img2->dim_z) |
| { |
| printf("WARNING: Only intersection of images will be compared:\n" |
| " Image 1: %dx%dx%d\n" |
| " Image 2: %dx%dx%d\n", |
| img1->dim_x, img1->dim_y, img1->dim_z, |
| img2->dim_x, img2->dim_y, img2->dim_z); |
| } |
| |
| double rgb_peak = 0.0; |
| unsigned int xsize1 = img1->dim_x; |
| unsigned int xsize2 = img2->dim_x; |
| |
| for (unsigned int z = 0; z < dim_z; z++) |
| { |
| for (unsigned int y = 0; y < dim_y; y++) |
| { |
| for (unsigned int x = 0; x < dim_x; x++) |
| { |
| vfloat4 color1; |
| vfloat4 color2; |
| |
| if (img1->data_type == ASTCENC_TYPE_U8) |
| { |
| uint8_t* data8 = static_cast<uint8_t*>(img1->data[z]); |
| |
| color1 = vfloat4( |
| data8[(4 * xsize1 * y) + (4 * x )], |
| data8[(4 * xsize1 * y) + (4 * x + 1)], |
| data8[(4 * xsize1 * y) + (4 * x + 2)], |
| data8[(4 * xsize1 * y) + (4 * x + 3)]); |
| |
| color1 = color1 / 255.0f; |
| } |
| else if (img1->data_type == ASTCENC_TYPE_F16) |
| { |
| uint16_t* data16 = static_cast<uint16_t*>(img1->data[z]); |
| |
| vint4 color1i = vint4( |
| data16[(4 * xsize1 * y) + (4 * x )], |
| data16[(4 * xsize1 * y) + (4 * x + 1)], |
| data16[(4 * xsize1 * y) + (4 * x + 2)], |
| data16[(4 * xsize1 * y) + (4 * x + 3)]); |
| |
| color1 = float16_to_float(color1i); |
| color1 = clamp(0, 65504.0f, color1); |
| } |
| else // if (img1->data_type == ASTCENC_TYPE_F32) |
| { |
| assert(img1->data_type == ASTCENC_TYPE_F32); |
| float* data32 = static_cast<float*>(img1->data[z]); |
| |
| color1 = vfloat4( |
| data32[(4 * xsize1 * y) + (4 * x )], |
| data32[(4 * xsize1 * y) + (4 * x + 1)], |
| data32[(4 * xsize1 * y) + (4 * x + 2)], |
| data32[(4 * xsize1 * y) + (4 * x + 3)]); |
| |
| color1 = clamp(0, 65504.0f, color1); |
| } |
| |
| if (img2->data_type == ASTCENC_TYPE_U8) |
| { |
| uint8_t* data8 = static_cast<uint8_t*>(img2->data[z]); |
| |
| color2 = vfloat4( |
| data8[(4 * xsize2 * y) + (4 * x )], |
| data8[(4 * xsize2 * y) + (4 * x + 1)], |
| data8[(4 * xsize2 * y) + (4 * x + 2)], |
| data8[(4 * xsize2 * y) + (4 * x + 3)]); |
| |
| color2 = color2 / 255.0f; |
| } |
| else if (img2->data_type == ASTCENC_TYPE_F16) |
| { |
| uint16_t* data16 = static_cast<uint16_t*>(img2->data[z]); |
| |
| vint4 color2i = vint4( |
| data16[(4 * xsize2 * y) + (4 * x )], |
| data16[(4 * xsize2 * y) + (4 * x + 1)], |
| data16[(4 * xsize2 * y) + (4 * x + 2)], |
| data16[(4 * xsize2 * y) + (4 * x + 3)]); |
| |
| color2 = float16_to_float(color2i); |
| color2 = clamp(0, 65504.0f, color2); |
| } |
| else // if (img2->data_type == ASTCENC_TYPE_F32) |
| { |
| assert(img2->data_type == ASTCENC_TYPE_F32); |
| float* data32 = static_cast<float*>(img2->data[z]); |
| |
| color2 = vfloat4( |
| data32[(4 * xsize2 * y) + (4 * x )], |
| data32[(4 * xsize2 * y) + (4 * x + 1)], |
| data32[(4 * xsize2 * y) + (4 * x + 2)], |
| data32[(4 * xsize2 * y) + (4 * x + 3)]); |
| |
| color2 = clamp(0, 65504.0f, color2); |
| } |
| |
| rgb_peak = astc::max(static_cast<double>(color1.lane<0>()), |
| static_cast<double>(color1.lane<1>()), |
| static_cast<double>(color1.lane<2>()), |
| rgb_peak); |
| |
| vfloat4 diffcolor = color1 - color2; |
| vfloat4 diffcolor_sq = diffcolor * diffcolor; |
| errorsum += diffcolor_sq; |
| |
| vfloat4 alpha_scaled_diffcolor = vfloat4( |
| diffcolor.lane<0>() * color1.lane<3>(), |
| diffcolor.lane<1>() * color1.lane<3>(), |
| diffcolor.lane<2>() * color1.lane<3>(), |
| diffcolor.lane<3>()); |
| |
| vfloat4 alpha_scaled_diffcolor_sq = alpha_scaled_diffcolor * alpha_scaled_diffcolor; |
| alpha_scaled_errorsum += alpha_scaled_diffcolor_sq; |
| |
| if (compute_hdr_metrics) |
| { |
| vfloat4 log_input_color1 = log2(color1); |
| vfloat4 log_input_color2 = log2(color2); |
| |
| vfloat4 log_diffcolor = log_input_color1 - log_input_color2; |
| |
| log_errorsum += log_diffcolor * log_diffcolor; |
| |
| vfloat4 mpsnr_error = vfloat4( |
| mpsnr_sumdiff(color1.lane<0>(), color2.lane<0>(), fstop_lo, fstop_hi), |
| mpsnr_sumdiff(color1.lane<1>(), color2.lane<1>(), fstop_lo, fstop_hi), |
| mpsnr_sumdiff(color1.lane<2>(), color2.lane<2>(), fstop_lo, fstop_hi), |
| mpsnr_sumdiff(color1.lane<3>(), color2.lane<3>(), fstop_lo, fstop_hi)); |
| |
| mpsnr_errorsum += mpsnr_error; |
| } |
| |
| if (compute_normal_metrics) |
| { |
| // Decode the normal vector |
| vfloat4 normal1 = (color1 - 0.5f) * 2.0f; |
| normal1 = normalize_safe(normal1.swz<0, 1, 2>(), unit3()); |
| |
| vfloat4 normal2 = (color2 - 0.5f) * 2.0f; |
| normal2 = normalize_safe(normal2.swz<0, 1, 2>(), unit3()); |
| |
| // Float error can push this outside of valid range for acos, so clamp to avoid NaN issues |
| float normal_cos = clamp(-1.0f, 1.0f, dot3(normal1, normal2)).lane<0>(); |
| float rad_to_degrees = 180.0f / astc::PI; |
| double error_degrees = std::acos(static_cast<double>(normal_cos)) * static_cast<double>(rad_to_degrees); |
| |
| mean_angular_errorsum += error_degrees / (dim_x * dim_y * dim_z); |
| worst_angular_errorsum = astc::max(worst_angular_errorsum, error_degrees); |
| } |
| } |
| } |
| } |
| |
| double pixels = static_cast<double>(dim_x * dim_y * dim_z); |
| double samples = 0.0; |
| |
| double num = 0.0; |
| double alpha_num = 0.0; |
| double log_num = 0.0; |
| double mpsnr_num = 0.0; |
| |
| if (componentmask & 1) |
| { |
| num += errorsum.sum_r; |
| alpha_num += alpha_scaled_errorsum.sum_r; |
| log_num += log_errorsum.sum_r; |
| mpsnr_num += mpsnr_errorsum.sum_r; |
| samples += pixels; |
| } |
| |
| if (componentmask & 2) |
| { |
| num += errorsum.sum_g; |
| alpha_num += alpha_scaled_errorsum.sum_g; |
| log_num += log_errorsum.sum_g; |
| mpsnr_num += mpsnr_errorsum.sum_g; |
| samples += pixels; |
| } |
| |
| if (componentmask & 4) |
| { |
| num += errorsum.sum_b; |
| alpha_num += alpha_scaled_errorsum.sum_b; |
| log_num += log_errorsum.sum_b; |
| mpsnr_num += mpsnr_errorsum.sum_b; |
| samples += pixels; |
| } |
| |
| if (componentmask & 8) |
| { |
| num += errorsum.sum_a; |
| alpha_num += alpha_scaled_errorsum.sum_a; |
| samples += pixels; |
| } |
| |
| double denom = samples; |
| double stopcount = static_cast<double>(fstop_hi - fstop_lo + 1); |
| double mpsnr_denom = pixels * 3.0 * stopcount * 255.0 * 255.0; |
| |
| double psnr; |
| if (num == 0.0) |
| { |
| psnr = 999.0; |
| } |
| else |
| { |
| psnr = 10.0 * log10(denom / num); |
| } |
| |
| double rgb_psnr = psnr; |
| |
| printf("Quality metrics\n"); |
| printf("===============\n\n"); |
| |
| if (componentmask & 8) |
| { |
| printf(" PSNR (LDR-RGBA): %9.4f dB\n", psnr); |
| |
| double alpha_psnr; |
| if (alpha_num == 0.0) |
| { |
| alpha_psnr = 999.0; |
| } |
| else |
| { |
| alpha_psnr = 10.0 * log10(denom / alpha_num); |
| } |
| printf(" Alpha-weighted PSNR: %9.4f dB\n", alpha_psnr); |
| |
| double rgb_num = errorsum.sum_r + errorsum.sum_g + errorsum.sum_b; |
| if (rgb_num == 0.0) |
| { |
| rgb_psnr = 999.0; |
| } |
| else |
| { |
| rgb_psnr = 10.0 * log10(pixels * 3.0 / rgb_num); |
| } |
| printf(" PSNR (LDR-RGB): %9.4f dB\n", rgb_psnr); |
| } |
| else |
| { |
| printf(" PSNR (LDR-RGB): %9.4f dB\n", psnr); |
| } |
| |
| if (compute_hdr_metrics) |
| { |
| printf(" PSNR (RGB norm to peak): %9.4f dB (peak %f)\n", |
| rgb_psnr + 20.0 * log10(rgb_peak), rgb_peak); |
| |
| double mpsnr; |
| if (mpsnr_num == 0.0) |
| { |
| mpsnr = 999.0; |
| } |
| else |
| { |
| mpsnr = 10.0 * log10(mpsnr_denom / mpsnr_num); |
| } |
| |
| printf(" mPSNR (RGB): %9.4f dB (fstops %+d to %+d)\n", |
| mpsnr, fstop_lo, fstop_hi); |
| |
| double logrmse = sqrt(log_num / pixels); |
| printf(" LogRMSE (RGB): %9.4f\n", logrmse); |
| } |
| |
| if (compute_normal_metrics) |
| { |
| printf(" Mean Angular Error: %9.4f degrees\n", mean_angular_errorsum); |
| printf(" Worst Angular Error: %9.4f degrees\n", worst_angular_errorsum); |
| } |
| |
| printf("\n"); |
| } |