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

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

.. _sphx_glr_generated_gallery_01-course_03_scanner_representation.py:


===========================
3. Scanner representation
===========================

A Pulseq file lists every block of a scan. The scanner plays it as an
execution stream of segment instances: each virtual segment, an ordered list
of base blocks, is prepared once and played many times with new amplitudes and
phases. This lesson converts the design of the previous lessons into that
representation, reads what it was reduced to, and walks the stream as the
scanner's playout does. The model is described in
:doc:`/explanations/scanner-representation`.

**Learning objectives**

- Convert a sequence file into its IR cache with :func:`~pulserver.ir.convert`.
- Read the repetition, the virtual segments and the readouts of a design from
  :func:`~pulserver.ir.summary`.
- Walk the execution stream with :func:`~pulserver.ir.play`, and tell what a
  segment instance changes from what its virtual segment fixes.
- Change where segment boundaries fall with :class:`~pulserver.ir.Grouping`.

The next lesson reconstructs the raw data a scan of this cache returns.

.. GENERATED FROM PYTHON SOURCE LINES 25-31








.. GENERATED FROM PYTHON SOURCE LINES 32-39

Conversion
----------

The design is pypulseqpp's 2D gradient echo at a 64 by 64 matrix, written to
a file. :func:`~pulserver.ir.convert` reads it under the scanner limits and
writes the IR cache beside it, and :func:`~pulserver.ir.summary` reports
what the conversion reduced it to.

.. GENERATED FROM PYTHON SOURCE LINES 39-56

.. code-block:: Python

    import tempfile
    from pathlib import Path

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

    from pulserver import ir

    system = pp.Opts(max_grad=40, grad_unit="mT/m", max_slew=150, slew_unit="T/m/s")
    path = Path(tempfile.mkdtemp()) / "gre2d.seq"
    seq = gre2d(system, n_x=64, n_y=64)
    seq.write(path)

    print(ir.convert(path, system).name)
    report = ir.summary(path, system, cache_ext=".pseg")





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

 .. code-block:: none

    gre2d.pseg




.. GENERATED FROM PYTHON SOURCE LINES 57-62

The repetition is the period pypulseqpp's ``Sequence.repetition`` finds: one
phase-encoding line, played once per line and once per dummy scan. The
conversion cuts it into virtual segments at block boundaries where the
gradients rest. A pure delay is a virtual segment of its own, played with
each instance's duration.

.. GENERATED FROM PYTHON SOURCE LINES 62-76

.. code-block:: Python

    repetition = report["subsequences"][0]
    print(f"{seq.num_blocks} blocks in the file")
    print(
        f"repetition: {repetition['tr_size']} blocks, "
        f"TR {repetition['tr_duration_us'] / 1e3:.1f} ms, "
        f"played {repetition['num_trs']} times"
    )
    for index, segment in enumerate(report["segments"]):
        first = segment["start_block"] + 1
        last = segment["start_block"] + segment["num_blocks"]
        kind = "pure delay" if segment["pure_delay"] else "events"
        print(f"virtual segment {index}: blocks {first}-{last} of the repetition, {kind}")
    print(f"{report['total_readouts']} readouts")





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

 .. code-block:: none

    480 blocks in the file
    repetition: 6 blocks, TR 250.0 ms, played 80 times
    virtual segment 0: blocks 1-5 of the repetition, events
    virtual segment 1: blocks 6-6 of the repetition, pure delay
    64 readouts




.. GENERATED FROM PYTHON SOURCE LINES 77-84

The execution stream
--------------------

:func:`~pulserver.ir.play` loads the cache with the C library a scanner
links and walks the execution stream with its cursor, one entry per played
block. Each segment instance plays the blocks of its virtual segment, so the
stream is read instance by instance by stepping over that many blocks.

.. GENERATED FROM PYTHON SOURCE LINES 84-93

.. code-block:: Python

    played = ir.play(path)
    instances = []
    block = 0
    while block < len(played["segment"]):
        instances.append(int(played["segment"][block]))
        block += report["segments"][instances[-1]]["num_blocks"]
    print(f"{block} blocks played as {len(instances)} segment instances")
    print("virtual segments of the first instances:", instances[:6])





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

 .. code-block:: none

    480 blocks played as 160 segment instances
    virtual segments of the first instances: [0, 1, 0, 1, 0, 1]




.. GENERATED FROM PYTHON SOURCE LINES 94-97

Every instance of the first virtual segment plays the same base blocks. What
changes from one to the next is held by the instance: here the amplitude of
the phase-encoding gradient and the RF phase offset that spoiling steps.

.. GENERATED FROM PYTHON SOURCE LINES 97-117

.. code-block:: Python

    blocks = repetition["tr_size"]
    gy = played["gradient_hz_per_m"][:, 1].reshape(-1, blocks)
    phase_encoding = np.abs(gy).max(axis=0).argmax()
    rf_phase = played["rf_phase_rad"].reshape(-1, blocks)[:, 0]





.. image-sg:: /generated/gallery/01-course/images/sphx_glr_03_scanner_representation_001.png
   :alt: 03 scanner representation
   :srcset: /generated/gallery/01-course/images/sphx_glr_03_scanner_representation_001.png
   :class: sphx-glr-single-img





.. GENERATED FROM PYTHON SOURCE LINES 118-131

The first repetitions are the dummy scans, played without phase encoding; the
lines follow. The RF phase offset steps by an increment that grows by
117° from one repetition to the next, the schedule of RF spoiling.
Both are parameters of segment instances: the virtual segment, and the
waveforms the scanner prepares for it, are the same in every repetition.

Grouping
--------

Where boundaries may fall depends on the interpreter: one that inserts
switching time at every segment boundary needs as few segments as possible.
:class:`~pulserver.ir.Grouping` states the rule. Without splitting off the
delay at the edge of a segment, the repetition is one virtual segment.

.. GENERATED FROM PYTHON SOURCE LINES 131-136

.. code-block:: Python

    ir.convert(path, system, grouping=ir.Grouping(split_edge_delays=False))
    merged = ir.summary(path, system, cache_ext=".pseg")
    print(
        f"virtual segments: {report['num_segments']} by default, {merged['num_segments']} merged"
    )




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

 .. code-block:: none

    virtual segments: 2 by default, 1 merged





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

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


.. _sphx_glr_download_generated_gallery_01-course_03_scanner_representation.py:

.. only:: html

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

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

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

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

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

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

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


.. only:: html

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

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