44
55import numpy as np
66
7+ from pypulseq import eps
78from pypulseq .block_to_events import block_to_events
89from pypulseq .compress_shape import compress_shape
910from pypulseq .decompress_shape import decompress_shape
1011from pypulseq .event_lib import EventLibrary
1112from pypulseq .supported_labels_rf_use import get_supported_labels
13+ from pypulseq .utils .tracing import trace_enabled
1214
1315
1416def set_block (self , block_index : int , * args : SimpleNamespace ) -> None :
@@ -39,34 +41,42 @@ def set_block(self, block_index: int, *args: SimpleNamespace) -> None:
3941 If a gradient that doesn't end at zero is not aligned to the block boundary.
4042 """
4143 events = block_to_events (* args )
42- self . block_events [ block_index ] = np .zeros (7 , dtype = np .int32 )
44+ new_block = np .zeros (7 , dtype = np .int32 )
4345 duration = 0
4446
45- check_g = {} # Key-value mapping of index and pairs of gradients/times
47+ check_g = {
48+ 0 : SimpleNamespace (idx = 2 , start = (0 ,0 ), stop = (0 ,0 )),
49+ 1 : SimpleNamespace (idx = 3 , start = (0 ,0 ), stop = (0 ,0 )),
50+ 2 : SimpleNamespace (idx = 4 , start = (0 ,0 ), stop = (0 ,0 ))
51+ } # Key-value mapping of index and pairs of gradients/times
4652 extensions = []
4753
4854 for event in events :
4955 if not isinstance (event , float ): # If event is not a block duration
5056 if event .type == "rf" :
51- if self . block_events [ block_index ] [1 ] != 0 :
57+ if new_block [1 ] != 0 :
5258 raise ValueError ('Multiple RF events were specified in set_block' )
53-
59+
5460 if hasattr (event , "id" ):
5561 rf_id = event .id
5662 else :
5763 rf_id , _ = register_rf_event (self , event )
5864
59- self . block_events [ block_index ] [1 ] = rf_id
65+ new_block [1 ] = rf_id
6066 duration = max (
6167 duration , event .shape_dur + event .delay + event .ringdown_time
6268 )
69+
70+ if trace_enabled ():
71+ if hasattr (event , 'trace' ):
72+ setattr (self .block_trace [block_index ], 'rf' , event .trace )
6373 elif event .type == "grad" :
6474 channel_num = ["x" , "y" , "z" ].index (event .channel )
6575 idx = 2 + channel_num
6676
67- if self . block_events [ block_index ] [idx ] != 0 :
77+ if new_block [idx ] != 0 :
6878 raise ValueError (f'Multiple { event .channel .upper ()} gradient events were specified in set_block' )
69-
79+
7080 grad_start = (
7181 event .delay
7282 + math .floor (event .tt [0 ] / self .grad_raster_time + 1e-10 )
@@ -88,42 +98,54 @@ def set_block(self, block_index: int, *args: SimpleNamespace) -> None:
8898 else :
8999 grad_id , _ = register_grad_event (self , event )
90100
91- self . block_events [ block_index ] [idx ] = grad_id
101+ new_block [idx ] = grad_id
92102 duration = max (duration , grad_duration )
103+
104+ if trace_enabled ():
105+ if hasattr (event , 'trace' ):
106+ setattr (self .block_trace [block_index ], 'g' + event .channel , event .trace )
93107 elif event .type == "trap" :
94108 channel_num = ["x" , "y" , "z" ].index (event .channel )
95109 idx = 2 + channel_num
96110
97- if self . block_events [ block_index ] [idx ] != 0 :
111+ if new_block [idx ] != 0 :
98112 raise ValueError (f'Multiple { event .channel .upper ()} gradient events were specified in set_block' )
99113
100114 if hasattr (event , "id" ):
101115 trap_id = event .id
102116 else :
103117 trap_id = register_grad_event (self , event )
104118
105- self . block_events [ block_index ] [idx ] = trap_id
119+ new_block [idx ] = trap_id
106120 duration = max (
107121 duration ,
108122 event .delay
109123 + event .rise_time
110124 + event .flat_time
111125 + event .fall_time
112126 )
127+
128+ if trace_enabled ():
129+ if hasattr (event , 'trace' ):
130+ setattr (self .block_trace [block_index ], 'g' + event .channel , event .trace )
113131 elif event .type == "adc" :
114- if self . block_events [ block_index ] [5 ] != 0 :
132+ if new_block [5 ] != 0 :
115133 raise ValueError ('Multiple ADC events were specified in set_block' )
116-
134+
117135 if hasattr (event , "id" ):
118136 adc_id = event .id
119137 else :
120138 adc_id = register_adc_event (self , event )
121139
122- self . block_events [ block_index ] [5 ] = adc_id
140+ new_block [5 ] = adc_id
123141 duration = max (
124142 duration ,
125143 event .delay + event .num_samples * event .dwell + event .dead_time
126144 )
145+
146+ if trace_enabled ():
147+ if hasattr (event , 'trace' ):
148+ setattr (self .block_trace [block_index ], 'adc' , event .trace )
127149 elif event .type == "delay" :
128150 duration = max (duration , event .delay )
129151 elif event .type in ["output" , "trigger" ]:
@@ -181,49 +203,86 @@ def set_block(self, block_index: int, *args: SimpleNamespace) -> None:
181203 self .extensions_library .insert (extension_id , data )
182204
183205 # Now we add the ID
184- self . block_events [ block_index ] [6 ] = extension_id
206+ new_block [6 ] = extension_id
185207
186208 # =========
187209 # PERFORM GRADIENT CHECKS
188210 # =========
189211 for grad_to_check in check_g .values ():
190212
191- if (
192- abs (grad_to_check .start [1 ])
193- > self .system .max_slew * self .system .grad_raster_time
194- ):
195- if grad_to_check .start [0 ] != 0 :
196- raise ValueError (
213+ if (abs (grad_to_check .start [1 ]) > self .system .max_slew * self .system .grad_raster_time ):
214+ if grad_to_check .start [0 ] > eps :
215+ raise RuntimeError (
197216 "No delay allowed for gradients which start with a non-zero amplitude"
198217 )
199218
200- if block_index > 1 :
201- # TODO: block_index - 1 assumes linear ordering of blocks.
202- # However, searching for the previous block index in block_events is a big performance hit
203- # For newly inserted blocks (block_index not in self.block_events), we can just use the last block as previous
204- prev_id = self .block_events [block_index - 1 ][grad_to_check .idx ]
219+ # Check whether any blocks exist in the sequence
220+ if self .next_free_block_ID > 1 :
221+ # Look up the previous block (and the next block in case of a set_block call)
222+ if block_index == self .next_free_block_ID :
223+ # New block inserted
224+ prev_block_index = next (reversed (self .block_events ))
225+ next_block_index = None
226+ else :
227+ blocks = list (self .block_events )
228+ try :
229+ # Existing block overwritten
230+ idx = blocks .index (block_index )
231+ prev_block_index = blocks [idx - 1 ] if idx > 0 else None
232+ next_block_index = blocks [idx + 1 ] if idx < len (blocks )- 1 else None
233+ except ValueError :
234+ # Inserting a new block with non-contiguous numbering
235+ prev_block_index = next (reversed (self .block_events ))
236+ next_block_index = None
237+
238+ # Look up the last gradient value in the previous block
239+ last = 0
240+ if prev_block_index is not None :
241+ prev_id = self .block_events [prev_block_index ][grad_to_check .idx ]
205242 if prev_id != 0 :
206243 prev_lib = self .grad_library .get (prev_id )
207- prev_data = prev_lib ["data" ]
208- prev_type = prev_lib ["type" ]
209- if prev_type == "t" :
210- raise RuntimeError (
211- "Two consecutive gradients need to have the same amplitude at the connection point"
212- )
213- elif prev_type == "g" :
214- last = prev_data [5 ]
215- if (
216- abs (last - grad_to_check .start [1 ])
217- > self .system .max_slew * self .system .grad_raster_time
218- ):
219- raise RuntimeError (
220- "Two consecutive gradients need to have the same amplitude at the connection point"
221- )
222- else :
244+ prev_type = prev_lib ['type' ]
245+
246+ if prev_type == 't' :
247+ last = 0
248+ elif prev_type == 'g' :
249+ last = prev_lib ['data' ][5 ]
250+
251+ # Check whether the difference between the last gradient value and
252+ # the first value of the new gradient is achievable with the
253+ # specified slew rate.
254+ if (abs (last - grad_to_check .start [1 ]) > self .system .max_slew * self .system .grad_raster_time ):
223255 raise RuntimeError (
224- "First gradient in the the first block has to start at 0. "
256+ "Two consecutive gradients need to have the same amplitude at the connection point "
225257 )
226258
259+ # Look up the first gradient value in the next block
260+ # (this only happens when using set_block to patch a block)
261+ if next_block_index is not None :
262+ next_id = self .block_events [next_block_index ][grad_to_check .idx ]
263+ if next_id != 0 :
264+ next_lib = self .grad_library .get (next_id )
265+ next_type = next_lib ['type' ]
266+
267+ if next_type == 't' :
268+ first = 0
269+ elif next_type == 'g' :
270+ first = next_lib ['data' ][4 ]
271+ else :
272+ first = 0
273+
274+ # Check whether the difference between the first gradient value
275+ # in the next block and the last value of the new gradient is
276+ # achievable with the specified slew rate.
277+ if (abs (first - grad_to_check .stop [1 ]) > self .system .max_slew * self .system .grad_raster_time ):
278+ raise RuntimeError (
279+ "Two consecutive gradients need to have the same amplitude at the connection point"
280+ )
281+ elif abs (grad_to_check .start [1 ]) > self .system .max_slew * self .system .grad_raster_time :
282+ raise RuntimeError (
283+ "First gradient in the the first block has to start at 0."
284+ )
285+
227286 if (
228287 grad_to_check .stop [1 ] > self .system .max_slew * self .system .grad_raster_time
229288 and abs (grad_to_check .stop [0 ] - duration ) > 1e-7
@@ -232,6 +291,7 @@ def set_block(self, block_index: int, *args: SimpleNamespace) -> None:
232291 "A gradient that doesn't end at zero needs to be aligned to the block boundary."
233292 )
234293
294+ self .block_events [block_index ] = new_block
235295 self .block_durations [block_index ] = float (duration )
236296
237297
@@ -410,7 +470,7 @@ def get_block(self, block_index: int) -> SimpleNamespace:
410470 # Enter block into the block cache
411471 if self .use_block_cache :
412472 self .block_cache [block_index ] = block
413-
473+
414474 return block
415475
416476
@@ -430,7 +490,7 @@ def register_adc_event(self, event: EventLibrary) -> int:
430490 data = (
431491 event .num_samples ,
432492 event .dwell ,
433- max ( event .delay , event . dead_time ) ,
493+ event .delay ,
434494 event .freq_offset ,
435495 event .phase_offset ,
436496 event .dead_time ,
@@ -548,12 +608,12 @@ def register_grad_event(
548608 any_changed = any_changed or found
549609 else :
550610 grad_id = self .grad_library .insert (0 , data , event .type [0 ])
551-
611+
552612 # Clear block cache because grad event or shapes were overwritten
553613 # TODO: Could find only the blocks that are affected by the changes
554614 if self .use_block_cache and any_changed :
555615 self .block_cache .clear ()
556-
616+
557617 if event .type == "grad" :
558618 return grad_id , shape_IDs
559619 elif event .type == "trap" :
@@ -658,14 +718,14 @@ def register_rf_event(self, event: SimpleNamespace) -> Tuple[int, List[int]]:
658718
659719 if may_exist :
660720 rf_id , found = self .rf_library .find_or_insert (new_data = data , data_type = use )
661-
721+
662722 # Clear block cache because RF event was overwritten
663723 # TODO: Could find only the blocks that are affected by the changes
664724 if self .use_block_cache and found :
665725 self .block_cache .clear ()
666726 else :
667727 rf_id = self .rf_library .insert (key_id = 0 , new_data = data , data_type = use )
668728
669-
729+
670730
671731 return rf_id , shape_IDs
0 commit comments