Global fit

First, we load the required packages and include some Helper functions

include("helper_functions.jl")

load all T₁-mapping methods:

include("T1_mapping_methods.jl")

and initialize the plot:

p = plot([0, 2], [0, 2], xlabel="simulated T1 (s)", ylabel="literature T1 (s)", legend=:topleft, label=:none, xlim=(0.5, 1.1), ylim=(0.5, 1.1))
marker_list = [(seq_type_i == :IR) ? (:circle) : ((seq_type_i == :LL) ? (:cross) : ((seq_type_i == :vFA) ? (:diamond) : ((seq_type_i == :SR) ? (:dtriangle) : (:x)))) for seq_type_i in seq_type]

Mono-exponential model

We simulate the mono-exponential model as an MT model with a vanishing semi-solid spin pool. In this case, the underlying MT model is irrelevant and we choose Graham's model for speed purposes:

MT_model = Graham()

The following parameters are hard-coded with the exception of R1f, which serves as an initialization for the global fit.

m0s = 0
R1f = 1 / 1.084  # 1/s
R1s = R1f        # 1/s
R2f = 1 / 0.0769 # 1/s
T2s = 0
Rx = 0           # 1/s
ω0 = 0
B1 = 1

We define a model for the global fit. It takes the global set of parameters p as an input and returns a vector of T₁ estimates that correspond to the T₁ mapping methods described by the vector T1_functions

function model(iseq, p)
    R1f = p[1]
    T1 = similar(iseq, Float64)
    Threads.@threads for i in eachindex(iseq)
        T1[i] = T1_functions[iseq[i]](m0s, R1f, R2f, Rx, R1s, T2s)
    end
    return T1
end

Perform the fit and calculate the simulated T₁:

fit_mono = curve_fit(model, 1:length(T1_literature), T1_literature, [R1f], x_tol=1e-3)
T1_simulated = model(1:length(T1_literature), fit_mono.param)

For this model, the single fitted global of parameter is:

T1f = 1 / fit_mono.param[1] # s
0.8894539621298889

The following plot visualizes the quality of the fit an replicates Fig. 1 in the manuscript:

scatter!(p, T1_simulated, T1_literature, label="mono-exponential model", markershape=marker_list, hover=seq_name)

Akaike (AIC) and Bayesian (BIC) information criteria

The information criteria depend on the number of measurements:

n = length(T1_literature)
25

the number of parameters:

k = length(fit_mono.param)
1

and the squared sum of the residuals:

RSS = norm(fit_mono.resid)^2
0.361652277711102

As the AIC and BIC are only informative in the difference between two models, we use the mono-exponential model as the reference:

AIC_mono = n * log(RSS / n) + 2k
-103.89869781164826
BIC_mono = n * log(RSS / n) + k * log(n)
-102.67982198678006

In this case, ΔAIC is per definition zero:

ΔAIC = n * log(RSS / n) + 2k - AIC_mono
0.0

as is ΔBIC:

ΔBIC = n * log(RSS / n) + k * log(n) - BIC_mono
0.0

Graham's MT model (unconstrained R₁ˢ)

MT_model = Graham()

The following parameters are hard-coded with the exception of m0s, R1f, and R1s, which serve as an initialization for the global fit.

m0s = 0.25
R1f = 1 / 1.84   # 1/s
R1s = 1 / 0.34   # 1/s
R2f = 1 / 0.0769 # 1/s
T2s = 12.5e-6    # s
Rx = 13.6        # 1/s
ω0 = 0
B1 = 1

We define a model for the global fit. It takes the global set of parameters p as an input and returns a vector of T₁ estimates that correspond to the T₁ mapping methods described by the vector T1_functions

function model(iseq, p)
    m0s, R1f, R1s = p
    T1 = similar(iseq, Float64)
    Threads.@threads for i in eachindex(iseq)
        T1[i] = T1_functions[iseq[i]](m0s, R1f, R2f, Rx, R1s, T2s)
    end
    return T1
