/
simple_metrics.py
150 lines (118 loc) · 4.46 KB
/
simple_metrics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from __future__ import division
import numpy as np
from ..util.dtype import dtype_range
from .._shared.utils import skimage_deprecation, warn
__all__ = ['compare_mse',
'compare_nrmse',
'compare_psnr',
]
def _assert_compatible(im1, im2):
"""Raise an error if the shape and dtype do not match."""
if not im1.shape == im2.shape:
raise ValueError('Input images must have the same dimensions.')
return
def _as_floats(im1, im2):
"""Promote im1, im2 to nearest appropriate floating point precision."""
float_type = np.result_type(im1.dtype, im2.dtype, np.float32)
im1 = np.asarray(im1, dtype=float_type)
im2 = np.asarray(im2, dtype=float_type)
return im1, im2
def compare_mse(im1, im2):
"""Compute the mean-squared error between two images.
Parameters
----------
im1, im2 : ndarray
Image. Any dimensionality.
Returns
-------
mse : float
The mean-squared error (MSE) metric.
"""
_assert_compatible(im1, im2)
im1, im2 = _as_floats(im1, im2)
return np.mean(np.square(im1 - im2), dtype=np.float64)
def compare_nrmse(im_true, im_test, norm_type='Euclidean'):
"""Compute the normalized root mean-squared error (NRMSE) between two
images.
Parameters
----------
im_true : ndarray
Ground-truth image.
im_test : ndarray
Test image.
norm_type : {'Euclidean', 'min-max', 'mean'}
Controls the normalization method to use in the denominator of the
NRMSE. There is no standard method of normalization across the
literature [1]_. The methods available here are as follows:
- 'Euclidean' : normalize by the averaged Euclidean norm of
``im_true``::
NRMSE = RMSE * sqrt(N) / || im_true ||
where || . || denotes the Frobenius norm and ``N = im_true.size``.
This result is equivalent to::
NRMSE = || im_true - im_test || / || im_true ||.
- 'min-max' : normalize by the intensity range of ``im_true``.
- 'mean' : normalize by the mean of ``im_true``
Returns
-------
nrmse : float
The NRMSE metric.
References
----------
.. [1] https://en.wikipedia.org/wiki/Root-mean-square_deviation
"""
_assert_compatible(im_true, im_test)
im_true, im_test = _as_floats(im_true, im_test)
norm_type = norm_type.lower()
if norm_type == 'euclidean':
denom = np.sqrt(np.mean((im_true*im_true), dtype=np.float64))
elif norm_type == 'min-max':
denom = im_true.max() - im_true.min()
elif norm_type == 'mean':
denom = im_true.mean()
else:
raise ValueError("Unsupported norm_type")
return np.sqrt(compare_mse(im_true, im_test)) / denom
def compare_psnr(im_true, im_test, data_range=None, dynamic_range=None):
""" Compute the peak signal to noise ratio (PSNR) for an image.
Parameters
----------
im_true : ndarray
Ground-truth image.
im_test : ndarray
Test image.
data_range : int
The data range of the input image (distance between minimum and
maximum possible values). By default, this is estimated from the image
data-type.
Returns
-------
psnr : float
The PSNR metric.
References
----------
.. [1] https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
"""
_assert_compatible(im_true, im_test)
if dynamic_range is not None:
warn('`dynamic_range` has been deprecated in favor of '
'`data_range`. The `dynamic_range` keyword argument '
'will be removed in v0.14', skimage_deprecation)
data_range = dynamic_range
if data_range is None:
if im_true.dtype != im_test.dtype:
warn("Inputs have mismatched dtype. Setting data_range based on "
"im_true.")
dmin, dmax = dtype_range[im_true.dtype.type]
true_min, true_max = np.min(im_true), np.max(im_true)
if true_max > dmax or true_min < dmin:
raise ValueError(
"im_true has intensity values outside the range expected for "
"its data type. Please manually specify the data_range")
if true_min >= 0:
# most common case (255 for uint8, 1 for float)
data_range = dmax
else:
data_range = dmax - dmin
im_true, im_test = _as_floats(im_true, im_test)
err = compare_mse(im_true, im_test)
return 10 * np.log10((data_range ** 2) / err)