diff --git a/src/all_building_blocks/base_building_blocks.jl b/src/all_building_blocks/base_building_blocks.jl index e6b75a8890f0aade116fe5e6a32a41490bb095f2..cd7d0137fb5c1a9c55b887fb2515e834ff9c92c8 100644 --- a/src/all_building_blocks/base_building_blocks.jl +++ b/src/all_building_blocks/base_building_blocks.jl @@ -15,8 +15,9 @@ Main interface: - [`qvec`](@ref) returns area under curve for (part of) the gradient waveform. Sub-types need to implement: -- `Base.keys`: returns sequence of keys to all the components -- `Base.getindex`: returns the actual component for each key +- `Base.keys`: returns sequence of keys to all the components. +- `Base.getindex`: returns the actual component for each key. +- [`duration`](@ref): total duration of the building block. """ abstract type BaseBuildingBlock <: ContainerBlock end @@ -106,7 +107,7 @@ Gets the sequence of [`GradientWaveform`](@ref) from the event with key `first` Setting `first` to nothing indicates to start from the beginning of the `building_block`. Similarly, setting `last` to nothing indicates to continue till the end of the `building_block`. """ -function waveform_seqeuence(bb::BaseBuildingBlock, first, last) +function waveform_sequence(bb::BaseBuildingBlock, first, last) started = isnothing(first) current_grad = current_start = nothing parts = GradientWaveform[] diff --git a/src/all_building_blocks/building_blocks.jl b/src/all_building_blocks/building_blocks.jl new file mode 100644 index 0000000000000000000000000000000000000000..75354d50beaa60358cbfd5899956fb4626820fc7 --- /dev/null +++ b/src/all_building_blocks/building_blocks.jl @@ -0,0 +1,75 @@ +module BuildingBlocks +import LinearAlgebra: norm +import ..BaseBuildingBlocks: BaseBuildingBlock, events, waveform_sequence +import ...Variables: VariableType, duration, make_generic +import ...Components: BaseComponent, DelayedEvent +import ...Readouts: InstantReadout, ADC +import ...Pulses: RFPulseBlock +import ...Gradients: GradientBlock +import ...BuildingBlocks: scanner_constraints!, make_generic + +""" + BuildingBlock(waveform, events; min_duration=nothing, orientation=nothing) + +Generic [`BaseBuildingBlock`](@ref) that can capture any overlapping gradients, RF pulses, and/or readouts. +The gradients cannot contain any free variables. + +## Arguments +- `waveform`: Sequence of 2-element tuples with (time, (Gx, Gy, Gz)). If `orientation` is set then the tuple is expected to look like (time, G). This cannot contain any free variables. +- `events`: Sequence of 2-element tuples with (index, pulse/readout). The start time of the pulse/readout at the start of the gradient waveform element with index `index` (use [`DelayedEvent`](@ref) to make this earlier or later). +- `duration`: duration of this `BuildingBlock`. If not set then it will be assumed to be the time of the last element in `waveform`. +- `orientation`: orientation of the gradients in the waveform. If not set, then the full gradient vector should be given explicitly. +""" +struct BuildingBlock <: BaseBuildingBlock + parts :: Vector{<:BaseComponent} + function BuildingBlock(parts::AbstractVector{<:BaseComponent}) + res = new(duration, parts) + for (_, part) in waveform_sequence(parts) + scanner_constraints!(part) + end + return res + end +end + +function BuildingBlock(waveform::AbstractVector, events::AbstractVector; duration=nothing, orientation=nothing) + events = Any[events...] + waveform = Any[waveform...] + ndim = isnothing(orientat) ? 1 : 3 + zero_grad = isnothing(orientation) ? zeros(3) : 0. + if length(waveform) == 0 || waveform[1][1] > 0. + pushfirst!(waveform, (0., zero_grad)) + events = [(i+1, e) for (i, e) in events] + end + + if isnothing(min_duration) + min_duration = waveform[end][1] + end + if !(min_duration ≈ waveform[end][1]) + @assert min_duration > waveform[end][1] + push!(waveform, (min_duration, zero_grad)) + end + components = BaseComponent[] + for (index_grad, ((prev_time, prev_grad), (time, grad))) in enumerate(zip(waveform[1:end-1], waveform[2:end])) + duration = time - prev_time + if norm(prev_grad) <= 1e-12 && norm(grad) <= 1e-12 + push!(components, NoGradientBlock{ndim}(duration)) + elseif norm(prev_grad) ≈ norm(grad) + push!(components, ConstantGradientBlock(prev_grad, duration)) + else + push!(components, ChangingGradientBlock(prev_grad, (grad .- prev_grad) ./ duration, duration)) + end + while length(events) > 0 && index_grad == events[1][1] + (_, event) = popfirst!(events) + push!(components, event) + end + end + return components +end + +make_generic(other_block::BaseBuildingBlock) = BuildingBlock(duration(other_block), [other_block...]) +Base.keys(bb::BuildingBlock) = 1:length(bb.parts) +Base.getindex(bb::BuildingBlock, i::Integer) = bb.parts[i] + +duration(go::BuildingBlock) = go.duration + +end \ No newline at end of file diff --git a/src/overlapping/generic.jl b/src/overlapping/generic.jl deleted file mode 100644 index 27bba0e856665015d8ecbcbc67a41772f528a4f7..0000000000000000000000000000000000000000 --- a/src/overlapping/generic.jl +++ /dev/null @@ -1,46 +0,0 @@ -module Generic -import ..Abstract: AbstractOverlapping, interruptions, duration, waveform_sequence -import ...Variables: VariableType, duration -import ...Wait: WaitBlock -import ...Readouts: InstantReadout, ADC -import ...Pulses: RFPulseBlock -import ...Gradients: GradientBlock -import ...BuildingBlocks: scanner_constraints!, make_generic - -""" - GenericWaveform(duration, waveform, interruptions) - -Generic description that can capture any overlapping gradients, RF pulses, and/or readouts. - -Interruptions are stores as tuples with 3 fields: -- `index`: which part of the `waveform` is being interrupted (cannot be a free variable). -- `time`: time of the interruption relative to the start of the `waveform` part (between 0 and the length of this part). This can be a variable. -- `block`: [`RFPulseBlock`](@ref) or [`InstantReadout`](@ref) -""" -struct GenericOverlapping <: AbstractOverlapping - duration :: VariableType - waveform :: Vector{Union{WaitBlock, GradientBlock}} - interruptions :: Vector{NamedTuple{(:index, :time, :object), Tuple{Int64, <:VariableType, <:Union{RFPulseBlock, InstantReadout, ADC}}}} - function GenericOverlapping(duration::VariableType, waveform::AbstractVector, interruptions::AbstractVector=[]) - res = new(duration, waveform, interruptions) - scanner_constraints!.(waveform) - return res - end -end - - -GenericOverlapping(other_waveform::AbstractOverlapping) = GenericOverlapping(duration(other_waveform), waveform_sequence(other_waveform), interruptions(other_waveform)) - -make_generic(ao::AbstractOverlapping) = GenericOverlapping( - duration(ao), - waveform_sequence(ao), - [(index=i.index, time=i.time, object=make_generic(i.object)) for i in interruptions(ao)] -) - -waveform_sequence(go::GenericOverlapping) = go.waveform -interruptions(go::GenericOverlapping) = go.interruptions -duration(go::GenericOverlapping) = go.duration - -make_generic(wait::WaitBlock) = GenericOverlapping(duration(wait), [], []) - -end \ No newline at end of file