Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inserting diffusion-code in EPG #4

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions sequences/fisp2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ output_eltype(sequence::FISP2D) = unitless(eltype(sequence.RF_train))
sample_transverse!(magnetization, TR, Ω)
# T2 decay F states, T1 decay Z states, B0 rotation until next RF excitation
rotate_decay!(Ω, E₁ᵀᴿ⁻ᵀᴱ, E₂ᵀᴿ⁻ᵀᴱ, eⁱᴮ⁰⁽ᵀᴿ⁻ᵀᴱ⁾)
if hasD(p)
diffuse!(Ω,p.D)
end
regrowth!(Ω, E₁ᵀᴿ⁻ᵀᴱ)
# shift F states due to dephasing gradients
dephasing!(Ω)
Expand Down
3 changes: 3 additions & 0 deletions sequences/fisp3d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ output_eltype(sequence::FISP3D) = unitless(eltype(sequence.RF_train))
end
# T2 decay F states, T1 decay Z states, B0 rotation until next RF excitation
rotate_decay!(Ω, E₁ᵀᴿ⁻ᵀᴱ, E₂ᵀᴿ⁻ᵀᴱ, eⁱᴮ⁰⁽ᵀᴿ⁻ᵀᴱ⁾)
if hasD(p)
diffuse!(Ω,p.D)
end
regrowth!(Ω, E₁ᵀᴿ⁻ᵀᴱ)
# shift F states due to dephasing gradients
dephasing!(Ω)
Expand Down
46 changes: 46 additions & 0 deletions src/interfaces/tissueproperties.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Abstract type for custom structs that hold tissue properties used for a simulati
- `T₂::T`: T₂ relaxation parameters of a voxel
- `B₁::T`: Scaling factor for effective B₁ excitation field within a voxel
- `B₀::T`: Off-resonance with respect to main magnetic field within a voxel
- `D::T`: Diffusion value: the TR/TD as used in https://doi.org/10.1002/nbm.5044
(in this context it is unitless: "the amount of dispersion per TR at state=1")
- `ρˣ::T`: Real part of proton density within a voxel
- `ρʸ::T`: Imaginary part of proton density within a voxel

Expand Down Expand Up @@ -39,6 +41,25 @@ struct T₁T₂B₁{T} <: AbstractTissueProperties{3,T}
B₁::T
end

"""
T₁T₂D{T} <: AbstractTissueProperties{3,T}
"""
struct T₁T₂D{T} <: AbstractTissueProperties{3,T}
T₁::T
T₂::T
D::T
end

"""
T₁T₂B₁D{T} <: AbstractTissueProperties{4,T}
"""
struct T₁T₂B₁D{T} <: AbstractTissueProperties{4,T}
T₁::T
T₂::T
B₁::T
D::T
end

"""
T₁T₂B₀{T} <: AbstractTissueProperties{2,T}
"""
Expand All @@ -58,6 +79,17 @@ struct T₁T₂B₁B₀{T} <: AbstractTissueProperties{4,T}
B₀::T
end

"""
T₁T₂B₁B₀D{T} <: AbstractTissueProperties{5,T}
"""
struct T₁T₂B₁B₀D{T} <: AbstractTissueProperties{5,T}
T₁::T
T₂::T
B₁::T
B₀::T
D::T
end

# For each subtype of AbstractTissueProperties created above, we use meta-programming to create
# additional types that also hold proton density (ρˣ and ρʸ)
#
Expand Down Expand Up @@ -103,10 +135,12 @@ end
# Set default value to false:
hasB₁(::AbstractTissueProperties) = false
hasB₀(::AbstractTissueProperties) = false
hasD(::AbstractTissueProperties) = false

for P in subtypes(AbstractTissueProperties)
@eval hasB₁(::$(P)) = $(:B₁ ∈ fieldnames(P))
@eval hasB₀(::$(P)) = $(:B₀ ∈ fieldnames(P))
@eval hasD(::$(P)) = $(:D ∈ fieldnames(P))
end

# Programatically export all subtypes of AbstractTissueProperties
Expand All @@ -125,10 +159,16 @@ function get_nonlinear_part(p::Type{<:AbstractTissueProperties})
return T₁T₂
elseif p <: T₁T₂B₁ρˣρʸ
return T₁T₂B₁
elseif p <: T₁T₂Dρˣρʸ
return T₁T₂D
elseif p <: T₁T₂B₁Dρˣρʸ
return T₁T₂B₁D
elseif p <: T₁T₂B₀ρˣρʸ
return T₁T₂B₀
elseif p <: T₁T₂B₁B₀ρˣρʸ
return T₁T₂B₁B₀
elseif p <: T₁T₂B₁B₀Dρˣρʸ
return T₁T₂B₁B₀D
else
error("Unknown parameter type: $p")
end
Expand Down Expand Up @@ -166,14 +206,20 @@ end

# Define aliases for the tissue parameter types that do not use unicode characters such that, for example, `@parameters T1 T2 B0` is equivalent to `@parameters T₁ T₂ B₀`. This makes it easier for users of the package to generate tissue parameter arrays without having to type unicode characters.
const T1T2 = T₁T₂
const T1T2D = T₁T₂D
const T1T2B1 = T₁T₂B₁
const T1T2B1D = T₁T₂B₁D
const T1T2B0 = T₁T₂B₀
const T1T2B1B0 = T₁T₂B₁B₀
const T1T2B1B0D = T₁T₂B₁B₀D

