
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "generated/gallery/01-course/01_first_gradient_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_01-course_01_first_gradient_echo.py>`
        to download the full example code.

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

.. _sphx_glr_generated_gallery_01-course_01_first_gradient_echo.py:


===========================
1. Your first gradient echo
===========================

This course takes you from a 2D Cartesian gradient echo to a 3D radial
MPRAGE, one step per lesson. This lesson starts with the gradient echo.

It covers the Pulseq basics: you describe your scanner, design the
events of one repetition (the excitation, the readout, the phase encoding),
play them in blocks, one k-space line per repetition, and write the file the
scanner plays. If you have written PyPulseq before, every line will look
familiar: pypulseqpp keeps PyPulseq's names, arguments and units.

**Learning objectives**

- Describe your scanner with a ``system`` object, and know which raster each
  event is aligned to.
- Design every event once, before the loop, from the field of view and the
  matrix.
- Play one repetition per k-space line in blocks, and check the timing.
- Read the sequence diagram and the k-space trajectory, and write the
  ``.seq`` file.

Next: :doc:`02_echo_and_repetition_time`, where you place the echo at the
echo time you ask for.

.. GENERATED FROM PYTHON SOURCE LINES 28-38








.. GENERATED FROM PYTHON SOURCE LINES 39-55

Repetition structure
--------------------

Each repetition (each TR) of the sequence consists of three steps:

1. **Excite one slice.** A sinc pulse is played together with a gradient
   along z, so only the spins in the slice are on resonance.
2. **Encode.** A phase-encoding gradient along y moves k-space to the line
   this repetition acquires, and a prewinder along x moves it to the start
   of that line.
3. **Read out.** A gradient along x sweeps the line while the ADC samples
   it. The echo forms in the middle of the readout, where the readout
   gradient has undone the prewinder.

The scan repeats this once per line, changing only the phase-encoding
amplitude. By the end of the lesson your sequence draws this diagram:

.. GENERATED FROM PYTHON SOURCE LINES 55-114




.. image-sg:: /generated/gallery/01-course/images/sphx_glr_01_first_gradient_echo_001.png
   :alt: 01 first gradient echo
   :srcset: /generated/gallery/01-course/images/sphx_glr_01_first_gradient_echo_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 115-124

System limits
-------------

Before you design anything, you tell pypulseqpp what your scanner can do.
This is the ``system`` object, a :class:`~pypulseqpp.Opts`: the gradient
amplitude and slew-rate limits, and the dead times the RF and receive chains
need around an event. Every factory below designs its event against it, so
the same code gives you a different, but always playable, event on a
different scanner. Anything you leave out keeps its default.

.. GENERATED FROM PYTHON SOURCE LINES 124-138

.. 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,
    )








.. GENERATED FROM PYTHON SOURCE LINES 139-144

The scanner does not play arbitrary times: each kind of event starts and
changes on its own clock, its *raster*. Gradient waveforms are defined on
the gradient raster, RF samples on the RF raster, ADC dwell times on the ADC
raster, and every block lasts a whole number of block-duration rasters.
They are part of ``system`` too:

.. GENERATED FROM PYTHON SOURCE LINES 144-150

.. code-block:: Python


    print(f"gradient raster {1e6 * system.grad_raster_time:g} us")
    print(f"RF raster       {1e6 * system.rf_raster_time:g} us")
    print(f"ADC raster      {1e6 * system.adc_raster_time:g} us")
    print(f"block raster    {1e6 * system.block_duration_raster:g} us")





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

 .. code-block:: none

    gradient raster 20 us
    RF raster       2 us
    ADC raster      2 us
    block raster    20 us




.. GENERATED FROM PYTHON SOURCE LINES 151-162

The factories place every event they design on its raster. Times you choose
yourself, such as a delay, a dwell time or an echo time, have to be placed
on the raster explicitly.

Protocol
--------

A field of view of 220 mm, a 128 by 128 matrix, a 5 mm slice, a 12 degree
flip angle and a TR of 20 ms. The field of view and the matrix fix k-space:
lines are :math:`1/\mathrm{FOV}` apart, and each line spans
:math:`N/\mathrm{FOV}`.

.. GENERATED FROM PYTHON SOURCE LINES 162-170

.. code-block:: Python

    FOV = 220e-3
    MATRIX = 128
    THICKNESS = 5e-3
    FLIP_ANGLE_DEG = 12.0
    TR = 20e-3

    delta_k = 1 / FOV








.. GENERATED FROM PYTHON SOURCE LINES 171-185

Excitation
----------

:func:`~pypulseqpp.make_sinc_pulse` designs a slice-selective sinc pulse.
You give it the flip angle, the duration, the slice thickness and the
time-bandwidth product, and with ``return_gz=True`` it also returns the
slice-selection gradient and the *rephaser*, a short negative gradient that
undoes the phase the slice accumulates under the second half of the
selection gradient. Without the rephaser the signal across the slice would
cancel out.

