
.. DO NOT EDIT.
.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY.
.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE:
.. "generated/gallery/05-sequence-modules/03_sequence_function.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_05-sequence-modules_03_sequence_function.py>`
        to download the full example code.

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

.. _sphx_glr_generated_gallery_05-sequence-modules_03_sequence_function.py:


===================
A sequence function
===================

The previous lesson,
:doc:`/generated/gallery/05-sequence-modules/01_sequence_modules`, designed an
excitation and a readout with modules. This lesson assembles them into a
complete acquisition. A sequence function
takes the system limits and a protocol: it designs the modules, plays one
repetition per line of k-space and returns the sequence. The function records
the encoding labels with :class:`~pypulseqpp.sequences.Labels`.
:func:`~pypulseqpp.sequences.parameters` reads its protocol, and
:func:`~pypulseqpp.sequences.write` writes the sequence it returns.

The sequence is the RF-spoiled slice-selective gradient echo of
:doc:`/generated/gallery/02-spoiling/01_spoiling`, expressed as a sequence
function rather than as a loop over events. Each sequence in :doc:`/sequences` is
such a function, in its own module of ``pypulseqpp.sequences``. The structure is
described in :doc:`/explanations/sequence-design`.

Learning objectives
-------------------

After this lesson, you should be able to:

- state what a sequence function designs before its loop, plays in the loop
  and records after it;
- write a function of the system limits and a protocol of keyword parameters
  that designs and plays a sequence, recording its encoding indices with
  :class:`~pypulseqpp.sequences.Labels`;
- read the acquisition order back from the ``LIN`` labels of the sequence;
- read the protocol of a sequence function with
  :func:`~pypulseqpp.sequences.parameters`, derive its command-line options
  from the same signature, and write what it returns with
  :func:`~pypulseqpp.sequences.write`.

.. GENERATED FROM PYTHON SOURCE LINES 40-53

Sequence function
-----------------

``system`` is the first parameter and the keyword parameters after it are the
protocol. Before the loop, the function lowers the limits of ``system`` to the
gradient and slew limits it is designed for with
:func:`~pypulseqpp.cap_system`, which never raises a limit, and designs the
excitation and readout modules; the readout module solves the echo and
repetition times that are ``None``. The loop plays one repetition per line of
k-space. A label keeps its value until an event changes it, so
:class:`~pypulseqpp.sequences.Labels` returns only the ``LIN`` events a block
needs. After the loop, the geometry a reconstruction reads is recorded as
definitions and the function returns the sequence.

.. GENERATED FROM PYTHON SOURCE LINES 53-129

