|
247 | 247 | end |
248 | 248 | end |
249 | 249 |
|
| 250 | +@testitem "Bloch waveform event type accuracy" tags=[:core, :nomotion] begin |
| 251 | + using OrdinaryDiffEqTsit5 |
| 252 | + include("initialize_backend.jl") |
| 253 | + include(joinpath(@__DIR__, "test_files", "utils.jl")) |
| 254 | + |
| 255 | + Tpulse = 1e-3 |
| 256 | + Tgrad = 1e-3 |
| 257 | + Tadc = 1e-3 |
| 258 | + Nadc = 6 |
| 259 | + M0 = 1.0 |
| 260 | + T1 = 1000e-3 |
| 261 | + T2 = 40e-3 |
| 262 | + Δw = 2π * 30 |
| 263 | + x0 = 1e-2 |
| 264 | + B1 = 1.5e-6 * cis(π / 7) |
| 265 | + Gx = 0.2e-3 |
| 266 | + |
| 267 | + sys = Scanner() |
| 268 | + obj = Phantom(x=[x0], ρ=[M0], T1=[T1], T2=[T2], Δw=[Δw]) |
| 269 | + |
| 270 | + grad_events = ( |
| 271 | + "trap" => Grad(Gx, Tgrad), |
| 272 | + "uniform" => Grad([Gx, Gx], Tgrad), |
| 273 | + "time-shaped" => Grad([Gx, Gx], [Tgrad]), |
| 274 | + ) |
| 275 | + rf_events = ( |
| 276 | + "block" => RF(B1, Tpulse), |
| 277 | + "uniform" => RF([B1, B1], Tpulse), |
| 278 | + "time-shaped" => RF([B1, B1], [Tpulse]), |
| 279 | + ) |
| 280 | + |
| 281 | + function waveform_sequence(grad, rf) |
| 282 | + seq = Sequence() |
| 283 | + @addblock seq += rf + (ADC(Nadc, Tadc), x=grad) |
| 284 | + return seq |
| 285 | + end |
| 286 | + |
| 287 | + function diffeq_signal(seq) |
| 288 | + B1e(t) = KomaMRIBase.get_rfs(seq, [t])[1][1] |
| 289 | + Gxe(t) = KomaMRIBase.get_grads(seq, [t])[1][1] |
| 290 | + function bloch!(dm, m, p, t) |
| 291 | + mx, my, mz = m |
| 292 | + B1t = B1e(t) |
| 293 | + bx, by, bz = real(B1t), imag(B1t), x0 * Gxe(t) + Δw / (2π * γ) |
| 294 | + dm[1] = -mx / T2 + 2π * γ * bz * my - 2π * γ * by * mz |
| 295 | + dm[2] = -2π * γ * bz * mx - my / T2 + 2π * γ * bx * mz |
| 296 | + dm[3] = 2π * γ * by * mx - 2π * γ * bx * my - mz / T1 + M0 / T1 |
| 297 | + return nothing |
| 298 | + end |
| 299 | + sol = solve( |
| 300 | + ODEProblem(bloch!, [0.0, 0.0, M0], (0.0, dur(seq))), |
| 301 | + Tsit5(); |
| 302 | + saveat=get_adc_sampling_times(seq), |
| 303 | + tstops=[Tpulse, Tpulse + Tgrad], |
| 304 | + dtmax=1e-6, |
| 305 | + abstol=1e-9, |
| 306 | + reltol=1e-9, |
| 307 | + ) |
| 308 | + sol_diffeq = hcat(sol.u...)' |
| 309 | + return sol_diffeq[:, 1] + im * sol_diffeq[:, 2] |
| 310 | + end |
| 311 | + |
| 312 | + ref_seq = waveform_sequence(grad_events[1][2], rf_events[1][2]) |
| 313 | + ref_adc_times = get_adc_sampling_times(ref_seq) |
| 314 | + mxy_diffeq = diffeq_signal(ref_seq) |
| 315 | + |
| 316 | + for (grad_name, grad) in grad_events, (rf_name, rf) in rf_events |
| 317 | + seq = waveform_sequence(grad, rf) |
| 318 | + @test get_adc_sampling_times(seq) ≈ ref_adc_times |
| 319 | + for sim_method in (Bloch(), BlochMagnus1(), BlochMagnus2(), BlochMagnus4()) |
| 320 | + @testset "$grad_name/$rf_name $(nameof(typeof(sim_method)))" begin |
| 321 | + sim_params = Dict{String, Any}( |
| 322 | + "gpu" => USE_GPU, |
| 323 | + "return_type" => "mat", |
| 324 | + "sim_method" => sim_method, |
| 325 | + "Δt" => 1e-5, |
| 326 | + "Δt_rf" => 1e-5, |
| 327 | + ) |
| 328 | + raw = simulate(obj, seq, sys; sim_params, verbose=false)[:, 1, 1] |
| 329 | + @test NRMSE(raw, mxy_diffeq) < 0.1 |
| 330 | + end |
| 331 | + end |
| 332 | + end |
| 333 | +end |
| 334 | + |
250 | 335 | @testitem "Bloch_RF_accuracy" tags=[:important, :core, :nomotion] begin |
251 | 336 | using OrdinaryDiffEqTsit5 |
252 | 337 | include("initialize_backend.jl") |
|
0 commit comments