uw_nriqa / tests /test_tensor_api.py
lizhi
add tensor metrics and local nuiq benchmark
e2bd3b8
Raw
History Blame Contribute Delete
6.32 kB
from __future__ import annotations
import pytest
def torch_or_skip():
return pytest.importorskip("torch")
def test_tensor_to_pil_images_quantizes_rgb_range():
torch = torch_or_skip()
from uw_nriqa.tensor import tensor_to_pil_images
images = torch.tensor([[[[0.0, 0.5], [1.0, 2.0]], [[-1.0, 0.25], [0.75, 1.0]], [[0.1, 0.2], [0.3, 0.4]]]])
pil = tensor_to_pil_images(images)
assert len(pil) == 1
assert pil[0].mode == "RGB"
assert pil[0].size == (2, 2)
assert pil[0].getpixel((0, 0)) == (0, 0, 26)
assert pil[0].getpixel((1, 0)) == (128, 64, 51)
assert pil[0].getpixel((0, 1)) == (255, 191, 77)
assert pil[0].getpixel((1, 1)) == (255, 255, 102)
def test_tensor_to_pil_images_rejects_non_bchw_rgb():
torch = torch_or_skip()
from uw_nriqa.tensor import tensor_to_pil_images
with pytest.raises(ValueError, match="BCHW RGB"):
tensor_to_pil_images(torch.zeros(1, 1, 8, 8))
def test_tensor_direct_rejects_unsupported_methods():
torch = torch_or_skip()
from uw_nriqa.tensor import score_tensor
with pytest.raises(ValueError, match="unsupported"):
score_tensor(torch.zeros(1, 3, 8, 8), methods=["tuda"], preload=False)
with pytest.raises(ValueError, match="rsuia"):
score_tensor(torch.zeros(1, 3, 8, 8), methods=["rsuia"], preload=False)
def test_tensor_direct_traditional_metrics_do_not_require_pil(monkeypatch):
torch = torch_or_skip()
from uw_nriqa.tensor import score_tensor
def fail_pil(_images):
raise AssertionError("PIL conversion should not be used for tensor metrics")
monkeypatch.setattr("uw_nriqa.tensor.tensor_to_pil_images", fail_pil)
images = torch.rand(4, 3, 64, 64)
scores = score_tensor(images, methods=["uciqe", "uiqm"], preload=False)
assert scores.shape == (4, 2)
assert torch.isfinite(scores).all()
def test_tensor_uciqe_tracks_legacy_metric():
torch = torch_or_skip()
from uw_nriqa.metrics import calculate_uciqe
from uw_nriqa.tensor import score_tensor, tensor_to_pil_images
images = torch.rand(3, 3, 64, 64)
tensor_scores = score_tensor(images, methods=["uciqe"], preload=False)[:, 0]
legacy_scores = torch.tensor(
[calculate_uciqe(pil) for pil in tensor_to_pil_images(images)],
dtype=torch.float32,
)
assert torch.allclose(tensor_scores, legacy_scores, atol=0.02)
def test_tensor_uiqm_tracks_legacy_metric():
torch = torch_or_skip()
import numpy as np
from uw_nriqa.metrics import calculate_uiqm
from uw_nriqa.tensor import score_tensor, tensor_to_pil_images
rng = np.random.default_rng(20260602)
arrays = rng.integers(0, 256, size=(4, 72, 96, 3), dtype=np.uint8)
images = torch.from_numpy(arrays.copy()).permute(0, 3, 1, 2).to(dtype=torch.float32) / 255.0
tensor_scores = score_tensor(images, methods=["uiqm"], preload=False)[:, 0]
legacy_scores = torch.tensor(
[calculate_uiqm(pil) for pil in tensor_to_pil_images(images)],
dtype=torch.float32,
)
assert torch.allclose(tensor_scores, legacy_scores, atol=1e-4, rtol=1e-4)
def test_tensor_niqe_tracks_legacy_metric():
pytest.importorskip("pyiqa")
torch = torch_or_skip()
import numpy as np
from uw_nriqa.metrics import calculate_niqe
from uw_nriqa.tensor import score_tensor, tensor_to_pil_images
rng = np.random.default_rng(20260602)
arrays = rng.integers(0, 256, size=(3, 256, 256, 3), dtype=np.uint8)
images = torch.from_numpy(arrays.copy()).permute(0, 3, 1, 2).to(dtype=torch.float32) / 255.0
tensor_scores = score_tensor(images, methods=["niqe"], preload=False)[:, 0]
legacy_scores = torch.tensor(
[calculate_niqe(pil) for pil in tensor_to_pil_images(images)],
dtype=torch.float32,
)
assert torch.allclose(tensor_scores, legacy_scores, atol=1e-4, rtol=1e-4)
def test_tensor_local_nuiq_tracks_image_batch_path():
torch = torch_or_skip()
import numpy as np
from PIL import Image
from uw_nriqa.nuiq import calculate_nuiq_local
from uw_nriqa.tensor import score_tensor
rng = np.random.default_rng(123)
arrays = rng.integers(0, 256, size=(4, 36, 44, 3), dtype=np.uint8)
images = [Image.fromarray(arr) for arr in arrays]
tensor = torch.from_numpy(arrays).permute(0, 3, 1, 2).to(dtype=torch.float32) / 255.0
image_scores = torch.tensor(calculate_nuiq_local(images), dtype=torch.float32)
tensor_scores = score_tensor(tensor, methods=["nuiq_local"], preload=False)[:, 0]
assert torch.all(torch.isfinite(tensor_scores))
assert torch.allclose(tensor_scores, image_scores, atol=1e-3)
def test_predict_tensor_uses_tensor_scorer(monkeypatch):
torch = torch_or_skip()
from uw_nriqa import NRIQAEvaluator
def fake_score_tensor_with_evaluator(evaluator, images):
assert evaluator.methods == ["uranker", "uciqe"]
assert tuple(images.shape) == (2, 3, 8, 8)
return torch.tensor([[1.0, 0.5], [2.0, 0.7]], dtype=torch.float32)
monkeypatch.setattr("uw_nriqa.tensor.score_tensor_with_evaluator", fake_score_tensor_with_evaluator)
evaluator = NRIQAEvaluator(["uranker", "uciqe"], preload=False)
scores = evaluator.predict_tensor(torch.zeros(2, 3, 8, 8), raise_errors=True)
assert scores == {"uranker": [1.0, 2.0], "uciqe": [0.5, pytest.approx(0.7)]}
def test_uranker_histogram_matches_legacy_histc():
torch = torch_or_skip()
from uw_nriqa.vendor.UnderwaterRanker import utils
images = torch.tensor(
[
[
[[0.0, 0.1, 0.5], [1.0, -0.1, 1.2]],
[[0.25, 0.25, 0.25], [0.75, 0.75, 0.75]],
[[0.0, 1.0, 0.5], [0.25, 0.75, 0.33]],
],
[
[[0.2, 0.4, 0.6], [0.8, 1.0, 0.0]],
[[0.9, 0.7, 0.5], [0.3, 0.1, 0.0]],
[[1.1, -0.2, 0.4], [0.4, 0.4, 0.4]],
],
],
dtype=torch.float32,
)
expected = []
for image in images:
channels = [torch.histc(channel, 64, min=0.0, max=1.0) for channel in image]
expected.append(torch.cat(channels))
expected = torch.stack(expected, dim=0).unsqueeze(1)
actual = utils.build_historgram(images)
assert actual.shape == (2, 1, 192)
assert torch.equal(actual, expected)