
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "generated/gallery/02-tours/04_multi_echo.py"
.. LINE NUMBERS ARE GIVEN BELOW.

.. only:: html

    .. note::
        :class: sphx-glr-download-link-note

        :ref:`Go to the end <sphx_glr_download_generated_gallery_02-tours_04_multi_echo.py>`
        to download the full example code.

.. rst-class:: sphx-glr-example-title

.. _sphx_glr_generated_gallery_02-tours_04_multi_echo.py:


=========================
Multi-echo gradient echo
=========================

A multi-echo gradient echo follows the first readout gradient with further
readout gradients of alternating polarity, each with its own ADC event. Each
gradient traverses the same k-space line, in the opposite direction to the
one before it, so the train forms one echo per gradient and needs no
rewinder between them. The echoes sample the line at increasing echo times,
which is what lets you map :math:`T_2^*` from a single repetition.

In this Tour you build the repetition and then ask how many echoes fit in it.
The echo spacing is the duration of one readout gradient, and it depends on
the receiver bandwidth through the ramps. You sweep the bandwidth, read the
echo spacing and the number of echoes that fit in the repetition time, and
see where the gradient amplitude limit stops the sweep.

**Prerequisites:** lessons 1 and 2 of the :doc:`course </examples/course>`.

.. GENERATED FROM PYTHON SOURCE LINES 21-31








.. GENERATED FROM PYTHON SOURCE LINES 32-41

Readout train
-------------

A readout gradient traverses one k-space line from one end to the other. A
second gradient of the opposite polarity traverses it back, so a train of
them needs no rewinder between the echoes and forms one echo per gradient.
The even echoes are therefore acquired in reverse order along the line.
The readout is designed as in lesson 1, with the amplitude set so that one
sample advances k-space by :math:`1/\mathrm{FOV}`.

.. GENERATED FROM PYTHON SOURCE LINES 41-119

.. code-block:: Python

    import numpy as np

    import pypulseqpp as pp

    system = pp.Opts(
        max_grad=32.0,
        grad_unit="mT/m",
        max_slew=130.0,
        slew_unit="T/m/s",
        rf_dead_time=100e-6,
        rf_ringdown_time=20e-6,
        adc_dead_time=10e-6,
    )

    FOV = 220e-3
    MATRIX = 128
    THICKNESS = 5e-3
    FLIP_ANGLE_DEG = 20.0
    REPETITION_TIME = 40e-3
    BANDWIDTH_HZ = 250e3
    ECHOES = 6

    rf, gz, gz_reph = pp.make_sinc_pulse(
        flip_angle=np.deg2rad(FLIP_ANGLE_DEG),
        duration=2e-3,
        slice_thickness=THICKNESS,
        apodization=0.5,
        time_bw_product=4.0,
        delay=system.rf_dead_time,
        system=system,
        use="excitation",
        return_gz=True,
    )


    def readout(bandwidth_hz):
        """The readout gradient, its acquisition window and its prewinder.

        The dwell time is put on the ADC raster and the flat top on the gradient
        raster, which are different rasters, so the flat top is the acquisition
        window rounded up rather than equal to it. The amplitude is set so that a
        sample advances k-space by ``1 / FOV`` however long the window is.
        """
        dwell = pp.round_to_raster(1.0 / bandwidth_hz, system.adc_raster_time)
        acquisition = MATRIX * dwell
        raster = system.grad_raster_time
        gx = pp.make_trapezoid(
            channel="x",
            amplitude=MATRIX / FOV / acquisition,
            flat_time=raster * np.ceil(acquisition / raster),
            system=system,
        )
        adc = pp.make_adc(
            num_samples=MATRIX, dwell=dwell, delay=gx.rise_time, system=system
        )
        # The prewinder cancels the ramp, one step per sample before the echo and
        # the half step to the centre of the first sample.
        gx_pre = pp.make_trapezoid(
            channel="x",
            area=-(gx.amplitude * gx.rise_time / 2 + (MATRIX / 2 + 0.5) / FOV),
            duration=1e-3,
            system=system,
        )
        return gx, adc, gx_pre


    gx, adc, gx_pre = readout(BANDWIDTH_HZ)
    gy_pre = pp.make_trapezoid(
        channel="y", area=MATRIX / (2 * FOV), duration=1e-3, system=system
    )

    print(
        f"dwell {1e6 * adc.dwell:.1f} us, "
        f"readout {1e3 * pp.calc_duration(gx):.3f} ms, "
        f"amplitude {1e3 * gx.amplitude / 42.576e6:.2f} mT/m, "
        f"slew {gx.amplitude / gx.rise_time / 42.576e6:.0f} T/m/s"
    )





