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

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

.. _sphx_glr_generated_gallery_01-course_02_sequence_plugin.py:


======================================
2. A scanner sequence and its protocol
======================================

A scanner sequence binds a pypulseqpp sequence function to the entries of the
scanner protocol. The operator edits the entries; pulserver converts their
values into the function's arguments, evaluates the protocol under the scanner
limits, and returns the protocol the design achieves. The previous lesson used
the shipped ``gre2d``; this lesson writes one like it.

**Learning objectives**

- Declare a :class:`~pulserver.design.SequencePlugin`: the sequence function
  and the entries that bind its arguments, with their units and presets.
- Write an evaluation that designs one repetition of the scan and reads back
  the values the design achieves, the scan time and the RF of one TR.
- Validate requests, including a preset and a prescription the design
  refuses, and write the design.

The next lesson converts a design into the representation the scanner plays.

.. GENERATED FROM PYTHON SOURCE LINES 23-33








.. GENERATED FROM PYTHON SOURCE LINES 34-52

Entries
-------

Each entry of :attr:`~pulserver.design.SequencePlugin.protocol` maps a
protocol key, which names a parameter of the scanner UI, to a keyword
argument of the sequence function. A :class:`~pulserver.design.TimeParam`
is exchanged in integer microseconds and given to the function in seconds;
its *Minimum* preset passes ``None``, for which ``gre2d`` designs its
shortest time. A :class:`~pulserver.design.FloatParam` carries a unit and a
scale, here mm on the UI and m for the function. Entries a protocol leaves
out keep the function's defaults.

:meth:`~pulserver.design.SequencePlugin.evaluate` answers every edit, so it
designs one repetition, a single phase-encoding line without dummy scans,
rather than the scan. The echo time and the repetition time are the ``TE``
and ``TR`` definitions the design records; the scan time is the repetition
time times the lines and dummy scans the scan plays. The RF layout is the RF
of that one TR, its excitation's amplitude proportional to the flip angle.

.. GENERATED FROM PYTHON SOURCE LINES 52-107

.. code-block:: Python

    import inspect

    import pypulseqpp as pp
    from pypulseqpp.sequences.sequence.gre2D_sequence import gre2d

    from pulserver.design import (
        Evaluation,
        FloatParam,
        IntParam,
        RfLayout,
        SequencePlugin,
        TimeParam,
    )
    from pulserver.protocol import TEPreset, TRPreset, UIParam, format_listing


    class Gre2D(SequencePlugin):
        app = gre2d
        protocol = {
            UIParam.FLIP: FloatParam(
                "flip_angle_deg", unit="deg", range_min=1.0, range_max=90.0
            ),
            UIParam.TE: TimeParam(
                "te", range_min=1000, range_max=80000, presets={TEPreset.MINIMUM: None}
            ),
            UIParam.TR: TimeParam(
                "tr", range_min=1000, range_max=5_000_000, presets={TRPreset.MINIMUM: None}
            ),
            UIParam.FOV: FloatParam(
                "fov_x", unit="mm", scale=1e-3, range_min=50.0, range_max=500.0
            ),
            UIParam.NX: IntParam("n_x", range_min=32, range_max=512, range_incr=2),
            UIParam.NY: IntParam("n_y", range_min=32, range_max=512, range_incr=2),
        }

        def evaluate(self, system, protocol):
            bound = inspect.signature(self.app).bind_partial(system, **protocol.arguments)
            bound.apply_defaults()
            a = bound.arguments
            one = self.app(
                system,
                **(protocol.arguments | {"n_dummy": 0, "ry": a["n_y"], "n_acs_y": 0}),
            )
            tr = one.definitions["TR"][0]
            achieved = {UIParam.TE: one.definitions["TE"][0], UIParam.TR: tr}
            return Evaluation(
                protocol.replace(achieved),
                duration=tr * (a["n_dummy"] + a["n_y"]),
                rf_layout=RfLayout.of(one, UIParam.FLIP, period=tr),
            )


    plugin = Gre2D()
    print(format_listing(plugin.listing()), end="")





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

 .. code-block:: none

    [Protocol]
    flip: float|typein|12.0|1.0|90.0|1.0|deg
    TE: int|dropdown|8000|1000|80000|1|us|-2
    TR: int|dropdown|250000|1000|5000000|1|us|-1
    fov: float|typein|220.0|50.0|500.0|1.0|mm
    nx: int|typein|128|32|512|2|
    ny: int|typein|128|32|512|2|
    fov_offset_x: float|off|0.0|-1000.0|1000.0|0.1|mm
    fov_offset_y: float|off|0.0|-1000.0|1000.0|0.1|mm
    fov_offset_z: float|off|0.0|-1000.0|1000.0|0.1|mm
    fov_rotation_11: float|off|1.0|-1.0|1.0|1e-06|
    fov_rotation_12: float|off|0.0|-1.0|1.0|1e-06|
    fov_rotation_13: float|off|0.0|-1.0|1.0|1e-06|
    fov_rotation_21: float|off|0.0|-1.0|1.0|1e-06|
    fov_rotation_22: float|off|1.0|-1.0|1.0|1e-06|
    fov_rotation_23: float|off|0.0|-1.0|1.0|1e-06|
    fov_rotation_31: float|off|0.0|-1.0|1.0|1e-06|
    fov_rotation_32: float|off|0.0|-1.0|1.0|1e-06|
    fov_rotation_33: float|off|1.0|-1.0|1.0|1e-06|
    [Protocol End]