const T1T2PDxPDy = T₁T₂ρˣρʸ
const T1T2DPDxPDy = T₁T₂Dρˣρʸ
const T1T2B1PDxPDy = T₁T₂B₁ρˣρʸ
const T1T2B1DPDxPDy = T₁T₂B₁Dρˣρʸ
const T1T2B0PDxPDy = T₁T₂B₀ρˣρʸ
const T1T2B1B0PDxPDy = T₁T₂B₁B₀ρˣρʸ
const T1T2B1B0DPDxPDy = T₁T₂B₁B₀Dρˣρʸ

# To perform simulations for multiple voxels, we store the tissue properties in a `StructArray` which we refer to as the `SimulationParameters`.
const SimulationParameters = StructArray{<:AbstractTissueProperties}
18 changes: 17 additions & 1 deletion src/operators/epg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Rotate `F₊` and `F̄₋` states under the influence of `eⁱᶿ = exp(i * ΔB
@. Ω[1:2, :] *= (eⁱᶿ, conj(eⁱᶿ))
end

# Decay
# Decay and diffuse

"""
decay!(Ω::EPGStates, E₁, E₂)
Expand All @@ -193,6 +193,22 @@ Rotate and decay combined
@. Ω *= (E₂ * eⁱᶿ, E₂ * conj(eⁱᶿ), complex(E₁))
end

"""
diffuse!(Ω::EPGStates, D)

diffusion decay according to state number.
"""
@inline function diffuse!(Ω::EPGStates, D)
for state in 0:size(Ω, 2)-1
bᵀD = ((state+0.5)^2+1.0/12.0)*D
bᴸD = (state^2)*D
expbᵀD = exp(-bᵀD)
F₊(Ω)[state] *= expbᵀD
F̄₋(Ω)[state] *= expbᵀD
Z(Ω)[state] *= exp(-bᴸD)
end
end

# Regrowth

"""
Expand Down
62 changes: 61 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,63 @@ function make_T₁T₂_structarray(nvoxels)
return @parameters T₁ T₂
end


@testset "Test diffusion code" begin

nTR = 1000
nvoxels = 1000
sequence = FISP2D(nTR)

sequence.RF_train .= complex.([30+25*sin(2π*4.0*t/nTR) for t = 1:nTR])

# simulate magnetization without diffusion
parameters = [T₁T₂ρˣρʸ(1.0, 0.1, 1.0, 0.0) for _ = 1:nvoxels] |> StructArray
d_no = simulate_magnetization(CPU1(), sequence, parameters)

# simulate magnetization with D=0
parameters = [T₁T₂Dρˣρʸ(1.0, 0.1, 0.0, 1.0, 0.0) for _ = 1:nvoxels] |> StructArray
d_zero = simulate_magnetization(CPU1(), sequence, parameters)

parameters = [T₁T₂Dρˣρʸ(1.0, 0.1, 0.001, 1.0, 0.0) for _ = 1:nvoxels] |> StructArray
d_nonzero = simulate_magnetization(CPU1(), sequence, parameters)

# test diffusion simulation to not affect result when D=0
rms_diff = sqrt(sum(abs2.(d_no - d_zero)) / length(d_no))
@test rms_diff < 1e-8

# test diffusion simulation to affect result when D>0
rms_diff = sqrt(sum(abs2.(d_zero - d_nonzero)) / length(d_no))
@test rms_diff > 1e-3

if CUDA.has_cuda_gpu()
parameters = [T₁T₂Dρˣρʸ(1.0, 0.1, 0.001, 1.0, 0.0) for _ = 1:nvoxels] |> StructArray
d_nonzero_gpu = simulate_magnetization(CUDALibs(), gpu(sequence), gpu(parameters))
rms_diff = sqrt(sum(abs2.(d_nonzero_gpu - gpu(d_nonzero))) / length(d_no))

@test rms_diff < 1e-8
end
end

@testset "Test operator functions for EPG model" begin
Ω = @MMatrix ones(ComplexF64, 3, 20)
Ω_in = copy(Ω)

# with no diffusion, diffuse!() has no effect
BlochSimulators.diffuse!(Ω, 0.0)
rms_diff = sqrt(sum(abs2.(Ω - Ω_in)) / length(Ω))
@test rms_diff < 1e-8

# with diffusion, diffuse!() reduces the states
BlochSimulators.diffuse!(Ω, 0.1)
nonzero_in = abs.(Ω_in[:, 2:end])
nonzero_out = abs.(Ω[:, 2:end])
@test all(nonzero_out .< nonzero_in)

# The effect on high states is expected to be larger than the effect on low states
effect_on_20 = sum(abs2.(Ω_in[:, 20])) - sum(abs2.(Ω[:, 20]))
effect_on_2 = sum(abs2.(Ω_in[:, 2])) - sum(abs2.(Ω[:, 2]))
@test effect_on_20 > effect_on_2
end
# test some individual functions in BlochSimulators
@testset "Test operator functions for isochromat model" begin

Expand Down Expand Up @@ -576,4 +633,7 @@ end
@test signal_cpu1 ≈ convert(Array, signal_cudalibs)
end

end
end