├── .gitignore ├── README.md ├── cfl.py ├── data ├── alias │ ├── brain_alias.cfl │ ├── brain_alias.hdr │ ├── cimg.cfl │ ├── cimg.hdr │ ├── maps.cfl │ ├── maps.hdr │ ├── noisy.cfl │ ├── noisy.hdr │ ├── pimg.cfl │ ├── pimg.hdr │ ├── x.cfl │ └── x.hdr ├── brain │ ├── cimg.cfl │ ├── cimg.hdr │ ├── full.cfl │ ├── full.hdr │ ├── maps.cfl │ ├── maps.hdr │ ├── noisy.cfl │ ├── noisy.hdr │ ├── pimg.cfl │ ├── pimg.hdr │ ├── x.cfl │ └── x.hdr └── knee │ ├── cimg.cfl │ ├── cimg.hdr │ ├── knee.cfl │ ├── knee.hdr │ ├── maps.cfl │ ├── maps.hdr │ ├── noisy.cfl │ ├── noisy.hdr │ ├── pimg.cfl │ ├── pimg.hdr │ ├── x.cfl │ └── x.hdr ├── espirit.py ├── estvar.py ├── example_espirit.py ├── example_estvar.py └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # espirit-python 2 | 3 | ESPIRiT implemented in Python. 4 | 5 | ## Prerequisites 6 | 7 | ESPIRiT itself requires ```numpy```. ```matplotlib``` is needed to display images. 8 | 9 | ## Usage 10 | 11 | Please see ```example.py``` for a usage example. 12 | 13 | ## References 14 | 15 | - Uecker, M., Lai, P., Murphy, M. J., Virtue, P., Elad, M., Pauly, J. M., ... & Lustig, M. (2014). ESPIRiT—an 16 | eigenvalue approach to autocalibrating parallel MRI: where SENSE meets GRAPPA. Magnetic resonance in medicine, 17 | 71(3), 990-1001. 18 | - Data from [mridata.org](http://mridata.org) 19 | -------------------------------------------------------------------------------- /cfl.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-2015. The Regents of the University of California. 2 | # All rights reserved. Use of this source code is governed by 3 | # a BSD-style license which can be found in the LICENSE file. 4 | # 5 | # Authors: 6 | # 2013 Martin Uecker 7 | # 2015 Jonathan Tamir 8 | 9 | 10 | import numpy as np 11 | 12 | def readcfl(name): 13 | # get dims from .hdr 14 | h = open(name + ".hdr", "r") 15 | h.readline() # skip 16 | l = h.readline() 17 | h.close() 18 | dims = [int(i) for i in l.split( )] 19 | 20 | # remove singleton dimensions from the end 21 | n = np.prod(dims) 22 | dims_prod = np.cumprod(dims) 23 | dims = dims[:np.searchsorted(dims_prod, n)+1] 24 | 25 | # load data and reshape into dims 26 | d = open(name + ".cfl", "r") 27 | a = np.fromfile(d, dtype=np.complex64, count=n); 28 | d.close() 29 | return a.reshape(dims, order='F') # column-major 30 | 31 | 32 | def writecfl(name, array): 33 | h = open(name + ".hdr", "w") 34 | h.write('# Dimensions\n') 35 | for i in (array.shape): 36 | h.write("%d " % i) 37 | h.write('\n') 38 | h.close() 39 | d = open(name + ".cfl", "w") 40 | array.T.astype(np.complex64).tofile(d) # tranpose for column-major order 41 | d.close() 42 | -------------------------------------------------------------------------------- /data/alias/brain_alias.cfl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidward/espirit-python/94b1afe7f7b6d750465ede54823b4819d3f0bd5c/data/alias/brain_alias.cfl -------------------------------------------------------------------------------- /data/alias/brain_alias.hdr: -------------------------------------------------------------------------------- 1 | # Dimensions 2 | 1 320 256 8 1 3 | -------------------------------------------------------------------------------- /data/alias/cimg.cfl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidward/espirit-python/94b1afe7f7b6d750465ede54823b4819d3f0bd5c/data/alias/cimg.cfl -------------------------------------------------------------------------------- /data/alias/cimg.hdr: -------------------------------------------------------------------------------- 1 | # Dimensions 2 | 1 320 256 8 1 1 1 1 1 1 1 1 1 1 1 1 3 | # Command 4 | fmac -s 16 pimg maps cimg 5 | # Files 6 | >cimg maps noisy pimg x cimg maps noisy pimg x cimg maps noisy pimg x 1) else (0, 1) 13 | syt = (sy//2-r//2, sy//2+r//2) if (sy > 1) else (0, 1) 14 | szt = (sz//2-r//2, sz//2+r//2) if (sz > 1) else (0, 1) 15 | 16 | # Extract calibration region. 17 | C = X[sxt[0]:sxt[1], syt[0]:syt[1], szt[0]:szt[1], :].copy().astype(np.complex64) 18 | 19 | return C 20 | 21 | def calmat(C, k, r): 22 | 23 | sx = np.shape(C)[0] 24 | sy = np.shape(C)[1] 25 | sz = np.shape(C)[2] 26 | nc = np.shape(C)[3] 27 | 28 | # Construct Hankel matrix. 29 | p = (sx > 1) + (sy > 1) + (sz > 1) 30 | A = np.zeros([(r-k+1)**p, k**p * nc]).astype(np.complex64) 31 | 32 | idx = 0 33 | for xdx in range(max(1, C.shape[0] - k + 1)): 34 | for ydx in range(max(1, C.shape[1] - k + 1)): 35 | for zdx in range(max(1, C.shape[2] - k + 1)): 36 | # numpy handles when the indices are too big 37 | block = C[xdx:xdx+k, ydx:ydx+k, zdx:zdx+k, :].astype(np.complex64) 38 | A[idx, :] = block.flatten() 39 | idx = idx + 1 40 | 41 | return A 42 | 43 | def espirit(X, k, r, t, c): 44 | """ 45 | Derives the ESPIRiT operator. 46 | 47 | Arguments: 48 | X: Multi channel k-space data. Expected dimensions are (sx, sy, sz, nc), 49 | where (sx, sy, sz) are volumetric dimensions and (nc) is the channel dimension. 50 | k: Parameter that determines the k-space kernel size. If X has 51 | dimensions (1, 256, 256, 8), then the kernel will have dimensions (1, k, k, 8) 52 | r: Parameter that determines the calibration region size. If X has 53 | dimensions (1, 256, 256, 8), then the calibration region will have 54 | dimensions (1, r, r, 8) 55 | t: Parameter that determines the rank of the auto-calibration matrix (A). Singular 56 | values below t times the largest singular value are set to zero. 57 | c: Crop threshold that determines eigenvalues "=1". 58 | Returns: 59 | maps: This is the ESPIRiT operator. It will have dimensions (sx, sy, sz, nc, nc) 60 | with (sx, sy, sz, :, idx) being the idx'th set of ESPIRiT maps. 61 | """ 62 | 63 | sx = np.shape(X)[0] 64 | sy = np.shape(X)[1] 65 | sz = np.shape(X)[2] 66 | nc = np.shape(X)[3] 67 | p = (sx > 1) + (sy > 1) + (sz > 1) 68 | 69 | C = calreg(X, r) 70 | A = calmat(C, k, r) 71 | 72 | # Take the Singular Value Decomposition. 73 | U, S, VH = np.linalg.svd(A, full_matrices=True) 74 | V = VH.conj().T 75 | 76 | # Select kernels. 77 | n = np.sum(S >= t * S[0]) 78 | V = V[:, 0:n] 79 | 80 | kxt = (sx//2-k//2, sx//2+k//2) if (sx > 1) else (0, 1) 81 | kyt = (sy//2-k//2, sy//2+k//2) if (sy > 1) else (0, 1) 82 | kzt = (sz//2-k//2, sz//2+k//2) if (sz > 1) else (0, 1) 83 | 84 | # Reshape into k-space kernel, flips it and takes the conjugate 85 | kernels = np.zeros(np.append(np.shape(X), n)).astype(np.complex64) 86 | kerdims = [(sx > 1) * k + (sx == 1) * 1, (sy > 1) * k + (sy == 1) * 1, (sz > 1) * k + (sz == 1) * 1, nc] 87 | for idx in range(n): 88 | kernels[kxt[0]:kxt[1],kyt[0]:kyt[1],kzt[0]:kzt[1], :, idx] = np.reshape(V[:, idx], kerdims) 89 | 90 | # Take the iucfft 91 | axes = (0, 1, 2) 92 | kerimgs = np.zeros(np.append(np.shape(X), n)).astype(np.complex64) 93 | for idx in range(n): 94 | for jdx in range(nc): 95 | ker = kernels[::-1, ::-1, ::-1, jdx, idx].conj() 96 | kerimgs[:,:,:,jdx,idx] = fft(ker, axes) * np.sqrt(sx * sy * sz)/np.sqrt(k**p) 97 | 98 | # Take the point-wise eigenvalue decomposition and keep eigenvalues greater than c 99 | maps = np.zeros(np.append(np.shape(X), nc)).astype(np.complex64) 100 | for idx in range(0, sx): 101 | for jdx in range(0, sy): 102 | for kdx in range(0, sz): 103 | 104 | Gq = kerimgs[idx,jdx,kdx,:,:] 105 | 106 | u, s, vh = np.linalg.svd(Gq, full_matrices=True) 107 | for ldx in range(0, nc): 108 | if (s[ldx]**2 > c): 109 | maps[idx, jdx, kdx, :, ldx] = u[:, ldx] 110 | 111 | return maps 112 | 113 | def espirit_proj(x, esp): 114 | """ 115 | Construct the projection of multi-channel image x onto the range of the ESPIRiT operator. Returns the inner 116 | product, complete projection and the null projection. 117 | 118 | Arguments: 119 | x: Multi channel image data. Expected dimensions are (sx, sy, sz, nc), where (sx, sy, sz) are volumetric 120 | dimensions and (nc) is the channel dimension. 121 | esp: ESPIRiT operator as returned by function: espirit 122 | 123 | Returns: 124 | ip: This is the inner product result, or the image information in the ESPIRiT subspace. 125 | proj: This is the resulting projection. If the ESPIRiT operator is E, then proj = E E^H x, where H is 126 | the hermitian. 127 | null: This is the null projection, which is equal to x - proj. 128 | """ 129 | ip = np.zeros(x.shape).astype(np.complex64) 130 | proj = np.zeros(x.shape).astype(np.complex64) 131 | for qdx in range(0, esp.shape[4]): 132 | for pdx in range(0, esp.shape[3]): 133 | ip[:, :, :, qdx] = ip[:, :, :, qdx] + x[:, :, :, pdx] * esp[:, :, :, pdx, qdx].conj() 134 | 135 | for qdx in range(0, esp.shape[4]): 136 | for pdx in range(0, esp.shape[3]): 137 | proj[:, :, :, pdx] = proj[:, :, :, pdx] + ip[:, :, :, qdx] * esp[:, :, :, pdx, qdx] 138 | 139 | return (ip, proj, x - proj) 140 | -------------------------------------------------------------------------------- /estvar.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from espirit import calreg, calmat, ifft 4 | 5 | def estvar(X, k, r, method): 6 | if (method == 1): 7 | return estvar_cov(X, k, r) 8 | elif (method == 2): 9 | return estvar_meansub(X, k, r) 10 | elif (method == 3): 11 | return estvar_patches(X, k, r) 12 | return -1 13 | 14 | def estvar_cov(X, k, r): 15 | # Extract calibration region. 16 | C = calreg(X, r) 17 | # Get low-res image by taking unitary ifft. 18 | c = ifft(C, (0, 1, 2)) 19 | # Reshape image into vectors in C^[m x n] where n is number of channels and 20 | # m is the product of the image dimensions 21 | X = np.reshape(c, (c.shape[0] * c.shape[1] * c.shape[2], c.shape[3])) 22 | # Get mean of vectors 23 | u = np.mean(X, 1) 24 | u.shape = (len(u), 1) 25 | # Subtract mean from data. 26 | X = X - np.tile(u, (1, c.shape[3])) 27 | # Construct covariance matrix and get its eigenvalues 28 | cov = X.conj().T.dot(X) 29 | d, v = np.linalg.eig(cov) 30 | # Scale eigenvalues down by m and sorting in ascending order. 31 | d = (np.abs(d)/X.shape[0]) 32 | d = np.sort(d)[::-1] 33 | assert (d[0] > d[-1]) 34 | # Discarding first two eigenvalues. 35 | d = d[2:] 36 | # Discarding any eigenvalues below image precision 37 | d = d[d > 1e-6] 38 | # Estimate: Mean of the remining eigenvalues. 39 | return np.mean(d) 40 | 41 | def estvar_meansub(X, k, r): 42 | C = calreg(X, r) 43 | c = ifft(C, (0, 1, 2)) 44 | 45 | vec = np.reshape(c, (c.shape[0] * c.shape[1] * c.shape[2], c.shape[3])) 46 | u = np.mean(vec, 1) 47 | u.shape = (len(u), 1) 48 | vec = vec - np.tile(u, (1, c.shape[3])) 49 | #vec = np.power(np.abs(vec.flatten()), 2) 50 | return np.var(vec) 51 | 52 | def estvar_patches(X, k, r): 53 | 54 | # http://www.cv-foundation.org/openaccess/content_iccv_2015/papers/Chen_An_Efficient_Statistical_ICCV_2015_paper.pdf 55 | 56 | C = calreg(X, r) 57 | c = ifft(C, (0, 1, 2)) 58 | 59 | sx = np.shape(c)[0] 60 | sy = np.shape(c)[1] 61 | sz = np.shape(c)[2] 62 | nc = np.shape(c)[3] 63 | 64 | p = (sx > 1) + (sy > 1) + (sz > 1) 65 | 66 | X = np.zeros((k**p * nc, (r - k + 1)**p)).astype(np.complex64) 67 | idx = 0 68 | for xdx in range(max(1, c.shape[0] - k + 1)): 69 | for ydx in range(max(1, c.shape[1] - k + 1)): 70 | for zdx in range(max(1, c.shape[2] - k + 1)): 71 | # numpy handles when the indices are too big 72 | block = c[xdx:xdx+k, ydx:ydx+k, zdx:zdx+k, :].astype(np.complex64) 73 | X[:, idx] = block.flatten() 74 | idx = idx + 1 75 | 76 | u = np.mean(X, 1) 77 | #u = np.sum(X, 1) 78 | 79 | cov = np.zeros((X.shape[0], X.shape[0])).astype(np.complex64) 80 | for idx in range((r - k + 1)**p): 81 | vec = X[:, idx] 82 | dif = vec - u 83 | dif.shape = (len(dif), 1) 84 | cov = cov + dif.dot(dif.conj().T) 85 | cov = cov/((r-k+1)**p) 86 | 87 | d, v = np.linalg.eig(cov) 88 | d = np.abs(np.sort(d)[::-1]) 89 | assert(d[0] > d[-1]) 90 | d = d[:k**p] 91 | 92 | tau_arr = np.zeros((k**p, 1)).astype(np.complex64) 93 | med_arr = np.zeros((k**p, 1)).astype(np.complex64) 94 | for idx in range(k**p): 95 | tau_arr[idx] = np.mean(d[idx:]) 96 | med_arr[idx] = np.median(d[idx:]) 97 | 98 | diff = np.abs(med_arr - tau_arr) 99 | idx = np.argmin(diff) 100 | 101 | return np.abs(med_arr[idx]) 102 | -------------------------------------------------------------------------------- /example_espirit.py: -------------------------------------------------------------------------------- 1 | import cfl 2 | from espirit import espirit, espirit_proj, ifft 3 | 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | # Load data 8 | X = cfl.readcfl('data/knee') 9 | x = ifft(X, (0, 1, 2)) 10 | 11 | # Derive ESPIRiT operator 12 | esp = espirit(X, 6, 24, 0.01, 0.9925) 13 | # Do projections 14 | ip, proj, null = espirit_proj(x, esp) 15 | 16 | # Figure code 17 | 18 | esp = np.squeeze(esp) 19 | x = np.squeeze(x) 20 | ip = np.squeeze(ip) 21 | proj = np.squeeze(proj) 22 | null = np.squeeze(null) 23 | 24 | print("Close figures to continue execution...") 25 | 26 | # Display ESPIRiT operator 27 | for idx in range(8): 28 | for jdx in range(8): 29 | plt.subplot(8, 8, (idx * 8 + jdx) + 1) 30 | plt.imshow(np.abs(esp[:,:,idx,jdx]), cmap='gray') 31 | plt.axis('off') 32 | plt.show() 33 | 34 | dspx = np.power(np.abs(np.concatenate((x[:, :, 0], x[:, :, 1], x[:, :, 2], x[:, :, 3], x[:, :, 4], x[:, :, 5], x[:, :, 6], x[:, :, 7]), 0)), 1/3) 35 | dspip = np.power(np.abs(np.concatenate((ip[:, :, 0], ip[:, :, 1], ip[:, :, 2], ip[:, :, 3], ip[:, :, 4], ip[:, :, 5], ip[:, :, 6], ip[:, :, 7]), 0)), 1/3) 36 | dspproj = np.power(np.abs(np.concatenate((proj[:, :, 0], proj[:, :, 1], proj[:, :, 2], proj[:, :, 3], proj[:, :, 4], proj[:, :, 5], proj[:, :, 6], proj[:, :, 7]), 0)), 1/3) 37 | dspnull = np.power(np.abs(np.concatenate((null[:, :, 0], null[:, :, 1], null[:, :, 2], null[:, :, 3], null[:, :, 4], null[:, :, 5], null[:, :, 6], null[:, :, 7]), 0)), 1/3) 38 | 39 | print("NOTE: Contrast has been changed") 40 | 41 | # Display ESPIRiT projection results 42 | plt.subplot(1, 4, 1) 43 | plt.imshow(dspx, cmap='gray') 44 | plt.title('Data') 45 | plt.axis('off') 46 | plt.subplot(1, 4, 2) 47 | plt.imshow(dspip, cmap='gray') 48 | plt.title('Inner product') 49 | plt.axis('off') 50 | plt.subplot(1, 4, 3) 51 | plt.imshow(dspproj, cmap='gray') 52 | plt.title('Projection') 53 | plt.axis('off') 54 | plt.subplot(1, 4, 4) 55 | plt.imshow(dspnull, cmap='gray') 56 | plt.title('Null Projection') 57 | plt.axis('off') 58 | plt.show() 59 | -------------------------------------------------------------------------------- /example_estvar.py: -------------------------------------------------------------------------------- 1 | import cfl 2 | from estvar import estvar 3 | 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | k = 6 8 | r = 24 9 | 10 | #var = 1000000 11 | #X = cfl.readcfl('data/knee/noisy') 12 | 13 | #var = 100 14 | #X = cfl.readcfl('data/brain/noisy') 15 | 16 | var = 100 17 | X = cfl.readcfl('data/alias/noisy') 18 | 19 | print("Method 1") 20 | est = estvar(X, k, r, 1) 21 | print("True variance: %f" % var) 22 | print("Estimated noise variance: %f" % est) 23 | print("Ratio of estimated over true value: %f" % (est/var)) 24 | 25 | print("Method 2") 26 | est = estvar(X, k, r, 2) 27 | print("True variance: %f" % var) 28 | print("Estimated noise variance: %f" % est) 29 | print("Ratio of estimated over true value: %f" % (est/var)) 30 | 31 | print("Method 3") 32 | est = estvar(X, k, r, 3) 33 | print("True variance: %f" % var) 34 | print("Estimated noise variance: %f" % est) 35 | print("Ratio of estimated over true value: %f" % (est/var)) 36 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |

espirit-python

2 |

Description

3 |

ESPIRiT implemented in Python. GitHub repository located here: https://github.com/mikgroup/espirit-python.

4 |

Prerequisites

5 |

ESPIRiT itself requires numpy. matplotlib is needed to display images.

6 |

Usage

7 |

Please see example.py for a usage example.

8 |

References

9 |
    10 |
  • Uecker, M., Lai, P., Murphy, M. J., Virtue, P., Elad, M., Pauly, J. M., ... & Lustig, M. (2014). ESPIRiT—an eigenvalue approach to autocalibrating parallel MRI: where SENSE meets GRAPPA. Magnetic resonance in medicine, 71(3), 990-1001.
  • 11 |
  • Data from mridata.org
  • 12 |
13 | --------------------------------------------------------------------------------