Image denoising via Random Matrix Theory: M-P Law and Generalized Covariance Matrix
Project description
rmt-denoise
Image denoising via Random Matrix Theory — automatically pick the best generalized-covariance parameters (a, β) for every test image and reconstruct it.
| Method | Best for | Parameters |
|---|---|---|
MPLawDenoiser |
i.i.d. Gaussian noise | auto-estimates σ |
GeneralizedCovDenoiser |
Heteroscedastic / structured noise | oracle search for (a, β) |
Installation
pip install rmt-denoise[images] # `images` extra pulls in Pillow for load_folder
Or from source:
git clone https://github.com/yu314-coder/rmt-denoise.git
cd rmt-denoise && pip install -e .[images]
Quick Start
GeneralizedCovDenoiser runs a single workflow: pick a folder of images, choose how many to use for training, choose which one to denoise, and the denoiser jointly optimises (a, β) via differential_evolution to maximise PSNR of that test image.
from rmt_denoise import GeneralizedCovDenoiser, add_gaussian_noise
gc = GeneralizedCovDenoiser() # always centres + T(a, β) + color-resize
denoised, clean, noisy, names = gc.denoise_folder(
folder = "path/to/images", # folder of (H, W) images
n_train = 300, # 300 training images (0 = use all)
test = "s12_1.png", # one filename — or an integer index
size = (100, 100), # resize on load (optional)
noise_fn = lambda imgs: add_gaussian_noise(imgs, sigma=0.04),
)
print(gc.a, gc.beta, gc.rank, gc.psnr_test)
# 0.27 0.95 12 34.5
The chosen (a, β) are exposed as direct attributes (gc.a, gc.beta, gc.rank, gc.sigma2, gc.psnr_test); the full diagnostic dict is still in gc.info.
denoised is the full (n_train+1, H, W) reconstructed stack — the test image lives at index -1. clean and noisy are the (clean / noisy) versions of that test image, returned for convenience.
Lower-level API (already have an in-memory stack)
from rmt_denoise import GeneralizedCovDenoiser
# noisy_stack: (n, H, W) clean_test: (H, W)
gc = GeneralizedCovDenoiser()
denoised = gc.denoise(noisy_stack, clean=clean_test, test_index=-1)
The test column at test_index drives the (a, β) search; the rest of the stack is reconstructed at the same chosen rank with the same post-processing.
Train + target convenience
# train: (n_train, H, W) noisy test_noisy: (H, W) test_clean: (H, W)
gc = GeneralizedCovDenoiser()
denoised_test = gc.denoise_test(train, test_noisy, test_clean) # (H, W)
print(gc.a, gc.beta, gc.rank, gc.psnr_test)
GPU acceleration
device='auto' (default) picks Apple MPS, then CUDA, then CPU. CPU runs
np.linalg.svd in float64; MPS / CUDA runs torch.linalg.svd in float32.
The downstream optimisation runs on CPU regardless of device.
gc = GeneralizedCovDenoiser(device='mps') # Apple Silicon
gc = GeneralizedCovDenoiser(device='cuda') # NVIDIA
gc = GeneralizedCovDenoiser(device='cpu') # force CPU (slower, float64)
After every .denoise() call the lib prints the chosen (â, β̂, r̂, σ̂², PSNR, device) to stdout. The same fields are available as attributes on the denoiser:
gc.a, gc.beta, gc.rank, gc.sigma2, gc.psnr_test
MPLawDenoiser
Standard Marčenko–Pastur baseline, no oracle search:
from rmt_denoise import MPLawDenoiser
mp = MPLawDenoiser()
denoised = mp.denoise(noisy_images) # (n, H, W) -> (n, H, W)
How GeneralizedCovDenoiser works
End-to-end:
- Centre the data:
X̃ = X − X̄. - SVD once (uses dual when
p > n). scipy.optimize.differential_evolutionover(log a, β)with boundsa ∈ [0.01, 1.0],β ∈ [0.01, 0.99]. Default settings:popsize=20, Sobol init,seed=42,polish=True.- Per-candidate evaluation: gen-cov acceptance test → rank
r̂→ cached projection → centring re-add → clip →T(a, β)→ color-resize → PSNR vs clean. - Reconstruct every column at the best rank, applying the same
Tand color-resize to all of them.
T(a, β) is the diagonal matrix whose first ⌊p·β⌋ entries are √a and the rest are 1 (p = H·W); it embeds the noise-model scaling H = β·δ_a + (1 − β)·δ_1 directly into the post-processed image.
color_resize rescales each image as y = (x − min(x)) / max(x_before_subtract) and clips to [0, 1].
Both steps are part of the algorithm and are always applied — there are no flags to disable them.
API Reference
GeneralizedCovDenoiser
GeneralizedCovDenoiser(
a_bracket=(0.01, 1.0),
beta_bracket=(0.01, 0.99),
seed=42,
de_kwargs=None,
device='auto', # 'auto' | 'cpu' | 'mps' | 'cuda'
)
| Method / attribute | Description |
|---|---|
.denoise(images, clean, test_index=-1) |
Run on a noisy (n, H, W) stack with a clean (H, W) reference for the test column. Returns the reconstructed stack. |
.denoise_test(train, test_noisy, test_clean) |
Single-target convenience: returns the denoised (H, W) test image given (n_train, H, W) training stack. |
.denoise_folder(folder, n_train, test, size=None, noise_fn=None, seed=42) |
End-to-end: load a folder, split into train + test, optionally inject noise, denoise. Returns (denoised, clean, noisy, names). |
.a, .beta, .rank, .sigma2, .psnr_test |
Selected parameters from the most recent run. |
.info |
Full diagnostic dict (a, beta, sigma2, rank, psnr_test, n_evals, y, p, n, device, method='best_a_beta_oracle'). |
MPLawDenoiser(sigma2=None)
| Method | Description |
|---|---|
.denoise(images) |
Denoise (n, H, W). Returns (n, H, W). |
.info |
sigma2, threshold, rank, y, p, n. |
Folder loaders
from rmt_denoise import load_folder, split_train_test
images, names = load_folder("path/", size=(H, W)) # (n, H, W) in [0, 1]
train_imgs, test_img, n_train = split_train_test(
images, names, n_train=300, test="s12_1.png", seed=42,
)
Noise utilities
from rmt_denoise import (
add_gaussian_noise, add_laplacian_noise,
add_mixture_gaussian_noise, add_structured_noise,
)
Metrics
from rmt_denoise import compute_psnr, compute_ssim
psnr = compute_psnr(clean, denoised) # dB
ssim = compute_ssim(clean, denoised) # [-1, 1]
Mathematical Background
B_n = S_n T_n where T_n has spectral distribution converging to H = β·δ_a + (1 − β)·δ_1. The noise eigenvalue support is bounded by
g(t) = -1/t + y · ( β·a/(1 + a·t) + (1 − β)/(1 + t) )
with bulk edges
λ_lower = σ² · max_{t > 0} g(t),
λ_upper = σ² · min_{−1/a < t < 0} g(t).
When a = 1 or β = 0, this reduces to classical Marčenko–Pastur: [(1 − √y)², (1 + √y)²].
The oracle workflow above selects (a, β) so that the corresponding acceptance-test rank gives the maximum PSNR against a clean reference, then bakes the resulting T(a, β) directly into the post-processed image.
References
- Yu, Yao-Hsing (2025). "Geometric Analysis of the Eigenvalue Range of the Generalized Covariance Matrix." 2025 S.T. Yau High School Science Award (Asia).
- Gavish, M. & Donoho, D. L. (2017). "Optimal Shrinkage of Singular Values." IEEE Trans. Inf. Theory, 63(4), 2137–2152.
- Marčenko, V. A. & Pastur, L. A. (1967). "Distribution of eigenvalues for some sets of random matrices." Math. USSR-Sbornik, 1(4), 457–483.
- Veraart, J. et al. (2016). "Denoising of diffusion MRI using random matrix theory." NeuroImage, 142, 394–406.
- Storn, R. & Price, K. (1997). "Differential Evolution — A Simple and Efficient Heuristic for Global Optimization over Continuous Spaces." J. Global Optim., 11(4), 341–359.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rmt_denoise-2.2.2.tar.gz.
File metadata
- Download URL: rmt_denoise-2.2.2.tar.gz
- Upload date:
- Size: 27.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0aa3f7a9ee947767ae8f92ec1c408b2cf6259e1ccaf078166ef5b667e3e8036
|
|
| MD5 |
65d60f379646bc28c8ddc400382e7dd5
|
|
| BLAKE2b-256 |
d3fe91237722c6a24dbba52ca7a28e93420f366b9d1ce9c24fe5150453bb910f
|
File details
Details for the file rmt_denoise-2.2.2-py3-none-any.whl.
File metadata
- Download URL: rmt_denoise-2.2.2-py3-none-any.whl
- Upload date:
- Size: 26.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10c091eea0027b2a35e072dd6c496f1b8abd837819aed6bccaad7c07779ad781
|
|
| MD5 |
d0003f1ff5141fbbbda6636575620c38
|
|
| BLAKE2b-256 |
137a7c83b6a798c321770b8b7f8f16c195af245c6200396b0f6ea37f2f9610af
|