.. GENERATED FROM PYTHON SOURCE LINES 108-115

Validation
----------

A request is a mapping of protocol keys to wire values; the entries it
leaves out take their initial values. Here the echo and repetition times
request the *Minimum* preset, sent as its negative value, and the reply
holds the times the design achieved.

.. GENERATED FROM PYTHON SOURCE LINES 115-128

.. code-block:: Python

    system = pp.Opts(max_grad=40, grad_unit="mT/m", max_slew=150, slew_unit="T/m/s")

    shortest = plugin.validate(
        system,
        {
            UIParam.TE: TEPreset.MINIMUM.value,
            UIParam.TR: TRPreset.MINIMUM.value,
            UIParam.NY: 64,
        },
    )
    print(f"valid: {shortest.valid}, scan time {shortest.duration:.2f} s")
    print(f"TE {shortest.values[UIParam.TE]} us, TR {shortest.values[UIParam.TR]} us")





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

 .. code-block:: none

    valid: True, scan time 0.62 s
    TE 3400 us, TR 7720 us




.. GENERATED FROM PYTHON SOURCE LINES 129-131

The RF layout states the definitions and the instances of one TR, from which
a scanner estimates the RF of a prescription without designing it.

.. GENERATED FROM PYTHON SOURCE LINES 131-136

.. code-block:: Python

    layout = shortest.rf_layout
    print(
        f"{len(layout.instances.definitions)} RF definition, {len(layout.control)} instance per TR of {layout.period * 1e3:.2f} ms, controlled by {layout.control}"
    )





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

 .. code-block:: none

    1 RF definition, 1 instance per TR of 7.72 ms, controlled by (<FloatKey.FLIP: 'flip'>,)




.. GENERATED FROM PYTHON SOURCE LINES 137-139

A prescription the sequence function cannot realize is invalid. The reply
carries the request unchanged and, as its note, the error the design raised.

.. GENERATED FROM PYTHON SOURCE LINES 139-143

.. code-block:: Python

    refused = plugin.validate(system, {UIParam.TE: 1000})
    print(f"valid: {refused.valid}")
    print(refused.info)





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

 .. code-block:: none

    valid: False
    the requested TE of 1.000 ms is shorter than the 3.400 ms this readout can achieve




.. GENERATED FROM PYTHON SOURCE LINES 144-152

Design
------

:meth:`~pulserver.design.SequencePlugin.design` evaluates a request and
writes the sequence of the requested protocol as signed binary Pulseq,
designing the whole scan with :meth:`~pulserver.design.SequencePlugin.generate`.
The design calls of the previous lesson do the same, then check and convert
the design and store it.

.. GENERATED FROM PYTHON SOURCE LINES 152-161

.. code-block:: Python

    import tempfile
    from pathlib import Path

    directory = Path(tempfile.mkdtemp())
    validation, paths = plugin.design(system, shortest.values, directory)
    print([Path(path).name for path in paths])
    written = pp.Sequence()
    written.read(paths[0])
    print(f"{written.num_blocks} blocks")




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

 .. code-block:: none

    ['sequence.seq']
    400 blocks





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

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


.. _sphx_glr_download_generated_gallery_01-course_02_sequence_plugin.py:

.. only:: html

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

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

      :download:`Download Jupyter notebook: 02_sequence_plugin.ipynb <02_sequence_plugin.ipynb>`

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

      :download:`Download Python source code: 02_sequence_plugin.py <02_sequence_plugin.py>`

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

      :download:`Download zipped: 02_sequence_plugin.zip <02_sequence_plugin.zip>`


.. only:: html

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

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