Skip to content

Commit

Permalink
Merge pull request #442 from JuliaHealth/new-motion
Browse files Browse the repository at this point in the history
New Motion
  • Loading branch information
cncastillo authored Sep 19, 2024
2 parents 4862b40 + e416800 commit 927de27
Show file tree
Hide file tree
Showing 55 changed files with 1,726 additions and 1,266 deletions.
11 changes: 10 additions & 1 deletion .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
steps:
- label: ":pipeline: Launch Tests"
- label: ":pipeline: Upload NoMotion Tests"
env:
TEST_GROUP: "nomotion"
command: buildkite-agent pipeline upload .buildkite/runtests.yml
agents:
queue: "juliagpu"

- label: ":pipeline: Upload Motion Tests"
env:
TEST_GROUP: "motion"
command: buildkite-agent pipeline upload .buildkite/runtests.yml
agents:
queue: "juliagpu"
Expand Down
21 changes: 16 additions & 5 deletions .buildkite/runtests.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
steps:
- group: ":julia: Tests"
- group: ":julia: ($TEST_GROUP) Tests"
steps:
- label: "CPU: Run tests on v{{matrix.version}}"
matrix:
setup:
version:
- "1.9"
- "1.10"
- "1"
plugins:
- JuliaCI/julia#v1:
Expand All @@ -15,6 +15,8 @@ steps:
dirs:
- KomaMRICore/src
- KomaMRICore/ext
env:
TEST_GROUP: $TEST_GROUP
command: |
julia -e 'println("--- :julia: Instantiating project")
using Pkg
Expand All @@ -34,6 +36,7 @@ steps:
matrix:
setup:
version:
- "1.10"
- "1"
plugins:
- JuliaCI/julia#v1:
Expand All @@ -43,6 +46,8 @@ steps:
dirs:
- KomaMRICore/src
- KomaMRICore/ext
env:
TEST_GROUP: $TEST_GROUP
command: |
julia -e 'println("--- :julia: Instantiating project")
using Pkg
Expand All @@ -67,7 +72,7 @@ steps:
matrix:
setup:
version:
- "1.9"
- "1.10"
- "1"
plugins:
- JuliaCI/julia#v1:
Expand All @@ -77,6 +82,8 @@ steps:
dirs:
- KomaMRICore/src
- KomaMRICore/ext
env:
TEST_GROUP: $TEST_GROUP
command: |
julia -e 'println("--- :julia: Instantiating project")
using Pkg
Expand All @@ -101,11 +108,13 @@ steps:
matrix:
setup:
version:
- "1.9"
- "1.10"
- "1"
plugins:
- JuliaCI/julia#v1:
version: "{{matrix.version}}"
env:
TEST_GROUP: $TEST_GROUP
command: |
julia -e 'println("--- :julia: Instantiating project")
using Pkg
Expand All @@ -131,7 +140,7 @@ steps:
matrix:
setup:
version:
- "1.9"
- "1.10"
- "1"
plugins:
- JuliaCI/julia#v1:
Expand All @@ -141,6 +150,8 @@ steps:
dirs:
- KomaMRICore/src
- KomaMRICore/ext
env:
TEST_GROUP: $TEST_GROUP
command: |
julia -e 'println("--- :julia: Instantiating project")
using Pkg
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.9' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
- '1.10' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'.
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
os: [ubuntu-latest, windows-latest, macos-12] # macos-latest] <- M1 Mac was generating problems #386, commented for now
arch: [x64]
Expand Down
14 changes: 8 additions & 6 deletions KomaMRIBase/src/KomaMRIBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ include("datatypes/sequence/ADC.jl")
include("timing/KeyValuesCalculation.jl")
include("datatypes/Sequence.jl")
include("datatypes/sequence/Delay.jl")
# Motion
include("motion/AbstractMotion.jl")
# Phantom
include("datatypes/Phantom.jl")
# Simulator
include("datatypes/simulation/DiscreteSequence.jl")
include("timing/TimeStepCalculation.jl")
include("timing/TrapezoidalIntegration.jl")
include("timing/UnitTime.jl")

# Main
export γ # gyro-magnetic ratio [Hz/T]
Expand All @@ -47,11 +48,12 @@ export kfoldperm, trapz, cumtrapz
# Phantom
export brain_phantom2D, brain_phantom3D, pelvis_phantom2D, heart_phantom
# Motion
export MotionModel
export NoMotion, SimpleMotion, ArbitraryMotion
export SimpleMotionType
export Translation, Rotation, HeartBeat
export PeriodicTranslation, PeriodicRotation, PeriodicHeartBeat
export MotionList, NoMotion, Motion
export Translate, TranslateX, TranslateY, TranslateZ
export Rotate, RotateX, RotateY, RotateZ
export HeartBeat, Path, FlowPath
export TimeRange, Periodic
export SpinRange, AllSpins
export get_spin_coords
# Secondary
export get_kspace, rotx, roty, rotz
Expand Down
92 changes: 47 additions & 45 deletions KomaMRIBase/src/datatypes/Phantom.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
abstract type MotionModel{T<:Real} end

