Source code for pyvisim.dense.pixelwise.psnr
"""Peak signal-to-noise ratio (PSNR) similarity metric."""
from __future__ import annotations
import numpy as np
from ...base import CANONICAL_DATA_RANGE, DenseMetricBase
from ...typing import Float64NumpyArray, UInt8NumpyArray
from ...utils.cython_utils import get_kernel_threads
from ._kernel._ssd_kernel import ssd_matrix
__all__ = ["PSNR"]
def _mean_squared_error(
batch1: UInt8NumpyArray, batch2: UInt8NumpyArray
) -> Float64NumpyArray:
"""
Compute the pairwise mean squared error matrix between two image batches.
:return: The ``(N, M)`` matrix of mean squared pixel errors.
"""
flat1 = np.ascontiguousarray(batch1.reshape(len(batch1), -1))
flat2 = np.ascontiguousarray(batch2.reshape(len(batch2), -1))
totals = ssd_matrix(flat1, flat2, get_kernel_threads())
error: Float64NumpyArray = totals / flat1.shape[1]
return error
def _peak_signal_noise_ratio(
batch1: UInt8NumpyArray, batch2: UInt8NumpyArray
) -> Float64NumpyArray:
"""
Compute the pairwise PSNR matrix in decibels between two image batches.
"""
error = _mean_squared_error(batch1, batch2)
with np.errstate(divide="ignore"):
return np.asarray(
10.0 * np.log10(CANONICAL_DATA_RANGE**2 / error), dtype=np.float64
)
[docs]
class PSNR(DenseMetricBase):
"""
Peak signal-to-noise ratio between two batches of images.
The metric is computed pairwise: for ``N`` images in the first batch and
``M`` images in the second batch, :meth:`similarity_score` returns an
``(N, M)`` matrix of PSNR values in decibels, where identical pairs yield
``inf``. Every compared pair must share the same ``(H, W[, C])`` shape.
The squared differences are summed by a compiled OpenMP kernel. Set the
``PYVISIM_NUM_THREADS`` environment variable to override the team size.
For more information, see the documentation:
``file:///home/critter_cool_laptop/workspace/Python-Visual-Similarity-parallel/docs/_build/html/pixelwise/psnr/psnr.html``.
:param batch_size: Maximum number of images processed in a single batch.
Set to ``-1`` to process all images as a single batch.
:raises ValueError: If ``batch_size`` is neither ``-1`` nor a positive
integer.
Example:
>>> import numpy as np
>>> from pyvisim.dense.pixelwise import PSNR
>>> image = np.random.default_rng(0).integers(0, 256, (32, 32, 3), dtype=np.uint8)
>>> PSNR().similarity_score(image, image)
array([[inf]])
"""
def _score_batches(
self, batch1: UInt8NumpyArray, batch2: UInt8NumpyArray
) -> Float64NumpyArray:
"""
Score both batches block by block, one kernel call per pair of blocks.
Each batch is cut into blocks of at most :attr:`batch_size` images.
:param batch1: ``(N, H, W, C)`` ``uint8`` batch.
:param batch2: ``(M, H, W, C)`` ``uint8`` batch of the same image shape.
:return: An ``(N, M)`` matrix of PSNR values in decibels.
"""
scores: Float64NumpyArray = np.empty(
(len(batch1), len(batch2)), dtype=np.float64
)
whole_input = self._batch_size == -1
step1 = len(batch1) if whole_input else self._batch_size
step2 = len(batch2) if whole_input else self._batch_size
for start1 in range(0, len(batch1), step1):
chunk1 = batch1[start1 : start1 + step1]
for start2 in range(0, len(batch2), step2):
chunk2 = batch2[start2 : start2 + step2]
scores[start1 : start1 + step1, start2 : start2 + step2] = (
_peak_signal_noise_ratio(chunk1, chunk2)
)
return scores