The ``delay`` starts the pulse after the RF dead time, and
``use="excitation"`` records the role of the pulse for the interpreter and
the k-space analysis.

.. GENERATED FROM PYTHON SOURCE LINES 185-197

.. code-block:: Python

    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,
    )








.. GENERATED FROM PYTHON SOURCE LINES 198-209

Readout
-------

The readout gradient has to sweep one whole line, :math:`N\,\Delta k`, while
the ADC samples it at constant amplitude, so you ask for the area of its
flat top and for a flat time that holds the samples. The time between two
samples is the *dwell time*, and its inverse is the receiver bandwidth.

You want a dwell of about 26 µs, and the dwell has to be on the ADC raster.
A dwell off the raster is rejected by the timing check. With 25.9 µs, one
readout gives:

.. GENERATED FROM PYTHON SOURCE LINES 209-222

.. code-block:: Python

    dwell = 25.9e-6
    gx = pp.make_trapezoid(
        "x", flat_area=MATRIX * delta_k, flat_time=MATRIX * dwell, system=system
    )
    adc = pp.make_adc(MATRIX, dwell=dwell, delay=gx.rise_time, system=system)

    seq = pp.Sequence(system)
    seq.add_block(gx, adc)
    ok, errors = seq.check_timing()
    print(f"timing ok: {ok}")
    for error in errors:
        print(f"  block {error.block}, {error.event}.{error.field}: {error.error_type}")





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

 .. code-block:: none

    timing ok: False
      block 1, gx.flat_time: RASTER
      block 1, adc.dwell: RASTER




.. GENERATED FROM PYTHON SOURCE LINES 223-232

:meth:`~pypulseqpp.Sequence.check_timing` walks every block and reports each
event that is off its raster or too close to a dead time. Here the flat time
of the gradient and the dwell time of the ADC are both off. Neither is wrong
by much, but the scanner cannot play them.

:func:`~pypulseqpp.calc_adc_timing` resolves this: it moves the requested
dwell onto the ADC raster, and returns a readout duration that ends on
the gradient raster. The ADC starts after the gradient's ramp
(``delay=gx.rise_time``), so every sample is taken on the flat top.

.. GENERATED FROM PYTHON SOURCE LINES 232-248

.. code-block:: Python

    dwell, readout_time = pp.calc_adc_timing(
        MATRIX,
        26e-6,
        grad_raster_time=system.grad_raster_time,
        adc_raster_time=system.adc_raster_time,
    )
    gx = pp.make_trapezoid(
        "x", flat_area=MATRIX * delta_k, flat_time=readout_time, system=system
    )
    adc = pp.make_adc(MATRIX, dwell=dwell, delay=gx.rise_time, system=system)

    print(
        f"dwell {1e6 * dwell:.0f} us, receiver bandwidth {1e-3 / dwell:.1f} kHz, "
        f"{1 / (dwell * MATRIX):.0f} Hz per pixel"
    )





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

 .. code-block:: none

    dwell 30 us, receiver bandwidth 33.3 kHz, 260 Hz per pixel




.. GENERATED FROM PYTHON SOURCE LINES 249-257

You asked for 26 µs and got the closest dwell at or above it for which both
conditions hold. A longer dwell means a lower receiver bandwidth, a longer
readout and a higher signal-to-noise ratio.

The prewinder moves k-space from the centre to the start of the line, so the
readout crosses the centre halfway through: it is a gradient along x with
minus half the readout's area. You also ask for its duration, 1 ms, so it
fits in the same block as the phase encoding.

.. GENERATED FROM PYTHON SOURCE LINES 257-259

.. code-block:: Python

    gx_pre = pp.make_trapezoid("x", area=-gx.area / 2, duration=1e-3, system=system)








.. GENERATED FROM PYTHON SOURCE LINES 260-267

Phase encoding
--------------

Each repetition acquires a different line, :math:`k_y = m\,\Delta k` with
:math:`m` from :math:`-N/2` to :math:`N/2 - 1`. You design one phase-encoding
gradient, the largest, and in the loop you only scale it with
:func:`~pypulseqpp.scale_grad`.

.. GENERATED FROM PYTHON SOURCE LINES 267-270