end

Perform the fit and calculate the simulated T₁:

fit_Graham = curve_fit(model, 1:length(T1_literature), T1_literature, [m0s, R1f, R1s], x_tol=1e-3)
T1_simulated = model(1:length(T1_literature), fit_Graham.param)

For this model, the fitted global set of parameters is:

m0s = fit_Graham.param[1]
0.19436834193025312
T1f = 1 / fit_Graham.param[2] # s
2.0217349657401127
T1s = 1 / fit_Graham.param[3] # s
0.24569338339800717

The following plot visualizes the quality of the fit an replicates Fig. 1 in the manuscript:

scatter!(p, T1_simulated, T1_literature, label="Graham's model (unconstrained R₁ˢ)", markershape=marker_list, hover=seq_name)

Note that clicking on a legend entry removes the fit from the plot. A double click on an entry selects only this particular fit.

Akaike (AIC) and Bayesian (BIC) information criteria

The information criteria depend on the number of measurements n, the number of parameters k, and the squared sum of the residuals RSS:

n = length(T1_literature)
25
k = length(fit_Graham.param)
3
RSS = norm(fit_Graham.resid)^2
0.14656319226751763

With this information, we can calculate the AIC difference to the mono-exponential model:

ΔAIC = n * log(RSS / n) + 2k - AIC_mono
-18.580662734862543

and similarly for the BIC:

ΔBIC = n * log(RSS / n) + k * log(n) - BIC_mono
-16.142911085126144

Sled's MT model (unconstrained R₁ˢ)

MT_model = Sled()

The following parameters are hard-coded with the exception of m0s, R1f, and R1s, which serve as an initialization for the global fit.

m0s = 0.25
R1f = 1 / 1.84   # 1/s
R1s = 1 / 0.34   # 1/s
R2f = 1 / 0.0769 # 1/s
T2s = 12.5e-6    # s
Rx = 13.6        # 1/s
ω0 = 0
B1 = 1

We define a model for the global fit. It takes the global set of parameters p as an input and returns a vector of T₁ estimates that correspond to the T₁ mapping methods described by the vector T1_functions

function model(iseq, p)
    m0s, R1f, R1s = p
    T1 = similar(iseq, Float64)
    Threads.@threads for i in eachindex(iseq)
        T1[i] = T1_functions[iseq[i]](m0s, R1f, R2f, Rx, R1s, T2s)
    end
    return T1
end

Perform the fit and calculate the simulated T₁:

fit_Sled = curve_fit(model, 1:length(T1_literature), T1_literature, [m0s, R1f, R1s], x_tol=1e-3)
T1_simulated = model(1:length(T1_literature), fit_Sled.param)

For this model, the fitted global set of parameters is:

m0s = fit_Sled.param[1]
0.09937562692628771
T1f = 1 / fit_Sled.param[2] # s
1.70480391856932
T1s = 1 / fit_Sled.param[3] # s
0.11763471088913305

The following plot visualizes the quality of the fit an replicates Fig. 1 in the manuscript:

scatter!(p, T1_simulated, T1_literature, label="Sled's model (unconstrained R₁ˢ)", markershape=marker_list, hover=seq_name)

Note that clicking on a legend entry removes the fit from the plot. A double click on an entry selects only this particular fit.

Akaike (AIC) and Bayesian (BIC) information criteria

The information criteria depend on the number of measurements n, the number of parameters k, and the squared sum of the residuals RSS:

n = length(T1_literature)
25
k = length(fit_Sled.param)
3
RSS = norm(fit_Sled.resid)^2
0.13996537913499815

With this information, we can calculate the AIC difference to the mono-exponential model:

ΔAIC = n * log(RSS / n) + 2k - AIC_mono
-19.732202281241385

and similarly for the BIC:

ΔBIC = n * log(RSS / n) + k * log(n) - BIC_mono
-17.294450631504986

Generalized Bloch model (R₁ˢ = R₁ᶠ)

MT_model = gBloch()

