1_block_synchronizer.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # synchronizer : converts Pulseq (.seq) files into sequences of amplitude, time and synchro sets.
  2. # Output is given by MRI machine
  3. # Babich Nikita, Kozin Roman, Karsakov Grigory
  4. # March 2024
  5. import numpy as np
  6. from matplotlib import pyplot as plt
  7. from pulseq_fixed import sequence_fixed as puls_fix
  8. def output_seq(dict):
  9. """
  10. The interpretation from pypulseq format of sequence to the files needed to analog part of MRI
  11. Интерпретация последовательности из формата pypulseq в файлы, необходимые для аналоговой части МРТ.
  12. :param dict: Dictionary of the impulse sequence pypulseq provided
  13. Словарь импульсных последовательностей, предоставленный pypulseq
  14. :return: files in "data_output_seq/" directory of every type of amplitudes and time points
  15. Запись в файлы в директории "data_output_seq/" амплитуд и времени для всех типов
  16. """
  17. loc_t_adc = dict['t_adc']
  18. loc_t_rf = dict['t_rf']
  19. loc_t_rf_centers = dict['t_rf_centers']
  20. loc_t_gx = dict['t_gx']
  21. loc_t_gy = dict['t_gy']
  22. loc_t_gz = dict['t_gz']
  23. loc_adc = dict['adc']
  24. loc_rf = dict['rf']
  25. loc_rf_centers = dict['rf_centers']
  26. loc_gx = dict['gx']
  27. loc_gy = dict['gy']
  28. loc_gz = dict['gz']
  29. with open('data_output_seq/t_adc.txt', 'w') as f:
  30. data = str(tuple(loc_t_adc))
  31. f.write(data)
  32. with open('data_output_seq/t_rf.txt', 'w') as f:
  33. data = str(tuple(loc_t_rf))
  34. f.write(data)
  35. with open('data_output_seq/t_rf_centers.txt', 'w') as f:
  36. data = str(tuple(loc_t_rf_centers))
  37. f.write(data)
  38. with open('data_output_seq/t_gx.txt', 'w') as f:
  39. data = str(tuple(loc_t_gx))
  40. f.write(data)
  41. with open('data_output_seq/t_gy.txt', 'w') as f:
  42. data = str(tuple(loc_t_gy))
  43. f.write(data)
  44. with open('data_output_seq/t_gz.txt', 'w') as f:
  45. data = str(tuple(loc_t_gz))
  46. f.write(data)
  47. with open('data_output_seq/adc.txt', 'w') as f:
  48. data = str(tuple(loc_adc))
  49. f.write(data)
  50. with open('data_output_seq/rf.txt', 'w') as f:
  51. data = str(tuple(loc_rf))
  52. f.write(data)
  53. with open('data_output_seq/rf_centers.txt', 'w') as f:
  54. data = str(tuple(loc_rf_centers))
  55. f.write(data)
  56. with open('data_output_seq/gx.txt', 'w') as f:
  57. data = str(tuple(loc_gx))
  58. f.write(data)
  59. with open('data_output_seq/gy.txt', 'w') as f:
  60. data = str(tuple(loc_gy))
  61. f.write(data)
  62. with open('data_output_seq/gz.txt', 'w') as f:
  63. data = str(tuple(loc_gz))
  64. f.write(data)
  65. def adc_correction():
  66. """
  67. Helper function that rise times for correction of ADC events
  68. Вспомогательная функция получения времён для коррекции АЦП событий
  69. :return: rise_time: float, stores in pulseq, related to exact type of gradient events
  70. хранится в pulseq, связан с конкретным типом градиентного события
  71. fall_time: float, same as rise_time
  72. аналогично rise_time
  73. """
  74. rise_time, fall_time = None, None
  75. is_adc_inside = False
  76. for j in range(blocks_number - 1):
  77. iterable_block = seq_input.get_block(block_index=j + 1)
  78. if iterable_block.adc is not None:
  79. is_adc_inside = True
  80. rise_time = iterable_block.gx.rise_time
  81. fall_time = iterable_block.gx.fall_time
  82. if not is_adc_inside:
  83. raise Exception("No ADC event found inside sequence")
  84. return rise_time, fall_time
  85. def adc_event_edges(local_gate_adc):
  86. """
  87. Helper function that rise numbers of blocks of border correction of ADC events
  88. Вспомогательная функция для получения номеров блоков границ коррекции АЦП событий
  89. :return: num_begin_l: int, number of time block when adc event starts
  90. номер временного блока начала АЦП события
  91. num_finish_l: int, same but ends
  92. то же, но для окончания
  93. """
  94. num_begin_l = 0
  95. flag_begin = False
  96. flag_finish = False
  97. num_finish_l = 1
  98. for k in range(len(local_gate_adc) - 1):
  99. if local_gate_adc[k] != 0 and not flag_begin:
  100. num_begin_l = k
  101. flag_begin = True
  102. if local_gate_adc[k] != 0 and local_gate_adc[k + 1] == 0 and not flag_finish:
  103. num_finish_l = k
  104. flag_finish = True
  105. return num_begin_l, num_finish_l
  106. def synchronization(N_samples):
  107. ### MAIN LOOP ###
  108. ### ОСНОВНОЙ ЦИКЛ###
  109. for i in range(N_samples):
  110. # delaying of RF event for time period of local delay
  111. # задержка RF события на период времени локальной задержки
  112. if RF_assintant[0] - RF_raster < time_sample[i] < RF_assintant[0] + RF_raster:
  113. RF_stop = int(RF_assintant[1] / time_step)
  114. gate_rf[i:RF_stop] = 1.0
  115. var = 1
  116. # mandatory disabling of RF gate due to ADC work same time
  117. # принудительное отключение RF-шлюза из-за одновременной работы АЦП
  118. gate_rf_2 = map(lambda x: time_sample[i] - ADC_raster < x < time_sample[i] + ADC_raster and 1 or 0,
  119. seq_output_dict['t_adc'])
  120. if np.any(np.array(list(gate_rf_2)) > 0):
  121. gate_rf[i] = 0.0
  122. # TR switch with own delay before ADC turning
  123. # TR перключение с собственной задержкой перед включением АЦП
  124. gate_tr_1 = map(lambda x: time_sample[i] - ADC_raster < x < time_sample[i] + ADC_raster and 1 or 0,
  125. seq_output_dict['t_adc'])
  126. if np.any(np.array(list(gate_tr_1)) > 0):
  127. block_delay_tr = int(local_delay_tr / time_step)
  128. gate_tr_switch[i - block_delay_tr:i + 1] = 0.0
  129. # first step of ADC gate - enabling
  130. # первый шак АЦП шлюза - включение
  131. gate_adc_1 = map(lambda x: time_sample[i] - ADC_raster < x < time_sample[i] + ADC_raster and 1 or 0,
  132. seq_output_dict['t_adc'])
  133. if np.any(np.array(list(gate_adc_1)) > 0):
  134. gate_adc[i] = 1.0
  135. # adc correction sue to rise and fall time of gradient
  136. # АЦП коррекция в зависимости от времени нарастания или спада градиента
  137. # defining time that ADC need to be disabled during of
  138. # определение премени, когда АЦП необходимо отключить
  139. rise_time_loc, fall_time_loc = adc_correction()
  140. num_beg, num_fin = adc_event_edges(gate_adc)
  141. rise_time_tick = int(rise_time_loc / time_step)
  142. fall_time_tick = int(rise_time_loc / time_step)
  143. gate_adc[num_beg:num_beg + rise_time_tick] = 0.0
  144. gate_adc[num_fin - fall_time_tick:num_fin + 1] = 0.0
  145. gates_release = {"adc": gate_adc,
  146. "rf": gate_rf,
  147. "tr_switch": gate_tr_switch,
  148. "gx": gate_gx,
  149. "gy": gate_gy,
  150. "gz": gate_gz}
  151. # gates_output(gates_release)
  152. if __name__ == '__main__':
  153. print('')
  154. seq_file = "seq_store/SE_rfdeath_5000.seq"
  155. seq_input = puls_fix.Sequence()
  156. seq_input.read(file_path=seq_file)
  157. seq_output_dict = seq_input.waveforms_export(time_range=(0, 3))
  158. # artificial delays due to construction of the MRI
  159. # искусственные задержки из-за тех. особенностей МРТ
  160. RF_dtime = 100 * 1e-6
  161. TR_dtime = 100 * 1e-6
  162. time_info = seq_input.duration()
  163. blocks_number = time_info[1]
  164. time_dur = time_info[0]
  165. time_step = 20 * 1e-9
  166. N_samples = int(time_dur / time_step)
  167. time_sample = np.linspace(0, time_dur, N_samples)
  168. # output interpretation. all formats of files defined in method
  169. # интерпретация выхода. Все форматы файлов определены в методе
  170. output_seq(seq_output_dict)
  171. # defining constants of the sequence
  172. # определение констант последовательности
  173. local_definitions = seq_input.definitions
  174. ADC_raster = local_definitions['AdcRasterTime']
  175. RF_raster = local_definitions['RadiofrequencyRasterTime']
  176. gate_adc = np.zeros(N_samples)
  177. gate_rf = np.zeros(N_samples)
  178. gate_tr_switch = np.ones(N_samples)
  179. gate_gx = np.zeros(N_samples)
  180. gate_gy = np.zeros(N_samples)
  181. gate_gz = np.zeros(N_samples)
  182. local_delay_rf = RF_dtime
  183. local_delay_tr = TR_dtime
  184. local_raster_time = time_step
  185. RF_assintant = [seq_output_dict['t_rf'][0] - RF_dtime, seq_output_dict['t_rf'][-1]]
  186. synchronization(N_samples)
  187. # testing plots for synchronization
  188. # графики тестов синхрозации
  189. plt.plot(seq_output_dict['t_gx'][:int(N_samples)], seq_output_dict['gx'][:int(N_samples)])
  190. plt.plot(seq_output_dict['t_gy'][:int(N_samples)], seq_output_dict['gy'][:int(N_samples)])
  191. plt.plot(seq_output_dict['t_gz'][:int(N_samples)], seq_output_dict['gz'][:int(N_samples)])
  192. plt.savefig("plots_output/gradients.png")
  193. plt.show()
  194. plt.plot(seq_output_dict['t_gx'][:int(N_samples)], seq_output_dict['gx'][:int(N_samples)] / 720)
  195. plt.plot(time_sample[:int(N_samples)], gate_adc[:int(N_samples)], label='ADC gate')
  196. plt.plot(time_sample[:int(N_samples)], gate_tr_switch[:int(N_samples)], label='TR switch')
  197. plt.plot(seq_output_dict['t_rf'], seq_output_dict['rf'] / 210, label='RF signal')
  198. plt.plot(time_sample[:int(N_samples)], gate_rf[:int(N_samples)], label='RF gate')
  199. plt.legend()
  200. plt.savefig("plots_output/synchro_pulse.png")
  201. plt.show()