make_extended_trapezoid.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. from types import SimpleNamespace
  2. import numpy as np
  3. from seqgen.pypulseq import eps
  4. from seqgen.pypulseq.make_arbitrary_grad import make_arbitrary_grad
  5. from seqgen.pypulseq.opts import Opts
  6. from seqgen.pypulseq.points_to_waveform import points_to_waveform
  7. def make_extended_trapezoid(
  8. channel: str,
  9. amplitudes: np.ndarray = np.zeros(1),
  10. convert_to_arbitrary: bool = False,
  11. max_grad: float = 0,
  12. max_slew: float = 0,
  13. skip_check: bool = False,
  14. system: Opts = Opts(),
  15. times: np.ndarray = np.zeros(1),
  16. ) -> SimpleNamespace:
  17. """
  18. Create a gradient by specifying a set of points (amplitudes) at specified time points(times) at a given channel
  19. with given system limits. Returns an arbitrary gradient object.
  20. See also:
  21. - `pypulseq.Sequence.sequence.Sequence.add_block()`
  22. - `pypulseq.opts.Opts`
  23. - `pypulseq.make_trapezoid.make_trapezoid()`
  24. Parameters
  25. ----------
  26. channel : str
  27. Orientation of extended trapezoidal gradient event. Must be one of 'x', 'y' or 'z'.
  28. convert_to_arbitrary : bool, default=False
  29. Boolean flag to indicate if the extended trapezoid gradient has to be converted into an arbitrary gradient.
  30. amplitudes : numpy.ndarray, default=09
  31. Values defined at `times` time indices.
  32. max_grad : float, default=0
  33. Maximum gradient strength.
  34. max_slew : float, default=0
  35. Maximum slew rate.
  36. system : Opts, default=Opts()
  37. System limits.
  38. skip_check : bool, default=False
  39. Boolean flag to indicate if amplitude check is to be skipped.
  40. times : numpy.ndarray, default=np.zeros(1)
  41. Time points at which `amplitudes` defines amplitude values.
  42. Returns
  43. -------
  44. grad : SimpleNamespace
  45. Extended trapezoid gradient event.
  46. Raises
  47. ------
  48. ValueError
  49. If invalid `channel` is passed. Must be one of 'x', 'y' or 'z'.
  50. If all elements in `times` are zero.
  51. If elements in `times` are not in ascending order or not distinct.
  52. If all elements in `amplitudes` are zero.
  53. If first amplitude of a gradient is non-ero and does not connect to a previous block.
  54. """
  55. if channel not in ["x", "y", "z"]:
  56. raise ValueError(
  57. f"Invalid channel. Must be one of 'x', 'y' or 'z'. Passed: {channel}"
  58. )
  59. times = np.asarray(times)
  60. amplitudes = np.asarray(amplitudes)
  61. if len(times) != len(amplitudes):
  62. raise ValueError("Times and amplitudes must have the same length.")
  63. if np.all(times == 0):
  64. raise ValueError("At least one of the given times must be non-zero")
  65. if np.any(np.diff(times) <= 0):
  66. raise ValueError(
  67. "Times must be in ascending order and all times must be distinct"
  68. )
  69. if (
  70. np.abs(
  71. np.round(times[-1] / system.grad_raster_time) * system.grad_raster_time
  72. - times[-1]
  73. )
  74. > eps
  75. ):
  76. raise ValueError("The last time point must be on a gradient raster")
  77. if skip_check is False and times[0] > 0 and amplitudes[0] != 0:
  78. raise ValueError(
  79. "If first amplitude of a gradient is non-zero, it must connect to previous block"
  80. )
  81. if max_grad <= 0:
  82. max_grad = system.max_grad
  83. if max_slew <= 0:
  84. max_slew = system.max_slew
  85. if convert_to_arbitrary:
  86. # Represent the extended trapezoid on the regularly sampled time grid
  87. waveform = points_to_waveform(
  88. times=times, amplitudes=amplitudes, grad_raster_time=system.grad_raster_time
  89. )
  90. grad = make_arbitrary_grad(
  91. channel=channel,
  92. waveform=waveform,
  93. system=system,
  94. max_slew=max_slew,
  95. max_grad=max_grad,
  96. delay=times[0],
  97. )
  98. else:
  99. # Keep the original possibly irregular sampling
  100. if np.any(
  101. np.abs(
  102. np.round(times / system.grad_raster_time) * system.grad_raster_time
  103. - times
  104. )
  105. > eps
  106. ):
  107. raise ValueError(
  108. 'All time points must be on a gradient raster or "convert_to_arbitrary" option must be used.'
  109. )
  110. grad = SimpleNamespace()
  111. grad.type = "grad"
  112. grad.channel = channel
  113. grad.waveform = amplitudes
  114. grad.delay = (
  115. np.round(times[0] / system.grad_raster_time) * system.grad_raster_time
  116. )
  117. grad.tt = times - grad.delay
  118. grad.shape_dur = (
  119. np.round(times[-1] / system.grad_raster_time) * system.grad_raster_time
  120. )
  121. grad.first = amplitudes[0]
  122. grad.last = amplitudes[-1]
  123. return grad