# torchmetrics -- 100+ PyTorch metric implementations (classification, # regression, image, audio, text, retrieval, segmentation, ...) with a # distributed-aware Metric base class. # # WHY IT IS HERE: DeepInverse (imaging inverse problems, MRI/CT reconstruction) # requires it at import time and computes SSIM, PSNR and LPIPS through it. # python-lightning-utilities -> python-torchmetrics -> python-deepinv # # Class 1 under docs/PACKAGING-POLICY.md: nothing in Fedora provides # python3dist(torchmetrics). Verified 2026-10-11: # dnf repoquery --whatprovides 'python3dist(torchmetrics)' -> empty # Runtime deps: numpy, packaging, torch (Fedora proper) and lightning-utilities # (this COPR, same commit series). %global pypi_name torchmetrics Name: python-%{pypi_name} Version: 1.9.0 Release: 1%{?dist} Summary: Machine learning metrics for PyTorch # Apache-2.0: the package. # BSD-2-Clause: functional/image/lpips_models/{alex,squeeze,vgg}.pth, the LPIPS # linear-layer weights (6-11 kB each). They are byte-identical to # richzhang/PerceptualSimilarity lpips/weights/v0.1/*.pth (md5 of alex.pth # 9642209e2b57a85d20f86d812320f9e6 in both), whose LICENSE is BSD-2-Clause. # That is a Fedora-allowed licence, so these are kept: they are not the # per-user-download class of weights (RAIL-M, CC-BY-NC) that never ship here. # LPIPS still downloads its torchvision backbone at first use. License: Apache-2.0 AND BSD-2-Clause URL: https://github.com/Lightning-AI/torchmetrics Source0: %{pypi_source %{pypi_name}} BuildArch: noarch BuildRequires: python3-devel # %%check: the functional test compares against scikit-image's SSIM and PSNR. BuildRequires: python3dist(scikit-image) %global _description %{expand: TorchMetrics is a collection of more than 100 PyTorch metric implementations and an API for writing custom metrics. Metrics accumulate state across batches, synchronise it across processes in distributed training, and run on any device a tensor can live on. Domains covered include classification, regression, image quality (PSNR, SSIM, LPIPS, FID, ...), audio, text, retrieval, detection, segmentation and clustering.} %description %_description %package -n python3-%{pypi_name} Summary: %{summary} %description -n python3-%{pypi_name} %_description %prep %autosetup -p1 -n %{pypi_name}-%{version} # setup.py already drops the non-"# strict" upper bounds from # requirements/base.txt (torch <2.9.0, numpy <2.4.0 are CI pins, see the NOTE # at the top of that file), so the generated Requires carry floors only. %generate_buildrequires # Core only. The domain extras (audio, text, detection, multimodal, ...) pull in # libraries Fedora mostly lacks and are all imported lazily behind # RequirementCache checks. %pyproject_buildrequires %build %pyproject_wheel %install %pyproject_install %pyproject_save_files -l %{pypi_name} %check # Every installed module must import with core dependencies only; optional # backends are guarded upstream by RequirementCache. %pyproject_check_import # Functional: the image metrics DeepInverse uses, against an independent # implementation (scikit-image) and against hand-computed values. cat > %{_builddir}/tm-smoke.py <<'EOF' import numpy as np import torch import torchmetrics from torchmetrics.classification import MulticlassAccuracy from torchmetrics.functional.image import ( peak_signal_noise_ratio, structural_similarity_index_measure, ) from skimage.metrics import peak_signal_noise_ratio as sk_psnr from skimage.metrics import structural_similarity as sk_ssim print("torchmetrics", torchmetrics.__version__, "torch", torch.__version__) assert torchmetrics.__version__ == "%{version}", torchmetrics.__version__ rng = np.random.default_rng(0) ref = rng.random((64, 64)) noisy = np.clip(ref + 0.05 * rng.standard_normal(ref.shape), 0, 1) t_ref = torch.tensor(ref)[None, None] t_noisy = torch.tensor(noisy)[None, None] # PSNR: exact formula, and scikit-image. mse = np.mean((ref - noisy) ** 2) psnr_hand = 10 * np.log10(1.0 / mse) psnr = peak_signal_noise_ratio(t_noisy, t_ref, data_range=1.0).item() # 1e-4 dB: torchmetrics reduces in float32 (the first run differed from the # float64 closed form by 1.2e-6 dB, and a 1e-9 tolerance failed on that). assert abs(psnr - psnr_hand) < 1e-4, (psnr, psnr_hand) assert abs(psnr - sk_psnr(ref, noisy, data_range=1.0)) < 1e-4 print(f"PSNR OK: {psnr:.6f} dB (hand and scikit-image agree)") # SSIM: gaussian window sigma 1.5, as in scikit-image's gaussian_weights mode. ssim = structural_similarity_index_measure( t_noisy, t_ref, data_range=1.0, gaussian_kernel=True, sigma=1.5, kernel_size=11 ).item() ssim_sk = sk_ssim(ref, noisy, data_range=1.0, gaussian_weights=True, sigma=1.5, use_sample_covariance=False) assert 0.0 < ssim < 1.0, ssim assert abs(ssim - ssim_sk) < 1e-2, (ssim, ssim_sk) same = structural_similarity_index_measure(t_ref, t_ref, data_range=1.0).item() assert abs(same - 1.0) < 1e-9, same print(f"SSIM OK: {ssim:.6f} vs scikit-image {ssim_sk:.6f}; identical images give {same}") # Stateful Metric across batches, against a hand count: 3 of 4 then 2 of 2. acc = MulticlassAccuracy(num_classes=3, average="micro") acc.update(torch.tensor([0, 1, 2, 1]), torch.tensor([0, 1, 2, 2])) acc.update(torch.tensor([2, 0]), torch.tensor([2, 0])) value = acc.compute().item() assert abs(value - 5 / 6) < 1e-6, value print(f"Metric accumulation OK: {value} == 5/6") # The bundled LPIPS linear weights are present and loadable (no network). import os, torchmetrics.functional.image.lpips as lp d = os.path.join(os.path.dirname(lp.__file__), "lpips_models") for net in ("alex", "squeeze", "vgg"): sd = torch.load(os.path.join(d, f"{net}.pth"), map_location="cpu", weights_only=True) assert sd and all(k.startswith("lin") for k in sd), list(sd)[:3] print("LPIPS linear weights OK: alex, squeeze, vgg") print("ALL FUNCTIONAL CHECKS PASSED") EOF OMP_NUM_THREADS=1 PYTHONPATH=%{buildroot}%{python3_sitelib} \ %{python3} %{_builddir}/tm-smoke.py %files -n python3-%{pypi_name} -f %{pyproject_files} %doc README.md CHANGELOG.md CITATION.cff %changelog * Sun Oct 11 2026 Morgan Hough - 1.9.0-1 - Initial package, for DeepInverse (lightning-utilities -> torchmetrics -> deepinv). - Keeps the three LPIPS linear-layer weight files: byte-identical to PerceptualSimilarity's BSD-2-Clause originals, so License carries BSD-2-Clause. - %%check: import of every module, PSNR against the closed form and scikit-image, SSIM against scikit-image, a stateful Metric over two batches against a hand count, and the LPIPS weight files load offline.