Skip to content
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

Adaptive Denoising #1066

Merged
merged 47 commits into from Aug 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d0af43c
Added wavelet functions in dipy.core
May 28, 2016
b075197
Added adaptive soft coefficient matching and blockwise nlmeans
May 28, 2016
1d35392
Added keyword for blockwise nlmeans
May 28, 2016
c1754f9
Added examples for adaptive denoising in doc/examples
May 29, 2016
73375f9
Fix build error, correct import in wavelet.py
May 29, 2016
f0e74ee
Added tests for ascm and modified test_nlmeans to add tests for nlmea…
May 30, 2016
8f1d911
Changes in wavelet.py
May 30, 2016
8439a33
removed depricated xrange command
May 30, 2016
81ffe9a
Resolved build errors, added references, PEP8 formatting
May 31, 2016
d3f777d
Updates in nlmeans, changed noise variance argument
riddhishb Jun 6, 2016
b8e9ebe
Changes in tests for ascm
riddhishb Jun 6, 2016
76cf022
Formatting changes
riddhishb Jun 17, 2016
031b910
Formatting Changes
riddhishb Jun 17, 2016
a5afb6c
Cython code optimization for nlmeans_block.pyx
riddhishb Jun 19, 2016
91f8961
Docstring formatting done
riddhishb Jun 21, 2016
789228e
Resolved build errors
riddhishb Jun 21, 2016
08e1a69
Corrections in denspeed.pyx
riddhishb Jun 23, 2016
cc0fcb9
restoring denspeed changes in this branch
riddhishb Jun 24, 2016
57096c7
restoring denspeed changes in this branch
riddhishb Jun 24, 2016
a911a74
Adding non_local_means, and other changes
riddhishb Jun 25, 2016
9faa082
Resolved build errors
riddhishb Jun 25, 2016
6770e91
Resolving issue in test_nlmeans
riddhishb Jun 25, 2016
e5439cd
Updated test_ascm and added new data
riddhishb Jun 26, 2016
faf52b5
resolved build error
riddhishb Jun 26, 2016
c4f4211
Changes in ascm tutorial
riddhishb Jul 1, 2016
b51f426
Docstring style correction
riddhishb Jul 29, 2016
482aa95
Updated docstring to current standard
riddhishb Jul 29, 2016
ad6c0b2
Added info and parameters in docstrings
riddhishb Jul 29, 2016
475a978
updated example with new function
riddhishb Jul 29, 2016
7c3a353
built sphinx documentation for ASCM tutorial
riddhishb Jul 30, 2016
c615d37
Fixed the docstring reference standard
riddhishb Jul 30, 2016
2f9f808
Fixed references
riddhishb Aug 4, 2016
a131207
finishing the code formatting
riddhishb Aug 6, 2016
700a295
added comparision in the tutorial
riddhishb Aug 6, 2016
a74e7e4
remaining tests
riddhishb Aug 7, 2016
5cd40f7
Added print statements in tutorial
riddhishb Aug 7, 2016
71d2f8f
changed deprecation warnings
riddhishb Aug 7, 2016
106f190
improved coverage and added more tests for the raised errors
riddhishb Aug 7, 2016
b018fb8
renaming parameter types
riddhishb Aug 7, 2016
b72bc24
changes in tutorial and renaming the ascm
riddhishb Aug 8, 2016
c3df90d
small changes in formatting
riddhishb Aug 8, 2016
83bb07f
added reference in tutorial
riddhishb Aug 8, 2016
fd6509b
changes in tutorial
riddhishb Aug 8, 2016
05dbed8
typo fix
riddhishb Aug 8, 2016
4d9d45e
corrected typos
riddhishb Aug 9, 2016
458ace6
Changes in tutorial
riddhishb Aug 9, 2016
13da3b9
Changes in tutorial
riddhishb Aug 9, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
293 changes: 293 additions & 0 deletions dipy/core/wavelet.py
@@ -0,0 +1,293 @@
import numpy as np
from dipy.denoise import nlmeans_block

