From c465fe695a1949a0b83ea1457ca120aac0909986 Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Thu, 22 Aug 2024 18:54:25 +0200 Subject: [PATCH 01/10] refac: made metrics `torch` compatible + added docs + added `RunningPSNR` * updated tests to reflect changes --- src/careamics/utils/metrics.py | 112 ++++++++++++++++++++++++++------- tests/utils/test_metrics.py | 13 +++- 2 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/careamics/utils/metrics.py b/src/careamics/utils/metrics.py index 2389316d4..6196fa857 100644 --- a/src/careamics/utils/metrics.py +++ b/src/careamics/utils/metrics.py @@ -10,8 +10,10 @@ import torch from skimage.metrics import peak_signal_noise_ratio +Array = Union[np.ndarray, torch.Tensor] -def psnr(gt: np.ndarray, pred: np.ndarray, range: float = 255.0) -> float: + +def psnr(gt: Array, pred: Array, range: float = 255.0) -> float: """ Peak Signal to Noise Ratio. @@ -20,9 +22,9 @@ def psnr(gt: np.ndarray, pred: np.ndarray, range: float = 255.0) -> float: Parameters ---------- - gt : NumPy array + gt : Array Ground truth image. - pred : NumPy array + pred : Array Predicted image. range : float, optional The images pixel range, by default 255.0. @@ -35,74 +37,72 @@ def psnr(gt: np.ndarray, pred: np.ndarray, range: float = 255.0) -> float: return peak_signal_noise_ratio(gt, pred, data_range=range) -def _zero_mean(x: np.ndarray) -> np.ndarray: +def _zero_mean(x: Array) -> Array: """ Zero the mean of an array. Parameters ---------- - x : NumPy array + x : Array Input array. Returns ------- - NumPy array + Array Zero-mean array. """ - return x - np.mean(x) + return x - x.mean() -def _fix_range(gt: np.ndarray, x: np.ndarray) -> np.ndarray: +def _fix_range(gt: Array, x: Array) -> Array: """ Adjust the range of an array based on a reference ground-truth array. Parameters ---------- - gt : np.ndarray + gt : Array Ground truth image. - x : np.ndarray + x : Array Input array. Returns ------- - np.ndarray + Array Range-adjusted array. """ - a = np.sum(gt * x) / (np.sum(x * x)) + a = (gt * x).sum() / (x * x).sum() return x * a -def _fix(gt: np.ndarray, x: np.ndarray) -> np.ndarray: +def _fix(gt: Array, x: Array) -> Array: """ Zero mean a groud truth array and adjust the range of the array. Parameters ---------- - gt : np.ndarray + gt : Array Ground truth image. - x : np.ndarray + x : Array Input array. Returns ------- - np.ndarray + Array Zero-mean and range-adjusted array. """ gt_ = _zero_mean(gt) return _fix_range(gt_, _zero_mean(x)) -def scale_invariant_psnr( - gt: np.ndarray, pred: np.ndarray -) -> Union[float, torch.tensor]: +def scale_invariant_psnr(gt: Array, pred: Array) -> Union[float, torch.tensor]: """ Scale invariant PSNR. Parameters ---------- - gt : np.ndarray + gt : Array Ground truth image. - pred : np.ndarray + pred : Array Predicted image. Returns @@ -110,6 +110,72 @@ def scale_invariant_psnr( Union[float, torch.tensor] Scale invariant PSNR value. """ - range_parameter = (np.max(gt) - np.min(gt)) / np.std(gt) - gt_ = _zero_mean(gt) / np.std(gt) + range_parameter = (gt.max() - gt.min()) / gt.std() + gt_ = _zero_mean(gt) / gt.std() return psnr(_zero_mean(gt_), _fix(gt_, pred), range_parameter) + + +class RunningPSNR: + """Compute the running PSNR during validation step in training. + + This class allows to compute the PSNR on the entire validation set + one batch at the time. + + Attributes + ---------- + N : int + Number of elements seen so far during the epoch. + mse_sum : float + Running sum of the MSE over the N elements seen so far. + max : float + Running max value of the N target images seen so far. + min : float + Running min value of the N target images seen so far. + """ + + def __init__(self): + self.N = None + self.mse_sum = None + self.max = self.min = None + self.reset() + + def reset(self): + """Reset the running PSNR computation. + + Usually called at the end of each epoch. + """ + self.mse_sum = 0 + self.N = 0 + self.max = self.min = None + + def update(self, rec: torch.Tensor, tar: torch.Tensor) -> None: + """Update the running PSNR statistics given a new batch. + + Parameters + ---------- + rec: torch.Tensor + Batch of reconstructed images (B, C, H, W). + tar: torch.Tensor + Batch of target images (B, C, H, W). + """ + ins_max = torch.max(tar).item() + ins_min = torch.min(tar).item() + if self.max is None: + assert self.min is None + self.max = ins_max + self.min = ins_min + else: + self.max = max(self.max, ins_max) + self.min = min(self.min, ins_min) + + mse = (rec - tar) ** 2 + elementwise_mse = torch.mean(mse.view(len(mse), -1), dim=1) + self.mse_sum += torch.nansum(elementwise_mse) + self.N += len(elementwise_mse) - torch.sum(torch.isnan(elementwise_mse)) + + def get(self): + """Get the actual PSNR value given the running statistics.""" + if self.N == 0 or self.N is None: + return None + rmse = torch.sqrt(self.mse_sum / self.N) + return 20 * torch.log10((self.max - self.min) / rmse) diff --git a/tests/utils/test_metrics.py b/tests/utils/test_metrics.py index b91e66909..4fc83d9b1 100644 --- a/tests/utils/test_metrics.py +++ b/tests/utils/test_metrics.py @@ -1,5 +1,6 @@ import numpy as np import pytest +import torch from careamics.utils.metrics import ( _zero_mean, @@ -10,9 +11,10 @@ @pytest.mark.parametrize( "x", [ - 5.6, np.array([1, 2, 3, 4, 5]), np.array([[1, 2, 3], [4, 5, 6]]), + torch.tensor([1, 2, 3, 4, 5]), + torch.tensor([[1, 2, 3], [4, 5, 6]]), ], ) def test_zero_mean(x): @@ -24,7 +26,16 @@ def test_zero_mean(x): [ (np.array([1, 2, 3, 4, 5, 6]), np.array([1, 2, 3, 4, 5, 6]), 332.22), (np.array([[1, 2, 3], [4, 5, 6]]), np.array([[1, 2, 3], [4, 5, 6]]), 332.22), + (torch.tensor([1, 2, 3, 4, 5, 6]), torch.tensor([1, 2, 3, 4, 5, 6]), 332.22), + ( + torch.tensor([[1, 2, 3], [4, 5, 6]]), + torch.tensor([[1, 2, 3], [4, 5, 6]]), + 332.22, + ), ], ) def test_scale_invariant_psnr(gt, pred, result): assert scale_invariant_psnr(gt, pred) == pytest.approx(result, rel=5e-3) + + +# TODO: add tests for RunningPSNR From 09b2f5fdf5c7a50b6fdd4b3af9cf70372a9c03d2 Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Thu, 22 Aug 2024 19:24:16 +0200 Subject: [PATCH 02/10] fix: fixed bug -> torch does not support mean() for non-float dtypes --- src/careamics/utils/metrics.py | 13 ++++++++++++- tests/utils/test_metrics.py | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/careamics/utils/metrics.py b/src/careamics/utils/metrics.py index 6196fa857..9dddd2e02 100644 --- a/src/careamics/utils/metrics.py +++ b/src/careamics/utils/metrics.py @@ -4,6 +4,10 @@ This module contains various metrics and a metrics tracking class. """ +# NOTE: this doesn't work with torch tensors, since `torch` refuses to +# compute the `mean()` or `std()` of a tensor whose dtype is not float. + + from typing import Union import numpy as np @@ -51,7 +55,14 @@ def _zero_mean(x: Array) -> Array: Array Zero-mean array. """ - return x - x.mean() + type_ = type(x) + dtype_ = x.dtype + x = np.asarray(x) + res = x - x.mean() + if type_ == torch.Tensor: + return torch.tensor(res, dtype=dtype_) + elif type_ == np.ndarray: + return res def _fix_range(gt: Array, x: Array) -> Array: diff --git a/tests/utils/test_metrics.py b/tests/utils/test_metrics.py index 4fc83d9b1..9157402e2 100644 --- a/tests/utils/test_metrics.py +++ b/tests/utils/test_metrics.py @@ -18,6 +18,7 @@ ], ) def test_zero_mean(x): + x = np.asarray(x) assert np.allclose(_zero_mean(x), x - np.mean(x)) From 0d0d3da7e5cb0e860f9d5f980d245b7aa8f58465 Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Thu, 22 Aug 2024 19:42:15 +0200 Subject: [PATCH 03/10] wip --- src/careamics/utils/metrics.py | 41 ++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/careamics/utils/metrics.py b/src/careamics/utils/metrics.py index 9dddd2e02..0b7a4ff07 100644 --- a/src/careamics/utils/metrics.py +++ b/src/careamics/utils/metrics.py @@ -7,7 +7,7 @@ # NOTE: this doesn't work with torch tensors, since `torch` refuses to # compute the `mean()` or `std()` of a tensor whose dtype is not float. - +from warnings import warn from typing import Union import numpy as np @@ -45,6 +45,10 @@ def _zero_mean(x: Array) -> Array: """ Zero the mean of an array. + NOTE: `torch` does not support the `mean()` method for tensors whose + `dtype` is not `float`. Hence, this function will raise a warning and + automatically cast the input tensor to `float` if it is a `torch.Tensor`. + Parameters ---------- x : Array @@ -55,14 +59,8 @@ def _zero_mean(x: Array) -> Array: Array Zero-mean array. """ - type_ = type(x) - dtype_ = x.dtype - x = np.asarray(x) - res = x - x.mean() - if type_ == torch.Tensor: - return torch.tensor(res, dtype=dtype_) - elif type_ == np.ndarray: - return res + x = cast_torch(x) + return x - x.mean() def _fix_range(gt: Array, x: Array) -> Array: @@ -108,6 +106,10 @@ def _fix(gt: Array, x: Array) -> Array: def scale_invariant_psnr(gt: Array, pred: Array) -> Union[float, torch.tensor]: """ Scale invariant PSNR. + + NOTE: `torch` does not support the `mean()` method for tensors whose + `dtype` is not `float`. Hence, this function will raise a warning and + automatically cast the input tensor to `float` if it is a `torch.Tensor`. Parameters ---------- @@ -121,11 +123,32 @@ def scale_invariant_psnr(gt: Array, pred: Array) -> Union[float, torch.tensor]: Union[float, torch.tensor] Scale invariant PSNR value. """ + gt = cast_torch(gt) range_parameter = (gt.max() - gt.min()) / gt.std() gt_ = _zero_mean(gt) / gt.std() return psnr(_zero_mean(gt_), _fix(gt_, pred), range_parameter) +def cast_torch(x: Array) -> Array: + """ + Cast a tensor to float. + + Parameters + ---------- + x : Array + Input tensor. + + Returns + ------- + Array + Float tensor. + """ + if isinstance(x, torch.Tensor) and x.dtype != torch.float: + warn(f"Casting tensor of type {x.dtype} to float.", UserWarning) + return x.float() + return x + + class RunningPSNR: """Compute the running PSNR during validation step in training. From 8f700a821431bd335957aec596cfee20bbe34d67 Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Fri, 23 Aug 2024 10:48:27 +0200 Subject: [PATCH 04/10] fix: fix bugs relative to casting torch to double --- src/careamics/check_metrics.ipynb | 159 ++++++++++++++++++++++++++++++ src/careamics/utils/metrics.py | 25 +++-- 2 files changed, 177 insertions(+), 7 deletions(-) create mode 100644 src/careamics/check_metrics.ipynb diff --git a/src/careamics/check_metrics.ipynb b/src/careamics/check_metrics.ipynb new file mode 100644 index 000000000..91a14ca69 --- /dev/null +++ b/src/careamics/check_metrics.ipynb @@ -0,0 +1,159 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import numpy as np\n", + "from skimage.metrics import peak_signal_noise_ratio as psnr\n", + "\n", + "from careamics.utils.metrics import _zero_mean, scale_invariant_psnr, torch_cast_to_float, _fix, _fix_range" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "inf" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x, y = np.array([1, 2, 3, 4, 5, 6]), np.array([1, 2, 3, 4, 5, 6])\n", + "# x, y = torch.tensor([1, 2, 3, 4, 5, 6]), torch.tensor([1, 2, 3, 4, 5, 6])\n", + "# x, y = torch_cast_to_float(x), torch_cast_to_float(y)\n", + "\n", + "# scale_invariant_psnr(x, y)\n", + "psnr(np.asarray(x), np.asarray(y), data_range=5.)" + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2.9277002188455996\n", + "[-1.46385011 -0.87831007 -0.29277002 0.29277002 0.87831007 1.46385011]\n", + "[-2.5 -1.5 -0.5 0.5 1.5 2.5]\n", + "[-1.46385011 -0.87831007 -0.29277002 0.29277002 0.87831007 1.46385011]\n", + "0.5855400437691199\n", + "[-1.46385011 -0.87831007 -0.29277002 0.29277002 0.87831007 1.46385011]\n", + "332.22443992463\n", + "inf\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/localscratch/miniforge3/envs/train_lvae/lib/python3.9/site-packages/skimage/metrics/simple_metrics.py:163: RuntimeWarning: divide by zero encountered in scalar divide\n", + " return 10 * np.log10((data_range ** 2) / err)\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 51, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "range_ = (x.max() - x.min()) / x.std()\n", + "print(range_)\n", + "x_ = _zero_mean(x) / x.std()\n", + "print(x_)\n", + "# fix\n", + "y_ = _zero_mean(y)\n", + "print(y_)\n", + "x__ = _zero_mean(x_)\n", + "print(x__)\n", + "# fix range\n", + "a = (x__ * y_).sum() / (y_**2).sum()\n", + "print(a)\n", + "y__ = y_ * a\n", + "print(y__)\n", + "print(psnr(np.asarray(x__), np.asarray(y__), data_range=range_))\n", + "# psnr(\n", + "# np.asarray([-1.3363, -0.8018, -0.2673, 0.2673, 0.8018, 1.3363]),\n", + "# np.asarray([-1.3363, -0.8018, -0.2673, 0.2673, 0.8018, 1.3363]),\n", + "# data_range=2.6726\n", + "# )\n", + "print(psnr(\n", + " np.asarray([-1.46385011, -0.87831007, -0.29277002, 0.29277002, 0.87831007, 1.46385011]),\n", + " np.asarray([-1.46385011, -0.87831007, -0.29277002, 0.29277002, 0.87831007, 1.46385011]),\n", + " data_range=2.9277\n", + "))\n", + "np.allclose(np.asarray(x__), np.asarray([-1.46385011, -0.87831007, -0.29277002, 0.29277002, 0.87831007, 1.46385011]))" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/federico.carrara/Documents/projects/spectral_careamics/src/careamics/utils/metrics.py:158: UserWarning: Casting tensor of type torch.float32 to double (`torch.float64`).\n", + " \n" + ] + }, + { + "data": { + "text/plain": [ + "tensor([1., 2., 3., 4., 5., 6.], dtype=torch.float64)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "x = torch.tensor([1, 2, 3, 4, 5, 6], dtype=torch.float32)\n", + "torch_cast_to_float(x)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "train_lvae", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.19" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/careamics/utils/metrics.py b/src/careamics/utils/metrics.py index 0b7a4ff07..36654a111 100644 --- a/src/careamics/utils/metrics.py +++ b/src/careamics/utils/metrics.py @@ -38,7 +38,11 @@ def psnr(gt: Array, pred: Array, range: float = 255.0) -> float: float PSNR value. """ - return peak_signal_noise_ratio(gt, pred, data_range=range) + return peak_signal_noise_ratio( + np.asarray(gt), + np.asarray(pred), + data_range=range, + ) def _zero_mean(x: Array) -> Array: @@ -59,7 +63,7 @@ def _zero_mean(x: Array) -> Array: Array Zero-mean array. """ - x = cast_torch(x) + x = _torch_cast_to_double(x) return x - x.mean() @@ -110,6 +114,10 @@ def scale_invariant_psnr(gt: Array, pred: Array) -> Union[float, torch.tensor]: NOTE: `torch` does not support the `mean()` method for tensors whose `dtype` is not `float`. Hence, this function will raise a warning and automatically cast the input tensor to `float` if it is a `torch.Tensor`. + + NOTE: results may vary slightly between `numpy` and `torch` due to the way + `var()` is computed. In `torch`, the unbiased estimator is used (i.e., SSE/n-1), + while in `numpy` the biased estimator is used (i.e., SSE/n). Parameters ---------- @@ -123,13 +131,16 @@ def scale_invariant_psnr(gt: Array, pred: Array) -> Union[float, torch.tensor]: Union[float, torch.tensor] Scale invariant PSNR value. """ - gt = cast_torch(gt) + # cast tensors to double dtype + gt = _torch_cast_to_double(gt) + pred = _torch_cast_to_double(pred) + # compute scale-invariant PSNR range_parameter = (gt.max() - gt.min()) / gt.std() gt_ = _zero_mean(gt) / gt.std() return psnr(_zero_mean(gt_), _fix(gt_, pred), range_parameter) -def cast_torch(x: Array) -> Array: +def _torch_cast_to_double(x: Array) -> Array: """ Cast a tensor to float. @@ -143,9 +154,9 @@ def cast_torch(x: Array) -> Array: Array Float tensor. """ - if isinstance(x, torch.Tensor) and x.dtype != torch.float: - warn(f"Casting tensor of type {x.dtype} to float.", UserWarning) - return x.float() + if isinstance(x, torch.Tensor) and x.dtype != torch.float64: + warn(f"Casting tensor of type `{x.dtype}` to double (`torch.float64`).", UserWarning) + return x.double() return x From 5ffc6bb58ccb305ce5e884b374aa0d358abda4a9 Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Fri, 23 Aug 2024 10:51:05 +0200 Subject: [PATCH 05/10] added NOTE to underline the strange behavior of 'psnr()' with numpy arrays --- tests/utils/test_metrics.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/utils/test_metrics.py b/tests/utils/test_metrics.py index 9157402e2..281d812bd 100644 --- a/tests/utils/test_metrics.py +++ b/tests/utils/test_metrics.py @@ -21,7 +21,9 @@ def test_zero_mean(x): x = np.asarray(x) assert np.allclose(_zero_mean(x), x - np.mean(x)) - +# NOTE: the behavior of the PSNR function for np.arrays is weird. Indeed, PSNR computed over +# identical vectors should be infinite, but the function returns a finite value. +# Using torch it gives instead `inf`. @pytest.mark.parametrize( "gt, pred, result", [ From ef0cf743e322519e5625b53f20b5f0ae51a92cc4 Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Fri, 23 Aug 2024 11:07:04 +0200 Subject: [PATCH 06/10] removed RunningPSNR class since not necessary for the PR --- src/careamics/utils/metrics.py | 68 +--------------------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/src/careamics/utils/metrics.py b/src/careamics/utils/metrics.py index 36654a111..e68a61ece 100644 --- a/src/careamics/utils/metrics.py +++ b/src/careamics/utils/metrics.py @@ -157,70 +157,4 @@ def _torch_cast_to_double(x: Array) -> Array: if isinstance(x, torch.Tensor) and x.dtype != torch.float64: warn(f"Casting tensor of type `{x.dtype}` to double (`torch.float64`).", UserWarning) return x.double() - return x - - -class RunningPSNR: - """Compute the running PSNR during validation step in training. - - This class allows to compute the PSNR on the entire validation set - one batch at the time. - - Attributes - ---------- - N : int - Number of elements seen so far during the epoch. - mse_sum : float - Running sum of the MSE over the N elements seen so far. - max : float - Running max value of the N target images seen so far. - min : float - Running min value of the N target images seen so far. - """ - - def __init__(self): - self.N = None - self.mse_sum = None - self.max = self.min = None - self.reset() - - def reset(self): - """Reset the running PSNR computation. - - Usually called at the end of each epoch. - """ - self.mse_sum = 0 - self.N = 0 - self.max = self.min = None - - def update(self, rec: torch.Tensor, tar: torch.Tensor) -> None: - """Update the running PSNR statistics given a new batch. - - Parameters - ---------- - rec: torch.Tensor - Batch of reconstructed images (B, C, H, W). - tar: torch.Tensor - Batch of target images (B, C, H, W). - """ - ins_max = torch.max(tar).item() - ins_min = torch.min(tar).item() - if self.max is None: - assert self.min is None - self.max = ins_max - self.min = ins_min - else: - self.max = max(self.max, ins_max) - self.min = min(self.min, ins_min) - - mse = (rec - tar) ** 2 - elementwise_mse = torch.mean(mse.view(len(mse), -1), dim=1) - self.mse_sum += torch.nansum(elementwise_mse) - self.N += len(elementwise_mse) - torch.sum(torch.isnan(elementwise_mse)) - - def get(self): - """Get the actual PSNR value given the running statistics.""" - if self.N == 0 or self.N is None: - return None - rmse = torch.sqrt(self.mse_sum / self.N) - return 20 * torch.log10((self.max - self.min) / rmse) + return x \ No newline at end of file From ccc0a09595bfefc7f5c346d524f6401da6fd301c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:18:19 +0000 Subject: [PATCH 07/10] style(pre-commit.ci): auto fixes [...] --- src/careamics/check_metrics.ipynb | 77 +++---------------------------- src/careamics/utils/metrics.py | 19 ++++---- tests/utils/test_metrics.py | 5 +- 3 files changed, 21 insertions(+), 80 deletions(-) diff --git a/src/careamics/check_metrics.ipynb b/src/careamics/check_metrics.ipynb index 91a14ca69..ac5df3c79 100644 --- a/src/careamics/check_metrics.ipynb +++ b/src/careamics/check_metrics.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -15,20 +15,9 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "inf" - ] - }, - "execution_count": 45, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "x, y = np.array([1, 2, 3, 4, 5, 6]), np.array([1, 2, 3, 4, 5, 6])\n", "# x, y = torch.tensor([1, 2, 3, 4, 5, 6]), torch.tensor([1, 2, 3, 4, 5, 6])\n", @@ -40,42 +29,9 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2.9277002188455996\n", - "[-1.46385011 -0.87831007 -0.29277002 0.29277002 0.87831007 1.46385011]\n", - "[-2.5 -1.5 -0.5 0.5 1.5 2.5]\n", - "[-1.46385011 -0.87831007 -0.29277002 0.29277002 0.87831007 1.46385011]\n", - "0.5855400437691199\n", - "[-1.46385011 -0.87831007 -0.29277002 0.29277002 0.87831007 1.46385011]\n", - "332.22443992463\n", - "inf\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/localscratch/miniforge3/envs/train_lvae/lib/python3.9/site-packages/skimage/metrics/simple_metrics.py:163: RuntimeWarning: divide by zero encountered in scalar divide\n", - " return 10 * np.log10((data_range ** 2) / err)\n" - ] - }, - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 51, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "range_ = (x.max() - x.min()) / x.std()\n", "print(range_)\n", @@ -107,28 +63,9 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/federico.carrara/Documents/projects/spectral_careamics/src/careamics/utils/metrics.py:158: UserWarning: Casting tensor of type torch.float32 to double (`torch.float64`).\n", - " \n" - ] - }, - { - "data": { - "text/plain": [ - "tensor([1., 2., 3., 4., 5., 6.], dtype=torch.float64)" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "x = torch.tensor([1, 2, 3, 4, 5, 6], dtype=torch.float32)\n", "torch_cast_to_float(x)" diff --git a/src/careamics/utils/metrics.py b/src/careamics/utils/metrics.py index e68a61ece..0887bb920 100644 --- a/src/careamics/utils/metrics.py +++ b/src/careamics/utils/metrics.py @@ -7,8 +7,8 @@ # NOTE: this doesn't work with torch tensors, since `torch` refuses to # compute the `mean()` or `std()` of a tensor whose dtype is not float. -from warnings import warn from typing import Union +from warnings import warn import numpy as np import torch @@ -39,8 +39,8 @@ def psnr(gt: Array, pred: Array, range: float = 255.0) -> float: PSNR value. """ return peak_signal_noise_ratio( - np.asarray(gt), - np.asarray(pred), + np.asarray(gt), + np.asarray(pred), data_range=range, ) @@ -52,7 +52,7 @@ def _zero_mean(x: Array) -> Array: NOTE: `torch` does not support the `mean()` method for tensors whose `dtype` is not `float`. Hence, this function will raise a warning and automatically cast the input tensor to `float` if it is a `torch.Tensor`. - + Parameters ---------- x : Array @@ -110,11 +110,11 @@ def _fix(gt: Array, x: Array) -> Array: def scale_invariant_psnr(gt: Array, pred: Array) -> Union[float, torch.tensor]: """ Scale invariant PSNR. - + NOTE: `torch` does not support the `mean()` method for tensors whose `dtype` is not `float`. Hence, this function will raise a warning and automatically cast the input tensor to `float` if it is a `torch.Tensor`. - + NOTE: results may vary slightly between `numpy` and `torch` due to the way `var()` is computed. In `torch`, the unbiased estimator is used (i.e., SSE/n-1), while in `numpy` the biased estimator is used (i.e., SSE/n). @@ -155,6 +155,9 @@ def _torch_cast_to_double(x: Array) -> Array: Float tensor. """ if isinstance(x, torch.Tensor) and x.dtype != torch.float64: - warn(f"Casting tensor of type `{x.dtype}` to double (`torch.float64`).", UserWarning) + warn( + f"Casting tensor of type `{x.dtype}` to double (`torch.float64`).", + UserWarning, + ) return x.double() - return x \ No newline at end of file + return x diff --git a/tests/utils/test_metrics.py b/tests/utils/test_metrics.py index 281d812bd..aea099783 100644 --- a/tests/utils/test_metrics.py +++ b/tests/utils/test_metrics.py @@ -21,9 +21,10 @@ def test_zero_mean(x): x = np.asarray(x) assert np.allclose(_zero_mean(x), x - np.mean(x)) + # NOTE: the behavior of the PSNR function for np.arrays is weird. Indeed, PSNR computed over -# identical vectors should be infinite, but the function returns a finite value. -# Using torch it gives instead `inf`. +# identical vectors should be infinite, but the function returns a finite value. +# Using torch it gives instead `inf`. @pytest.mark.parametrize( "gt, pred, result", [ From f5d17d72b4c14eab989a1c8a3108fbe7a374abb0 Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Fri, 23 Aug 2024 11:20:05 +0200 Subject: [PATCH 08/10] todo: added TODO about replacing skimage psnr function with explicit formula --- src/careamics/utils/metrics.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/careamics/utils/metrics.py b/src/careamics/utils/metrics.py index e68a61ece..04715dbda 100644 --- a/src/careamics/utils/metrics.py +++ b/src/careamics/utils/metrics.py @@ -38,6 +38,9 @@ def psnr(gt: Array, pred: Array, range: float = 255.0) -> float: float PSNR value. """ + # TODO: replace with explicit formula (?) it'd be a couple lines of code + # and won't impact performance. On the contrary it would make the code + # more explicit and easier to test. return peak_signal_noise_ratio( np.asarray(gt), np.asarray(pred), From e5cd38632d36a2654a693f3abb2a43a5a409602d Mon Sep 17 00:00:00 2001 From: federico-carrara Date: Fri, 23 Aug 2024 11:33:43 +0200 Subject: [PATCH 09/10] todo: added TODO to add tests for CUDA tensors --- tests/utils/test_metrics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils/test_metrics.py b/tests/utils/test_metrics.py index aea099783..6eb1ef9e0 100644 --- a/tests/utils/test_metrics.py +++ b/tests/utils/test_metrics.py @@ -7,6 +7,7 @@ scale_invariant_psnr, ) +# TODO: add tests for cudaTensors @pytest.mark.parametrize( "x", From 7f8293813a850d03ec46823f6ac65293c6251d55 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:34:05 +0000 Subject: [PATCH 10/10] style(pre-commit.ci): auto fixes [...] --- tests/utils/test_metrics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/utils/test_metrics.py b/tests/utils/test_metrics.py index 6eb1ef9e0..262baf9aa 100644 --- a/tests/utils/test_metrics.py +++ b/tests/utils/test_metrics.py @@ -9,6 +9,7 @@ # TODO: add tests for cudaTensors + @pytest.mark.parametrize( "x", [