Sequence optimisation
In MRIBuilder an MR Sequence
is defined as a sequence of BuildingBlock
objects. Most BuildingBlock
objects will contain free parameters determining, for example, the duration of the block or the strength/orientation of the MR gradient. In most MR sequence building software, the user will have to set all of these free parameters by computing the appropriate values given a desired echo time, b-value, etc.
In MRIBuilder the internal free parameters are not set directly. Instead, they are inferred using a non-linear, constrained optimisation. For each sequence type, the developer defines how to compute various summary variables from the BuildingBlock
free parameters, such as variables.echo_time
, variables.duration
, variables.resolution
, variables.gradient_strength
, variables.diffusion_time
, variables.duration_transverse
etc. A user can then create a specific instantiation of the sequence by fixing any of these summary variables to their desired values (or setting them to :min
/:max
to minimise/maximise them). In addition to the user-defined constraints, this optimisation will also take into account any scanner-defined constraints. Internally, MRIBuilder will then optimise the BuildingBlock
free parameters to match any user-defined constraints and/or objectives. This optimisation uses the Ipopt optimiser accessed through the JuMP.jl library.
In addition to any user-defined objectives, the developer might also have defined secondary objectives (e.g., minimise the total sequence duration). These objective functions will only be considered if they do not affect the result of the user-defined primary objective. More details on these developer-defined secondary objectives can be found in the section on defining new sequences
Summary variables
All variables are available as members of the variables
structure.
MRIBuilder.Variables.variables
— ModuleMain module containing all the MRIBuilder sequence variables.
All variables are available as members of this module, e.g. variables.echo_time
returns the echo time variable. New variables can be defined using @defvar
.
Set constraints on variables by passing them on as keywords during the sequence generation, e.g., seq=SpinEcho(echo_time=70)
.
After sequence generation you can get the variable values by calling variables.echo_time(seq)
. For the sequence defined above this would return 70. (or a number very close to that).
MRIBuilder.Variables.variables.N_left
— FunctionN_left(sinc_pulse)
Number of zero-crossings of the SincPulse
before the maximum.
Also, see variables.N_right
MRIBuilder.Variables.variables.N_right
— FunctionN_left(sinc_pulse)
Number of zero-crossings of the SincPulse
after the maximum.
Also, see variables.N_left
MRIBuilder.Variables.variables.TE
— Functionecho_time(sequence)
Returns the echo time of a sequence in ms.
This is typically defined as the time between the excitation pulse and the crossing of k=0 during the MRI readout.
echo_time(sequence)
Computes the echo time(s) of a sequence in ms.
MRIBuilder.Variables.variables.TR
— Functionrepetition_time(sequence)
Computes the repetition_times of a sequence in ms.
MRIBuilder.Variables.variables.all_gradient_strengths
— Functionall_gradient_strengths(spoilt_slice_select)
Returns the gradient strength before, during, and after the pulse in SpoiltSliceSelect
.
MRIBuilder.Variables.variables.amplitude
— Functionamplitude(pulse)
Return the amplitude of an RFPulseComponent
in kHz.
MRIBuilder.Variables.variables.area_under_curve
— Functionarea_under_curve(pathway::Pathway)
Return net displacement in k-space (i.e., spoiling) experienced by the spins following a specific Pathway
.
Only gradients active while the spins are in the transverse plane are considered.
Returns a NamedTuple with the area_under_curve
for all gradient groups.
MRIBuilder.Variables.variables.bandwidth
— Functionbandwidth(pulse)
Return the bandwidth of an RFPulseComponent
in kHz.
MRIBuilder.Variables.variables.bmat
— Functionbmat(pathway::Pathway)
Return 3x3 diffusion-weighted matrix experienced by the spins following a specific Pathway
in rad^2 ms/um^2.
Only gradients active while the spins are in the transverse plane are considered.
Returns a NamedTuple with the bmat
for all gradient groups.
MRIBuilder.Variables.variables.bmat_gradient
— Functionbmat_gradient(overlapping, qstart[, first_event, last_event])
Computes the addition to the variables.bmat
contributed by a specific building block or gradient.
qstart
represents the variables.qvec
at the start of this component.
If first_event
is set to something else than nothing
, only the gradient waveform after this RF pulse/Readout will be considered. Similarly, if last_event
is set to something else than nothing
, only the gradient waveform up to this RF pulse/Readout will be considered.
MRIBuilder.Variables.variables.bval
— Functionbval(pathway::Pathway)
Return size of diffusion-weighting experienced by the spins following a specific Pathway
in rad^2 ms/um^2.
Only gradients active while the spins are in the transverse plane will contribute to the diffusion weighting.
Returns a NamedTuple with the bval
for all gradient groups.
MRIBuilder.Variables.variables.delay
— Functiondelay(sequence)
Returns the offset beetween the readout and the spin echo in ms.
MRIBuilder.Variables.variables.diffusion_time
— Functiondiffusion_time(diffusion_sequence)
Returns the diffusion time of a DiffusionSpinEcho
in ms.
diffusion_time(sequence)
Computes the diffusion time of a sequence in ms.
MRIBuilder.Variables.variables.duration
— Functionduration(block)
Duration of the sequence or building block in ms.
MRIBuilder.Variables.variables.duration_dephase
— Functionduration_dephase(pathway::Pathway)
Returns the net time that spins following the given Pathway
spent in the +transverse versus the -transverse state. This determines the amount of T2'-weighting as $e^{t/T_2'}$, where $t$ is the duration_dephase
.
Also see variables.duration_transverse
for T2-weighting.
MRIBuilder.Variables.variables.duration_state
— Functionduration_state(pathway::Pathway, transverse::Bool, positive::Bool)
Returns how long the Pathway
spent in a specific state.
The requested state can be set using transverse
and positive
as follows:
transverse=false
,positive=true
: +longitudinaltransverse=true
,positive=true
: +transversetransverse=false
,positive=false
: -longitudinaltransverse=true
,positive=false
: -transverse
MRIBuilder.Variables.variables.duration_transverse
— Functionduration_transverse(pathway::Pathway)
Returns the total amount of time that spins following the given Pathway
spent in the transverse plane. This determines the amount of T2-weighting as $e^{t/T_2}$, where $t$ is the duration_transverse
.
Also see variables.duration_dephase
for T2'-weighting.
MRIBuilder.Variables.variables.dwell_time
— Functiondwell_time(adc)
The dwell time of the ADC readout in ms.
MRIBuilder.Variables.variables.echo_time
— Functionecho_time(sequence)
Returns the echo time of a sequence in ms.
This is typically defined as the time between the excitation pulse and the crossing of k=0 during the MRI readout.
echo_time(sequence)
Computes the echo time(s) of a sequence in ms.
MRIBuilder.Variables.variables.effective_time
— Functioneffective_time(container, indices...)
Returns the start time of component with given indices
with respect to the start of the ContainerBlock
.
This will crash if the component does not have an variables.effective_time
(e.g., if it is (part of) a gradient waveform).
Also see variables.duration
, start_time
, and end_time
MRIBuilder.Variables.variables.flat_time
— Functionflat_time(trapezoid)
Returns the flat time of a Trapezoid
gradient profile in ms.
MRIBuilder.Variables.variables.flip_angle
— Functionflip_angle(pulse)
Return the flip angle of an RFPulseComponent
in degrees.
MRIBuilder.Variables.variables.fov
— Functionfov(readout)
Defines the field of view of a readout in mm.
MRIBuilder.Variables.variables.frequency
— Functionfrequency(pulse)
Return the off-resonance frequency of an RFPulseComponent
in kHz.
MRIBuilder.Variables.variables.gradient_strength
— Functiongradient_strength(gradient)
Maximum 3D gradient strength of the gradient in kHz/um.
MRIBuilder.Variables.variables.gradient_strength_norm
— Functiongradient_strength_norm(gradient)
The norm of the variables.gradient_strength
.
MRIBuilder.Variables.variables.lobe_duration
— Functionlobe_duration(sinc_pulse)
Time between two zero-crossings of a SincPulse
.
MRIBuilder.Variables.variables.net_dephasing
— Functionnet_dephasing(pathway::Pathway)
Return net displacement vector in k-space/q-space experienced by the spins following a specific Pathway
in kHz/um.
Only gradients active while the spins are in the transverse plane are considered.
Returns a NamedTuple with the qvec
for all gradient groups.
MRIBuilder.Variables.variables.nsamples
— Functionnsamples(adc)
Number of samples in an ADC.
MRIBuilder.Variables.variables.oversample
— Functionoversample(adc)
The oversampling rate of the ADC readout.
MRIBuilder.Variables.variables.phase
— Functionphase(pulse)
Return the phase of an RFPulseComponent
in degrees.
MRIBuilder.Variables.variables.qval
— Functionqval(gradient)
The norm of the variables.qvec
.
MRIBuilder.Variables.variables.qvec
— Functionqvec(overlapping[, first_event, last_event])
Computes the area under the curve for the gradient waveform in BaseBuildingBlock
.
If first_event
is set to something else than nothing
, only the gradient waveform after this RF pulse/Readout will be considered. Similarly, if last_event
is set to something else than nothing
, only the gradient waveform up to this RF pulse/Readout will be considered.
qvec(gradient)
The total integral of the area under the gradient curve as a length-3 vector.
The norm of this vector is available as qval
.
MRIBuilder.Variables.variables.ramp_overlap
— Functionramp_overlap(line_readout)
Return the fraction of the gradient ramp that overlaps with the ADC readout.
Set to 0 to ensure that the ADC is only active during the flat time of the readout.
MRIBuilder.Variables.variables.readout_times
— Functionreadout_times(sequence)
Returns all the times that the sequence will readout.
MRIBuilder.Variables.variables.resolution
— Functionresolution(readout)
Resolution of the readout.
MRIBuilder.Variables.variables.rise_time
— Functionrise_time(trapezoid)
Returns the rise time of a Trapezoid
gradient profile in ms.
MRIBuilder.Variables.variables.slew_rate
— Functionslew_rate(gradient)
Maximum 3D slew rate of the gradient in kHz/um/ms.
MRIBuilder.Variables.variables.slew_rate_norm
— Functionslew_rate_norm(gradient)
The norm of the variables.slew_rate
.
MRIBuilder.Variables.variables.slice_thickness
— Functionslice_thickness(slice_select)
Defines the slice thickness for a RF pulse with an active gradient in mm (e.g., SliceSelect
).
Defines as variables.gradient_strength_norm
(gradient) / variables.bandwidth
(pulse)
MRIBuilder.Variables.variables.spoiler
— Functionspoiler(gradient)
Spatial scale in mm over which the spoiler gradient will dephase by 2π.
Automatically computed based on variables.qvec
.
MRIBuilder.Variables.variables.time_to_center
— Functiontime_to_center(adc)
The time of the ADC readout to reach the center of k-space.
MRIBuilder.Variables.variables.voxel_size
— Functionvoxel_size(readout)
Defines the voxel size of a readout in mm.
MRIBuilder.Variables.variables.Δ
— Functiondiffusion_time(diffusion_sequence)
Returns the diffusion time of a DiffusionSpinEcho
in ms.
diffusion_time(sequence)
Computes the diffusion time of a sequence in ms.
MRIBuilder.Variables.variables.δ
— Functionδ(trapezoid)
Returns the effective duration of a Trapezoid
gradient profile in ms.
Defined as variables.rise_time
+ variables.flat_time
.
Variables interface
MRIBuilder.Variables
— ModuleDefines the functions that can be called on parts of an MRI sequence to query or constrain any variables.
In addition this defines:
variables
: module containing all variables.VariableType
: parent type for any variables (whether number or JuMP variable).get_free_variable
: helper function to create new JuMP variables.add_cost_function!
: add a specific term to the model cost functions.set_simple_constraints!
: callapply_simple_constraint!
for each keyword argument.apply_simple_constraint!
: set a simple equality constraint.get_pulse
/get_gradient
/get_readout
: Used to get the pulse/gradient/readout part of a building blockgradient_orientation
: returns the gradient orientation of a waveform if fixed.
MRIBuilder.Variables.VariableType
— TypeParent type for any variable in the MRI sequence.
Each variable can be one of:
- a new JuMP variable
- an expression linking this variable to other JuMP variable
- a number
Create these using get_free_variable
.
MRIBuilder.Variables.alternative_variables
— ConstantMapping of variable names to alternative ways to compute that variables.
MRIBuilder.Variables.default_generic_method
— ConstantContains for each variable the default, generic method.
This is the method that checks for alternative_functions
or uses one of the getters.
MRIBuilder.Variables.getter_functions
— ConstantMapping of symbols to actual getter functions.
Used in set_getter!
.
MRIBuilder.Variables.getters
— ConstantAssigns getters to specific variables.
MRIBuilder.Variables.AbstractBlock
— TypeParent type of all components, building block, and sequences that form an MRI sequence.
MRIBuilder.Variables.InvalidRoute
— TypeRaised if there is no way to reach a valid function by the default, generic method (default_generic_method
).
MRIBuilder.Variables._get_mult_variable
— MethodHelper to call the variable for a result of a getter. Used in _get_variable
.
MRIBuilder.Variables._get_variable
— Method_get_variable(name, tried_names, args...; kwargs...)
Tries to find a route to get values for the variable name
with the given args
and kwargs
.
The route through tried_names
has already been attempted.
This function returns the route to get to the value and the value itself.
MRIBuilder.Variables.add_alternative_variable!
— Methodadd_alternative_variable!(name, other_func, conversion)
Defines an alternative way to compute the variable with given name
.
If the variable name
is not defined and other_name
is, then the value of name
is computed by applying conversion
to the value of other_name
.
MRIBuilder.Variables.add_cost_function!
— Functionadd_cost_function!(function, level=2)
Adds an additional term to the cost function.
This term will be minimised together with any other terms in the cost function. Terms added at a lower level will be optimised before any terms with a higher level.
By default, the term is added to the level=2
, which is appropriate for any cost functions added by the developer, which will generally only be optimised after any user-defined cost functions (which are added at level=1
by add_simple_constraint!
or set_simple_constraints!
.
Any sequence will also have a level=3
cost function, which minimises the total sequence duration.
MRIBuilder.Variables.add_new_variable!
— Methodadd_new_variable!(name)
Adds a new variable in variables
.
This is a helper function, which is called by @defvar
for any variable that does not exist yet.
MRIBuilder.Variables.adjust_groups
— Methodadjust_groups(block)
Returns an array of keywords in adjust
that should affect a specfic block.
If any of these keywords are present in adjust
, then adjust_internal
will be called.
Some standard keywords are:
:gradient
: expects gradient adjustment parameters:pulse
: expects RF pulse adjustment parameters
MRIBuilder.Variables.adjust_internal
— Functionadjust_internal(block, names_used; kwargs...)
Returns the adjusted blocks and add any keywords used in the process to names_used
.
This is a helper function used by adjust
. It should be defined for any block that is adjustable (as defined by adjust_groups
).
MRIBuilder.Variables.apply_simple_constraint!
— Methodapply_simple_constraint!(variable, value)
Add a single constraint or objective to the variable
.
value
can be one of:
nothing
: do nothing:min
: minimise the variable:max
: maximise the variablenumber
: fix variable to this valueequation
: fix variable to the result of this equation
apply_simple_constraint!(variable, :>=/:<=, value)
Set an inequality constraint to the variable
.
value
can be a number of an equation.
MRIBuilder.Variables.base_variables
— Methodbase_variables([T])
Return dictionary with all Variable
objects defined for a specific sequence component/block T
.
This only returns those Variable
directly defined for this component/block, not for any sub-components (through get_pulse
, [get_gradient
][(@ref), etc.)
If T
is not provided, all Variable
objects are returned.
MRIBuilder.Variables.get_free_variable
— Methodget_free_variable(value; integer=false, start=0.01)
Get a representation of a given variable
given a user-defined constraint.
The result is guaranteed to be a VariableType
.
MRIBuilder.Variables.get_readout
— Functionget_readout(sequence)
Get the main readout events played out during the sequence.
This has to be defined for individual sequences to work.
Any readout
variables not explicitly defined for this sequence will be passed on to the readout.
MRIBuilder.Variables.gradient_orientation
— Functiongradient_orientation(building_block)
Returns the gradient orientation.
MRIBuilder.Variables.make_generic
— Functionmake_generic(sequence/building_block/component)
Returns a generic version of the BaseSequence
, BaseBuildingBlock
, or BaseComponent
- Sequences are all flattened and returned as a single
Sequence
containing onlyBuildingBlock
objects. - Any
BaseBuildingBlock
is converted into aBuildingBlock
. - Pulses are replaced with
GenericPulse
(except for instant pulses). - Instant readouts are replaced with
ADC
.
MRIBuilder.Variables.scanner_constraints!
— Methodscanner_constraints!(block)
Constraints variables.gradient_strength
and variables.slew_rate
to be less than the global_scanner
maximum.
MRIBuilder.Variables.set_getter!
— Methodset_getter!(variable_name, getter)
Set the getter function for variable_name
.
If the value for variable
is not defined for a sequence, the value for the result of the getter
function is returned instead.
Possible values for the getter
function are:
:pulse
:get_pulse
:gradient
:get_gradient
:readout
:get_readout
:pathway
:get_pathway
MRIBuilder.Variables.set_simple_constraints!
— Methodset_simple_constraints!(block, kwargs)
Add any constraints or objective functions to the variables of a AbstractBlock
.
Each keyword argument has to match one of the functions in variables
(block). If set to a numeric value, a constraint will be added to fix the function value to that numeric value. If set to :min
or :max
, minimising or maximising this function will be added to the cost function.
MRIBuilder.Variables.variable_defined_for
— Methodvariable_defined_for(variable, args...; kwargs...)
MRIBuilder.Variables.@defvar
— Macro@defvar(function(s))
Defines new variables
.
Each variable is defined as regular Julia functions embedded within a @defvar
macro. For example, to define a variables.echo_time
variable for a SpinEcho
sequence, one can use:
@defvar echo_time(ge::SpinEcho) = 2 * (variables.effective_time(ge, :refocus) - variables.effective_time(ge, :excitation))
Multiple variables can be defined in a single @defvar
by including them in a code block:
@defvar begin
function var1(seq::SomeSequenceType)
...
end
function var2(seq::SomeSequenceType)
...
end
end