"""
Functions for Wavelet Transforms in 3D domain

Code adapted from

WAVELET SOFTWARE AT POLYTECHNIC UNIVERSITY, BROOKLYN, NY
http://taco.poly.edu/WaveletSoftware/
"""


def cshift3D(x, m, d):
"""3D Circular Shift

Parameters
----------
x : 3D ndarray
N1 by N2 by N3 array
m : int
amount of shift
d : int
dimension of shift (d = 1,2,3)

Returns
-------
y : 3D ndarray
array x will be shifed by m samples down
along dimension d
"""

s = x.shape
idx = (np.array(range(s[d])) + (s[d] - m % s[d])) % s[d]
idx = np.array(idx, dtype=np.int64)
if d == 0:
return x[idx, :, :]
elif d == 1:
return x[:, idx, :]
else:
return x[:, :, idx]


def permutationinverse(perm):
"""
Function generating inverse of the permutation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

permutation_inverse no capitals in function names. Also fix docstring style and explain parameter.


Parameters
----------
perm : 1D array

Returns
-------
inverse : 1D array
permutation inverse of the input
"""

inverse = [0] * len(perm)
for i, p in enumerate(perm):
inverse[p] = i
return inverse


def afb3D_A(x, af, d):
"""3D Analysis Filter Bank
(along one dimension only)

Parameters
----------
x : 3D ndarray
N1xN2xN2 matrix, where min(N1,N2,N3) > 2*length(filter)
(Ni are even)
af : 2D ndarray
analysis filter for the columns
af[:, 1] - lowpass filter
af[:, 2] - highpass filter
d : int
dimension of filtering (d = 1, 2 or 3)

Returns
-------
lo : 1D array
lowpass subbands
hi : 1D array
highpass subbands
"""

lpf = af[:, 0]
hpf = af[:, 1]
# permute dimensions of x so that dimension d is first.
p = [(i + d) % 3 for i in range(3)]
x = x.transpose(p)
# filter along dimension 0
(N1, N2, N3) = x.shape
L = af.shape[0] // 2
x = cshift3D(x, -L, 0)
n1Half = N1 // 2
lo = np.zeros((L + n1Half, N2, N3))
hi = np.zeros((L + n1Half, N2, N3))
for k in range(N3):
lo[:, :, k] = nlmeans_block.firdn(x[:, :, k], lpf)
lo[:L] = lo[:L] + lo[n1Half:n1Half + L, :, :]
lo = lo[:n1Half, :, :]

for k in range(N3):
hi[:, :, k] = nlmeans_block.firdn(x[:, :, k], hpf)
hi[:L] = hi[:L] + hi[n1Half:n1Half + L, :, :]
hi = hi[:n1Half, :, :]
# permute dimensions of x (inverse permutation)
q = permutationinverse(p)
lo = lo.transpose(q)
hi = hi.transpose(q)
return lo, hi


def sfb3D_A(lo, hi, sf, d):
"""3D Synthesis Filter Bank
(along single dimension only)

Parameters
----------
lo : 1D array
lowpass subbands
hi : 1D array
highpass subbands
sf : 2D ndarray
synthesis filters
d : int
dimension of filtering

Returns
-------
y : 3D ndarray
the N1xN2xN3 matrix
"""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

lpf = sf[:, 0]
hpf = sf[:, 1]
# permute dimensions of lo and hi so that dimension d is first.
p = [(i + d) % 3 for i in range(3)]
lo = lo.transpose(p)
hi = hi.transpose(p)

(N1, N2, N3) = lo.shape
N = 2 * N1
L = sf.shape[0]
y = np.zeros((N + L - 2, N2, N3))
for k in range(N3):
y[:, :, k] = (np.array(nlmeans_block.upfir(lo[:, :, k], lpf)) +
np.array(nlmeans_block.upfir(hi[:, :, k], hpf)))
y[:(L - 2), :, :] = y[:(L - 2), :, :] + y[N:(N + L - 2), :, :]
y = y[:N, :, :]
y = cshift3D(y, 1 - L / 2, 0)
# permute dimensions of y (inverse permutation)
q = permutationinverse(p)
y = y.transpose(q)
return y


