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

Incorrect gradient interpretation when waveforms do not end in zero #321

Merged
merged 62 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
cd844bc
Avoid abrupt changes between block gradients
beorostica Mar 11, 2024
c0d5589
Remove delay for RF when no RF is present during pulseq reading
beorostica Mar 11, 2024
ce9e97a
Add condition for no RF or ADC when reading pulseq file
beorostica Mar 12, 2024
a4b1d1e
Temporal read tests: comparison between koma and pulseq
beorostica Mar 13, 2024
929bf5e
Move folder for temporal read tests
beorostica Mar 13, 2024
cf572ca
Temporal updates for reading pulseq files
beorostica Mar 14, 2024
aefa3e0
Fix bug when running new read comparison tests
beorostica Mar 15, 2024
ba56717
Sort tests when comparing pulseq and koma readings
beorostica Mar 15, 2024
17899c1
Merge branch 'master' into zero-concat
beorostica Mar 26, 2024
7ed7c4b
Temporal local changes: add first last pulseq points for grads
beorostica Mar 26, 2024
c95e309
Add logic to consider first and last points for gradients
beorostica Mar 26, 2024
83da9a5
Add temporal time_shape_id property to Grad struct for debuging
beorostica Mar 27, 2024
f2e3b05
Merge branch 'master' into zero-concat
cncastillo Mar 27, 2024
ddc6d93
Remove temporal time_shape_id property from Grad struct
beorostica Mar 27, 2024
d3cc103
Add temporal test for comparing read RF
beorostica Mar 28, 2024
591130c
Merge branch 'master' into zero-concat
cncastillo Mar 28, 2024
84043cf
Add tests for spiral GRE and EPI when reading pulseq
beorostica Apr 1, 2024
5ed4362
Add some changes for first/last points in gradients
beorostica Apr 3, 2024
ed9def0
Merge branch 'zero-concat' of github.com:cncastillo/KomaMRI.jl into z…
beorostica Apr 3, 2024
8d680e4
Update KomaMRIFiles/src/Sequence/Pulseq.jl
beorostica Apr 3, 2024
aefbc9a
Update KomaMRIFiles/src/Sequence/Pulseq.jl
beorostica Apr 3, 2024
5baa0a0
Remove dummy conditional for pulseq read_ADC
beorostica Apr 3, 2024
d96120e
Merge branch 'zero-concat' of github.com:cncastillo/KomaMRI.jl into z…
beorostica Apr 3, 2024
435d447
Update KomaMRIFiles/src/Sequence/Pulseq.jl
beorostica Apr 3, 2024
e8d4d85
Add some tabs to Grad struct
beorostica Apr 3, 2024
1a989f5
Create `fix_first_last_grads!` function
beorostica Apr 3, 2024
5c97fcb
Merge branch 'master' into zero-concat
cncastillo Apr 3, 2024
8dfb9d2
Change RF interpretation from ZOH -> linear-interpolation and read th…
beorostica Apr 4, 2024
b55c84f
Add `if: !cancelled()` to CI
beorostica Apr 4, 2024
e437fca
Use @testitem for pulseq reading comparison
beorostica Apr 4, 2024
c9b6556
Proper RF Δf handling
cncastillo Apr 4, 2024
0a6c3f3
Fixed Δf in get_theo_A
cncastillo Apr 4, 2024
c98fb51
Removed RF phase bug (after ZOH->Lin)
cncastillo Apr 4, 2024
8819526
Plots now consider Δf
cncastillo Apr 4, 2024
44c8e31
Fixes eps causing incorrect block categorization.
cncastillo Apr 4, 2024
516b909
Removed unecessary functions
cncastillo Apr 4, 2024
ed884c9
Removed print in test
cncastillo Apr 4, 2024
8d6f08f
Added missing @suppress in tests
cncastillo Apr 4, 2024
a7fab0f
Temporal changes for testing pulseq first/last
beorostica Apr 5, 2024
1a3b546
More temporal changes for testing pulseq first/las
beorostica Apr 5, 2024
6a099dc
More x2 Temporal changes for testing pulseq first/last
beorostica Apr 5, 2024
c9677ee
KomaMRIBase: Export is_X_on
cncastillo Apr 7, 2024
d0448a7
KomaMRIBase: Changed get_samples and constructors for type-stability
cncastillo Apr 7, 2024
05cfda6
Renames get_tho to time and ampl, improved type stability
cncastillo Apr 7, 2024
c505bb2
More efficient Seq constructor, and fix #203
cncastillo Apr 7, 2024
8c5f7fe
Delay now creates Seq with seq.DUR=T
cncastillo Apr 7, 2024
8fc52a5
RF flip angle now calculated using ampl, time, trapz
cncastillo Apr 7, 2024
47dea75
`trapz` now works with vectors
cncastillo Apr 7, 2024
a3cea4f
KomaBase test changes
cncastillo Apr 7, 2024
940b828
KomaCore test do not use global variables anymore
cncastillo Apr 7, 2024
0c10aaa
KomaFiles Removed utils.jl
cncastillo Apr 7, 2024
6176d03
KomaFiles Pulseq read optimization and type stability #224
cncastillo Apr 7, 2024
2b53c89
KomaFiles Improved Pulseq compat tests
cncastillo Apr 7, 2024
682af57
Fixed #203
cncastillo Apr 7, 2024
1d589b9
KomaPlots Improved performance of plot_seq
cncastillo Apr 7, 2024
26b8d50
KomaMRI: Fixed seq export to mat
cncastillo Apr 7, 2024
d1573c2
KomaMRI/Plots performance improvements plots #364
cncastillo Apr 8, 2024
a0ab60a
Fixed DUR for older Pulseq versions
cncastillo Apr 8, 2024
18ce3ae
Improved show_seq_blocks
cncastillo Apr 8, 2024
4e0c670
KomaBase export time ampl freq
cncastillo Apr 8, 2024
0bba7c5
KomaPlots give option to include RF freq inside phase (integration)
cncastillo Apr 8, 2024
303e78c
Minor changes in Pulseq.jl [skip ci]
cncastillo Apr 8, 2024
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 KomaMRIBase/src/datatypes/sequence/Grad.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ mutable struct Grad
rise::Real
fall::Real
delay::Real
first
last
beorostica marked this conversation as resolved.
Show resolved Hide resolved
time_shape_id
beorostica marked this conversation as resolved.
Show resolved Hide resolved
function Grad(A, T, rise, fall, delay)
all(T .< 0) || rise < 0 || fall < 0 || delay < 0 ? error("Gradient timings must be positive.") : new(A, T, rise, fall, delay)
end
Expand Down
8 changes: 8 additions & 0 deletions KomaMRIBase/src/timing/KeyValuesCalculation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ get_theo_A(g::Grad; off_val=0) = begin
else
aux = [off_val; 0; A; 0]
end
# To remove abrut changes in the plots
if g.rise == 0
aux[2] = 0 # zero
end
if g.fall == 0
aux[end] = aux[end-1] # keep the last value
end
# If no signal, then don't show samples
beorostica marked this conversation as resolved.
Show resolved Hide resolved
if sum(abs.(g.A)) == 0
aux *= off_val
end
Expand Down
58 changes: 55 additions & 3 deletions KomaMRIFiles/src/Sequence/Pulseq.jl
Original file line number Diff line number Diff line change
Expand Up @@ -434,9 +434,39 @@ function read_seq(filename)
for i = 1:length(blockEvents)
seq += get_block(obj, i)
end
# Final details
# Remove dummy seq block at the start, Issue #203
seq = seq[2:end]