The following parameters are hard-coded with the exception of m0s, and R1f, which serve as an initialization for the global fit.

m0s = 0.139
R1f = 1 / 1.084  # 1/s
R1s = 1          # 1/s
R2f = 1 / 0.0769 # 1/s
T2s = 12.5e-6    # s
Rx = 23          # 1/s
ω0 = 0
B1 = 1

We define a model for the global fit. It takes the global set of parameters p as an input and returns a vector of T₁ estimates that correspond to the T₁ mapping methods described by the vector T1_functions

function model(iseq, p)
    m0s, R1f = p
    R1s = R1f
    T1 = similar(iseq, Float64)
    Threads.@threads for i in eachindex(iseq)
        T1[i] = T1_functions[iseq[i]](m0s, R1f, R2f, Rx, R1s, T2s)
    end
    return T1
end

Perform the fit and calculate the simulated T₁:

fit_constr = curve_fit(model, 1:length(T1_literature), T1_literature, [m0s, R1f], x_tol=1e-3)
T1_simulated = model(1:length(T1_literature), fit_constr.param)

For this model, the fitted global set of parameters is:

m0s = fit_constr.param[1]
0.12881834367305509
T1f = 1 / fit_constr.param[2] # s
0.9742293629275287

The following plot visualizes the quality of the fit an replicates Fig. 1 in the manuscript:

scatter!(p, T1_simulated, T1_literature, label="generalized Bloch model (R₁ˢ = R₁ᶠ constraint)", markershape=marker_list, hover=seq_name)

Note that clicking on a legend entry removes the fit from the plot. A double click on an entry selects only this particular fit.

Akaike (AIC) and Bayesian (BIC) information criteria

The information criteria depend on the number of measurements n, the number of parameters k, and the squared sum of the residuals RSS:

n = length(T1_literature)
25
k = length(fit_constr.param)
2
RSS = norm(fit_constr.resid)^2
0.17054658200500056

With this information, we can calculate the AIC difference to the mono-exponential model:

ΔAIC = n * log(RSS / n) + 2k - AIC_mono
-16.791868093373466

and similarly for the BIC:

ΔBIC = n * log(RSS / n) + k * log(n) - BIC_mono
-15.572992268505274

Generalized Bloch model (unconstrained R₁ˢ)

MT_model = gBloch()

The following parameters are hard-coded with the exception of m0s, R1f, and R1s, which serve as an initialization for the global fit.

m0s = 0.25
R1f = 1 / 1.84   # 1/s
R1s = 1 / 0.34   # 1/s
R2f = 1 / 0.0769 # 1/s
T2s = 12.5e-6    # s
Rx = 13.6        # 1/s
ω0 = 0
B1 = 1

We define a model for the global fit. It takes the global set of parameters p as an input and returns a vector of T₁ estimates that correspond to the T₁ mapping methods described by the vector T1_functions

function model(iseq, p)
    m0s, R1f, R1s = p
    T1 = similar(iseq, Float64)
    Threads.@threads for i in eachindex(iseq)
        T1[i] = T1_functions[iseq[i]](m0s, R1f, R2f, Rx, R1s, T2s)
    end
    return T1
end

Perform the fit and calculate the simulated T₁:

fit_uncon = curve_fit(model, 1:length(T1_literature), T1_literature, [m0s, R1f, R1s], x_tol=1e-3)
T1_simulated = model(1:length(T1_literature), fit_uncon.param)

For this model, the fitted global set of parameters is:

m0s = fit_uncon.param[1]
0.20903499148698299
T1f = 1 / fit_uncon.param[2] # s
2.0632291957979128
T1s = 1 / fit_uncon.param[3] # s
0.26253379951628936

The following plot visualizes the quality of the fit an replicates Fig. 1 in the manuscript:

scatter!(p, T1_simulated, T1_literature, label="generalized Bloch model (unconstrained R₁ˢ)", markershape=marker_list, hover=seq_name)

