├── __init__.py ├── signaleqns.py ├── coils.py ├── README.md ├── basics.py ├── classes.py └── mrsigpy.py /__init__.py: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /signaleqns.py: -------------------------------------------------------------------------------- 1 | 2 | #### Signal Equations 3 | def spgr(t1, tr, angle,m0=1.): 4 | e1 = np.exp(-tr/t1) 5 | alpha = angle/180.*np.pi 6 | return (1-e1)*np.sin(alpha)/(1-e1*np.cos(alpha)) 7 | 8 | 9 | def twospgr(angle=135.,tr=np.array([100, 70, 50, 30, 20, 15.5, 25, 35, 40]), t1a = 30.,t1b = 10.,m0a = .5, m0b = 0.5): 10 | spgr1 = spgr(t1a,tr,angle) 11 | spgr2 = spgr(t1b,tr,angle) 12 | spgrsum = m0a*spgr1+m0b*spgr2 13 | return spgrsum 14 | 15 | 16 | def bssfp(t1,tr,angle,te, t2,m0=1.): 17 | e1 = np.exp(-tr/t1) 18 | e2 = np.exp(-te/t2) 19 | alpha = angle/180.*np.pi 20 | return m0*(np.sqrt(e2*np.sin(alpha)*(1-e1)))/(1-(e1-e2)*np.cos(alpha)-e1*e2) 21 | 22 | 23 | def ssfpfid(t1,tr,angle,te, t2,m0=1.): 24 | e1 = np.exp(-tr/t1) 25 | e2 = np.exp(-tr/t2) 26 | alpha = angle/180.*np.pi 27 | p = (1-e1*np.cos(alpha)) 28 | q = e2*(e1-np.cos(alpha)) 29 | r = (1-e2**2)/np.sqrt(p**2-q**2) 30 | signal = m0*np.tan(alpha/2)*(1-(e1-np.cos(alpha))) 31 | return signal 32 | 33 | def ssfpecho(t1,tr,angle,te, t2,m0=1.): 34 | e1 = np.exp(-tr/t1) 35 | e2 = np.exp(-te/t2) 36 | alpha = angle/180.*np.pi 37 | p = (1-e1*np.cos(alpha)) 38 | q = e2*(e1-np.cos(alpha)) 39 | r = (1-e2**2)/np.sqrt(p**2-q**2) 40 | signal = m0*np.tan(alpha/2)*(1-(1-np.cos(alpha))) 41 | 42 | return signal 43 | 44 | def ssfpboth(t1,tr,angle,te,t2,m0=1.): 45 | echosig = ssfpecho(t1,tr,angle,te, t2,m0) 46 | fidsig = ssfpfid(t1,tr,angle,te, t2,m0) 47 | return echosig+fidsig 48 | 49 | def Mxyss (t1, tr, t2, te, flipangle, phaseangle, domega=0.): 50 | e1 = np.exp(-tr/t1) 51 | e2 = np.exp(-tr/t2) 52 | alpha = flipangle/180.*np.pi 53 | theta = phaseangle/180.*np.pi+domega*tr 54 | f1 = (1.-e1)*e2*sin(alpha)*sin(theta) 55 | f2 = (1.-e1*cos(alpha))*(1-e2*cos(theta))-e2*(e1-cos(alpha))*(e2-cos(theta)) 56 | f3 = (1.-e1)*e2*sin(alpha)*(cos(theta)-e2) 57 | f4 = (1.-e1*cos(alpha))*(1-e2*cos(theta))-e2*(e1-cos(alpha))*(e2-cos(theta)) 58 | return f1/f2, f3/f4, f1/f2+1j*f3/f4 59 | -------------------------------------------------------------------------------- /coils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | mur = 1 4 | mu0 = 1 5 | c = 2.999e8 # speed of light 6 | 7 | ### comment 8 | proton_freq = 128.77 #asdf asdf 9 | 10 | def findpower(voltage = None, resistance = None, current = None, power = None): 11 | all_vals = {'voltage': voltage, 12 | 'resistance': resistance, 13 | 'current': current, 14 | 'power': power} 15 | if voltage is not None and resistance is not None: 16 | all_vals['power'] = voltage**2./resistance 17 | if voltage is not None and current is not None: 18 | all_vals['power'] = voltage*curren 19 | if current is not None and resistance is not None: 20 | all_vals['power'] = current**2.*resistance 21 | if voltage is not None and power is not None: 22 | all_vals['resistance'] = voltage**2./power 23 | if current is not None and resistance is not None: 24 | all_vals['voltage'] = current*resistance 25 | if voltage is not None and current is not None: 26 | all_vals['resistance'] = voltage/current 27 | if voltage is not None and resistance is not None: 28 | all_vals['current'] = voltage/resistance 29 | return all_vals 30 | 31 | def radiation_dipole(I, length, wavelength): 32 | average_power = np.pi**2 / 3/c*(I*length/wavelength)**2 33 | return average_power 34 | 35 | def induct_solenoid(D,d,N): 36 | return N*N*mu0*mur*D/2.*(np.log(8.*D/d)-2) 37 | 38 | 39 | def findQ(bandwidth, f0 = proton_freq): 40 | Q = f0/bandwidth 41 | return Q 42 | 43 | def findbandwidth(fhigh, flow): 44 | return fhigh - flow 45 | 46 | def findreactance(C=None, L=None, R=0., f0 = proton_freq): 47 | XL = 2*np.pi*f0*L 48 | XC = 1./(2*np.pi*f0*C) 49 | Xtotal = XL-XC 50 | Z = np.sqrt(R**2+Xtotal**2) 51 | flow_cutoff = -R/2./L + np.sqrt(1/(L*C)+(R/2./L)**2) 52 | fhigh_cutoff = R/2./L + np.sqrt(1/(L*C)+(R/2./L)**2) 53 | Q = 2*np.pi*f0*L/R 54 | if XL > XC: 55 | return XL, XC, Z, Q, flow_cutoff, fhigh_cutoff, 'inductive' 56 | else: 57 | return XL, XC, Z, Q,f_cutoff, fhigh_cutoff, 'capacitive' 58 | 59 | def findomega(L, C): 60 | omega = 1/np.sqrt(L*C) 61 | return omega 62 | 63 | def findL(C=1., freq = proton_freq): 64 | return 1/(2.*np.pi*freq)**2./C 65 | 66 | def findC(L=1., freq = proton_freq): 67 | return 1/(2.*np.pi*freq)**2./L 68 | 69 | def findnewC(C, f1 = proton_freq*.9, f_desired = proton_freq): 70 | return C*f1*f1/f_desired/f_desired 71 | 72 | def findbyZ0(Z0=50., f0 = proton_freq): 73 | L = Z0/(2.*np.pi*f0) 74 | C = 1/(2.*np.pi*f0*Z0) 75 | return L,C 76 | 77 | 78 | 79 | def microstrip(speed = None, length = None, eps_eff = 1.): 80 | if speed is None: 81 | speed = c/(2.*length *np.sqrt(eps_eff)) 82 | return speed 83 | elif length is None: 84 | length = c/(2.*speed *np.sqrt(eps_eff)) 85 | return length 86 | else: 87 | return 0 88 | 89 | 90 | r_colors = {'black':0, 91 | 'brown':1, 92 | 'red':2, 93 | 'orange':3, 94 | 'yellow':4, 95 | 'green':5, 96 | 'blue':6, 97 | 'violet':7, 98 | 'grey':8, 99 | 'white':9, 100 | 'gold':-1, 101 | 'silver':-2, 102 | } 103 | def resistor_value(color1 = 'black', color2 = 'black', color3 = 'black'): 104 | return (r_colors[color1]*10+r_colors[color2])*r_colors[color3] 105 | 106 | 107 | def biot_savart_db(I, dl, r, J = None): 108 | if J is None: 109 | Jdl = I*dl 110 | else: 111 | Jdl = J*dl 112 | magnitude = np.sqrt(r[0]**2. + r[1]**2. + r[2]**2.) 113 | factor = mu/4./np.pi/magnitude 114 | s1 = Jdl[1]*r[2] - Jdl[2]*r[1] 115 | s2 = Jdl[2]*r[0] - Jdl[0]*r[2] 116 | s3 = Jdl[0]*r[1] - Jdl[1]*r[2] 117 | s1 = s1/factor 118 | s2 = s2/factor 119 | s3 = s3/factor 120 | return s1, s2, s3 121 | 122 | 123 | def VSWR(S11=0): 124 | VSWR = (1+np.abs(S11))/(1-np.abs(S11)) 125 | return VSWR 126 | 127 | def amplifier_stability(S11, S12, S21, S22): 128 | Delta = S11*S22-S12*S21 129 | center = np.conj(S11-Delta*np.conj(S22))/(np.abs(S11)**2-np.abs(Delta)**2) 130 | radius = np.abs(S12*S21/(np.abs(S11)**2-np.abs(Delta)**2)) 131 | rollet = (1-np.abs(S11)**2-np.abs(S22)**2-np.abs(Delta)**2)/(2.*np.abs(S12*S21)) 132 | is_stable = np.abs(K) > 1 and np.abs(Delta) < 1 133 | return center, radius, Delta, rollet, is_stable 134 | 135 | 136 | 137 | def z_to_s(Zload, Z0=50.): 138 | S11 = (Zload - Z0)/(Zload+Z0) 139 | return S11 140 | 141 | def gamma_to_z(gamma = 0, Z0 = 50.): 142 | Z = Z0 * (1+gamma)/(1-gamma) 143 | return Z 144 | 145 | 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mripy 2 | Consolidation of MRI Databases 3 | (AWESOME MRI! -- for google) 4 | 5 | 6 | Welcome to the mripy wiki! 7 | 8 | # 0D MRI 9 | 10 | ## OpenSource MRI 11 | * Openmri.github.io/ocra 12 | * https://github.com/opencore Opencore nmr 13 | * https://github.com/mtwieg/NMR/ Mtwieg github.com 14 | * http://od1n.sourceforge.net/ 15 | medusa Stanford 16 | * http://www.opensourceimaging.org/project/gr-mri-a-software-package-for-magnetic-resonance-imaging-using-software-defined-radios/ 17 | * https://imr-framework.github.io/ (Pulseq) 18 | OCRA 19 | 20 | ## MEG 21 | * https://github.com/mne-tools/mne-hcp 22 | 23 | ## Vendor Specific Support 24 | * https://spinsights.chem.agilent.com/login.jspa?referer=%252Findex.jspa 25 | * https://collaborate.mr.gehealthcare.com/ 26 | * https://www.mr-idea.com/ 27 | * https://www.bruker.com/service/information-communication/nmr-pulse-program-lib/bruker-user-library.html 28 | 29 | # 1D MRI 30 | 31 | ## Spectroscopy 32 | * https://scion.duhs.duke.edu/vespa/gamma 33 | * https://github.com/chenkonturek/MRS_MRI_libs 34 | * https://github.com/openmrslab/suspect 35 | * https://github.com/SIVICLab/sivic 36 | * https://github.com/cni/MRS 37 | * https://github.com/jjhelmus/nmrglue 38 | * https://github.com/LarsonLab/hyperpolarized-mri-toolbox 39 | * http://hugadams.github.io/scikit-spectra/ 40 | 41 | ## Pulse design 42 | * https://github.com/ekgibbons/slr/blob/master/python/slr.py 43 | * https://github.com/LarsonLab/Spectral-Spatial-RF-Pulse-Design 44 | * https://scion.duhs.duke.edu/vespa/ 45 | 46 | ## Bloch Equations 47 | * https://github.com/bretglun/BlochBuster 48 | * https://github.com/leoliuf/MRiLab 49 | * https://github.com/neji49/bloch-simulator-python 50 | * https://github.com/k7hoven/blochsimu 51 | * https://github.com/mriesch-tum/mbsolve 52 | * https://pypi.org/project/blochsimu/ 53 | * http://qutip.org/download.html 54 | * http://www.drcmr.dk/bloch 55 | * https://github.com/jtamir/mri-sim-py/blob/master/epg/epg.py 56 | 57 | ## Pulse Sequence Design 58 | * https://github.com/pulseq/pulseq 59 | * https://github.com/magland/sequencetree5 60 | * https://pulseq.github.io/ 61 | * https://toppemri.github.io/ 62 | * http://www.jemris.org/ug_command_line.html 63 | * https://github.com/wgrissom/ISMRM-RF-Pulse-Design-Challenge 64 | 65 | ## SPGR 66 | * https://github.com/mezera/mrQ 67 | 68 | ## Bruker 69 | * https://github.com/tesch1/BruKitchen 70 | * https://github.com/jdoepfert/brukerMRI 71 | * https://github.com/cmwalker/Bruker 72 | * https://github.com/jamesjcook/mat_recon_pipe 73 | * https://github.com/bennomeier/pyNMR 74 | 75 | 76 | # 2D and 3D MRI 77 | 78 | ## Image reconstruction 79 | * https://github.com/mrirecon/bart 80 | * https://github.com/gadgetron/gadgetron-python-ismrmrd-client 81 | * https://github.com/ismrmrd/ismrmrd-python 82 | * https://github.com/mikgroup/espirit-python 83 | 84 | 85 | # Offline / Adjacent Recon 86 | * https//yarra.rocks 87 | * https://github.com/gadgetron/gadgetron 88 | 89 | 90 | 91 | 92 | ## Non-Uniform FFT 93 | * https://github.com/dfm/python-nufft 94 | * https://github.com/jakevdp/nufftpy 95 | * https://github.com/cea-cosmic/pysap 96 | 97 | ## Registration 98 | * https://pypi.org/project/imreg_dft/ 99 | * https://simpleelastix.github.io/ 100 | * https://fsl.fmrib.ox.ac.uk/fsl/fslwiki 101 | 102 | 103 | ## Neuroimaging in Python 104 | * https://github.com/swederik/nipype 105 | * http://nipype.readthedocs.io/en/latest/devel/software_using_nipype.html 106 | 107 | ## Susceptibility Mapping 108 | * https://github.com/lruthotto/QSMReconstruction.m 109 | * https://github.com/sunhongfu/QSM 110 | * https://github.com/mathieuboudreau/qsm-tools 111 | * https://github.com/daiweis05/QITK 112 | * https://github.com/philgd/CVI-MRI 113 | 114 | ## Compressed Sensing 115 | * https://github.com/topics/compressed-sensing 116 | * https://github.com/thomaskuestner/CS_MoCo_LAB 117 | * https://mrfil.github.io/PowerGrid 118 | 119 | 120 | ## Spin echo 121 | * https://github.com/jyhmiinlin/cineFSE 122 | * https://github.com/jtamir/mri-sim-py/blob/master/epg/epg.py 123 | * http://mrsrl.stanford.edu/~brian/bloch/ 124 | 125 | 126 | * https://www.ismrm.org/MR-Hub/ 127 | 128 | ## Other 129 | 130 | * https://github.com/sammba-mri/sammba-mri 131 | * https://bitbucket.org/norok2/pymrt/ 132 | 133 | 134 | 135 | # 4D MRI 136 | 137 | ## T1 and T2 Mapping 138 | * https://github.com/gattia/PyMap 139 | * https://github.com/gdurin/pyFitting 140 | * https://github.com/neuropoly/qMRLab 141 | 142 | ## DCE 143 | * https://github.com/welcheb/pydcemri 144 | 145 | ## Diffusion 146 | * https://github.com/flohorovicic/pynoddy 147 | * https://github.com/ecaruyer/qspace 148 | * https://github.com/jsjol/NOW 149 | * https://diffusionmritool.github.io/tutorial_qspacesampling.html 150 | * https://github.com/nipy/dipy 151 | * https://github.com/AthenaEPI/dmipy 152 | * https://www.nitrc.org/projects/noddi_toolbox 153 | * https://github.com/cbclab/MDT 154 | * https://github.com/NYU-DiffusionMRI/Diffusion-Kurtosis-Imaging 155 | * https://github.com/DiffusionMRITool/dmritool-MultiShellSampling 156 | * https://bitbucket.org/siawoosh/acid-artefact-correction-in-diffusion-mri/ 157 | 158 | 159 | ## CEST Sources 160 | * https://github.com/cest-sources/BM_sim_fit 161 | 162 | ## fMRI and pupil size analysis 163 | * https://github.com/tknapen/FIRDeconvolution 164 | 165 | ## Intracranial electrodes in MRI 166 | * https://github.com/kingjr/ecoggui 167 | 168 | 169 | 170 | # ND MRI 171 | 172 | ## T1 T2 Shuffling 173 | * https://github.com/jtamir/t2shuffling-support 174 | 175 | ## MR Fingerprinting 176 | * https://github.com/welcheb/Hamburg_MRF_workshop_VUMC_results 177 | * https://github.com/peng-cao/mripy 178 | * https://www.ismrm.org/mri_unbound/sequence.htm 179 | * https://bitbucket.org/asslaender/nyu_mrf_recon 180 | 181 | * https://github.com/Corey-Zumar/MRI-Reconstruction 182 | * https://github.com/StephanZheng/neural-fingerprinting 183 | 184 | 185 | ## Connectome 186 | * https://www.nitrc.org/projects/cviewer/ 187 | * https://github.com/dPys/PyNets 188 | 189 | ## Biobank 190 | * https://oai.epi-ucsf.org/datarelease/ 191 | * http://www.ukbiobank.ac.uk/ 192 | * http://cai2r.net/resources/software/one-hundred-knee-mri-cases 193 | 194 | 195 | ## Radiomics 196 | * https://github.com/Radiomics/pyradiomics 197 | * https://github.com/Radiomics/SlicerRadiomics 198 | * https://pypi.org/project/MedPy/ 199 | * https://github.com/QTIM-Lab/DeepNeuro 200 | 201 | ### Knee 202 | * https://bitbucket.org/marcniethammer/ksrt/wiki/Home 203 | 204 | ## NI Learn for Brain Mapping 205 | * https://github.com/nilearn/nilearn 206 | * https://github.com/WinawerLab 207 | * https://github.com/mwaskom/lyman 208 | * https://github.com/jlohmeier/atlasBREX 209 | * https://github.com/taigw/brats17 210 | 211 | 212 | ## Machine learning 213 | * https://github.com/js3611/Deep-MRI-Reconstruction 214 | * https://github.com/Kamnitsask/deepmedic 215 | * Github.com/FlorianKnoll 216 | * Colah.github.io/post/2015-08-Backprop 217 | * https://qtim-lab.github.io/research/ 218 | 219 | 220 | ## General Machine Learning (not MRI) 221 | * https://github.com/KaimingHe/deep-residual-networks 222 | * https://github.com/ry/tensorflow-resnet 223 | * https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py 224 | * https://github.com/DeepLearnPhysics/pytorch-resnet-example 225 | * https://github.com/alexjc/neural-enhance 226 | * https://github.com/david-gpu/srez 227 | * https://github.com/mriphysics 228 | * https://github.com/khcs/brain-synthesis-lesion-segmentation/blob/master/utils/merge_2d_test_to_nii.py 229 | * https://github.com/ad12/DOSMA 230 | 231 | 232 | # Radiofrequency and Electromagnetism 233 | 234 | ## FDTD 235 | * https://github.com/stevengj/meep 236 | 237 | ## FDFD 238 | * http://www.mit.edu/~wsshin/maxwellfdfd.html 239 | 240 | ## Finite Element 241 | * http://sfepy.org/doc-devel/index.html 242 | * https://github.com/AppliedMechanics-EAFIT/SolidsPy 243 | 244 | ## RF Coils 245 | * https://github.com/neuropoly/coil_simulation_web 246 | * https://github.com/BIC-MNI/mrisim 247 | * https://github.com/leoliuf/MRiLab 248 | * https://github.com/usc-mrel/lowfieldsim 249 | * https://github.com/adamkettinger/phase-optimization-for-VCC 250 | * https://github.com/thanospol/MARIE 251 | * https://bitbucket.org/wgrissom/gnuradio-mri/src/master/ 252 | 253 | ## Gradient Coils 254 | * https://github.com/gBringout/CoilDesign 255 | 256 | ## Mesh Extraction 257 | * https://opensource.ncsa.illinois.edu/bitbucket/projects/CATS/repos/extractors-mri/browse/mri2mesh 258 | 259 | 260 | # Other Modalities 261 | 262 | ## Xray 263 | * https://github.com/pydata/xarray 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | #### Other (part a.0.0.0.0.0.0.1) 281 | 282 | https://web.eecs.umich.edu/~fessler/code/ 283 | BART 284 | http://gpilab.com/ 285 | http://mridata.org/ 286 | 287 | 288 | 289 | 290 | ## Other (part a.0.0.0.0.0.0.2) 291 | 292 | 293 | http://people.eecs.berkeley.edu/~mlustig/Software.html 294 | 295 | https://gitlab.com/mariadeprez/irtk-public 296 | 297 | https://github.com/khammernik 298 | 299 | ### Tools to think about 300 | 301 | Check out Gitlab.com, Github.com, Bitbucket.com, Docker, Jupyter, Binder, BART, MIRT, PowerGrid, GPI -------------------------------------------------------------------------------- /basics.py: -------------------------------------------------------------------------------- 1 | ## Some very basic MRI functions. 2 | ## A random assortment of tools 3 | 4 | import numpy as np 5 | 6 | 7 | 8 | def dice_coeff(im1, im2): 9 | return np.sum(im1[im1=im2])*2.0 / (np.sum(im1) + np.sum(im2)) 10 | 11 | 12 | def read_roi_xml(filename): 13 | obj = untangle.parse(filename) 14 | p0 = obj.ANNOTATION.OBJECT.GEOM.FIGURES.FIGURE.SEGMENTS.SEGMENT.POINTS.P 15 | Xs = [] 16 | Ys = [] 17 | for p in p0: 18 | x = float(p.X.cdata) 19 | y = float(p.Y.cdata) 20 | Xs.append(x) 21 | Ys.append(y) 22 | return Xs, Ys 23 | 24 | 25 | 26 | 27 | def sitkregister(regim, otherim,register_type = 'affine',parent=None,return_param_map=False,param_map = None): 28 | #newimage,self.sitk_param_map = sitkregister(im1, im2,register_type = 'repeat',param_map = self.sitk_param_map, parent=self.parent,return_param_map=True) 29 | import SimpleITK as sitk 30 | print regim.shape,' regimshape' 31 | import SimpleITK as sitk 32 | print regim.shape,' regimshape' 33 | fixedImage = sitk.GetImageFromArray(regim) 34 | movingImage = sitk.GetImageFromArray(otherim) 35 | elastixImageFilter = sitk.ElastixImageFilter() 36 | if register_type != 'repeat': 37 | parameterMap = sitk.GetDefaultParameterMap(register_type) #'translation' 38 | #parameterMap1 = sitk.GetDefaultParameterMap('affine') #'translation' 39 | #parameterMap.append(sitk.GetDefaultParameterMaprigid') #'translation' 40 | #composite_transform = sitk.Transform(parame('translation')) 41 | #parameterMap = sitk.GetDefaultParameterMap('nonterMap0) 42 | #composite_transform.AddTransform(parameterMap1) 43 | elastixImageFilter.SetFixedImage(fixedImage) 44 | elastixImageFilter.SetMovingImage([movingImage, movingImage, movingImage, movingImage]) 45 | elastixImageFilter.SetParameterMap(parameterMap) 46 | elastixImageFilter.Execute() 47 | else: 48 | #parameterMap = sitk.GetDefaultParameterMap('translation') 49 | #elastixImageFilter.SetParameterMap(param_map) 50 | # 51 | elastixImageFilter = param_map 52 | elastixImageFilter.SetMovingImage(movingImage) 53 | #elastixImageFilter.SetFixedImage(fixedImage) 54 | elastixImageFilter.Execute() 55 | transformParameterMap = elastixImageFilter.GetTransformParameterMap() 56 | resultImage = elastixImageFilter.GetResultImage() 57 | if parent != None: 58 | parent.sitkdata = resultImage 59 | if return_param_map: 60 | return sitk.GetArrayFromImage(resultImage), elastixImageFilter # transformParameterMap 61 | return sitk.GetArrayFromImage(resultImage) 62 | sitkdata = sitk.GetImageFromArray(resultImage) 63 | try: 64 | return sitk.GetImageFromArray(resultImage) 65 | except: 66 | return resultImage, transformParameterMap 67 | 68 | 69 | 70 | class gradient_class(): 71 | def __init__(self,k=np.array(10)): 72 | self.k = k 73 | def calc_gradient(self): 74 | self.gradient = np.gradient(self.k) 75 | self.slew = np.gradient(self.gradient) 76 | def calc_k(self): 77 | self.gradient = np.cumsum(self.slew) 78 | self.k = np.cumsum(self.gradient) 79 | def slewlimit(self, slewmax=.1): 80 | slew = self.slew 81 | slewnew = np.array(slew) 82 | slewnew[slew>slewmax] = slewmax 83 | slewnew[slew<-slewmax] = -slewmax 84 | self.slew = slewnew 85 | return slewnew 86 | def gradlimit(self, gradmax=.1): 87 | grad = self.gradient 88 | grad0 = np.abs(grad)gradmax] = gradmax 91 | gradnew[grad<-gradmax] = -gradmax 92 | self.gradient = gradnew 93 | return gradnew 94 | def ramp(self,vstart=0, vend = 1, vmax = .2,vdotmax=.2): 95 | # v could be gradient, or kspace. 96 | # vdot means the derivative of v, like slew rate or gradient 97 | vdiff = vend - vstart 98 | nvt = np.abs(vdiff/vdotmax) 99 | vdots = np.arange(nvt)*vdotmax 100 | v = np.cumsum(vdots) 101 | v[v>vmax] = vmax 102 | vsum = np.cumsum(v) 103 | print(v) 104 | if len(v)>1: 105 | vdots = np.gradient(v) 106 | else: 107 | vdots = [0] 108 | return v, vdots, vsum #, k, slew 109 | def addrampup(self,gradx,gradmax=.2) 110 | gradx = np.hstack([ramp(0,gradx[0],vmax=gradmax)[0],gradx]) 111 | return gradx 112 | 113 | 114 | def ernst(t1=0,tr=0,alpha=0,alpharad = 0): 115 | varA = -1 116 | if alpha==0 and alpharad==0: #solve for alpha 117 | varA = np.arccos(np.exp(-tr/t1))*180/np.pi #returns value in degrees 118 | elif tr==0: #solver for tr 119 | if alpharad==0: 120 | alpharad = alpha*np.pi/180 121 | varA = -t1*np.log(np.cos(alpharad)) 122 | elif t1==0: #solve for t1 123 | if alpharad ==0: 124 | alpharad = alpha*np.pi/180 125 | varA = -tr/np.log(np.cos(alpharad)) 126 | else: 127 | print('not enough defined values to calculate!') 128 | return varA 129 | 130 | 131 | def zerofill(im,padx,pady): 132 | "zero fills both sides of an image" 133 | im = np.array(im) 134 | try: 135 | newim = np.zeros((im.shape[0]+2*padx,im.shape[1]+2*pady),dtype=complex) 136 | newim[padx:(im.shape[0]+padx),pady:(im.shape[1]+pady)] = im 137 | except: 138 | newim = np.zeros((im.shape[0]+2*padx,im.shape[1]+2*pady,im.shape[2]),dtype=complex) 139 | newim[padx:(im.shape[0]+padx),pady:(im.shape[1]+pady),:] = im 140 | return newim 141 | 142 | #import sp.ndimage.interpolation.zoom as zoom 143 | 144 | 145 | 146 | def zerointerp(im,padx=0,pady=0,zoom=0): 147 | "returns a kspace zero-filled interpolated image" 148 | im = np.array(im) 149 | newim = im 150 | if padx>0 or pady>0 or zoom >0: 151 | if zoom!=0: 152 | padx = im.shape[0]*zoom/2 153 | pady = im.shape[1]*zoom/2 154 | im = im*(zoom+1)**2 155 | newim = np.fft.ifft2(zerofill(np.fft.fftshift(np.fft.fft2(im,axes=(0,1)),axes=(0,1)),padx,pady),axes=(0,1)) 156 | elif padx<0 or pady<0 or zoom <0: 157 | if zoom!=0: 158 | padx = np.int(im.shape[0]/zoom/2) 159 | pady = np.int(im.shape[1]/zoom/2 ) 160 | newim = np.fft.ifft2(np.fft.fftshift(np.fft.fft2(im,axes=(0,1)),axes=(0,1))[-padx:padx,-pady:pady],axes=(0,1)) 161 | return newim 162 | 163 | 164 | from numpy.fft import fftshift 165 | 166 | def im2kspace(im): 167 | return fftshift(np.fft.fft2(im,axes=(0,1)),axes=(0,1)) 168 | 169 | def kspace2im(ksp): 170 | return np.fft.ifft2(ksp,axes=(0,1)) 171 | 172 | def kspace3d2im(ksp): 173 | return np.fft.ifftn(ksp,axes=(0,1,2)) 174 | 175 | 176 | class shapes(): 177 | "returns a shape that can then be used as a filter" 178 | def __init__ (self,Nx,Ny=1,Nz=1,offsetx=.12345, offsety=.12345, offsetz=.12345, scalex=1, scaley=1,scalez=1): 179 | self.Nx = Nx 180 | self.Ny = Ny 181 | self.Nz = Nz 182 | if offsetx ==.12345: #just an unlikely number that no one would use 183 | offsetx = Nx/2 184 | if offsety ==.12345: 185 | offsety = Nx/2 186 | if offsetz ==.12345 and Nz>1: 187 | offsetz = Nx/2 188 | elif Nz ==1: 189 | offsetz = 0 190 | #if Nz>1: 191 | vectorz = np.arange(Nz) 192 | #else: 193 | # vectorz = np.arange(1)+1 194 | self.meshx,self.meshy,self.meshz = np.meshgrid(np.arange(Nx)-offsetx,np.arange(Ny)-offsety,vectorz-offsetz) 195 | self.meshx = self.meshx/scalex;self.meshy = self.meshy/scaley;self.meshz = self.meshz/scalez; 196 | self.meshr = np.sqrt(self.meshx**2+self.meshy**2+self.meshz**2) 197 | def circle(self,radius=1): 198 | self.out = np.zeros((self.Nx,self.Ny,self.Nz)) 199 | self.out[self.meshr1000)]=0 297 | im[np.nonzero(im0: 356 | newimage[:shifty] = im[-shifty:] 357 | newimage[shifty:] = im[:-shifty] 358 | im = np.array(newimage) 359 | if np.abs(shiftx)>0: 360 | newimage[:,:shiftx] = im[:,-shiftx:] 361 | newimage[:,shiftx:] = im[:,:-shiftx] 362 | im = np.array(newimage) 363 | if np.abs(shiftz)>0: 364 | newimage[:,:,:shiftz] = im[:,:,-shiftz:] 365 | newimage[:,:,shiftz:] = im[:,:,:-shiftz] 366 | return newimage 367 | 368 | 369 | def dixon2dicom(TE1=4.7, TE2=5.75, TE3=6.8, filename1='', filename2='', filename3=''): 370 | filename1,path1 = _checkloadfilename(filename1,title='Open TE1') 371 | fat,water,b0 = dixonfiles(TE1=TE1, TE2=TE2, TE3=TE3, filename1=filename1,\ 372 | filename2=filename2, filename3=filename3) 373 | fat = fat[64:192] 374 | water = water[64:192] 375 | fatdicomclass = vol2dicom(fliplr(r(fat)),justdoit=False,reducebyroi=False,studytype='fat') 376 | fatdicomclass.defpath = path1 377 | dicomfilename = _checkloadfilename(initialdir=fatdicomclass.defpath, title='Open Example Dicom File',filetypes=[('Dicom', '.IMA'),('all','')]) 378 | fatdicomclass.getdicom(dicomfilename[0]) 379 | savefatas = _checksavefilename(initialdir=fatdicomclass.defpath,title='Save Fat Dicoms as') 380 | fatdicomclass.dicominfo.ProtocolName = '3 Point Dixon - Fat' 381 | fatdicomclass.writeslices(savefatas[0]) 382 | waterdicomclass = vol2dicom(fliplr(r(water)),justdoit=False,reducebyroi=False,studytype='fat') 383 | waterdicomclass.defpath = fatdicomclass.defpath 384 | waterdicomclass.dicominfo = fatdicomclass.dicominfo 385 | waterdicomclass.dicominfo.ProtocolName = '3 Point Dixon - Water' 386 | waterdicomclass.dicominfo.SeriesDescription = 'Water' 387 | waterdicomclass.dicominfo.SeriesNumber = 79 388 | savewateras = _checksavefilename(initialdir=waterdicomclass.defpath,title='Save Water Dicoms as') 389 | waterdicomclass.writeslices(savewateras[0]) 390 | 391 | 392 | 393 | 394 | 395 | 396 | def dixon(acq1,acq2,acq3,teinc1,teinc2): 397 | def dixon4d(acq1,acq2,acq3,teinc1,teinc2): 398 | fat = [] 399 | water = [] 400 | b0 = [] 401 | for xi in xrange(np.shape(acq1)[3]): 402 | fata,watera,b0a = dixon2d3d(acq1[:,:,:,xi], acq2[:,:,:,xi], acq3[:,:,:,xi], teinc1, teinc2) 403 | fat.append(fata) 404 | water.append(watera) 405 | b0.append(b0a) 406 | fat = np.array(fat) 407 | water = np.array(water) 408 | fat = np.sqrt(np.sum(np.abs(fat)**2,axis=0)) 409 | water = np.sqrt(np.sum(np.abs(water)**2,axis=0)) 410 | return fat,water,b0 411 | def dixon2d3d(acq1, acq2, acq3, teinc1, teinc2, freqshift = 444): 412 | b0 = unwrap(np.angle(acq3 / acq1)) * 1000 / teinc2 / (2*np.pi); 413 | acq1 = np.array(np.array(acq1)) 414 | acq2 = np.array(np.array(acq2)) 415 | acq3 = np.array(np.array(acq3)) 416 | #b0 = np.angle(acq3 / acq1) * 1000 / teinc2 / (2*np.pi); #doesn't use gammabar, as it cancels out in D 417 | fat = 0; 418 | water = 0; 419 | B = np.exp(1j*freqshift*teinc1*1e-3*2*np.pi); 420 | D = np.exp(-1j*b0*teinc1*1e-3*2*np.pi); 421 | water = (acq1 - acq2*B*D)/(1-B); 422 | fat = acq1 - water; 423 | return fat,water,b0 424 | if len(np.shape(acq1))==4: 425 | fat,water,b0 = dixon4d(acq1, acq2, acq3, teinc1, teinc2) 426 | else: 427 | fat,water,b0 = dixon2d3d(acq1, acq2, acq3, teinc1, teinc2) 428 | return fat,water,b0 429 | 430 | 431 | 432 | 433 | def weightbynoise(imSpace): 434 | "returns imSpace, by SNR, calculated by the corners of the image" 435 | imSpace = np.array(imSpace) 436 | stdd=np.zeros([9,1]) 437 | try: 438 | for xi in xrange(imSpace.shape[3]): 439 | stdd[0] = np.std(imSpace[:,:,0,xi]) 440 | stdd[1] = np.std(imSpace[0:20,0:20,0,xi]) 441 | stdd[2 ]= np.std(imSpace[0:20,-20:,0,xi]) 442 | stdd[3] = np.std(imSpace[-20:,0:20,0,xi]) 443 | stdd[4] = np.std(imSpace[-20:,-20:,0,xi]) 444 | stdd[5] = np.std(imSpace[-10:,:,0,xi]) 445 | stdd[6] = np.std(imSpace[:10,:,0,xi]) 446 | stdd[7] = np.std(imSpace[:,:10,0,xi]) 447 | stdd[8] = np.std(imSpace[:,-10:,0,xi]) 448 | stddev = np.min(stdd) 449 | if stddev==0: 450 | stddev = 1 451 | imSpace[:,:,:,xi] = imSpace[:,:,:,xi]/stddev 452 | except: #no coils 453 | if 1: #for xi in xrange(imSpace.shape[3]): 454 | stdd[0] = np.std(imSpace[:,:,0]) 455 | stdd[1] = np.std(imSpace[0:20,0:20,0]) 456 | stdd[2 ]= np.std(imSpace[0:20,-20:,0]) 457 | stdd[3] = np.std(imSpace[-20:,0:20,0]) 458 | stdd[4] = np.std(imSpace[-20:,-20:,0]) 459 | stdd[5] = np.std(imSpace[-10:,:,0]) 460 | stdd[6] = np.std(imSpace[:10,:,0]) 461 | stdd[7] = np.std(imSpace[:,:10,0]) 462 | stdd[8] = np.std(imSpace[:,-10:,0]) 463 | stddev = np.min(stdd) 464 | if stddev==0: 465 | stddev = 1 466 | imSpace[:,:,:] = imSpace[:,:,:]/stddev 467 | return imSpace 468 | 469 | 470 | def t2eqn(x,a,t2): 471 | return (1/t2-a*x)**(-1) 472 | 473 | def fit_data(points, times, which='fexp'): 474 | from scipy.optimize import curve_fit 475 | points = np.asarray(np.abs(points)) 476 | times = np.asarray(times) 477 | def flinear(x,a,b): 478 | return a+x*b 479 | def fexp(x,a,b): 480 | return a*np.exp(-x/b) 481 | def fexpnoise(x,a,b,c): 482 | return a*np.exp(-x/b)+c 483 | def fdoubleexp(x,a,b,c,d): 484 | return a*np.exp(-x/b)+c*np.exp(-x/d) 485 | def inverseeqn(x,a): 486 | return a/x 487 | def inversion(x,a,b): 488 | return a*(1-np.exp(-x/b)) 489 | popt, pcov = curve_fit(eval(which), times, points) 490 | return popt 491 | 492 | def fit_loop_nocoils(imspace, TEs, which='fexp'): 493 | "imspace[x, y, TE, coil" 494 | yi = 0; ci = 0; ai = 0;xi=0; 495 | imspace = np.abs(imspace) 496 | li = np.array(imspace).shape 497 | llength = (li[0],li[1]) 498 | Aarray = np.zeros((llength)) 499 | Barray = np.zeros((llength)) 500 | print Aarray.shape 501 | print Barray.shape 502 | for ci in xrange(0, (imspace.shape)[3]): 503 | print( li[3]-ci, li[1]-yi, li[0]-xi, ai) 504 | for yi in range(0, (imspace.shape)[1]): 505 | print( li[3]-ci, li[1]-yi, li[0]-xi, ai,) 506 | for xi in xrange(0, (imspace.shape)[0]): 507 | #print imspace.shape 508 | if 1: 509 | #for ai in xrange(0, 1): #length(imspace)[3]): 510 | 511 | points = imspace[xi,yi,:,ci] 512 | try: 513 | popt = fit_data(points,TEs, which) 514 | except KeyboardInterrupt: 515 | sys.exit() 516 | #return Aarray, Barray 517 | except: 518 | popt = -0.0001,-0.0001 519 | Aarray[xi, yi, ci] = popt[0] 520 | Barray[xi, yi, ci] = popt[1] 521 | return Aarray, Barray 522 | 523 | 524 | 525 | def fit_linear_t1(im_array, flip_angles,TR,expon = True,magthreshlow = 2, t1threshlow=0, t1threshhigh= 10000): 526 | # y = a+bx 527 | im_array = np.abs(im_array).astype(np.float) 528 | Si_sin_alpha = im_array/np.sin(flip_angles).astype(np.float) 529 | Si_tan_alpha = im_array/np.tan(flip_angles).astype(np.float) 530 | #Si_sin_alpha, Si_tan_alpha 531 | y = Si_sin_alpha/100000. 532 | x = Si_tan_alpha/100000. 533 | 534 | sxy = np.sum(x*y, axis=-1).astype(np.float) 535 | sxx = np.sum(x**2, axis=-1).astype(np.float) 536 | sx = np.sum(x, axis=-1).astype(np.float) 537 | sy = np.sum(y, axis=-1).astype(np.float) 538 | sx2 = sx**2 539 | n = np.shape(flip_angles)[-1] 540 | b = (n*sxy-sx*sy)/(n*sxx-sx2) 541 | a = ((sy-b*sx)/n) 542 | t1 = -TR/np.log(b)/100000.*1000 543 | t1[t1t1threshhigh] = 0 545 | return t1 #{'y':y,'x':x,'sxy':sxy,'sxx':sxx,'sx':sx,'sy':sy,'sx2':sx2,'n':n,'b':b,'a':a,'t1':t1} 546 | 547 | 548 | #findminofarray(x,y,polydim,sizeofsides) 549 | #finds the poly interpolated minimum of x and y, to the order 550 | #of polydim, keeping sizesofsides on both sides of the initial minimum in the matrix 551 | def findminofarray(x,y,polydim=3,sizeofsides=2,verbose=False): 552 | themin = np.argmin(y) 553 | mina = themin-sizeofsides 554 | maxa = themin+sizeofsides 555 | if mina<0: 556 | mina = 0 557 | if maxa>len(x): 558 | maxa = len(x) 559 | x = np.array(x[mina:maxa]) 560 | y = np.array(y[mina:maxa]) 561 | if not verbose: 562 | import warnings 563 | warnings.simplefilter('ignore', np.RankWarning) 564 | z = np.polyfit(x,y,polydim) 565 | a = 3*z[0]; b = 2*z[1]; c = z[2] 566 | xroot1 = (-b+np.sqrt(b**2-4*a*c))/2/a 567 | xroot2 = (-b-np.sqrt(b**2-4*a*c))/2/a 568 | xroot = xroot1 569 | if np.abs(xroot1-themin) > np.abs(xroot2-themin): 570 | xroot = xroot2 571 | return xroot 572 | 573 | 574 | 575 | def averagemats(filenames = [], thekey='immat',makeabs = False): 576 | if len(filenames) == 0: 577 | filenames = getfiles(initialdir = defpath,useQT = True) 578 | collection = [] 579 | for thisfile in filenames: 580 | collection.append(sio.loadmat(thisfile)[thekey]) 581 | collection = np.array(collection) 582 | if makeabs: 583 | collection = np.abs(collection) 584 | return np.mean(collection,0) 585 | 586 | 587 | 588 | class imspacedata(): 589 | def __init__(self,imspace=[0]): 590 | self.imspace = imspace 591 | 592 | 593 | 594 | 595 | def basicregrid(data, ktrajs,divideby=256 , resolution=[64.,64.,64.], method='nearest'): 596 | #also method='linear', 'cubic'; method='nearest', 597 | if data.shape == ktrajs[0].shape: 598 | from scipy.interpolate import griddata 599 | data = np.array(data).flatten() 600 | ktrajx = np.array(ktrajs[0]).flatten()/divideby 601 | ktrajy = np.array(ktrajs[1]).flatten()/divideby 602 | ktrajz = np.array(ktrajs[2]).flatten()/divideby 603 | points = np.transpose(np.array([ktrajx, ktrajy, ktrajz])) 604 | stepx = 1/resolution[0] 605 | #stepx = .05 606 | gridx = np.arange(ktrajx.min(),ktrajx.max(),stepx) 607 | stepy = 1/resolution[1] 608 | #stepy = .05 609 | gridy = np.arange(ktrajy.min(),ktrajy.max(),stepy) 610 | stepz = 1/resolution[2] 611 | #stepz = .05 612 | gridz = np.arange(ktrajz.min(),ktrajz.max(),stepz) 613 | meshx, meshy, meshz = np.meshgrid(gridx, gridy, gridz) 614 | 615 | regridded = griddata(points,data,(meshx,meshy,meshz),method=method) 616 | else: 617 | regridded = -1 618 | print( "shapes are different") 619 | return regridded 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | -------------------------------------------------------------------------------- /classes.py: -------------------------------------------------------------------------------- 1 | class datastore(): 2 | def __init__(self,parent=None, imdata = None, name = '', datatype = None, scanparams = None, plotdata = None, y = None, x = None, 3 | TEs = None, TSLs = None, TRs = None, T1s = None, T2s = None, 4 | B0 = None, FlipAngle = None, M0 = None, position = (None,None,None), rotation = None, 5 | FOV = (None, None, None), voxelsize = (None, None, None), nucleus = None, zoom = (None, None, None), TxVoltage = None, dicominfo = None, 6 | itype = None, metadata = None, preferredIndex = None, preferredplotSettings = {}): 7 | self.parent = parent 8 | self.dims = {} # 'nx','ny','nz','te','ncest','ntsl','ntr' 9 | self.imdata = np.array(imdata) 10 | self.datatype = datatype 11 | self.name = name 12 | self.plotdata = plotdata #{'x':, 'y':, 'rho':, 'theta':, } 13 | self.scanparams = scanparams 14 | self.metadata = metadata 15 | self.y = y 16 | self.x = x 17 | self.TEs = TEs 18 | self.TSLs = TSLs 19 | self.TRs = TRs 20 | self.T1s = T1s 21 | self.T2s = T2s 22 | self.B0 = B0 23 | self.FlipAngle = FlipAngle 24 | self.M0 = M0 25 | self.Mz = None 26 | self.Mx = None 27 | self.My = None 28 | self.gradB0 = None 29 | self.T1 = None 30 | self.T2 = None 31 | self.T2star = None 32 | self.T1rho = None 33 | self.preferredIndex = preferredIndex 34 | self.preferredplotSettings = preferredplotSettings 35 | self.phase = None 36 | self.unwrappedphase = None 37 | self.elasticity = None 38 | self.DWI = None 39 | self.DTI = None 40 | self.position = position 41 | self.rotation = rotation 42 | self.FOV = FOV 43 | self.voxelsize = voxelsize 44 | self.nucleus = nucleus 45 | self.zoom = zoom 46 | self.TxVoltage = TxVoltage 47 | self.dicominfo = dicominfo 48 | self.gradientx = None 49 | self.gradientz = None 50 | self.gradienty = None 51 | self.conductivity = None 52 | self.Temp = None 53 | self.B1 = None 54 | self.EMfield = None 55 | self.pulsesequence = None 56 | self.coils = None 57 | self.permittivity = None 58 | self.permeability = None 59 | self.voxelsensitivity = None 60 | self.biotsavartfield = None 61 | def return_name(self, increment=False): 62 | if self.name == None: 63 | if len(np.shape(self.imdata))>0: 64 | 65 | self.parent.datastorecount = self.parent.datastorecount+1 66 | self.name = str(self.parent.datastorecount).zfill(2)+': ' + str(np.shape(self.imdata)) 67 | else: 68 | pass 69 | if increment == True: 70 | self.parent.datastorecount = self.parent.datastorecount+1 71 | self.name = str(self.parent.datastorecount)+ self.name[3:] 72 | return self.name 73 | def copy(self, datastoretocopy): 74 | #self.parent = datastoretocopy.parent 75 | self.imdata = np.array(datastoretocopy.imdata) 76 | self.datatype = datastoretocopy.datatype 77 | self.name = datastoretocopy.name 78 | self.plotdata = datastoretocopy.plotdata 79 | self.y = datastoretocopy.y 80 | self.x = datastoretocopy.x 81 | self.TEs = datastoretocopy.TEs 82 | self.TSLs = datastoretocopy.TSLs 83 | self.TRs = datastoretocopy.TRs 84 | self.T1s = datastoretocopy.T1s 85 | self.T2s = datastoretocopy.T2s 86 | self.B0 = datastoretocopy.B0 87 | self.FlipAngle = datastoretocopy.FlipAngle 88 | self.M0 = datastoretocopy.M0 89 | self.position = datastoretocopy.position 90 | self.rotation = datastoretocopy.rotation 91 | self.FOV = datastoretocopy.FOV 92 | self.voxelsize = datastoretocopy.voxelsize 93 | self.nucleus = datastoretocopy.nucleus 94 | self.zoom = datastoretocopy.zoom 95 | self.TxVoltage = datastoretocopy.TxVoltage 96 | self.dicominfo = datastoretocopy.dicominfo 97 | self.preferredIndex = datastoretocopy.preferredIndex 98 | self.preferredplotSettings = datastoretocopy.preferredplotSettings 99 | 100 | 101 | 102 | 103 | 104 | class multinuclear(): 105 | def __init(self): 106 | gyros = """Isotope Symbol Name Spin Natural 107 | Abund. % Receptivity 108 | (rel. to 13C) Magnetic 109 | Moment Gamma 110 | (x 10^7 111 | rad/Ts) Quadrup. 112 | Moment 113 | Q/fm^2 Frequency Reference 114 | 191 Ir Iridium 3/2 37.30000 0.06412 0.19460 0.48120 81.60000 6.872 115 | 197 Au Gold 3/2 100.00000 0.16294 0.19127 0.47306 54.70000 6.916 116 | 235 U Uranium 7/2 0.72000 --- -0.43000 -0.52000 493.60000 7.366 117 | 193 Ir Iridium 3/2 62.70000 0.13765 0.21130 0.52270 75.10000 7.484 118 | 187 Os Osmium 1/2 1.96000 0.00143 0.11198 0.61929 --- 9.129 OsO4 119 | 179 Hf Hafnium 9/2 13.62000 0.43824 -0.70850 -0.68210 379.30000 10.068 120 | 41 K Potassium 3/2 6.73020 0.03341 0.27740 0.68607 7.11000 10.245 KCl 121 | 167 Er Erbium 7/2 22.93000 --- -0.63935 -0.77157 356.50000 11.520 122 | 155 Gd Gadolinium 3/2 14.80000 --- -0.33208 -0.82132 127.00000 12.280 123 | 103 Rh Rhodium 1/2 100.00000 0.18600 -0.15310 -0.84680 --- 12.746 Rh(acac)3 p 124 | 57 Fe Iron 1/2 2.11900 0.00425 0.15696 0.86806 --- 12.951 Fe(CO)5 125 | 145 Nd Neodymium 7/2 8.30000 --- -0.74400 -0.89800 -33.00000 13.440 126 | 161 Dy Dysprosium 5/2 18.91000 --- -0.56830 -0.92010 250.70000 13.760 127 | 149 Sm Samarium 7/2 13.82000 --- -0.76160 -0.91920 7.40000 13.760 128 | 73 Ge Germanium 9/2 7.73000 0.64118 -0.97229 -0.93603 -19.60000 13.953 (CH3)4Ge 129 | 83 Kr Krypton 9/2 11.49000 1.28235 -1.07311 -1.03310 25.90000 15.390 Kr 130 | 177 Hf Hafnium 7/2 18.60000 1.53529 0.89970 1.08600 336.50000 16.028 131 | 157 Gd Gadolinium 3/2 15.65000 --- -0.43540 -1.07690 135.00000 16.120 132 | 107 Ag Silver 1/2 51.83900 0.20500 -0.19690 -1.08892 --- 16.191 AgNO3 133 | 183 W Tungsten 1/2 14.31000 0.06310 0.20401 1.12824 --- 16.666 Na2WO4 134 | 147 Sm Samarium 7/2 14.99000 --- -0.92390 -1.11500 -25.90000 16.680 135 | 87 Sr Strontium 9/2 7.00000 1.11765 -1.20902 -1.16394 33.50000 17.335 SrCl2 136 | 105 Pd Palladium 5/2 22.33000 1.48824 -0.76000 -1.23000 66.00000 18.304 K2PdCl6 137 | 99 Ru Ruthenium 5/2 12.76000 0.84706 -0.75880 -1.22900 7.90000 18.421 K4[Ru(CN)6] 138 | 109 Ag Silver 1/2 48.16100 0.29000 -0.22636 -1.25186 --- 18.614 AgNO3 139 | 39 K Potassium 3/2 93.25810 2.80000 0.50543 1.25006 5.85000 18.665 KCl 140 | 163 Dy Dysprosium 5/2 24.90000 --- 0.79580 1.28900 264.80000 19.280 141 | 173 Yb Ytterbium 5/2 16.13000 --- -0.80446 -1.30250 280.00000 19.284 142 | 89 Y Yttrium 1/2 100.00000 0.70000 -0.23801 -1.31628 --- 19.601 Y(NO3)3 143 | 101 Ru Ruthenium 5/2 17.06000 1.59412 -0.85050 -1.37700 45.70000 20.645 K4[Ru(CN)6] 144 | 143 Nd Neodymium 7/2 12.20000 --- -1.20800 -1.45700 -63.00000 21.800 145 | 47 Ti Titanium 5/2 7.44000 0.91765 -0.93294 -1.51050 30.20000 22.550 TiCl4 146 | 49 Ti Titanium 7/2 5.41000 1.20588 -1.25201 -1.51095 24.70000 22.556 TiCl4 147 | 53 Cr Chromium 3/2 9.50100 0.50765 -0.61263 -1.51520 -15.00000 22.610 K2CrO4 148 | 40 K Potassium 4 0.01170 0.00360 -1.45132 -1.55429 -7.30000 23.208 KCl 149 | 25 Mg Magnesium 5/2 10.00000 1.57647 -1.01220 -1.63887 19.94000 24.487 MgCl2 150 | 67 Zn Zinc 5/2 4.10000 0.69412 1.03556 1.67669 15.00000 25.027 Zn(NO3)2 151 | 95 Mo Molybdenium 5/2 15.92000 3.06471 -1.08200 -1.75100 -2.20000 26.068 Na2MoO4 152 | 201 Hg Mercury 3/2 13.18000 1.15882 -0.72325 -1.78877 38.60000 26.446 (CH3)2HgD 153 | 97 Mo Molybdenium 5/2 9.55000 1.95882 -1.10500 -1.78800 25.50000 26.615 Na2MoO4 154 | 43 Ca Calcium 7/2 0.13500 0.05106 -1.49407 -1.80307 -4.08000 26.920 CaCl2 155 | 14 N Nitrogen 1 99.63200 5.88235 0.57100 1.93378 2.04400 28.905 CH3NO2 156 | 33 S Sulfur 3/2 0.76000 0.10118 0.83117 2.05568 -6.78000 30.704 (NH4)2SO4 157 | 189 Os Osmium 3/2 16.15000 2.32353 0.85197 2.10713 85.60000 31.062 OsO4 158 | 21 Ne Neon 3/2 0.27000 0.03912 -0.85438 -2.11308 10.15500 31.577 Ne 159 | 176 Lu Lutetium 7 2.59000 --- 3.38800 2.16840 497.00000 32.524 160 | 37 Cl Chlorine 3/2 24.22000 3.87647 0.88320 2.18437 -6.43500 32.623 NaCl 161 | 131 Xe Xenon 3/2 21.18000 3.50588 0.89319 2.20908 -11.40000 32.976 XeOF4 162 | 169 Tm Thulium 1/2 100.00000 --- -0.40110 -2.21800 --- 33.160 163 | 61 Ni Nickel 3/2 1.13990 0.24059 -0.96827 -2.39480 16.20000 35.744 Ni(CO)4 164 | 91 Zr Zirconium 5/2 11.22000 6.29412 -1.54246 -2.49743 -17.60000 37.185 Zr(C5H5)2Cl2 """ 165 | 166 | def sep(text): 167 | lines=gyros.split('\n') 168 | dicts = lines[0].split('\t') 169 | alldata = [] 170 | for line in lines: 171 | entries = line.split('\t') 172 | mydict = {} 173 | print dicts, len(dicts), len(entries) 174 | if len(entries) > len(dicts): 175 | for xi in xrange(len(dicts)): 176 | mydict[dicts[xi]] = entries[xi] 177 | print dicts[xi], entries[xi] 178 | alldata.append(mydict) 179 | return alldata 180 | 181 | alldata = sep(gyros) 182 | 183 | 184 | 185 | 186 | 187 | class dicom_dirs: 188 | 'create a list of all files within a directory \n level=1 is just the top directory' 189 | def __init__(self, level=1,path='~', exten='*dcm', MessageBox = None,showupdatefunc=None, updatefunc = None): 190 | 'create a list of all files within a directory \n level=1 is just the top directory'\ 191 | 'to be usable the function update_protocolnames() must be run'\ 192 | 'example: \n hey = dicom_dirs(4); hey.update_protocolnames(); print hey.unique_protocol_names(); print hey.unique_patient_names; hey._savedata()' 193 | if path=='': 194 | try: 195 | self.path = tkFileDialog.askdirectory() 196 | except: 197 | pass 198 | self.MessageBox = MessageBox 199 | self.path = path 200 | self.showupdatefunc = updatefunc 201 | self.level = level 202 | self.exten=exten 203 | self._listdir() 204 | self.ProtocolNames = [] 205 | self.PatientNames = [] 206 | self.AccessionNumbers = [] 207 | self.SeriesNumber = [] 208 | self.TEs = [] 209 | self.EchoTimes = [] 210 | self.TRs = [] 211 | self.TSLs = [] 212 | self.FlipAngles = [] 213 | self.MagPhases = [] 214 | self.ScanTimes = [] 215 | def _removematfiles(self): 216 | newallfiles = [] 217 | for checkedfile in self.allfiles: 218 | if checkedfile[-4:] != '.mat': 219 | for checkedfile in self.allfiles: 220 | if checkedfile[-4:] != '.mat': 221 | newallfiles.append(checkedfile) 222 | self.allfiles = newallfiles 223 | def _listdir(self,level=-1): 224 | if (level!=-1): 225 | self.level=level 226 | self.allfiles = [] 227 | for xi in range(self.level): 228 | self.allfiles.extend(self._listfiles(xi+1)) 229 | def _listfiles( self,templevel): 230 | path = self.path 231 | try: 232 | if path[-1]=='/': 233 | path=path[0:-1] 234 | addpath = '/' 235 | for xi in range(0,templevel-1): 236 | addpath = addpath + '*/' 237 | except: 238 | pass 239 | filelist = sorted(glob.glob(path+addpath+self.exten)) 240 | 241 | return filelist 242 | def getfile(self,filename='',filetypes=[('image', '.mat')],defaultextension = '.mat', title='Open File:',\ 243 | filetypesQT = 'Matlab .mat (*.MAT *.mat *.fig *.FIG);;Dicom .IMA (*.IMA);;Raw .dat (*.dat);;All Files (*)',useQT = False ): 244 | print filename 245 | #print useQT 246 | if filename == '' and useQT == True: 247 | filename = str(pg.Qt.QtGui.QFileDialog.getOpenFileName(None, title,defpath,filetypesQT)) 248 | #dlg = QtGui.QFileDialog() 249 | #dlg.setWindowTitle(title) 250 | #dlg.setNameFilters(filetypesQT) 251 | #dlg.setViewMode(QtGui.QFileDialog.Detail) 252 | #dlg.exec_() 253 | #filename = str(dlg.selectedFiles()[0]) 254 | 255 | if filename=='': 256 | return '' 257 | self.defaultpath = self.strip_filepath(filename) 258 | elif filename == '': 259 | filename = tkFileDialog.askopenfilename(initialdir=self.defaultpath,defaultextension=defaultextension, filetypes=filetypes, title = title) 260 | if len(filename)>0: 261 | self.defaultpath = self.strip_filepath(filename) 262 | return filename 263 | def strip_filepath(self, filename): 264 | path = '/'.join(filename.split('/')[:-1])+'/' 265 | return path 266 | def get_filenames_dir(self,path='~'): 267 | import glob 268 | all_files = [] 269 | DicomExtens = ['MR.*','CT.*','*IMA','s0*i*','*MRDC*','*dcm*','*DCM*','z*','*IM0*','0*0*','1.3*','CT.*','IM*'] 270 | for exten in DicomExtens: 271 | all_files = all_files+glob.glob(path+exten) 272 | all_files = all_files+glob.glob(path+'*/'+exten) 273 | all_files = all_files+glob.glob(path+'*/*/'+exten) 274 | all_files = all_files+glob.glob(path+'*/*/*/'+exten) 275 | self.allfiles = all_files 276 | def update_protocolnames(self, DISPLAY_DONE=True): 277 | percentage = .1 278 | for xi in range(len(self.allfiles)): 279 | if xi>=len(self.allfiles): #have to check numbers do to removing false directories 280 | break 281 | try: 282 | break 283 | try: 284 | if self.allfiles[xi][-7:] == 'MR.1.2.' or self.allfiles[xi][-5:] == 'MR.1.' or self.allfiles[xi][-6:] == 'MR.1.2' or self.allfiles[xi][-3:] == 'MR.': 285 | self.allfiles.remove(self.allfiles[xi]) 286 | except: 287 | print xi, len(self.allfiles) 288 | print self.allfiles[xi] 289 | try: 290 | dicominfo = dicom.read_file(self.allfiles[xi],stop_before_pixels=True) 291 | except: 292 | print self.allfiles[xi] 293 | continue 294 | pass 295 | if not hasattr(dicominfo, 'ProtocolName'): 296 | dicominfo.ProtocolName = '' 297 | if int(dicominfo.SeriesNumber)>99: 298 | self.ProtocolNames.append(str(dicominfo.SeriesNumber) + ' ' + str(dicominfo.ProtocolName)+' ' + str(dicominfo.SeriesDescription)) 299 | elif int(dicominfo.SeriesNumber)>9: 300 | self.ProtocolNames.append('0' + str(dicominfo.SeriesNumber) + ' ' + str(dicominfo.ProtocolName)+' ' + str(dicominfo.SeriesDescription)) 301 | else: 302 | self.ProtocolNames.append('00'+ str(dicominfo.SeriesNumber) + ' ' + str(dicominfo.ProtocolName)+' ' + str(dicominfo.SeriesDescription)) 303 | self.PatientNames.append(str(dicominfo.PatientsName) + ' ' + str(dicominfo.AccessionNumber)) 304 | #print dicominfo.AccessionNumber 305 | self.AccessionNumbers.append(dicominfo.AccessionNumber) 306 | self.SeriesNumber.append(dicominfo.SeriesNumber) 307 | self.ScanTimes.append(dicominfo.StudyDate) 308 | try: 309 | self.MagPhases.append(ord(dicominfo[0x0043,0x102f].value)) 310 | except: 311 | pass 312 | try: 313 | self.EchoTimes.append((dicominfo.EchoTime)) 314 | except: 315 | pass 316 | try: 317 | self.TRs.append((dicominfo.RepetitionTime)) 318 | except: 319 | pass 320 | try: 321 | self.FlipAngles.append((dicominfo.FlipAngle)) 322 | except: 323 | pass 324 | if DISPLAY_DONE: 325 | if(xi/len(self.allfiles) >= percentage): 326 | if MessageBox != None: 327 | self.MessageBox( str(xi/len(self.allfiles)*100 //1) + '% done') 328 | percentage +=.1 329 | else: 330 | print str(xi/len(self.allfiles)*100 //1) + '% done' 331 | percentage +=.1 332 | self.nonunique_len() 333 | def nonunique_len(self): 334 | print len(self.unique_TRs()) 335 | print len(self.unique_TEs()) 336 | print len(self.unique_MagPhases()) 337 | print len(self.unique_FlipAngles()) ##should check if all above 0 338 | self.nuN = len(self.unique_TRs())*len(self.unique_TEs())*len(self.unique_FlipAngles()) 339 | def unique_TRs(self): 340 | 'lists all the unique TRs' 341 | return sorted(list(set(self.TRs))) 342 | def unique_ScanTimes(self): 343 | return sorted(list(set(self.ScanTimes))) 344 | def unique_TEs(self): 345 | 'lists all the unique TEs' 346 | return sorted(list(set(self.EchoTimes))) 347 | def unique_MagPhases(self): 348 | 'lists all the unique Mag/Phases' 349 | return sorted(list(set(self.MagPhases))) 350 | def unique_protocol_names(self): 351 | 'lists all the unique Protocol names' 352 | return sorted(list(set(self.ProtocolNames))) 353 | def unique_patient_names(self): 354 | 'lists all the unique Protocol names' 355 | return sorted(list(set(self.PatientNames))) 356 | def unique_accession_numbers(self): 357 | 'lists all the unique Protocol names' 358 | return sorted(list(set(self.AccessionNumbers))) 359 | def unique_series_numbers(self): 360 | 'lists all the unique Protocol names' 361 | return sorted(list(set(self.SeriesNumber))) 362 | def get_TEs(self): 363 | times = np.zeros(len(self.allfiles)) 364 | dicomfiledata = dicom.read_file(self.allfiles[0]) 365 | 366 | if [0x0019, 0x10a9] in dicomfiledata.keys(): 367 | if not dicomfiledata[0x0019, 0x10a9].value == '500.000000': 368 | for xi in xrange(len(self.allfiles)): 369 | dicom_data = dicom.read_file(self.allfiles[xi],stop_before_pixels=True) 370 | times[xi]=dicom_data.EchoTime 371 | 372 | def get_imdata(self,protocol_pattern = None): 373 | try: 374 | self.get_TEs() 375 | except: 376 | print 'no echo times in dicom' 377 | imagedata = [] 378 | all_files = self.allfiles 379 | if protocol_pattern != None: 380 | all_files = self.return_protocol_pattern(protocol_pattern)[0] 381 | old_dicominfo = dicom.read_file(all_files[0]) 382 | imagedata2 = [] 383 | files_im1 = [] 384 | files_im2 = [] 385 | init_row = old_dicominfo.Rows 386 | init_col = old_dicominfo.Columns 387 | instancenums = [] 388 | SOPnums = [] 389 | 390 | files = sorted(self.remove_file_copies(all_files)) 391 | print "Reading files.." 392 | for xi in xrange(len(files)): 393 | filei = files[xi] 394 | if (np.double(xi)/10 % 1)==0: 395 | print str(xi)+ ' / ' + str(len(files))+ ' loaded' 396 | dicominfo = dicom.read_file(filei) 397 | 398 | instancenums.append(int(dicominfo.InstanceNumber)-1) 399 | try: 400 | SOPid = dicominfo.SOPInstanceUID[-8:] # get the last few digits in the SOP id 401 | SOPid = SOPid[SOPid.find('.')-3:] 402 | SOPnums.append(float(SOPid)) 403 | except: 404 | SOPnums.append(int(dicominfo.InstanceNumber)-1) 405 | 406 | imagebuffer =np.fromstring(dicominfo.PixelData, dtype=np.int16) 407 | imagebuffer2 = imagebuffer.reshape([dicominfo.Rows, dicominfo.Columns]).T 408 | if dicominfo.Rows != init_row or dicominfo.Columns != init_col: 409 | imagedata2.append(imagebuffer2) 410 | files_im2.append(filei) 411 | else: 412 | imagedata.append(imagebuffer2) 413 | files_im1.append(filei) 414 | old_dicominfo = dicominfo 415 | 416 | files_im1 = files_im1 417 | files_im2 = files_im2 418 | self.im1 = np.squeeze(imagedata) 419 | self.im2 = np.squeeze(imagedata2) 420 | self.instance_nums = instancenums 421 | self.SOP_nums = SOPnums 422 | 423 | return 424 | 425 | init_row = old_dicominfo.Rows 426 | init_col = old_dicominfo.Columns 427 | 428 | files = self.remove_file_copies(dummydcm.allfiles) 429 | files = sorted(files) 430 | 431 | self.mprint("Reading files..") 432 | instancenums = [] 433 | SOPnums = [] 434 | def is_odd_filei(num,index=-5): 435 | num = int(num[index:].replace('.','0')) 436 | return num & 0x1 437 | for xi in xrange(len(files)): 438 | filei = files[xi] 439 | if (np.double(xi)/10 % 1)==0: 440 | self.mprint(str(xi)+ ' / ' + str(len(files))+ ' loaded') 441 | dicominfo = dicom.read_file(filei.rstrip()) 442 | instancenums.append(int(dicominfo.InstanceNumber)-1) 443 | try: 444 | SOPid = dicominfo.SOPInstanceUID[-8:] # get the last few digits in the SOP id 445 | SOPid = SOPid[SOPid.find('.')-3:] 446 | SOPnums.append(float(SOPid)) 447 | except: 448 | SOPnums.append(int(dicominfo.InstanceNumber)-1) 449 | #instancenums.append(int(dicominfo.SOPInstanceUID)-1) 450 | imagebuffer =np.fromstring(dicominfo.PixelData, dtype=np.int16) 451 | imagebuffer2 = imagebuffer.reshape([dicominfo.Rows, dicominfo.Columns]).T 452 | if LoadSpecial: 453 | imagebuffer2,flag = self.LoadSpecial(imdata=imagebuffer2) 454 | if flag == 'Empty': 455 | continue 456 | 457 | if LoadSpecial2: 458 | flag2 = is_odd_filei(filei) 459 | '''if flag2 == 1: 460 | is_odd_filei 461 | flag2 = 2 462 | else: 463 | flag2 = 1''' 464 | if dicominfo.Rows != init_row or dicominfo.Columns != init_col or flag=='Phase' or flag2==1: 465 | imagedata2.append(imagebuffer2) 466 | files_im2.append(filei) 467 | else: 468 | imagedata.append(imagebuffer2) 469 | files_im1.append(filei) 470 | old_dicominfo = dicominfo 471 | #self.scanparams['DICOM'] = dicominfo 472 | self.metadata = dicominfo 473 | self.setmeta_scanparams_dicom(dicominfo = dicominfo) 474 | 475 | self.mprint('Updating the image ') 476 | #reorder, because sometimes dicoms are named funny 477 | newimagedata = [] 478 | newfiles = [] 479 | newimagedata = [] 480 | newfiles = [] 481 | newfiles_im1 = [] 482 | imdata_sorted = np.zeros(np.shape(imagedata)) 483 | sortedxi = np.argsort(SOPnums) 484 | newfiles = [] 485 | newfiles_im1 = [] 486 | newimagedata = [] 487 | method1 = False 488 | if method1 == True: 489 | for xi in xrange(len(sortedxi)): 490 | posi = sortedxi[xi] 491 | imdata_sorted[xi] = imagedata[posi] 492 | newimagedata.append(imagedata[posi]) 493 | newfiles.append(files[posi]) 494 | newfiles_im1.append(files_im1[posi]) 495 | else: 496 | 497 | #original method, was in order but left data out 498 | for xi in xrange(len(instancenums)): 499 | try: 500 | posi = instancenums.index(xi) 501 | SOPid = SOPnums[xi] 502 | #print posi, SOPid 503 | newimagedata.append(imagedata[posi]) 504 | newfiles.append(files[posi]) 505 | newfiles_im1.append(files_im1[posi]) 506 | except: 507 | self.mprint('Is ' + str(xi) + ' in list?') 508 | -------------------------------------------------------------------------------- /mrsigpy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # ----------------------------------------------------- 4 | # MR Signal Python Library 5 | # ----------------------------------------------------- 6 | # 7 | # Basic MRI signal simulations, intended primaraly for 8 | # learning and low-to-medium complexity MRI simulations. 9 | # 10 | # Derived from Stanford RAD229 Class (Matlab) functions 11 | # 12 | # Created on Tue Nov 19 08:57:48 2019 13 | # authors: Joshua Kaggie, Brian Hargreaves 14 | # ----------------------------------------------------- 15 | # 16 | # 17 | # To see a more up-to-date version, check out : https://github.com/mribri999/MRSignalsSeqs 18 | 19 | 20 | import numpy as np 21 | import matplotlib.pyplot as plt 22 | from mpl_toolkits.mplot3d import Axes3D 23 | 24 | PLOTON = True 25 | 26 | 27 | from time import sleep 28 | 29 | 30 | 31 | 32 | 33 | def animation_plot(): 34 | #%matplotlib notebook ## RUN THIS LINE IF IN JUPYTER 35 | import matplotlib.animation 36 | t = np.linspace(0,2*np.pi) 37 | x = np.sin(t) 38 | 39 | fig, ax = plt.subplots() 40 | ax.axis([0,2*np.pi,-1,1]) 41 | l, = ax.plot([],[]) 42 | 43 | def animate(i): 44 | l.set_data(t[:i], x[:i]) 45 | 46 | ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t)) 47 | 48 | from IPython.display import HTML 49 | HTML(ani.to_jshtml()) 50 | 51 | 52 | 53 | 54 | 55 | 56 | def relax(t,T1 = 4., T2 = 0.1, combine=True): 57 | T1 = T1 * 1. 58 | T2 = T2 * 1. 59 | t = t * 1. 60 | E1 = np.exp(-t/T1) 61 | E2 = np.exp(-t/T2) 62 | A = np.diag([E2, E2, E1]) 63 | B = np.array([0,0,1-E1]) 64 | 65 | B = np.reshape(B,[3,1]) 66 | if combine: 67 | A = np.hstack([A,B]) ### ADD TO FOURTH AXIS! HSTACK???? 68 | return A 69 | return A,B 70 | 71 | 72 | #-------------------------------------------------------- 73 | # By convention all rotations are left-handed 74 | #-------------------------------------------------------- 75 | 76 | # Returns 3x3 matrix for left-handed rotation about x 77 | def xrot(angle = 0., in_degs = True): 78 | if in_degs: 79 | angle = angle*np.pi/180. 80 | c = np.cos(angle) 81 | s = np.sin(angle) 82 | M = np.array([[1.,0.,0.],[0., c, s],[0,-s, c]]) 83 | return M 84 | 85 | # Returns 3x3 matrix for left-handed rotation about y 86 | def yrot(angle = 0., in_degs = True): 87 | if in_degs: 88 | angle = angle*np.pi/180. 89 | c = np.cos(angle) 90 | s = np.sin(angle) 91 | M = np.array([[c,0.,-s],[0.,1.,0.],[s,0.,c]]) 92 | return M 93 | 94 | 95 | # Returns 3x3 matrix for left-handed rotation about z 96 | def zrot(angle = 0., in_degs = True): 97 | 'This is equivalent to help' 98 | if in_degs: 99 | angle = angle*np.pi/180. 100 | c = np.cos(angle) 101 | s = np.sin(angle) 102 | M = np.array([[c,s,0.],[-s,c,0],[0.,0.,1.]]) 103 | return M 104 | 105 | 106 | # Returns 3x3 matrix for rotation about axis in x-y plan phi away from x 107 | def throt(theta = 0., phi=0., in_degs = True): 108 | 'This is equivalent to help' 109 | if in_degs: 110 | theta = theta*np.pi/180. 111 | phi = phi*np.pi/180. 112 | 113 | 114 | def _3dspins(): 115 | # This import registers the 3D projection, but is otherwise unused. 116 | from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import 117 | import matplotlib.pyplot as plt 118 | import numpy as np 119 | fig = plt.figure() 120 | ax = fig.gca(projection='3d') 121 | # Make the grid 122 | x, y, z = np.meshgrid(np.arange(-1, 1, 0.1), 123 | np.arange(-1, 1, 0.1), 124 | np.arange(0, 1, 1)) 125 | 126 | # Make the direction data for the arrows 127 | u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z) 128 | v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z) 129 | w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) * 130 | np.sin(np.pi * z)) 131 | 132 | ax.quiver(x*0, y*0, z*0, u, v, w, length=0.1, normalize=True) 133 | 134 | plt.show() 135 | 136 | # Converts a time vector to the corresponding frequency vector of an FFT 137 | def time2freq(t): 138 | dt = t[1]-t[0] 139 | num_p = len(t) 140 | df = 1./ num_p/dt 141 | f = np.arange(np.ceil((num_p-1.)/2.), np.floor((num_p-1.)/2.))*df 142 | return f 143 | 144 | 145 | import numpy as np 146 | from matplotlib.pyplot import figure, plot, subplot, title, xlabel, ylabel 147 | 148 | # abprop propagates a series operations Ai,Bi into a single A,B 149 | # A = ... A3*A2*A1 B = ... B3 + A3*(B2 + A2*B1 150 | # 151 | # Example: 152 | # TR = 1 153 | # TI = 0.5 154 | # TE = 0.05 155 | # T1 = 0.5 156 | # T2 = 0.1 157 | # abprop(relax(TR-TE-TI,T1,T2, combine = True),xrot(180.)) 158 | # Should return mss = [0,0,-0.42] 159 | def abprop(*varargin): 160 | A = np.eye(3) 161 | B = np.array([[0],[0],[0]]) 162 | mss = [] 163 | 164 | argcount = 0 165 | nargin = len(varargin) 166 | 167 | while (argcount < nargin): 168 | Ai = varargin[argcount] 169 | argcount += 1 170 | 171 | if np.shape(Ai)[0] != 3: 172 | print('The number of rows is not 3.') 173 | if np.shape(Ai)[1] == 3: 174 | if argcount < nargin: 175 | Bi = varargin[argcount+1] 176 | if np.shape(Bi) != [1,3]: 177 | Bi = np.array([[0],[0],[0]]) 178 | if argcount == nargin: 179 | Bi = np.array([[0],[0],[0]]) 180 | elif np.shape(Ai)[1] == 4: 181 | Bi = Ai[:,3:] 182 | Ai = Ai[:,:3] 183 | else: 184 | print('Arg count '+str(np.shape(Ai))+' should be 3x3 or 3x4') 185 | 186 | A = np.matmul(Ai,A) 187 | B = np.matmul(Ai,B)+Bi 188 | 189 | mss = np.matmul(np.linalg.inv(np.eye(3)-A),B) 190 | return A,B, mss 191 | 192 | 193 | def adiabatic(peakb1 = 0.2, bw = 2000, beta = 1000., T = 0.01, Ts = 0.00001, blochsim = True): 194 | T = 2*np.round(T/Ts/2)*Ts 195 | N = T/Ts 196 | 197 | t = np.arange(Ts, T, Ts) - np.round(N/2)*Ts #time from -t/2 to t/2 198 | 199 | b1 = peakb1 * np.sech(beta*t) 200 | freq = bw/2 * np.tanh(beta*t) 201 | phase = np.cumsum(freq)*2*np.pi*Ts 202 | phase = phase-phase(np.round(N/2)) #zero phase half-way 203 | phase = np.mod(phase+np.pi,2*np.pi)-np.pi #limit to -pi to pi 204 | 205 | if blochsim: 206 | t = t-t[0] #start t at 0 for plots 207 | figure(1) 208 | print('Adiabatic silver-hoult pulse (beta = '+str(beta)+' Hz) ') 209 | subplot(3,1,1) 210 | plot(t,b1); xlable('Time(s)'); ylabel('B1(G)') 211 | title(tt) 212 | subplot(3,1,2) 213 | plot(t,phase); xlabel('Time(s)'); ylabel('Phase(rad)') 214 | subplot(3,1,3) 215 | plot(t,freq); xlabel('Time(s)'); ylabel('Freq(hz)') 216 | 217 | #add in 218 | if 0: 219 | gr = 0*b1 220 | tp = Ts 221 | t1 = 0.6; t2 = 0.1 222 | df = np.arange(-3*bw, 3*bw, bw/20.) 223 | dp = 0 224 | mode = 0 225 | mx,my,mz = bloch(0) # finish this 226 | 227 | 228 | 229 | def bvalue(gradwave, T): 230 | gamma = 2*np.pi*42.58 231 | intg = np.cumsum(gradwave)*T 232 | b = gamma**2 * np.sum(intg**2)*T 233 | return b 234 | 235 | def calcgradinfo(g,T=0.000004,k0=0,R=0.35,L=0.0014,eta=1/56, gamma = 4258): 236 | s = np.shape(g) 237 | lg = np.max(np.size(g)) 238 | k = k0+np.cumsum(g)*gamma*T 239 | t = T*(np.arange(len(g))-0.5) 240 | t = np.transpose(t) 241 | tt = t*np.ones(s[1]) 242 | s = [[g],[g[lg]]]-[[0*g[0]],[g]] 243 | sz = np.shape(s) 244 | s = s[1:sz[0]]/T 245 | m1 = np.cumsum(g*tt)*gamma*T 246 | m2 = np.cumsum(g*(tt*tt+T**2/12))**gamma*T 247 | v = (1/eta)*(L*s+R*g) 248 | return k,g,s,m1,m2,t,v 249 | 250 | 251 | 252 | _default_R = [np.arange(0.2,1,.2),np.arange(0.1,0.8,.2),np.arange(-0.2,0.6,.2)] 253 | def corrnoise(mn=None,R=_default_R,n=100): 254 | if mn is None: 255 | mn = np.zeros(np.shape(R[0])[0]) 256 | nvect = np.random.randn(np.shape(R)[0],n) 257 | R = 0.5 * (R+ np.tranpose(R)) 258 | v,d = np.linalg.eig(R) 259 | 260 | if np.any(np.diag <=0): 261 | print('R must be positive definite') 262 | 263 | w = v * np.sqrt(d) 264 | nc = w*nvect 265 | Rout = (nc*np.tranpose(nc))/n 266 | return nc,Rout 267 | 268 | 269 | def cropim(im,sx=None,sy=None): 270 | sz = np.shape(im) 271 | if sx is None: sx = np.floor(sz[0]/2) 272 | if sy is None: sy = sx 273 | if sx < 1: sx = sz[0]*sx 274 | if sy < 1: sy = sz[0]*sy 275 | 276 | stx = np.floor(sz[1]/2-sx/2)+1 277 | sty = np.floor(sz[0]/2-sy/2)+1 278 | 279 | return im[sty:sty+sy-1, stx:stx+sx-1] 280 | 281 | 282 | 283 | def csens2d(klow): 284 | sz = np.shape(klow) 285 | nc = sz[2] 286 | 287 | cs = np.fft.fftshift(np.fft.fft(np.fft.fftshift(klow,axis=0),axis=0),axis=0) 288 | cs = np.fft.fftshift(np.fft.fft(np.fft.fftshift(cs,axis=1),axis=1),axis=1) 289 | cmrms = np.sqrt(np.sum(np.conj(cs)*cs,axis=-1)) 290 | meancm = np.mean(cmrms) 291 | 292 | f = np.argwhere(cmrms == 0) 293 | cs = np.reshape(cs,sz[0]*sz[1],nc) 294 | cmrms[f] = meancm/100 295 | 296 | cs[f] = meancm/10000 297 | ncs = cs / (cmrms*np.ones[0,nc]) 298 | ncs = np.reshape(ncs,sz) 299 | cs = np.reshape(cs,sz) 300 | 301 | return ncs, cs, cmrms 302 | 303 | 304 | def dispangle(arr): 305 | angarr = np.angle(arr)+np.pi 306 | dispim(angarr,0,2*np.pi) 307 | 308 | 309 | def dispim(im,low=0,high = None): 310 | im = np.squeeze(im) 311 | 312 | if high is None: 313 | immax = np.max(np.abs(im)) 314 | imstd = np.std(np.abs(im)) 315 | high = immax - 0.5 * imstd 316 | 317 | scale = 256/(high-low) 318 | offset = scale*low 319 | 320 | from matplotlib import cm 321 | colormap = cm.get_cmap() 322 | if colormap.is_gray(): 323 | print('set the colormap') 324 | plt.imshow(np.abs(im)) 325 | plt.axis('square') 326 | 327 | 328 | def displogim(im): 329 | im = np.squeeze(im) 330 | lowexp = 0 331 | im = np.log(np.abs(im)) 332 | 333 | f = np.where(im1 and np.abs(flipangle).all()=0) 446 | Fstates = np.hstack([np.fliplr(np.conj(FpFmZ[1:2,1:])), FpFmZ[0:1,0:]]) 447 | 448 | # -- Get last row (Z states) 449 | Zstates = 2.*FpFmZ[2:,:] # Double to account for one-sided FT. 450 | Zstates[0,0]=Zstates[0,0]/2 # Don't double Z0 451 | #print("Z states (doubled for n>0) = %s" % Zstates) 452 | 453 | # -- Fourier transform to Mxy 454 | Mxy = np.matmul(Fstates,fmat) 455 | #print("Mxy is %s" % Mxy) 456 | 457 | # -- Fourier transform to Mz 458 | fmatz = fmat[Ns:,:] 459 | #print("fmatz = %s" % fmatz) 460 | Mz = np.real(np.matmul(Zstates,fmat[Ns:,:])) 461 | #print("Mz is %s " % Mz) 462 | 463 | # -- Extract Mx, My and Mz and return as 3xN. 464 | spins = np.concatenate((np.real(Mxy),np.imag(Mxy),Mz),axis=0) 465 | 466 | return spins 467 | 468 | 469 | 470 | 471 | 472 | def epg_grad(FpFmZ=[[1],[1],[0]], noadd=0, positive = True): 473 | if not noadd: 474 | FpFmZ = np.hstack([FpFmZ, [[0],[0],[0]]]) 475 | if positive: 476 | FpFmZ[0][1:] = FpFmZ[0][:-1] 477 | FpFmZ[1][:-1] = FpFmZ[1][1:] 478 | FpFmZ[1][-1] = 0; 479 | FpFmZ[0,0] = np.conj(FpFmZ[1,0]) 480 | else: 481 | FpFmZ[1][1:] = FpFmZ[1][:-1] 482 | FpFmZ[0][:-1] = FpFmZ[0][1:] 483 | FpFmZ[0][-1] = 0; 484 | FpFmZ[1,0] = np.conj(FpFmZ[0,0]) 485 | 486 | return FpFmZ 487 | 488 | def epg_mgrad(*kw, **kws): 489 | "Negative gradients" 490 | return epg_grad(positive = False, *kw, **kws) 491 | 492 | 493 | # Discard states higher than highest with a coefficient greater than thres 494 | def epg_trim(FpFmZ, thres): 495 | 496 | a = np.max(np.argwhere(np.abs(FpFmZ)>thres),axis=0)[1] 497 | FpFmZ = FpFmZ[:,:a+1] 498 | return FpFmZ 499 | 500 | 501 | def epg_rf(FpFmZ = [[0],[0],[1]], alpha = 90.,phi = 90, in_degs = True, return_rotation = False, 502 | frames = 1, ploton = PLOTON): 503 | if frames > 1: 504 | for xi in range(frames): 505 | FpFmZ = epg_rf(FpFmZ, alpha/frames , phi, in_degs, return_rotation=False, frames=1, ploton = False) 506 | epg_show(FpFmZ) 507 | return 0 508 | 509 | if in_degs: 510 | alpha = alpha*np.pi/180. 511 | phi = phi*np.pi/180. 512 | 513 | RR = [[(np.cos(alpha/2.))**2., np.exp(2.*1j*phi)*(np.sin(alpha/2.))**2., -1j*np.exp(1j*phi)*np.sin(alpha)], 514 | [np.exp(-2.*1j*phi)*(np.sin(alpha/2.))**2., (np.cos(alpha/2.))**2., 1j*np.exp(-1j*phi)*np.sin(alpha)], 515 | [-1j/2.*np.exp(-1j*phi)*np.sin(alpha), 1j/2.*np.exp(1j*phi)*np.sin(alpha), np.cos(alpha)]]; 516 | 517 | if return_rotation: 518 | return RR 519 | else: 520 | return np.matmul(RR,FpFmZ) 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | def epg_stim_calc(flips, in_degs = True): 530 | P = [[0],[0],[1]] 531 | 532 | if in_degs: 533 | flips = np.array(flips)*np.pi/180. 534 | 535 | for flip in flips: 536 | P = epg_rf(P, flips,np.pi/2, in_degs = in_degs) 537 | P = epg_grelax(P,1,.2,0,1,0,1) 538 | S = P[0,0] 539 | return S,P 540 | 541 | 542 | 543 | def ft(dat): 544 | return np.fft.fftshift(np.fft.fft2(np.fft.fftshift(dat))) 545 | 546 | def gaussian(x,mn,sig): 547 | return np.exp(-(x-mn)**2/(2*sig**2))/np.sqrt(2*np.pi)/sig 548 | 549 | def ghist(data,gmean = None,gsig = None,bins = None, 550 | gtitle = 'Data and Gaussian'): 551 | N = len(data) 552 | 553 | def gridmat(): 554 | pass 555 | 556 | def homodyneshow(im,w=16): 557 | m,n = np.shape(im) 558 | lpf = np.zeros(m) 559 | lpf[np.floor(m/2)-w/2:np.floor(m/2)+w/2-1] =1 560 | 561 | mf = np.cumsum(lpf) 562 | mf = 2*mf/np.max(mf) 563 | subplot(3,2,1) 564 | plot(lpf) 565 | subplot(3,2,2) 566 | plot(mf) 567 | ksp = np.fft.ifftshift(np.fft.ifft2(np.fft.ifftshift(im))) 568 | klp = np.diag(lpf)*ksp 569 | imlp = ft(klp) 570 | subplot(3,2,3) 571 | dispim(imlp) 572 | plt.axis('off') 573 | title('low res image') 574 | subplot(3,2,4) 575 | dispangle(imlp) 576 | plt.axis('off') 577 | title('phase') 578 | 579 | khf = np.diag(mf)*ksp 580 | imhf = ft(khf) 581 | subplot(3,2,5) 582 | dispim(imhf) 583 | title('zero filled') 584 | subplot(3,2,6) 585 | dispangle(imhf) 586 | title('phase') 587 | 588 | phest = np.angle(imlp) 589 | imhd = imhf * np.exp(-1j*phest) 590 | return imhd 591 | 592 | def ksquare(center=0, swidth=1.9, kloc=None, tsamp=0.000004, df=0): 593 | if kloc is None: 594 | kx,ky = np.meshgrid(np.arange(-128,128)/128*5, np.arange(-128,128)/128*5) 595 | kloc = kx*i*ky 596 | sdata = swidth* np.sinc(swidth*np.real(kloc))*np.sinc(swidth*np.imag(kloc)) 597 | kdata = 0*sdata 598 | 599 | for q in np.arange(len(center)): 600 | thisk = np.exp(1j*2*np.pi*np.real(center(q))*np.real(kloc))*sdata 601 | thisk = np.exp(1j*2*np.pi*np.imag(center(q))*np.imag(kloc))*thisk 602 | kdata = kdata + thisk 603 | 604 | ph = np.exp(2*1j*np.pi*tsamp*np.arange(len(kloc))*df) 605 | kdata = np.diag(ph)*kdata 606 | return kdata 607 | 608 | 609 | 610 | def lfphase(m,n = None,w = 4): 611 | if n is None: 612 | n=m 613 | arr = np.zeros((m,n)) 614 | arr[np.floor[m/2-w]:np.floor[m/2-w],np.floor[n/2-w]:np.floor[n/2-w]] = np.random.randn((2*w+1,2*w+1)) 615 | ang = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(arr))) 616 | ang = np.real(ang) 617 | ang = 2*np.pi(ang-np.min(ang))/(np.max(ang)-np.min(ang)-np.pi) # watch the dimensions.. 618 | ph = np.exp(1j*ang) 619 | return ph 620 | 621 | def lplot(xlab = None,ylab = None,tit = None,ax = None,grid = None): 622 | if xlab is not None: 623 | xlabel(xlab) 624 | if ylab is not None: 625 | ylabel(ylab) 626 | if title is not None: 627 | title(tit) 628 | if ax is not None: 629 | plt.axis(ax) 630 | 631 | 632 | 633 | def lsfatwater(): 634 | pass 635 | 636 | 637 | def magphase(x,arr): 638 | mag = np.abs(arr) 639 | phase = np.angle(arr) 640 | 641 | fig1 = plt.subplot(2,1,1) 642 | plt.plot(x,phase/np.pi) 643 | 644 | 645 | # epg_showstate(ax,FZ,frac=0,Nspins=19,voxvar=0): 646 | # 647 | # Plots a set of vectors on a single axis, from EPG coefficients. 648 | # 649 | # INPUTS: 650 | # ax = plot figure/axis upon which to plot 651 | # FZ = EPG coefficient matrix to display 652 | # frac = Additional dephasing. Essentially adds to n for F_n 653 | # states to show the dephasing/rephasing during gradient. 654 | # Nspins = Number of spins to use in graphical displays 655 | # voxvar = 1 to vary origin of vectors along z, 2 along x and 656 | # 0 all from (0,0,0). Often use 1 for F_n and 2 for Z_n. 657 | # 658 | # OUTPUT: 659 | # M = corresponding magnetization endpoints ([[Mx],[My],[Mz]]) 660 | 661 | def epg_showstate(ax,FZ,frac=0,Nspins=19,voxvar=0): 662 | 663 | M = epg_FZ2spins(FZ,Nspins) 664 | scale = 1. 665 | 666 | mx = M[0:1,:] 667 | my = M[1:2,:] 668 | mz = M[2:3,:] 669 | x = np.zeros((1,Nspins)) 670 | y = np.zeros((1,Nspins)) 671 | z = np.zeros((1,Nspins)) 672 | 673 | if (voxvar==1): 674 | z = 2*(np.arange(0,Nspins)-np.floor(Nspins/2))/Nspins #2x fills axis! 675 | if (voxvar==2): 676 | x = 2*(np.arange(0,Nspins)-np.floor(Nspins/2))/Nspins 677 | #x = 2*(np.arange(0,Nspins)-np.float(Nspins)/2.+0.5)/Nspins 678 | 679 | ax.quiver(x, y, z, mx,my,mz,normalize=False) 680 | ax.set(xlim=(-scale,scale),ylim=(-scale,scale),zlim=(-scale,scale)) 681 | 682 | 683 | # -- Turn off grid axis with numbering, and add lines 684 | axlims = np.array([-1,1]) 685 | ax.plot(axlims,0*axlims,0*axlims,'k-') # x axis 686 | ax.plot(0*axlims,axlims,0*axlims,'k-') # y axis 687 | ax.plot(0*axlims,0*axlims,axlims,'k-') # z axis 688 | ax.set_axis_off() 689 | 690 | return M 691 | 692 | 693 | 694 | # epg_show() 695 | # Uses subplots to graphically show EPG decomposition of magnetisation. 696 | # Subplots are rows for F+, F- and Z coefficients. In most epg_ functions 697 | # a matrix of the same size, "FZ" or "FpFmZ" is used to store the 698 | # coefficients. 699 | # 700 | # INPUTS: 701 | # FZ = EPG coefficient matrix to display 702 | # Nspins = Number of spins to use in graphical displays 703 | # frac = Additional dephasing. Essentially adds to n for F_n 704 | # states to show the dephasing/rephasing during gradient. 705 | # skipfull = True will not show "full" spins state in row 2, col 1 706 | # twists = True to show as "twists" and "cosines" for F/Z vs 707 | # False to have all vectors start at (0,0,0). 708 | # OUTPUT: 709 | # none (plot is updated) 710 | # 711 | 712 | def epg_show(FZ,Nspins=19,frac=0,skipfull=False,twists=True): 713 | # from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import 714 | # fig = plt.figure() 715 | # ax = fig.gca(projection='3d') 716 | 717 | # STARTING to write this! 718 | # Basic version works... lots to do! 719 | 720 | m = np.shape(FZ)[0] 721 | n = np.shape(FZ)[1] 722 | slabel = ('F_{','F_{-','Z_{') 723 | 724 | #fig = plt.figure(figsize=plt.figaspect(np.float(m)/np.float(n))) 725 | fig = plt.figure(figsize=(3*n,3*m)) # Note (width,height) 726 | 727 | # -- Go through FZ Matrix 728 | for mm in range(m): 729 | for nn in range(n): 730 | figax = fig.add_subplot(m,n,nn+mm*n+1, projection='3d') 731 | voxelvar=0 # -- Initialize - all M vectors start at (0,0,0) 732 | if (twists==True): 733 | voxelvar=1 # -- Twists for F by default 734 | if (mm > 1): 735 | voxelvar=2 # -- Z states with variation along x 736 | 737 | 738 | if (nn==0 and mm==1): # -- Plot of all states/spins 739 | if (skipfull==False): 740 | epg_showstate(figax,FZ,frac=frac,Nspins=Nspins,voxvar=0) # All spins 741 | figax.title.set_text('All Spins') # All spins combined 742 | else: 743 | figax.set_axis_off() 744 | 745 | else: # -- Plot single state. 746 | Q = 0*FZ 747 | Q[mm,nn]=FZ[mm,nn] # -- Just 1 non-zero basis at a time 748 | epg_showstate(figax,Q,frac=frac,Nspins=Nspins,voxvar=voxelvar) 749 | 750 | # -- Label state 751 | stateval = "%s%d} = %4g +%4gj" % (slabel[mm], \ 752 | nn,np.real(FZ[mm,nn]),np.imag(FZ[mm,nn])) 753 | figax.title.set_text(stateval) # F+ states 754 | #!!! Would be great to make subscript in titles but 755 | # Jupyter doesn't seem to handle that. 756 | 757 | # !!! Orient plots! Could make F states from above so you just 758 | # see the circle. Once they are in color, this could be a good 759 | # option. 760 | 761 | return 762 | 763 | 764 | 765 | def circ(radius, nx,ny): 766 | mx, my = np.meshgrid(np.arange(-nx/2,nx/2+1),np.arange(-ny/2,ny/2+1)) 767 | mout = mx**2+my**2 768 | mout[moutradius] = 1000 770 | mout = (-mout/2000)+1 771 | return mout 772 | 773 | def makenoiseykspace(nscale = 0.001): 774 | im = circ(100, 256,256) 775 | ksp = np.fft.ifftshift(np.fft.ifft2(np.fft.ifftshift(im))) 776 | kspace = [] 777 | for n in np.arange(1000): 778 | kspace.append(ksp+1j*nscale*np.random.randn((256,256))+nscale*np.random.randn((256,256))) 779 | return np.array(kspace) 780 | 781 | def mingrad(area,Gmax = 50,Smax = 200,dt = 0.004, gamma = 42.58): 782 | area = area*100 783 | Atri = gamma *Gmax**2/Smax 784 | 785 | if area <= Atri: 786 | tramp = np.sqrt(area/gamma/Smax) 787 | Nramp = np.ceil(tramp/dt) 788 | g = np.arange(Nramp)*Smax*dt 789 | g = np.array((g,np.fliplr(g))) 790 | else: 791 | tramp = Gmax/Smax 792 | Nramp = np.ceil(tramp/dt) 793 | gramp = np.arange(Nramp)/Nramp*Gmax 794 | Nplat = np.ceil(area/gamma/Gmax/dt - Gmax/Smax/dt) 795 | g = np.array([gramp, Gmax*np.ones(Nplat), np.fliplr(gramp)]) 796 | t = np.arange(len(g))*dt 797 | g = g * area/gamma/np.sum(g)/dt 798 | return g, t 799 | 800 | 801 | 802 | 803 | def nlegend(): 804 | pass 805 | 806 | def plotc(): 807 | pass 808 | 809 | def plotgradinfo(): 810 | pass 811 | 812 | 813 | 814 | def setprops(): 815 | import matplotlib.style as style 816 | style.use('seaborn') # 'ggplot' 817 | 818 | 819 | def sinc(x): 820 | return np.sinc(x) 821 | 822 | def sweptfreqrf(): 823 | pass 824 | 825 | def throt(theta = 0., phi=0., in_degs = True): 826 | 'This is equivalent to help' 827 | if in_degs: 828 | theta = theta*np.pi/180. 829 | phi = phi*np.pi/180. 830 | 831 | ca = np.cos(theta) 832 | sa = np.sin(theta) 833 | cp = np.cos(phi) 834 | sp = np.sin(phi) 835 | M = np.array([[cp*cp+sp*sp*ca, cp*sp*(1-ca), -sp*sa], 836 | [cp*sp-sp*cp*ca, sp*sp+cp*cp*ca, cp*sa], 837 | [sa*sp, -sa*cp, ca]]) 838 | 839 | return M 840 | 841 | 842 | 843 | 844 | def whirl(N,res,fov, tsample = 0.000004, 845 | upsamp = 16, gmax = 3.9, smax = 14500, 846 | gamma = 4258): 847 | 848 | dT = tsample/upsamp 849 | kmax = 0.5/res 850 | delta = N/2./np.pi/fov 851 | r1 = 1./np.sqrt(5)*delta 852 | r2 = 3./np.sqrt(5)*delta 853 | 854 | 855 | Gc= np.sqrt(2*smax*r1/gamma) 856 | g = np.arange(0,Gc, smax*dT) 857 | k = np.cumsum(g)*gamma*dT 858 | ng = len(g) 859 | ng1 = ng*1 # multiply by one to create a new instance because python is funny 860 | 861 | G = g[-1] 862 | r = k[-1] 863 | kk = k[-1] 864 | phi = 0 865 | 866 | # pre allocate space? 867 | maxng = 10000*upsamp 868 | if maxng > len(g): 869 | g = np.pad(g, maxng-len(g), 'constant').astype(np.complex) 870 | k = np.pad(k, maxng-len(k), 'constant').astype(np.complex) 871 | Grec = np.array(g) 872 | Grec[10000] = 0 873 | phirec = 0*Grec 874 | 875 | done = False 876 | dphi = smax/np.abs(G)*dT 877 | 878 | while r= gmax: 902 | G = gmax 903 | dG = 0 904 | 905 | phi = phi+dphi 906 | g[ng] = G*np.exp(1j*phi) 907 | 908 | kk = kk+g[ng]*dT*gamma 909 | k[ng] = kk 910 | r = np.abs(kk) 911 | 912 | if ng>= maxng: 913 | print('max points', maxng) 914 | 915 | 916 | k = k[:ng:upsamp] 917 | g = g[:ng:upsamp] 918 | 919 | g1 = g[:np.round(ng1/upsamp)] 920 | g2 = g[np.round(ng1/upsamp):np.round(ng2/upsamp)] 921 | g3 = g[np.round(ng2/upsamp)+1:len(g)] 922 | 923 | return g, g1, g2, g3 924 | 925 | 926 | 927 | def vds(smax, gmax, T, N, Fcoeff, rmax, z=0, 928 | gamma = 4258, oversamp = 8): 929 | 930 | To = T*1./oversamp 931 | 932 | q0 = 0. 933 | q1 = 0. 934 | 935 | Nprepare = 10000 936 | theta = np.zeros(Nprepare) 937 | r = np.zeros(Nprepare) 938 | 939 | r0 = 0 940 | r1 = 1 941 | 942 | time = np.zeros(Nprepare) 943 | t = 0 944 | count = 1 945 | 946 | theta = np.zeros(Nprepare) 947 | r = np.zeros(Nprepare) 948 | time = np.zeros(Nprepare) 949 | 950 | while r < rmax: 951 | print('Error findq2r2 does not exist') 952 | 953 | q1 = q1+q2*To 954 | q0 = q0 + q1*To 955 | t = t+To 956 | r1 = r1+r2*To 957 | r0 = r0 + r1*To 958 | 959 | count = count+1 960 | theta[count] = q0 961 | r[count] = r0 962 | time[count] = t 963 | 964 | if np.remainder(count,100) == 0: 965 | print('points') 966 | r = r[oversamp/2:count:oversamp] 967 | theta = theta[oversamp/2:count:oversamp] 968 | time = time[oversamp/2:count:oversamp] 969 | 970 | ltheta = 4*np.floor(len(theta)/4) 971 | r = r[:ltheta] 972 | theta = theta[:ltheta] 973 | time = time[:ltheta] 974 | 975 | r = r*np.exp(1j*theta) 976 | g = 1./gamma*(np.gradient(g))/T 977 | 978 | s = np.gradient(g) 979 | 980 | 981 | def qdf(a,b,c): 982 | return np.roots([a,b,c]) 983 | 984 | 985 | def findq2r2(smax, gmax, r, r1, T, Ts, N, Fcoeff, rmax, z): 986 | gamma = 4258. 987 | smax = smax + z*gmax 988 | F= 0. 989 | dFdr = 0 990 | for rind in np.arange(Fcoeff): 991 | F = F+Fcoeff[rind]*(r/rmax)**(rind-1) 992 | if rind>1: 993 | dFdr = dFdr + (rind-1)*Fcoeff[rind]*(r/rmax)**(rind-2)/rmax 994 | 995 | GmaxFOV = 1./gamma/F/Ts 996 | Gmax = np.min([GmaxFOV, gmax]) 997 | maxr1 = np.sqrt((gamma*Gmax)**2)/(1+(2*p.pi*F*r/N)**2) 998 | 999 | if r1>maxr1: 1000 | r2 = (maxr1-r1)/T 1001 | 1002 | twopiFoN = 2*np.pi*F/N 1003 | twopiFoN2 = twopiFoN**2 1004 | 1005 | A = 1+ twopiFoN2*r*r 1006 | 1007 | B = 2*twopiFoN2*r*r1*r1 + 2*twopiFoN2/F*dFdr*r*r*r1*r1 + 2*z*r1 + 2*twopiFoN2*r1*r 1008 | C1 = twopiFoN2**2*r*r*r1**4 + 4*twopiFoN2*r1**4 + (2*pi/N*dFdr)**2*r*r*r1**4 + 4*twopiFoN2/F*dFdr*r*r1**4 - (gamma)**2*smax**2 1009 | C2 = z*(z*r1**2 + z*twopiFoN2*r1**2 + 2*twopiFoN2*r1**3*r + 2*twopiFoN2/F*dFdr*r1**3*r) 1010 | C = C1+C2; 1011 | 1012 | theroots = np.roots((A,B,C)) 1013 | r2 = np.real(theroots[0]) 1014 | 1015 | slew = 1./gamma*(r2*twopiFoN2*r*r1**2+1j*twopiFoN*(2*r1**2+r*r2+dFdr/F*r*r1**2)) 1016 | sr = np.abs(slew)/smax 1017 | 1018 | if np.abs(slew)/smax > 1.01: 1019 | print('slew',slew) 1020 | 1021 | q2 = 2*np.pi/N*dFdr*r1**2+2*np.pi/F/N*r2 1022 | 1023 | return q2, r2 1024 | 1025 | 1026 | def vecdcf(g,k = None,N = None,res = None,FOV = None): 1027 | if np.shape(g)[1]==2: 1028 | g = g[:,0]+1j*g[:,1] 1029 | 1030 | if k is None: 1031 | k = np.minimum.accumulate(g, 0.5) # this isn't equivalent. hmm 1032 | k = k * 0.5/ np.max(np.abs(k)) # watch dimension collapse 1033 | 1034 | Nsamps = len(k) 1035 | 1036 | g3 = np.array([np.real(g), np.imag(g), 0*np.real(g)]) 1037 | k3 = np.array([np.real(k), np.imag(k), 0*np.real(k)]) 1038 | 1039 | dcf = 0*k 1040 | 1041 | for m in np.arange(Nsamps): 1042 | dcf[m] = np.linalg.norm() 1043 | # I'm afraid I have to stop for the moment. Pull! 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | --------------------------------------------------------------------------------