-
-
Notifications
You must be signed in to change notification settings - Fork 495
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add estimate_sigma function to threshold features #395
base: main
Are you sure you want to change the base?
Changes from 7 commits
48e6006
f018bd3
3cb502e
25fab87
65ce36c
1e53ece
484856b
407985b
557738b
602ceb0
7dab21a
bc8b1d5
ed1d9ff
e2aa85b
11282da
606fae6
0ba3996
644a1c5
1f5beee
1f284f5
73fa511
1c2a6ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,10 @@ | |
from __future__ import division, print_function, absolute_import | ||
import numpy as np | ||
|
||
__all__ = ['threshold', 'threshold_firm'] | ||
from ._multidim import dwtn | ||
|
||
|
||
__all__ = ['threshold', 'threshold_firm', 'estimate_sigma'] | ||
|
||
|
||
def soft(data, value, substitute=0): | ||
|
@@ -245,3 +248,88 @@ def threshold_firm(data, value_low, value_high): | |
if np.any(large_vals[0]): | ||
thresholded[large_vals] = data[large_vals] | ||
return thresholded | ||
|
||
|
||
def estimate_sigma(data, distribution='Gaussian', **kwargs): | ||
""" | ||
Robust wavelet-based estimator of the (Gaussian) noise standard deviation. | ||
Parameters | ||
---------- | ||
data : ndarray | ||
The data used to estimate sigma. | ||
distribution : str or object with ppf method | ||
The underlying noise distribution. | ||
**kwargs : **kwargs | ||
Keyword arguments to pass into distribution ppf method. | ||
|
||
Returns | ||
------- | ||
sigma : float | ||
Estimated noise standard deviation. | ||
|
||
Notes | ||
----- | ||
This function assumes the noise follows a Gaussian distribution. The | ||
estimation algorithm is based on the median absolute deviation of the | ||
wavelet detail coefficients as described in section 4.2 of [1]_. | ||
|
||
References | ||
---------- | ||
.. [1] D. L. Donoho and I. M. Johnstone. "Ideal spatial adaptation | ||
by wavelet shrinkage." Biometrika 81.3 (1994): 425-455. | ||
DOI:10.1093/biomet/81.3.425 | ||
|
||
Examples | ||
-------- | ||
>>> import numpy as np | ||
>>> import pywt | ||
>>> data = np.sin(np.linspace(0,10,1000)) | ||
>>> np.random.seed(42) | ||
>>> noise = np.random.normal(0,1,1000) | ||
>>> sigma = pywt.estimate_sigma(data + noise) | ||
""" | ||
|
||
coeffs = dwtn(data, wavelet='db2') | ||
detail_coeffs = coeffs['d' * data.ndim] | ||
return _sigma_est_dwt(detail_coeffs, distribution=distribution, **kwargs) | ||
|
||
|
||
def _sigma_est_dwt(detail_coeffs, distribution='Gaussian', **kwargs): | ||
"""Calculate the robust median estimator of the noise standard deviation. | ||
Parameters | ||
---------- | ||
detail_coeffs : ndarray | ||
The detail coefficients corresponding to the discrete wavelet | ||
transform of an image. | ||
distribution : str or object with ppf method | ||
The underlying noise distribution. | ||
**kwargs : **kwargs | ||
Keyword arguments to pass into distribution ppf method. | ||
|
||
Returns | ||
------- | ||
sigma : float | ||
The estimated noise standard deviation (see section 4.2 of [1]_). | ||
References | ||
---------- | ||
.. [1] D. L. Donoho and I. M. Johnstone. "Ideal spatial adaptation | ||
by wavelet shrinkage." Biometrika 81.3 (1994): 425-455. | ||
DOI:10.1093/biomet/81.3.425 | ||
""" | ||
# Consider regions with detail coefficients exactly zero to be masked out | ||
detail_coeffs = detail_coeffs[np.nonzero(detail_coeffs)] | ||
|
||
if hasattr(distribution, 'ppf'): | ||
if not kwargs: | ||
kwargs = {'q': 0.75} | ||
denom = distribution.ppf(**kwargs) | ||
elif str(distribution).lower() == 'gaussian': | ||
# 75th quantile of the underlying, symmetric noise distribution | ||
# denom = scipy.stats.norm.ppf(0.75) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In addition to a string name, you could accept any object with a The bigger issue here seems to be the hardcoding of the 75th quantile. Why is that not a keyword? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like both choices originate from specific suggestions by the author, I agree that the best solution would be to have any distribution or quantile as valid inputs, as that could prove useful. The issue I saw is the use of scipy.stats in the original code, because PyWavelets doesn't rely on scipy (right?) I'm not sure what the best thing to do there is, including scipy for just this is overkill, perhaps a couple hand coded options for now? Or for just Gaussian with a variable percent input, shouldn't be too hard to get going without scipy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Btw, nice suggestion for finding if a distribution supports a method, seeing the example it seems obvious now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed we don't want to rely on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A concise explanation of the rationale for use of the 75th quantile in MAD is given on the following wikipedia page: I also agree about not relying on The idea to check for a Another approach to deal with other noise distributions such as Poisson noise is to first apply a variance stabilizing transform prior to calling this Gaussian-based MAD method. An example implementation of this type of approach for use with Rician noise is available here, but it is not under compatible license terms. |
||
# magic number to fill in because no scipy | ||
denom = 0.6744897501960817 | ||
else: | ||
raise ValueError("Only Gaussian noise estimation or objects with" | ||
" ppf method currently supported") | ||
sigma = np.median(np.abs(detail_coeffs)) / denom | ||
return sigma |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally thought it would be good to auto-fill the arguments with 75th quantile in the case there are no args passed in to keep up the defaults, but this may be a bad idea in case someone wants to use a ppf method which takes no arguments (as this would result in passing in an unwanted keyword argument).