concept-collection / seqlab
seqlab / src / engine / pulseq / +mr / @EventLibrary / EventLibrary.m
237 lines · 9.4 KBBlameHistoryRaw
1classdef EventLibrary < handle
2 % EventLibrary Maintain a list of events.
3 %
4 % The class is used by the Sequence class to store events of an MRI
5 % sequence defined using the Pulseq file format.
6 % See http://pulseq.github.io/
7 %
8 % This class is designed for *performance* so some functions can be
9 % compiled to mex files using MATLAB's code generation. A parallel list
10 % of IDs, data, and meta information is maintained to maximise search
11 % speed.
12 %
13 % Sequence Properties:
14 % keys - A list of event IDs
15 % data - A struct array with field 'array' to store data of varying
16 % lengths, remaining compatible with codegen.
17 % lengths - Corresponding lengths of the data arrays
18 % type - Type to distinguish events in the same class (e.g.
19 % trapezoids and arbitrary gradients)
20 %
21 % Sequence Methods:
22 % find - Find an event in the library
23 % insert - Add a new event to the library
24 %
25 % See also mr.Sequence
26 %
27 % Kelvin Layton <kelvin.layton@uniklinik-freiburg.de>
28 % Stefan Kroboth <stefan.kroboth@uniklinik-freiburg.de>
29 % Maxim Zaitsev <maxim.zaitsev@uniklinik-freiburg.de>
31 properties
32 keys;
33 data;
34 lengths;
35 type;
36 keymap;
37 lookup_key;
38 next_free_id;
39 %id_hit_count;
40 end
42 methods
43 function obj = EventLibrary()
44 obj.keys = zeros(1,0);
45 obj.data = struct('array',{});
46 obj.lengths = zeros(1,0);
47 obj.type = char(zeros(1,0));
48 try
49 obj.keymap = configureDictionary("string","double"); % dictionary("",[]); % use newer Matlab dictionary class if it is available -- this makes Pulseq ~40% faster
50 obj.lookup_key = @(key,fallback) obj.keymap.lookup(key,'FallbackValue',fallback);
51 catch
52 %obj.keymap = containers.Map('KeyType', 'char', 'ValueType', 'double'); % fallback to containers.Map %,'UniformValues',true
53 %if mr.aux.isOctave
54 % obj.lookup_key = @(key,fallback) mr.aux.containers_map_lookup_ik(obj.keymap,key,fallback);
55 %else
56 % obj.lookup_key = @(key,fallback) mr.aux.containers_map_lookup_ex(obj.keymap,key,fallback);
57 %end
58 if mr.aux.isOctave
59 obj.keymap = mr.aux.BalancedBST(256);
60 obj.lookup_key = @(key,fallback) obj.keymap.lookup(key,fallback);
61 else
62 obj.keymap = containers.Map('KeyType', 'char', 'ValueType', 'double'); % fallback to containers.Map %,'UniformValues',true
63 obj.lookup_key = @(key,fallback) mr.aux.containers_map_lookup_ex(obj.keymap,key,fallback);
64 end
65 end
66 obj.next_free_id = 1;
67 %obj.id_hit_count=[];
68 end
70 function [id, found] = find(obj, data)
71 %find Lookup a data structure in the given library.
72 % [id,found]=find(lib,data) Return the index of the data in
73 % the library. If the data does not exist in the library then
74 % the index for the next new entry is returned.
75 %
76 % The data is a 1xN array with event-specific data.
77 %
78 % See also insert mr.Sequence.addBlock
80 % use map index for faster searches
81 % matlab is extremely limited with regard to advanced containers
82 % we therefore are forced to use hashed map and convert data to a
83 % string
84 data_string = sprintf('%.6g ', data); % precision can be discussed
85 % try-catch code further below performs ~20% faster for MPRAGE and just a bit slower for TSE
86 % if obj.keymap.isKey(data_string(1:end-1))
87 % id = obj.keymap(data_string(1:end-1));
88 % found = true;
89 % else
90 % id = obj.next_free_id;
91 % found = false;
92 % end
93 % containers.Map does not have a proper find/lookup function so we use direct
94 % access and catch the possible error
95 id = obj.lookup_key(data_string(1:end-1),0);
96 found = (id~=0);
97 if ~found
98 id = obj.next_free_id;
99 end
100 end
102 function [id, found] = find_or_insert(obj, data, type)
103 %find Lookup a data structure in the given library.
104 % [id,found]=find_or_insert(lib,data) Return the index of the data in
105 % the library. If the data does not exist in the library it
106 % is inserted right away
107 %
108 % The data is a 1xN array with event-specific data.
109 %
110 % See also insert mr.Sequence.addBlock
112 % use map index for faster searches
113 % matlab is extremely limited with regard to advanced contasiners
114 % we therefore are forced to use hashed map and convert data to a
115 % string
116 data_string = sprintf('%.6g ', data); % precision can be discussed
117 % try-catch code further below performs ~20% faster for MPRAGE and just a bit slower for TSE
118 % if obj.keymap.isKey(data_string(1:end-1))
119 % id = obj.keymap(data_string(1:end-1));
120 % found = true;
121 % %obj.id_hit_count(id)=obj.id_hit_count(id)+1;
122 % else
123 % id = obj.next_free_id;
124 % found = false;
125 % % insert
126 % obj.keys(id) = id;
127 % obj.data(id).array = data;
128 % obj.lengths(id) = length(data);
129 % if nargin>2
130 % obj.type(id) = type;
131 % end
132 % obj.keymap(data_string(1:end-1)) = id;
133 % %obj.id_hit_count(id)=0;
134 % obj.next_free_id=id+1; % update next_free_id
135 % end
136 % containers.Map does not have a proper find function so we use direct
137 % access and catch the possible error
138 id = obj.lookup_key(data_string(1:end-1),0);
139 found = (id~=0);
140 if ~found
141 id = obj.next_free_id;
142 % insert
143 obj.keys(id) = id;
144 obj.data(id).array = data;
145 obj.lengths(id) = length(data);
146 if nargin>2
147 obj.type(id) = type;
148 end
149 obj.keymap(data_string(1:end-1)) = id;
150 %obj.id_hit_count(id)=0;
151 obj.next_free_id=id+1; % update next_free_id
152 end
153 end
155 function id=insert(obj, id, data, type)
156 %insert Add event to library
157 %
158 % See also find
160 if id==0 % get the next free ID
161 id = obj.next_free_id;
162 end
164 obj.keys(id) = id;
165 obj.data(id).array = data;
166 obj.lengths(id) = length(data);
167 if nargin>3
168 obj.type(id) = type;
169 end
171 % use map index for faster searches
172 % matlab is extremely limited with regard to advanced containers
173 % we therefore are forced to use hashed map and convert data to a
174 % string
175 data_string=sprintf('%.6g ', data);
176 obj.keymap(data_string(1:end-1)) = id;
177 %obj.id_hit_count(id)=0;
178 if id>=obj.next_free_id
179 obj.next_free_id=id+1; % update next_free_id
180 end
181 end
183 function update(obj, id, old_data, new_data, type)
184 if length(obj.keys)>=id
185 data_string=sprintf('%.6g ', old_data); % see EventLibrary.insert()
186 % % this code eliminates the warning but is twice as slow as
187 % % a simpe 'remove'
188 % if obj.keymap.isKey(data_string(1:end-1))
189 % obj.keymap.remove(data_string(1:end-1));
190 % else
191 % % see if the data have been updated already
192 % data_string_new=sprintf('%.6g ', new_data); % see EventLibrary.insert()
193 % if ~obj.keymap.isKey(data_string_new(1:end-1))
194 % warning('The old key (old_data) could not be found and the new key (new_data) does not exist either, looks like there is some inconsistency going');
195 % end
196 % end
197 obj.keymap.remove(data_string(1:end-1));
198 end
199 if nargin>4
200 insert(obj, id, new_data, type);
201 else
202 insert(obj, id, new_data);
203 end
204 end
206 function update_data(obj, id, old_data, new_data, type)
207 %[id, found] = find(obj, old_data);
208 %if found
209 if nargin>4
210 update(obj, id, old_data, new_data, type);
211 else
212 update(obj, id, old_data, new_data)
213 end
214 %else
215 % if nargin>3
216 % insert(obj, id, new_data, type);
217 % else
218 % insert(obj, id, new_data)
219 % end
220 %end
221 end
223 function out = get(obj, id)
224 %get Get element from library by key
225 %
226 % See also find
227 out = struct;
228 out.key = obj.keys(id);
229 out.data = obj.data(id).array;
230 out.length = obj.lengths(id);
231 out.type = obj.type(id);
232 end
234 end
236end