Attachment 'ScannerSynchClass.m'
Download 1 % Interface for National Instruments PCI 6503 card
2 % Version 3.2
3 %
4 % DESCRIPTION
5 %
6 % N.B.: It does not monitor pulses in the background, so you have to make sure that you wait for any pulse before it comes!
7 %
8 % Properties (internal variables):
9 % IsValid = device set and operational
10 % TR = set a non-zero value (in seconds) for emulation mode (will not detect "real" pulses)
11 % Keys = set a cell of key names for emulation mode. For key names look for KbName.m.
12 % N.B.: Requires PTB.
13 % N.B.: Suppress passing keypresses to MATLAB during the whole experiment
14 %
15 % Clock = interal clock (seconds past since the first scanner pulse or clock reset)
16 % Synch = current state of the scanner synch pulse
17 % TimeOfLastPulse = time (according to the internal clock) of the last pulse
18 % MeasuredTR = estimated TR
19 % SynchCount = number of scanner synch pulses
20 % MissedSynch = number of missed scanner synch pulses
21 %
22 % EmulSynch = is scanner synch pulse emulated
23 % EmulButtons = is button box emulated
24 %
25 % Buttons = current state of the any button
26 % ButtonPresses = index/indices of each button(s) pressed since last check
27 % TimeOfButtonPresses = time (according to the internal clock) of each button(s) pressed since last check
28 % LastButtonPress = index/indices of the last button(s) pressed
29 % TimeOfLastButtonPress = time (according to the internal clock) of the last button press (any)
30 % BBoxTimeout = set a non-Inf value (in seconds) to wait for button press only for a limited time
31 % = set a negative value (in seconds) to wait even in case of response
32 %
33 % Methods (internal functions):
34 % ScannerSynchClass = constructor
35 % delete = destructor
36 % ResetClock = reset internal clock
37 %
38 % ResetSynchCount = reset scanner synch pulse counter
39 % SetSynchReadoutTime(t) = blocks scanner synch pulse readout after a pulse for 't' seconds
40 % WaitForSynch = wait until a scanner synch pulse arrives
41 % CheckSynch(t) = wait for a scanner synch pulse for 't' seconds or unitl a scanner synch pulse arrives (whichever first) and returns whether a scanner synch pulse was detected
42 %
43 % SetButtonReadoutTime(t) = blocks individual button readout after a button press for 't' seconds (detection of other buttons is still possible)
44 % SetButtonBoxReadoutTime(t) = blocks the whole button box readout after a button press for 't' seconds (detection of other buttons is also not possible)
45 % WaitForButtonPress = wait until a button is pressed
46 % WaitForButtonRelease = wait until a button is released
47 %
48 % USAGE
49 %
50 % Initialise:
51 % SSO = ScannerSynchClass;
52 % % SSO = ScannerSynchClass(1); % emulate scanner synch pulse
53 % % SSO = ScannerSynchClass(0,1); % emulate button box
54 % % SSO = ScannerSynchClass(1,1); % emulate scanner synch pulse and button box
55 %
56 % Close:
57 % SSO.delete;
58 %
59 % Example for scanner synch pulse #1: - Simple case
60 % SSO.SetSynchReadoutTime(0.5);
61 % SSO.TR = 2; % allows detecting missing pulses
62 % while SSO.SynchCount < 10 % polls 10 pulses
63 % SSO.WaitForSynch;
64 % fprintf('Pulse %d: %2.3f. Measured TR = %2.3fs\n',...
65 % SSO.SynchCount,...
66 % SSO.TimeOfLastPulse,...
67 % SSO.MeasuredTR);
68 % end
69 %
70 % Example for scanner synch pulse #2 - Chance for missing pulse
71 % SSO.SetSynchReadoutTime(0.5);
72 % SSO.TR = 2; % allows detecting missing pulses
73 % while SSO.SynchCount < 10 % until 10 pulses
74 % WaitSecs(Randi(100)/1000); % in every 0-100 ms ...
75 % if SSO.CheckSynch(0.01) % ... waits for 10 ms for a pulse
76 % fprintf('Pulse %d: %2.3f. Measured TR = %2.3fs. %d synch pulses has/have been missed\n',...
77 % SSO.SynchCount,...
78 % SSO.TimeOfLastPulse,...
79 % SSO.MeasuredTR,...
80 % SSO.MissedSynch);
81 % end
82 % end
83 %
84 % Example for buttons:
85 % SSO.SetButtonReadoutTime(0.5); % block individual buttons
86 % % SSO.SetButtonBoxReadoutTime(0.5); % block the whole buttonbox
87 % % SSO.Keys = {'f1','f2','f3','f4'}; % emulation Buttons #1-#4 with F1-F4
88 % n = 0;
89 % % SSO.BBoxTimeout = 1.5; % Wait for button press for 1.5s
90 % % SSO.BBoxTimeout = -1.5; % Wait for button press for 1.5s even in case of response
91 % SSO.ResetClock;
92 % while n ~= 10 % polls 10 button presses
93 % SSO.WaitForButtonPress; % Wait for any button to be pressed
94 % % SSO.WaitForButtonRelease; % Wait for any button to be released
95 % % SSO.WaitForButtonPress([],2); % Wait for Button #2
96 % % SSO.WaitForButtonPress(2); % Wait for any button for 2s (overrides SSO.BBoxTimeout only for this event)
97 % % SSO.WaitForButtonPress(-2); % Wait for any button for 2s even in case of response (overrides SSO.BBoxTimeout only for this event)
98 % % SSO.WaitForButtonPress(2,2); % Wait for Button #2 for 2s (overrides SSO.BBoxTimeout only for this event)
99 % % SSO.WaitForButtonPress(-2,2); % Wait for Button #2 for 2s even in case of response (overrides SSO.BBoxTimeout only for this event)
100 % n = n + 1;
101 % for b = 1:numel(SSO.ButtonPresses)
102 % fprintf('#%d Button %d ',b,SSO.ButtonPresses(b));
103 % fprintf('pressed at %2.3fs\n',SSO.TimeOfButtonPresses(b));
104 % end
105 % fprintf('Last: Button %d ',SSO.LastButtonPress);
106 % fprintf('pressed at %2.3fs\n\n',SSO.TimeOfLastButtonPress);
107 % end
108 %_______________________________________________________________________
109 % Copyright (C) 2015 MRC CBSU Cambridge
110 %
111 % Tibor Auer: tibor.auer@mrc-cbu.cam.ac.uk
112 %_______________________________________________________________________
113
114 classdef ScannerSynchClass < handle
115
116
117 properties
118 TR = 0 % emulated pulse frequency
119 PulseWidth = 0.005 % emulated pulse width
120
121 Keys = {}
122 BBoxTimeout = Inf % second (timeout for WaitForButtonPress)
123 end
124
125 properties (SetAccess = private)
126 SynchCount = 0
127 MissedSynch = 0
128
129 ButtonPresses
130 TimeOfButtonPresses
131
132 LastButtonPress
133
134 EmulSynch
135 EmulButtons
136 end
137
138 properties (Access = private)
139 DAQ
140 nChannels
141
142 tID % internal timer
143
144 Data % current data
145 Datap % previous data
146 TOA % time of access 1*n
147 TOAp % previous time of access 1*n
148 ReadoutTime = 0 % sec to store data before refresh 1*n
149 BBoxReadout = false
150 BBoxWaitForRealease = false % wait for release instead of press
151
152 isDAQ
153 isPTB
154 end
155
156 properties (Dependent)
157 IsValid
158
159 Clock
160
161 Synch
162 TimeOfLastPulse
163 MeasuredTR
164
165 Buttons
166 TimeOfLastButtonPress
167 end
168
169 methods
170
171 %% Contructor and destructor
172 function obj = ScannerSynchClass(emulSynch,emulButtons)
173 fprintf('Initialising Scanner Synch...');
174 % test environment
175 obj.isDAQ = true;
176 try
177 D = daq.getDevices;
178 if ~D.isvalid ||...
179 ~any(strcmp({D.Vendor.ID},'ni')) ||...
180 ~D.Vendor.isvalid ||...
181 ~D.Vendor.IsOperational, ...
182 obj.isDAQ = false;
183 end % no NI card or not working
184 catch
185 obj.isDAQ = false; % no DA Toolbox
186 end
187
188 % Create session
189 if ((nargin<2) || ~emulSynch || ~emulButtons) && obj.isDAQ
190 warning off daq:Session:onDemandOnlyChannelsAdded
191 obj.DAQ = daq.createSession('ni');
192 % Add channels for scanner pulse
193 obj.DAQ.addDigitalChannel('Dev1', 'port0/line0', 'InputOnly');
194 % Add channels for button 1-4
195 obj.DAQ.addDigitalChannel('Dev1', 'port0/line1', 'InputOnly');
196 obj.DAQ.addDigitalChannel('Dev1', 'port0/line2', 'InputOnly');
197 obj.DAQ.addDigitalChannel('Dev1', 'port0/line3', 'InputOnly');
198 obj.DAQ.addDigitalChannel('Dev1', 'port0/line4', 'InputOnly');
199
200 switch nargin
201 case 2
202 obj.EmulSynch = emulSynch;
203 obj.EmulButtons = emulButtons;
204 case 1
205 obj.EmulSynch = emulSynch;
206 obj.EmulButtons = false;
207 case 0
208 obj.EmulSynch = false;
209 obj.EmulButtons = false;
210 end
211 else
212 obj.isDAQ = false;
213 obj.DAQ.isvalid = true;
214 obj.DAQ.Vendor.isvalid = true;
215 obj.DAQ.Vendor.IsOperational = true;
216 obj.EmulSynch = true;
217 obj.EmulButtons = true;
218
219 obj.DAQ.Channels = 1:5;
220 fprintf('\n');
221 fprintf('WARNING: DAQ card is not in use!\n');
222 end
223
224 obj.isPTB = exist('KbCheck','file') == 2;
225
226 if ~obj.IsValid
227 warning('WARNING: Scanner Synch is not open!');
228 obj.delete;
229 return
230 end
231
232 if obj.EmulSynch
233 fprintf('Emulation: Scanner synch pulse is not in use --> ');
234 fprintf('You may need to set TR!\n');
235 end
236 if obj.EmulButtons
237 fprintf('Emulation: ButtonBox is not in use --> ');
238 fprintf('You may need to set Keys!\n');
239 end
240
241 obj.nChannels = numel(obj.DAQ.Channels);
242
243 obj.Data = zeros(1,obj.nChannels);
244 obj.Datap = zeros(1,obj.nChannels);
245 obj.ReadoutTime = obj.ReadoutTime * ones(1,obj.nChannels);
246 obj.ResetClock;
247 fprintf('Done\n');
248 end
249
250 function delete(obj)
251 fprintf('Scanner Synch is closing...');
252 if obj.isDAQ
253 obj.DAQ.release();
254 delete(obj.DAQ);
255 warning on daq:Session:onDemandOnlyChannelsAdded
256 end
257 if obj.isPTB, ListenChar(0); end
258 fprintf('Done\n');
259 end
260
261 %% Utils
262 function val = get.IsValid(obj)
263 val = ~isempty(obj.DAQ) &&...
264 obj.DAQ.isvalid &&...
265 obj.DAQ.Vendor.isvalid &&...
266 obj.DAQ.Vendor.IsOperational &&...
267 (~obj.EmulButtons || (obj.EmulButtons && obj.isPTB));
268 end
269
270 function ResetClock(obj)
271 obj.tID = tic;
272 obj.TOA = zeros(1,obj.nChannels);
273 obj.TOAp = zeros(1,obj.nChannels);
274 end
275
276 function val = get.Clock(obj)
277 val = toc(obj.tID);
278 end
279
280 function set.Keys(obj,val)
281 obj.Keys = val;
282 if obj.EmulButtons && obj.isPTB, ListenChar(2); end % suppress passing keypresses to MATLAB
283 end
284
285 %% Scanner Pulse
286 function ResetSynchCount(obj)
287 obj.SynchCount = 0;
288 end
289
290 function SetSynchReadoutTime(obj,t)
291 obj.ReadoutTime(1) = t;
292 end
293
294 function WaitForSynch(obj)
295 while ~obj.Synch
296 end
297 obj.NewSynch;
298 end
299
300 function val = CheckSynch(obj,timeout)
301 SynchQuery = obj.Clock;
302
303 val = false;
304
305 while (obj.Clock - SynchQuery) < timeout
306 if obj.Synch
307 obj.NewSynch;
308 val = true;
309 break;
310 end
311 end
312 end
313
314 function val = get.TimeOfLastPulse(obj)
315 val = obj.TOA(1);
316 end
317
318 function val = get.MeasuredTR(obj)
319 val = (obj.TOA(1)-obj.TOAp(1))/(obj.MissedSynch+1);
320 end
321
322 %% Buttons
323 function SetButtonReadoutTime(obj,t)
324 obj.ReadoutTime(2:end) = t;
325 obj.BBoxReadout = false;
326 end
327
328 function SetButtonBoxReadoutTime(obj,t)
329 obj.ReadoutTime(2:end) = t;
330 obj.BBoxReadout = true;
331 end
332
333 function WaitForButtonPress(obj,timeout,ind)
334 BBoxQuery = obj.Clock;
335
336 % Reset indicator
337 obj.ButtonPresses = [];
338 obj.TimeOfButtonPresses = [];
339 obj.LastButtonPress = [];
340
341 % timeout
342 if (nargin < 2 || isempty(timeout)), timeout = obj.BBoxTimeout; end
343 wait = timeout < 0; % wait until timeout even in case of response
344 timeout = abs(timeout);
345
346 while (~obj.Buttons ||... % button pressed
347 wait || ...
348 (nargin >= 3 && ~isempty(ind) && ~any(obj.LastButtonPress == ind))) && ... % correct button pressed
349 (obj.Clock - BBoxQuery) < timeout % timeout
350 if ~isempty(obj.LastButtonPress)
351 if nargin >= 3 && ~isempty(ind) && ~any(obj.LastButtonPress == ind), continue; end % incorrect button
352 if ~isempty(obj.TimeOfButtonPresses) && (obj.TimeOfButtonPresses(end) == obj.TimeOfLastButtonPress), continue; end % same event
353 obj.ButtonPresses = horzcat(obj.ButtonPresses,obj.LastButtonPress);
354 obj.TimeOfButtonPresses = horzcat(obj.TimeOfButtonPresses,ones(1,numel(obj.LastButtonPress))*obj.TimeOfLastButtonPress);
355 end
356 end
357 end
358
359 function WaitForButtonRelease(obj,varargin)
360 % backup settings
361 rot = obj.ReadoutTime(2:end);
362 bbro = obj.BBoxReadout;
363
364 % config for release
365 obj.BBoxWaitForRealease = true;
366 obj.SetButtonBoxReadoutTime(0);
367
368 WaitForButtonPress(obj,varargin);
369
370 % restore settings
371 obj.BBoxWaitForRealease = false;
372 obj.ReadoutTime(2:end) = rot;
373 obj.BBoxReadout = bbro;
374 end
375
376 function val = get.TimeOfLastButtonPress(obj)
377 val = max(obj.TOA(2:end)) * ~isempty(obj.LastButtonPress);
378 end
379
380 function [b, t] = ReadButton(obj)
381 b = obj.LastButtonPress;
382 t = obj.TimeOfLastButtonPress;
383 obj.LastButtonPress = [];
384 obj.ButtonPresses = [];
385 obj.TimeOfButtonPresses = [];
386 end
387
388 %% Low level access
389 function val = get.Synch(obj)
390 val = 0;
391 obj.Refresh;
392 if obj.Data(1)
393 obj.Data(1) = 0;
394 val = 1;
395 end
396 end
397 function val = get.Buttons(obj)
398 val = 0;
399 obj.Refresh;
400 if obj.BBoxWaitForRealease
401 if any(obj.Datap(2:end)) && all(~(obj.Data(2:end).*obj.Datap(2:end)))
402 obj.LastButtonPress = find(obj.Datap(2:end));
403 obj.Datap(2:end) = 0;
404 val = 1;
405 end
406 else
407 if any(obj.Data(2:end))
408 obj.LastButtonPress = find(obj.Data(2:end));
409 obj.Data(2:end) = 0;
410 val = 1;
411 end
412 end
413 end
414 end
415
416 methods (Access = private)
417 function Refresh(obj)
418 t = obj.Clock;
419
420 % get data
421 if obj.isDAQ
422 data = ~inputSingleScan(obj.DAQ); % inverted
423 else
424 data = zeros(1,obj.nChannels);
425 end
426
427 % scanner synch pulse emulation
428 if obj.EmulSynch && obj.TR
429 data(1) = ~obj.SynchCount || ((t-obj.TOA(1) >= obj.TR) && (mod(t-obj.TOA(1),obj.TR) <= obj.PulseWidth));
430 end
431
432 % button press emulation (keyboard) via PTB
433 if obj.EmulButtons
434 nKeys = numel(obj.Keys);
435 if obj.isPTB && nKeys
436 [ ~, ~, keyCode ] = KbCheck;
437 data(2:2-1+nKeys) = keyCode(KbName(obj.Keys));
438 end
439 end
440 if obj.BBoxReadout, obj.TOA(2:end) = max(obj.TOA(2:end)); end
441 ind = obj.ReadoutTime < (t-obj.TOA);
442 obj.Datap = obj.Data;
443 obj.Data(ind) = data(ind);
444 obj.TOAp = obj.TOA;
445 obj.TOA(logical(obj.Data)) = t;
446 end
447
448 function NewSynch(obj)
449 if ~obj.SynchCount
450 obj.ResetClock;
451 obj.SynchCount = 1;
452 else
453 if obj.TR
454 obj.MissedSynch = 0;
455 obj.MissedSynch = round(obj.MeasuredTR/obj.TR)-1;
456 end
457 obj.SynchCount = obj.SynchCount + 1 + obj.MissedSynch;
458 end
459 end
460 end
461 end
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.