# Add first and last Pulseq points
for gi in 1:3
grad_prev_last = 0
for bi in 1:length(seq)
gr = seq.GR[gi, bi]
if sum(abs.(gr.A)) == 0 # this is for no-gradient case
beorostica marked this conversation as resolved.
Show resolved Hide resolved
grad_prev_last = 0
else
if length(gr.A) > 1 # this is for the uniformly-shaped or time-shaped case
if gr.delay > 0
grad_prev_last = 0
end
seq.GR[gi, bi].first = grad_prev_last
if length(gr.T) > 1 || (isdefined(gr, :time_shape_id) && gr.time_shape_id != 0) # this is for time-shaped case (I assume it is the extended trapezoid case)
seq.GR[gi, bi].last = gr.A[end] #I need to check this or [end-1]
else
odd_step1 = [seq.GR[gi, bi].first; 2 * gr.A]
odd_step2 = odd_step1 .* (mod.(1:length(odd_step1), 2) * 2 .- 1)
waveform_odd_rest = cumsum(odd_step2) .* (mod.(1:length(odd_step2), 2) * 2 .- 1)
seq.GR[gi, bi].last = waveform_odd_rest[end]
end
grad_prev_last = seq.GR[gi, bi].last
else # this is for the trapedoid case
grad_prev_last = 0
end
end
end
end

