diff --git a/src/MRIBuilder.jl b/src/MRIBuilder.jl index 70a276771e046c55ff2549235fad203412afae22..3e1f516a5bb59e0e3887a3612cb130423d9869fc 100644 --- a/src/MRIBuilder.jl +++ b/src/MRIBuilder.jl @@ -14,6 +14,7 @@ include("readouts/readouts.jl") include("overlapping/overlapping.jl") include("sequences.jl") include("pathways.jl") +include("helper_functions.jl") import .BuildSequences: build_sequence export build_sequence @@ -48,6 +49,9 @@ export Sequence import .Pathways: Pathway, duration_transverse, duration_dephase, bval, bmat export Pathway, duration_transverse, duration_dephase, bval, bmat +import .HelperFunctions: excitation_pulse +export excitation_pulse + import JuMP: @constraint, @objective, objective_function, optimize!, has_values, value, owner_model, Model export @constraint, @objective, objective_function, optimize!, has_values, value, owner_model, Model diff --git a/src/helper_functions.jl b/src/helper_functions.jl new file mode 100644 index 0000000000000000000000000000000000000000..89cd7c0f5289ee86ebbb0ab4b80243f301fc59bf --- /dev/null +++ b/src/helper_functions.jl @@ -0,0 +1,63 @@ +module HelperFunctions +import JuMP: @constraint +import ..BuildSequences: get_global_model +import ..Sequences: Sequence +import ..Pulses: SincPulse, ConstantPulse, InstantRFPulseBlock +import ..Overlapping: TrapezoidGradient + +""" + excitation_pulse(; parameters...) + +Create an excitation RF pulse. + +By default there is no slice-selective gradient. +To enable slice selection `min_slice_thickness` has to be set to a number or to :min. +if `min_slice_thickness` is not set or is set to `:min`, then either `bandwidth` or `duration` should be set, otherwise the problem might be unconstrained (ignore this for `shape=:instant`). + +## Parameters +For an [`InstantRFPulseBlock`](@ref) (i.e., `shape=:instant`), only the `flip_angle`, `phase`, and `scale` will be used. All other parameters are ignored. +### Pulse parameters +- `shape`: The shape of the RF pulse. One of `:sinc` (for [`SincPulse`](@ref)), `:constant`/`:hard` (for [`ConstantPulse`](@ref)), or `:instant` (for [`InstantRFPulseBlock`](@ref)). +- `flip_angle`: size of the flip due to the RF pulse in degrees (default: 90). +- `phase`: angle of the RF pulse in the x-y plane in degrees (default: 0). +- `frequency`: frequency of the RF pulse relative to the Larmor frequency in kHz (default: 0). +- `bandwidth`: width of the RF pulse in Fourier space in kHz (default: free variable). +- `duration`: duration of the RF pulse in ms (default: free variable). +- `Nzero`: sets the number of zero crossings for a [`SincPulse`](@ref) (default: 3). Can be set to a tuple of two numbers to set a different number of zero crossings before and after the pulse maximum. +- `scale`: name of the parameter with which the RF pulse amplitude can be modulated after sequence optimisation (default: `:transmit_B1`). + +### Slice selection +- `min_slice_thickness`: minimum slice thickness that should be possible without adjusting the sequence timings in um (default: no slice selection). Can be set to `:min` to indicate that this should be minimised given the scanner constraints and user values for `bandwidth` or `duration`. +- `rephase`: set to false to disable the spin rephasing after the RF pulse. +- `rotate_grad`: name of the parameter with which the slice selection gradient will be rotated after sequence optimisation (default: `:FOV`). +""" +function excitation_pulse(; flip_angle=90, phase=0., frequency=0., shape=:sinc, min_slice_thickness=Inf, rephase=true, Nzero=3, scale=:transmit_B1, rotate_grad=:FOV, bandwidth=nothing, duration=nothing) + if shape == :sinc + pulse = SincPulse(flip_angle=flip_angle, phase=phase, frequency=frequency, N_lobes=Nzero, scale=scale, bandwidth=bandwidth, duration=duration) + elseif shape in (:constant, :hard) + pulse = ConstantPulse(flip_angle=flip_angle, phase=phase, frequency=frequency, scale=scale, bandwidth=bandwidth, duration=duration) + elseif shape == :instant + pulse = InstantRFPulseBlock(flip_angle=flip_angle, phase=phase, scale=scale) + if !isinf(min_slice_thickness) + error("An instant RF pulse always affects all spins equally, so using `shape=:instant` is incompatible with setting `min_slice_thickness`.") + end + return pulse + end + if isinf(min_slice_thickness) + return pulse + end + grad = TrapezoidGradient(pulse=pulse, rise_time=:min, slice_thickness=min_slice_thickness, orientation=[0, 0, 1.], rotate=rotate_grad) + if !rephase + return grad + end + seq = Sequence( + grad, + TrapezoidGradient(orientation=[0, 0, -1.], rotate=rotate_grad, duration=:min); + TR=Inf + ) + @constraint get_global_model() qvec(grad, 1, nothing)[2] == -qvec(seq[2])[2] + return seq +end + + +end \ No newline at end of file