.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    dwell 4.0 us, readout 0.960 ms, amplitude 26.69 mT/m, slew 121 T/m/s




.. GENERATED FROM PYTHON SOURCE LINES 120-126

Repetition
----------

The train replaces the single readout block. The gradients of the train are
played back to back, so the echo spacing is the duration of one readout
gradient, ramps included.

.. GENERATED FROM PYTHON SOURCE LINES 126-165

.. code-block:: Python



    def multi_echo(echoes, bandwidth_hz=BANDWIDTH_HZ, lines=MATRIX):
        """A multi-echo gradient echo with the given train length."""
        gx, adc, gx_pre = readout(bandwidth_hz)
        spoiler = pp.make_crusher(4.0, FOV / MATRIX, channel="z", system=system)[0]
        played = (
            pp.calc_duration(rf, gz)
            + pp.calc_duration(gx_pre, gy_pre, gz_reph)
            + echoes * pp.calc_duration(gx)
            + pp.calc_duration(spoiler)
        )
        seq = pp.Sequence(system=system)
        for step in np.linspace(-1.0, 1.0, lines, endpoint=False):
            seq.add_block(rf, gz)
            seq.add_block(gx_pre, pp.scale_grad(gy_pre, step), gz_reph)
            for echo in range(echoes):
                seq.add_block(pp.scale_grad(gx, (-1.0) ** echo), adc)
            seq.add_block(spoiler)
            seq.add_block(
                pp.make_delay(
                    pp.round_to_raster(
                        REPETITION_TIME - played, system.block_duration_raster
                    )
                )
            )
        return seq


    seq = multi_echo(ECHOES)

    ok, errors = seq.check_timing()
    print(
        f"timing {ok}, {seq.num_blocks} blocks, "
        f"{seq.duration()[0]:.3f} s for {ECHOES} echoes on each of {MATRIX} lines"
    )

    seq.paper_plot(tr=1)




.. image-sg:: /generated/gallery/02-tours/images/sphx_glr_04_multi_echo_001.png
   :alt: 04 multi echo
   :srcset: /generated/gallery/02-tours/images/sphx_glr_04_multi_echo_001.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none

    timing True, 1280 blocks, 5.120 s for 6 echoes on each of 128 lines




.. GENERATED FROM PYTHON SOURCE LINES 166-174

Echo spacing against receiver bandwidth
---------------------------------------

The readout gradient has to cover the same area whatever the bandwidth, so a
shorter flat top is a proportionally stronger gradient, until the amplitude
limit is reached and the flat top can shorten no further. The echo spacing
follows the flat top, and the train that fits in a repetition follows the
echo spacing.

.. GENERATED FROM PYTHON SOURCE LINES 174-255

