diff --git a/src/MRIBuilder.jl b/src/MRIBuilder.jl index 2eab1cae635043a69d1d41de32809bbf94ed4e8d..6d7e3f1ee634feaac3b266732a3519fdd1928833 100644 --- a/src/MRIBuilder.jl +++ b/src/MRIBuilder.jl @@ -10,6 +10,7 @@ include("components/components.jl") include("containers/containers.jl") include("pathways.jl") include("parts/parts.jl") +include("sequences/sequences.jl") include("printing.jl") import .BuildSequences: build_sequence, global_model, global_scanner, fixed @@ -18,8 +19,8 @@ export build_sequence, global_model, global_scanner, fixed import .Scanners: Scanner, B0, Siemens_Connectom, Siemens_Prisma, Siemens_Terra export Scanner, B0, Siemens_Connectom, Siemens_Prisma, Siemens_Terra -import .Variables: variables, duration, 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, nsamples, oversample, dwell_time, ramp_overlap, spoiler_scale, TR, Δ, get_gradient, get_pulse, get_readout -export variables, duration, 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, nsamples, oversample, dwell_time, ramp_overlap, spoiler_scale, TR, Δ, get_gradient, get_pulse, get_readout +import .Variables: variables, duration, 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, nsamples, oversample, dwell_time, ramp_overlap, spoiler_scale, repetition_time, TR, Δ, get_gradient, get_pulse, get_readout, TE, echo_time +export variables, duration, 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, nsamples, oversample, dwell_time, ramp_overlap, spoiler_scale, repetition_time, TR, Δ, get_gradient, get_pulse, get_readout, TE, echo_time import .Components: InstantPulse, ConstantPulse, SincPulse, GenericPulse, InstantGradient, SingleReadout, ADC export InstantPulse, ConstantPulse, SincPulse, GenericPulse, InstantGradient, SingleReadout, ADC @@ -33,6 +34,9 @@ export Pathway, duration_transverse, duration_dephase, bval, bmat, get_pathway import .Parts: dwi_gradients, readout_event, excitation_pulse, refocus_pulse, Trapezoid, SliceSelect, LineReadout, opposite_kspace_lines, SpoiltSliceSelect, SliceSelectRephase, EPIReadout, interpret_image_size export dwi_gradients, readout_event, excitation_pulse, refocus_pulse, Trapezoid, SliceSelect, LineReadout, opposite_kspace_lines, SpoiltSliceSelect, SliceSelectRephase, EPIReadout, interpret_image_size +import .Sequences: GradientEcho +export GradientEcho + import JuMP: @constraint, @objective, objective_function, value, Model export @constraint, @objective, objective_function, value, Model diff --git a/src/containers/sequences.jl b/src/containers/base_sequences.jl similarity index 74% rename from src/containers/sequences.jl rename to src/containers/base_sequences.jl index bfa46ba0c11de79a5c0ac9ef4e83d668901a5b82..e22b5a5618e3c6f5278dbe0555db124e3087df42 100644 --- a/src/containers/sequences.jl +++ b/src/containers/base_sequences.jl @@ -1,10 +1,10 @@ """ Defines [`BaseSequence`](@ref) and [`Sequence`](@ref) """ -module Sequences +module BaseSequences import StaticArrays: SVector import JuMP: @constraint -import ...Variables: get_free_variable, TR, VariableType, duration, variables, VariableNotAvailable, Variables, set_simple_constraints! +import ...Variables: get_free_variable, repetition_time, VariableType, duration, variables, VariableNotAvailable, Variables, set_simple_constraints!, TR import ...BuildSequences: global_model import ...Components: EventComponent import ..Abstract: ContainerBlock, start_time @@ -22,8 +22,6 @@ Main interface: Sub-types need to implement: - `get_index_single_TR`: return the index assuming it is between 1 and N -- [`nrepeat`](@ref): how often the sequence should repeat (if not implemented, this will be 1). -- [`TR`](@ref): time scale on which to repeat (if not implemented, this will be the sum of the individual block durations). """ abstract type BaseSequence{N} <: ContainerBlock end @@ -49,7 +47,7 @@ function start_time(bs::BaseSequence{N}, index::Integer) where {N} if iszero(nTR) return base_time else - return nTR * TR(bs) + base_time + return nTR * repetition_time(bs) + base_time end end @@ -79,28 +77,28 @@ How often sequence should be repeated. """ nrepeat(bs::BaseSequence) = 1 -""" - TR(sequence) - -On what timescale the sequence should repeat itself in ms (will have no effect if [`nrepeat`](@ref) is one). - -By default this is set to the total duration of the sequence. -""" -TR(bs::BaseSequence) = duration(bs) +repetition_time(bs::BaseSequence) = duration(bs) duration(bs::BaseSequence{0}) = 0. duration(bs::BaseSequence) = sum(duration.(bs); init=0.) """ - Sequence(blocks; TR=:min, nrepeat=0) - Sequence(blocks...; TR=:min, nrepeat=0) + Sequence(blocks; name=:Sequence, variables...) + Sequence(blocks...; name=:Sequence, variables...) Defines an MRI sequence from a vector of building blocks. ## Arguments -- [`nrepeat`](@ref): how often the sequence will repeat itself (keep at default of 0 to repeat indefinetely). -- [`TR`](@ref): how long between repeats in ms (defaults to the duration of the sequence). Can be set to `nothing` to be a free variable. +- `blocks`: The actual building blocks that will be played in sequence. All the building blocks must be of type [`ContainerBlock`](@ref), which means that they cannot only contain actual [`BaseBuildingBlock`](@ref) objects, but also other [`BaseSequence`](@ref) objects. + Objects of a different type are converted into a [`ContainerBlock`](@ref) internally: + - numbers/`nothing`/`:min`/`:max` : replaced with a [`Wait`](@ref) block with the appropriate constraint/objective added to its [`duration`](@ref). + - RF pulse or readout: will be embedded within a [`BuildingBlock`](@ref) of the appropriate length + +## Variables +- [`repetition_time`](@ref)/[`TR`](@ref): how long between repeats in ms. This is always set to the total length of the sequence. If you want to add some down-time between repeats, you can simply add a [`Wait`](@ref) block of the appropriate length at the end of the sequence. + +Specific named sequences might define additional variables. """ struct Sequence{S, N} <: BaseSequence{N} blocks :: SVector{N, Pair{<:Union{Symbol, Nothing}, <:ContainerBlock}} diff --git a/src/containers/containers.jl b/src/containers/containers.jl index c62f548d5c6b2fd45fd926889cf9785c69534863..871e9de15b1ef235738979e17c18168c34c2e7a5 100644 --- a/src/containers/containers.jl +++ b/src/containers/containers.jl @@ -1,12 +1,12 @@ module Containers include("abstract.jl") include("building_blocks.jl") -include("sequences.jl") +include("base_sequences.jl") include("alternatives.jl") import .Abstract: ContainerBlock, start_time, end_time, effective_time import .BuildingBlocks: BaseBuildingBlock, BuildingBlock, Wait, waveform, waveform_sequence, events -import .Sequences: BaseSequence, Sequence, nrepeat, get_index_single_TR +import .BaseSequences: BaseSequence, Sequence, nrepeat, get_index_single_TR import .Alternatives: AlternativeBlocks, match_blocks! end \ No newline at end of file diff --git a/src/scanners.jl b/src/scanners.jl index ea886d2cd5238060455d493f9c44dd34c7b429b7..6af82bd48902e0ea5c8df6f9b5388a1fe187d4e2 100644 --- a/src/scanners.jl +++ b/src/scanners.jl @@ -63,6 +63,13 @@ By setting `units` to :Tesla, the slew rate can be returned in T/m/s instead. slew_rate(scanner::Scanner, units=:kHz) = units == :kHz ? scanner.slew_rate : scanner.slew_rate / (gyromagnetic_ratio * 1e-9) +""" +A default 1.5T scanner. + +Matches the one used in `pulseq` (https://github.com/pulseq/pulseq/blob/master/matlab/%2Bmr/opts.m). +""" +Default_Scanner = Scanner(B0=1.5, gradient=40, slew_rate=170, units=:Tesla) + """ Siemens MAGNETOM 3T Prisma MRI scanner (https://www.siemens-healthineers.com/en-uk/magnetic-resonance-imaging/3t-mri-scanner/magnetom-prisma). """ diff --git a/src/sequences/gradient_echos.jl b/src/sequences/gradient_echos.jl new file mode 100644 index 0000000000000000000000000000000000000000..3eaee59a4db93fda432de6ed687a3ffae388bcbf --- /dev/null +++ b/src/sequences/gradient_echos.jl @@ -0,0 +1,52 @@ +module GradientEchos +import ...Containers: Sequence +import ...Parts: excitation_pulse, readout_event, interpret_image_size, Trapezoid, gradient_spoiler +import ...Variables: get_pulse, get_readout, echo_time, duration_transverse +import ...Pathways: Pathway, get_pathway +import ...BuildSequences: build_sequence +import ...Scanners: Default_Scanner + +const GradientEcho = Sequence{:GradientEcho} + +""" + GradientEcho(; echo_time, excitation=(), readout=(), resolution/fov/voxel_size/slice_thickness) + +Defines a gradient echo sequence with a single readout event. + +By default, an instant excitation pulse and readout event are used. +If image parameters are provided, this will switch to a sinc pulse and EPI readout. + +## Parameters +- [`excitation`](@ref): properties of the excitation pulse as described in [`excitation_pulse`](@ref). +- [`readout`](@ref): properties of the readout as described in [`readout_event`](@ref). +- [`spoiler`](@ref): if set adds a spoiler [`gradient_spoiler`](@ref) gradient after the readout (e.g., `spoiler=(spoiler_scale=1, orientation=[0, 0, 1], group=:FOV)` to add a gradient in the z-direction of the `FOV` coordinate system that fully dephases spins over 1 mm). +- Image parameters ([`resolution`](@ref)/[`fov`](@ref)/[`voxel_size`](@ref)/[`slice_thickness`](@ref)): describe the properties of the resulting image. See [`interpret_image_size`](@ref) for details. + +## Variables +- [`TE`](@ref)/[`echo_time`](@ref): echo time between excitation pulse and readout in ms (required). +- [`TR`](@ref)/[`repetition_time`](@ref)/[`duration`](@ref): total duration of the sequence from start of excitation pulse to end of readout or spoiler in ms. +""" +function GradientEcho(; excitation=(), readout=(), spoiler=nothing, resolution=nothing, fov=nothing, voxel_size=nothing, slice_thickness=nothing, scanner=Default_Scanner, variables...) + build_sequence(scanner) do + (slice_thickness, _, extra_readout_params) = interpret_image_size(fov, resolution, voxel_size, slice_thickness) + parts = Any[ + :excitation => excitation_pulse(; slice_thickness=slice_thickness, excitation...), + nothing, + :readout => readout_event(; extra_readout_params..., readout...), + ] + if !isnothing(spoiler) + push!(parts, gradient_spoiler(; spoiler...)) + end + return Sequence(parts; name=:GradientEcho, variables...) + end +end + + +get_pulse(ge::GradientEcho) = ge.excitation +get_readout(ge::GradientEcho) = ge.readout +get_pathway(ge::GradientEcho) = Pathway(ge, [90], 1) +echo_time(ge::GradientEcho) = duration_transverse(ge) + + + +end \ No newline at end of file diff --git a/src/sequences/sequences.jl b/src/sequences/sequences.jl new file mode 100644 index 0000000000000000000000000000000000000000..209e87746666242d130c6761ebf9ebf4d77097c7 --- /dev/null +++ b/src/sequences/sequences.jl @@ -0,0 +1,6 @@ +module Sequences +include("gradient_echos.jl") + +import .GradientEchos: GradientEcho + +end \ No newline at end of file diff --git a/src/variables.jl b/src/variables.jl index 3ebb8fe5773fe035d755e7b9efe45dd386621319..9d6a73816393b50ea7b050ae63108dd4b3fc0bfc 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -34,7 +34,10 @@ all_variables_symbols = [ :duration => "duration of the building block in ms.", ], :sequence => [ - :TR => "Time on which an MRI sequence repeats itself in ms.", + :TR => "Time on which an MRI sequence repeats itself in ms. Defaults to the result of [`repetition_time`](@ref).", + :repetition_time => "Time on which an MRI sequence repeats itself in ms.", + :TE => "Echo time of the sequence in ms. Defaults to the result of [`echo_time`](@ref).", + :echo_time => "Echo time of the sequence in ms.", :Δ => "Diffusion time in ms (i.e., time between start of the diffusion-weighted gradients).", :qvec => "Net dephasing due to gradients in rad/um.", :area_under_curve => "Net dephasing due to gradients in rad/um (same as [`qvec`](@ref)).", @@ -98,6 +101,9 @@ for (block_symbol, all_functions) in all_variables_symbols end +TE(ab::AbstractBlock) = echo_time(ab) +TR(ab::AbstractBlock) = repetition_time(ab) + """ Dictionary with alternative versions of specific function. @@ -228,7 +234,7 @@ end for (target_name, all_vars) in all_variables_symbols for (variable_func, _) in all_vars - if variable_func == :qval3 + if variable_func in [:qval3, :TR, :TE] continue end get_func = Symbol("get_" * string(target_name))