From 7b0294f41b16a9ba566d260ac067e20593f2d373 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 08:34:49 -0400 Subject: [PATCH 1/7] Fix typos. --- docs/denoisers.rst | 4 ++-- src/patch_denoise/bindings/cli.py | 3 ++- src/patch_denoise/space_time/lowrank.py | 13 +++++++------ src/patch_denoise/space_time/utils.py | 24 ++++++++++++++++++++++-- tests/test_spacetime_utils.py | 4 ++-- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/docs/denoisers.rst b/docs/denoisers.rst index 6ac715a..4b94898 100644 --- a/docs/denoisers.rst +++ b/docs/denoisers.rst @@ -1,10 +1,10 @@ -LLR Denosing methods +LLR Denoising Methods ===================== Patch-denoise implemement several local-low-rank methods, based on singular values thresholding. -Singular Value thresholding +Singular Value Thresholding --------------------------- General Procedure diff --git a/src/patch_denoise/bindings/cli.py b/src/patch_denoise/bindings/cli.py index 7325041..1ae23ed 100644 --- a/src/patch_denoise/bindings/cli.py +++ b/src/patch_denoise/bindings/cli.py @@ -177,7 +177,8 @@ def main(): ]: extra_kwargs["noise_std"] = noise_map if noise_map is None: - raise RuntimeError("A noise map must me specified for this method.") + raise RuntimeError("A noise map must be specified for this method.") + denoised_data, patchs_weight, noise_std_map, rank_map = denoise_func( input_data, patch_shape=d_par.patch_shape, diff --git a/src/patch_denoise/space_time/lowrank.py b/src/patch_denoise/space_time/lowrank.py index 345f39e..51649f2 100644 --- a/src/patch_denoise/space_time/lowrank.py +++ b/src/patch_denoise/space_time/lowrank.py @@ -1,4 +1,5 @@ -"""Low Rank methods.""" +"""Low Rank methods.""" + from types import MappingProxyType import numpy as np @@ -9,7 +10,7 @@ from .utils import ( eig_analysis, eig_synthesis, - marshenko_pastur_median, + marchenko_pastur_median, svd_analysis, svd_synthesis, ) @@ -25,7 +26,7 @@ @fill_doc class MPPCADenoiser(BaseSpaceTimeDenoiser): - """Denoising using the MP-PCA threshoding. + """Denoising using Marchenko-Pastur principal components analysis (MP-PCA) thresholding. Parameters ---------- @@ -127,7 +128,7 @@ class RawSVDDenoiser(BaseSpaceTimeDenoiser): ---------- $patch_config threshold_vlue: float - treshold value for the singular values. + threshold value for the singular values. """ def __init__( @@ -204,7 +205,7 @@ def denoise( ): """Denoise using the NORDIC method. - Along with the input data a noise stp map or value should be provided. + Along with the input data a noise std map or value should be provided. Parameters ---------- @@ -356,7 +357,7 @@ def denoise( """ p_s, p_o = self._get_patch_param(input_data.shape) - self.input_denoising_kwargs["mp_median"] = marshenko_pastur_median( + self.input_denoising_kwargs["mp_median"] = marchenko_pastur_median( beta=input_data.shape[-1] / np.prod(p_s), eps=eps_marshenko_pastur, ) diff --git a/src/patch_denoise/space_time/utils.py b/src/patch_denoise/space_time/utils.py index 50a2e41..84daa8f 100644 --- a/src/patch_denoise/space_time/utils.py +++ b/src/patch_denoise/space_time/utils.py @@ -1,4 +1,5 @@ """Utilities for space-time denoising.""" + import numpy as np from scipy.integrate import quad from scipy.linalg import eigh, svd @@ -145,7 +146,7 @@ def eig_synthesis(data_centered, eig_vec, mean, max_val): return ((data_centered @ eig_vec) @ eig_vec.conj().T) + mean -def marshenko_pastur_median(beta, eps=1e-7): +def marchenko_pastur_median(beta, eps=1e-7): r"""Compute the median of the Marchenko-Pastur Distribution. Parameters @@ -204,19 +205,38 @@ def mp_pdf(x): def estimate_noise(noise_sequence, block_size=1): - """Estimate the temporal noise standard deviation of a noise only sequence.""" + """Estimate a noise map from a noise only sequence. + + The noise map is the standard deviation of the noise in each patch. + + Parameters + ---------- + noise_sequence : np.ndarray of shape (X, Y, Z, T) + The noise-only data. + block_size : int + The size of the patch used to estimate the noise. + + Returns + ------- + np.ndarray of shape (X, Y, Z) + The estimated noise map. + """ volume_shape = noise_sequence.shape[:-1] noise_map = np.empty(volume_shape) patch_shape = (block_size,) * len(volume_shape) patch_overlap = (block_size - 1,) * len(volume_shape) for patch_tl in get_patch_locs(patch_shape, patch_overlap, volume_shape): + # Get the index of voxels in the patch patch_slice = tuple( slice(ptl, ptl + ps) for ptl, ps in zip(patch_tl, patch_shape) ) + # Identify the voxel in the center of the patch patch_center_img = tuple( slice(ptl + ps // 2, ptl + ps // 2 + 1) for ptl, ps in zip(patch_tl, patch_shape) ) + # Set the value of the voxel in the center of the patch to the SD of + # the patch noise_map[patch_center_img] = np.std(noise_sequence[patch_slice]) return noise_map diff --git a/tests/test_spacetime_utils.py b/tests/test_spacetime_utils.py index 29ccfbe..4272fef 100644 --- a/tests/test_spacetime_utils.py +++ b/tests/test_spacetime_utils.py @@ -8,7 +8,7 @@ eig_analysis, eig_synthesis, estimate_noise, - marshenko_pastur_median, + marchenko_pastur_median, svd_analysis, svd_synthesis, ) @@ -55,7 +55,7 @@ def f(x): else: return 0 - integral_median = marshenko_pastur_median(beta, eps=1e-7) + integral_median = marchenko_pastur_median(beta, eps=1e-7) vals = np.linspace(beta_m, beta_p, n_samples) proba = np.array(list(map(f, vals))) From 43e64ce99ce498915bfcdf942efcc70396819084 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 08:44:37 -0400 Subject: [PATCH 2/7] Fix a handful of typos. --- src/patch_denoise/space_time/base.py | 3 ++- src/patch_denoise/space_time/lowrank.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/patch_denoise/space_time/base.py b/src/patch_denoise/space_time/base.py index e11a916..3d1163b 100644 --- a/src/patch_denoise/space_time/base.py +++ b/src/patch_denoise/space_time/base.py @@ -1,7 +1,8 @@ """Base Structure for patch-based denoising on spatio-temporal dimension.""" + import abc -from functools import partial, cached_property import logging + import numpy as np from tqdm.auto import tqdm diff --git a/src/patch_denoise/space_time/lowrank.py b/src/patch_denoise/space_time/lowrank.py index 51649f2..4614b0f 100644 --- a/src/patch_denoise/space_time/lowrank.py +++ b/src/patch_denoise/space_time/lowrank.py @@ -292,7 +292,7 @@ class OptimalSVDDenoiser(BaseSpaceTimeDenoiser): ---------- $patch_config loss: str - The loss determines the choise of the optimal thresholding function + The loss determines the choice of the optimal thresholding function associated to it. The losses `"fro"`, `"nuc"` and `"op"` are supported, for the frobenius, nuclear and operator norm, respectively. """ @@ -336,7 +336,7 @@ def denoise( $mask_config $noise_std loss: str - The loss for which the optimal thresholding is perform. + The loss for which the optimal thresholding is performed. eps_marshenko_pastur: float The precision with which the optimal threshold is computed. @@ -346,7 +346,7 @@ def denoise( Notes ----- - Reimplement of the original Matlab code [#]_ in python. + Reimplementation of the original Matlab code [#]_ in python. References ---------- From f60a7c2b06f9c8297366aacbaa9ec7fd9a0f5517 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 09:05:41 -0400 Subject: [PATCH 3/7] Replace ruff with ruff check --- .github/workflows/test-ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-ci.yml b/.github/workflows/test-ci.yml index bfbe6d8..a4f3f1f 100644 --- a/.github/workflows/test-ci.yml +++ b/.github/workflows/test-ci.yml @@ -11,8 +11,8 @@ on: jobs: linter-check: runs-on: ubuntu-latest - steps: - - name: Checkout + steps: + - name: Checkout uses: actions/checkout@v3 - name: Set up Python "3.10" uses: actions/setup-python@v4 @@ -26,8 +26,8 @@ jobs: run: black . --diff --color - name: ruff Check shell: bash - run: ruff src - + run: ruff check src + test-suite: runs-on: ${{ matrix.os }} needs: linter-check @@ -36,7 +36,7 @@ jobs: matrix: os: [ubuntu-latest] python-version: ["3.10", "3.8"] - + steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -51,5 +51,5 @@ jobs: python -m pip install .[test,optional] - name: Run Tests shell: bash - run: | + run: | pytest -n auto -x From df2355317ebd1b99bfc7fc8bc9ca7c436587206d Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 09:31:07 -0400 Subject: [PATCH 4/7] Address linter errors. --- pyproject.toml | 4 ++-- src/patch_denoise/space_time/base.py | 3 ++- src/patch_denoise/space_time/lowrank.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7546e31..98ea649 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,7 +52,6 @@ omit = ["*tests*"] precision = 2 exclude_lines = ["pragma: no cover", "raise NotImplementedError"] - # Formatting using black. [tool.black] @@ -61,10 +60,11 @@ exclude_lines = ["pragma: no cover", "raise NotImplementedError"] src = ["src", "tests"] select = ["E", "F", "B", "Q", "UP", "D"] +[tool.ruff.lint] ignore = ["B905"] exclude = ["examples/", "tests/"] -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention="numpy" [tool.isort] diff --git a/src/patch_denoise/space_time/base.py b/src/patch_denoise/space_time/base.py index 3d1163b..0910509 100644 --- a/src/patch_denoise/space_time/base.py +++ b/src/patch_denoise/space_time/base.py @@ -44,7 +44,8 @@ def __init__( if self._ps.size != dimensions or step.size != dimensions: raise ValueError( - "self._ps and step must have the same number of dimensions as the input self._array." + "self._ps and step must have the same number of dimensions as the " + "input self._array." ) # Ensure patch size is not larger than self._array size along each axis diff --git a/src/patch_denoise/space_time/lowrank.py b/src/patch_denoise/space_time/lowrank.py index 4614b0f..6e5ee3f 100644 --- a/src/patch_denoise/space_time/lowrank.py +++ b/src/patch_denoise/space_time/lowrank.py @@ -26,7 +26,7 @@ @fill_doc class MPPCADenoiser(BaseSpaceTimeDenoiser): - """Denoising using Marchenko-Pastur principal components analysis (MP-PCA) thresholding. + """Denoising using Marchenko-Pastur principal components analysis thresholding. Parameters ---------- From 916ecb39cb1f833a69044656d441baf6c49513e7 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 09:44:18 -0400 Subject: [PATCH 5/7] Reorder stuff a bit. --- pyproject.toml | 2 +- src/patch_denoise/bindings/utils.py | 5 ++++- tests/test_bindings.py | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 98ea649..d18bbde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,11 +58,11 @@ exclude_lines = ["pragma: no cover", "raise NotImplementedError"] #linting using ruff [tool.ruff] src = ["src", "tests"] -select = ["E", "F", "B", "Q", "UP", "D"] [tool.ruff.lint] ignore = ["B905"] exclude = ["examples/", "tests/"] +select = ["E", "F", "B", "Q", "UP", "D"] [tool.ruff.lint.pydocstyle] convention="numpy" diff --git a/src/patch_denoise/bindings/utils.py b/src/patch_denoise/bindings/utils.py index c303b6e..f5c9835 100644 --- a/src/patch_denoise/bindings/utils.py +++ b/src/patch_denoise/bindings/utils.py @@ -1,6 +1,9 @@ """Common utilities for bindings.""" -from dataclasses import dataclass + +from __future__ import annotations + import logging +from dataclasses import dataclass import numpy as np diff --git a/tests/test_bindings.py b/tests/test_bindings.py index b3bacbe..4695b9b 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -1,5 +1,7 @@ """Test for the binding module.""" + import os + import numpy as np import numpy.testing as npt import pytest @@ -16,7 +18,6 @@ except ImportError as e: NIPYPE_AVAILABLE = False - from patch_denoise.bindings.modopt import LLRDenoiserOperator from patch_denoise.bindings.nipype import PatchDenoise from patch_denoise.bindings.utils import DenoiseParameters From 26fb325e313b44aefb42dddabef63133b853c13a Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 09:51:26 -0400 Subject: [PATCH 6/7] np.NaN --> np.nan --- src/patch_denoise/bindings/modopt.py | 2 +- src/patch_denoise/space_time/lowrank.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/patch_denoise/bindings/modopt.py b/src/patch_denoise/bindings/modopt.py index 1d14478..017edf5 100644 --- a/src/patch_denoise/bindings/modopt.py +++ b/src/patch_denoise/bindings/modopt.py @@ -49,7 +49,7 @@ def __init__( **kwargs, ) self.op = self._op_method - self.cost = lambda *args, **kw: np.NaN + self.cost = lambda *args, **kw: np.nan self.time_dimension = time_dimension def _op_method(self, data, **kwargs): diff --git a/src/patch_denoise/space_time/lowrank.py b/src/patch_denoise/space_time/lowrank.py index 6e5ee3f..60ef9e7 100644 --- a/src/patch_denoise/space_time/lowrank.py +++ b/src/patch_denoise/space_time/lowrank.py @@ -181,7 +181,7 @@ def _patch_processing(self, patch, patch_idx=None, **kwargs): # Equation (3) in Manjon 2013 - return p_new, maxidx, np.NaN + return p_new, maxidx, np.nan @fill_doc @@ -403,7 +403,7 @@ def _patch_processing( maxidx = 0 p_new = np.zeros_like(patch) + p_tmean - return p_new, maxidx, np.NaN + return p_new, maxidx, np.nan def _sure_atn_cost(X, method, sing_vals, gamma, sigma=None, tau=None): @@ -631,4 +631,4 @@ def _patch_processing( maxidx = 0 p_new = np.zeros_like(patch) + p_tmean - return p_new, maxidx, np.NaN + return p_new, maxidx, np.nan From 2f030727d36176a36a5494b16a45d293251a67a5 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 09:54:38 -0400 Subject: [PATCH 7/7] np.Inf --> np.inf --- src/patch_denoise/space_time/lowrank.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/patch_denoise/space_time/lowrank.py b/src/patch_denoise/space_time/lowrank.py index 60ef9e7..923fab1 100644 --- a/src/patch_denoise/space_time/lowrank.py +++ b/src/patch_denoise/space_time/lowrank.py @@ -501,7 +501,7 @@ def sure_tau(tau, *args): if tau0 is None: tau0 = np.log(np.median(sing_vals)) - cost_glob = np.Inf + cost_glob = np.inf for g in gamma0: res_opti = minimize( lambda x: _sure_atn_cost(