.. code-block:: Python


    # The dwell time is the quantity the ADC raster quantizes, so the sweep is over
    # dwell times and the bandwidth is read from them.
    DWELLS = np.array([16e-6, 12e-6, 10e-6, 8e-6, 6e-6, 4e-6, 2e-6])
    BANDWIDTHS = 1.0 / DWELLS

    overhead = (
        pp.calc_duration(rf, gz)
        + pp.calc_duration(gx_pre, gy_pre, gz_reph)
        + pp.calc_duration(
            pp.make_crusher(4.0, FOV / MATRIX, channel="z", system=system)[0]
        )
    )

    spacing = []
    for bandwidth in BANDWIDTHS:
        try:
            gradient = readout(bandwidth)[0]
        except ValueError:
            spacing.append({"bandwidth": bandwidth, "realizable": False})
            continue
        spacing.append(
            {
                "bandwidth": bandwidth,
                "realizable": True,
                "spacing": pp.calc_duration(gradient),
                "ramps": gradient.rise_time + gradient.fall_time,
                "amplitude": gradient.amplitude / 42.576e6,
                "echoes": int((REPETITION_TIME - overhead) // pp.calc_duration(gradient)),
            }
        )

    realizable = [row for row in spacing if row["realizable"]]





.. image-sg:: /generated/gallery/02-tours/images/sphx_glr_04_multi_echo_002.png
   :alt: 04 multi echo
   :srcset: /generated/gallery/02-tours/images/sphx_glr_04_multi_echo_002.png
   :class: sphx-glr-single-img


.. rst-class:: sphx-glr-script-out

 .. code-block:: none


       bandwidth     amplitude     spacing    ramps   echoes
           62 kHz       6.67 mT/m    2.180 ms       6%       15
           83 kHz       8.90 mT/m    1.700 ms       9%       20
          100 kHz      10.68 mT/m    1.480 ms      14%       23
          125 kHz      13.35 mT/m    1.280 ms      19%       27
          167 kHz      17.79 mT/m    1.060 ms      26%       32
          250 kHz      26.69 mT/m    0.960 ms      46%       36
          500 kHz                beyond the amplitude limit




.. GENERATED FROM PYTHON SOURCE LINES 256-286

The acquisition window falls as the reciprocal of the bandwidth; the echo
spacing does not. A shorter window at the same k-space extent is a stronger
gradient, and a stronger gradient takes longer to ramp, at both ends of every
echo. Over the sweep the window shortens fourfold and the spacing by a factor
of little more than two, with the ramps growing from a sixteenth of the echo
spacing to nearly half of it. Beyond the last point the amplitude the readout
would need is above the limit, and the factory raises an error rather than
lengthening the flat top.

The shorter spacing reduces the signal-to-noise ratio. The noise in a sample
grows as the square root of the bandwidth, so the fourfold bandwidth of the
sweep halves the signal-to-noise ratio of each echo, in exchange for the
number of echoes and a shorter shortest echo time.

Specification
-------------

What this Tour built, stated the way you would ask an agent for it:

.. code-block:: text

   Write a 2D multi-echo gradient echo with pypulseqpp: 220 mm FOV,
   128 matrix, 5 mm slice, 20 degree sinc excitation, TR 40 ms. After
   the excitation and the readout and phase prewinders, play a train of
   6 readout gradients of alternating polarity at 250 kHz receiver
   bandwidth, each with its own ADC event and no rewinder between them,
   then a z spoiler. Check the timing. Sweep the dwell time from 16 us
   to 2 us and report the echo spacing, the ramp fraction and the
   number of echoes that fit in TR, stopping where the readout
   amplitude exceeds the system limit.


.. rst-class:: sphx-glr-timing

   **Total running time of the script:** (0 minutes 0.226 seconds)


.. _sphx_glr_download_generated_gallery_02-tours_04_multi_echo.py:

.. only:: html

  .. container:: sphx-glr-footer sphx-glr-footer-example

    .. container:: sphx-glr-download sphx-glr-download-jupyter

      :download:`Download Jupyter notebook: 04_multi_echo.ipynb <04_multi_echo.ipynb>`

    .. container:: sphx-glr-download sphx-glr-download-python

      :download:`Download Python source code: 04_multi_echo.py <04_multi_echo.py>`

    .. container:: sphx-glr-download sphx-glr-download-zip

      :download:`Download zipped: 04_multi_echo.zip <04_multi_echo.zip>`


.. only:: html

 .. rst-class:: sphx-glr-signature

    `Gallery generated by Sphinx-Gallery <https://sphinx-gallery.github.io>`_
