From 581eb39d93d7533c5db65e1a49aa01ad181107d8 Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Thu, 2 Jan 2025 12:59:27 +0100 Subject: [PATCH 01/11] Commit of the following: - New `TimeCurve` struct and - Slight changes in `times` and `sort_motions!` - Allow to and export Booleans in KomaMRIFiles/Phantom.jl --- KomaMRIBase/src/KomaMRIBase.jl | 2 +- KomaMRIBase/src/motion/MotionList.jl | 2 +- KomaMRIBase/src/motion/TimeSpan.jl | 40 +++++++++++++++++++++++++++- KomaMRIFiles/src/Phantom/Phantom.jl | 6 +++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/KomaMRIBase/src/KomaMRIBase.jl b/KomaMRIBase/src/KomaMRIBase.jl index 3b7033ba4..2f819bf89 100644 --- a/KomaMRIBase/src/KomaMRIBase.jl +++ b/KomaMRIBase/src/KomaMRIBase.jl @@ -53,7 +53,7 @@ export MotionList, NoMotion, Motion export Translate, TranslateX, TranslateY, TranslateZ export Rotate, RotateX, RotateY, RotateZ export HeartBeat, Path, FlowPath -export TimeRange, Periodic +export TimeRange, Periodic, TimeCurve export SpinRange, AllSpins export get_spin_coords # Secondary diff --git a/KomaMRIBase/src/motion/MotionList.jl b/KomaMRIBase/src/motion/MotionList.jl index 7879f5408..e22cf0c90 100644 --- a/KomaMRIBase/src/motion/MotionList.jl +++ b/KomaMRIBase/src/motion/MotionList.jl @@ -199,7 +199,7 @@ If `motionset::MotionList`, this function sorts its motions. - `nothing` """ function sort_motions!(m::MotionList) - sort!(m.motions; by=m -> times(m)[1]) + sort!(m.motions; by=m -> m.time.t_start) return nothing end diff --git a/KomaMRIBase/src/motion/TimeSpan.jl b/KomaMRIBase/src/motion/TimeSpan.jl index 551e4965a..af9651725 100644 --- a/KomaMRIBase/src/motion/TimeSpan.jl +++ b/KomaMRIBase/src/motion/TimeSpan.jl @@ -91,13 +91,14 @@ julia> periodic = Periodic(1.0, 0.2) @with_kw struct Periodic{T<:Real} <: AbstractTimeSpan{T} period::T asymmetry::T = typeof(period)(0.5) + t_start::T = zero(typeof(period)) end """ Constructors """ Periodic(period) = Periodic(period, typeof(period)(0.5)) """ times """ -times(ts::Periodic{T}) where {T<:Real} = [zero(T), ts.period * ts.asymmetry, ts.period] +times(ts::Periodic{T}) where {T<:Real} = ts.t_start .+ [0, ts.period * ts.asymmetry, ts.period] """ t_unit = unit_time(t, periodic) @@ -140,4 +141,41 @@ function unit_time(t, ts::Periodic{T}) where {T<:Real} t_unit = ifelse.( t_relative .< t_rise, t_relative ./ t_rise, oneunit(T) .- (t_relative .- t_rise) ./ t_fall) end return t_unit +end + + +""" + timecurve = TimeCurve(x, y, periodic, delay) + +TimeCurve struct. It is a specialized type that inherits from AbstractTimeSpan and +defines a time curve. It is defined by the `x` and `y` vectors, an optional `periodic` +flag, and an optional delay. + +# Arguments +- `x`: (`::AbstractVector{<:Real}`, `[s]`) time vector +- `y`: (`::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 +- `delyay`: (`<:Real`, `=0.0`) initial delay which takes effect before the time curve + +# Returns +- `timecurve`: (`::TimeCurve`) TimeCurve struct + +# Examples +```julia-repl +julia> timecurve = TimeCurve(x=[0.0, 0.1, 0.3, 0.4], y=[0.0, 0.6, 0.2, 0.0], periodic=true, delay=0.0) +``` +""" +@with_kw struct TimeCurve{T<:Real} <: AbstractTimeSpan{T} + x::AbstractVector{T} + y::AbstractVector{T} + periodic::Bool = false + t_start::T=x[1] +end + +""" times """ +times(tc::TimeCurve) = tc.x +""" unit_time """ +function unit_time(t, tc::TimeCurve{T}) where T<:Real + itp = GriddedInterpolation((tc.x, ), tc.y, Gridded(Linear())) + return extrapolate(itp, tc.periodic ? Interpolations.Periodic() : Flat()).(t) end \ No newline at end of file diff --git a/KomaMRIFiles/src/Phantom/Phantom.jl b/KomaMRIFiles/src/Phantom/Phantom.jl index 0a2890a54..11a990c9a 100644 --- a/KomaMRIFiles/src/Phantom/Phantom.jl +++ b/KomaMRIFiles/src/Phantom/Phantom.jl @@ -89,6 +89,9 @@ function import_motion_subfield!(motion_subfields::Array, subfield_value::Union{ return nothing end function import_motion_subfield!(motion_subfields::Array, subfield_value::String, key::String) + if subfield_value in ["true", "false"] + return push!(motion_subfields, subfield_value == "true" ? true : false) + end endpoints = parse.(Int, split(subfield_value, ":")) range = length(endpoints) == 3 ? (endpoints[1]:endpoints[2]:endpoints[3]) : (endpoints[1]:endpoints[2]) push!(motion_subfields, range) @@ -166,4 +169,7 @@ function export_motion_subfield!(field_group::HDF5.Group, subfield::Array, subna end function export_motion_subfield!(field_group::HDF5.Group, subfield::BitMatrix, subname::String) field_group[subname] = Int.(subfield) +end +function export_motion_subfield!(field_group::HDF5.Group, subfield::Bool, subname::String) + field_group[subname] = string(subfield) end \ No newline at end of file From 06c795a53012fa9d27f06ba2f458067aa45845b5 Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Thu, 2 Jan 2025 13:00:04 +0100 Subject: [PATCH 02/11] Move Interpolation-related functions to a separate file --- KomaMRIBase/src/motion/Interpolation.jl | 61 ++++++++++++++++++ KomaMRIBase/src/motion/MotionList.jl | 1 + .../src/motion/actions/ArbitraryAction.jl | 63 ------------------- 3 files changed, 62 insertions(+), 63 deletions(-) create mode 100644 KomaMRIBase/src/motion/Interpolation.jl diff --git a/KomaMRIBase/src/motion/Interpolation.jl b/KomaMRIBase/src/motion/Interpolation.jl new file mode 100644 index 000000000..ad72f9851 --- /dev/null +++ b/KomaMRIBase/src/motion/Interpolation.jl @@ -0,0 +1,61 @@ +# We defined two types of Interpolation objects: Interpolator1D and Interpolator2D +# 1D is for interpolating for 1 spin +# 2D is for interpolating for 2 or more spins +# This dispatch based on the number of spins wouldn't be necessary if it weren't for this: +# https://github.com/JuliaMath/Interpolations.jl/issues/603 +# +# Once this issue is solved, this file should be simpler. +# We should then be able to define a single method for functions: +# - interpolate +# - resample +# and delete the Interpolator1D and Interpolator2D definitions + +const Interpolator1D = Interpolations.GriddedInterpolation{ + TCoefs,1,V,Itp,K +} where { + TCoefs<:Real, + TNodes<:Real, + V<:AbstractArray{TCoefs}, + Itp<:Interpolations.Gridded, + K<:Tuple{AbstractVector{TNodes}}, +} + +const Interpolator2D = Interpolations.GriddedInterpolation{ + TCoefs,2,V,Itp,K +} where { + TCoefs<:Real, + TNodes<:Real, + V<:AbstractArray{TCoefs}, + Itp<:Interpolations.Gridded, + K<:Tuple{AbstractVector{TNodes}, AbstractVector{TNodes}}, +} +function GriddedInterpolation(nodes, A, ITP) + return Interpolations.GriddedInterpolation{eltype(A), length(nodes), typeof(A), typeof(ITP), typeof(nodes)}(nodes, A, ITP) +end + +function interpolate(d, ITPType, Ns::Val{1}, t) + _, Nt = size(d) + t_knots = _similar(t, Nt); copyto!(t_knots, collect(range(zero(eltype(t)), oneunit(eltype(t)), Nt))) + return GriddedInterpolation((t_knots, ), d[:], ITPType) +end + +function interpolate(d, ITPType, Ns::Val, t) + Ns, Nt = size(d) + id_knots = _similar(t, Ns); copyto!(id_knots, collect(range(oneunit(eltype(t)), eltype(t)(Ns), Ns))) + t_knots = _similar(t, Nt); copyto!(t_knots, collect(range(zero(eltype(t)), oneunit(eltype(t)), Nt))) + return GriddedInterpolation((id_knots, t_knots), d, ITPType) +end + +function resample(itp::Interpolator1D, t) + return itp.(t) +end + +function resample(itp::Interpolator2D, t) + Ns = size(itp.coefs, 1) + id = _similar(t, Ns) + copyto!(id, collect(range(oneunit(eltype(t)), eltype(t)(Ns), Ns))) + return itp.(id, t) +end + +_similar(a, N) = similar(a, N) +_similar(a::Real, N) = zeros(typeof(a), N) \ No newline at end of file diff --git a/KomaMRIBase/src/motion/MotionList.jl b/KomaMRIBase/src/motion/MotionList.jl index e22cf0c90..cd7e01f35 100644 --- a/KomaMRIBase/src/motion/MotionList.jl +++ b/KomaMRIBase/src/motion/MotionList.jl @@ -1,3 +1,4 @@ +include("Interpolation.jl") include("SpinSpan.jl") include("TimeSpan.jl") include("Action.jl") diff --git a/KomaMRIBase/src/motion/actions/ArbitraryAction.jl b/KomaMRIBase/src/motion/actions/ArbitraryAction.jl index c62a7b4bd..5507d24f8 100644 --- a/KomaMRIBase/src/motion/actions/ArbitraryAction.jl +++ b/KomaMRIBase/src/motion/actions/ArbitraryAction.jl @@ -1,35 +1,3 @@ -# We defined two types of Interpolation objects: Interpolator1D and Interpolator2D -# 1D is for interpolating for 1 spin -# 2D is for interpolating for 2 or more spins -# This dispatch based on the number of spins wouldn't be necessary if it weren't for this: -# https://github.com/JuliaMath/Interpolations.jl/issues/603 -# -# Once this issue is solved, this file should be simpler. -# We should then be able to define a single method for functions: -# - interpolate -# - resample -# and delete the Interpolator1D and Interpolator2D definitions - -const Interpolator1D = Interpolations.GriddedInterpolation{ - TCoefs,1,V,Itp,K -} where { - TCoefs<:Real, - TNodes<:Real, - V<:AbstractArray{TCoefs}, - Itp<:Interpolations.Gridded, - K<:Tuple{AbstractVector{TNodes}}, -} - -const Interpolator2D = Interpolations.GriddedInterpolation{ - TCoefs,2,V,Itp,K -} where { - TCoefs<:Real, - TNodes<:Real, - V<:AbstractArray{TCoefs}, - Itp<:Interpolations.Gridded, - K<:Tuple{AbstractVector{TNodes}, AbstractVector{TNodes}}, -} - abstract type ArbitraryAction{T<:Real} <: AbstractAction{T} end function Base.getindex(action::ArbitraryAction, p) @@ -42,34 +10,6 @@ end Base.:(==)(m1::ArbitraryAction, m2::ArbitraryAction) = (typeof(m1) == typeof(m2)) & reduce(&, [getfield(m1, field) == getfield(m2, field) for field in fieldnames(typeof(m1))]) Base.:(≈)(m1::ArbitraryAction, m2::ArbitraryAction) = (typeof(m1) == typeof(m2)) & reduce(&, [getfield(m1, field) ≈ getfield(m2, field) for field in fieldnames(typeof(m1))]) -function GriddedInterpolation(nodes, A, ITP) - return Interpolations.GriddedInterpolation{eltype(A), length(nodes), typeof(A), typeof(ITP), typeof(nodes)}(nodes, A, ITP) -end - -function interpolate(d, ITPType, Ns::Val{1}, t) - _, Nt = size(d) - t_knots = _similar(t, Nt); copyto!(t_knots, collect(range(zero(eltype(t)), oneunit(eltype(t)), Nt))) - return GriddedInterpolation((t_knots, ), d[:], ITPType) -end - -function interpolate(d, ITPType, Ns::Val, t) - Ns, Nt = size(d) - id_knots = _similar(t, Ns); copyto!(id_knots, collect(range(oneunit(eltype(t)), eltype(t)(Ns), Ns))) - t_knots = _similar(t, Nt); copyto!(t_knots, collect(range(zero(eltype(t)), oneunit(eltype(t)), Nt))) - return GriddedInterpolation((id_knots, t_knots), d, ITPType) -end - -function resample(itp::Interpolator1D, t) - return itp.(t) -end - -function resample(itp::Interpolator2D, t) - Ns = size(itp.coefs, 1) - id = _similar(t, Ns) - copyto!(id, collect(range(oneunit(eltype(t)), eltype(t)(Ns), Ns))) - return itp.(id, t) -end - function displacement_x!(ux, action::ArbitraryAction, x, y, z, t) itp = interpolate(action.dx, Gridded(Linear()), Val(size(action.dx,1)), t) ux .= resample(itp, t) @@ -88,8 +28,5 @@ function displacement_z!(uz, action::ArbitraryAction, x, y, z, t) return nothing end -_similar(a, N) = similar(a, N) -_similar(a::Real, N) = zeros(typeof(a), N) - include("arbitraryactions/Path.jl") include("arbitraryactions/FlowPath.jl") \ No newline at end of file From 0d45cb468db9269e481dac285858142020969ebf Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Thu, 2 Jan 2025 13:48:34 +0100 Subject: [PATCH 03/11] Fix tests --- KomaMRIBase/test/runtests.jl | 6 +++--- KomaMRICore/test/runtests.jl | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/KomaMRIBase/test/runtests.jl b/KomaMRIBase/test/runtests.jl index 2ed71555f..62f6be653 100644 --- a/KomaMRIBase/test/runtests.jl +++ b/KomaMRIBase/test/runtests.jl @@ -545,7 +545,7 @@ end asymmetry = 0.5 dx, dy, dz = [1.0, 0.0, 0.0] vx, vy, vz = [dx, dy, dz] ./ (t_end - t_start) - periodictranslation = Translate(dx, dy, dz, Periodic(period, asymmetry)) + periodictranslation = Translate(dx, dy, dz, Periodic(period=period, asymmetry=asymmetry)) xt, yt, zt = get_spin_coords(periodictranslation, ph.x, ph.y, ph.z, t') @test xt == ph.x .+ vx.*t' @test yt == ph.y .+ vy.*t' @@ -584,7 +584,7 @@ end pitch = 45.0 roll = 0.0 yaw = 45.0 - periodicrotation = Rotate(pitch, roll, yaw, Periodic(period, asymmetry)) + periodicrotation = Rotate(pitch, roll, yaw, Periodic(period=period, asymmetry=asymmetry)) xt, yt, zt = get_spin_coords(periodicrotation, ph.x, ph.y, ph.z, t') r = vcat(ph.x, ph.y, ph.z) R = rotz(π*yaw/180) * roty(π*roll/180) * rotx(π*pitch/180) @@ -630,7 +630,7 @@ end circumferential_strain = -0.1 radial_strain = 0.0 longitudinal_strain = -0.1 - periodicheartbeat = HeartBeat(circumferential_strain, radial_strain, longitudinal_strain, Periodic(period, asymmetry)) + periodicheartbeat = HeartBeat(circumferential_strain, radial_strain, longitudinal_strain, Periodic(period=period, asymmetry=asymmetry)) xt, yt, zt = get_spin_coords(periodicheartbeat, ph.x, ph.y, ph.z, t') r = sqrt.(ph.x .^ 2 + ph.y .^ 2) θ = atan.(ph.y, ph.x) diff --git a/KomaMRICore/test/runtests.jl b/KomaMRICore/test/runtests.jl index be10f384f..022b281cd 100644 --- a/KomaMRICore/test/runtests.jl +++ b/KomaMRICore/test/runtests.jl @@ -441,7 +441,7 @@ end motions = [ Translate(0.1, 0.1, 0.0, TimeRange(0.0, 1.0)), Rotate(0.0, 0.0, 45.0, TimeRange(0.0, 1.0)), - HeartBeat(-0.6, 0.0, 0.0, Periodic(1.0)), + HeartBeat(-0.6, 0.0, 0.0, Periodic(period=1.0)), Path([0.0 0.0], [0.0 1.0], [0.0 0.0], TimeRange(0.0, 10.0)), FlowPath([0.0 0.0], [0.0 1.0], [0.0 0.0], [false false], TimeRange(0.0, 10.0)) ] From ed7d38f71ee82130dca201b15ea370f83a665bcd Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Fri, 3 Jan 2025 18:15:13 +0100 Subject: [PATCH 04/11] Leave `TimeCurve` as the only time span type --- KomaMRIBase/src/motion/Interpolation.jl | 5 + KomaMRIBase/src/motion/Motion.jl | 38 ++-- KomaMRIBase/src/motion/MotionList.jl | 6 +- KomaMRIBase/src/motion/TimeCurve.jl | 67 +++++++ KomaMRIBase/src/motion/TimeSpan.jl | 181 ------------------ .../actions/arbitraryactions/FlowPath.jl | 4 +- KomaMRIBase/test/runtests.jl | 35 +--- KomaMRICore/src/simulation/Functors.jl | 3 +- KomaMRIFiles/src/Phantom/Phantom.jl | 10 +- docs/src/reference/2-koma-base.md | 5 +- 10 files changed, 108 insertions(+), 246 deletions(-) create mode 100644 KomaMRIBase/src/motion/TimeCurve.jl delete mode 100644 KomaMRIBase/src/motion/TimeSpan.jl diff --git a/KomaMRIBase/src/motion/Interpolation.jl b/KomaMRIBase/src/motion/Interpolation.jl index ad72f9851..09b699867 100644 --- a/KomaMRIBase/src/motion/Interpolation.jl +++ b/KomaMRIBase/src/motion/Interpolation.jl @@ -57,5 +57,10 @@ function resample(itp::Interpolator2D, t) return itp.(id, t) end +function interpolate_times(t, t_unit, periodic, tq) + itp = GriddedInterpolation((t, ), t_unit, Gridded(Linear())) + return extrapolate(itp, periodic ? Interpolations.Periodic() : Flat()).(tq) +end + _similar(a, N) = similar(a, N) _similar(a::Real, N) = zeros(typeof(a), N) \ No newline at end of file diff --git a/KomaMRIBase/src/motion/Motion.jl b/KomaMRIBase/src/motion/Motion.jl index 8adbd6233..168344339 100644 --- a/KomaMRIBase/src/motion/Motion.jl +++ b/KomaMRIBase/src/motion/Motion.jl @@ -11,7 +11,7 @@ that are affected by that motion. # Arguments - `action`: (`::AbstractAction{T<:Real}`) action, such as [`Translate`](@ref) or [`Rotate`](@ref) -- `time`: (`::AbstractTimeSpan{T<:Real}`, `=TimeRange(0.0)`) time information about the motion +- `time`: (`::TimeCurve{T<:Real}`, `=TimeRange(0.0)`) time information about the motion - `spins`: (`::AbstractSpinSpan`, `=AllSpins()`) spin indexes affected by the motion # Returns @@ -28,22 +28,22 @@ julia> motion = Motion( """ @with_kw mutable struct Motion{T<:Real} action::AbstractAction{T} - time ::AbstractTimeSpan{T} = TimeRange(zero(typeof(action).parameters[1])) - spins ::AbstractSpinSpan = AllSpins() + time ::TimeCurve{T} = TimeRange(t_start=-oneunit(typeof(action).parameters[1]), t_end=zero(typeof(action).parameters[1])) + spins ::AbstractSpinSpan = AllSpins() end # Main constructors function Motion(action) T = first(typeof(action).parameters) - return Motion(action, TimeRange(zero(T)), AllSpins()) + return Motion(action, TimeRange(t_start=-oneunit(T), t_end=zero(T)), AllSpins()) end -function Motion(action, time::AbstractTimeSpan) +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(zero(T)), spins) + return Motion(action, TimeRange(t_start=-oneunit(T), t_end=zero(T)), spins) end # Custom constructors @@ -54,7 +54,7 @@ end - `dx`: (`::Real`, `[m]`) translation in x - `dy`: (`::Real`, `[m]`) translation in y - `dz`: (`::Real`, `[m]`) translation in z -- `time`: (`::AbstractTimeSpan{T<:Real}`) time information about the motion +- `time`: (`::TimeCurve{T<:Real}`) time information about the motion - `spins`: (`::AbstractSpinSpan`) spin indexes affected by the motion # Returns @@ -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(zero(eltype(dx))), spins=AllSpins()) +function Translate(dx, dy, dz, time=TimeRange(t_start=-oneunit(eltype(dx)), t_end=zero(eltype(dx))), spins=AllSpins()) return Motion(Translate(dx, dy, dz), time, spins) end @@ -76,7 +76,7 @@ end - `pitch`: (`::Real`, `[º]`) rotation in x - `roll`: (`::Real`, `[º]`) rotation in y - `yaw`: (`::Real`, `[º]`) rotation in z -- `time`: (`::AbstractTimeSpan{T<:Real}`) time information about the motion +- `time`: (`::TimeCurve{T<:Real}`) time information about the motion - `spins`: (`::AbstractSpinSpan`) spin indexes affected by the motion # Returns @@ -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(zero(eltype(pitch))), spins=AllSpins()) +function Rotate(pitch, roll, yaw, time=TimeRange(t_start=-oneunit(eltype(pitch)), t_end=zero(eltype(pitch))), spins=AllSpins()) return Motion(Rotate(pitch, roll, yaw), time, spins) end @@ -98,7 +98,7 @@ end - `circumferential_strain`: (`::Real`) contraction parameter - `radial_strain`: (`::Real`) contraction parameter - `longitudinal_strain`: (`::Real`) contraction parameter -- `time`: (`::AbstractTimeSpan{T<:Real}`) time information about the motion +- `time`: (`::TimeCurve{T<:Real}`) time information about the motion - `spins`: (`::AbstractSpinSpan`) spin indexes affected by the motion # Returns @@ -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(zero(eltype(circumferential_strain))), spins=AllSpins()) +function HeartBeat(circumferential_strain, radial_strain, longitudinal_strain, time=TimeRange(t_start=-oneunit(eltype(circumferential_strain)), t_end=zero(eltype(circumferential_strain))), spins=AllSpins()) return Motion(HeartBeat(circumferential_strain, radial_strain, longitudinal_strain), time, spins) end @@ -120,7 +120,7 @@ end - `dx`: (`::AbstractArray{T<:Real}`, `[m]`) displacements in x - `dy`: (`::AbstractArray{T<:Real}`, `[m]`) displacements in y - `dz`: (`::AbstractArray{T<:Real}`, `[m]`) displacements in z -- `time`: (`::AbstractTimeSpan{T<:Real}`) time information about the motion +- `time`: (`::TimeCurve{T<:Real}`) time information about the motion - `spins`: (`::AbstractSpinSpan`) spin indexes affected by the motion # Returns @@ -137,7 +137,7 @@ julia> path = Path( ) ``` """ -function Path(dx, dy, dz, time=TimeRange(zero(eltype(dx))), spins=AllSpins()) +function Path(dx, dy, dz, time=TimeRange(t_start=-oneunit(eltype(dx)), t_end=zero(eltype(dx))), spins=AllSpins()) return Motion(Path(dx, dy, dz), time, spins) end @@ -149,7 +149,7 @@ end - `dy`: (`::AbstractArray{T<:Real}`, `[m]`) displacements in y - `dz`: (`::AbstractArray{T<:Real}`, `[m]`) displacements in z - `spin_reset`: (`::AbstractArray{Bool}`) reset spin state flags -- `time`: (`::AbstractTimeSpan{T<:Real}`) time information about the motion +- `time`: (`::TimeCurve{T<:Real}`) time information about the motion - `spins`: (`::AbstractSpinSpan`) spin indexes affected by the motion # Returns @@ -167,7 +167,7 @@ julia> flowpath = FlowPath( ) ``` """ -function FlowPath(dx, dy, dz, spin_reset, time=TimeRange(zero(eltype(dx))), spins=AllSpins()) +function FlowPath(dx, dy, dz, spin_reset, time=TimeRange(t_start=-oneunit(eltype(dx)), t_end=zero(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_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.duration) 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) +times(m::Motion) = times(m.time.t, m.time.duration) 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, ::AbstractTimeSpan) = nothing \ No newline at end of file +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 cd7e01f35..65f487a14 100644 --- a/KomaMRIBase/src/motion/MotionList.jl +++ b/KomaMRIBase/src/motion/MotionList.jl @@ -1,6 +1,6 @@ include("Interpolation.jl") include("SpinSpan.jl") -include("TimeSpan.jl") +include("TimeCurve.jl") include("Action.jl") include("Motion.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_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.duration) 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_unit = unit_time(t, m.time.t, m.time.t_unit, m.time.periodic, m.time.duration) 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 new file mode 100644 index 000000000..cd4729f3c --- /dev/null +++ b/KomaMRIBase/src/motion/TimeCurve.jl @@ -0,0 +1,67 @@ +""" + timecurve = TimeCurve(t, t_unit, periodic, duration) + +TimeCurve struct. It is a specialized type that defines a time curve. +(...) + +# 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`) + +# Returns +- `timecurve`: (`::TimeCurve`) TimeCurve struct + +# Examples +```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) +``` +""" +@with_kw struct TimeCurve{T<:Real} + t::AbstractVector{T} + t_unit::AbstractVector{T} + periodic::Bool = false + duration::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" +end + +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) = TimeCurve(t=t, t_unit=t_unit) +# Custom constructors +# --- TimeRange +TimeRange(t_start::T, t_end::T) where T = TimeCurve(t=[t_start, t_end], t_unit=[zero(T), oneunit(T)]) +TimeRange(; t_start=0.0, t_end=1.0) = TimeRange(t_start, t_end) +# --- Periodic +Periodic(period::T, asymmetry::T) where T = TimeCurve(t=[zero(T), period*asymmetry, period], t_unit=[zero(T), oneunit(T), zero(T)]) +Periodic(; period=1.0, asymmetry=0.5) = Periodic(period, asymmetry) + +""" Compare two TimeCurves """ +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)]) + tr .= (tr .* scale) .+ offsets + return tr +end +function times(t, dur::Real) + return dur .* t +end + +""" unit_time """ +function unit_time(tq, t, t_unit, periodic, dur::Real) + return interpolate_times(t .* dur, t_unit, periodic, tq) +end +function unit_time(tq, t, t_unit, periodic, dur) + return interpolate_times(times(t, dur), repeat(t_unit, length(dur)), periodic, tq) +end \ No newline at end of file diff --git a/KomaMRIBase/src/motion/TimeSpan.jl b/KomaMRIBase/src/motion/TimeSpan.jl deleted file mode 100644 index af9651725..000000000 --- a/KomaMRIBase/src/motion/TimeSpan.jl +++ /dev/null @@ -1,181 +0,0 @@ -abstract type AbstractTimeSpan{T<:Real} end - -""" - timerange = TimeRange(t_start, t_end) - -TimeRange struct. It is a specialized type that inherits from AbstractTimeSpan and -defines a time interval, with start and end times. - -# Arguments -- `t_start`: (`::Real`, `[s]`) start time -- `t_end`: (`::Real`, `[s]`) end time - -# Returns -- `timerange`: (`::TimeRange`) TimeRange struct - -# Examples -```julia-repl -julia> timerange = TimeRange(0.0, 1.0) -``` -""" -@with_kw struct TimeRange{T<:Real} <: AbstractTimeSpan{T} - t_start ::T - t_end ::T - @assert t_end >= t_start "t_end must be greater or equal than t_start" -end - -""" Constructors """ -TimeRange(t_start) = TimeRange(t_start, t_start) - -""" times """ -times(ts::TimeRange) = [ts.t_start, ts.t_end] - -""" - t_unit = unit_time(t, time_range) - -The `unit_time` function normalizes a given array of time values t -to a unit interval [0, 1] based on a specified start time `t_start` and end time `t_end`. -This function is used for non-periodic motions, where each element of t is transformed -to fit within the range [0, 1] based on the provided start and end times. - -![Unit Time](../assets/unit-time.svg) - -# Arguments -- `t`: (`::AbstractArray{T<:Real}`, `[s]`) array of time values to be normalized -- `time_range`: (`::TimeRange{T<:Real}`, `[s]`) time interval (defined by `t_start` and `t_end`) over which we want to normalise - -# Returns -- `t_unit`: (`::AbstractArray{T<:Real}`, `[s]`) array of normalized time values - -# Examples -```julia-repl -julia> t_unit = KomaMRIBase.unit_time([0.0, 1.0, 2.0, 3.0, 4.0, 5.0], TimeRange(1.0, 4.0)) -6-element Vector{Float64}: - 0.0 - 0.0 - 0.333 - 0.666 - 1.0 - 1.0 -``` -""" -function unit_time(t, ts::TimeRange{T}) where {T<:Real} - if ts.t_start == ts.t_end - return (t .>= ts.t_start) .* oneunit(T) - else - tmp = max.((t .- ts.t_start) ./ (ts.t_end - ts.t_start), zero(T)) - return min.(tmp, oneunit(T)) - end -end - - -""" - periodic = Periodic(period, asymmetry) - -Periodic struct. It is a specialized type that inherits from AbstractTimeSpan, -designed to work with time intervals that repeat periodically. It includes a measure of -asymmetry in order to recreate a asymmetric period. - -# Arguments -- `period`: (`::Real`, `[s]`) period duration -- `asymmetry`: (`::Real`, `=0.5`) temporal asymmetry factor. Between 0 and 1. - -# Returns -- `periodic`: (`::Periodic`) Periodic struct - -# Examples -```julia-repl -julia> periodic = Periodic(1.0, 0.2) -``` -""" -@with_kw struct Periodic{T<:Real} <: AbstractTimeSpan{T} - period::T - asymmetry::T = typeof(period)(0.5) - t_start::T = zero(typeof(period)) -end - -""" Constructors """ -Periodic(period) = Periodic(period, typeof(period)(0.5)) - -""" times """ -times(ts::Periodic{T}) where {T<:Real} = ts.t_start .+ [0, ts.period * ts.asymmetry, ts.period] - -""" - t_unit = unit_time(t, periodic) - -The `unit_time` function normalizes a given array -of time values t to a unit interval [0, 1] for periodic motions, -based on a specified period and an asymmetry factor. -This function is useful for creating triangular waveforms -or normalizing time values in periodic processes. - -![Unit Time Triangular](../assets/unit-time-triangular.svg) - -# Arguments -- `t`: (`::AbstractArray{T<:Real}`, `[s]`) array of time values to be normalized -- `periodic`: (`::Periodic{T<:Real}`, `[s]`) information about the `period` and the temporal `asymmetry` -# Returns -- `t_unit`: (`::AbstractArray{T<:Real}`, `[s]`) array of normalized time values - -# Examples -```julia-repl -julia> t_unit = KomaMRIBase.unit_time([0.0, 1.0, 2.0, 3.0, 4.0, 5.0], Periodic(4.0, 0.5)) -6-element Vector{Float64}: - 0.0 - 0.5 - 1.0 - 0.5 - 0.0 - 0.5 -``` -""" -function unit_time(t, ts::Periodic{T}) where {T<:Real} - t_rise = ts.period * ts.asymmetry - t_fall = ts.period * (oneunit(T) - ts.asymmetry) - t_relative = mod.(t, ts.period) - if t_rise == 0 - t_unit = ifelse.(t_relative .< t_rise, zero(T), oneunit(T) .- t_relative ./ t_fall) - elseif t_fall == 0 - t_unit = ifelse.(t_relative .< t_rise, t_relative ./ t_rise, oneunit(T)) - else - t_unit = ifelse.( t_relative .< t_rise, t_relative ./ t_rise, oneunit(T) .- (t_relative .- t_rise) ./ t_fall) - end - return t_unit -end - - -""" - timecurve = TimeCurve(x, y, periodic, delay) - -TimeCurve struct. It is a specialized type that inherits from AbstractTimeSpan and -defines a time curve. It is defined by the `x` and `y` vectors, an optional `periodic` -flag, and an optional delay. - -# Arguments -- `x`: (`::AbstractVector{<:Real}`, `[s]`) time vector -- `y`: (`::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 -- `delyay`: (`<:Real`, `=0.0`) initial delay which takes effect before the time curve - -# Returns -- `timecurve`: (`::TimeCurve`) TimeCurve struct - -# Examples -```julia-repl -julia> timecurve = TimeCurve(x=[0.0, 0.1, 0.3, 0.4], y=[0.0, 0.6, 0.2, 0.0], periodic=true, delay=0.0) -``` -""" -@with_kw struct TimeCurve{T<:Real} <: AbstractTimeSpan{T} - x::AbstractVector{T} - y::AbstractVector{T} - periodic::Bool = false - t_start::T=x[1] -end - -""" times """ -times(tc::TimeCurve) = tc.x -""" unit_time """ -function unit_time(t, tc::TimeCurve{T}) where T<:Real - itp = GriddedInterpolation((tc.x, ), tc.y, Gridded(Linear())) - return extrapolate(itp, tc.periodic ? Interpolations.Periodic() : Flat()).(t) -end \ No newline at end of file diff --git a/KomaMRIBase/src/motion/actions/arbitraryactions/FlowPath.jl b/KomaMRIBase/src/motion/actions/arbitraryactions/FlowPath.jl index 391a56839..5046b49a6 100644 --- a/KomaMRIBase/src/motion/actions/arbitraryactions/FlowPath.jl +++ b/KomaMRIBase/src/motion/actions/arbitraryactions/FlowPath.jl @@ -36,7 +36,7 @@ julia> flowpath = FlowPath( spin_reset::AbstractArray{Bool} end -function add_jump_times!(t, a::FlowPath, time_span::AbstractTimeSpan) - jump_times = (times(time_span)[end] - times(time_span)[1])/(size(a.spin_reset)[2]-1) * (getindex.(findall(a.spin_reset .== 1), 2) .- 1) .- 1e-6 +function add_jump_times!(t, a::FlowPath, tc::TimeCurve) + jump_times = (tc.t_end - tc.t_start)/(size(a.spin_reset)[2]-1) * (getindex.(findall(a.spin_reset .== 1), 2) .- 1) .- 1e-6 append!(t, jump_times) end \ No newline at end of file diff --git a/KomaMRIBase/test/runtests.jl b/KomaMRIBase/test/runtests.jl index 62f6be653..6de9feea0 100644 --- a/KomaMRIBase/test/runtests.jl +++ b/KomaMRIBase/test/runtests.jl @@ -497,9 +497,13 @@ end # Test Motion constructors @testset "Constructors" begin action = Rotate(10.0, 20.0, 40.0) - time = TimeRange(0.0, 0.0) spins = AllSpins() + # 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]) + m = Motion(action, time, spins) @test Motion(action) == m @@ -528,14 +532,6 @@ end @test xt == ph.x .+ vx.*t' @test yt == ph.y .+ vy.*t' @test zt == ph.z .+ vz.*t' - # ----- t_start = t_end -------- - t_start = t_end = 0.0 - t = [-0.5, -0.25, 0.0, 0.25, 0.5] - translation = Translate(dx, dy, dz, TimeRange(t_start, t_end)) - xt, yt, zt = get_spin_coords(translation, ph.x, ph.y, ph.z, t') - @test xt == ph.x .+ dx*[0, 0, 1, 1, 1]' - @test yt == ph.y .+ dy*[0, 0, 1, 1, 1]' - @test zt == ph.z .+ dz*[0, 0, 1, 1, 1]' end @testset "PeriodicTranslate" begin ph = Phantom(x=[1.0], y=[1.0]) @@ -566,14 +562,6 @@ end @test xt[end ,end] ≈ rot_x @test yt[end ,end] ≈ rot_y @test zt[end ,end] ≈ rot_z - # ----- t_start = t_end -------- - t_start = t_end = 0.0 - t = [-0.5, -0.25, 0.0, 0.25, 0.5] - rotation = Rotate(pitch, roll, yaw, TimeRange(t_start, t_end)) - xt, yt, zt = get_spin_coords(rotation, ph.x, ph.y, ph.z, t') - @test xt ≈ [ph.x ph.x rot_x rot_x rot_x] - @test yt ≈ [ph.y ph.y rot_y rot_y rot_y] - @test zt ≈ [ph.z ph.z rot_z rot_z rot_z] end @testset "PeriodicRotation" begin ph = Phantom(x=[1.0], y=[1.0]) @@ -607,19 +595,6 @@ end @test xt[:,end] == ph.x .* (1 .+ circumferential_strain * maximum(r) .* cos.(θ)) @test yt[:,end] == ph.y .* (1 .+ circumferential_strain * maximum(r) .* sin.(θ)) @test zt[:,end] == ph.z .* (1 .+ longitudinal_strain) - # ----- t_start = t_end -------- - t_start = t_end = 0.0 - t = [-0.5, -0.25, 0.0, 0.25, 0.5] - heartbeat = HeartBeat(circumferential_strain, radial_strain, longitudinal_strain, TimeRange(t_start, t_end)) - xt, yt, zt = get_spin_coords(heartbeat, ph.x, ph.y, ph.z, t') - r = sqrt.(ph.x .^ 2 + ph.y .^ 2) - θ = atan.(ph.y, ph.x) - dx = (1 .+ circumferential_strain * maximum(r) .* cos.(θ)) - dy = (1 .+ circumferential_strain * maximum(r) .* sin.(θ)) - dz = (1 .+ longitudinal_strain) - @test xt == [ph.x ph.x ph.x .* dx ph.x .* dx ph.x .* dx] - @test yt == [ph.y ph.y ph.y .* dy ph.y .* dy ph.y .* dy] - @test zt == [ph.z ph.z ph.z .* dz ph.z .* dz ph.z .* dz] end @testset "PeriodicHeartBeat" begin ph = Phantom(x=[1.0], y=[1.0]) diff --git a/KomaMRICore/src/simulation/Functors.jl b/KomaMRICore/src/simulation/Functors.jl index 8434818c7..f24b57681 100644 --- a/KomaMRICore/src/simulation/Functors.jl +++ b/KomaMRICore/src/simulation/Functors.jl @@ -110,8 +110,7 @@ adapt_storage(T::Type{<:Real}, xs::MotionList) = MotionList(paramtype.(T, xs.mot @functor HeartBeat @functor Path @functor FlowPath -@functor TimeRange -@functor Periodic +@functor TimeCurve # Spinor @functor Spinor # DiscreteSequence diff --git a/KomaMRIFiles/src/Phantom/Phantom.jl b/KomaMRIFiles/src/Phantom/Phantom.jl index 11a990c9a..fe0c796aa 100644 --- a/KomaMRIFiles/src/Phantom/Phantom.jl +++ b/KomaMRIFiles/src/Phantom/Phantom.jl @@ -57,19 +57,17 @@ function import_motion_field!(motion_fields::Array, motion::HDF5.Group, name::St get_subtypes(t::Type) = reduce(vcat,(subtypes(t))) get_subtype_strings(t::Type) = last.(split.(string.(get_subtypes(t::Type)), ".")) - subtype_strings = reduce(vcat, get_subtype_strings.([ + subtype_strings = [reduce(vcat, get_subtype_strings.([ KomaMRIBase.SimpleAction, KomaMRIBase.ArbitraryAction, - KomaMRIBase.AbstractTimeSpan, KomaMRIBase.AbstractSpinSpan - ])) + ])); "TimeCurve"] - subtype_vector = reduce(vcat, get_subtypes.([ + subtype_vector = [reduce(vcat, get_subtypes.([ KomaMRIBase.SimpleAction, KomaMRIBase.ArbitraryAction, - KomaMRIBase.AbstractTimeSpan, KomaMRIBase.AbstractSpinSpan - ])) + ])); KomaMRIBase.TimeCurve] motion_subfields = [] for (i, subtype_string) in enumerate(subtype_strings) diff --git a/docs/src/reference/2-koma-base.md b/docs/src/reference/2-koma-base.md index 2fc69cf23..439aa7546 100644 --- a/docs/src/reference/2-koma-base.md +++ b/docs/src/reference/2-koma-base.md @@ -44,11 +44,10 @@ FlowPath FlowPath(dx, dy, dz, spin_reset, time, spins) ``` -### `AbstractTimeSpan` types and related functions +### `TimeCurve` types and related functions ```@docs -TimeRange -Periodic +TimeCurve unit_time ``` From a88bae6f0dc636a0d52fdc8b9c1973e5f686d3b4 Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Sat, 4 Jan 2025 13:23:44 +0100 Subject: [PATCH 05/11] 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 32b8b1fd0564f29804fcfcb8514adf6830065b72 Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Sat, 4 Jan 2025 14:17:28 +0100 Subject: [PATCH 06/11] Commit of the following: - Improve docstrings of `TimeCurve` and its custom constructors. - Add figures to `TimeRange` and `Periodic` docstrings - Remove `unit_time` docstring, since it is not exported --- KomaMRIBase/src/motion/TimeCurve.jl | 62 ++++-- docs/src/assets/periodic.svg | 303 ++++++++++++++++++++++++++++ docs/src/assets/time-range.svg | 299 +++++++++++++++++++++++++++ docs/src/reference/2-koma-base.md | 3 +- 4 files changed, 653 insertions(+), 14 deletions(-) create mode 100644 docs/src/assets/periodic.svg create mode 100644 docs/src/assets/time-range.svg diff --git a/KomaMRIBase/src/motion/TimeCurve.jl b/KomaMRIBase/src/motion/TimeCurve.jl index cd5cb38e0..8bb0d0e1f 100644 --- a/KomaMRIBase/src/motion/TimeCurve.jl +++ b/KomaMRIBase/src/motion/TimeCurve.jl @@ -67,10 +67,47 @@ check_unique(t::Vector) = length(t) == length(unique(t)) 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 +""" + timerange = TimeRange(t_start, t_end) + +The `TimeRange` function is a custom constructor for the `TimeCurve` struct. +It allows defining a simple time interval, with start and end times. + +# Arguments +- `t_start`: (`::Real`, `[s]`, `=0.0`) start time +- `t_end`: (`::Real`, `[s]`, `=1.0`) end time + +# Returns +- `timerange`: (`::TimeCurve`) TimeCurve struct + +# Examples +```julia-repl +julia> timerange = TimeRange(t_start=0.6, t_end=1.4) +``` +![Time Range](../assets/time-range.svg) +""" TimeRange(t_start::T, t_end::T) where T = TimeCurve(t=[t_start, t_end], t_unit=[zero(T), oneunit(T)]) TimeRange(; t_start=0.0, t_end=1.0) = TimeRange(t_start, t_end) -# --- Periodic +""" + periodic = Periodic(period, asymmetry) + +The `Periodic` function is a custom constructor for the `TimeCurve` struct. +It allows defining time intervals that repeat periodically with a triangular period. +It includes a measure of asymmetry in order to recreate a asymmetric period. + +# Arguments +- `period`: (`::Real`, `[s]`, `=1.0`) period duration +- `asymmetry`: (`::Real`, `=0.5`) temporal asymmetry factor. Between 0 and 1. + +# Returns +- `periodic`: (`::TimeCurve`) TimeCurve struct + +# Examples +```julia-repl +julia> periodic = Periodic(period=1.0, asymmetry=0.2) +``` +![Periodic](../assets/periodic.svg) +""" Periodic(period::T, asymmetry::T) where T = TimeCurve(t=[zero(T), period*asymmetry, period], t_unit=[zero(T), oneunit(T), zero(T)]) Periodic(; period=1.0, asymmetry=0.5) = Periodic(period, asymmetry) @@ -78,11 +115,13 @@ 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 & 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. +""" times & unit_time """ +# 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::Real) + return per .* t +end function times(t, per::AbstractVector) tr = repeat(t, length(per)) scale = repeat(per, inner=[length(t)]) @@ -90,12 +129,9 @@ function times(t, per::AbstractVector) tr .= (tr .* scale) .+ offsets return tr end -function unit_time(tq, t, t_unit, periodic, per::AbstractVector) - return interpolate_times(times(t, per), repeat(t_unit, length(per)), periodic, tq) -end -function times(t, per::Real) - return per .* t -end function unit_time(tq, t, t_unit, periodic, per::Real) return interpolate_times(t .* per, t_unit, periodic, tq) +end +function unit_time(tq, t, t_unit, periodic, per::AbstractVector) + return interpolate_times(times(t, per), repeat(t_unit, length(per)), periodic, tq) end \ No newline at end of file diff --git a/docs/src/assets/periodic.svg b/docs/src/assets/periodic.svg new file mode 100644 index 000000000..f84409d10 --- /dev/null +++ b/docs/src/assets/periodic.svg @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + 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-range.svg b/docs/src/assets/time-range.svg new file mode 100644 index 000000000..a80537e25 --- /dev/null +++ b/docs/src/assets/time-range.svg @@ -0,0 +1,299 @@ + + + + + + + + + + + + + + + + + + + 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/reference/2-koma-base.md b/docs/src/reference/2-koma-base.md index 439aa7546..86dd6a10d 100644 --- a/docs/src/reference/2-koma-base.md +++ b/docs/src/reference/2-koma-base.md @@ -48,7 +48,8 @@ FlowPath(dx, dy, dz, spin_reset, time, spins) ```@docs TimeCurve -unit_time +TimeRange +Periodic ``` ### `AbstractSpinSapn` types From f4abd19868fd503203333e2bc9bb757e936e59b9 Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Mon, 6 Jan 2025 10:55:12 +0100 Subject: [PATCH 07/11] Empty commit to build documentation --- docs/src/reference/2-koma-base.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/reference/2-koma-base.md b/docs/src/reference/2-koma-base.md index 86dd6a10d..5cc479ff5 100644 --- a/docs/src/reference/2-koma-base.md +++ b/docs/src/reference/2-koma-base.md @@ -49,7 +49,7 @@ FlowPath(dx, dy, dz, spin_reset, time, spins) ```@docs TimeCurve TimeRange -Periodic +Periodic ``` ### `AbstractSpinSapn` types From 887b441879ffdbefa5855d951d99a41ea816f937 Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Mon, 6 Jan 2025 13:17:32 +0100 Subject: [PATCH 08/11] Improve formatting of TimeCurve examples (fix numbered list) --- KomaMRIBase/src/motion/TimeCurve.jl | 44 +++++++++++++---------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/KomaMRIBase/src/motion/TimeCurve.jl b/KomaMRIBase/src/motion/TimeCurve.jl index 8bb0d0e1f..5a3fce662 100644 --- a/KomaMRIBase/src/motion/TimeCurve.jl +++ b/KomaMRIBase/src/motion/TimeCurve.jl @@ -25,30 +25,26 @@ Each element specifies the scaling factor for that repetition. - `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.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) - +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.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} From e23048659196e7ad609f4e7ade26bf74f2c5215c Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Mon, 6 Jan 2025 13:48:14 +0100 Subject: [PATCH 09/11] Update TimeCurve.jl to fix docstring --- KomaMRIBase/src/motion/TimeCurve.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/KomaMRIBase/src/motion/TimeCurve.jl b/KomaMRIBase/src/motion/TimeCurve.jl index 5a3fce662..9436a84f6 100644 --- a/KomaMRIBase/src/motion/TimeCurve.jl +++ b/KomaMRIBase/src/motion/TimeCurve.jl @@ -26,24 +26,32 @@ Each element specifies the scaling factor for that repetition. # 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.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} @@ -130,4 +138,4 @@ function unit_time(tq, t, t_unit, periodic, per::Real) end function unit_time(tq, t, t_unit, periodic, per::AbstractVector) return interpolate_times(times(t, per), repeat(t_unit, length(per)), periodic, tq) -end \ No newline at end of file +end From 6dc38dbd7100b03679727eb42d97d2baf3a60d0d Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Mon, 6 Jan 2025 14:28:53 +0100 Subject: [PATCH 10/11] Update TimeCurve.jl to fix docstrings again --- KomaMRIBase/src/motion/TimeCurve.jl | 51 +++++++++++++---------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/KomaMRIBase/src/motion/TimeCurve.jl b/KomaMRIBase/src/motion/TimeCurve.jl index 9436a84f6..f0fa5eef4 100644 --- a/KomaMRIBase/src/motion/TimeCurve.jl +++ b/KomaMRIBase/src/motion/TimeCurve.jl @@ -25,34 +25,29 @@ Each element specifies the scaling factor for that repetition. - `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.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) +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.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} From c20de8f6b1916d882dfb3a10b04b47597cc2d6bf Mon Sep 17 00:00:00 2001 From: Pablo Villacorta Aylagas Date: Mon, 6 Jan 2025 17:32:59 +0100 Subject: [PATCH 11/11] Fix invalid escape sequence in `TimeCurve` docstring --- KomaMRIBase/src/motion/TimeCurve.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KomaMRIBase/src/motion/TimeCurve.jl b/KomaMRIBase/src/motion/TimeCurve.jl index f0fa5eef4..235f6c9ca 100644 --- a/KomaMRIBase/src/motion/TimeCurve.jl +++ b/KomaMRIBase/src/motion/TimeCurve.jl @@ -1,4 +1,4 @@ -""" +@doc raw""" timecurve = TimeCurve(t, t_unit, periodic, periods) TimeCurve struct. It is a specialized type that defines a time curve, which represents