Skip to content

Commit

Permalink
Commit of the following:
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
pvillacorta committed Jan 4, 2025
1 parent ed7d38f commit a88bae6
Show file tree
Hide file tree
Showing 10 changed files with 1,649 additions and 793 deletions.
20 changes: 10 additions & 10 deletions KomaMRIBase/src/motion/Motion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,22 @@ 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)
return Motion(action, time, AllSpins())
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
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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
4 changes: 2 additions & 2 deletions KomaMRIBase/src/motion/MotionList.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
74 changes: 54 additions & 20 deletions KomaMRIBase/src/motion/TimeCurve.jl
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand All @@ -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
2 changes: 1 addition & 1 deletion KomaMRIBase/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading

0 comments on commit a88bae6

Please sign in to comment.