-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pulse Designer #305
base: master
Are you sure you want to change the base?
Pulse Designer #305
Changes from 6 commits
133a7b1
882eab9
63fefd7
5898fdd
3e4a339
844c3a5
d1fcb58
2d0749e
33f32fd
94220ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
#### ext | ||
module KomaMRIBaseUnitfulExt | ||
|
||
using KomaMRIBase, Unitful | ||
|
||
#Angle{T} = Union{Quantity{T, NoDims, typeof(u"rad")}, Quantity{T, NoDims, typeof(u"°")}} where T | ||
|
||
function KomaMRIBase.block_pulse(x::Real) | ||
return "Hola" | ||
end | ||
|
||
#function KomaMRIBase.block_pulse(duration::Unitful.Time; | ||
# phase_offset::Angle=0u"°", freq_offset::Frequency=0u"Hz", delay=0u"s", sys=Scanner()) | ||
# duration, phase_offset, freq_offset, delay = upreferred.((duration, phase_offset, freq_offset, delay)) | ||
# return PulseDesigner.block_pulse(FlipAngle(π/2), Duration(duration); phase_offset, freq_offset, delay, sys) | ||
#end | ||
|
||
|
||
#function PulseDesigner.block_pulse(flip_angle::Angle, duration::Unitful.Time; | ||
# phase_offset::Angle=0u"°", freq_offset::Frequency=0u"Hz", delay=0u"s", sys=Scanner()) | ||
# flip_angle, duration, phase_offset, freq_offset, delay = upreferred.((flip_angle, duration, phase_offset, freq_offset, delay)) | ||
# return PulseDesigner.block_pulse(FlipAngle(flip_angle), Duration(duration); phase_offset, freq_offset, delay, sys) | ||
#end | ||
# | ||
#function PulseDesigner.block_pulse(flip_angle::Angle, bandwidth::Unitful.Frequency; | ||
# phase_offset::Angle=0u"°", freq_offset::Frequency=0u"Hz", delay=0u"s", sys=Scanner()) | ||
# flip_angle, bandwidth, phase_offset, freq_offset, delay = upreferred.((flip_angle, bandwidth, phase_offset, freq_offset, delay)) | ||
# return PulseDesigner.block_pulse(FlipAngle(flip_angle), Bandwidth(bandwidth); phase_offset, freq_offset, delay, sys) | ||
#end | ||
# | ||
#function PulseDesigner.block_pulse(flip_angle::Angle, bandwidth::Unitful.Frequency, time_bw_product::DimensionlessQuantity; | ||
# phase_offset::Angle=0u"°", freq_offset::Frequency=0u"Hz", delay=0u"s", sys=Scanner()) | ||
# flip_angle, bandwidth, time_bw_product, phase_offset, freq_offset, delay = upreferred.((flip_angle, bandwidth, time_bw_product, phase_offset, freq_offset, delay)) | ||
# return PulseDesigner.block_pulse(FlipAngle(flip_angle), Bandwidth(bandwidth), TimeBwProduct(time_bw_product); phase_offset, freq_offset, delay, sys) | ||
#end | ||
|
||
|
||
end |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -55,6 +55,15 @@ export get_M0, get_M1, get_M2, get_kspace | |||||
include("sequences/PulseDesigner.jl") | ||||||
export PulseDesigner | ||||||
|
||||||
# Test | ||||||
struct FlipAngle val end | ||||||
struct Duration val end | ||||||
struct Bandwidth val end | ||||||
struct TimeBwProduct val end | ||||||
struct Angle end | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
export FlipAngle, Duration, Bandwidth, TimeBwProduct, Angle | ||||||
include("sequences/RF/block_pulse.jl") | ||||||
|
||||||
#Package version, KomaMRIBase.__VERSION__ | ||||||
using Pkg | ||||||
__VERSION__ = VersionNumber(Pkg.TOML.parsefile(joinpath(@__DIR__, "..", "Project.toml"))["version"]) | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Separate functions in different files. All of them, not only the ones in ADC. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
""" | ||
""" | ||
function make_adc(num_samles; Δtadc=0, duration=0, delay=0, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo num_samles to num_samples There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function will be completelly replaced |
||
freq_offset=0, phase_offset=0, dead_time=0, sys=nothing) | ||
|
||
if !isnothing(sys) | ||
dead_time = sys.ADC_dead_time_T | ||
Δtadc = sys.ADC_Δt | ||
end | ||
Comment on lines
+6
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be required, set the default for optional parameter sys = Scanner() |
||
|
||
if (Δtadc == 0 && duration == 0) || (Δtadc > 0 && duration > 0) | ||
@error "Either dwell or duration must be defined" | ||
end | ||
if duration > 0 | ||
Δtadc = duration / num_samles | ||
elseif Δtadc > 0 | ||
duration = Δtadc * num_samles; | ||
end | ||
Comment on lines
+11
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use multiple dispatch |
||
if dead_time > delay | ||
delay = dead_time; # adcDeadTime is added before the actual sampling (and also second time after the sampling period) | ||
end | ||
|
||
adc = ADC(num_samles, duration, delay, freq_offset, phase_offset) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should output a Sequence There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function will be completelly replaced, all the outpus will be of type Sequence |
||
|
||
return adc | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
""" | ||
""" | ||
function trapezoid(; duration=0, amplitude=0, area=0, flat_area=0, | ||
flat_time=0, rise_time=0, fall_time=0, delay=0, | ||
max_grad=Inf, max_slew=Inf, Δtgr=1e-3, sys=nothing) | ||
|
||
if !isnothing(sys) | ||
max_grad = sys.Gmax | ||
max_slew = sys.Smax | ||
Δtgr = sys.GR_Δt | ||
end | ||
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be required, set the default for optional parameter sys = Scanner() |
||
|
||
if area == 0 && flat_area == 0 && amplitude == 0 | ||
@error "trapezoid: invalid keywords. Must supply either 'area', 'flat_area' or 'amplitude'" | ||
end | ||
if fall_time > 0 && rise_time == 0 | ||
@error "trapezoid: invalid keywords. Must always supply 'rise_time' if 'fall_time' is specified explicitly." | ||
end | ||
|
||
if flat_time > 0 | ||
if amplitude == 0 | ||
if flat_area == 0 | ||
@error "trapezoid: invalid keyworks. When 'flat_time' is provided either 'flat_area' or 'amplitude' must be provided as well; you may consider providing 'duration', 'area' and optionally ramp times instead." | ||
end | ||
amplitude = flat_area / flat_time | ||
end | ||
if rise_time == 0 | ||
rise_time = abs(amplitude) / max_slew; | ||
rise_time = ceil(rise_time / Δtgr) * Δtgr; | ||
if rise_time == 0 | ||
rise_time = Δtgr | ||
end | ||
end | ||
if fall_time == 0 | ||
fall_time = rise_time | ||
end | ||
elseif duration > 0 | ||
if amplitude == 0 | ||
if rise_time == 0 | ||
dC = 1 / abs(2 * max_slew) + 1 / abs(2 * max_slew) | ||
possible = duration^2 > 4 * abs(area) * dC; | ||
@assert possible "Requested area is too large for this gradient. Minimum required duration (assuming triangle gradient can be realized) is $(round(sqrt(4 * abs(area) * dC) * 1e6)) us" | ||
amplitude = (duration - sqrt(duration^2 - 4 * abs(area) * dC)) / (2 * dC) | ||
else | ||
if fall_time == 0 | ||
fall_time = rise_time | ||
end | ||
amplitude = area / (duration - 0.5 * rise_time - 0.5 * fall_time) | ||
possible = duration > (rise_time + fall_time) && abs(amplitude) < max_grad | ||
@assert possible "Requested area is too large for this gradient. Probably amplitude is violated ($(round(abs(amplitude) / max_grad * 100))%)" | ||
end | ||
end | ||
if rise_time == 0 | ||
rise_time = ceil(abs(amplitude) / max_slew / Δtgr) * Δtgr | ||
if rise_time == 0 | ||
rise_time = Δtgr | ||
end | ||
end | ||
if fall_time == 0 | ||
fall_time = rise_time | ||
end | ||
flat_time = duration - rise_time - fall_time | ||
if amplitude == 0 | ||
# Adjust amplitude (after rounding) to achieve given area | ||
amplitude = area / (rise_time / 2 + fall_time / 2 + flat_time) | ||
end | ||
else | ||
if area == 0 | ||
@error "trapezoid: invalid keywords. Must supply area or duration" | ||
else | ||
# find the shortest possible duration | ||
# first check if the area can be realized as a triangle | ||
# if not we calculate a trapezoid | ||
rise_time = ceil(sqrt(abs(area) / max_slew) / Δtgr) * Δtgr | ||
if rise_time < Δtgr # the "area" was probably 0 or almost 0 ... | ||
rise_time = Δtgr; | ||
end | ||
amplitude = area / rise_time | ||
t_eff = rise_time | ||
if abs(amplitude) > max_grad | ||
t_eff = ceil(abs(area) / max_grad / Δtgr) * Δtgr | ||
amplitude = area / t_eff | ||
if rise_time == 0 | ||
rise_time = Δtgr | ||
end | ||
end | ||
flat_time = t_eff - rise_time | ||
fall_time = rise_time | ||
end | ||
end | ||
Comment on lines
+13
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use multiple dispatch to simplify this, the code is too convoluted |
||
|
||
@assert abs(amplitude) <= max_grad "trapezoid: invalid amplitude. Amplitude violation ($(round(abs(amplitude) / max_grad * 100))%)" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use functions to check hardware limits for the input |
||
|
||
return Grad(amplitude, flat_time, rise_time, fall_time, delay) | ||
end | ||
|
||
""" | ||
""" | ||
function arbitrary_grad(waveform; delay=0, | ||
max_grad=Inf, max_slew=Inf, Δtgr=1e-3, sys=nothing) | ||
|
||
if !isnothing(sys) | ||
max_grad = sys.Gmax | ||
max_slew = sys.Smax | ||
Δtgr = sys.GR_Δt | ||
end | ||
Comment on lines
+102
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not required |
||
|
||
slew = (waveform[2:end] - waveform[1:end-1]) / Δtgr | ||
if !isempty(slew) | ||
@assert maximum(abs.(slew)) <= max_slew "Slew rate violation ($(maximum(abs.(slew)) / max_slew * 100)%)" | ||
end | ||
@assert maximum(abs.(waveform)) <= max_grad "Gradient amplitude violation ($(maximum(abs.(waveform)) / max_grad * 100)%)" | ||
Comment on lines
+108
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use functions to check this. |
||
|
||
duration = (length(waveform)-1) * Δtgr | ||
return Grad(waveform, duration, 0, 0, delay) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should output a Sequence There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function will be completelly replaced, all the outpus will be of type Sequence |
||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
|
||
function block_pulse(flip_angle::FlipAngle, duration::Duration; | ||
phase_offset=0, freq_offset=0, delay=0, sys=Scanner()) | ||
|
||
flip_angle, duration = flip_angle.val, duration.val | ||
amplitude = flip_angle / (2π * γ * duration) * exp(im * phase_offset) | ||
dead_time = sys.RF_dead_time_T | ||
ring_down_time = sys.RF_ring_down_T | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why define these? |
||
delay = max(delay, dead_time) | ||
block_duration = delay + duration + ring_down_time | ||
|
||
rf = RF(amplitude, duration, freq_offset, delay) | ||
return Sequence([Grad(0, 0);;], [rf;;], [ADC(0, 0)], block_duration) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need a way to combine events so this is easier, |
||
end | ||
|
||
function block_pulse(flip_angle::FlipAngle, bandwidth::Bandwidth; | ||
phase_offset=0, freq_offset=0, delay=0, sys=Scanner()) | ||
duration = 1 / (4 * bandwidth.val) | ||
return block_pulse(flip_angle, Duration(duration); phase_offset, freq_offset, delay, sys) | ||
end | ||
|
||
function block_pulse(flip_angle::FlipAngle, bandwidth::Bandwidth, time_bw_product::TimeBwProduct; | ||
phase_offset=0, freq_offset=0, delay=0, sys=Scanner()) | ||
duration = time_bw_product.val / bandwidth.val | ||
return block_pulse(flip_angle, Duration(duration); phase_offset, freq_offset, delay, sys) | ||
end | ||
|
||
export block_pulse |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
""" | ||
""" | ||
function sinc_pulse(flip_angle; duration=0, freq_offset=0, phase_offset=0, | ||
time_bw_product=0, apodization=0.5, centerpos=0.5, delay=0, slice_thickness=0, | ||
dead_time=0, ring_down_time=0, Δtrf=1e-5, Δtgr=1e-3, sys=nothing) | ||
|
||
if !isnothing(sys) | ||
dead_time = sys.RF_dead_time_T | ||
ring_down_time = sys.RF_ring_down_T | ||
Δtrf = sys.RF_Δt | ||
Δtgr = sys.GR_Δt | ||
end | ||
Comment on lines
+7
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not required. |
||
|
||
if duration <= 0 | ||
@error "rf pulse duration must be positive" | ||
end | ||
if Δtrf <= 0 | ||
@error "the Δtrf gradient raster time must be positive" | ||
end | ||
|
||
BW = time_bw_product / duration | ||
N = Integer(ceil(duration / Δtrf)) | ||
t = range(0, duration; length=N) | ||
window = (1 - apodization) .+ apodization * cos.(2π * ((t .- (centerpos * duration)) / duration)) | ||
signal = window .* sinc.(BW * (t .- (centerpos * duration))) | ||
flip = 0.5 * sum(signal[2:end] + signal[1:end-1]) * Δtrf * 2π | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use get_flip_angle function, I think this assumes that signal is in a particular unit. Is missing the multiplication by gamma. |
||
signal = signal * flip_angle / flip * cis(phase_offset) | ||
if dead_time > delay | ||
delay = dead_time | ||
end | ||
Comment on lines
+28
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. delay = max(delay, dead_time) |
||
rf = RF(signal, duration, freq_offset, delay) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indentation problem. |
||
|
||
@assert slice_thickness > 0 "slice_thickness must be provided" | ||
|
||
amplitude = BW / slice_thickness | ||
area = amplitude * duration | ||
gz = trapezoid(; flat_time=duration, flat_area=area, sys=sys); | ||
gz_area = gz.A * (gz.T + gz.rise / 2 + gz.fall / 2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need a function to calculate the area of a Grad (get_kspace almost does this) |
||
gzr_area = -area*(1 - centerpos) - 0.5*(gz_area - area) | ||
gzr = trapezoid(; sys=sys, area=gzr_area) | ||
if rf.delay > gz.rise | ||
gz.delay = ceil((rf.delay - gz.rise) / Δtgr) * Δtgr # round-up to gradient raster | ||
end | ||
if rf.delay < gz.rise + gz.delay | ||
rf.delay = gz.rise + gz.delay # these are on the grad raster already which is coarser | ||
end | ||
|
||
dly = Delay(0) | ||
if ring_down_time > 0 | ||
dly = Delay(rf.delay + rf.T + ring_down_time) # I NEED a review | ||
end | ||
|
||
return rf, gz, gzr, dly | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should output a Sequence. The Delay should be the DUR. |
||
end | ||
|
||
""" | ||
""" | ||
function arbitrary_rf(signal, flip; freq_offset=0, phase_offset=0, | ||
time_bw_product=0, bandwidth=0, delay=0, slice_thickness=0, | ||
dead_time=0, ring_down_time=0, Δtrf=1e-5, Δtgr=1e-3, sys=nothing) | ||
|
||
if !isnothing(sys) | ||
dead_time = sys.RF_dead_time_T | ||
ring_down_time = sys.RF_ring_down_T | ||
Δtrf = sys.RF_Δt | ||
Δtgr = sys.GR_Δt | ||
end | ||
|
||
signal = signal / abs(sum(signal * Δtrf)) * flip / 2π * cis(phase_offset) | ||
|
||
N = length(signal) | ||
duration = (N-1) * Δtrf | ||
t = range(0, duration; length=N) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this even being used? ( |
||
|
||
if dead_time > delay | ||
delay = dead_time; | ||
end | ||
|
||
rf = RF(signal, duration, freq_offset, delay) | ||
|
||
if time_bw_product > 0 | ||
if bandwidth > 0 | ||
@error "Both 'bandwidth' and 'time_bw_product' cannot be specified at the same time" | ||
else | ||
bandwidth = time_bw_product / duration | ||
end | ||
end | ||
|
||
@assert slice_thickness > 0 "SliceThickness must be provided" | ||
@assert bandwidth > 0 "Bandwidth of pulse must be provided" | ||
|
||
BW = bandwidth | ||
if time_bw_product > 0 | ||
BW = time_bw_product / duration | ||
end | ||
|
||
amplitude = BW / slice_thickness | ||
area = amplitude * duration | ||
gz = trapezoid(; flat_time=duration, flat_area=area, sys=sys) | ||
gz_area = gz.A * (gz.T + gz.rise / 2 + gz.fall / 2) | ||
gzr_area = -area*(1 - KomaMRIBase.get_RF_center(rf) / rf.T) - 0.5*(gz_area - area) | ||
gzr = trapezoid(; sys=sys, area=gzr_area) | ||
|
||
|
||
if rf.delay > gz.rise | ||
gz.delay = ceil((rf.delay - gz.rise) / Δtgr) * Δtgr # round-up to gradient raster | ||
end | ||
if rf.delay < gz.rise + gz.delay | ||
rf.delay = gz.rise + gz.delay # these are on the grad raster already which is coarser | ||
end | ||
Comment on lines
+108
to
+110
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. delay = max(...) |
||
|
||
dly = Delay(0) | ||
if ring_down_time > 0 | ||
dly = Delay(rf.delay + rf.T + ring_down_time) # I NEED a review | ||
end | ||
Comment on lines
+112
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if ring_down_time is not > 0, it must be zero, therefore |
||
|
||
return rf, gz, gzr, delay | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should output a Sequence |
||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
??