#Motion models:
include("phantom/motion/SimpleMotion.jl")
include("phantom/motion/ArbitraryMotion.jl")
include("phantom/motion/NoMotion.jl")

"""
obj = Phantom(name, x, y, z, ρ, T1, T2, T2s, Δw, Dλ1, Dλ2, Dθ, motion)
Expand All @@ -24,7 +17,7 @@ a property value representing a spin. This struct serves as an input for the sim
- `Dλ1`: (`::AbstractVector{T<:Real}`) spin Dλ1 (diffusion) parameter vector
- `Dλ2`: (`::AbstractVector{T<:Real}`) spin Dλ2 (diffusion) parameter vector
- `Dθ`: (`::AbstractVector{T<:Real}`) spin Dθ (diffusion) parameter vector
- `motion`: (`::MotionModel{T<:Real}`) motion model
- `motion`: (`::AbstractMotion{T<:Real}`) motion
# Returns
- `obj`: (`::Phantom`) Phantom struct
Expand Down Expand Up @@ -54,9 +47,12 @@ julia> obj.ρ
::AbstractVector{T} = zeros(eltype(x), size(x))
#Diff::Vector{DiffusionModel} #Diffusion map
#Motion
motion::MotionModel{T} = NoMotion{eltype(x)}()
motion::AbstractMotion{T} = NoMotion{eltype(x)}()
end

const NON_STRING_PHANTOM_FIELDS = Iterators.filter(x -> fieldtype(Phantom, x) != String, fieldnames(Phantom))
const VECTOR_PHANTOM_FIELDS = Iterators.filter(x -> fieldtype(Phantom, x) <: AbstractVector, fieldnames(Phantom))

"""Size and length of a phantom"""
size(x::Phantom) = size(x.ρ)
Base.length(x::Phantom) = length(x.ρ)
Expand All @@ -65,43 +61,45 @@ Base.iterate(x::Phantom) = (x[1], 2)
Base.iterate(x::Phantom, i::Integer) = (i <= length(x)) ? (x[i], i + 1) : nothing
Base.lastindex(x::Phantom) = length(x)
Base.getindex(x::Phantom, i::Integer) = x[i:i]
Base.view(x::Phantom, i::Integer) = @view(x[i:i])

"""Compare two phantoms"""
Base.:(==)(obj1::Phantom, obj2::Phantom) = reduce(
&,
[getfield(obj1, field) == getfield(obj2, field) for field in Iterators.filter(x -> !(x == :name), fieldnames(Phantom))],
)
Base.:()(obj1::Phantom, obj2::Phantom) = reduce(&, [getfield(obj1, field) getfield(obj2, field) for field in Iterators.filter(x -> !(x == :name), fieldnames(Phantom))])
Base.:(==)(m1::MotionModel, m2::MotionModel) = false
Base.:()(m1::MotionModel, m2::MotionModel) = false
function Base.:(==)(obj1::Phantom, obj2::Phantom)
if length(obj1) != length(obj2) return false end
return reduce(&, [getfield(obj1, field) == getfield(obj2, field) for field in NON_STRING_PHANTOM_FIELDS])
end
function Base.:()(obj1::Phantom, obj2::Phantom)
if length(obj1) != length(obj2) return false end
return reduce(&, [getfield(obj1, field) getfield(obj2, field) for field in NON_STRING_PHANTOM_FIELDS])
end

"""Separate object spins in a sub-group"""
Base.getindex(obj::Phantom, p::Union{AbstractRange,AbstractVector,Colon}) = begin
function Base.getindex(obj::Phantom, p)
fields = []
for field in Iterators.filter(x -> !(x == :name), fieldnames(Phantom))
for field in NON_STRING_PHANTOM_FIELDS
push!(fields, (field, getfield(obj, field)[p]))
end
return Phantom(; name=obj.name, fields...)
end

"""Separate object spins in a sub-group (lightweigth)."""
Base.view(obj::Phantom, p::Union{AbstractRange,AbstractVector,Colon}) = begin
function Base.view(obj::Phantom, p)
fields = []
for field in Iterators.filter(x -> !(x == :name), fieldnames(Phantom))
for field in NON_STRING_PHANTOM_FIELDS
push!(fields, (field, @view(getfield(obj, field)[p])))
end
return Phantom(; name=obj.name, fields...)
end

"""Addition of phantoms"""
+(obj1::Phantom, obj2::Phantom) = begin
name = first(obj1.name * "+" * obj2.name, 50) # The name is limited to 50 characters
fields = []
for field in Iterators.filter(x -> !(x == :name), fieldnames(Phantom))
for field in VECTOR_PHANTOM_FIELDS
push!(fields, (field, [getfield(obj1, field); getfield(obj2, field)]))
end
Nmaxchars = 50
name = first(obj1.name * "+" * obj2.name, Nmaxchars)
return Phantom(; name=name, fields...)
return Phantom(;
name = name,
fields...,
motion = vcat(obj1.motion, obj2.motion, length(obj1), length(obj2)))
end

"""Scalar multiplication of a phantom"""
Expand All @@ -121,25 +119,30 @@ function get_dims(obj::Phantom)
end

"""
obj = heart_phantom(...)
obj = heart_phantom(
circumferential_strain, radial_strain, rotation_angle;
heart_rate, asymmetry
)
Heart-like LV 2D phantom. The variable `circumferential_strain` and `radial_strain` are for streching (if positive)
or contraction (if negative). `rotation_angle` is for rotation.
# Arguments
- `circumferential_strain`: (`::Real`, `=-0.3`) contraction parameter
- `radial_strain`: (`::Real`, `=-0.3`) contraction parameter
- `rotation_angle`: (`::Real`, `=1`) rotation parameter
# Keywords
- `circumferential_strain`: (`::Real`, `=-0.3`) contraction parameter. Between -1 and 1
- `radial_strain`: (`::Real`, `=-0.3`) contraction parameter. Between -1 and 1
- `rotation_angle`: (`::Real`, `=15.0`, `[º]`) maximum rotation angle
- `heart_rate`: (`::Real`, `=60`, `[bpm]`) heartbeat frequency
- `temporal_asymmetry`: (`::Real`, `=0.2`) time fraction of the period in which the systole occurs. Therefore, diastole lasts for `period * (1 - temporal_asymmetry)`
# Returns
- `phantom`: (`::Phantom`) Heart-like LV phantom struct
- `obj`: (`::Phantom`) Heart-like LV phantom struct
"""
function heart_phantom(
function heart_phantom(;
circumferential_strain=-0.3,
radial_strain=-0.3,
rotation_angle=15.0;
rotation_angle=15.0,
heart_rate=60,
asymmetry=0.2,
temporal_asymmetry=0.2,
)
#PARAMETERS
FOV = 10e-2 # [m] Diameter ventricule
Expand Down Expand Up @@ -177,16 +180,15 @@ function heart_phantom(
Dλ1=Dλ1[ρ .!= 0],
Dλ2=Dλ2[ρ .!= 0],
=Dθ[ρ .!= 0],
motion=SimpleMotion(
PeriodicHeartBeat(;
period=period,
asymmetry=asymmetry,
circumferential_strain=circumferential_strain,
radial_strain=radial_strain,
longitudinal_strain=0.0,
motion=MotionList(
HeartBeat(
circumferential_strain,
radial_strain,
0.0,
Periodic(; period=period, asymmetry=temporal_asymmetry),
),
PeriodicRotation(;
period=period, asymmetry=asymmetry, yaw=rotation_angle, pitch=0.0, roll=0.0
Rotate(
0.0, 0.0, rotation_angle, Periodic(; period=period, asymmetry=temporal_asymmetry)
),
),
)
Expand Down Expand Up @@ -318,7 +320,7 @@ function brain_phantom2D(; axis="axial", ss=4, us=1)
end

"""
obj = brain_phantom3D(; ss=4, us=1)
obj = brain_phantom3D(; ss=4, us=1, start_end=[160,200])
Creates a three-dimentional brain Phantom struct.
Default ss=4 sample spacing is 2 mm. Original file (ss=1) sample spacing is .5 mm.
Expand Down
Loading

