From 9f4dfbc27842f080704a4f710313b56f897d5322 Mon Sep 17 00:00:00 2001
From: Michiel Cottaar <michiel.cottaar@ndcn.ox.ac.uk>
Date: Wed, 17 Apr 2024 15:23:44 +0100
Subject: [PATCH] Compute RF pulse frequency at specific times

---
 src/components/pulses/constant_pulses.jl |  1 +
 src/components/pulses/generic_pulses.jl  | 24 +++++++++++++++++++++++-
 src/components/pulses/sinc_pulses.jl     |  1 +
 src/containers/abstract.jl               | 16 +++++++++++++---
 src/containers/base_sequences.jl         |  4 ++--
 src/containers/building_blocks.jl        |  4 ++--
 test/test_sequences.jl                   |  4 ++++
 7 files changed, 46 insertions(+), 8 deletions(-)

diff --git a/src/components/pulses/constant_pulses.jl b/src/components/pulses/constant_pulses.jl
index f2891a0..f330df0 100644
--- a/src/components/pulses/constant_pulses.jl
+++ b/src/components/pulses/constant_pulses.jl
@@ -48,6 +48,7 @@ effective_time(pulse::ConstantPulse) = duration(pulse) / 2
 
 amplitude(pulse::ConstantPulse, time::Number) = amplitude(pulse)
 phase(pulse::ConstantPulse, time::Number) = phase(pulse) + frequency(pulse) * (time - effective_time(pulse))
+frequency(pulse::ConstantPulse, time::Number) = frequency(pulse)
 
 function make_generic(block::ConstantPulse)
     d = duration(block)
diff --git a/src/components/pulses/generic_pulses.jl b/src/components/pulses/generic_pulses.jl
index 8142c49..d70c0d7 100644
--- a/src/components/pulses/generic_pulses.jl
+++ b/src/components/pulses/generic_pulses.jl
@@ -2,7 +2,7 @@ module GenericPulses
 
 import Polynomials: fit
 import ...AbstractTypes: RFPulseComponent, split_timestep
-import ....Variables: duration, amplitude, effective_time, flip_angle, make_generic, phase
+import ....Variables: duration, amplitude, effective_time, flip_angle, make_generic, phase, frequency
 
 
 """
@@ -99,6 +99,28 @@ for fn in (:amplitude, :phase)
 end
 
 
+function frequency(gp::GenericPulse, time::Number)
+    i2 = findfirst(t -> t > time, gp.time)
+    if isnothing(i2)
+        @assert time ≈ fp.time[end]
+        i2 = length(time)
+    end
+    if i2 != length(time) && time ≈ fp.time[i2 + 1]
+        i2 += 1
+    end
+    if time ≈ fp.time[i2]
+        if i2 == 1
+            return (gp.phase[2] - gp.phase[1]) / (gp.time[2] - gp.time[1]) / 360
+        elseif i2 == length(time)
+            return (gp.phase[end] - gp.phase[end-1]) / (gp.time[end] - gp.time[end-1]) / 360
+        end
+        return (gp.phase[i2 + 1] - gp.phase[i2 - 1]) / (gp.time[i2 + 1] - gp.time[i2 - 1]) / 360
+    end
+    @assert !isone(i2)
+    return (gp.phase[i2] - gp.phase[i2 - 1]) / (gp.time[i2] - gp.time[i2 - 1]) / 360
+end
+
+
 function get_weights(pulse::GenericPulse)
     weights_double = pulse.time[2:end] - pulse.time[1:end-1]
     return [weights_double[1] / 2, ((weights_double[1:end-1] + weights_double[2:end]) / 2)..., weights_double[end]/2]
diff --git a/src/components/pulses/sinc_pulses.jl b/src/components/pulses/sinc_pulses.jl
index 99dfb53..2c41504 100644
--- a/src/components/pulses/sinc_pulses.jl
+++ b/src/components/pulses/sinc_pulses.jl
@@ -92,6 +92,7 @@ effective_time(pulse::SincPulse) = N_left(pulse) * lobe_duration(pulse)
 
 amplitude(pulse::SincPulse, time::Number) = amplitude(pulse) * normalised_function(abs((time - effective_time(pulse))) / lobe_duration(pulse), N_left(pulse), N_right(pulse); apodise=pulse.apodise)
 phase(pulse::SincPulse, time::Number) = phase(pulse) + frequency(pulse) * (time - effective_time(pulse))
+frequency(pulse::SincPulse, time::Number) = frequency(pulse)
 
 function make_generic(block::SincPulse)
     normed_times = -N_left(block):0.1:N_right(block) + 1e-5
diff --git a/src/containers/abstract.jl b/src/containers/abstract.jl
index 0148c6d..a1dabc3 100644
--- a/src/containers/abstract.jl
+++ b/src/containers/abstract.jl
@@ -72,7 +72,7 @@ function gradient_strength end
 """
     amplitude(sequence, time)
 