def sfb3D(lo, hi, sf1, sf2=None, sf3=None):
"""3D Synthesis Filter Bank

Parameters
----------
lo : 1D array
lowpass subbands
hi : 1D array
highpass subbands
sfi : 2D ndarray
synthesis filters for dimension i

Returns
-------
y : 3D ndarray
output array
"""

if sf2 is None:
sf2 = sf1
if sf3 is None:
sf3 = sf1
LLL = lo
LLH = hi[0]
LHL = hi[1]
LHH = hi[2]
HLL = hi[3]
HLH = hi[4]
HHL = hi[5]
HHH = hi[6]
# filter along dimension 2
LL = sfb3D_A(LLL, LLH, sf3, 2)
LH = sfb3D_A(LHL, LHH, sf3, 2)
HL = sfb3D_A(HLL, HLH, sf3, 2)
HH = sfb3D_A(HHL, HHH, sf3, 2)
# filter along dimension 1
L = sfb3D_A(LL, LH, sf2, 1)
H = sfb3D_A(HL, HH, sf2, 1)
# filter along dimension 0
y = sfb3D_A(L, H, sf1, 0)
return y


def afb3D(x, af1, af2=None, af3=None):
"""3D Analysis Filter Bank

Parameters
----------
x : 3D ndarray
N1 by N2 by N3 array matrix, where
1) N1, N2, N3 all even
2) N1 >= 2*len(af1)
3) N2 >= 2*len(af2)
4) N3 >= 2*len(af3)
afi : 2D ndarray
analysis filters for dimension i
afi[:, 1] - lowpass filter
afi[:, 2] - highpass filter

Returns
-------
lo : 1D array
lowpass subband
hi : 1D array
highpass subbands, h[d]- d = 1..7
"""

if af2 is None:
af2 = af1
if af3 is None:
af3 = af1
# filter along dimension 0
L, H = afb3D_A(x, af1, 0)
# filter along dimension 1
LL, LH = afb3D_A(L, af2, 1)
HL, HH = afb3D_A(H, af2, 1)
# filter along dimension 3
LLL, LLH = afb3D_A(LL, af3, 2)
LHL, LHH = afb3D_A(LH, af3, 2)
HLL, HLH = afb3D_A(HL, af3, 2)
HHL, HHH = afb3D_A(HH, af3, 2)
return LLL, [LLH, LHL, LHH, HLL, HLH, HHL, HHH]


def dwt3D(x, J, af):
"""3-D Discrete Wavelet Transform

Parameters
----------
x : 3D ndarray
N1 x N2 x N3 matrix
1) Ni all even
2) min(Ni) >= 2^(J-1)*length(af)
J : int
number of stages
af : 2D ndarray
analysis filters

Returns
-------
w : cell array
wavelet coefficients
"""

w = [None] * (J + 1)
for k in range(J):
x, w[k] = afb3D(x, af, af, af)
w[J] = x
return w


def idwt3D(w, J, sf):
"""
Inverse 3-D Discrete Wavelet Transform

Parameters
----------
w : cell array
wavelet coefficient
J : int
number of stages
sf : 2D ndarray
synthesis filters

Returns
-------
y : 3D ndarray
output array
"""