.. code-block:: Python

    gy_pre = pp.make_trapezoid("y", area=MATRIX / 2 * delta_k, duration=1e-3, system=system)
    phase_steps = np.arange(-MATRIX // 2, MATRIX // 2) / (MATRIX / 2)








.. GENERATED FROM PYTHON SOURCE LINES 271-293

This is the main rule of writing a Pulseq sequence: **design every event
once, before the loop**. A factory solves for the shortest waveform that does
what you ask, and solving it again in every repetition costs time and
produces nothing new. Inside the loop, only the amplitudes and phases of
existing events change. The scan then reuses the same few waveforms, and the file stores each
of them once.

Scan loop
---------

One repetition is four blocks. A block holds at most one event per channel
(RF, ADC and each gradient axis), all of them start together, and the block
lasts as long as its longest event. Blocks play one after the other.

1. the pulse and the slice-selection gradient;
2. the prewinder, the phase encoding and the rephaser, which do not
   interfere and so play together;
3. the readout gradient and the ADC;
4. a delay that fills the rest of the TR.

The delay is what you choose yourself, so you put it on the block-duration
raster with :func:`~pypulseqpp.round_to_raster`.

.. GENERATED FROM PYTHON SOURCE LINES 293-310

.. code-block:: Python

    played = (
        pp.calc_duration(rf, gz)
        + pp.calc_duration(gx_pre, gy_pre, gz_reph)
        + pp.calc_duration(gx, adc)
    )
    tr_delay = pp.round_to_raster(TR - played, system.block_duration_raster)

    seq = pp.Sequence(system)
    for step in phase_steps:
        seq.add_block(rf, gz)
        seq.add_block(gx_pre, pp.scale_grad(gy_pre, step), gz_reph)
        seq.add_block(gx, adc)
        seq.add_block(pp.make_delay(tr_delay))

    ok, errors = seq.check_timing()
    print(f"timing ok: {ok}, {seq.num_blocks} blocks, scan time {seq.duration()[0]:.2f} s")





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

 .. code-block:: none

    timing ok: True, 512 blocks, scan time 2.56 s




.. GENERATED FROM PYTHON SOURCE LINES 311-318

Sequence diagram
----------------

:meth:`~pypulseqpp.Sequence.paper_plot` draws one repetition, one row per
channel. The solid trace is the repetition you pick (here the first); the
shaded traces are all the others, so you can see what changes from one TR
to the next: only the phase-encoding amplitude.

.. GENERATED FROM PYTHON SOURCE LINES 318-320

.. code-block:: Python

    seq.paper_plot(tr=1)




.. image-sg:: /generated/gallery/01-course/images/sphx_glr_01_first_gradient_echo_002.png
   :alt: 01 first gradient echo
   :srcset: /generated/gallery/01-course/images/sphx_glr_01_first_gradient_echo_002.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 321-328

k-space trajectory
------------------

:func:`pypulseqpp.plot.plot_kspace` integrates the gradients from each
excitation and marks where the ADC samples. Each repetition jumps to its
line during the prewinder and sweeps it left to right during the readout:
128 lines on a Cartesian grid, :math:`\Delta k` apart.

.. GENERATED FROM PYTHON SOURCE LINES 328-330

.. code-block:: Python

    pp.plot.plot_kspace(seq, plane="xy")




.. image-sg:: /generated/gallery/01-course/images/sphx_glr_01_first_gradient_echo_003.png
   :alt: 01 first gradient echo
   :srcset: /generated/gallery/01-course/images/sphx_glr_01_first_gradient_echo_003.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 331-337

Pulseq file
-----------

Last, you write the file the scanner plays. The definitions you set go in
its header, next to the blocks; a reconstruction reads them to know what was
acquired. Lesson 4 covers them in detail.

.. GENERATED FROM PYTHON SOURCE LINES 337-346

.. code-block:: Python

    from pathlib import Path
    from tempfile import mkdtemp

    seq.set_definition("Name", "gre")
    seq.set_definition("FOV", [FOV, FOV, THICKNESS])
    path = Path(mkdtemp()) / "gre.seq"
    seq.write(str(path))
    print("\n".join(path.read_text().splitlines()[:8]))





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

 .. code-block:: none

    # Pulseq sequence file
    # Created by PyPulseq

    [VERSION]
    major 1
    minor 5
    revision 1





.. GENERATED FROM PYTHON SOURCE LINES 347-366

Any Pulseq interpreter plays this file. What it holds (blocks, events and
shared libraries of shapes), and the rasters its times lie on, are explained
in :doc:`/explanations/pulseq-file`.

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

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

.. code-block:: text

   Write a pypulseqpp 2D Cartesian gradient echo for a scanner with
   32 mT/m and 130 T/m/s, 100 us RF dead time, 20 us RF ringdown and
   10 us ADC dead time. Protocol: 220 mm FOV, 128x128 matrix, one 5 mm
   slice, 12 degree sinc excitation (2 ms, time-bandwidth product 4),
   about 26 us dwell, TR 20 ms. Design every event before the loop,
   scale the phase encoding in the loop, put the TR delay on the block
   raster, check the timing, and write gre.seq with the Name and FOV
   definitions.


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

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


.. _sphx_glr_download_generated_gallery_01-course_01_first_gradient_echo.py:

.. only:: html

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

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

      :download:`Download Jupyter notebook: 01_first_gradient_echo.ipynb <01_first_gradient_echo.ipynb>`

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

      :download:`Download Python source code: 01_first_gradient_echo.py <01_first_gradient_echo.py>`

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

      :download:`Download zipped: 01_first_gradient_echo.zip <01_first_gradient_echo.zip>`


.. only:: html

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

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