# Final details
# Hack for including extension and triggers
seq.DEF["additional_text"] = read_Extension(extensionLibrary, triggerLibrary) #Temporary hack
seq.DEF = KomaMRIBase.recursive_merge(obj["definitions"], seq.DEF)
Expand All @@ -458,6 +488,8 @@ function read_seq(filename)
return seq
end



beorostica marked this conversation as resolved.
Show resolved Hide resolved
#To Sequence
"""
grad = read_Grad(gradLibrary, shapeLibrary, Δt_gr, i)
Expand Down Expand Up @@ -492,16 +524,24 @@ function read_Grad(gradLibrary, shapeLibrary, Δt_gr, i)
delay = g[4]
#Amplitude
gA = amplitude * decompress_shape(shapeLibrary[amp_shape_id]...)
if length(gA) == 1
gA = gA[1]
end
Nrf = length(gA) - 1
#Creating timings
if time_shape_id == 0 #no time waveform
gT = Nrf * Δt_gr
G = Grad(gA, gT, Δt_gr/2, Δt_gr/2, delay)
beorostica marked this conversation as resolved.
Show resolved Hide resolved
#G = Grad(gA, gT, 0, 0, delay)
beorostica marked this conversation as resolved.
Show resolved Hide resolved
else
gt = decompress_shape(shapeLibrary[time_shape_id]...)
gT = (gt[2:end] .- gt[1:end-1]) * Δt_gr
if length(gT) == 1
gT = gT[1]
end
G = Grad(gA,gT,0,0,delay)
end
G.time_shape_id = time_shape_id
end
G
end
Expand All @@ -521,6 +561,11 @@ Reads the RF. It is used internally by [`get_block`](@ref).
- `rf`: (`1x1 ::Matrix{RF}`) RF struct
"""
function read_RF(rfLibrary, shapeLibrary, Δt_rf, i)

if isempty(rfLibrary) || i==0
return reshape([RF(0,0)], 1, 1)
end

