align.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. from copy import deepcopy
  2. from types import SimpleNamespace
  3. from typing import List, Union
  4. import numpy as np
  5. from seqgen.pypulseq.calc_duration import calc_duration
  6. def align(
  7. **kwargs: Union[SimpleNamespace, List[SimpleNamespace]]
  8. ) -> List[SimpleNamespace]:
  9. """
  10. Sets delays of the objects within the block to achieve the desired alignment of the objects in the block. Aligns
  11. objects as per specified alignment options by setting delays of the pulse sequence events within the block. All
  12. previously configured delays within objects are taken into account during calculating of the block duration but
  13. then reset according to the selected alignment. Possible values for align_spec are 'left', 'center', 'right'.
  14. Parameters
  15. ----------
  16. args : dict{str, [SimpleNamespace, ...]}
  17. Dictionary mapping of alignment options and `SimpleNamespace` objects.
  18. Format: alignment_spec1=SimpleNamespace, alignment_spec2=[SimpleNamespace, ...], ...
  19. Alignment spec must be one of `left`, `center` or `right`.
  20. Returns
  21. -------
  22. objects : [SimpleNamespace, ...]
  23. List of aligned `SimpleNamespace` objects.
  24. Raises
  25. ------
  26. ValueError
  27. If first parameter is not of type `str`.
  28. If invalid alignment spec is passed. Must be one of `left`, `center` or `right`.
  29. Examples
  30. --------
  31. al_grad1, al_grad2, al_grad3 = align(right=[grad1, grad2, grad3])
  32. """
  33. alignment_specs = list(kwargs.keys())
  34. if not isinstance(alignment_specs[0], str):
  35. raise ValueError(
  36. f"First parameter must be of type str. Passed: {type(alignment_specs[0])}"
  37. )
  38. alignment_options = ["left", "center", "right"]
  39. if np.any([align_opt not in alignment_options for align_opt in alignment_specs]):
  40. raise ValueError("Invalid alignment spec.")
  41. alignments = []
  42. objects = []
  43. for curr_align in alignment_specs:
  44. objects_to_align = kwargs[curr_align]
  45. curr_align = alignment_options.index(curr_align)
  46. if isinstance(objects_to_align, (list, np.ndarray, tuple)):
  47. alignments.extend([curr_align] * len(objects_to_align))
  48. objects.extend(objects_to_align)
  49. elif isinstance(objects_to_align, SimpleNamespace):
  50. alignments.extend([curr_align])
  51. objects.append(objects_to_align)
  52. dur = calc_duration(*objects)
  53. # copy() to emulate pass-by-value; otherwise passed events are modified
  54. objects = deepcopy(objects)
  55. # Set new delays
  56. for i in range(len(objects)):
  57. if alignments[i] == 0:
  58. objects[i].delay = 0
  59. elif alignments[i] == 1:
  60. objects[i].delay = (dur - calc_duration(objects[i])) / 2
  61. elif alignments[i] == 2:
  62. objects[i].delay = dur - calc_duration(objects[i]) + objects[i].delay
  63. if objects[i].delay < 0:
  64. raise ValueError(
  65. "align() attempts to set a negative delay, probably some RF pulses ignore rf_ringdown_time"
  66. )
  67. return objects