event_lib.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. from types import SimpleNamespace
  2. from typing import Tuple
  3. import numpy as np
  4. class EventLibrary:
  5. """
  6. Defines an event library ot maintain a list of events. Provides methods to insert new data and find existing data.
  7. Sequence Properties:
  8. - keys - A list of event IDs
  9. - data - A struct array with field 'array' to store data of varying lengths, remaining compatible with codegen.
  10. - lengths - Corresponding lengths of the data arrays
  11. - type - Type to distinguish events in the same class (e.g. trapezoids and arbitrary gradients)
  12. Sequence Methods:
  13. - find - Find an event in the library
  14. - insert - Add a new event to the library
  15. See also `Sequence.py`.
  16. Attributes
  17. ----------
  18. keys : dict{str, int}
  19. Key-value pairs of event keys and corresponding... event keys.
  20. data : dict{str: numpy.array}
  21. Key-value pairs of event keys and corresponding data.
  22. lengths : dict{str, int}
  23. Key-value pairs of event keys and corresponding length of data values in `self.data`.
  24. type : dict{str, str}
  25. Key-value pairs of event keys and corresponding event types.
  26. keymap : dict{str, int}
  27. Key-value pairs of data values and corresponding event keys.
  28. """
  29. def __init__(self):
  30. self.keys = dict()
  31. self.data = dict()
  32. self.lengths = dict()
  33. self.type = dict()
  34. self.keymap = dict()
  35. self.next_free_ID = 1
  36. def __str__(self) -> str:
  37. s = "EventLibrary:"
  38. s += "\nkeys: " + str(len(self.keys))
  39. s += "\ndata: " + str(len(self.data))
  40. s += "\nlengths: " + str(len(self.lengths))
  41. s += "\ntype: " + str(len(self.type))
  42. return s
  43. def find(self, new_data: np.ndarray) -> Tuple[int, bool]:
  44. """
  45. Finds data `new_data` in event library.
  46. Parameters
  47. ----------
  48. new_data : numpy.ndarray
  49. Data to be found in event library.
  50. Returns
  51. -------
  52. key_id : int
  53. Key of `new_data` in event library, if found.
  54. found : bool
  55. If `new_data` was found in the event library or not.
  56. """
  57. new_data = np.array(new_data)
  58. data_string = np.array2string(
  59. new_data, formatter={"float": lambda x: f"{x:.6g}"}
  60. )
  61. data_string = data_string.replace("[", "")
  62. data_string = data_string.replace("]", "")
  63. try:
  64. key_id = self.keymap[data_string]
  65. found = True
  66. except KeyError:
  67. key_id = 1 if len(self.keys) == 0 else max(self.keys) + 1
  68. found = False
  69. return key_id, found
  70. def find_or_insert(
  71. self, new_data: np.ndarray, data_type: str = str()
  72. ) -> Tuple[int, bool]:
  73. """
  74. Lookup a data structure in the given library and return the index of the data in the library. If the data does
  75. not exist in the library it is inserted right away. The data is a 1xN array with event-specific data.
  76. See also insert `pypulseq.Sequence.sequence.Sequence.add_block()`.
  77. Parameters
  78. ----------
  79. new_data : numpy.ndarray
  80. Data to be found (or added, if not found) in event library.
  81. data_type : str, default=str()
  82. Type of data.
  83. Returns
  84. -------
  85. key_id : int
  86. Key of `new_data` in event library, if found.
  87. found : bool
  88. If `new_data` was found in the event library or not.
  89. """
  90. if not isinstance(new_data, np.ndarray):
  91. new_data = np.array(new_data)
  92. data_string = new_data.tobytes()
  93. if data_string in self.keymap:
  94. key_id = self.keymap[data_string]
  95. found = True
  96. else:
  97. key_id = self.next_free_ID
  98. found = False
  99. # Insert
  100. self.keys[key_id] = key_id
  101. self.data[key_id] = new_data
  102. self.lengths[key_id] = np.max(new_data.shape)
  103. if data_type != str():
  104. self.type[key_id] = data_type
  105. self.keymap[data_string] = key_id
  106. self.next_free_ID = key_id + 1 # Update next_free_id
  107. return key_id, found
  108. def insert(self, key_id: int, new_data: np.ndarray, data_type: str = str()) -> int:
  109. """
  110. Add event to library.
  111. See also `pypulseq.event_library.EventLibrary.find()`.
  112. Parameters
  113. ----------
  114. key_id : int
  115. Key of `new_data`.
  116. new_data : numpy.ndarray
  117. Data to be inserted into event library.
  118. data_type : str, default=str()
  119. Data type of `new_data`.
  120. Returns
  121. -------
  122. key_id : int
  123. Key ID of inserted event.
  124. """
  125. if isinstance(key_id, float):
  126. key_id = int(key_id)
  127. if key_id == 0:
  128. key_id = self.next_free_ID
  129. new_data = np.array(new_data)
  130. self.keys[key_id] = key_id
  131. self.data[key_id] = new_data
  132. self.lengths[key_id] = max(new_data.shape)
  133. if data_type != str():
  134. self.type[key_id] = data_type
  135. data_string = np.array2string(
  136. new_data, formatter={"float_kind": lambda x: "%.6g" % x}
  137. )
  138. data_string = data_string.replace("[", "")
  139. data_string = data_string.replace("]", "")
  140. self.keymap[data_string] = key_id
  141. if key_id >= self.next_free_ID:
  142. self.next_free_ID += 1 # Update next_free_id
  143. return key_id
  144. def get(self, key_id: int) -> dict:
  145. """
  146. Parameters
  147. ----------
  148. key_id : int
  149. Returns
  150. -------
  151. dict
  152. """
  153. return {
  154. "key": self.keys[key_id],
  155. "data": self.data[key_id],
  156. "length": self.lengths[key_id],
  157. "type": self.type[key_id],
  158. }
  159. def out(self, key_id: int) -> SimpleNamespace:
  160. """
  161. Get element from library by key.
  162. See also `pypulseq.event_library.EventLibrary.find()`.
  163. Parameters
  164. ----------
  165. key_id : int
  166. Returns
  167. -------
  168. out : SimpleNamespace
  169. """
  170. out = SimpleNamespace()
  171. out.key = self.keys[key_id]
  172. out.data = self.data[key_id]
  173. out.length = self.lengths[key_id]
  174. out.type = self.type[key_id]
  175. return out
  176. def update(
  177. self,
  178. key_id: int,
  179. old_data: np.ndarray,
  180. new_data: np.ndarray,
  181. data_type: str = str(),
  182. ):
  183. """
  184. Parameters
  185. ----------
  186. key_id : int
  187. old_data : numpy.ndarray
  188. new_data : numpy.ndarray
  189. data_type : str, default=str()
  190. """
  191. if len(self.keys) >= key_id:
  192. data_string = np.array2string(
  193. old_data, formatter={"float_kind": lambda x: "%.6g" % x}
  194. )
  195. data_string = data_string.replace("[", "")
  196. data_string = data_string.replace("]", "")
  197. del self.keymap[data_string]
  198. self.insert(key_id, new_data, data_type)
  199. def update_data(
  200. self,
  201. key_id: int,
  202. old_data: np.ndarray,
  203. new_data: np.ndarray,
  204. data_type: str = str(),
  205. ):
  206. """
  207. Parameters
  208. ----------
  209. key_id : int
  210. old_data : np.ndarray
  211. new_data : np.ndarray
  212. data_type : str
  213. """
  214. self.update(key_id, old_data, new_data, data_type)