Skip to content
Snippets Groups Projects
Verified Commit bed96769 authored by Michiel Cottaar's avatar Michiel Cottaar
Browse files

Update for refactor into component/building block/sequence

parent 98814ce2
No related branches found
No related tags found
No related merge requests found
......@@ -7,12 +7,9 @@ include("scanners.jl")
include("build_sequences.jl")
include("variables.jl")
include("building_blocks.jl")
include("wait.jl")
include("gradients/gradients.jl")
include("pulses/pulses.jl")
include("readouts/readouts.jl")
include("overlapping/overlapping.jl")
include("sequences.jl")
include("components/components.jl")
include("all_building_blocks/all_building_blocks.jl")
include("all_sequences/all_sequences.jl")
include("alternatives.jl")
include("pathways.jl")
include("helper_functions.jl")
......@@ -27,26 +24,14 @@ export Scanner, B0, Siemens_Connectom, Siemens_Prisma, Siemens_Terra
import .Variables: variables, duration, start_time, end_time, effective_time, flip_angle, amplitude, phase, frequency, bandwidth, N_left, N_right, qval, δ, rise_time, flat_time, slew_rate, gradient_strength, qvec, qval_square, slice_thickness, inverse_slice_thickness, fov, inverse_fov, voxel_size, inverse_voxel_size, resolution
export variables, duration, start_time, end_time, effective_time, flip_angle, amplitude, phase, frequency, bandwidth, N_left, N_right, qval, δ, rise_time, flat_time, slew_rate, gradient_strength, qvec, qval_square, slice_thickness, inversne_slice_thickness, fov, inverse_fov, voxel_size, inverse_voxel_size, resolution
import .BuildingBlocks: BuildingBlock, fixed, get_children_blocks, get_children_indices, make_generic
export BuildingBlocks, fixed, get_children_blocks, get_children_indices, make_generic
import .Components: InstantRFPulse, ConstantPulse, SincPulse, GenericPulse, InstantGradient, SingleReadout, ADC
export InstantRFPulse, ConstantPulse, SincPulse, GenericPulse, InstantGradient, SingleReadout, ADC
import .Wait: WaitBlock
export WaitBlock
import .AllBuildingBlocks: waveform, waveform_sequence, events, BaseBuildingBlock, BuildingBlock, Trapezoid, SliceSelect, LineReadout, SpoiltSliceSelect, Wait
export waveform, waveform_sequence, events, BaseBuildingBlock, BuildingBlock, Trapezoid, SliceSelect, LineReadout, SpoiltSliceSelect, Wait
import .Pulses: InstantRFPulseBlock, ConstantPulse, SincPulse, GenericPulse
export InstantRFPulseBlock, ConstantPulse, SincPulse, GenericPulse
import .Gradients: InstantGradientBlock
export InstantGradientBlock
import .Readouts: InstantReadout, ADC
export InstantReadout, ADC
import .Overlapping: TrapezoidGradient, SpoiltSliceSelect, interruptions, waveform, SingleLine, opposite_kspace_lines, waveform_sequence
export TrapezoidGradient, SpoiltSliceSelect, interruptions, waveform, SingleLine, opposite_kspace_lines, waveform_sequence
import .Sequences: Sequence
export Sequence
import .AllSequences: BaseSequence, nrepeat, Sequence
export BaseSequence, nrepeat, Sequence
import .Alternatives: AlternativeBlocks
export AlternativeBlocks
......
module BuildingBlocks
import JuMP: value, Model, @constraint, @objective, objective_function, AbstractJuMPScalar
import ..Variables: Variables, variables, start_time, duration, end_time, gradient_strength, slew_rate, effective_time, VariableType, alternative_variables, qval_square
import ..BuildSequences: global_model, global_scanner, fixed
import ..Scanners: Scanner
"""
Parent type for all individual components out of which a sequence can be built.
Required methods:
- [`duration`](@ref)(block, parameters): Return block duration in ms.
- [`variables`](@ref): A list of all functions that are used to compute variables of the building block. Any of these can be used in constraints or objective functions.
"""
abstract type BuildingBlock end
"""
Parent type for all RF pulses.
RF pulses combined with gradients, should be childrent of [`ContainerBlock`](@ref) instead.
Required methods:
- [`effective_time`](@ref)(pulse): Best approximation of time the RF pulse is applied. This is defined relative to the start of the pulse.
"""
abstract type RFPulseBlock <: BuildingBlock end
"""
Parent type for all gradient profiles.
"""
abstract type GradientBlock <: BuildingBlock end
"""
Parent type for all types combining one or more pulses/gradients.
Required methods:
- [`get_children_blocks`](@ref)(container): return all the [`BuildingBlock`](@ref) objects includes in this container.
- [`start_time`](@ref)(container, index): returns the starting time of the child corresponding to `index` relative to the start of the `container` in ms.
- `Base.getindex`(container, index): get child [`BuildingBlock`](@ref) corresponding to `index`.
"""
abstract type ContainerBlock <: BuildingBlock end
"""
get_children_blocks(container)
Return all the [`BuildingBlock`](@ref) objects includes in this container.
"""
get_children_blocks(bb::BuildingBlock) = [bb[i] for i in get_children_indices(bb)]
"""
get_children_indices(container)
Return the indices of all the children in a [`ContainerBlock`](@ref).
This needs to be defined for every [`ContainerBlock`](@ref).
"""
function get_children_indices end
"""
start_time(container, args...)
Returns the starting time of the specific [`BuildingBlock`](@ref) within the container.
The [`BuildingBlock`](@ref) is defined by one or more indices as defined below.
"""
start_time(bb::BuildingBlock) = 0.
start_time(container::ContainerBlock, index1, index2, more_indices...) = start_time(container, index1) + start_time(container[index1], index2, more_indices)
"""
effective_time(pulse)
effective_time(readout)
effective_time(container, indices...)
Returns the effective time of a pulse or readout.
For a pulse, this means the timepoint at which one would place an [`InstantRFPulseBlock`](@ref) if one would want to have a similar effect.
For a reaodut, this is the time the readout passes through the zero-point in k-space (or the minimum in k-space if it does not go through zero).
The time is given with respect to the start of the pulse or readout, or to the start of a container if the pulse/readout is identified using indices.
"""
effective_time(bb::ContainerBlock, index, indices...) = start_time(bb, index) + effective_time(bb[index], indices...)
"""
end_time(container, args...)
Returns the end time of the specific [`BuildingBlock`](@ref) within the container.
The [`BuildingBlock`](@ref) is defined by one or more indices as defined below.
"""
end_time(bb::BuildingBlock) = duration(bb::BuildingBlock)
end_time(container::ContainerBlock, index1, indices...) = start_time(container, index1) + end_time(container[index1], indices...)
"""
to_block(object)
Function used internally to convert a wide variety of objects into [`BuildingBlock`](@ref) objects.
"""
to_block(bb::BuildingBlock) = bb
"""
match_blocks!(block1, block2, property_list)
Matches the listed variables between two [`BuildingBlock`](@ref) objects.
"""
function match_blocks!(block1::BuildingBlock, block2::BuildingBlock, property_list)
for fn in property_list
@constraint global_model() fn(block1) == fn(block2)
end
end
"""
scanner_constraints!(building_block[, scanner])
Adds the gradient strength and slew rate constraints from a specific [`Scanner`](@ref) to a [`BuildingBlock`]{@ref}.
This is applied iteratively to each part of a `Sequence`.
"""
function scanner_constraints!(building_block::BuildingBlock)
try
scanner_constraints!(building_block, global_scanner())
catch e
if occursin("No valid scanner", e.msg)
return
end
rethrow()
end
end
function scanner_constraints!(building_block::BuildingBlock, scanner::Scanner)
for func in [gradient_strength, slew_rate]
if isfinite(func(scanner))
scanner_constraints!(building_block, scanner, func)
end
end
end
function scanner_constraints!(building_block::BuildingBlock, scanner::Scanner, func::Function)
model = global_model()
try
# apply constraint at this level
res_bb = func(building_block)
if res_bb isa AbstractVector
if isnothing(building_block.rotate)
# no rotation; apply constraint to each dimension independently
for expr in res_bb
@constraint model expr <= func(scanner)
@constraint model expr >= -func(scanner)
end
else
# with rotation: apply constraint to total squared
total_squared = sum(map(n->n^2, res_bb))
@constraint model total_squared <= func(scanner)^2
end
else
@constraint model res_bb <= func(scanner)
@constraint model res_bb >= -func(scanner)
end
catch e
if !(e isa VariableNotAvailable)
rethrow()
end
if building_block isa ContainerBlock
for child_block in get_children_blocks(building_block)
scanner_constraints!(child_block, scanner, func)
end
end
end
end
function fixed(bb::BuildingBlock)
arguments = []
for name in propertynames(bb)
push!(arguments, fixed(getproperty(bb, name)))
end
return typeof(bb)(arguments...)
end
"""
make_generic(bb::BuildingBlock)
Replaces a [`BuildingBlock`](@ref) or whole sequence with a generic version.
This replaces all functional RF pulses and gradient waveforms with their generic equivalents.
"""
function make_generic end
end
\ No newline at end of file
"""
Define the [`Sequence`](@ref) building block.
"""
module Sequences
import JuMP: @constraint
import ...BuildSequences: global_model
import ...Variables: variables, start_time, duration, VariableType, get_free_variable, TR, end_time
import ...BuildingBlocks: BuildingBlock, ContainerBlock, to_block, get_children_indices, fixed, fixed, make_generic
"""
Sequence(building_blocks...; TR=nothing)
Sequence([building_blocks]; TR=nothing)
Represents a series of [`BuildingBlock`](@ref) objects run in turn.
This can be used as a top-level NMR/MRI sequence (in which case the [`TR`](@ref) variable is relevant)
or be embedded as a [`BuildingBlock`](@ref) into higher-order `Sequence` or other [`ContainerBlock`](@ref) objects.
## Variables
- [`TR`](@ref): repetition time of sequence in ms.
"""
struct Sequence <: ContainerBlock
_blocks :: Vector{<:BuildingBlock}
TR :: VariableType
function Sequence(blocks::AbstractVector; TR=nothing)
seq = new(
to_block.(blocks),
get_free_variable(TR),
)
if !(TR isa Number && (isinf(TR) || duration(seq) isa Number))
@constraint global_model() seq.TR >= duration(seq)
end
return seq
end
end
Sequence(blocks...; TR=nothing) = Sequence([blocks...]; TR=TR)
fixed(seq::Sequence) = Sequence(fixed.(seq._blocks), TR=fixed(seq.TR))
make_generic(seq::Sequence) = Sequence(make_generic.(seq._blocks), TR=seq.TR)
Base.length(seq::Sequence) = length(seq._blocks)
Base.getindex(seq::Sequence, index) = seq._blocks[index]
get_children_indices(seq::Sequence) = eachindex(seq._blocks)
"""
start_time(sequence::Sequence, index::Integer, args...)
Returns the starting time of the [`BuildingBlock`](@ref) with index `index`.
Additional `args` can be used to select a sub-block of that [`BuildingBlock`](@ref).
The starting time is returned with respect to the start of this sequence.
"""
start_time(seq::Sequence, index::Integer) = isone(index) ? start_time(seq) : (start_time(seq, index-1) + duration(seq[index-1]))
duration(seq::Sequence) = end_time(seq, length(seq))
TR(seq::Sequence) = seq.TR
end
module Wait
import JuMP: @constraint, @variable, VariableRef, value
import ..Variables: VariableType, variables, duration, get_free_variable
import ..BuildingBlocks: BuildingBlock, to_block, make_generic
import ..BuildSequences: global_model
import ...Scanners: Scanner
"""
WaitBlock(duration)
An empty [`BuildingBlock`](@ref) of given `duration` (in ms).
Duration can be set to one of:
- numeric value to fix it
- `:min` to minimise its value given any external constraints
- `:max` to maximise its value given any external constraints
- `nothing` to make it fully determined by external constraints and objectives
"""
struct WaitBlock <: BuildingBlock
duration :: VariableType
function WaitBlock(duration=nothing)
if duration isa Number
return new(duration)
end
res = new(
get_free_variable(duration),
)
@constraint global_model() res.duration >= 0
return res
end
end
"""
to_block(JuMP variable/:min or :max/nothing/number)
Converts object into a [`WaitBlock`](@ref).
- if a Number or a JuMP variable the wait time will match that value.
- if `:min`, the wait time will be minimised in the final sequence (limited by any external constraints).
- if `:max`, the wait time will be maximised in the final sequence (limited by any external constraints).
- if `nothing`, the wait time is only constrained by external factors.
"""
to_block(time::Union{VariableType, Symbol, Nothing, Val{:min}, Val{:max}}) = WaitBlock(time)
duration(wb::WaitBlock) = wb.duration
make_generic(wb::WaitBlock) = wb
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