#!/usr/bin/env python3 # Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, are permitted # provided that the following conditions are met: # * Redistributions of source code must retain the above copyright notice, this list of # conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, this list of # conditions and the following disclaimer in the documentation and/or other materials # provided with the distribution. # * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used # to endorse or promote products derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import imageio import numpy as np import os import struct ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) def mse2psnr(x): return -10.*np.log(x)/np.log(10.) def write_image_imageio(img_file, img, quality): img = (np.clip(img, 0.0, 1.0) * 255.0 + 0.5).astype(np.uint8) kwargs = {} if os.path.splitext(img_file)[1].lower() in [".jpg", ".jpeg"]: if img.ndim >= 3 and img.shape[2] > 3: img = img[:,:,:3] kwargs["quality"] = quality kwargs["subsampling"] = 0 imageio.imwrite(img_file, img, **kwargs) def read_image_imageio(img_file): img = imageio.imread(img_file) img = np.asarray(img).astype(np.float32) if len(img.shape) == 2: img = img[:,:,np.newaxis] return img / 255.0 def srgb_to_linear(img): limit = 0.04045 return np.where(img > limit, np.power((img + 0.055) / 1.055, 2.4), img / 12.92) def linear_to_srgb(img): limit = 0.0031308 return np.where(img > limit, 1.055 * (img ** (1.0 / 2.4)) - 0.055, 12.92 * img) def read_image(file): if os.path.splitext(file)[1] == ".bin": with open(file, "rb") as f: bytes = f.read() h, w = struct.unpack("ii", bytes[:8]) img = np.frombuffer(bytes, dtype=np.float16, count=h*w*4, offset=8).astype(np.float32).reshape([h, w, 4]) else: img = read_image_imageio(file) if img.shape[2] == 4: img[...,0:3] = srgb_to_linear(img[...,0:3]) # Premultiply alpha img[...,0:3] *= img[...,3:4] else: img = srgb_to_linear(img) return img def write_image(file, img, quality=95): if os.path.splitext(file)[1] == ".bin": if img.shape[2] < 4: img = np.dstack((img, np.ones([img.shape[0], img.shape[1], 4 - img.shape[2]]))) with open(file, "wb") as f: f.write(struct.pack("ii", img.shape[0], img.shape[1])) f.write(img.astype(np.float16).tobytes()) else: if img.shape[2] == 4: img = np.copy(img) # Unmultiply alpha img[...,0:3] = np.divide(img[...,0:3], img[...,3:4], out=np.zeros_like(img[...,0:3]), where=img[...,3:4] != 0) img[...,0:3] = linear_to_srgb(img[...,0:3]) else: img = linear_to_srgb(img) write_image_imageio(file, img, quality) def trim(error, skip=0.000001): error = np.sort(error.flatten()) size = error.size skip = int(skip * size) return error[skip:size-skip].mean() def luminance(a): a = np.maximum(0, a)**0.4545454545 return 0.2126 * a[:,:,0] + 0.7152 * a[:,:,1] + 0.0722 * a[:,:,2] def L1(img, ref): return np.abs(img - ref) def APE(img, ref): return L1(img, ref) / (1e-2 + ref) def SAPE(img, ref): return L1(img, ref) / (1e-2 + (ref + img) / 2.) def L2(img, ref): return (img - ref)**2 def RSE(img, ref): return L2(img, ref) / (1e-2 + ref**2) def rgb_mean(img): return np.mean(img, axis=2) def compute_error_img(metric, img, ref): img[np.logical_not(np.isfinite(img))] = 0 img = np.maximum(img, 0.) if metric == "MAE": return L1(img, ref) elif metric == "MAPE": return APE(img, ref) elif metric == "SMAPE": return SAPE(img, ref) elif metric == "MSE": return L2(img, ref) elif metric == "MScE": return L2(np.clip(img, 0.0, 1.0), np.clip(ref, 0.0, 1.0)) elif metric == "MRSE": return RSE(img, ref) elif metric == "MtRSE": return trim(RSE(img, ref)) elif metric == "MRScE": return RSE(np.clip(img, 0, 100), np.clip(ref, 0, 100)) raise ValueError(f"Unknown metric: {metric}.") def compute_error(metric, img, ref): metric_map = compute_error_img(metric, img, ref) metric_map[np.logical_not(np.isfinite(metric_map))] = 0 if len(metric_map.shape) == 3: metric_map = np.mean(metric_map, axis=2) mean = np.mean(metric_map) return mean