srv_interp.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. # -*- coding: utf-8 -*-
  2. """
  3. Created on 05/09/2024
  4. @author: spacexer
  5. """
  6. from LF_scanner import pypulseq as pp
  7. import numpy as np
  8. from types import SimpleNamespace
  9. import json
  10. from yattag import Doc, indent
  11. def seq_file_input(seq_file_name="empty.seq"):
  12. seq_input = pp.Sequence()
  13. seq_input.read(file_path=seq_file_name)
  14. seq_output_dict = seq_input.waveforms_export()
  15. return seq_input, seq_output_dict
  16. def output_seq(dict, param, path='test1/'):
  17. """
  18. The interpretation from pypulseq format of sequence to the files needed to analog part of MRI
  19. :param dict: Dictionary of the impulse sequence pypulseq provided
  20. :return: files in "grad_output/" directory of every type of amplitudes and time points
  21. """
  22. '''
  23. Gradient
  24. '''
  25. loc_t_gx = gradient_time_convertation(param, dict['t_gx'])
  26. loc_t_gy = gradient_time_convertation(param, dict['t_gy'])
  27. loc_t_gz = gradient_time_convertation(param, dict['t_gz'])
  28. loc_gx = gradient_ampl_convertation(param, dict['gx'])
  29. loc_gy = gradient_ampl_convertation(param, dict['gy'])
  30. loc_gz = gradient_ampl_convertation(param, dict['gz'])
  31. gx_out = duplicates_delete(np.transpose([loc_t_gx, loc_gx]))
  32. gy_out = duplicates_delete(np.transpose([loc_t_gy, loc_gy]))
  33. gz_out = duplicates_delete(np.transpose([loc_t_gz, loc_gz]))
  34. np.savetxt(path + 'gx.txt', gx_out, fmt='%10.0f')
  35. np.savetxt(path + 'gy.txt', gy_out, fmt='%10.0f')
  36. np.savetxt(path + 'gz.txt', gz_out, fmt='%10.0f')
  37. '''
  38. Radio
  39. '''
  40. rf_raster_local = param['rf_raster_time']
  41. rf_out = radio_ampl_convertation(dict["rf"], rf_raster=rf_raster_local)
  42. file_rf = open(path + 'rf_' + str(rf_raster_local) + '_raster.bin', "wb")
  43. for byte in rf_out:
  44. file_rf.write(byte.to_bytes(1, byteorder='big', signed=1))
  45. file_rf.close()
  46. '''
  47. for radiofreq tests
  48. '''
  49. # np.savetxt(path + 'rf_time.txt', np.transpose(dict["t_rf"]))
  50. # np.savetxt(path + 'rf_ampl.txt', np.transpose(dict["rf"]))
  51. # plt.plot(dict["t_rf"][0:2000], np.real(dict["rf"][0:2000]), label="real")
  52. # plt.plot(dict["t_rf"][0:2000], np.imag(dict["rf"][0:2000]), label="image")
  53. # plt.legend()
  54. # plt.show()
  55. def radio_ampl_convertation(rf_ampl, rf_raster=1e-6):
  56. #TODO: sampling resize to raster different with seqgen
  57. out_rf_list = []
  58. rf_ampl_raster = 127
  59. rf_ampl_maximum = np.abs(max(rf_ampl))
  60. proportional_cf_rf = rf_ampl_raster / rf_ampl_maximum
  61. for rf_iter in range(len(rf_ampl)):
  62. out_rf_list.append(round(rf_ampl[rf_iter].real * proportional_cf_rf))
  63. out_rf_list.append(round(rf_ampl[rf_iter].imag * proportional_cf_rf))
  64. return out_rf_list
  65. def duplicates_delete(loc_list):
  66. new_list = [[0] * 2]
  67. for i in range(len(loc_list)):
  68. if loc_list[i][0] not in np.transpose(new_list)[0]:
  69. new_list.append(loc_list[i])
  70. return new_list
  71. def gradient_time_convertation(param_loc, time_sample):
  72. g_raster_time = param_loc['grad_raster_time']
  73. time_sample /= g_raster_time
  74. return time_sample
  75. def gradient_ampl_convertation(param, gradient_herz):
  76. """
  77. Helper function that convert amplitudes to dimensionless format for machine
  78. 1 bit for sign, 15 bits of numbers
  79. :param gradient_herz: 2D array of amplitude and time points in Hz/m
  80. :return: gradient_dimless: 2D array of dimensionless points
  81. """
  82. # amplitude raster is 32768
  83. # maximum grad = 10 mT/m
  84. # artificial gap is 1 mT/m so 9 mT/m is now should be split in parts
  85. amplitude_max = param['G_amp_max']
  86. amplitude_raster = 32767
  87. step_Hz_m = amplitude_max / amplitude_raster # Hz/m step gradient
  88. gradient_dimless = gradient_herz / step_Hz_m * 1000
  89. # assert abs(any(gradient_dimless)) > 32768, 'Amplitude is higher than expected, check the rate number'
  90. return gradient_dimless
  91. def adc_correction(blocks_number_loc, seq_input_loc):
  92. """
  93. Helper function that rise times for correction of ADC events
  94. Вспомогательная функция получения времён для коррекции АЦП событий
  95. :return: rise_time: float, stores in pulseq, related to exact type of gradient events
  96. хранится в pulseq, связан с конкретным типом градиентного события
  97. fall_time: float, same as rise_time
  98. аналогично rise_time
  99. """
  100. rise_time, fall_time = None, None
  101. is_adc_inside = False
  102. for j in range(blocks_number_loc - 1):
  103. iterable_block = seq_input_loc.get_block(block_index=j + 1)
  104. if iterable_block.adc is not None:
  105. is_adc_inside = True
  106. rise_time = iterable_block.gx.rise_time
  107. fall_time = iterable_block.gx.fall_time
  108. if not is_adc_inside:
  109. raise Exception("No ADC event found inside sequence")
  110. return rise_time, fall_time
  111. def adc_event_edges(local_gate_adc):
  112. """
  113. Helper function that rise numbers of blocks of border correction of ADC events
  114. Вспомогательная функция для получения номеров блоков границ коррекции АЦП событий
  115. :return: num_begin_l: int, number of time block when adc event starts
  116. номер временного блока начала АЦП события
  117. num_finish_l: int, same but ends
  118. то же, но для окончания
  119. """
  120. num_begin_l = 0
  121. flag_begin = False
  122. flag_finish = False
  123. num_finish_l = 1
  124. for k in range(len(local_gate_adc) - 1):
  125. if local_gate_adc[k] != 0 and not flag_begin:
  126. num_begin_l = k
  127. flag_begin = True
  128. if local_gate_adc[k] != 0 and local_gate_adc[k + 1] == 0 and not flag_finish:
  129. num_finish_l = k
  130. flag_finish = True
  131. return num_begin_l, num_finish_l
  132. def synchronization(sync_sequence, synchro_block_timer=20e-9, path='test1/', TR_DELAY_L=800e-9, RF_DELAY_L=800e-9,
  133. START_DELAY_L=800e-9):
  134. ### MAIN LOOP ###
  135. ### ОСНОВНОЙ ЦИКЛ###
  136. MIN_BLOCK_TIME = 400e-9
  137. assert START_DELAY_L >= RF_DELAY_L
  138. assert TR_DELAY_L >= synchro_block_timer
  139. assert RF_DELAY_L >= synchro_block_timer
  140. number_of_blocks = len(sync_sequence.block_events)
  141. gate_adc = [0]
  142. gate_rf = [0] * CONST_HACK_RF_DELAY
  143. gate_tr_switch = [1]
  144. blocks_duration = [START_DELAY_L]
  145. adc_times_values = []
  146. adc_times_starts = []
  147. '''
  148. ID RF GX GY GZ ADC EXT
  149. 0 1 2 3 4 5 6
  150. '''
  151. added_blocks = 0
  152. for block_counter in range(number_of_blocks):
  153. is_not_adc_block = True
  154. if sync_sequence.block_events[block_counter + 1][5]:
  155. is_not_adc_block = False
  156. gate_adc.append(0)
  157. gate_rf.append(gate_rf[-1])
  158. blocks_duration[-1] -= TR_DELAY_L
  159. blocks_duration.append(TR_DELAY_L)
  160. gate_tr_switch.append(0)
  161. added_blocks += 1
  162. gate_adc.append(1)
  163. gate_tr_switch.append(0)
  164. else:
  165. gate_tr_switch.append(1)
  166. gate_adc.append(0)
  167. if sync_sequence.block_events[block_counter + 1][1] and is_not_adc_block:
  168. gate_rf.append(1)
  169. gate_adc.append(gate_adc[-1])
  170. blocks_duration[-1] -= RF_DELAY_L
  171. blocks_duration.append(RF_DELAY_L)
  172. gate_tr_switch.append(gate_tr_switch[-1])
  173. added_blocks += 1
  174. gate_rf.append(1)
  175. else:
  176. gate_rf.append(0)
  177. current_block_dur = sync_sequence.block_durations[block_counter + 1]
  178. blocks_duration.append(current_block_dur)
  179. number_of_blocks += added_blocks
  180. # gate_gx = [1] * number_of_blocks
  181. # gate_gy = [1] * number_of_blocks
  182. # gate_gz = [1] * number_of_blocks
  183. '''
  184. test1 swap
  185. '''
  186. # assert any(block_times) < MIN_BLOCK_TIME, "ERROR: events in the current sequence are less than 400 ns"
  187. doc, tag, text = Doc().tagtext()
  188. with tag('root'):
  189. with tag('ParamCount'):
  190. text(number_of_blocks)
  191. with tag('RF'):
  192. for RF_iter in range(number_of_blocks):
  193. with tag('RF' + str(RF_iter + 1)):
  194. text(gate_rf[RF_iter])
  195. with tag('SW'):
  196. for SW_iter in range(number_of_blocks):
  197. with tag('SW' + str(SW_iter + 1)):
  198. text(gate_tr_switch[SW_iter])
  199. with tag('ADC'):
  200. for ADC_iter in range(number_of_blocks):
  201. if gate_adc[ADC_iter] == 1:
  202. adc_times_values.append(blocks_duration[ADC_iter])
  203. adc_times_starts.append(sum(blocks_duration[0:ADC_iter]))
  204. with tag('ADC' + str(ADC_iter + 1)):
  205. text(gate_adc[ADC_iter])
  206. with tag('GR'):
  207. with tag('GR1'):
  208. text(1)
  209. for GX_iter in range(1, number_of_blocks):
  210. with tag('GR'+ str(GX_iter + 1)):
  211. text(0)
  212. with tag('CL'):
  213. with tag('CL' + str(1)):
  214. text(int(MIN_BLOCK_TIME / synchro_block_timer))
  215. for CL_iter in range(1, number_of_blocks):
  216. with tag('CL' + str(CL_iter + 1)):
  217. text(int(blocks_duration[CL_iter] / synchro_block_timer))
  218. result = indent(
  219. doc.getvalue(),
  220. indentation=' ' * 4,
  221. newline='\r'
  222. )
  223. sync_file = open(path + "sync_v2.xml", "w")
  224. sync_file.write(result)
  225. sync_file.close()
  226. picoscope_set(adc_times_values, adc_times_starts)
  227. def picoscope_set(adc_val, adc_start, number_of_channels_l=8, sampling_freq_l=4e7, path='test1/'):
  228. # sampling rate = 40 MHz = 4e7 1/s
  229. # adc_val in seconds
  230. adc_out_timings = []
  231. for i in adc_val:
  232. adc_out_timings.append(int(i * sampling_freq_l))
  233. doc, tag, text, line = Doc().ttl()
  234. with tag('root'):
  235. with tag('points'):
  236. with tag('title'):
  237. text("Points")
  238. with tag('value'):
  239. text(str(adc_out_timings))
  240. with tag('num_of_channels'):
  241. with tag('title'):
  242. text("Number of Channels")
  243. with tag('value'):
  244. text(number_of_channels_l)
  245. with tag('times'):
  246. with tag('title'):
  247. text("Times")
  248. with tag('value'):
  249. text(str(adc_start).format('%.e'))
  250. with tag('sample_freq'):
  251. with tag('title'):
  252. text("Sample Frequency")
  253. with tag('value'):
  254. text(sampling_freq_l)
  255. result = indent(
  256. doc.getvalue(),
  257. indentation=' ' * 4,
  258. newline='\r'
  259. )
  260. sync_file = open(path + "picoscope_params.xml", "w")
  261. sync_file.write(result)
  262. sync_file.close()
  263. if __name__ == "__main__":
  264. CONST_HACK_RF_DELAY = 17 * 2 * 2
  265. SEQ_INPUT, SEQ_DICT = seq_file_input(seq_file_name='sequences/turbo_FLASH_060924_0444.seq')
  266. # SEQ_INPUT, SEQ_DICT = seq_file_input(seq_file_name='sequences/test1_full.seq')
  267. params_path = 'sequences/'
  268. params_filename = "turbo_FLASH_060924_0444"
  269. # params_filename = "test1_full"
  270. file = open(params_path + params_filename + ".json", 'r')
  271. SEQ_PARAM = json.load(file)
  272. file.close()
  273. '''
  274. integartion of srv_seq_gen
  275. '''
  276. # SEQ_PARAM = set_limits()
  277. # SEQ_INPUT = save_param()
  278. # SEQ_DICT = SEQ_INPUT.waveforms_export()
  279. '''
  280. simulation of inputing the JSON and SEQ
  281. '''
  282. # artificial delays due to construction of the MRI
  283. # искусственные задержки из-за тех. особенностей МРТ
  284. # RF_dtime = 10 * 1e-6
  285. # TR_dtime = 10 * 1e-6
  286. time_info = SEQ_INPUT.duration()
  287. blocks_number = time_info[1]
  288. time_dur = time_info[0]
  289. # output interpretation. all formats of files defined in method
  290. # интерпретация выхода. Все форматы файлов определены в методе
  291. output_seq(SEQ_DICT, SEQ_PARAM)
  292. # defining constants of the sequence
  293. # определение констант последовательности
  294. local_definitions = SEQ_INPUT.definitions
  295. ADC_raster = local_definitions['AdcRasterTime']
  296. RF_raster = local_definitions['RadiofrequencyRasterTime']
  297. synchronization(SEQ_INPUT)