y = w[J]
for k in range(J)[::-1]:
y = sfb3D(y, w[k], sf, sf, sf)
return y
2 changes: 2 additions & 0 deletions dipy/data/__init__.py
Expand Up @@ -248,6 +248,8 @@ def get_data(name='small_64D'):
return fimg, fbvals, fbvecs
if name == 'aniso_vox':
return pjoin(DATA_DIR, 'aniso_vox.nii.gz')
if name == 'ascm_test':
return pjoin(DATA_DIR, 'ascm_out_test.nii.gz')
if name == 'fornix':
return pjoin(DATA_DIR, 'tracks300.trk')
if name == 'gqi_vectors':
Expand Down
Binary file added dipy/data/files/ascm_out_test.nii.gz
Binary file not shown.
86 changes: 86 additions & 0 deletions dipy/denoise/adaptive_soft_matching.py
@@ -0,0 +1,86 @@
import math
import numpy as np
from dipy.core import wavelet


def adaptive_soft_matching(ima, fimau, fimao, sigma):
r"""Adaptive Soft Coefficient Matching

Combines two filtered 3D-images at different resolutions and the orginal
image. Returns the resulting combined image.

Parameters
----------
ima : the original (not filtered) image
fimau : 3D double array,
filtered image with optimized non-local means using a small block
(suggested:3x3), which corresponds to a "high resolution" filter.
fimao : 3D double array,
filtered image with optimized non-local means using a small block
(suggested:5x5), which corresponds to a "low resolution" filter.
sigma : the estimated standard deviation of the Gaussian random variables
that explain the rician noise. Note: In P. Coupe et al. the
rician noise was simulated as sqrt((f+x)^2 + (y)^2) where f is
the pixel value and x and y are independent realizations of a
random variable with Normal distribution, with mean=0 and
standard deviation=h

Returns
-------
fima : 3D double array
output denoised array which is of the same shape as that of
the input

References
----------
.. [Coupe11] Pierrick Coupe, Jose Manjon, Montserrat Robles, Louis Collins.
"Multiresolution Non-Local Means Filter for 3D MR Image
Denoising" IET Image Processing, Institution of Engineering
and Technology,
2011

"""

s = fimau.shape
p = [0, 0, 0]
p[0] = np.int(2**math.ceil(math.log(s[0], 2)))
p[1] = np.int(2**math.ceil(math.log(s[1], 2)))
p[2] = np.int(2**math.ceil(math.log(s[2], 2)))
pad1 = np.zeros((p[0], p[1], p[2]))
pad2 = np.zeros((p[0], p[1], p[2]))
pad3 = np.zeros((p[0], p[1], p[2]))
pad1[:s[0], :s[1], :s[2]] = fimau[:, :, :]
pad2[:s[0], :s[1], :s[2]] = fimao[:, :, :]
pad3[:s[0], :s[1], :s[2]] = ima[:, :, :]
af = np.array([[0, -0.01122679215254],
[0, 0.01122679215254],
[-0.08838834764832, 0.08838834764832],
[0.08838834764832, 0.08838834764832],
[0.69587998903400, -0.69587998903400],
[0.69587998903400, 0.69587998903400],
[0.08838834764832, -0.08838834764832],
[-0.08838834764832, -0.08838834764832],
[0.01122679215254, 0],
[0.01122679215254, 0]])
sf = np.array(af[::-1, :])
w1 = wavelet.dwt3D(pad1, 1, af)
w2 = wavelet.dwt3D(pad2, 1, af)
w3 = wavelet.dwt3D(pad3, 1, af)
for i in range(7):
tmp = np.array(w3[0][i])
tmp = tmp[:(s[0] // 2), :(s[1] // 2), :(s[2] // 2)]
sigY = np.std(tmp, ddof=1)
sigX = (sigY * sigY) - sigma * sigma
if sigX < 0:
T = abs(w3[0][i]).max()
else:
T = (sigma * sigma) / (sigX**0.5)
w3[0][i] = abs(w3[0][i])
dist = np.array(w3[0][i]) - T
dist = np.exp(-0.01 * dist)
dist = 1. / (1 + dist)
w3[0][i] = dist * w1[0][i] + (1 - dist) * w2[0][i]
w3[1] = w1[1]
fima = wavelet.idwt3D(w3, 1, sf)
fima = fima[:s[0], :s[1], :s[2]]
return fima