Newer
Older
import ...Components: GradientWaveform, split_timestep, InstantPulse, InstantGradient3D
import ...Variables: amplitude, phase, gradient_strength3, duration
import ..Abstract: edge_times, start_time, end_time, ContainerBlock, iter_instant_gradients, iter_instant_pulses
import ..BaseSequences: BaseSequence, Sequence
import ..BuildingBlocks: BaseBuildingBlock
struct LinearPart{T}
start_value :: T
end_value :: T
end
"""
SequencePart(sequence, time1, time2)
Represents the time between `time1` and `time2` of a larger [`LinearSequence`](@ref)
The gradient, RF amplitude, and RF phase are all be modeled as changing linearly during this time.
"""
struct SequencePart
gradient :: LinearPart{SVector{3, Float64}}
gradient_origin :: SVector{3, Float64}
rf_amplitude :: LinearPart{Float64}
rf_phase :: LinearPart{Float64}
duration :: Float64
end
function SequencePart(sequence::BaseSequence{N}, time1::Number, time2::Number) where {N}
tmean = (time1 + time2) / 2
nTR = div(tmean, duration(sequence), RoundDown)
time1 -= nTR * duration(sequence)
time2 -= nTR * duration(sequence)
tmean -= nTR * duration(sequence)
if -1e-9 < time1 < 0.
time1 = 0.
end
if time2 > duration(sequence) && time2 ≈ duration(sequence)
time2 = duration(sequence)
end
if !(0 <= time1 <= time2 <= duration(sequence))
error("Initial time ($time1) and final time ($time2) are not part of the same TR.")
return SequencePart(sequence[key], time1 - start_time(sequence, key), time2 - start_time(sequence, key))
end
end
end
function SequencePart(bb::BaseBuildingBlock, time1::Number, time2::Number)
if -1e-9 < time1 < 0.
time1 = 0.
end
if time2 > duration(bb) && time2 ≈ duration(bb)
time2 = duration(bb)
end
if !(0 <= time1 <= time2 <= duration(bb))
error("Sequence timings are out of bound")
end
tmean = (time1 + time2) / 2
function fit_linear_part(fn)
start = fn(bb, time1)
final = fn(bb, time2)
middle = fn(bb, tmean)
mid_deviation = @. middle - (start + final) / 2
return LinearPart(start .+ mid_deviation ./ 3, final .+ mid_deviation ./ 3)
return SequencePart(
fit_linear_part(gradient_strength3),
zero(SVector{3, Float64}),
fit_linear_part(amplitude),
fit_linear_part(phase),
time2 - time1
)
duration(sp::SequencePart) = sp.duration
"""
split_times(sequence(s); precision=0.01, max_timestep=Inf)
Suggests at what times to split one or more sequence into linear parts (see [`linearise`](@ref)).
The split times will include any time when (for any of the provided sequences):
- the starting/end points of building blocks, gradients, RF pulses, or ADC readouts.
- a gradient or RF pulse is discontinuous in the first derivative
- the time of any instantaneous gradients, RF pulses, or readouts.
Continuous gradient waveforms or RF pulses might be split up further to ensure the linear approximations meet the required `precision` (see [`split_timestep`](@ref)).
"""
split_times(sequence::BaseSequence, args...; kwargs...) = split_times([sequence], args...; kwargs...)
split_times(sequences::AbstractVector{<:BaseSequence}; kwargs...) = split_times(sequences, 0., maximum(duration.(sequences)); kwargs...)
function split_times(sequences::AbstractVector{<:BaseSequence}, tstart::Number, tfinal::Number; precision=0.01, max_timestep=Inf)
edges = Float64.([tstart, tfinal])
for sequence in sequences
raw_edges = edge_times(sequence)
nTR_start = Int(div(tstart, duration(sequence), RoundDown))
nTR_final = Int(div(tfinal, duration(sequence), RoundUp))
for nTR in nTR_start:nTR_final
for time in raw_edges .+ (nTR * duration(sequence))
if tstart < time < tfinal
push!(edges, time)
end
end
end
edges = sort(unique(edges))
for (t1, t2) in zip(edges[1:end-1], edges[2:end])
tmean = (t1 + t2) / 2
timestep = Float64(max_timestep)
for sequence in sequences
(block_time, block) = sequence[mod(tmean, duration(sequence))]
for key in keys(block)
if !(start_time(block, key) < block_time < end_time(block, key))
continue
end
new_timestep = split_timestep(block[key], precision)
if new_timestep < timestep
timestep = new_timestep
end
nsteps = isinf(timestep) ? 1 : Int(div(t2 - t1, timestep, RoundUp))
append!(splits, range(t1, t2, length=nsteps+1))
return unique(splits)
LinearSequence(sequence, times)
LinearSequence(sequence; max_timestep=Inf, precision=0.01)
LinearSequence(sequence, time1, time2; max_timestep=Inf, precision=0.01)
A piece-wise linear approximation of a sequence.
By default it represents the sequence between `time1=0` and `time2=TR`
The gradient, RF amplitude, and RF phase are all be modeled as changing linearly during this time.
If multiple sequences are provided a vector of `LinearSequence` objects are returned, all of which are split at the same time.
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
struct LinearSequence
finite_parts :: Vector{SequencePart}
instant_pulses :: Vector{Tuple{Int, InstantPulse}}
instant_gradient :: Vector{Tuple{Int, InstantGradient3D}}
end
LinearSequence(sequence::BaseSequence; kwargs...) = LinearSequence(sequence, 0., duration(sequence); kwargs...)
LinearSequence(container::Union{BaseSequence, AbstractVector{<:BaseSequence}}, tstart::Number, tfinal::Number; kwargs...) = LinearSequence(container, split_times(container, tstart, tfinal; kwargs...))
LinearSequence(containers::AbstractVector{<:BaseSequence}, times::AbstractVector{<:Number}) = map(c -> LinearSequence(c, times), containers)
function LinearSequence(container::BaseSequence, times::AbstractVector{<:Number})
parts = [SequencePart(container, t1, t2) for (t1, t2) in zip(times[1:end-1], times[2:end])]
tstart = times[1]
tfinal = times[end]
pulses = Tuple{Int, InstantPulse}[]
gradients = Tuple{Int, InstantGradient3D}[]
for nTR in div(tstart, duration(container), RoundDown):div(tfinal, duration(container), RoundUp)
for (to_store, func) in [
(pulses, iter_instant_pulses),
(gradients, iter_instant_gradients),
]
for (time, pulse) in func(container)
real_time = time + nTR * duration(container)
if !(tstart <= real_time < tfinal)
continue
end
index = findmin(t -> abs(t - real_time), times)[2]-1
push!(to_store, (index, pulse))
end
end
end
return LinearSequence(parts, pulses, gradients)
end