-
Notifications
You must be signed in to change notification settings - Fork 16
/
espirit.py
117 lines (95 loc) · 4.87 KB
/
espirit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import numpy as np
fft = lambda x, ax : np.fft.fftshift(np.fft.fftn(np.fft.ifftshift(x, axes=ax), axes=ax, norm='ortho'), axes=ax)
ifft = lambda X, ax : np.fft.fftshift(np.fft.ifftn(np.fft.ifftshift(X, axes=ax), axes=ax, norm='ortho'), axes=ax)
def espirit(X, k, r, t, c):
"""
Derives the ESPIRiT operator.
Arguments:
X: Multi channel k-space data. Expected dimensions are (sx, sy, sz, nc), where (sx, sy, sz) are volumetric
dimensions and (nc) is the channel dimension.
k: Parameter that determines the k-space kernel size. If X has dimensions (1, 256, 256, 8), then the kernel
will have dimensions (1, k, k, 8)
r: Parameter that determines the calibration region size. If X has dimensions (1, 256, 256, 8), then the
calibration region will have dimensions (1, r, r, 8)
t: Parameter that determines the rank of the auto-calibration matrix (A). Singular values below t times the
largest singular value are set to zero.
c: Crop threshold that determines eigenvalues "=1".
Returns:
maps: This is the ESPIRiT operator. It will have dimensions (sx, sy, sz, nc, nc) with (sx, sy, sz, :, idx)
being the idx'th set of ESPIRiT maps.
"""
sx = np.shape(X)[0]
sy = np.shape(X)[1]
sz = np.shape(X)[2]
nc = np.shape(X)[3]
sxt = (sx//2-r//2, sx//2+r//2) if (sx > 1) else (0, 1)
syt = (sy//2-r//2, sy//2+r//2) if (sy > 1) else (0, 1)
szt = (sz//2-r//2, sz//2+r//2) if (sz > 1) else (0, 1)
# Extract calibration region.
C = X[sxt[0]:sxt[1], syt[0]:syt[1], szt[0]:szt[1], :].astype(np.complex64)
# Construct Hankel matrix.
p = (sx > 1) + (sy > 1) + (sz > 1)
A = np.zeros([(r-k+1)**p, k**p * nc]).astype(np.complex64)
idx = 0
for xdx in range(max(1, C.shape[0] - k + 1)):
for ydx in range(max(1, C.shape[1] - k + 1)):
for zdx in range(max(1, C.shape[2] - k + 1)):
# numpy handles when the indices are too big
block = C[xdx:xdx+k, ydx:ydx+k, zdx:zdx+k, :].astype(np.complex64)
A[idx, :] = block.flatten()
idx = idx + 1
# Take the Singular Value Decomposition.
U, S, VH = np.linalg.svd(A, full_matrices=True)
V = VH.conj().T
# Select kernels.
n = np.sum(S >= t * S[0])
V = V[:, 0:n]
kxt = (sx//2-k//2, sx//2+k//2) if (sx > 1) else (0, 1)
kyt = (sy//2-k//2, sy//2+k//2) if (sy > 1) else (0, 1)
kzt = (sz//2-k//2, sz//2+k//2) if (sz > 1) else (0, 1)
# Reshape into k-space kernel, flips it and takes the conjugate
kernels = np.zeros(np.append(np.shape(X), n)).astype(np.complex64)
kerdims = [(sx > 1) * k + (sx == 1) * 1, (sy > 1) * k + (sy == 1) * 1, (sz > 1) * k + (sz == 1) * 1, nc]
for idx in range(n):
kernels[kxt[0]:kxt[1],kyt[0]:kyt[1],kzt[0]:kzt[1], :, idx] = np.reshape(V[:, idx], kerdims)
# Take the iucfft
axes = (0, 1, 2)
kerimgs = np.zeros(np.append(np.shape(X), n)).astype(np.complex64)
for idx in range(n):
for jdx in range(nc):
ker = kernels[::-1, ::-1, ::-1, jdx, idx].conj()
kerimgs[:,:,:,jdx,idx] = fft(ker, axes) * np.sqrt(sx * sy * sz)/np.sqrt(k**p)
# Take the point-wise eigenvalue decomposition and keep eigenvalues greater than c
maps = np.zeros(np.append(np.shape(X), nc)).astype(np.complex64)
for idx in range(0, sx):
for jdx in range(0, sy):
for kdx in range(0, sz):
Gq = kerimgs[idx,jdx,kdx,:,:]
u, s, vh = np.linalg.svd(Gq, full_matrices=True)
for ldx in range(0, nc):
if (s[ldx]**2 > c):
maps[idx, jdx, kdx, :, ldx] = u[:, ldx]
return maps
def espirit_proj(x, esp):
"""
Construct the projection of multi-channel image x onto the range of the ESPIRiT operator. Returns the inner
product, complete projection and the null projection.
Arguments:
x: Multi channel image data. Expected dimensions are (sx, sy, sz, nc), where (sx, sy, sz) are volumetric
dimensions and (nc) is the channel dimension.
esp: ESPIRiT operator as returned by function: espirit
Returns:
ip: This is the inner product result, or the image information in the ESPIRiT subspace.
proj: This is the resulting projection. If the ESPIRiT operator is E, then proj = E E^H x, where H is
the hermitian.
null: This is the null projection, which is equal to x - proj.
"""
ip = np.zeros(x.shape).astype(np.complex64)
proj = np.zeros(x.shape).astype(np.complex64)
for qdx in range(0, esp.shape[4]):
for pdx in range(0, esp.shape[3]):
ip[:, :, :, qdx] = ip[:, :, :, qdx] + x[:, :, :, pdx] * esp[:, :, :, pdx, qdx].conj()
for qdx in range(0, esp.shape[4]):
for pdx in range(0, esp.shape[3]):
proj[:, :, :, pdx] = proj[:, :, :, pdx] + ip[:, :, :, qdx] * esp[:, :, :, pdx, qdx]
return (ip, proj, x - proj)