From b34360bbfe62f6c2db320cd0c5523faf3f493ea8 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 3 Oct 2024 10:07:13 -0400 Subject: [PATCH] Address linting and test errors (#5) * Fix typos. * Fix a handful of typos. * Replace ruff with ruff check * Address linter errors. * Reorder stuff a bit. * np.NaN --> np.nan * np.Inf --> np.inf --- .github/workflows/test-ci.yml | 12 +++++------ docs/denoisers.rst | 4 ++-- pyproject.toml | 6 +++--- src/patch_denoise/bindings/cli.py | 3 ++- src/patch_denoise/bindings/modopt.py | 2 +- src/patch_denoise/bindings/utils.py | 5 ++++- src/patch_denoise/space_time/base.py | 6 ++++-- src/patch_denoise/space_time/lowrank.py | 27 +++++++++++++------------ src/patch_denoise/space_time/utils.py | 24 ++++++++++++++++++++-- tests/test_bindings.py | 3 ++- tests/test_spacetime_utils.py | 4 ++-- 11 files changed, 62 insertions(+), 34 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 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/pyproject.toml b/pyproject.toml index 7546e31..d18bbde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,19 +52,19 @@ omit = ["*tests*"] precision = 2 exclude_lines = ["pragma: no cover", "raise NotImplementedError"] - # Formatting using black. [tool.black] #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.pydocstyle] +[tool.ruff.lint.pydocstyle] convention="numpy" [tool.isort] 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/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/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/src/patch_denoise/space_time/base.py b/src/patch_denoise/space_time/base.py index e11a916..0910509 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 @@ -43,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 345f39e..923fab1 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 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__( @@ -180,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 @@ -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 ---------- @@ -291,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. """ @@ -335,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. @@ -345,7 +346,7 @@ def denoise( Notes ----- - Reimplement of the original Matlab code [#]_ in python. + Reimplementation of the original Matlab code [#]_ in python. References ---------- @@ -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, ) @@ -402,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): @@ -500,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( @@ -630,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 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_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 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)))