-Returns the RF amplitude at a particular time within the sequence.
+Returns the RF amplitude at a particular time within the sequence in kHz.
 """
 function amplitude end
 
@@ -80,11 +80,21 @@ function amplitude end
 """
     phase(sequence, time)
 
-Returns the RF phase at a particular time within the sequence.
-NaN if the ampltiude is zero.
+Returns the RF phase at a particular time within the sequence in degrees.
+
+NaN is returned if there is no pulse activate at that `time`.
 """
 function phase end
 
+"""
+    frequency(sequence, time)
+
+Returns the RF frequency at a particular time within the sequence in kHz.
+
+NaN is returned if there is no pulse activate at that `time`.
+"""
+function frequency end
+
 """
     iter(sequence, get_type)
 
diff --git a/src/containers/base_sequences.jl b/src/containers/base_sequences.jl
index 80603f4..629c348 100644
--- a/src/containers/base_sequences.jl
+++ b/src/containers/base_sequences.jl
@@ -4,7 +4,7 @@ Defines [`BaseSequence`](@ref) and [`Sequence`](@ref)
 module BaseSequences
 import StaticArrays: SVector
 import JuMP: @constraint
-import ...Variables: get_free_variable, repetition_time, VariableType, duration, variables, VariableNotAvailable, Variables, set_simple_constraints!, TR, make_generic, gradient_strength, amplitude, phase, gradient_strength3, get_gradient, get_pulse
+import ...Variables: get_free_variable, repetition_time, VariableType, duration, variables, VariableNotAvailable, Variables, set_simple_constraints!, TR, make_generic, gradient_strength, amplitude, phase, gradient_strength3, get_gradient, get_pulse, frequency
 import ...BuildSequences: global_model, global_scanner
 import ...Components: EventComponent, NoGradient
 import ...Scanners: Scanner, B0
@@ -114,7 +114,7 @@ function edge_times(seq::BaseSequence; tol=1e-6)
     return sort(unique_res)
 end
 
-for fn in (:gradient_strength, :amplitude, :phase, :gradient_strength3, :get_gradient, :get_pulse)
+for fn in (:gradient_strength, :amplitude, :phase, :frequency, :gradient_strength3, :get_gradient, :get_pulse)
     @eval function $fn(sequence::BaseSequence, time::AbstractFloat)
         (block_time, block) = sequence(time)
         return $fn(block, block_time)
diff --git a/src/containers/building_blocks.jl b/src/containers/building_blocks.jl
index 6cd930f..9fb9d71 100644
--- a/src/containers/building_blocks.jl
+++ b/src/containers/building_blocks.jl
@@ -8,7 +8,7 @@ import StaticArrays: SVector
 import ..Abstract: ContainerBlock, start_time, readout_times, edge_times, end_time, iter
 import ...BuildSequences: global_model
 import ...Components: BaseComponent, GradientWaveform, EventComponent, NoGradient, ChangingGradient, ConstantGradient, split_gradient, RFPulseComponent, ReadoutComponent, InstantGradient
-import ...Variables: qval, bmat_gradient, effective_time, get_free_variable, qval3, slew_rate, gradient_strength, amplitude, phase
+import ...Variables: qval, bmat_gradient, effective_time, get_free_variable, qval3, slew_rate, gradient_strength, amplitude, phase, frequency
 import ...Variables: VariableType, duration, make_generic, get_pulse, get_readout, scanner_constraints!, get_gradient
 
 """
@@ -230,7 +230,7 @@ function get_pulse(bb::BaseBuildingBlock, time::Number)
     return nothing
 end
 
-for (fn, default_value) in ((:amplitude, 0.), (:phase, NaN))
+for (fn, default_value) in ((:amplitude, 0.), (:phase, NaN), (:frequency, NaN))
     @eval function $fn(bb::BaseBuildingBlock, time::Number)
         pulse = get_pulse(bb, time)
         if isnothing(pulse)
diff --git a/test/test_sequences.jl b/test/test_sequences.jl
index 6f741aa..d82c008 100644
--- a/test/test_sequences.jl
+++ b/test/test_sequences.jl
@@ -117,6 +117,10 @@
                 gp = GenericPulse(pulse, 0., 1.)
                 @test gp.amplitude[1] ≈ 0. atol=1e-8
                 @test gp.amplitude[end] ≈ amplitude(pulse, 1.) rtol=1e-2
+                @test iszero(phase(pulse, 1.)) rtol=1e-2
+                @test iszero(frequency(pulse, 1.)) rtol=1e-2
+                @test isnan(phase(pulse, 1.)) rtol=1e-2
+                @test isnan(frequency(pulse, 1.)) rtol=1e-2
                 @test all(iszero.(gp.phase))
 
                 (pulse, t_pulse) = get_pulse(seq, 35.)
-- 
GitLab