Skip to content
Snippets Groups Projects
Unverified Commit 14387e21 authored by Michiel Cottaar's avatar Michiel Cottaar
Browse files

Read pulseq files into pulseq types

parent e79b2044
No related branches found
No related tags found
1 merge request!1Add writing to Pulseq files
This commit is part of merge request !1. Comments created here will be created in the context of that merge request.
module Pulseq
import ...Variables: duration
import ...Scanners: Scanner
import ...Components: GenericPulse, GradientWaveform, ADC
import ...Containers: BuildingBlock, Sequence, Wait
import DataStructures: OrderedDict
import Interpolations: linear_interpolation, Flat
import StaticArrays: SVector
struct PulseqSection
title :: String
content :: Vector{String}
"""
read_pulseq(IO)
Reads a sequence from a pulseq file (http://pulseq.github.io/).
Pulseq files can be produced using matlab (http://pulseq.github.io/) or python (https://pypulseq.readthedocs.io/en/master/).
"""
function read_pulseq(io::IO)
sections = parse_pulseq_sections(io)
return parse_all_sections(sections)
end
struct CompressedPulseqShape
id :: Int
num :: Int
samples :: Vector{Float64}
"""
write_pulseq(IO, sequence)
Writes a sequence to an output IO file.
"""
function write_pulseq(io::IO, sequence::PulseqSequence)
if sequence.version < v"1.4"
error("Can only write to pulseq version 1.4 or later.")
end
sections = gen_all_sections(sequence)
write_section.(io, sections)
end
"""
control_points(shape::CompressedPulseqShape)
parse_pulseq_dict(line, names, dtypes)
Parse a line of integers/floats with known names and dtypes.
This is useful to parse most of the columnar data in Pulseq, such as in BLOCKS, RF, GRADIENTS, etc.
"""
function parse_pulseq_dict(line, names, dtypes)
parts = split(line)
@assert length(parts) == length(names)
values = parse.(dtypes, split(line))
@assert names[1] == :id
return Dict(Symbol(name) => value for (name, value) in zip(names, values))
end
"""
parse_pulseq_properties(lines)
Parse any `pulseq` section formatted as:
```
<name> <value>
<name2> <value2>
...
```
Returns a tuple with:
- vector of times
- vector of amplitudes
This includes the VERSION, DEFINITIONS, and part of the SHAPES
"""
function control_points(pulseq::CompressedPulseqShape)
compressed = length(pulseq.samples) != pulseq.num
if !compressed
times = range(0, 1, length=pulseq.num)
return (times, pulseq.samples)
function parse_pulseq_properties(strings::Vector{<:AbstractString})
result = Dict{String, Any}()
for s in strings
(name, value) = split(s, limit=2)
result[name] = _parse_value(value)
end
times = [zero(Float64)]
amplitudes = [pulseq.samples[1]]
repeating = false
time_norm = 1 / (pulseq.num - 1)
prev_sample = pulseq.samples[1]
prev_applied = true
for sample in pulseq.samples[2:end]
if repeating
nrepeats = Int(sample) + 2
if prev_applied
nrepeats -= 1
end
push!(times, times[end] + nrepeats * time_norm)
push!(amplitudes, amplitudes[end] + nrepeats * prev_sample)
repeating = false
prev_applied = true
elseif sample == prev_sample
repeating = true
else
if !prev_applied
push!(times, times[end] + time_norm)
push!(amplitudes, amplitudes[end] + prev_sample)
end
prev_sample = sample
prev_applied = false
return result
end
function _parse_value(value::AbstractString)
for t in (Int, Float64)
try
return parse(t, value)
catch
end
end
if !prev_applied
push!(times, times[end] + time_norm)
push!(amplitudes, amplitudes[end] + prev_sample)
for t in (Int, Float64)
try
return parse.(t, split(value))
catch
end
end
return (times, amplitudes)
return value
end
"""
read_pulseq(IO; scanner=Scanner(B0=B0), B0=3., TR=<sequence duration>)
PulseqSection(:<title>)(lines)
Reads a sequence from a pulseq file (http://pulseq.github.io/).
Pulseq files can be produced using matlab (http://pulseq.github.io/) or python (https://pypulseq.readthedocs.io/en/master/).
Represents a section in the pulseq file format.
"""
function read_pulseq(io::IO; kwargs...)
keywords = read_pulseq_sections(io)
return build_sequence(; kwargs..., keywords...)
struct PulseqSection{T}
content :: Vector{String}
end
function read_pulseq_sections(io::IO)
sections = PulseqSection[]
version = nothing
title = ""
"""
parse_pulseq_sections(io)
Reads a Pulseq file into a dictionary of [`PulseqSection`](@ref) objects.
"""
function parse_pulseq_sections(io::IO)
sections = Dict{String, PulseqSection}()
current_title = ""
for line in readlines(io)
line = strip(line)
if length(line) == 0 || line[1] == '#'
continue # ignore comments
end
if line[1] == '[' && line[end] == ']'
if title == "VERSION"
version = parse_pulseq_section(sections[end]).second
end
# new section starts
title = line[2:end-1]
push!(sections, PulseqSection(title, String[]))
elseif length(sections) > 0
push!(sections[end].content, line)
current_title = lowercase(line[2:end-1])
sections[current_title] = PulseqSection{Symbol(current_title)}(String[])
elseif length(current_title) > 0
push!(sections[current_title].content, line)
else
error("Content found in pulseq file before first section")
end
end
Dict(filter(pair -> !isnothing(pair), parse_pulseq_section.(sections, version))...)
return sections
end
function parse_pulseq_ordered_dict(strings::Vector{<:AbstractString}, names, dtypes)
as_dict = OrderedDict{Int, NamedTuple{Tuple(names), Tuple{dtypes...}}}()
for line in strings
parts = split(line)
@assert length(parts) == length(names)
values = parse.(dtypes, split(line))
@assert names[1] == :id
as_dict[values[1]] = (; zip(names, values)...)
end
return as_dict
# Writing pulseq files
struct PulseqShape
samples :: Vector{Float64}
end
function parse_pulseq_properties(strings::Vector{<:AbstractString})
result = Dict{String, Any}()
for s in strings
(name, value) = split(s, limit=2)
result[name] = value
struct PulseqRFPulse
amplitude :: Float64
magnitude :: PulseqShape
phase :: PulseqShape
time :: Union{Nothing, PulseqShape}
delay :: Int
frequency :: Float64
phase_offset :: Float64
end
abstract type AnyPulseqGradient end
struct PulseqGradient <: AnyPulseqGradient
amplitude :: Float64
shape :: PulseqShape
time :: Union{Nothing, PulseqShape}
delay :: Int
end
struct PulseqTrapezoid <:AnyPulseqGradient
amplitude :: Float64
rise :: Int
flat :: Int
fall :: Int
delay :: Int
end
struct PulseqADC
nsamples :: Int
dwell_time :: Float64
delay :: Int
frequency :: Float64
phase :: Float64
end
struct PulseqExtension
name :: String
content :: Vector{String}
end
struct PulseqBlock
duration :: Int
rf :: Union{Nothing, PulseqRFPulse}
gx :: Union{Nothing, AnyPulseqGradient}
gy :: Union{Nothing, AnyPulseqGradient}
gz :: Union{Nothing, AnyPulseqGradient}
adc :: Union{Nothing, PulseqADC}
ext :: Vector{<:Tuple{PulseqExtension, Int}}
end
struct PulseqSequence
version:: VersionNumber
definitions:: NamedTuple
blocks:: Vector{PulseqBlock}
end
# I/O all sections
function parse_all_sections(sections:: Dict{String, PulseqSection})
sections = copy(sections)
all_parts = Dict{Symbol, Any}()
for name in ["version", "definitions", "delays", "shapes", "rf", "gradients", "trap", "adc", "extensions", "blocks"]
if name in keys(sections)
section = pop!(sections, name)
all_parts[Symbol(name)] = parse_section(section; all_parts...)
end
end
return result
if length(sections) > 0
@warn "Following sections in pulseq input file are not being used: $(keys(sections))"
end
return PulseqSequence(all_parts[:version], all_parts[:definitions], all_parts[:blocks])
end
section_headers = Dict(
"BLOCKS" => ([:id, :duration, :rf, :gx, :gy, :gz, :adc, :ext], fill(Int, 8)),
("BLOCKS", v"1.3.1") => ([:id, :delay, :rf, :gx, :gy, :gz, :adc, :ext], fill(Int, 8)),
("DELAYS", v"1.3.1") => ([:id, :delay], [Int, Int]),
("RF", v"1.3.1") => ([:id, :amp, :mag_id, :phase_id, :delay, :freq, :phase], [Int, Float64, Int, Int, Int, Float64, Float64]),
"RF" => ([:id, :amp, :mag_id, :phase_id, :time_id, :delay, :freq, :phase], [Int, Float64, Int, Int, Int, Int, Float64, Float64]),
("GRADIENTS", v"1.3.1") => ([:id, :amp, :shape_id, :delay], [Int, Float64, Int, Int]),
"GRADIENTS" => ([:id, :amp, :shape_id, :time_id, :delay], [Int, Float64, Int, Int, Int]),
"TRAP" => ([:id, :amp, :rise, :flat, :fall, :delay], [Int, Float64, Int, Int, Int, Int]),
"ADC" => ([:id, :num, :dwell, :delay, :freq, :phase], [Int, Int, Float64, Int, Float64, Float64]),
)
function parse_pulseq_section(section::PulseqSection, version=nothing)
if section.title == "VERSION"
props = parse_pulseq_properties(section.content)
result = VersionNumber(
parse(Int, props["major"]),
parse(Int, props["minor"]),
parse(Int, props["revision"]),
)
elseif section.title == "DEFINITIONS"
result = parse_pulseq_properties(section.content)
elseif (section.title, version) in keys(section_headers)
result = parse_pulseq_ordered_dict(section.content, section_headers[(section.title, version)]...)
elseif section.title in keys(section_headers)
result = parse_pulseq_ordered_dict(section.content, section_headers[section.title]...)
elseif section.title == "EXTENSION"
println("Ignoring all extensions in pulseq")
elseif section.title == "SHAPES"
current_id = -1
shapes = CompressedPulseqShape[]
for line in section.content
if length(line) > 8 && lowercase(line[1:8]) == "shape_id"
current_id = parse(Int, line[9:end])
continue
end
for text in ("num_uncompressed", "num_samples")
if startswith(lowercase(line), text)
@assert current_id != -1
push!(shapes, CompressedPulseqShape(current_id, parse(Int, line[length(text)+1:end]), Float64[]))
current_id = -1
break
function gen_all_sections(seq:: PulseqSequence)
sections = [gen_section(seq, Val(symbol)) for symbol in [:version, :definitions, :shapes, :rf, :gradients, :trap, :adc, :extensions, :blocks]]
return [section for section in sections if length(section.content) > 0]
end
# Version I/O
"""
parse_section(section)
Parses any [`PulseqSection`](@ref) and return the appropriate type.
The opposite is [`gen_section`](@ref).
"""
function parse_section(section:: PulseqSection{:version}; kwargs...)
props = parse_pulseq_properties(section.content)
return VersionNumber(props["major"], props["minor"], props["revision"])
end
"""
gen_section(sequence, Val(:<title>))
Creates a specific [`PulseqSection`](@ref){<title>} from a part of the PulseqSequence.
This is the opposite of [`parse_section`](@ref)
"""
function gen_section(seq::PulseqSequence, ::Val{:version})
version = seq.version
return PulseqSection{:version}([
"major $(version.major)",
"minor $(version.minor)",
"revision $(version.patch)",
])
end
# Definitions I/O
function parse_section(section:: PulseqSection{:definitions}; kwargs...)
props = parse_pulseq_properties(section.content)
return NamedTuple(Symbol(key) => value for (key, value) in props)
end
_to_string_value(value::AbstractString) = value
_to_string_value(value::Number) = string(value)
_to_string_value(value::Vector) = join(_to_string_value.(value), " ")
function gen_section(seq:: PulseqSequence, ::Val{:definitions})
definitions = seq.definitions
return PulseqSection{:definitions}(["$(key) $(_to_string_value(value))" for (key, value) in pairs(definitions)])
end
# Shapes I/O
struct CompressedPulseqShape
num :: Int
samples :: Vector{Float64}
end
function parse_section(section:: PulseqSection{:shapes}; kwargs...)
current_id = -1
shapes = Dict{Int, CompressedPulseqShape}()
for line in section.content
if startswith(lowercase(line), "shape_id")
current_id = parse(Int, line[9:end])
continue
end
for text in ("num_uncompressed", "num_samples")
if startswith(lowercase(line), text)
@assert current_id != -1
if current_id in keys(shapes)
error("Multiple shapes with the same ID detected.")
end
end
if !startswith(lowercase(line), "num")
@assert current_id == -1
push!(shapes[end].samples, parse(Float64, line))
shapes[current_id] = CompressedPulseqShape(parse(Int, line[length(text)+1:end]), Float64[])
end
end
result = Dict(
[s.id => (s.num, control_points(s)...) for s in shapes]...
)
elseif section.title in ["SIGNATURE"]
# silently ignore these sections
return nothing
else
error("Unrecognised pulseq section: $(section.title)")
if !startswith(lowercase(line), "num")
push!(shapes[current_id].samples, parse(Float64, line))
end
end
return Symbol(lowercase(section.title)) => result
return Dict(key => uncompress(shape) for (key, shape) in shapes)
end
function align_in_time(pairs...)
interps = [linear_interpolation(time, ampl; extrapolation_bc=Flat()) for (time, ampl) in pairs]
all_times = sort(unique(vcat([time for (time, _) in pairs]...)))
return (all_times, [interp.(all_times) for interp in interps]...)
function uncompress(compressed::CompressedPulseqShape)
if compressed.num == length(compressed.samples)
# not actually compressed
return PulseqShape(compressed.samples)
end
amplitudes = [compressed.samples[1]]
repeating = false
prev_sample = compressed.samples[1]
for sample in compressed.samples[2:end]
if repeating
for _ in 1:Int(sample)
push!(amplitudes, prev_sample)
end
repeating = false
else
push!(amplitudes, sample)
if sample == prev_sample
repeating = true
end
end
end
return PulseqShape(amplitudes)
end
same_shape(shape1::PulseqShape, shape2::PulseqShape) = length(shape1.samples) == length(shape2.samples) && all(shape1.samples . shape2.samples)
function build_sequence(; scanner=nothing, B0=3., TR=nothing, definitions, version, blocks, rf=nothing, gradients=nothing, trap=nothing, delays=nothing, shapes=nothing, adc=nothing, name=:Pulseq)
if isnothing(scanner)
scanner = Scanner(B0=B0)
end
if version == v"1.4.0"
# load raster times (converting seconds to milliseconds)
convert = key -> parse(Float64, definitions[key]) * Float64(1e3)
gradient_raster = convert("GradientRasterTime")
rf_raster = convert("RadiofrequencyRasterTime")
adc_raster = convert("AdcRasterTime")
block_duration_raster = convert("BlockDurationRaster")
elseif version == v"1.3.1"
gradient_raster = rf_raster = block_duration_raster = Float64(1e-3) # 1 microsecond as default raster
adc_raster = Float64(1e-6) # ADC dwell time is in ns by default
else
error("Can only load pulseq files with versions v1.3.1 and v1.4.0, not $(version)")
end
"""
shapes(sequence::PulseqSequence)
full_blocks = BuildingBlock[]
for block in values(blocks)
block_duration = 0.
events = []
if !iszero(block.rf)
proc = rf[block.rf]
(num, times_shape, amplitudes_shape) = shapes[proc.mag_id]
block_duration = max(num * rf_raster + proc.delay * 1e-3, block_duration)
ampl_times = times_shape .* (num * rf_raster)
ampl_size = amplitudes_shape .* (proc.amp * 1e-3)
if iszero(proc.phase_id)
phase_times = ampl_times
phase_size = ampl_times .* 0
else
(num, times_shape, phase_shape) = shapes[proc.phase_id]
block_duration = max(num * rf_raster + proc.delay * 1e-3, block_duration)
phase_times = times_shape .* (num * rf_raster)
phase_size = rad2deg.(phase_shape) .+ phase_times .* (proc.freq * 1e-3 * 360) .+ rad2deg(proc.phase)
end
if version != v"1.3.1" && !iszero(proc.time_id)
(num, _, time_shape) = shapes[proc.time_id]
times = time_shape .* rf_raster
ampl = ampl_size
phase = phase_size
@assert length(times) == length(ampl) == length(phase)
else
(times, ampl, phase) = align_in_time((ampl_times, ampl_size), (phase_times, phase_size))
Return all the unique shapes used in the MR sequence.
"""
function shapes(sequence:: PulseqSequence)
result = PulseqShape[]
for block in sequence.blocks
for new_shape in _all_shapes(block)
if !any(s -> same_shape(s, new_shape), result)
push!(result, new_shape)
end
push!(events, (proc.delay * 1e-3, GenericPulse(times, ampl, phase)))
end
if !iszero(block.adc)
proc = adc[block.adc]
push!(events, (proc.delay * 1e-3, ADC(proc.num, proc.dwell * 1e-6, proc.dwell * proc.num * 1e-6 / 2, 1.)))
block_duration = max(proc.delay * 1e-3 + proc.dwell * proc.num * 1e-6, block_duration)
end
return result
end
_all_shapes(block::PulseqBlock) = vcat(_all_shapes.([block.gx, block.gy, block.gz, block.rf])...)
_all_shapes(block::PulseqRFPulse) = vcat(_all_shapes.([block.magnitude, block.phase, block.time])...)
_all_shapes(block::PulseqGradient) = vcat(_all_shapes.([block.shape, block.time])...)
_all_shapes(shape::PulseqShape) = PulseqShape[shape]
_all_shapes(::PulseqTrapezoid) = PulseqShape[]
_all_shapes(::Nothing) = PulseqShape[]
function gen_section(seq:: PulseqSequence, ::Val{:shapes})
res = PulseqSection{:shapes}(String[])
for (index, shape) in enumerate(shapes(seq))
append!(res.content, [
"",
"shape_id $index",
"num_samples $(length(shape.samples))"
])
for sample in shape.samples
push!(res.content, string(sample))
end
grad_shapes = []
for symbol_grad in [:gx, :gy, :gz]
grad_id = getfield(block, symbol_grad)
if iszero(grad_id)
push!(grad_shapes, (Float64[], Float64[]))
continue
end
if !isnothing(gradients) && grad_id in keys(gradients)
proc = gradients[grad_id]
start_time = proc.delay * 1e-3
(num, grad_time, grad_shape) = shapes[proc.shape_id]
if version != v"1.3.1" && !iszero(proc.time_id)
(num, _, time_shape) = shapes[proc.time_id]
times = time_shape .* gradient_raster .+ start_time
@assert length(times) == length(grad_shape)
else
times = grad_time .* (num * gradient_raster) .+ start_time
end
push!(grad_shapes, (times, grad_shape .* (proc.amp * 1e-9)))
block_duration = max(start_time + num * gradient_raster, block_duration)
elseif !isnothing(trap) && grad_id in keys(trap)
proc = trap[grad_id]
start_time = proc.delay * 1e-3
times = (cumsum([0, proc.rise, proc.flat, proc.fall]) .* 1e-3) .+ start_time
push!(grad_shapes, (times, [0, proc.amp * 1e-9, proc.amp * 1e-9, 0]))
block_duration = max((start_time + proc.rise + proc.flat + proc.fall) * 1e-3, block_duration)
else
error("Gradient ID $grad_id not found in either of [GRADIENTS] or [TRAP] sections")
end
end
return res
end
function parse_section(section::PulseqSection{:delays}; version, kwargs...)
if version > v"1.3.1"
error("Did not expect a [DELAYS] section in pulseq file with version $(version)")
end
delays = Dict{Int, Int}()
for line in section.content
(id, delay) = parse.(Int, split(line))
delays[id] = delay
end
return delays
end
# Components I/O
_get_component(id::Int, shapes::Dict) = iszero(id) ? nothing : shapes[id]
function parse_section(section::PulseqSection{:rf}; shapes::Dict{Int, PulseqShape}, version, kwargs...)
result = Dict{Int, PulseqRFPulse}()
for line in section.content
if version == v"1.3.1"
props = parse_pulseq_dict(
line,
[:id, :amp, :mag_id, :phase_id, :delay, :freq, :phase],
[Int, Float64, Int, Int, Int, Float64, Float64],
)
props[:time_id] = 0
else
props = parse_pulseq_dict(
line,
[:id, :amp, :mag_id, :phase_id, :time_id, :delay, :freq, :phase],
[Int, Float64, Int, Int, Int, Int, Float64, Float64],
)
end
result[props[:id]] = PulseqRFPulse(
props[:amp],
_get_component(props[:mag_id], shapes),
_get_component(props[:phase_id], shapes),
_get_component(props[:time_id], shapes),
props[:delay],
props[:freq],
props[:phase],
)
end
return result
end
function parse_section(section::PulseqSection{:gradients}; shapes::Dict{Int, PulseqShape}, version::VersionNumber, kwargs...)
result = Dict{Int, PulseqGradient}()
for line in section.content
if version == v"1.3.1"
if !iszero(block.delay)
actual_duration = max(block_duration, delays[block.delay].delay * 1e-3)
else
actual_duration = block_duration
end
props = parse_pulseq_dict(
line,
[:id, :amp, :shape_id, :delay],
[Int, Float64, Int, Int]
)
props[:time_id] = 0
else
actual_duration = block.duration * block_duration_raster
@assert actual_duration >= block_duration
props = parse_pulseq_dict(
line,
[:id, :amp, :shape_id, :time_id, :delay],
[Int, Float64, Int, Int, Int]
)
end
function extend_grad!(pair)
(times, amplitudes) = pair
if iszero(length(times)) || !iszero(times[1])
pushfirst!(times, 0.)
pushfirst!(amplitudes, 0.)
end
if times[end] != actual_duration
push!(times, actual_duration)
push!(amplitudes, 0.)
end
result[props[:id]] = PulseqGradient(
props[:amp],
_get_component(props[:shape_id], shapes),
_get_component(props[:time_id], shapes),
props[:delay]
)
end
return result
end
function _get_shape_id(shapes, shape)
for (i, s) in enumerate(shapes)
if same_shape(s, shape)
return i
end
end
error("Shape not found.")
end
function parse_section(section::PulseqSection{:trap}; kwargs...)
result = Dict{Int, PulseqTrapezoid}()
for line in section.content
props = parse_pulseq_dict(
line,
[:id, :amp, :rise, :flat, :fall, :delay],
[Int, Float64, Int, Int, Int, Int],
)
result[props[:id]] = PulseqTrapezoid(
props[:amp],
props[:rise],
props[:flat],
props[:fall],
props[:delay],
)
end
return result
end
function parse_section(section::PulseqSection{:adc}; kwargs...)
result = Dict{Int, PulseqADC}()
for line in section.content
props = parse_pulseq_dict(
line,
[:id, :num, :dwell, :delay, :freq, :phase],
[Int, Int, Float64, Int, Float64, Float64],
)
result[props[:id]] = PulseqADC(
props[:num],
props[:dwell],
props[:delay],
props[:freq],
props[:phase],
)
end
return result
end
function parse_section(section::PulseqSection{:extensions}; kwargs...)
current_extension = -1
pre_amble = true
linked_list = Dict{Int, NTuple{3, Int}}()
extensions = Dict{Int, PulseqExtension}()
for line in section.content
if startswith(line, "extension ")
pre_amble = false
(_, name, str_id) = split(line)
current_extension = int(str_id)
extensions[current_extension] = PulseqExtension(name, String[])
elseif pre_ample
(id, type, ref, next) = int.(split(line))
linked_list[id] = (type=type, ref=ref, next=next)
else
push!(extensions[current_extension], line)
end
extend_grad!.(grad_shapes)
arrs = align_in_time(grad_shapes...)
waveform = [(t, SVector{3, Float64}(gx, gy, gz)) for (t, gx, gy, gz) in zip(arrs...)]
push!(full_blocks, BuildingBlock(waveform, events))
end
full_block_duration = sum(duration.(full_blocks))
if isnothing(TR)
total_duration = parse(Float64, get(definitions, "TotalDuration", "0")) * 1000
TR = iszero(total_duration) ? full_block_duration : total_duration
function get_extension_list(key::Int)
if iszero(key)
return Tuple{PulseqExtension, Int}[]
else
base = get_extension_list(linked_list[key].next)
pushfirst!(base, (extensions[linked_list[key].type], linked_list[key].ref))
return base
end
end
if TR > full_block_duration
push!(full_blocks, Wait(TR - full_block_duration))
elseif TR < full_block_duration
@warn "Given TR ($TR) is shorter than the total duration of all the building blocks ($full_block_duration). Ignoring TR..."
return Dict(key => get_extension_list(key) for key in keys(linked_list))
end
function parse_section(section::PulseqSection{:blocks}; version, rf=Dict(), gradients=Dict(), trap=Dict(), adc=Dict(), extensions=Dict(), delays=nothing, kwargs...)
all_grad = merge(gradients, trap)
res = Vector{PulseqBlock}()
for line in section.content
props = parse_pulseq_dict(
line,
[:id, :duration, :rf, :gx, :gy, :gz, :adc, :ext],
fill(Int, 8),
)
if version == v"1.3.1"
props[:duration] = iszero(props[:duration]) ? 0 : delays[props[:duration]]
end
@assert length(res) + 1 == props[:id]
push!(res, PulseqBlock(
props[:duration],
_get_component(props[:rf], rf),
_get_component(props[:gx], all_grad),
_get_component(props[:gy], all_grad),
_get_component(props[:gz], all_grad),
_get_component(props[:adc], adc),
iszero(props[:ext]) ? Tuple{PulseqExtension, Int}[] : _get_component(props[:ext], extensions),
))
end
Sequence(full_blocks; scanner=scanner, name=name)
return res
end
end
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment