Rotation conventions

This page discusses the rotation conventions of the Julia package BlochSim

This page comes from a single Julia file: 01-rotate.jl.

You can access the source code for such Julia documentation using the 'Edit on GitHub' link in the top right. You can view the corresponding notebook in nbviewer here: 01-rotate.ipynb, or open it in binder here: 01-rotate.ipynb.

Setup

First we add the Julia packages that are need for this demo. Change false to true in the following code block if you are using any of the following packages for the first time.

if false
    import Pkg
    Pkg.add([
        "BlochSim"
        "InteractiveUtils"
        "LaTeXStrings"
        "LinearAlgebra"
        "MIRTjim"
        "Plots"
    ])
end

Tell this Julia session to use the following packages for this example. Run Pkg.add() in the preceding code block first, if needed.

using BlochSim: Spin, SpinMC, InstantaneousRF, RF, excite
using BlochSim: excite_bloch3
using MIRTjim: prompt
using Plots: annotate!, color, default, gui, plot, plot!, scatter!, scatter3d!, text

default(titlefontsize = 10, markerstrokecolor = :auto, label="", width = 1.5,
    linewidth = 2)

The following line is helpful when running this file as a script; this way it will prompt user to hit a key after each figure is displayed.

isinteractive() || prompt(:draw);

Rotation convention

The convention for rotation by RF excitation in BlochSim is illustrated by the following plots.

round2(x) = round(x, digits = 2)

Mz0, T1_ms, T2_ms, Δf_Hz = 1, 400, 10, 9 # tissue parameters
xt = (; Mz0, T1_ms, T2_ms, Δf_Hz) # tuple
spin = Spin(Mz0, T1_ms, T2_ms, Δf_Hz)
Spin{Float64}:
 M = Magnetization(0.0, 0.0, 1.0)
 M0 = 1.0
 T1 = 400.0 ms
 T2 = 10.0 ms
 Δf = 9.0 Hz
 pos = Position(0.0, 0.0, 0.0) cm

Helper function to show rotation from equilibrium to transverse plane

function plot_tip(θ; # phase angle
    α=π/2, # flip angle (radians)
    color=:blue, excite=excite,
)
    p = plot(
     xaxis = ("", (-1, 1), ), xtick = [1],
     yaxis = ("", (-1, 1), ), ytick = [1],
     zaxis = ("", (-1, 1), ), ztick = [1],
     aspect_ratio = 1,
     framestyle = :origin,
     size = (400, 400),
     camera = (139, 30),
     title = "θ = $(round2(rad2deg(θ)))°",
    )

    plot!([0, -sin(θ)], [0, -cos(θ)], [0, 0], lw=3, color=:black) # B₁

    for (i, α) in enumerate(range(0, α, 11))
        rf = InstantaneousRF(α, θ)
        A, _ = excite(spin, rf)
        v = Matrix(A) * [0, 0, 1]
        alpha = i/11 # transparency
        plot!([0, v[1]], [0, v[2]], [0, v[3]], lw=2; color, alpha,)
        scatter3d!([v[1]], [v[2]], [v[3]]; alpha,
            markercolor = color,
            markershape = :utriangle, # lazy arrow tip
        )
    end

    annotate!(p, [
        (1.3, 0, 0, text("x", :left, 10)),
        (0, 1.2, 0, text("y", :left, 10)),
        (0, 0, 1.2, text("z", :bottom, 10)),
    ])
    return p
end;

Helper to show 3 different θ cases:

function plot_tips( ; excite=excite)
    return plot(
     plot_tip(  0, color=:blue; excite),
     plot_tip(π/4, color=:magenta; excite),
     plot_tip(π/2, color=:red; excite),
     layout = (1,3),
     size = (900, 300),
    )
end
ptip1 = plot_tips( ; excite=excite)
Example block output
prompt()

As described in the docstring for rotatetheta!(A, α, θ), excitation here applies "left-handed rotation by angle α about an axis in the x-y plane that makes left-handed angle θ with the negative y-axis."

Comments within the function refer to p.27 of Dwight Nishimura's "Principles of Magnetic Resonance Imaging" (1996).

As illustrated by the black line above, the B₁ axis is [-sin(θ) -cos(θ) 0]

So α = π/2 and θ = 0 takes equilibrium magnetization [0, 0, 1] to [1, 0, 0].

The following excitation matrix examples illustrate.

θ=0

α_deg = 90 # flip angle °
α_rad = deg2rad(α_deg)
rf0 = InstantaneousRF(α_rad, 0)
A1, _ = excite(spin, rf0)
round2.(Matrix(A1))
3×3 Matrix{Float64}:
  0.0  0.0   1.0
  0.0  1.0  -0.0
 -1.0  0.0   0.0
round2.(Matrix(A1) * [0, 0, 1])
3-element Vector{Float64}:
 1.0
 0.0
 0.0

θ=π/2

rf2 = InstantaneousRF(α_rad, π/2)
A2, _ = excite(spin, rf2)
round2.(Matrix(A2))
3×3 Matrix{Float64}:
  1.0  0.0   0.0
  0.0  0.0  -1.0
 -0.0  1.0   0.0
round2.(Matrix(A2) * [0, 0, 1])
3-element Vector{Float64}:
  0.0
 -1.0
  0.0

The following figure uses the function excite_bloch3 that is based on analytical solutions to the eigenvalues of the 3×3 Bloch matrix. It is designed to use the same rotation conventions for self consistency.

ptip3 = plot_tips( ; excite = (spin, rf) -> excite_bloch3(spin, rf; warn=false))
Example block output
prompt()

Reproducibility

This page was generated with the following version of Julia:

using InteractiveUtils: versioninfo
io = IOBuffer(); versioninfo(io); split(String(take!(io)), '\n')
12-element Vector{SubString{String}}:
 "Julia Version 1.12.6"
 "Commit 15346901f00 (2026-04-09 19:20 UTC)"
 "Build Info:"
 "  Official https://julialang.org release"
 "Platform Info:"
 "  OS: Linux (x86_64-linux-gnu)"
 "  CPU: 4 × AMD EPYC 9V74 80-Core Processor"
 "  WORD_SIZE: 64"
 "  LLVM: libLLVM-18.1.7 (ORCJIT, znver4)"
 "  GC: Built with stock GC"
 "Threads: 1 default, 1 interactive, 1 GC (on 4 virtual cores)"
 ""

And with the following package versions

import Pkg; Pkg.status()
Status `~/work/BlochSim.jl/BlochSim.jl/docs/Project.toml`
  [47edcb42] ADTypes v1.22.0
  [6e4b80f9] BenchmarkTools v1.8.0
  [5c0f8cbe] BlochSim v0.10.0 `~/work/BlochSim.jl/BlochSim.jl`
  [e30172f5] Documenter v1.17.0
  [e24c0720] ExponentialAction v0.2.10
  [f6369f11] ForwardDiff v1.3.3
  [b964fa9f] LaTeXStrings v1.4.0
  [98b081ad] Literate v2.21.0
  [170b2178] MIRTjim v0.26.0
  [0dd308a2] MRIPulses v0.0.2
  [429524aa] Optim v2.1.0
  [91a5bcdd] Plots v1.41.6
  [1986cc42] Unitful v1.28.0
  [b77e0a4c] InteractiveUtils v1.11.0
  [37e2e46d] LinearAlgebra v1.12.0
  [44cfe95a] Pkg v1.12.1
  [9a3f8284] Random v1.11.0

This page was generated using Literate.jl.