imr-framework / pypulseq

Pulseq in Python
https://pypulseq.readthedocs.io
GNU Affero General Public License v3.0
117 stars 63 forks source link

Questions about ADC dead time check #127

Closed zhengliuer closed 1 year ago

zhengliuer commented 1 year ago

Hi, I'm now using pypulseq to write some sequences. I had a small problem with adc time checking: https://github.com/imr-framework/pypulseq/blob/355952819df01c5bcbd484b85d1839547d4d08cb/pypulseq/Sequence/sequence.py#L618-L633 For an ADC, check_timing checks

  1. if adc.delay is smaller than adc.dead_time. As described in the Pulseq specification, the delay is the time between the start time of this block and the start time of this ADC, and dead time is the minimal time interval between two ADC. Why ADC.delay has to be smaller than the dead time?
  2. the second if checks if adc.delay + adc.num_samples * adc.dwell + system.adc_dead_time is bigger than the duration, I'm not sure why we need adc.dead_time here?

I would appreciate it if anyone could help with these two questions.

btasdelen commented 1 year ago

Hi Zheng,

  1. If you check the make_adc function: https://github.com/imr-framework/pypulseq/blob/355952819df01c5bcbd484b85d1839547d4d08cb/pypulseq/make_adc.py#L63-L64

You will see that if there is not enough delay set by the programmer, it forces having at least adc_dead_time amount of delay.

The first check just makes sure that this is not violated by the programmer manually after the ADC creation.

  1. I asked a similar question here: https://github.com/pulseq/pulseq/issues/22 Hope this answers your question.
zhengliuer commented 1 year ago

Sorry for the delay. Thanks a lot for the reply.

  1. I'm not sure that I understand why adc.delay should be at least adc_dead_time. What if I just have one block which only has one ADC, like

    adc = pp.make_adc(num_samples=1000, delay=0, duration=1e-3)
    seq.add_block(adc)
  2. As Maxim said, in pulseq, ADC objects are not allowed to touch the edges of the real time event blocks(according to Siemens scanners). However, if I just add a block which only has one delay object, setting adc.delay as adc.dead_time seems a little weird.

I suppose if we want to make sure that the ADC objects won't touch the edges of the real time event, we need to store the former real-time block's last real-time objects, make sure the time interval is appropriate.

Also, I notice in pulseq, there is a system parameter called rf_ringdown_time, as I asked before here, it should the time interval between ADC and RF? A little confused here.

btasdelen commented 1 year ago
  1. I am not sure about the exact reason why we need adc_dead_time, but it is typically set to a very short time, like 10us (or 1 Gradient Raster Time). If you have a block with only a single ADC and don't want the 10 us shift, you can remove that amount of delay from the previous block as a workaround. You can also set it to 0 and see what happens, I will be curious to learn that.

  2. rf_ringdown_time specifies the time it takes the RF to settle down. Yes, it also implies ADC should be at least that much further away from the last RF. It also implies a subsequent RF should be at least rf_ringdown_time apart from the previous RF. But if you look at the code, you will see that unlike the adc_dead_time, it is not enforced by setting a delay, and it is typically larger than adc_dead_time. They serve very different purposes.

Edit: For the sake of clarification, in summary, adc_dead_time has nothing to do with RF. rf_dead_time dictates that relationship. adc_dead_time only makes sure the ADC is not touching edges of the event blocks, it does not imply anything about other event blocks.

zhengliuer commented 1 year ago

If I create a sequence like this:

    system == pp.Opts(...)
    seq = pp.Sequence(system=system)

    n_slice = 1
    rf_duration = 300e-5
    slice_thickness = 3e-3
    fa = 30
    tr = 100e-3
    te = 20e-3
    nx = 256
    ny = 100
    pe_fov = ro_fov = 300e-3

    adc = pp.make_adc(num_samples=1000, delay=0, duration=1e-3)
    seq.add_block(adc)

        ok, error_report = seq.check_timing()
    if ok:
        print('Timing check passed successfully')
    else:
        print('Timing check failed. Error listing follows:')
        [print(e) for e in error_report]

The report says Event: 0 - adc.delay < system.adc_dead_timeadc: system.adc_dead_time (post-ADC) violation as expected for the adc.delay is 0.

So in summary, in pulseq,adc_dead_time is used to make sure that the ADC objects won't touch the edge of the following block(including RF event in the same block), not what I thought before: interval between two ADC objects.

btasdelen commented 1 year ago

Timing check violations does not prohibit .seq creation, so you can go ahead and try it on the scanner. If it fails, it means it is not a Pulseq limitation, but a Siemens limitation.

zhengliuer commented 1 year ago

Thanks a lot!