#Unpacking
#(1)amplitude (2)mag_id (3)phase_id (4)time_shape_id (5)delay (6)freq (7)phase
r = rfLibrary[i]["data"]
Expand All @@ -533,8 +578,8 @@ function read_RF(rfLibrary, shapeLibrary, Δt_rf, i)
phase = r[7]
#Amplitude and phase waveforms
if amplitude != 0 && mag_id != 0
rfA = decompress_shape(shapeLibrary[mag_id]...)[1:end-1]
rfϕ = decompress_shape(shapeLibrary[phase_id]...)[1:end-1]
rfA = decompress_shape(shapeLibrary[mag_id]...)#[1:end-1] # Temporal change for reading all samples from .seq
rfϕ = decompress_shape(shapeLibrary[phase_id]...)#[1:end-1] # Temporal change for reading all samples from .seq
cncastillo marked this conversation as resolved.
Show resolved Hide resolved
@assert all(rfϕ.>=0) "[RF id $i] Phase waveform rfϕ must have non-negative samples (1.>=rfϕ.>=0). "
Nrf = shapeLibrary[mag_id][1] - 1
rfAϕ = amplitude .* rfA .* exp.(1im*(2π*rfϕ .+ phase))
Expand All @@ -550,6 +595,7 @@ function read_RF(rfLibrary, shapeLibrary, Δt_rf, i)
else
rft = decompress_shape(shapeLibrary[time_shape_id]...)
rfT = (rft[2:end] .- rft[1:end-1]) * Δt_rf
rfAϕ = rfAϕ[1:length(rfT)] # Temporal change for RF-Pulses when reading all samples from .seq
cncastillo marked this conversation as resolved.
Show resolved Hide resolved
end
R = reshape([RF(rfAϕ,rfT,freq,delay)],1,1)#[RF(rfAϕ,rfT,freq,delay);;]
R
Expand All @@ -568,8 +614,14 @@ Reads the ADC. It is used internally by [`get_block`](@ref).
- `adc`: (`1x1 ::Vector{ADC}`) ADC struct
"""
function read_ADC(adcLibrary, i)

if isempty(adcLibrary) || i==0
return [ADC(0, 0)]
end
beorostica marked this conversation as resolved.
Show resolved Hide resolved

#Unpacking
#(1)num (2)dwell (3)delay (4)freq (5)phase
# It is needed a review for this, the dwell definition may cause problems
beorostica marked this conversation as resolved.
Show resolved Hide resolved
if !isempty(adcLibrary) # Is this the best? maybe defining i=0 is better, it works with RFs(?)
a = adcLibrary[i]["data"]
else
beorostica marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
3 changes: 3 additions & 0 deletions KomaMRIFiles/test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[deps]
KomaMRIBase = "d0bc0b20-b151-4d03-b2a4-6ca51751cb9c"
MAT = "23992714-dd62-5051-b70f-ba57cb901cac"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a"
Expand Down
189 changes: 189 additions & 0 deletions KomaMRIFiles/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,192 @@ using TestItems, TestItemRunner
@test obj.name == "brain_mrilab.mat"
end
end

@testitem "Pulseq_Read_Comparison" tags=[:files] begin
using KomaMRIBase, MAT, PrettyTables
TOLERANCE = 1e-6

# Auxiliar functions
function get_theo_t_aux(rf::RF)
Namp, Ntim = length(rf.A), length(rf.T)
if Namp == 1 && Ntim == 1
return KomaMRIBase.get_theo_t(rf; max_rf_samples=Inf)[2:end]
elseif Namp > 1 && Ntim == 1
amps = KomaMRIBase.get_theo_t(rf; max_rf_samples=Inf)[2:end]
return [amps[1]; amps[2:end-1][[i for i in 1:length(amps)-1 if i % 2 == 0]]; amps[end]]
end
return []#KomaMRIBase.get_theo_t(rf; max_rf_samples=Inf)[2:end]
end
function get_theo_A_aux(rf::RF)
Namp, Ntim = length(rf.A), length(rf.T)
if Namp == 1 && Ntim == 1
return γ*KomaMRIBase.get_theo_A(rf; off_val=NaN, max_rf_samples=Inf)[2:end]
elseif Namp > 1 && Ntim == 1
amps = γ*KomaMRIBase.get_theo_A(rf; off_val=NaN, max_rf_samples=Inf)[2:end]
return [amps[1]; amps[2:end-1][[i for i in 1:length(amps)-1 if i % 2 == 0]]; amps[end]]
end
return []#γ*KomaMRIBase.get_theo_A(rf; off_val=NaN, max_rf_samples=Inf)[2:end]
end
function get_theo_t_aux(gr::Grad)
Namp, Ntim = length(gr.A), length(gr.T)
if Namp == 1 && Ntim == 1
return KomaMRIBase.get_theo_t(gr)
elseif Namp > 1 && Ntim == 1
return KomaMRIBase.get_theo_t(gr)[2:end]
elseif Namp > 1 && Ntim > 1
return KomaMRIBase.get_theo_t(gr)[2:end]
end
return []
end
function get_theo_A_aux(gr::Grad)
Namp, Ntim = length(gr.A), length(gr.T)
if Namp == 1 && Ntim == 1
return 1e-3*γ*KomaMRIBase.get_theo_A(gr; off_val=0)
elseif Namp > 1 && Ntim == 1
return 1e-3*γ*[gr.first; KomaMRIBase.get_theo_A(gr; off_val=0)[3:end-1]; gr.last]
elseif Namp > 1 && Ntim > 1
return 1e-3*γ*KomaMRIBase.get_theo_A(gr; off_val=0)[2:end]
end
return []
end
function get_theo_t_aux(adc::ADC)
return (adc.N == 1) ? [adc.T/2] .+ adc.delay : [range(0, adc.T; length=adc.N)...] .+ adc.delay
cncastillo marked this conversation as resolved.
Show resolved Hide resolved
end

function koma_read(filename)
path = @__DIR__
seq = read_seq("$(path)/test_files/pulseq_read_comparison/$(filename).seq")
N = length(seq)
T0 = KomaMRIBase.get_block_start_times(seq)
return Dict(
"rf" => Dict(
"blocks" => [i for i in 1:N if KomaMRIBase.is_RF_on(seq[i])],
"times" => [get_theo_t_aux(seq[i].RF[1]) .+ T0[i] for i in 1:N if KomaMRIBase.is_RF_on(seq[i])],
"amplitudes" => [get_theo_A_aux(seq[i].RF[1]) for i in 1:N if KomaMRIBase.is_RF_on(seq[i])]
),
"gx" => Dict(
"blocks" => [i for i in 1:N if KomaMRIBase.is_Gx_on(seq[i])],
"times" => [get_theo_t_aux(seq.GR[1,i]) .+ T0[i] for i in 1:N if KomaMRIBase.is_Gx_on(seq[i])],
"amplitudes" => [get_theo_A_aux(seq.GR[1,i]) for i in 1:N if KomaMRIBase.is_Gx_on(seq[i])]
),
"gy" => Dict(
"blocks" => [i for i in 1:N if KomaMRIBase.is_Gy_on(seq[i])],
"times" => [get_theo_t_aux(seq.GR[2,i]) .+ T0[i] for i in 1:N if KomaMRIBase.is_Gy_on(seq[i])],
"amplitudes" => [get_theo_A_aux(seq.GR[2,i]) for i in 1:N if KomaMRIBase.is_Gy_on(seq[i])]
),
"gz" => Dict(
"blocks" => [i for i in 1:N if KomaMRIBase.is_Gz_on(seq[i])],
"times" => [get_theo_t_aux(seq.GR[3,i]) .+ T0[i] for i in 1:N if KomaMRIBase.is_Gz_on(seq[i])],
"amplitudes" => [get_theo_A_aux(seq.GR[3,i]) for i in 1:N if KomaMRIBase.is_Gz_on(seq[i])]
),
"adc" => Dict(
"blocks" => [i for i in 1:N if KomaMRIBase.is_ADC_on(seq[i])],
"times" => [get_theo_t_aux(seq.ADC[i]) .+ T0[i] for i in 1:N if KomaMRIBase.is_ADC_on(seq[i])],
)
)
end

function pulseq_read(filename)
path = @__DIR__
seq_pulseq = matread("$(path)/test_files/pulseq_read_comparison/$(filename).mat")
return Dict(
"rf" => Dict(
"blocks" => Int.(vec(seq_pulseq["rfBlocks"])),
"times" => vec([vec(blockRfTimes) for blockRfTimes in seq_pulseq["rfTimes"]]),
"amplitudes" => vec([vec(blockRfAmplitudes) for blockRfAmplitudes in seq_pulseq["rfAmplitudes"]])
),
"gx" => Dict(
"blocks" => Int.(vec(seq_pulseq["gxBlocks"])),
"times" => vec([vec(blockGxTimes) for blockGxTimes in seq_pulseq["gxTimes"]]),
"amplitudes" => vec([vec(blockGxAmplitudes) for blockGxAmplitudes in seq_pulseq["gxAmplitudes"]])
),
"gy" => Dict(
"blocks" => Int.(vec(seq_pulseq["gyBlocks"])),
"times" => vec([vec(blockGyTimes) for blockGyTimes in seq_pulseq["gyTimes"]]),
"amplitudes" => vec([vec(blockGyAmplitudes) for blockGyAmplitudes in seq_pulseq["gyAmplitudes"]])
),
"gz" => Dict(
"blocks" => Int.(vec(seq_pulseq["gzBlocks"])),
"times" => vec([vec(blockGzTimes) for blockGzTimes in seq_pulseq["gzTimes"]]),
"amplitudes" => vec([vec(blockGzAmplitudes) for blockGzAmplitudes in seq_pulseq["gzAmplitudes"]])
),
"adc" => Dict(
"blocks" => Int.(vec(seq_pulseq["adcBlocks"])),
"times" => vec([vec(blockAdcTimes) for blockAdcTimes in seq_pulseq["adcTimes"]]),
)
)
end
cncastillo marked this conversation as resolved.
Show resolved Hide resolved

function read_comparison(filename)
# Define some names of the sequence dictionaries
event_names = ["rf", "gx", "gy", "gz", "adc"]
sample_names = ["times", "amplitudes"]
# Read data for Koma and Pulseq
seq_koma = koma_read(filename) # it reads the .seq
seq_pulseq = pulseq_read(filename) # it reads the .mat (ground truth)
# Checks for the blocks IDs of each event
for event in event_names
blocks_koma = seq_koma[event]["blocks"]
blocks_pulseq = seq_pulseq[event]["blocks"]
same_number_of_blocks = length(blocks_koma) == length(blocks_pulseq)
@test same_number_of_blocks
if same_number_of_blocks
same_blocks = all(blocks_koma .== blocks_pulseq)
@test same_blocks
if !same_blocks
println("Mismatch for $(event) blocks")
data = [blocks_koma blocks_pulseq]
pretty_table(data; header=["koma", "pulseq"])
end
else
println("Length mismatch for $(event):")
println("Koma $(length(seq_koma[event])) != Pulseq $(length(seq_pulseq[event]))")
end
end
# Checks for the timings of each event
for event in event_names
for sample in sample_names
if !(event == "adc" && sample == "amplitudes")
for i in 1:length(seq_pulseq[event]["blocks"])
samples_koma = seq_koma[event][sample][i]
samples_pulseq = seq_pulseq[event][sample][i]
same_points = all(.≈(samples_koma, samples_pulseq, atol=TOLERANCE))
@test same_points
if !same_points
println("Mismatch for $(event) $(sample). Block $(seq_pulseq[event]["blocks"][i])")
data = [samples_koma samples_pulseq]
pretty_table(data; header=["koma", "pulseq"])
# This is just an example about how to debug locally when there are problems
#if event == "gx" && sample == "amplitudes" && seq_pulseq[event]["blocks"][i] == 3
# pretty_table(data[end-10:end,:]; header=["koma", "pulseq"])
#end
end
end
end
end
end
cncastillo marked this conversation as resolved.
Show resolved Hide resolved
end

@testset "Trapezoidal_Grad" begin
read_comparison("gr-trapezoidal")
end
@testset "Uniformly_Shaped_Grad" begin
read_comparison("gr-uniformly-shaped")
end
@testset "Time_Shaped_Grad" begin
read_comparison("gr-time-shaped")
end
@testset "Pulse_RF" begin
read_comparison("rf-pulse")
end
@testset "Uniformly_Shaped_RF" begin
read_comparison("rf-uniformly-shaped")
end
@testset "FID" begin
read_comparison("fid")
end
@testset "Spiral" begin
read_comparison("spiral")
beorostica marked this conversation as resolved.
Show resolved Hide resolved
end

end
Binary file not shown.
Loading
Loading