Note that clicking on a legend entry removes the fit from the plot. A double click on an entry selects only this particular fit.

Akaike (AIC) and Bayesian (BIC) information criteria

The information criteria depend on the number of measurements n, the number of parameters k, and the squared sum of the residuals RSS:

n = length(T1_literature)
25
k = length(fit_uncon.param)
3
RSS = norm(fit_uncon.resid)^2
0.11479629852721095

With this information, we can calculate the AIC difference to the mono-exponential model:

ΔAIC = n * log(RSS / n) + 2k - AIC_mono
-24.688098770327016

and similarly for the BIC:

ΔBIC = n * log(RSS / n) + k * log(n) - BIC_mono
-22.250347120590618

Data analysis

The span of T₁ estimates in the literature is

extrema(T1_literature)
(0.64, 1.0855)

and the coefficient of variation is

variation(T1_literature)
0.1408807728048427

Median absolute deviation

In the paper, the median absolute deviation from the mean value is used, as it is more robust to outliers compared to the mean absolute deviation or the standard deviation. Note, however, that the median absolute deviation of the mono-exponential fit is dominated by an outlier, artificially inflating the corresponding reduction.

A mono-exponential model explains the following fraction of the T₁ variability in the literature:

1 - mad(fit_mono.resid; center=mean(fit_mono.resid)) / mad(T1_literature; center=mean(T1_literature))
0.07118939004058966

Graham's spectral model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mad(fit_Graham.resid; center=mean(fit_Graham.resid)) / mad(T1_literature; center=mean(T1_literature))
0.6081039577630925

Sled's model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mad(fit_Sled.resid; center=mean(fit_Sled.resid)) / mad(T1_literature; center=mean(T1_literature))
0.5566257805848849

The generalized Bloch model constrained by R₁ˢ = R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mad(fit_constr.resid; center=mean(fit_constr.resid)) / mad(T1_literature; center=mean(T1_literature))
0.46842014634227236

The generalized Bloch model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mad(fit_uncon.resid; center=mean(fit_uncon.resid)) / mad(T1_literature; center=mean(T1_literature))
0.6171458040051151

Mean absolute deviation

For comparison, the mean absolute deviation is analyzed: A mono-exponential model explains the following fraction of the T₁ variability in the literature:

1 - mean(abs.(fit_mono.resid .- mean(fit_mono.resid))) / mean(abs.(T1_literature .- mean(T1_literature)))
0.02342645296916457

Graham's spectral model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mean(abs.(fit_Graham.resid .- mean(fit_Graham.resid))) / mean(abs.(T1_literature .- mean(T1_literature)))
0.4835563907183299

Sled's model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mean(abs.(fit_Sled.resid .- mean(fit_Sled.resid))) / mean(abs.(T1_literature .- mean(T1_literature)))
0.4826962444141223

The generalized Bloch model constrained by R₁ˢ = R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mean(abs.(fit_constr.resid .- mean(fit_constr.resid))) / mean(abs.(T1_literature .- mean(T1_literature)))
0.42795925010299074

The generalized Bloch model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - mean(abs.(fit_uncon.resid .- mean(fit_uncon.resid))) / mean(abs.(T1_literature .- mean(T1_literature)))
0.526153617724735

Standard deviation

For comparison, the standard deviation is analyzed: A mono-exponential model explains the following fraction of the T₁ variability in the literature:

1 - std(fit_mono.resid) / std(T1_literature)
0.01826096820611134

Graham's spectral model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - std(fit_Graham.resid) / std(T1_literature)
0.3750273359460128

Sled's spectral model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - std(fit_Sled.resid) / std(T1_literature)
0.3892489555712527

The generalized Bloch model constrained by R₁ˢ = R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - std(fit_constr.resid) / std(T1_literature)
0.3258711248440125

The generalized Bloch model without constraints on R₁ᶠ explains the following fraction of the T₁ variability in the literature:

1 - std(fit_uncon.resid) / std(T1_literature)
0.4468849197416881