1_block_synchronizer.py 10 KB

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