forked from jcbyts/MarmoV5
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MarmoV5linux.m
1579 lines (1392 loc) · 60.3 KB
/
MarmoV5linux.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
function varargout = MarmoV5linux(varargin)
% MARMOV5LINUX M-file for MarmoV5linux.fig
%
% THIS IS MARMOV5LINUX VERSION 1B, THIS CORRESPONDS TO THE VERSION TEXT
% IN THE MarmoV5linux.fig FILE
%
% MARMOV5LINUX, by itself, creates a new MARMOV5LINUX or raises the existing
% singleton*.
%
% H = MARMOV5LINUX returns the handle to a new MARMOV5LINUX or the handle to
% the existing singleton*.
%
% MARMOV5LINUX('CALLBACK',hObject,eventData,handles,...) calls the local
% function named CALLBACK in MARMOV5LINUX.M with the given input arguments.
%
% MARMOV5LINUX('Property','Value',...) creates a new MARMOV5LINUX or raises the
% existing singleton*. Starting from the left, property value pairs are
% applied to the GUI before MarmoV5linux_OpeningFcn gets called. An
% unrecognized property name or invalid value makes property application
% stop. All inputs are passed to MarmoV5linux_OpeningFcn via varargin.
%
% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one
% instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES
% Edit the above text to modify the response to help MarmoV5linux
% Last Modified by GUIDE v2.5 27-Apr-2021 13:44:03
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @MarmoV5linux_OpeningFcn, ...
'gui_OutputFcn', @MarmoV5linux_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end
if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT
% --- Executes just before MarmoV5linux is made visible.
function MarmoV5linux_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% varargin command line arguments to MarmoV5linux (see VARARGIN)
% Choose default command line output for MarmoV5linux
handles.output = hObject;
%%%%% IMPORTANT GROUNDWORK FOR THE GUI IS PLACED HERE %%%%%%%%%%%%%%%%%%%%%
% GET SOME CRUCIAL DIRECTORIES -- THESE DIRECTORIES MUST EXIST!!
% Present working directory, location of all GUIs
handles.taskPath = [fullfile(pwd) filesep];
% Settings directory, settings files should be kept here
handles.settingsPath = fullfile(pwd, ['Settings' filesep]);
% Output directory, all data will be saved here!
handles.outputPath = fullfile(pwd, ['Output' filesep]);
% Support data directory, data to support MarmoV5linux or its protocols can be
% kept here unintrusively (e.g. eye calibration values or marmoset images)
handles.supportPath = fullfile(pwd, ['SupportData' filesep]);
%****** start with no settings file
handles.settingsFile = 'none';
set(handles.SettingsFile,'String',handles.settingsFile);
% AS DEFAULT, THE GUI WILL USE THE CALIBRATION SETTINGS AT THE END OF THE
% LAST GUI RUN, THIS GUI SUPPORT DATA IS IN THE 'SUPPORT DATA' DIRECTORY,
% A different calibration file can be loaded, if specified as a field in
% the settings structure, but any changes made will only be saved to the
% default 'MarmoViewLastCalib.mat' -- I suspect this won't be used, but
% could be if two subjects had substantially different eye position gains
handles.calibFile = 'MarmoViewLastCalib.mat';
set(handles.CalibFilename,'String',handles.calibFile);
if exist(handles.calibFile, 'file')
tmp = load([handles.supportPath handles.calibFile]);
handles.C.dx = tmp.dx;
handles.C.dy = tmp.dy;
handles.C.c = tmp.c;
else
handles.C.dx = .1;
handles.C.dy = .1;
handles.C.c = [0 0];
end
handles.eyeTraceRadius = 15;
% This C structure is never changed until a protocol is cleared or
% MarmoV5linux is exited, until then, it may be reset to the C values using
% the ResetCalib callback.
% CREATE THE STRUCTURES USED BY ALL PROTOCOLS
handles.A = struct; % Values necessary for protocols to run current trial
handles.S = struct; % Settings for the protocol, NOT changed while running
handles.P = struct; % Parameters for the current protocol, changeable
handles.SI = handles.S;
handles.PI = struct;
%****** AT SOME POINT THIS TASK CONTROL MAY INCLUDE EPHYS TIMING WRAPPER
handles.FC = marmoview.FrameControl(); % create generic task control
% LOAD RIG SETTINGS TO S, THIS IS RELOADED FOR EACH PROTOCOL, SO IT SHOULD
% BE LOCATED IN A DIRECTORY IN MATLAB'S PATH, I SUGGEST THE
% 'marmov5linux\SupportFunctions' DIRECTORY
handles.outputSubject = 'none';
S = MarmoViewRigSettings;
S.subject = handles.outputSubject;
handles.S = S;
%****** if a DummyEye, use mouse and change coordinates
%****** so the eye is estimated to be where the mouse is located
if handles.S.DummyEye
handles.calibFile = 'Using Mouse as Eye';
set(handles.CalibFilename,'String',handles.calibFile);
cx = round((S.screenRect(3)-S.screenRect(1))/2) + S.screenRect(1);
cy = round((S.screenRect(4)-S.screenRect(2))/2) + S.screenRect(2);
dx = 1; % stay in pixel coordinates
dy = -1; % in pixel coordinates, don't scale, but do invert y
handles.C.dx = dx;
handles.C.dy = dy;
handles.C.c = [cx cy];
end
%********** if using the DataPixx, initialize it here
if (handles.S.DataPixx)
datapixx.init();
end
% Load calibration variables into the A structure to be changed if needed
handles.A = handles.C;
% Add in the plot handles to A in case handles isn't available
% e.g. while running protocols)
handles.A.EyeTrace = handles.EyeTrace;
handles.A.DataPlot1 = handles.DataPlot1;
handles.A.DataPlot2 = handles.DataPlot2;
handles.A.DataPlot3 = handles.DataPlot3;
handles.A.DataPlot4 = handles.DataPlot4;
handles.A.outputFile = 'none';
% OPEN UP COMMUNICATION WITH THE PUMP FOR REWARD DELIVERY -- THIS IS DONE
% IMMEDIATELY USING THE RIG SETTINGS, SO THAT JUICE IS AVAILABLE TO THE
% MARMOSET WHILE NO PROTOCOLS ARE LOADED
if handles.S.newera % create an @newera object for delivering liquid reward
handles.reward = marmoview.newera('port',S.pumpCom,'diameter',S.pumpDiameter,'volume',S.pumpDefVol,'rate',S.pumpRate);
% handles.reward = marmoview.newera('port',S.pumpCom,'diameter',S.pumpDiameter,'volume',S.pumpDefVol,'rate',S.pumpRate, 'baud', 9600);
% handles.reward.open();
% handles.reward.diameter = S.pumpDiameter;
else % no syringe pump? use the @dbgreward object object instead
if handles.S.solenoid
handles.reward = marmoview.SolenoidControl(S.pumpCom);
S.pumpDefVol = handles.reward.volume;
vol = sprintf('%d',S.pumpDefVol);
set(handles.JuiceVolumeText,'String',[vol ' ms']); % displayed in microliters!!
else
handles.reward = marmoview.dbgreward(handles);
end
end
% % TYPICALLY, I PREFER TO HANDLES LARGER/SMALLER REWARDS BY NUMBER OF PULSES
% INSTEAD OF CHANGING THE VOLUME, ALTHOUGH THE VOLUME CAN BE CHANGED, I
% SUGGEST ONLY USING A NUMBER OF JUICE PULSE PARAMETER FOR PROTOCOLS.
% !!!IF YOU DO CHANGE JUICE VOLUME, MAKE SURE THE PUMP IS GIVEN TIME TO
% DELIVER EACH PULSE BEFORE STARTING ON THE NEXT ONE, IT TAKES LONGER TO
% DELIVER A BIG JUICE PULSE THAN A SMALL ONE!!!
handles.A.juiceVolume = handles.reward.volume; %S.pumpDefVol;
% Also start a juice counter, for now at 0 -- It will be reset upon loading
% a new protocol and between trials. But it's changed with the give juice
% button, so best to assign it now
handles.A.juiceCounter = 0;
%******** ADDED VIA SHAUN ************************
%******* and then Arrington wrapper by Jude ******
if isfield(handles.S, 'eyetracker') && ischar(handles.S.eyetracker)
switch handles.S.eyetracker
case 'arrington'
handles.eyetrack = marmoview.eyetrack_arrington(hObject,'EyeDump',S.EyeDump);
case 'ddpi'
handles.eyetrack = marmoview.eyetrack_ddpi(hObject,'EyeDump',S.EyeDump);
case 'eyelink'
% if EYELINK, you must wait until initializing the protocol to setup
% the eye tracker .... for now, set it to a default object
handles.eyetrack = marmoview.eyetrack();
handles.S.eyelink = true;
otherwise
handles.eyetrack = marmoview.eyetrack();
end
else % dump to the old version
if handles.S.arrington % create an @arrington eyetrack object for eye position
handles.eyetrack = marmoview.eyetrack_arrington(hObject,'EyeDump',S.EyeDump);
elseif handles.S.eyelink
% if EYELINK, you must wait until initializing the protocol to setup
% the eye tracker .... for now, set it to a default object
handles.eyetrack = marmoview.eyetrack();
else % no eyetrack, use @eyetrack object instead that uses mouse pointer
handles.eyetrack = marmoview.eyetrack();
end
end
%********************************************************
%********* add the task controller for storing eye movements, flipping
%********* frames
% WRITE THE CALIBRATION DATA INTO THE EYE TRACKER PANEL AND GET THE SIZES
% OF GAIN AND SHIFT CONTROLS FOR CALIBRATING EYE POSITION
% FOR UPDATE EYE TEXT TO RUN PROPPERLY, CALBIRATION MUST ALREADY BE IN
% STRUCTURE 'A'
UpdateEyeText(handles);
handles.shiftSize = str2double(get(handles.ShiftSize,'String'));
handles.gainSize = str2double(get(handles.GainSize,'String'));
% THESE VARIABLES CONTROL THE RUN LOOP
handles.runTask = false;
handles.stopTask = false;
%******** New parameters for running background image
handles.runOneTrial = false;
handles.runImage = false;
handles.lastRunWasImage = false;
% SET ACCESS TO GUI CONTROLS
set(handles.Initialize,'Enable','Off');
set(handles.ClearSettings,'Enable','Off');
set(handles.RunTrial,'Enable','Off');
set(handles.PauseTrial,'Enable','Off');
set(handles.FlipFrame,'Enable','Off');
set(handles.ParameterPanel,'Visible','Off');
set(handles.EyeTrackerPanel,'Visible','Off');
set(handles.Background_Image,'Enable','Off');
set(handles.Calib_Screen,'Enable','Off');
set(handles.TaskPerformancePanel,'Visible','Off');
set(handles.SettingsPanel,'Visible','Off');
% Force to select subject name first thing
set(handles.OutputPrefixEdit,'Enable','Off');
set(handles.OutputSubjectEdit,'String','none');
handles.outputSubject = 'none';
% set(handles.OutputSubjectEdit,'Enable','Off'); %user can edit this!
set(handles.OutputDateEdit,'Enable','Off');
set(handles.OutputSuffixEdit,'Enable','Off');
%****** set names to empty for starting
handles.outputPrefix = [];
handles.outputDateEdit = [];
handles.outputSuffixEdit = [];
%**************
tstring = 'Please select SUBJECT to begin';
set(handles.StatusText,'String',tstring);
% For the protocol title, note that no protocol has been loaded yet
set(handles.ProtocolTitle,'String','No protocol is loaded.');
% The task light is a neutral gray when no protocol is loaded
ChangeLight(handles.TaskLight,[.5 .5 .5]);
UpdateEyeText(handles);
% Update handles structure
guidata(hObject, handles);
% --- Outputs from this function are returned to the command line.
function varargout = MarmoV5linux_OutputFcn(hObject, eventdata, handles) %#ok<*INUSL>
% varargout cell array for returning output args (see VARARGOUT);
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Get default command line output from handles structure
varargout{1} = handles.output;
%%%%% SETTINGS PANEL %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% CHOOSE A SETTINGS FILE
function ChooseSettings_Callback(hObject, eventdata, handles) %#ok<*DEFNU>
% Go into the settings path
cd(handles.settingsPath);
% Have user select the file
handles.settingsFile = uigetfile;
% Show the selected outputfile
if handles.settingsFile ~= 0
set(handles.SettingsFile,'String',handles.settingsFile);
else
% Or no outputfile if cancelled selection
set(handles.SettingsFile,'String','none');
handles.settingsFile = 'none';
end
% If file exists, then we can get the protocol initialized
if exist(handles.settingsFile,'file');
if (strcmp(handles.outputSubject,'none'))
set(handles.Initialize,'Enable','off');
tstring = 'Please select SUBJECT NAME >>>';
else
set(handles.Initialize,'Enable','on');
tstring = 'Ready to initialize protocol...';
end
else
set(handles.Initialize,'Enable','off');
tstring = 'Please select a settings file...';
end
% Regardless, update status
set(handles.StatusText,'String',tstring);
% Return to task directory
cd(handles.taskPath);
% Update handles structure
guidata(hObject, handles);
% INITIALIZE A PROTOCOL FROM THE SETTINGS SELECTED
function Initialize_Callback(hObject, eventdata, handles)
% PREPARE THE GUI FOR INITIALIZING THE PROTOCOL
% Update GUI status
set(handles.StatusText,'String','Initializing...');
% The task light is blue only during protocol initialization
ChangeLight(handles.TaskLight,[.2 .2 1]);
% TURN OFF BUTTONS TO PREVENT FIDDLING DURING INITIALIZATION
set(handles.ChooseSettings,'Enable','Off');
set(handles.Initialize,'Enable','Off');
set(handles.OutputSubjectEdit,'Enable','Off'); % subject already set
% Effect these changes on the GUI immediately
guidata(hObject, handles); drawnow;
% GET PROTOCOL SETTINGS
cd(handles.settingsPath);
cmd = sprintf('[handles.S,handles.P] = %s;',handles.settingsFile(1:end-2));
eval(cmd);
handles.S.subject = handles.outputSubject;
cd(handles.taskPath);
% MOVE THE GUI OFF OF THE VISUAL STIMULUS SCREEN TO THE CONSOLE SCREEN
% THIS IS CHANGED IN PROTOCOL SETTINGS AND IS NOT A NECESSARY SETTING
if isfield(handles.S,'guiLocation')
set(handles.figure1,'Position',handles.S.guiLocation);
end
% SHOW THE PROTOCOL TITLE
set(handles.ProtocolTitle,'String',handles.S.protocolTitle);
% OPEN THE PBT SCREEN
handles.A = marmoview.openScreen(handles.S,handles.A);
% INITIALIZE THE PROTOCOL
cmd = sprintf('handles.PR = %s(handles.A.window);',handles.S.protocol_class);
eval(cmd); %Establishes the PR object
%***************
% GENERATE DEFAULT TRIALS LIST
handles.PR.generate_trialsList(handles.S,handles.P);
%*****************
handles.PR.initFunc(handles.S, handles.P);
%***************
% ALSO GENERATE A BACKGROUND IMAGE VIEWER PROTOCOL
%********* Setup Image Viewer Protocol ******************
cd(handles.settingsPath);
[handles.SI,handles.PI] = BackImage;
cd(handles.taskPath);
% INITIALIZE THE Back Image Protocl
handles.PRI = protocols.PR_BackImage(handles.A.window);
handles.PRI.generate_trialsList(handles.SI,handles.PI);
handles.PRI.initFunc(handles.SI, handles.PI);
%***************
%*****************************************
% INITIALIZE THE TASK CONTROLLER FOR THE TRIAL
handles.FC.initialize(handles.A.window, handles.P, handles.C, handles.S);
% SET UP THE OUTPUT PANEL
% Get the output file name components
handles.outputPrefix = handles.S.protocol;
set(handles.OutputPrefixEdit,'String',handles.outputPrefix);
set(handles.OutputSubjectEdit,'String',handles.outputSubject);
handles.outputDate = datestr(now,'ddmmyy');
set(handles.OutputDateEdit,'String',handles.outputDate);
i = 0; handles.outputSuffix = '00';
% Generate the file name
handles.A.outputFile = strcat(handles.outputPrefix,'_',handles.outputSubject,...
'_',handles.outputDate,'_',handles.outputSuffix,'.mat');
% If the file name already exists, iterate the suffix to a nonexistant file
while exist([handles.outputPath handles.A.outputFile],'file')
i = i+1; handles.outputSuffix = num2str(i,'%.2d');
handles.A.outputFile = strcat(handles.outputPrefix,'_',handles.outputSubject,...
'_',handles.outputDate,'_',handles.outputSuffix,'.mat');
end
% FOR EYELINK, you cannot setup until you have screen pointer and each
% edf file is created per opening the screen
if handles.S.eyelink,
if handles.S.EyeDump
eyeFile = sprintf('%s_%s_%s_%s', ...
handles.outputPrefix, ...
handles.outputSubject, ...
handles.outputDate, ...
handles.outputSuffix);
eyePath = handles.outputPath;
else
eyeFile = [];
eyePath = [];
end
handles.eyetrack = marmoview.eyetrack_eyelink(hObject,handles.A.window,eyeFile,eyePath,...
'EyeDump',handles.S.EyeDump,'screen', handles.S.screenNumber);
end
%*********** ADDED VIA SHAUN
% SC: eye posn data
handles.eyetrack.startfile(handles);
%
%*************************
% Show the file name on the GUI
set(handles.OutputSuffixEdit,'String',handles.outputSuffix);
set(handles.OutputFile,'String',handles.A.outputFile);
% Note that a new output file is being used
handles.A.newOutput = 1;
% SET UP THE PARAMETERS PANEL
% Trial counting section of the parameters
handles.A.j = 1; handles.A.finish = handles.S.finish;
set(handles.TrialCountText,'String',['Trial ' num2str(handles.A.j-1)]);
set(handles.TrialMaxText,'String',num2str(handles.A.finish));
set(handles.TrialMaxEdit,'String','');
% Get strings for the parameters list
handles.pNames = fieldnames(handles.P); % pNames are the actual parameter names
handles.pList = cell(size(handles.pNames,1),1); % pList is the list of parameter names with values
for i = 1:size(handles.pNames,1)
pName = handles.pNames{i};
tName = sprintf('%s = %2g',pName,handles.P.(pName));
handles.pList{i,1} = tName;
end
set(handles.Parameters,'String',handles.pList);
% For the highlighted parameter, provide a description and editable value
set(handles.Parameters,'Value',1);
set(handles.ParameterText,'String',handles.S.(handles.pNames{1}));
set(handles.ParameterEdit,'String',num2str(handles.P.(handles.pNames{1})));
% UPDATE ACCESS TO CONTROLS
set(handles.RunTrial,'Enable','On');
set(handles.FlipFrame,'Enable','On');
set(handles.ClearSettings,'Enable','On');
set(handles.ParameterPanel,'Visible','On');
set(handles.EyeTrackerPanel,'Visible','On');
set(handles.OutputPanel,'Visible','On');
set(handles.OutputSubjectEdit,'Enable','Off');
set(handles.OutputPrefixEdit,'Enable','Off');
set(handles.OutputDateEdit,'Enable','Off');
set(handles.OutputSuffixEdit,'Enable','Off');
set(handles.TaskPerformancePanel,'Visible','On');
set(handles.Background_Image,'Enable','On');
set(handles.Calib_Screen,'Enable','On');
%******* allow for graph zoom in and out
set(handles.GraphZoomIn,'Enable','On');
set(handles.GraphZoomOut,'Enable','On');
%*******Blank the eyetrace plot
h = handles.EyeTrace;
eyeRad = handles.eyeTraceRadius;
set(h,'NextPlot','Replace');
plot(h,0,0,'+k','LineWidth',2);
set(h,'NextPlot','Add');
plot(h,[-eyeRad eyeRad],[0 0],'--','Color',[.5 .5 .5]);
plot(h,[0 0],[-eyeRad eyeRad],'--','Color',[.5 .5 .5]);
axis(h,[-eyeRad eyeRad -eyeRad eyeRad]);
%*************************
if handles.S.DummyEye
EnableEyeCalibration(handles,'Off'); %dont update if Dummy, use mouse
%******* but allow for graph zoom in and out
set(handles.GraphZoomIn,'Enable','On');
set(handles.GraphZoomOut,'Enable','On');
end
% UPDATE GUI STATUS
set(handles.StatusText,'String','Protocol is ready to run trials.');
% Now that a protocol is loaded (but not running), task light is red
ChangeLight(handles.TaskLight,[1 0 0]);
% FINALLY, RESET THE JUICE COUNTER WHENEVER A NEW PROTOCOL IS LOADED
handles.A.juiceCounter = 0;
% UPDATE HANDLES STRUCTURE
guidata(hObject,handles);
% UNLOAD CURRENT PROTOCOL, RESET GUI TO INITIAL STATE
function ClearSettings_Callback(hObject, eventdata, handles)
% DISABLE RUNNING THINGS WHILE CLEARING
set(handles.RunTrial,'Enable','Off');
set(handles.FlipFrame,'Enable','Off');
set(handles.ClearSettings,'Enable','Off');
set(handles.OutputPanel,'Visible','Off');
set(handles.ParameterPanel,'Visible','Off');
set(handles.EyeTrackerPanel,'Visible','Off');
set(handles.OutputPanel,'Visible','Off');
set(handles.TaskPerformancePanel,'Visible','Off');
set(handles.Background_Image,'Enable','Off');
set(handles.Calib_Screen,'Enable','Off');
% Clear plots
plot(handles.DataPlot1,0,0,'+k');
plot(handles.DataPlot2,0,0,'+k');
plot(handles.DataPlot3,0,0,'+k');
plot(handles.DataPlot4,0,0,'+k');
% Eye trace needs to be treated differently to maintain important
% properties
plot(handles.EyeTrace,0,0,'+k');
set(handles.EyeTrace,'UserData',15); % 15 degrees of visual arc is default
%****** ADDED VIA SHAUN **********
%%% SC: eye posn data
% tell ViewPoint to close the eye posn data file
handles.eyetrack.closefile();
%*************************
% DE-INITIALIZE PROTOCOL (remove screens or objects created on init)
handles.PR.closeFunc(); % de-initialize any objects
handles.PRI.closeFunc(); % close the back-ground image protocol
handles.lastRunWasImage = false;
% REFORMAT DATA FILES TO CONDENSED STRUCT
CondenseAppendedData(hObject, handles)
% Close all screens from ptb
sca;
% Save the eye calibration values at closing time to the MarmoViewLastCalib
c = handles.A.c;
dx = handles.A.dx;
dy = handles.A.dy;
if ~handles.S.DummyEye
save([handles.supportPath 'MarmoViewLastCalib.mat'],'c','dx','dy');
end
% Create a structure for A that maintains only basic values required
% outside the protocol
handles.C.c = c; handles.C.dx = dx; handles.C.dy = dy;
A = handles.C;
A.EyeTrace = handles.EyeTrace;
A.DataPlot1 = handles.DataPlot1;
A.DataPlot2 = handles.DataPlot2;
A.DataPlot3 = handles.DataPlot3;
A.DataPlot4 = handles.DataPlot4;
A.outputFile = 'none';
% Reset structures
handles.A = A;
handles.S = MarmoViewRigSettings;
handles.S.subject = handles.outputSubject;
handles.P = struct;
handles.SI = handles.S;
handles.PI = struct;
% If juicer delivery volume was changed during the previous protocol,
% return it to default. Also add the juice counter for the juice button.
% fprintf(handles.A.pump,['0 VOL ' num2str(handles.S.pumpDefVol)]);
% handles.reward.volume = handles.S.pumpDefVol; % milliliters
handles.A.juiceVolume = handles.reward.volume;
handles.A.juiceCounter = 0;
if handles.S.solenoid
set(handles.JuiceVolumeText,'String',sprintf('%3i ms',handles.A.juiceVolume));
else
set(handles.JuiceVolumeText,'String',sprintf('%3i ul',handles.A.juiceVolume));
end
% RE-ENABLE CONTROLS
set(handles.ChooseSettings,'Enable','On');
% Initialize is only available if the settings file exists
handles.settingsFile = get(handles.SettingsFile,'String');
if ~exist([handles.settingsPath handles.settingsFile],'file')
set(handles.Initialize,'Enable','off');
tstring = 'Please select a settings file...';
else
set(handles.Initialize,'Enable','on');
tstring = 'Ready to initialize protocol...';
end
% Update GUI status
set(handles.StatusText,'String',tstring);
% For the protocol title, note that no protocol is now loaded
set(handles.ProtocolTitle,'String','No protocol is loaded.');
% The task light is a neutral gray when no protocol is loaded
ChangeLight(handles.TaskLight,[.5 .5 .5]);
%****** RE-ENABLE THE SUBJECT ENTRY, in case want to change subject and
%****** continue the program without closing MarmoV5linux (should be rare)
set(handles.OutputPanel,'Visible','On');
set(handles.OutputPrefixEdit,'Enable','Off');
set(handles.OutputSubjectEdit,'Enable','On'); %user can edit this!
set(handles.OutputDateEdit,'Enable','Off');
set(handles.OutputSuffixEdit,'Enable','Off');
% Update handles structure
guidata(hObject, handles);
%%%%% TRIAL CONTROL PANEL %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function RunTrial_Callback(hObject, eventdata, handles)
% SET THE TASK TO RUN
handles.runTask = true;
%********* store what is the current EyeTrace to plot, based on
%********* what protocol is most recently called (image or other)
if ~handles.runImage
handles.lastRunWasImage = false;
else
handles.lastRunWasImage = true;
end
%****************************
% SET TASK LIGHT TO GREEN
ChangeLight(handles.TaskLight,[0 1 0]);
%*********
%****** NOTE, maybe you can turn off some graphics figure features
%****** like resize and move functions in the future
%****** Gray out controls so it is clear you can't press them
set(handles.RunTrial,'Enable','Off');
set(handles.FlipFrame,'Enable','Off');
set(handles.Background_Image,'Enable','Off');
set(handles.Calib_Screen,'Enable','Off');
set(handles.CloseGui,'Enable','Off');
set(handles.ClearSettings,'Enable','Off')
set(handles.OutputPrefixEdit,'Enable','Off');
% set(handles.OutputSubjectEdit,'Enable','Off');
set(handles.OutputDateEdit,'Enable','Off');
set(handles.OutputSuffixEdit,'Enable','Off');
%********** even more turned off
set(handles.Parameters,'Enable','Off');
set(handles.TrialMaxEdit,'Enable','Off');
set(handles.JuiceVolumeEdit,'Enable','Off');
set(handles.ChooseSettings,'Enable','Off');
set(handles.Initialize,'Enable','Off');
set(handles.ParameterEdit','Enable','Off');
%********* Optional Turn Offs *****************
%****** These might remain on for calib eye
if ( isfield(handles.P,'InTrialCalib') && (handles.P.InTrialCalib == 1) && ...
(~handles.S.DummyEye) ) %dont allow calibration if dummy screen (use mouse)
if ~handles.S.DummyEye
EnableEyeCalibration(handles,'On');
else
EnableEyeCalibration(handles,'Off');
set(handles.GraphZoomIn,'Enable','On');
set(handles.GraphZoomOut,'Enable','On');
end
UpdateEyeText(handles);
else
EnableEyeCalibration(handles,'Off');
UpdateEyeText(handles);
end
%********** leave the pause button functioning **
set(handles.PauseTrial,'Enable','On');
%***********************************************
%************ ADDED VIA SHAUN ****
%%% SC: eye posn data
% 1. tell ViewPoint to (re-)start recording of eye posn data
handles.eyetrack.unpause();
%%%
%********************************
% UPDATE GUI STATUS
set(handles.StatusText,'String','Protocol trials are running.');
% RESET THE JUICER COUNTER BEFORE ENTERING THE RUN LOOP
handles.A.juiceCounter = 0;
% UPDATE THE HANDLES
guidata(hObject,handles); drawnow;
% MOVE TASK RELATED STRUCTURES OUT OF HANDLES FOR THE RUN LOOP -- this way
% if a callback interrupts the run task function, we can update any changes
% the interrupting callback makes to handles without affecting those task
% related structures. E.g. we can run the task using parameters as they
% were at the start of the trial, while getting ready to cue any changes
% the user made on the next trial.
A = handles.A; % these structs are small enough we will pass them
if ~handles.runImage
S = handles.S; % as arguments .... don't make them huge ... larger
P = handles.P; % data should stay in D, or inside the PR or FC objects
else
S = handles.SI; % pull other arguments for image protocol
P = handles.PI;
end
% IF NOT DATA FILE OPENED, CREATE AND INSERT S Struct first
%****** ONCE OPENED, YOU ONLY APPEND TO THAT FILE EACH TRIAL NEW DATA
cd(handles.outputPath); % goto output directory
if ~exist(A.outputFile)
save(A.outputFile,'S'); % save settings struct to output file
end
cd(handles.taskPath); % return to task directory
%****** pass in any updated calibration params (can calib when paused!)
handles.FC.update_eye_calib(A.c,A.dx,A.dy);
%****** also, check if user turned on showEye during a pause
handles.FC.update_args_from_Pstruct(P); %showEye, eyeIntensity, eye Radius, ...
%*********************************
% RUN TRIALS
CorCount = 0; % count consecutive correct trials (for BackImage interleaving)
SetRunBack = 0; % flag for swapping to interleaved image trials and back
%*******
while handles.runTask && A.j <= A.finish
% 'pause', 'drawnow', 'figure', 'getframe', or 'waitfor' will allow
% other callbacks to interrupt this run task callback -- be aware that
% if handles aren't properly managed then changes either in the run
% loop or in other parts of the GUI may be out-of-sync. Nothing changes
% to GUI-wide handles until the local callback puts them there. If
% other callbacks change handles, and they are not brought into this
% callback, then those changes are lost when this run loop updates that
% handles. This concept is explained further right below during the
% nextCmd handles management.
%******* Check if automatic interleaving of BackImage trials
%******* and set the trial accordingly
if isfield(handles.P,'CycleBackImage')
if handles.P.CycleBackImage > 0
if ~mod((CorCount+1),handles.P.CycleBackImage)
handles.runImage = true;
SetRunBack = 1;
S = handles.SI;
P = handles.PI;
end
end
end
%*****************************
P.rng_before_trial = rng(); % save current state of the random number generator
% set which protocol to use
if handles.runImage
PR = handles.PRI;
else
PR = handles.PR;
end
% if isa(PR, 'protocols.protocol')
% PR = copy(PR); % unlink PR from handles.PR
% end
% EXECUTE THE NEXT TRIAL COMMAND
P = PR.next_trial(S,P);
% UPDATE IN CASE JUICE VOLUME WAS CHANGED USING A PARAMETER
if handles.A.juiceVolume ~= A.juiceVolume
handles.reward.volume = A.juiceVolume; % A.juiceVolume is in milliliters
if (handles.S.solenoid)
set(handles.JuiceVolumeText,'String',sprintf('%3i ms',A.juiceVolume*1e3));
else
set(handles.JuiceVolumeText,'String',sprintf('%3i ul',A.juiceVolume*1e3));
end
handles.A.juiceVolume = A.juiceVolume;
end
% UPDATE HANDLES FROM ANY CHANGES DURING NEXT TRIAL -- IF THIS ISN'T
% DONE, THEN THE OTHER CALLBACKS WILL BE USING A DIFFERENT HANDLES
% STRUCTURE THAN THIS LOOP IS
guidata(hObject,handles);
% ALLOW OTHER CALLBACKS INTO THE QUEUE AND UPDATE HANDLES --
% HERE, HAVING UPDATED ANY RUN LOOP CHANGES TO HANDLES, WE LET OTHER
% CALLBACKS DO THEIR THING. WE THEN GRAB THOSE HANDLES SO THE RUN LOOP
% IS ON THE SAME PAGE. FORTUNATELY, IF A PARAMETER CHANGES IN HANDLES,
% THAT WON'T AFFECT THE CURRENT TRIAL WHICH IS USING 'P', NOT handles.P
pause(.001); handles = guidata(hObject);
% EXECUTE THE RUN TRIAL COMMAND
% eval(handles.runCmd);
%******** IMPLEMENT DEFAULT RUN TRIAL HERE DIRECTLY **********
%***** Note, PR will refer to the PROTOCOL object ************
[FP,TS] = PR.prep_run_trial();
handles.FC.set_task(FP,TS); % load values into class for plotting (FP)
% and to label TimeSensitive states (TS)
% Task Controller flips first frame and logs the trial start
[ex,ey] = handles.eyetrack.getgaze();
pupil = handles.eyetrack.getpupil();
%******* This is where to perform TimeStamp Syncing (start of trial)
STARTCLOCK = handles.FC.prep_run_trial([ex,ey],pupil);
STARTCLOCKTIME = GetSecs;
if (S.DataPixx)
datapixx.strobe(63,0); % send all bits on to mark trial start
end
%***********************
tstring = sprintf('dataFile_InsertString "TRIALSTART:TRIALNO:%5i %2d %2d %2d %2d %2d %2d"',...
handles.A.j,STARTCLOCK(1:6)); % code the sixlet
STARTMESSAGE = str2double(sprintf('%02d', STARTCLOCK));
handles.eyetrack.sendcommand(tstring,STARTMESSAGE);
%***********************************************************
if (S.DataPixx)
for k = 1:6
datapixx.strobe(STARTCLOCK(k),0);
end
end
%**************************************************
%%%%% Start trial loop %%%%%
rewardtimes = [];
runloop = 1;
%****** added to control when juice drop is delivered based on graphics
%****** demands, drop juice on frames with low demands basically
screenTime = GetSecs;
frameTime = (0.5/handles.S.frameRate);
holdrop = 0;
dropreject = 0;
%**************
while runloop
state = PR.get_state();
%%%%% GET ON-LINE VALUES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[ex,ey] = handles.eyetrack.getgaze();
pupil = handles.eyetrack.getpupil();
[currentTime,x,y] = handles.FC.grabeye_run_trial(state,[ex,ey],pupil);
%**********************************
drop = PR.state_and_screen_update(currentTime,x,y);
%******* One idea, only deliver drop if there is alot of time
%******* before the next screen flush (since drop command takes time)
if ( drop > 0)
holdrop = 1;
dropreject = 0;
end
if (holdrop > 0)
droptime = GetSecs;
if ( (droptime-screenTime) < frameTime) || (dropreject > 12)
holdrop = 0;
rewardtimes = [rewardtimes droptime];
handles.reward.deliver();
else
dropreject = dropreject + 1;
end
end
%**********************************
% if (drop > 0)
% rewardtimes = [rewardtimes GetSecs];
% handles.reward.deliver();
% end
%**********************************
% EYE DISPLAY (SHOWEYE), SCREEN FLIP, and
% ANY GUI UPDATING (if not time sensitive states)
[updateGUI,screenTime] = handles.FC.screen_update_run_trial(state);
%********* returns the time of screen flip **********
if updateGUI
drawnow; % regrettable that this has to be included to grab the pause button hit
% Update any changes made to the calibration
handles = guidata(hObject);
%*** pass update back into task controller
A.c = handles.A.c;
A.dx = handles.A.dx;
A.dy = handles.A.dy;
handles.FC.update_eye_calib(A.c,A.dx,A.dy);
end
runloop = PR.continue_run_trial(screenTime);
end
%******** Update eye trace window before ITI start
ENDCLOCK = handles.FC.last_screen_flip(); % set screen to gray, trial over, start ITI
ENDCLOCKTIME = GetSecs;
%******* the data pix strobe will take about 0.5 ms **********
if (S.DataPixx)
datapixx.strobe(62,0); % send all bits on but first (254) to mark trial end
end
%****** AGAIN this is a place for timing event to synch up trial ends
% this takes about 2 ms to send VPX command string
tstring = sprintf('dataFile_InsertString "TRIALENDED:TRIALNO:%5i %2d %2d %2d %2d %2d %2d"',...
handles.A.j,ENDCLOCK(1:6)); % code the sixlet
ENDMESSAGE = str2double(sprintf('%02d', ENDCLOCK));
handles.eyetrack.sendcommand(tstring,ENDMESSAGE);
handles.eyetrack.endtrial();
%****** send the rest of the sixlet via DataPixx
% this sixlet of numbers takes about 2 ms, but not used for time strobe
if (S.DataPixx)
for k = 1:6
datapixx.strobe(ENDCLOCK(k),0);
end
end
%**********************************************************
%******** Any final clean-up for PR in the trial
Iti = PR.end_run_trial();
%*************************************************************
% PLOT THE EYETRACE and enforce an ITI interval
itiStart = GetSecs;
subplot(handles.EyeTrace); hold off; % clear old plot
PR.plot_trace(handles); hold on; % command to plot on eye traces
handles.FC.plot_eye_trace_and_flips(handles); %plot the eye traces
% eval(handles.plotCmd);
while (GetSecs < (itiStart + Iti))
drawnow; % grab GUI events while running ITI interval
handles = guidata(hObject);
end
%*************************************
% UPDATE HANDLES FROM ANY CHANGES DURING RUN TRIAL
guidata(hObject,handles);
% ALLOW OTHER CALLBACKS INTO THE QUEUE AND UPDATE HANDLES
pause(.001); handles = guidata(hObject);
% SKETCH OF MY DATA SOLUTION HERE (I hated that it was passing
% a big D struct into functions)
% D should be a struct that stores per trial data (not everything)
% D.P has trial parameters (struct)
% D.eyeData has the eye trace (matrix)
% D.PR has feedback from the protocol (struct)
% if the protocol is complicated (rev cor), this could be large
% for example, might list every stim shown per frame in trial
% D.C has the eye calibration (struct)
% ******************************
% In this scenario, the PR.end_plots does not get D at all.
% What does that mean, if your PR wants to plot stats over trials
% then it must store its own internal D with that information in
% a list .... so the experimenter needs to police this function.
% It will get the P struct and A each trial and can update then.
%********* Some Data is uploaded automatically from Task Controller
D = struct;
D.P = P; % THE TRIAL PARAMETERS
D.STARTCLOCKTIME = STARTCLOCKTIME;
D.ENDCLOCKTIME = ENDCLOCKTIME;
D.STARTCLOCK = STARTCLOCK;
D.ENDCLOCK = ENDCLOCK;
D.PR = PR.end_plots(P,A); %if critical trial info save as D.PR
if ~handles.runImage
D.PR.name = handles.S.protocol;
if (D.PR.error == 0)
CorCount = CorCount + 1;
end
else
D.PR.name = 'BackImage';
end
D.eyeData = handles.FC.upload_eyeData();
[c,dx,dy] = handles.FC.upload_C();
D.c = c;
D.dx = dx;
D.dy = dy;
D.rewardtimes = rewardtimes; % log the time of juice pulses
D.juiceButtonCount = handles.A.juiceCounter; % SUPPLEMENTARY JUICE DURING THE TRIAL
D.juiceVolume = A.juiceVolume; % THE VOLUME OF JUICE PULSES DURING THE TRIAL
%***************
% SAVE THE DATA
% here is a place to think as well ... what is the best way to save D?
% can we append to a Matlab file only those parts news to the trial??
cd(handles.outputPath); % goto output directory
Dstring = sprintf('D%d',A.j); % will store trial data in this variable
eval(sprintf('%s = D;',Dstring)); % set variable
save(A.outputFile,'-append','S',Dstring); % append file
cd(handles.taskPath); % return to task directory
eval(sprintf('clear %s;',Dstring));
clear D; % release the memory for D once saved
%************** END OF THE TRIAL DATA SECTION *************************
% UPDATE TRIAL COUNT AND FINISH NUMBER
A.j = A.j+1;
set(handles.TrialCountText,'String',num2str(A.j-1));
if ~handles.runOneTrial
A.finish = handles.A.finish;
set(handles.TrialMaxText,'String',num2str(A.finish));
end
% UPDATE IN CASE JUICE VOLUME WAS CHANGED DURING END TRIAL
if handles.A.juiceVolume ~= A.juiceVolume
fprintf(A.pump,['0 VOL ' num2str(A.juiceVolume/1000)]);
if handles.S.solenoid
set(handles.JuiceVolumeText,'String',[num2str(A.juiceVolume) ' ms']);
else
set(handles.JuiceVolumeText,'String',[num2str(A.juiceVolume) ' ul']);
end
end
% UPDATE THE TASK RELATED STRUCTURES IN CASE OF LEAVING THE RUN LOOP
handles.A = A;
if ~handles.runImage
handles.S = S;
handles.P = P;
else
handles.SI = S;
handles.PI = P;
end
%****** if it was an interleave Image trial, set it back proper
if (SetRunBack == 1)
handles.runImage = false;
SetRunBack = 0;
S = handles.S;
P = handles.P;
CorCount = 0;