attachment:ScannerSynchClass.m of ScannerSynch - CBU MRI facility Wiki
location: attachment:ScannerSynchClass.m of ScannerSynch

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.
  • [get | view] (2016-04-27 09:03:29, 17.4 KB) [[attachment:ScannerSynchClass.m]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.