1 comment on commit 927de27

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KomaMRI Benchmarks

Benchmark suite Current: 927de27 Previous: a26d598 Ratio
MRI Lab/Bloch/CPU/2 thread(s) 224153374 ns 224991845.5 ns 1.00
MRI Lab/Bloch/CPU/4 thread(s) 135808380 ns 174863887 ns 0.78
MRI Lab/Bloch/CPU/8 thread(s) 149398614 ns 90572834 ns 1.65
MRI Lab/Bloch/CPU/1 thread(s) 413189478 ns 347400444 ns 1.19
MRI Lab/Bloch/GPU/CUDA 55925086 ns 57092571.5 ns 0.98
MRI Lab/Bloch/GPU/oneAPI 496536737.5 ns 522866366 ns 0.95
MRI Lab/Bloch/GPU/Metal 553973500 ns 568303458 ns 0.97
MRI Lab/Bloch/GPU/AMDGPU 34739822 ns 36981171 ns 0.94
Slice Selection 3D/Bloch/CPU/2 thread(s) 1032666827.5 ns 1151416902 ns 0.90
Slice Selection 3D/Bloch/CPU/4 thread(s) 624017416 ns 580233533 ns 1.08
Slice Selection 3D/Bloch/CPU/8 thread(s) 388957396 ns 341164837 ns 1.14
Slice Selection 3D/Bloch/CPU/1 thread(s) 2245739282 ns 1930414196.5 ns 1.16
Slice Selection 3D/Bloch/GPU/CUDA 101367770.5 ns 101438582.5 ns 1.00
Slice Selection 3D/Bloch/GPU/oneAPI 636447326 ns 636188156.5 ns 1.00
Slice Selection 3D/Bloch/GPU/Metal 554868916 ns 565478250 ns 0.98
Slice Selection 3D/Bloch/GPU/AMDGPU 58876104 ns 60929383 ns 0.97

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.