From 1427f18a14ca7978a0cf8d0d6064a4c54ba928e5 Mon Sep 17 00:00:00 2001
From: Michiel Cottaar <MichielCottaar@protonmail.com>
Date: Mon, 20 May 2024 15:53:13 +0100
Subject: [PATCH] Write MRIBuilder.Sequence to pulseq file

---
 src/sequence_io/pulseq.jl                     | 88 +++++++++++++++++--
 .../pulseq_io/parsers/definitions.jl          |  1 +
 src/sequence_io/pulseq_io/pulseq_io.jl        |  1 +
 src/sequence_io/sequence_io.jl                | 12 +--
 4 files changed, 92 insertions(+), 10 deletions(-)

diff --git a/src/sequence_io/pulseq.jl b/src/sequence_io/pulseq.jl
index 4477b6e..fd25519 100644
--- a/src/sequence_io/pulseq.jl
+++ b/src/sequence_io/pulseq.jl
@@ -3,14 +3,16 @@ Module converting MRIBuilder sequences to and from sequences recognised by [`Pul
 """
 module Pulseq
 import Interpolations: linear_interpolation
-import ..PulseqIO.Types: PulseqSequence, PulseqBlock, PulseqTrapezoid, PulseqGradient
-import ...Containers: Sequence, BuildingBlock
-import ...Components: GenericPulse, ADC
+import ..PulseqIO.Types: PulseqSequence, PulseqBlock, PulseqTrapezoid, PulseqGradient, PulseqRFPulse, PulseqShape, PulseqADC, PulseqExtension
+import ...Containers: Sequence, BuildingBlock, BaseBuildingBlock, events, waveform, iter_blocks
+import ...Components: GenericPulse, ADC, RFPulseComponent
 import ...Scanners: Scanner
+import ...Variables: duration, nsamples, dwell_time, make_generic
 
-function Sequence(pulseq::PulseqSequence; scanner=nothing, B0=3)
+function Sequence(pulseq::PulseqSequence; scanner=nothing, B0=nothing)
     if isnothing(scanner)
-        scanner = Scanner(B0=B0)
+        use_B0 = isnothing(B0) ? get(pulseq.definitions, :B0, 3.) : B0
+        scanner = Scanner(B0=use_B0)
     end
     blocks = BuildingBlock.(pulseq.blocks; pulseq.definitions...)
     return Sequence(blocks; name=Symbol(get(pulseq.definitions, :name, "from_pulseq")), scanner=scanner)
@@ -88,4 +90,80 @@ function _get_amplitude(grad::PulseqGradient, time::Number, raster::Number)
 end
 
 
+function PulseqSequence(seq::Sequence{S}) where {S}
+    definitions = (
+        name=S,
+        AdcRasterTime=1e-9,
+        BlockDurationRaster=1e-9,
+        RadiofrequencyRasterTime=1e-9,
+        GradientRasterTime=1e-9,
+        TotalDuration=duration(seq) * 1e-3,
+        B0=seq.scanner.B0,
+    )
+    blocks = [PulseqBlock(block; definitions...) for (_, block) in iter_blocks(seq)]
+    return PulseqSequence(
+        v"1.4.0",
+        definitions,
+        blocks
+    )
+end
+
+function PulseqBlock(block::BaseBuildingBlock; BlockDurationRaster, AdcRasterTime, kwargs...)
+    rf = nothing
+    adc = nothing
+    for (delay, event) in events(block)
+        if event isa RFPulseComponent
+            if !isnothing(rf)
+                error("Pulseq does not support a single building block containing multiple RF pulses.")
+            end
+            gen = make_generic(event)
+            rf = PulseqRFPulse(
+                maximum(gen.amplitude) * 1e3,
+                PulseqShape(gen.amplitude ./ maximum(gen.amplitude)),
+                PulseqShape(deg2rad.(gen.phase)),
+                PulseqShape(gen.time .* 1e-3),
+                Int(div(delay, 1e-3)),
+                0., 
+                0.
+            )
+        elseif event isa ADC
+            if !isnothing(rf)
+                error("Pulseq does not support a single building block containing multiple ADC events.")
+            end
+            adc = PulseqADC(
+                nsamples(event),
+                div(dwell_time(event), AdcRasterTime),
+                Int(div(delay, 1e-3)),
+                0., 0.
+            )
+        else
+            error("Cannot write $(typeof(event)) to Pulseq.")
+        end
+    end
+
+    grads = []
+    times = [t for (t, _) in waveform(block)]
+    for dim in 1:3
+        amplitudes = [g[dim] for (_, g) in waveform(block)]
+        if iszero(maximum(abs.(amplitudes); init=0.))
+            push!(grads, nothing)
+        else
+            push!(grads, PulseqGradient(
+                maximum(amplitudes) * 1e3,
+                PulseqShape(amplitudes ./ maximum(amplitudes)),
+                PulseqShape(times .* 1e-3),
+                0.,
+            ))
+        end
+    end
+    
+    return PulseqBlock(
+        Int(div(1e-3 * duration(block), BlockDurationRaster)),
+        rf,
+        grads...,
+        adc,
+        Tuple{PulseqExtension, Int}[]
+    )
+end
+
 end
\ No newline at end of file
diff --git a/src/sequence_io/pulseq_io/parsers/definitions.jl b/src/sequence_io/pulseq_io/parsers/definitions.jl
index 2da9962..5ba67c7 100644
--- a/src/sequence_io/pulseq_io/parsers/definitions.jl
+++ b/src/sequence_io/pulseq_io/parsers/definitions.jl
@@ -4,6 +4,7 @@ function parse_section(section:: PulseqSection{:definitions}; kwargs...)
 end
 
 _to_string_value(value::AbstractString) = value
+_to_string_value(value::Symbol) = string(value)
 _to_string_value(value::Number) = string(value)
 _to_string_value(value::Vector) = join(_to_string_value.(value), " ")
 
diff --git a/src/sequence_io/pulseq_io/pulseq_io.jl b/src/sequence_io/pulseq_io/pulseq_io.jl
index d60141e..278e207 100644
--- a/src/sequence_io/pulseq_io/pulseq_io.jl
+++ b/src/sequence_io/pulseq_io/pulseq_io.jl
@@ -12,6 +12,7 @@ include("components.jl")
 include("parsers/parsers.jl")
 include("parse_sections.jl")
 
+import .Types: PulseqSequence
 
 
 """
diff --git a/src/sequence_io/sequence_io.jl b/src/sequence_io/sequence_io.jl
index 062a156..e0ebd01 100644
--- a/src/sequence_io/sequence_io.jl
+++ b/src/sequence_io/sequence_io.jl
@@ -4,7 +4,7 @@ include("pulseq_io/pulseq_io.jl")
 include("pulseq.jl")
 
 import Serialization: serialize, deserialize
-import .PulseqIO: read_pulseq
+import .PulseqIO: read_pulseq, write_pulseq, PulseqSequence
 import ..Containers: Sequence
 
 
@@ -14,7 +14,7 @@ function read_sequence(filename::AbstractString, args...; kwargs...)
     end
 end
 
-function read_sequence(io::IO; format=nothing, kwargs...)
+function read_sequence(io::IO; format=nothing, B0=nothing, scanner=nothing,kwargs...)
     if isnothing(format)
         pos = position(io)
         for format in (:pulseq, :serialize)
@@ -27,7 +27,7 @@ function read_sequence(io::IO; format=nothing, kwargs...)
         error("Could not read the input filename. Tried all formats (:pulseq/:serialize).")
     end
     if format == :pulseq
-        return Sequence(read_pulseq(io, kwargs...))
+        return Sequence(read_pulseq(io, kwargs...), B0=B0, scanner=scanner)
     elseif format == :serialize
         return deserialize(io, kwargs...)
     else
@@ -42,8 +42,10 @@ function write_sequence(filename::AbstractString, args...; kwargs...)
     end
 end
 
-function write_sequence(io::IO, sequence; format::Symbol=:serialize)
-    if format == :serialize
+function write_sequence(io::IO, sequence; format::Symbol=:pulseq)
+    if format == :pulseq
+        write_pulseq(io, PulseqSequence(sequence))
+    elseif format == :serialize
         serialize(io, sequence)
     else
         error("Unrecognised selected format for output ($format). Only :serialize is supported..")
-- 
GitLab