.. code-block:: Python


    import numpy as np

    import pypulseqpp as pp
    import pypulseqpp.sequences as design


    def spoiled_gradient_echo(
        system: pp.Opts,
        *,
        fov: float = 220e-3,
        matrix: int = 128,
        slice_thickness: float = 5e-3,
        flip_angle_deg: float = 12.0,
        te: float | None = None,
        tr: float | None = None,
    ) -> pp.Sequence:
        """RF-spoiled 2D Cartesian gradient echo, one line per repetition.

        Parameters
        ----------
        fov : float, default=0.22
            Isotropic field of view (m).
        matrix : int, default=128
            Matrix size along both encoded axes.
        slice_thickness : float, default=0.005
            Slice thickness (m).
        flip_angle_deg : float, default=12.0
            Excitation flip angle (degrees).
        te : float | None, default=None
            Echo time (s). ``None`` is as short as the readout admits.
        tr : float | None, default=None
            Repetition time (s). ``None`` is as short as possible.
        """
        system = pp.cap_system(system, max_grad=40.0, max_slew=150.0)
        excitation = design.SpatialSelectiveExcitation(
            system,
            flip_angle_deg=flip_angle_deg,
            thickness_m=slice_thickness,
            duration_s=3e-3,
        )
        readout = design.LineReadout2D(
            system,
            excitation.rf,
            excitation.gz,
            excitation.gz_reph,
            fov=(fov, fov),
            matrix=(matrix, matrix),
            te=te,
            tr=tr,
        )

        # Quadratic RF spoiling: the phase increment grows by 117 degrees per
        # repetition.
        phases = np.deg2rad(117.0) * np.cumsum(np.arange(matrix))

        seq, labels = pp.Sequence(system), design.Labels()
        for line in range(matrix):
            phase = phases[line] % (2 * np.pi)
            readout.rf.phase_offset = readout.adc.phase_offset = phase
            step = (line - matrix // 2) / (matrix / 2)
            seq.add_block(readout.rf, readout.gz, *labels(LIN=line))
            seq.add_block(
                readout.gx_pre, pp.scale_grad(readout.gy_pre, step), readout.gz_reph
            )
            seq.add_block(readout.gx, readout.adc)
            seq.add_block(readout.gx_spoil, pp.scale_grad(readout.gy_rew, step))
            if getattr(readout, "wait_tr", None) is not None:
                seq.add_block(readout.wait_tr)

        seq.set_definition("FOV", [fov, fov, slice_thickness])
        seq.set_definition("Matrix", [matrix, matrix, 1])
        seq.set_definition("Name", "tour_gre_2d")
        return seq









.. GENERATED FROM PYTHON SOURCE LINES 130-136

Sequence construction
---------------------

Calling the function designs the whole scan. The system is the default
:class:`~pypulseqpp.Opts`, whose limits the function lowers to at most
40 mT/m and 150 T/m/s.

.. GENERATED FROM PYTHON SOURCE LINES 136-142

.. code-block:: Python


    seq = spoiled_gradient_echo(pp.Opts(), matrix=128)
    print(f"{seq.num_blocks} blocks, {seq.duration()[0]:.2f} s")
    print("timing:", seq.check_timing()[0])
    print("definitions:", sorted(seq.definitions))





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

 .. code-block:: none

    512 blocks, 0.71 s
    timing: True
    definitions: ['FOV', 'Matrix', 'Name', 'TotalDuration']




.. GENERATED FROM PYTHON SOURCE LINES 143-145

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

.. GENERATED FROM PYTHON SOURCE LINES 145-148

.. code-block:: Python


    seq.paper_plot(tr=32)




.. image-sg:: /generated/gallery/05-sequence-modules/images/sphx_glr_03_sequence_function_001.png
   :alt: 03 sequence function
   :srcset: /generated/gallery/05-sequence-modules/images/sphx_glr_03_sequence_function_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 149-154

Acquisition order
-----------------

The first change of a label is a SET, a change that repeats the previous
change is an INC, and an unchanged value writes no event.

.. GENERATED FROM PYTHON SOURCE LINES 154-159

.. code-block:: Python


    labels = design.Labels()
    for line in (0, 1, 2, 3, 3):
        print(f"LIN={line}:", [(e.type, e.value) for e in labels(LIN=line)])





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

 .. code-block:: none

    LIN=0: [('labelset', 0)]
    LIN=1: [('labelset', 1)]
    LIN=2: [('labelinc', 1)]
    LIN=3: [('labelinc', 1)]
    LIN=3: []




.. GENERATED FROM PYTHON SOURCE LINES 160-162

Every acquisition of the sequence carries its ``LIN`` label, so the order the
loop played is read back from the sequence rather than reconstructed.

.. GENERATED FROM PYTHON SOURCE LINES 162-165

.. code-block:: Python


    pp.plot.plot_kspace(seq, color_by="order", plane="xy", show_trajectory=False)




.. image-sg:: /generated/gallery/05-sequence-modules/images/sphx_glr_03_sequence_function_002.png
   :alt: 03 sequence function
   :srcset: /generated/gallery/05-sequence-modules/images/sphx_glr_03_sequence_function_002.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 166-173

Protocol
--------

The type and default of a protocol parameter are read from the signature, and
its unit from the first parenthesised group of its description in the
``Parameters`` section. A protocol editor reads this record, and the command
line derives its options from the same signature and ``Parameters`` section.

.. GENERATED FROM PYTHON SOURCE LINES 173-180

.. code-block:: Python


    for name, parameter in design.parameters(spoiled_gradient_echo).items():
        print(
            f"{name:16}{parameter.type.__name__:7}{parameter.unit:9}"
            f"default {parameter.default}"
        )





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

 .. code-block:: none

    fov             float  m        default 0.22
    matrix          int             default 128
    slice_thickness float  m        default 0.005
    flip_angle_deg  float  degrees  default 12.0
    te              float  s        default None
    tr              float  s        default None




.. GENERATED FROM PYTHON SOURCE LINES 181-197

Command-line interface
----------------------

:func:`pypulseqpp.cli.run` derives one option from each keyword parameter,
with the help text from the ``Parameters`` section, builds ``system`` from
the limit options and writes the result with
:func:`~pypulseqpp.sequences.write`. A script that ends with

.. code-block:: python

   if __name__ == "__main__":
       raise SystemExit(cli.run(spoiled_gradient_echo, sys.argv[1:]))

is run as ``python gre.py --matrix 64 --te 5e-3 -o gre.seq``. A function that
returns a list of sequences is written as a chain of files, each naming the
next with ``NextSequence``.

.. GENERATED FROM PYTHON SOURCE LINES 197-203

.. code-block:: Python


    from pathlib import Path
    from tempfile import mkdtemp

    path = Path(mkdtemp()) / "tour_gre_2d.seq"
    print([Path(written).name for written in design.write(path, seq)])




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

 .. code-block:: none

    ['tour_gre_2d.seq']





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

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


.. _sphx_glr_download_generated_gallery_05-sequence-modules_03_sequence_function.py:

.. only:: html

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

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

      :download:`Download Jupyter notebook: 03_sequence_function.ipynb <03_sequence_function.ipynb>`

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

      :download:`Download Python source code: 03_sequence_function.py <03_sequence_function.py>`

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

      :download:`Download zipped: 03_sequence_function.zip <03_sequence_function.zip>`


.. only:: html

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

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