diff --git a/src/MRIBuilder.jl b/src/MRIBuilder.jl index 4f82f40d2b1220791788696a8f88d8dcaf9c07c7..de4ecbcfda25928b5212b36a8ae634e7a1d61a7e 100644 --- a/src/MRIBuilder.jl +++ b/src/MRIBuilder.jl @@ -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 diff --git a/src/building_blocks.jl b/src/building_blocks.jl deleted file mode 100644 index 4d03278c70aefb661fcf16a5e8857bb07478340e..0000000000000000000000000000000000000000 --- a/src/building_blocks.jl +++ /dev/null @@ -1,192 +0,0 @@ -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 diff --git a/src/sequences.jl b/src/sequences.jl deleted file mode 100644 index 68cc45d45452ae5d555bad4b8a060ec6cde8be23..0000000000000000000000000000000000000000 --- a/src/sequences.jl +++ /dev/null @@ -1,61 +0,0 @@ -""" -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 - - diff --git a/src/wait.jl b/src/wait.jl deleted file mode 100644 index b672e95b69dea718fa45f404aa8488fa3896f32a..0000000000000000000000000000000000000000 --- a/src/wait.jl +++ /dev/null @@ -1,48 +0,0 @@ -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