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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
-
-
-
-
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 @@
-
-
-
-