-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathBlochSimple.jl
104 lines (95 loc) · 4.02 KB
/
BlochSimple.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#Simplest sim method, works for GPU and CPU but not optimized for either. Although Bloch()
#is the simulation method chosen if none is passed, the run_spin_precession! and
#run_spin_excitation! functions in this file are dispatched to at the most abstract level,
#so new simulation methods will start by using these functions.
struct BlochSimple <: SimulationMethod end
export BlochSimple
"""
run_spin_precession(obj, seq, Xt, sig)
Simulates an MRI sequence `seq` on the Phantom `obj` for time points `t`. It calculates S(t)
= ∑ᵢ ρ(xᵢ) exp(- t/T2(xᵢ) ) exp(- 𝒊 γ ∫ Bz(xᵢ,t)). It performs the simulation in free
precession.
# Arguments
- `obj`: (`::Phantom`) Phantom struct (actually, it's a part of the complete phantom)
- `seq`: (`::Sequence`) Sequence struct
# Returns
- `S`: (`Vector{ComplexF64}`) raw signal over time
- `M0`: (`::Vector{Mag}`) final state of the Mag vector
"""
function run_spin_precession!(
p::Phantom{T},
seq::DiscreteSequence{T},
sig::AbstractArray{Complex{T}},
M::Mag{T},
sim_method::SimulationMethod,
backend::KA.Backend,
prealloc::PreallocResult
) where {T<:Real}
#Simulation
#Motion
x, y, z = get_spin_coords(p.motion, p.x, p.y, p.z, seq.t')
#Effective field
Bz = x .* seq.Gx' .+ y .* seq.Gy' .+ z .* seq.Gz' .+ p.Δw ./ T(2π .* γ)
#Rotation
if is_ADC_on(seq)
ϕ = T(-2π .* γ) .* cumtrapz(seq.Δt', Bz)
else
ϕ = T(-2π .* γ) .* trapz(seq.Δt', Bz)
end
#Mxy precession and relaxation, and Mz relaxation
tp = cumsum(seq.Δt) # t' = t - t0
dur = sum(seq.Δt) # Total length, used for signal relaxation
Mxy = [M.xy M.xy .* exp.(-tp' ./ p.T2) .* cis.(ϕ)] #This assumes Δw and T2 are constant in time
M.xy .= Mxy[:, end]
M.z .= M.z .* exp.(-dur ./ p.T1) .+ p.ρ .* (1 .- exp.(-dur ./ p.T1))
#Reset Spin-State (Magnetization). Only for FlowPath
outflow_spin_reset!(Mxy, seq.t', p.motion)
outflow_spin_reset!(M, seq.t', p.motion; replace_by=p.ρ)
#Acquired signal
sig .= transpose(sum(Mxy[:, findall(seq.ADC)]; dims=1)) #<--- TODO: add coil sensitivities
return nothing
end
"""
M0 = run_spin_excitation(obj, seq, M0)
It gives rise to a rotation of `M0` with an angle given by the efective magnetic field
(including B1, gradients and off resonance) and with respect to a rotation axis.
# Arguments
- `obj`: (`::Phantom`) Phantom struct (actually, it's a part of the complete phantom)
- `seq`: (`::Sequence`) Sequence struct
# Returns
- `M0`: (`::Vector{Mag}`) final state of the Mag vector after a rotation (actually, it's
a part of the complete Mag vector and it's a part of the initial state for the next
precession simulation step)
"""
function run_spin_excitation!(
p::Phantom{T},
seq::DiscreteSequence{T},
sig::AbstractArray{Complex{T}},
M::Mag{T},
sim_method::SimulationMethod,
backend::KA.Backend,
prealloc::PreallocResult
) where {T<:Real}
#Simulation
for s in seq #This iterates over seq, "s = seq[i,:]"
#Motion
x, y, z = get_spin_coords(p.motion, p.x, p.y, p.z, s.t)
#Effective field
ΔBz = p.Δw ./ T(2π .* γ) .- s.Δf ./ T(γ) # ΔB_0 = (B_0 - ω_rf/γ), Need to add a component here to model scanner's dB0(x,y,z)
Bz = (s.Gx .* x .+ s.Gy .* y .+ s.Gz .* z) .+ ΔBz
B1 = s.B1 .* p.B1 # Take B1+ transmit RF field map into account. This is complex
B = sqrt.(abs.(B1) .^ 2 .+ abs.(Bz) .^ 2)
B[B .== 0] .= eps(T)
#Spinor Rotation
φ = T(-2π .* γ) .* (B .* s.Δt) # TODO: Use trapezoidal integration here (?), this is just Forward Euler
mul!(Q(φ, B1 ./ B, Bz ./ B), M)
#Relaxation
M.xy .= M.xy .* exp.(-s.Δt ./ p.T2)
M.z .= M.z .* exp.(-s.Δt ./ p.T1) .+ p.ρ .* (1 .- exp.(-s.Δt ./ p.T1))
#Reset Spin-State (Magnetization). Only for FlowPath
outflow_spin_reset!(M, s.t, p.motion; replace_by=p.ρ)
end
#Acquired signal
#sig .= -1.4im #<-- This was to test if an ADC point was inside an RF block
return nothing
end