| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- import math
- from types import SimpleNamespace
- from typing import Tuple, Union
- import numpy as np
- from seqgen.pypulseq.calc_duration import calc_duration
- from seqgen.pypulseq.make_delay import make_delay
- from seqgen.pypulseq.make_trapezoid import make_trapezoid
- from seqgen.pypulseq.opts import Opts
- def make_gauss_pulse(
- flip_angle: float,
- apodization: float = 0,
- bandwidth: float = 0,
- center_pos: float = 0.5,
- delay: float = 0,
- dwell: float = 0,
- duration: float = 4e-3,
- freq_offset: float = 0,
- max_grad: float = 0,
- max_slew: float = 0,
- phase_offset: float = 0,
- return_gz: bool = False,
- return_delay: bool = False,
- slice_thickness: float = 0,
- system: Opts = Opts(),
- time_bw_product: float = 4,
- use: str = str(),
- ) -> Union[
- SimpleNamespace,
- Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace],
- Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace, SimpleNamespace],
- ]:
- """
- Create a [optionally slice selective] Gauss pulse.
- See also `pypulseq.Sequence.sequence.Sequence.add_block()`.
- Parameters
- ----------
- flip_angle : float
- Flip angle in radians.
- apodization : float, default=0
- Apodization.
- bandwidth : float, default=0
- Bandwidth in Hertz (Hz).
- center_pos : float, default=0.5
- Position of peak.
- delay : float, default=0
- Delay in seconds (s).
- dwell : float, default=0
- duration : float, default=4e-3
- Duration in seconds (s).
- freq_offset : float, default=0
- Frequency offset in Hertz (Hz).
- max_grad : float, default=0
- Maximum gradient strength of accompanying slice select trapezoidal event.
- max_slew : float, default=0
- Maximum slew rate of accompanying slice select trapezoidal event.
- phase_offset : float, default=0
- Phase offset in Hertz (Hz).
- return_delay : bool, default=False
- Boolean flag to indicate if the delay event has to be returned.
- return_gz : bool, default=False
- Boolean flag to indicate if the slice-selective gradient has to be returned.
- slice_thickness : float, default=0
- Slice thickness of accompanying slice select trapezoidal event. The slice thickness determines the area of the
- slice select event.
- system : Opts, default=Opts()
- System limits.
- time_bw_product : int, default=4
- Time-bandwidth product.
- use : str, default=str()
- Use of radio-frequency gauss pulse event. Must be one of 'excitation', 'refocusing' or 'inversion'.
- Returns
- -------
- rf : SimpleNamespace
- Radio-frequency gauss pulse event.
- gz : SimpleNamespace, optional
- Accompanying slice select trapezoidal gradient event.
- gzr : SimpleNamespace, optional
- Accompanying slice select rephasing trapezoidal gradient event.
- delay : SimpleNamespace, optional
- Delay event.
- Raises
- ------
- ValueError
- If invalid `use` is passed. Must be one of 'excitation', 'refocusing' or 'inversion'.
- If `return_gz=True` and `slice_thickness` was not passed.
- """
- valid_use_pulses = ["excitation", "refocusing", "inversion"]
- if use != "" and use not in valid_use_pulses:
- raise ValueError(
- f"Invalid use parameter. Must be one of 'excitation', 'refocusing' or 'inversion'. Passed: {use}"
- )
- if dwell == 0:
- dwell = system.rf_raster_time
- if bandwidth == 0:
- BW = time_bw_product / duration
- else:
- BW = bandwidth
- alpha = apodization
- N = int(np.round(duration / dwell))
- t = (np.arange(1, N + 1) - 0.5) * dwell
- tt = t - (duration * center_pos)
- window = 1 - alpha + alpha * np.cos(2 * np.pi * tt / duration)
- signal = window * __gauss(BW * tt)
- flip = np.sum(signal) * dwell * 2 * np.pi
- signal = signal * flip_angle / flip
- rf = SimpleNamespace()
- rf.type = "rf"
- rf.signal = signal
- rf.t = t
- rf.shape_dur = N * dwell
- rf.freq_offset = freq_offset
- rf.phase_offset = phase_offset
- rf.dead_time = system.rf_dead_time
- rf.ringdown_time = system.rf_ringdown_time
- rf.delay = delay
- if use != "":
- rf.use = use
- if rf.dead_time > rf.delay:
- rf.delay = rf.dead_time
- if return_gz:
- if slice_thickness == 0:
- raise ValueError("Slice thickness must be provided")
- if max_grad > 0:
- system.max_grad = max_grad
- if max_slew > 0:
- system.max_slew = max_slew
- amplitude = BW / slice_thickness
- area = amplitude * duration
- gz = make_trapezoid(
- channel="z", system=system, flat_time=duration, flat_area=area
- )
- gzr = make_trapezoid(
- channel="z",
- system=system,
- area=-area * (1 - center_pos) - 0.5 * (gz.area - area),
- )
- if rf.delay > gz.rise_time:
- gz.delay = (
- np.ceil((rf.delay - gz.rise_time) / system.grad_raster_time)
- * system.grad_raster_time
- )
- if rf.delay < (gz.rise_time + gz.delay):
- rf.delay = gz.rise_time + gz.delay
- if rf.ringdown_time > 0 and return_delay:
- delay = make_delay(calc_duration(rf) + rf.ringdown_time)
- # Following 2 lines of code are workarounds for numpy returning 3.14... for np.angle(-0.00...)
- negative_zero_indices = np.where(rf.signal == -0.0)
- rf.signal[negative_zero_indices] = 0
- if return_gz and return_delay:
- return rf, gz, gzr, delay
- elif return_gz:
- return rf, gz, gzr
- else:
- return rf
- def __gauss(x: np.ndarray) -> np.ndarray:
- return np.exp(-np.pi * np.square(x))
|