pulseq_jemris_simulator.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. # Same role as pulseq_bloch_simulator.py except (1) It uses JEMRIS (2) It is run as a function, not a script
  2. # INPUTS: sequence type, geometry parameters, contrast parameters,
  3. # phantom type (pre-set or custom), coil type (pre-set or custom) (Tx / Rx),
  4. # k-space trajectory (if noncartesian; flattened version (Nro_total, 3))
  5. import time
  6. import os
  7. from sim_jemris import sim_jemris
  8. from recon_jemris import read_jemris_output, recon_jemris
  9. from coil2xml import coil2xml
  10. #from virtualscanner.utils import constants
  11. from seq2xml import seq2xml
  12. from pypulseq.Sequence.sequence import Sequence
  13. import phantom as pht
  14. import numpy as np
  15. import xml.etree.ElementTree as ET
  16. from pulseq_library import make_pulseq_se_oblique,make_pulseq_gre_oblique, make_pulseq_irse_oblique
  17. from scipy.io import savemat, loadmat
  18. def simulate_pulseq_jemris(seq_path, phantom_info, coil_fov,
  19. tx='uniform', rx='uniform', # TODO add input that includes sequence info for
  20. # TODO dimensioning the RO points into kspace
  21. tx_maps=None, rx_maps=None, sim_name=None, env_option="local"):
  22. """Runs simulation using an already-made .seq file
  23. Inputs
  24. ------
  25. seq_path : str
  26. Path to seq file
  27. phantom_info : dict
  28. Information used to create phantom; input to create_and_save_phantom()
  29. coil_fov : float
  30. Field-of-view of coil in mm
  31. tx : str, optional
  32. Type of tx coil; default is 'uniform'; the only other option is 'custom'
  33. rx : str, optional
  34. Type of rx coil; default is 'uniform'; the only other option is 'custom'
  35. tx_maps : list, optional
  36. List of np.ndarray (dtype='complex') maps for all tx channels
  37. Required for 'custom' type tx
  38. rx_maps : list, optional
  39. List of np.ndarray (dtype='complex') maps for all rx channels
  40. Required for 'custom' type rx
  41. sim_name : str, optional
  42. Used as folder name inside sim folder
  43. Default is None, in which case sim_name will be set to the current timestamp
  44. Returns
  45. -------
  46. sim_output :
  47. Delivers output from sim_jemris
  48. """
  49. if sim_name is None:
  50. sim_name = time.strftime("%Y%m%d%H%M%S")
  51. if env_option == 'local':
  52. target_path = 'sim\\' + sim_name
  53. elif env_option == 'colab':
  54. target_path = 'sim\\' + sim_name
  55. # Make target folder
  56. dir_str = f'sim\\{sim_name}'
  57. if not os.path.isdir(dir_str):
  58. os.system(f'mkdir {dir_str}')
  59. # Convert .seq to .xml
  60. seq = Sequence()
  61. seq.read(seq_path)
  62. print(seq.get_block(1))
  63. seq_name = seq_path[seq_path.rfind('/')+1:seq_path.rfind('.seq')]
  64. seq2xml(seq, seq_name=seq_name, out_folder=str(target_path))
  65. # Make phantom and save as .h5 file
  66. pht_name = create_and_save_phantom(phantom_info, out_folder=target_path)
  67. # Make sure we have the tx/rx files
  68. tx_filename = tx + '.xml'
  69. rx_filename = rx + '.xml'
  70. # Save Tx as xml
  71. if tx == 'uniform':
  72. cp_command = f'copy {os.getcwd()}\\sim\\{tx}.xml {os.getcwd()}\\{str(target_path)}\\{tx}.xml'
  73. print(cp_command)
  74. a = os.system(cp_command)
  75. print(a)
  76. elif tx == 'custom' and tx_maps is not None:
  77. coil2xml(b1maps=tx_maps, fov=coil_fov, name='custom_tx', out_folder=target_path)
  78. tx_filename = 'custom_tx.xml'
  79. else:
  80. raise ValueError('Tx coil type not found')
  81. # save Rx as xml
  82. if rx == 'uniform':
  83. os.system(f'copy sim\\{rx}.xml sim\\{str(target_path)}')
  84. elif rx == 'custom' and rx_maps is not None:
  85. coil2xml(b1maps=rx_maps, fov=coil_fov, name='custom_rx', out_folder=target_path)
  86. rx_filename = 'custom_rx.xml'
  87. else:
  88. raise ValueError('Rx coil type not found')
  89. # Run simuluation in target folder
  90. list_sim_files = {'seq_xml': seq_name+'.xml', 'pht_h5': pht_name + '.h5', 'tx_xml': tx_filename,
  91. 'rx_xml': rx_filename}
  92. sim_output = sim_jemris(list_sim_files=list_sim_files, working_folder=target_path)
  93. return sim_output
  94. # TODO
  95. def create_and_save_phantom(phantom_info, out_folder):
  96. """Generates a phantom and saves it into desired folder as .h5 file for JEMRIS purposes
  97. Inputs
  98. ------
  99. phantom_info : dict
  100. Info of phantom to be constructed
  101. REQUIRED
  102. 'fov' : float, field-of-view [meters]
  103. 'N' : int, phantom matrix size (isotropic)
  104. 'type' : str, 'spherical', 'cylindrical' or 'custom'
  105. 'dim' : int, either 3 or 2; 3D or 2D phantom options
  106. 'dir' : str, {'x', 'y', 'z'}; orientation of 2D phantom
  107. OPTIONAL (only required for 'custom' phantom type)
  108. 'T1' : np.ndarray, T1 map matrix
  109. 'T2' : np.ndarray, T2 map matrix
  110. 'PD' : np.ndarray, PD map matrix
  111. 'dr' : float, voxel size [meters] (isotropic)
  112. 'dBmap' : optional even for 'custom' type. If not provided, dB is set to 0 everywhere.
  113. out_folder : str or pathlib Path object
  114. Path to directory where phantom will be saved
  115. Returns
  116. -------
  117. pht_type : str
  118. phantom_info['pht_type'] (returned for naming purposes)
  119. """
  120. out_folder = str(out_folder)
  121. FOV = phantom_info['fov']
  122. N = phantom_info['N']
  123. pht_type = phantom_info['type']
  124. pht_dim = phantom_info['dim']
  125. pht_dir = phantom_info['dir']
  126. pht_loc = phantom_info['loc']
  127. sim_phantom = 0
  128. if pht_type == 'spherical':
  129. print('Making spherical phantom')
  130. T1s = [1000*1e-3]
  131. T2s = [100*1e-3]
  132. PDs = [1]
  133. R = 0.8*FOV/2
  134. Rs = [R]
  135. if pht_dim == 3:
  136. sim_phantom = pht.makeSphericalPhantom(n=N, fov=FOV, T1s=T1s, T2s=T2s, PDs=PDs, radii=Rs)
  137. elif pht_dim == 2:
  138. sim_phantom = pht.makePlanarPhantom(n=N, fov=FOV, T1s=T1s, T2s=T2s, PDs=PDs, radii=Rs,
  139. dir=pht_dir, loc=0)
  140. elif pht_type == 'cylindrical':
  141. print("Making cylindrical phantom")
  142. sim_phantom = pht.makeCylindricalPhantom(dim=pht_dim, n=N, dir=pht_dir, loc=pht_loc)
  143. elif pht_type == 'custom':
  144. # Use a custom file!
  145. T1 = phantom_info['T1']
  146. T2 = phantom_info['T2']
  147. PD = phantom_info['PD']
  148. dr = phantom_info['dr']
  149. if 'dBmap' in phantom_info.keys():
  150. dBmap = phantom_info['dBmap']
  151. else:
  152. dBmap = 0
  153. sim_phantom = pht.Phantom(T1map=T1, T2map=T2, PDmap=PD, vsize=dr, dBmap=dBmap, loc=(0,0,0))
  154. else:
  155. raise ValueError("Phantom type non-existent!")
  156. # Save as h5
  157. sim_phantom.output_h5(out_folder, pht_type)
  158. return pht_type
  159. if __name__ == '__main__':
  160. # Define the same phantom
  161. phantom_info = {'fov': 0.256, 'N': 32, 'type': 'cylindrical', 'dim':2, 'dir':'z'}
  162. sim_names = ['test0413_GRE', 'test0413_SE', 'test0413_IRSE']
  163. sps = ['gre_fov256mm_Nf15_Np15_TE50ms_TR200ms_FA90deg.seq',
  164. 'se_fov256mm_Nf15_Np15_TE50ms_TR200ms_FA90deg.seq',
  165. 'irse_fov256mm_Nf15_Np15_TI20ms_TE50ms_TR200ms_FA90deg.seq']
  166. # make_pulseq_irse_oblique(fov=0.256,n=15, thk=0.005, tr=0.2, te=0.05, ti=0.02, fa=90,
  167. # enc='xyz', slice_locs=[0], write=True)
  168. # make_pulseq_gre_oblique(fov=0.256,n=15, thk=0.005, tr=0.2, te=0.05, fa=90,
  169. # enc='xyz', slice_locs=[0], write=True)
  170. # make_pulseq_se_oblique(fov=0.256,n=15, thk=0.005, tr=0.2, te=0.05, fa=90,
  171. # enc='xyz', slice_locs=[0], write=True)
  172. simulate_pulseq_jemris(seq_path=sps[0], phantom_info=phantom_info, sim_name=sim_names[0],
  173. coil_fov=0.256)
  174. kk, im, images = recon_jemris(file='sim/' + sim_names[0] + '/signals.h5', dims=[15,15])
  175. savemat('sim/'+sim_names[0]+'/output.mat', {'images': images, 'kspace': kk, 'imspace': im})