From a88bae6f0dc636a0d52fdc8b9c1973e5f686d3b4 Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Sat, 4 Jan 2025 13:23:44 +0100 Subject: [PATCH] Commit of the following: - Change default `TimeRange` in `Motion` constructors from `[-1,0]` to `[0,eps]` - Rename `duration` to `periods` in TimeCurve.jl, Motion.jl and MotionList.jl - Improve docstrings in TimeCurve.jl - Add new figures and remove old ones --- KomaMRIBase/src/motion/Motion.jl | 20 +- KomaMRIBase/src/motion/MotionList.jl | 4 +- KomaMRIBase/src/motion/TimeCurve.jl | 74 ++-- KomaMRIBase/test/runtests.jl | 2 +- docs/src/assets/time-curve-1.svg | 327 +++++++++++++++++ docs/src/assets/time-curve-2.svg | 425 +++++++++++++++++++++++ docs/src/assets/time-curve-3.svg | 405 +++++++++++++++++++++ docs/src/assets/time-curve-4.svg | 425 +++++++++++++++++++++++ docs/src/assets/unit-time-triangular.svg | 355 ------------------- docs/src/assets/unit-time.svg | 405 --------------------- 10 files changed, 1649 insertions(+), 793 deletions(-) create mode 100644 docs/src/assets/time-curve-1.svg create mode 100644 docs/src/assets/time-curve-2.svg create mode 100644 docs/src/assets/time-curve-3.svg create mode 100644 docs/src/assets/time-curve-4.svg delete mode 100644 docs/src/assets/unit-time-triangular.svg delete mode 100644 docs/src/assets/unit-time.svg diff --git a/KomaMRIBase/src/motion/Motion.jl b/KomaMRIBase/src/motion/Motion.jl index 168344339..6eed9d3c7 100644 --- a/KomaMRIBase/src/motion/Motion.jl +++ b/KomaMRIBase/src/motion/Motion.jl @@ -28,14 +28,14 @@ julia> motion = Motion( """ @with_kw mutable struct Motion{T<:Real} action::AbstractAction{T} - time ::TimeCurve{T} = TimeRange(t_start=-oneunit(typeof(action).parameters[1]), t_end=zero(typeof(action).parameters[1])) + time ::TimeCurve{T} = TimeRange(t_start=zero(typeof(action).parameters[1]), t_end=eps(typeof(action).parameters[1])) spins ::AbstractSpinSpan = AllSpins() end # Main constructors function Motion(action) T = first(typeof(action).parameters) - return Motion(action, TimeRange(t_start=-oneunit(T), t_end=zero(T)), AllSpins()) + return Motion(action, TimeRange(t_start=zero(T), t_end=eps(T)), AllSpins()) end function Motion(action, time::TimeCurve) T = first(typeof(action).parameters) @@ -43,7 +43,7 @@ function Motion(action, time::TimeCurve) end function Motion(action, spins::AbstractSpinSpan) T = first(typeof(action).parameters) - return Motion(action, TimeRange(t_start=-oneunit(T), t_end=zero(T)), spins) + return Motion(action, TimeRange(t_start=zero(T), t_end=eps(T)), spins) end # Custom constructors @@ -65,7 +65,7 @@ end julia> translate = Translate(0.01, 0.02, 0.03, TimeRange(0.0, 1.0), SpinRange(1:10)) ``` """ -function Translate(dx, dy, dz, time=TimeRange(t_start=-oneunit(eltype(dx)), t_end=zero(eltype(dx))), spins=AllSpins()) +function Translate(dx, dy, dz, time=TimeRange(t_start=zero(eltype(dx)), t_end=eps(eltype(dx))), spins=AllSpins()) return Motion(Translate(dx, dy, dz), time, spins) end @@ -87,7 +87,7 @@ end julia> rotate = Rotate(15.0, 0.0, 20.0, TimeRange(0.0, 1.0), SpinRange(1:10)) ``` """ -function Rotate(pitch, roll, yaw, time=TimeRange(t_start=-oneunit(eltype(pitch)), t_end=zero(eltype(pitch))), spins=AllSpins()) +function Rotate(pitch, roll, yaw, time=TimeRange(t_start=zero(eltype(pitch)), t_end=eps(eltype(pitch))), spins=AllSpins()) return Motion(Rotate(pitch, roll, yaw), time, spins) end @@ -109,7 +109,7 @@ end julia> heartbeat = HeartBeat(-0.3, -0.2, 0.0, TimeRange(0.0, 1.0), SpinRange(1:10)) ``` """ -function HeartBeat(circumferential_strain, radial_strain, longitudinal_strain, time=TimeRange(t_start=-oneunit(eltype(circumferential_strain)), t_end=zero(eltype(circumferential_strain))), spins=AllSpins()) +function HeartBeat(circumferential_strain, radial_strain, longitudinal_strain, time=TimeRange(t_start=zero(eltype(circumferential_strain)), t_end=eps(eltype(circumferential_strain))), spins=AllSpins()) return Motion(HeartBeat(circumferential_strain, radial_strain, longitudinal_strain), time, spins) end @@ -137,7 +137,7 @@ julia> path = Path( ) ``` """ -function Path(dx, dy, dz, time=TimeRange(t_start=-oneunit(eltype(dx)), t_end=zero(eltype(dx))), spins=AllSpins()) +function Path(dx, dy, dz, time=TimeRange(t_start=zero(eltype(dx)), t_end=eps(eltype(dx))), spins=AllSpins()) return Motion(Path(dx, dy, dz), time, spins) end @@ -167,7 +167,7 @@ julia> flowpath = FlowPath( ) ``` """ -function FlowPath(dx, dy, dz, spin_reset, time=TimeRange(t_start=-oneunit(eltype(dx)), t_end=zero(eltype(dx))), spins=AllSpins()) +function FlowPath(dx, dy, dz, spin_reset, time=TimeRange(t_start=zero(eltype(dx)), t_end=eps(eltype(dx))), spins=AllSpins()) return Motion(FlowPath(dx, dy, dz, spin_reset), time, spins) end @@ -192,7 +192,7 @@ function get_spin_coords( m::Motion{T}, x::AbstractVector{T}, y::AbstractVector{T}, z::AbstractVector{T}, t ) where {T<:Real} ux, uy, uz = x .* (0*t), y .* (0*t), z .* (0*t) # Buffers for displacements - t_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.duration) + t_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.periods) idx = get_indexing_range(m.spins) displacement_x!(@view(ux[idx, :]), m.action, @view(x[idx]), @view(y[idx]), @view(z[idx]), t_unit) displacement_y!(@view(uy[idx, :]), m.action, @view(x[idx]), @view(y[idx]), @view(z[idx]), t_unit) @@ -201,7 +201,7 @@ function get_spin_coords( end # Auxiliary functions -times(m::Motion) = times(m.time.t, m.time.duration) +times(m::Motion) = times(m.time.t, m.time.periods) is_composable(m::Motion) = is_composable(m.action) add_jump_times!(t, m::Motion) = add_jump_times!(t, m.action, m.time) add_jump_times!(t, ::AbstractAction, ::TimeCurve) = nothing \ No newline at end of file diff --git a/KomaMRIBase/src/motion/MotionList.jl b/KomaMRIBase/src/motion/MotionList.jl index 65f487a14..d03fb6b78 100644 --- a/KomaMRIBase/src/motion/MotionList.jl +++ b/KomaMRIBase/src/motion/MotionList.jl @@ -157,7 +157,7 @@ function get_spin_coords( ux, uy, uz = xt .* zero(T), yt .* zero(T), zt .* zero(T) # Composable motions: they need to be run sequentially. Note that they depend on xt, yt, and zt for m in Iterators.filter(is_composable, ml.motions) - t_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.duration) + t_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.periods) idx = get_indexing_range(m.spins) displacement_x!(@view(ux[idx, :]), m.action, @view(xt[idx, :]), @view(yt[idx, :]), @view(zt[idx, :]), t_unit) displacement_y!(@view(uy[idx, :]), m.action, @view(xt[idx, :]), @view(yt[idx, :]), @view(zt[idx, :]), t_unit) @@ -167,7 +167,7 @@ function get_spin_coords( end # Additive motions: these motions can be run in parallel for m in Iterators.filter(!is_composable, ml.motions) - t_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.duration) + t_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.periods) idx = get_indexing_range(m.spins) displacement_x!(@view(ux[idx, :]), m.action, @view(x[idx]), @view(y[idx]), @view(z[idx]), t_unit) displacement_y!(@view(uy[idx, :]), m.action, @view(x[idx]), @view(y[idx]), @view(z[idx]), t_unit) diff --git a/KomaMRIBase/src/motion/TimeCurve.jl b/KomaMRIBase/src/motion/TimeCurve.jl index cd4729f3c..cd5cb38e0 100644 --- a/KomaMRIBase/src/motion/TimeCurve.jl +++ b/KomaMRIBase/src/motion/TimeCurve.jl @@ -1,28 +1,60 @@ """ - timecurve = TimeCurve(t, t_unit, periodic, duration) + timecurve = TimeCurve(t, t_unit, periodic, periods) -TimeCurve struct. It is a specialized type that defines a time curve. -(...) +TimeCurve struct. It is a specialized type that defines a time curve, which represents +the temporal behavior of motion. This curve is defined by two vectors: +`t` and `t_unit`, which represent the horizontal (x-axis) and vertical (y-axis) axes +of the curve, respectively. To some extent, this curve can be associated with animation curves, +commonly found in software for video editing, 3D scene creation, or video game development. + +Additionally, the TimeCurve struct contains two more fields, independent of each other: +`periodic` is a Boolean that indicates whether the time curve should be repeated periodically. +`periods` contains as many elements as repetitions are desired in the time curve. +Each element specifies the scaling factor for that repetition. # Arguments - `t`: (`::AbstractVector{<:Real}`, `[s]`) time vector - `t_unit`: (`::AbstractVector{<:Real}`) y vector, it needs to be scaled between 0 and 1 - `periodic`: (`::Bool`, `=false`) indicates whether the time curve should be periodically repeated -- `duration`: (`::Union{<:Real,AbstractVector{<:Real}}`, `=1.0`) +- `periods`: (`::Union{<:Real,AbstractVector{<:Real}}`, `=1.0`): represents the relative duration + of each period with respect to the baseline duration defined by `t[end] - t[1]`. + In other words, it acts as a scaling factor to lengthen or shorten specific periods. + This allows for the creation of patterns such as arrhythmias or other variations in periodicity. # Returns - `timecurve`: (`::TimeCurve`) TimeCurve struct # Examples +1. Non-periodic motion with a single repetition. +```julia-repl +julia> timecurve = TimeCurve(t=[0.0, 0.2, 0.4, 0.6], t_unit=[0.0, 0.2, 0.5, 1.0]) +``` +![Time Curve 1](../assets/time-curve-1.svg) + +2. Periodic motion with a single repetition. +```julia-repl +julia> timecurve = TimeCurve(t=[0.0, 0.2, 0.4, 0.6], t_unit=[0.0, 1.0, 1.0, 0.0], periodic=true) +``` +![Time Curve 2](../assets/time-curve-2.svg) + +3. Non-periodic motion with multiple repetitions. +```julia-repl +julia> timecurve = TimeCurve(t=[0.0, 0.2, 0.4, 0.6], t_unit=[0.0, 1.0, 1.0, 0.0], periods=[1.0, 0.5, 1.5]) +``` +![Time Curve 3](../assets/time-curve-3.svg) + +4. Periodic motion with multiple repetitions. ```julia-repl -julia> timecurve = TimeCurve(t=[0.0, 0.1, 0.3, 0.4], t_unit=[0.0, 0.6, 0.2, 0.0], periodic=true) +julia> timecurve = TimeCurve(t=[0.0, 0.2, 0.4, 0.6], t_unit=[0.0, 1.0, 1.0, 0.0], periods=[1.0, 0.5, 1.5], periodic=true) ``` +![Time Curve 4](../assets/time-curve-4.svg) + """ @with_kw struct TimeCurve{T<:Real} t::AbstractVector{T} t_unit::AbstractVector{T} periodic::Bool = false - duration::Union{T,AbstractVector{T}} = oneunit(eltype(t)) + periods::Union{T,AbstractVector{T}} = oneunit(eltype(t)) t_start::T = t[1] t_end::T = t[end] @assert check_unique(t) "Vector t=$(t) contains duplicate elements. Please ensure all elements in t are unique and try again" @@ -32,7 +64,7 @@ check_unique(t) = true check_unique(t::Vector) = length(t) == length(unique(t)) # Main Constructors -TimeCurve(t, t_unit, periodic, duration) = TimeCurve(t=t, t_unit=t_unit, periodic=periodic, duration=duration) +TimeCurve(t, t_unit, periodic, periods) = TimeCurve(t=t, t_unit=t_unit, periodic=periodic, periods=periods) TimeCurve(t, t_unit) = TimeCurve(t=t, t_unit=t_unit) # Custom constructors # --- TimeRange @@ -46,22 +78,24 @@ Periodic(; period=1.0, asymmetry=0.5) = Periodic(period, asymmetry) Base.:(==)(t1::TimeCurve, t2::TimeCurve) = reduce(&, [getfield(t1, field) == getfield(t2, field) for field in fieldnames(typeof(t1))]) Base.:(≈)(t1::TimeCurve, t2::TimeCurve) = reduce(&, [getfield(t1, field) ≈ getfield(t2, field) for field in fieldnames(typeof(t1))]) -""" times """ -function times(t, dur::AbstractVector) - tr = repeat(t, length(dur)) - scale = repeat(dur, inner=[length(t)]) - offsets = repeat(vcat(0, cumsum(dur)[1:end-1]), inner=[length(t)]) +""" times & unit_time functions """ +# Although the implementation of these two functions +# when per is a vector is valid for all cases, it performs +# unnecessary and costly operations when per is a scalar. Therefore, +# it has been decided to use method dispatch between these two cases. +function times(t, per::AbstractVector) + tr = repeat(t, length(per)) + scale = repeat(per, inner=[length(t)]) + offsets = repeat(vcat(0, cumsum(per)[1:end-1]), inner=[length(t)]) tr .= (tr .* scale) .+ offsets return tr end -function times(t, dur::Real) - return dur .* t +function unit_time(tq, t, t_unit, periodic, per::AbstractVector) + return interpolate_times(times(t, per), repeat(t_unit, length(per)), periodic, tq) end - -""" unit_time """ -function unit_time(tq, t, t_unit, periodic, dur::Real) - return interpolate_times(t .* dur, t_unit, periodic, tq) +function times(t, per::Real) + return per .* t end -function unit_time(tq, t, t_unit, periodic, dur) - return interpolate_times(times(t, dur), repeat(t_unit, length(dur)), periodic, tq) +function unit_time(tq, t, t_unit, periodic, per::Real) + return interpolate_times(t .* per, t_unit, periodic, tq) end \ No newline at end of file diff --git a/KomaMRIBase/test/runtests.jl b/KomaMRIBase/test/runtests.jl index 6de9feea0..2fddb1abb 100644 --- a/KomaMRIBase/test/runtests.jl +++ b/KomaMRIBase/test/runtests.jl @@ -502,7 +502,7 @@ end # TimeCurve constructors time = TimeRange(t_start=0.0, t_end=1.0) time = Periodic(period=1.0, asymmetry=0.5) - time = TimeCurve([-1.0, 0.0], [0.0, 1.0]) + time = TimeCurve([0.0, eps()], [0.0, 1.0]) m = Motion(action, time, spins) diff --git a/docs/src/assets/time-curve-1.svg b/docs/src/assets/time-curve-1.svg new file mode 100644 index 000000000..a637ec4db --- /dev/null +++ b/docs/src/assets/time-curve-1.svg @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + t [s] + tunit(t) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.3 -0.2 -0.1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 + 1.00.90.80.70.60.50.40.30.20.1 + + + + + + diff --git a/docs/src/assets/time-curve-2.svg b/docs/src/assets/time-curve-2.svg new file mode 100644 index 000000000..101da15a5 --- /dev/null +++ b/docs/src/assets/time-curve-2.svg @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + t [s] + tunit(t) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.3 -0.2 -0.1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 + 1.00.90.80.70.60.50.40.30.20.1 + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/time-curve-3.svg b/docs/src/assets/time-curve-3.svg new file mode 100644 index 000000000..d69b1d68a --- /dev/null +++ b/docs/src/assets/time-curve-3.svg @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + t [s] + tunit(t) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.3 -0.2 -0.1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 + 1.00.90.80.70.60.50.40.30.20.1 + + + + + + + + + + + + + + diff --git a/docs/src/assets/time-curve-4.svg b/docs/src/assets/time-curve-4.svg new file mode 100644 index 000000000..ab64f7145 --- /dev/null +++ b/docs/src/assets/time-curve-4.svg @@ -0,0 +1,425 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + t [s] + tunit(t) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.3 -0.2 -0.1 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 + 1.00.90.80.70.60.50.40.30.20.1 + + + + + + + + + + + + + + diff --git a/docs/src/assets/unit-time-triangular.svg b/docs/src/assets/unit-time-triangular.svg deleted file mode 100644 index 5a91bd907..000000000 --- a/docs/src/assets/unit-time-triangular.svg +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - - - - - - - - - Periodic - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tunit - t - - - - - period - - - - - - - - period(1-asymmetry) - - - - period asymmetry - · - - 1 - - - - - - diff --git a/docs/src/assets/unit-time.svg b/docs/src/assets/unit-time.svg deleted file mode 100644 index c7cc6d3f9..000000000 --- a/docs/src/assets/unit-time.svg +++ /dev/null @@ -1,405 +0,0 @@ - - - - - - - - - - - - - - - - Non-periodic - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tunit - t - - - - tstart - tend - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -