| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- from types import SimpleNamespace
- from typing import Tuple, Union
- import numpy as np
- from sigpy.mri.rf import hypsec, wurst
- from seqgen.pypulseq import eps
- from seqgen.pypulseq.calc_duration import calc_duration
- from seqgen.pypulseq.calc_rf_center import calc_rf_center
- from seqgen.pypulseq.make_delay import make_delay
- from seqgen.pypulseq.make_trapezoid import make_trapezoid
- from seqgen.pypulseq.opts import Opts
- from seqgen.pypulseq.supported_labels_rf_use import get_supported_rf_uses
- def make_adiabatic_pulse(
- pulse_type: str,
- adiabaticity: int = 4,
- bandwidth: int = 40000,
- beta: int = 800,
- delay: float = 0,
- duration: float = 10e-3,
- dwell: float = 0,
- freq_offset: float = 0,
- max_grad: float = 0,
- max_slew: float = 0,
- n_fac: int = 40,
- mu: float = 4.9,
- phase_offset: float = 0,
- return_gz: bool = False,
- return_delay: bool = False,
- slice_thickness: float = 0,
- system=Opts(),
- use: str = str(),
- ) -> Union[
- SimpleNamespace,
- Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace, SimpleNamespace],
- Tuple[SimpleNamespace, SimpleNamespace, SimpleNamespace],
- ]:
- """
- Make an adiabatic inversion pulse.
- Note: some parameters only affect certain pulse types and are ignored for other; e.g. bandwidth is ignored if
- type='hypsec'.
- hypsec(n=512, beta=800, mu=4.9, dur=0.012)
- Design a hyperbolic secant adiabatic pulse. `mu` * `beta` becomes the amplitude of the frequency sweep.
- Args:
- - n (int): number of samples (should be a multiple of 4).
- - beta (float): AM waveform parameter.
- - mu (float): a constant, determines amplitude of frequency sweep.
- - dur (float): pulse time (s).
- Returns:
- 2-element tuple containing
- - **a** (*array*): AM waveform.
- - **om** (*array*): FM waveform (radians/s).
- References:
- Baum, J., Tycko, R. and Pines, A. (1985). 'Broadband and adiabatic
- inversion of a two-level system by phase-modulated pulses'.
- Phys. Rev. A., 32:3435-3447.
- wurst(n=512, n_fac=40, bw=40000.0, dur=0.002)
- Design a WURST (wideband, uniform rate, smooth truncation) adiabatic inversion pulse
- Args:
- - n (int): number of samples (should be a multiple of 4).
- - n_fac (int): power to exponentiate to within AM term. ~20 or greater is typical.
- - bw (float): pulse bandwidth.
- - dur (float): pulse time (s).
- Returns:
- 2-element tuple containing
- - **a** (*array*): AM waveform.
- - **om** (*array*): FM waveform (radians/s).
- References:
- Kupce, E. and Freeman, R. (1995). 'Stretched Adiabatic Pulses for
- Broadband Spin Inversion'.
- J. Magn. Reson. Ser. A., 117:246-256.
- Parameters
- ----------
- pulse_type : str
- One of 'hypsec' or 'wurst' pulse types.
- adiabaticity : int, default=4
- bandwidth : int, default=40000
- Pulse bandwidth.
- beta : int, default=800
- AM waveform parameter.
- delay : float, default=0
- Delay in seconds (s).
- duration : float, default=10e-3
- Pulse time (s).
- dwell : float, default=0
- freq_offset : float, default=0
- max_grad : float, default=0
- Maximum gradient strength.
- max_slew : float, default=0
- Maximum slew rate.
- mu : float, default=4.9
- Constant determining amplitude of frequency sweep.
- n_fac : int, default=40
- Power to exponentiate to within AM term. ~20 or greater is typical.
- phase_offset : float, default=0
- Phase offset.
- return_delay : bool, default=False
- Boolean flag to indicate if the delay 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
- system : Opts, default=Opts()
- System limits.
- use : str
- Whether it is a 'refocusing' pulse (for k-space calculation).
- Returns
- -------
- rf : SimpleNamespace
- Adiabatic RF pulse event.
- gz : SimpleNamespace, optional
- Slice-selective trapezoid event.
- gzr : SimpleNamespace, optional
- Slice-select rephasing trapezoid event.
- delay : SimpleNamespace, optional
- Delay event.
- Raises
- ------
- ValueError
- If invalid pulse type is encountered.
- If invalid pulse use is encountered.
- If slice thickness is not provided but slice-selective trapezoid event is expected.
- """
- valid_pulse_types = ["hypsec", "wurst"]
- if pulse_type != "" and pulse_type not in valid_pulse_types:
- raise ValueError(
- f"Invalid type parameter. Must be one of {valid_pulse_types}.Passed: {pulse_type}"
- )
- valid_pulse_uses = get_supported_rf_uses()
- if use != "" and use not in valid_pulse_uses:
- raise ValueError(
- f"Invalid use parameter. Must be one of {valid_pulse_uses}. Passed: {use}"
- )
- if dwell == 0:
- dwell = system.rf_raster_time
- n_raw = np.round(duration / dwell + eps)
- # Number of points must be divisible by 4 - requirement of sigpy.mri
- N = np.floor(n_raw / 4) * 4
- if pulse_type == "hypsec":
- am, fm = hypsec(n=N, beta=beta, mu=mu, dur=duration)
- elif pulse_type == "wurst":
- am, fm = wurst(n=N, n_fac=n_fac, bw=bandwidth, dur=duration)
- else:
- raise ValueError("Unsupported adiabatic pulse type.")
- pm = np.cumsum(fm) * dwell
- ifm = np.argmin(np.abs(fm))
- dfm = np.abs(fm)[ifm]
- # Find rate of change of frequency at the center of the pulse
- if dfm == 0:
- pm0 = pm[ifm]
- am0 = am[ifm]
- roc_fm0 = np.abs(fm[ifm + 1] - fm[ifm - 1]) / 2 / dwell
- else: # We need to bracket the zero-crossing
- if fm[ifm] * fm[ifm + 1] < 0:
- b = 1
- else:
- b = -1
- pm0 = (pm[ifm] * fm[ifm + b] - pm[ifm + b] * fm[ifm]) / (fm[ifm + b] - fm[ifm])
- am0 = (am[ifm] * fm[ifm + b] - am[ifm + b] * fm[ifm]) / (fm[ifm + b] - fm[ifm])
- roc_fm0 = np.abs(fm[ifm] - fm[ifm + b]) / dwell
- pm -= pm0
- a = (roc_fm0 * adiabaticity) ** 0.5 / 2 / np.pi / am0
- signal = a * am * np.exp(1j * pm)
- if N != n_raw:
- n_pad = n_raw - N
- signal = [
- np.zeros(1, n_pad - np.floor(n_pad / 2)),
- signal,
- np.zeros(1, np.floor(n_pad / 2)),
- ]
- N = n_raw
- t = (np.arange(1, N + 1) - 0.5) * dwell
- 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
- else:
- rf.use = "inversion"
- 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
- if pulse_type == "hypsec":
- bandwidth = mu * beta / np.pi
- elif pulse_type == "wurst":
- bandwidth = bandwidth
- else:
- raise ValueError("Unsupported adiabatic pulse type.")
- center_pos, _ = calc_rf_center(rf)
- amplitude = bandwidth / 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: # Round-up to gradient raster
- 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)
- if return_gz and return_delay:
- return rf, gz, gzr, delay
- elif return_gz:
- return rf, gz, gzr
- else:
- return rf
|