-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgoalmaze_cns.m
More file actions
2322 lines (2116 loc) · 91.8 KB
/
goalmaze_cns.m
File metadata and controls
2322 lines (2116 loc) · 91.8 KB
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 goalmaze_cns(newline)
% ____ _
% / ___| ___ __ _| |_ __ ___ __ _ _______
% | | _ / _ \ / _` | | '_ ` _ \ / _` |_ / _ \
% | |_| | (_) | (_| | | | | | | | (_| |/ / __/
% \____|\___/ \__,_|_|_| |_| |_|\__,_/___\___|
%
% ____ __ _ _ _ ____ _ _ __
% / / \/ | __ _| |_| | __ _| |__ | _ \ ___ _ __| |_(_) ___ _ __\ \
% | || |\/| |/ _` | __| |/ _` | '_ \| |_) / _ \| '__| __| |/ _ \| '_ \| |
% | || | | | (_| | |_| | (_| | |_) | __/ (_) | | | |_| | (_) | | | | |
% | ||_| |_|\__,_|\__|_|\__,_|_.__/|_| \___/|_| \__|_|\___/|_| |_| |
% \_\ /_/
% This is a different breed of statescript code that runs in Matlab, like
% L Frank suggested for Statescript in Python, but Matlab.
%
% This is the _CNS portion, in that is is like the central nervous system, or the brains of the operation. The _PNS .sc portion are like the limbs/musculature used by this code to interact with the maze.
% Function Shortcuts (highlight the text and us ctrl+D to navigate)
% -------------------
% SetParameters
% RestartStimulus
%
% ParseStatescriptMessages
% ParseCommands
% Correct
% Incorrect
%
%
% FEATURES NEEDED
% ------------------
%
% - Block version of cue - memory training
% - More unified performance table
% - Auto plotting of unique slices of performance
% characteristics
% - Better gui menu for mode selection, like a popup menu
% (gui master becoming too cluttered)
%
% Consult wishlist.txt
%% ---------------- (A) Preprocessing ----------------
% ---- Statescript QT Callback Variables ----
%Required global variables
global goalinit pokeinit
% ---- Local Callback Variables ----
global maze const state perf tones;
%debug = ''; % {'ci','restart','plotmaze','plotmaze_error'}
%const.app.debug=debug;
% ---- Initialize Maze Variables and Logic? ----
if nargin==0 && isempty(goalinit)
fprintf('INITIALIZING');
SetParameters();
ReinstateGui();
[Y,Fs] = audioread('error.wav');
tones = audioplayer(Y,Fs);
% STARTUP
fprintf('Startup delay = %d seconds\n',const.dur.start);
pause(const.dur.start);
if state.gostate; restartStimulus([],{'home','cue'}); else; sendScQtControlMessage('disp(''disp start maze ... with semi-colon terminator to initiate maze'')'); end
% We inititalized!
goalinit = true;
pokeinit = false;
set(0,'defaultAxesFontSize',12);
f=figure(1);clf;
sz=get(0,'screensize');
f.Position = [sz(1:2) sz(3)/2 sz(4)/2];
return
end
% If no message fed into this function return immediately
try if isempty(newline) || isequal(strtrim(newline), '~~~') || isempty(goalinit) || ~goalinit; return; end; catch; return; end
originalnewline = newline;
newline = originalnewline;
% Consume any alphanumeric characters, e.g. Error: or SCQT: that can
% proceed a time
[tok, ~] = strtok(newline,' ');
if any(isstrprop(tok(1:end),'alpha')), return; end
%keyboard;
%% ---------------- (B) PARSE Statescript/Commands ----------------
% Get current time & process
[currtime,newline] = scclock(newline);
if ~pokeinit; pokeinit=true; end
% ----------------------------------------------------------------------
% String commands or DIO state change?
state.maze.id = [];
state.maze.onoff = [];
zone = [];
din = []; dout = []; % Initialize din dout (just in case they're not later)
fdin = []; %infdin = [];
hasstring = any(isstrprop(newline(2:end),'alpha'));
if hasstring % Command/Message
%% --- PARSE USER COMMANDS and STATESCRIPT-2-MATLAB messages --------
[newline,rem] = strtok(newline);
% Goal maze commands (user inputs and things happen)
ParseCommands(newline,rem)
if state.gostate == false; fprintf('maze=off '); return; end % if the maze has been issued a pause command at the statescript console by the user, pause the maze execution
% Commands from statescript to the matlab brains
exitRequested = ParseStatescriptMessages(newline);
if exitRequested; return; end
else % DIO state change
%% ACQUIRE DIO
% -----------------------------------------------------------------------
if ~const.pokingoff % Zone only mode
[din,dout,newline] = portstate(newline);
if (sum(din) || sum(dout)) && ~isempty(const.app.debug)
fprintf('din=%s .. dout=%s\n',num2str(din),num2str(dout));
end
if const.app.stimdoutcheck && state.gostate
stimulusDoutCheck(dout);
end
if state.manual_homeir
din(maze.homeirbeam) = true;
end
% make list of dins on
fdin = find(din);
% make list of din inputs on
%infdin = fdin(ismember(fdin,maze.inputs));
state.platforms = platform(din, 'find');
state.input = fdin;
state.output = find(dout);
end
end
%if const.pokingoff ; din = []; end % if the nose poke input options is set to "off", we apply that effect here, by wiping digital input set
if const.app.stimdoutcheck && state.gostate
trialchecksum()
end
%% Post-string Exit Conditions
% -----------------------------------------------------------------------
guiUpdate();
if state.gostate == false; fprintf('maze=off '); return; end
if isempty(din) || const.resetzone_after_blocklockout && state.afterblocklockout; return; end % apprently, even is hasstring is false, sometimes else condition cannot find port info, so we need to exit if we
%% Continue on to processing location?
% Are we in a blockmode lockout?
if state.blocklockout
%pokeHappened_upstate=~isempty(state.maze.id) && state.maze.onoff==1;
%TODO Figure out why the above condition doesn't work
pokeHappened_upstate = any(state.platforms);
notHomewell = ~din(maze.home);
if pokeHappened_upstate && notHomewell
blocklockout(true);
recordperformance('errlockout');
if const.ploton
plotmaze(perf,maze,state);
else
calcPctCorr();
end
end
fprintf('blocklockout'); return;
end
% Is there a zone or an input?
if ~sum(din) && ~sum(zone); return; end
% Is there at least no string left if zone has been processed?
if ~isempty(newline) && ~isempty(zone); return; end % IF THERE IS A STRING LEFT AFTER READING THE PORTS (and zone strings have not been processed), exit ..
%assert(~isempty(state.sequence_queue),'Instruction set should not be empty as at this point');
%% ------------ (C) PROCESS INPUTS? ---------
% -----------------------------------------------------------------------
processInputs = (~hasstring || ~isempty(zone)); % When to check for state or zone changes!
if processInputs
debugmessage('\nProcessing: trialtype=(''%s %s'')', state.trialtype{:});
% Non well events?
potentialNonWellTriggers();
% Well events
if ~inLockout()
% ---- Debug Triggers --------
debugmessage('\nLockout: ')
debugmessage('...not in lockout...input detected...');
debugmessage('din = %s -- dout = %s, zone=%s .... ',num2str(din),num2str(dout),num2str(zone));
%assert(~isempty(state.sequence_queue));
% ---- Test Correct or Incorrect --------
if decideIfSkip()
else
if decideIfRight(), Correct();
elseif decideIfWrong(), Incorrect();
else, potentialTimeExtension(); %FIXME time extension can occur if decideIfRight runs while state.sequence_queue is nan or empty
end
end % decideIfSkip
else
potentialTimeExtension()
end % -- end lockout thread
% Ensure that statescript hasn't fucked up my stimuli, cutting them off
stimuluschecksum()
end % -- end location process
% --------------------------------------------------------------------
% -------------------- HELPER FUNCTIONS ------------------------------
% --------------------------------------------------------------------
function checksumRewardOff(reward,dur)
% checksum that ensures the reward function actually terminated the reward properly
% (you would think I wouldn't need this, but statescript fucks it up now and then)
global timerBufferCnt;
if isempty(timerBufferCnt); timerBufferCnt = 0; end
if nargin == 1; dur = const.reward.dur; end
executeString = sprintf('disp(''Timed reward end''); sendScQtControlMessage(''portout[%d]=0'');',maze.rewards(reward));
T = timer();
T.StartDelay = dur/1e3;
T.TimerFcn = executeString;
buffpos = mod(timerBufferCnt,5)+1;
try
state.timers(buffpos) = T;
catch E
for i = 1:buffpos-1
if numel(state.timers) < i || isempty(state.timers(i))
state.timers(i)=timer();
end
end
state.timers(buffpos) = T;
end
start(state.timers(buffpos));
pause(const.dur.statescriptExecTime);
timerBufferCnt = timerBufferCnt + 1;
end
function calcPctCorr()
for i = maze.platforms
incorrects = sum(perf.isinrow(-i,perf.record(:)));
corrects = sum(perf.isinrow(i,perf.record(:)));
perct = corrects/(corrects+incorrects);
perf.total(i) = corrects + incorrects;
if corrects || incorrects
perf.percent(i) = perct;
else
perf.percent(i) = 0;
end
end
end
% ----------------------------------------------------------------------
% Function : potentialNonWellTriggers
% Purpose : Any process that might require messages sentb based on
% non-well inputs is computed here.
% Input : MAZE GLOBALS
% ----------------------------------------------------------------------
function [] = potentialNonWellTriggers()
if strcmp(const.adapt.wm.controlimp,'matlab')
if din( maze.trialirbeam ) == false
sendScQtControlMessage('trigger(21)');
end
end
end
% ----------------------------------------------------------------------
% Function : stimulusDoutCheck
% Purpose : Checks for improper douts and if so removes them
% Input : dout - current dout as logical vec
% ----------------------------------------------------------------------
function stimulusDoutCheck(dout)
if sum(dout(maze.leds)) > numel(state.sequence_queue) % are there more douts than there ought to be?
improper = setdiff(find(dout),maze.leds(state.sequence_queue));
for i = improper
sendScQtControlMessage(sprintf('portout[%d]=0',i));
end
end
end
function ReinstateGui()
global gui
if ~isfield(gui,'guiHandle')
const.guiHandle = GoalmazeControlLayout();
gui.guiHandle = const.guiHandle;
else
const.guiHandle = gui.guiHandle;
end
end
% --------------------------------------------------------------------
% Name : guiUpdate
% Purpose : updates the graphical user interface with the latest values
% Input : MAZE GLOBALS
% --------------------------------------------------------------------
function guiUpdate()
gdata = guidata(const.guiHandle); %TODO see if could have a persistent pointer to this through const
updateAdaptions(gdata);
updateStatus(gdata);
updateHome(gdata);
function updateAdaptions(gdata)
if const.adapt.wm.flag
gdata.wmvalue.String = sprintf('%2.3f sec',state.adapt.wm/1e3);
end
end
function updateStatus(gdata)
if state.gostate
if state.blocklockout
%whitebg('red')
gdata.axes_status.Color = [255,99,71]/255;
else
gdata.axes_status.Color = 'green';
end
else
gdata.axes_status.Color = 'red';
end
end
function updateHome(gdata)
if const.home.on
if state.home.currNstim == 0
gdata.currNstim.String = sprintf('Home\ntrial');
else
switch mod(state.home.currNstim,10)
case {0,4,5,6,7,8,9}, str = 'th';
case 1, str = 'st';
case 2, str = 'nd';
case 3, str = 'rd';
end
gdata.currNstim.String = sprintf('%d%s\ntrial',state.home.currNstim,str);
end
end
end
end
% --------------------------------------------------------------------
% Name : portdown
% Purpose : runs or stores code that can be run on portdown events
% Input : list of ports, a function
% --------------------------------------------------------------------
function portdown(varargin)
persistant P, F
if isempty(varargin) % ACTION MODE
for i = 1:numel(P)
if din(P(i)), feval(F{i}); end
end
else
portlist = varargin{1};
funclist = varargin{2};
end
end
% --------------------------------------------------------------------
% Function : recordperformance
% Purpose : function that handles the general task of recording
% performance metrics. abstracts everything about perfor-
% mmance recording.
% Input : empty or can specify the type of recording event.
% TODO : Slowly reorganizing to have tables of relevant properties
% instead of confusing mixtures of struct variables. Someone
% else may have to deal with this code someday if anyone
% continues goal pursuit experiments.
% --------------------------------------------------------------------
function recordperformance(type)
function classification = label_region(platform)
switch platform
case maze.home, classification = 1;
otherwise, classification = 0;
end
end
function [wm, correct] = label_wm_and_correct(type)
switch type
case 'sequence_correct'
wm = true;
correct = true;
case 'sequence_incorrect'
wm = true;
correct = false;
case 'correct'
wm = false;
correct = true;
case 'incorrect'
wm = false;
correct = false;
otherwise;
wm = nan;
correct = nan;
end
end
udesc = perf.tc; % normal/home, wm/not_wm, end_seq/not_end_seq
N = seqN();
ishome = ismember(state.sequence,maze.home) ;
% Common operations
perf.precord(end+1:end+numel(state.platforms)) = maze.platforms(state.platforms);
perf.ptime(end+1:end+numel(state.platforms)) = currtime;
perf.pseq(end+1:end+numel(state.platforms)) = N;
% Common table operations
udesc.poke = maze.platforms(infdin);
udesc.time = currtime;
udesc.seq = N;
%udesc.region = label_region(udesc.poke);
[udesc.wm, udesc.correct] = label_wm_and_correct(udesc.poke);
switch type
case 'errlockout'
state.bcount = state.bcount + 1;
perf.brecord(end+1) = state.bcount;
perf.btime(end+1) = currtime;
perf.pseq(end) = nan;
case 'correct'
perf.arecord(end+1) = maze.home;
perf.atime(end+1) = currtime;
% Home well specific
%if const.home.error && ishome
if ishome
perf.hrecord(end+1) = true;
perf.htime(end+1) = currtime;
unifiedDescription(3) = 1;
end
if isequal(const.seq.mode, 'alternate')
state.sequence_queue = state.platforms;
ishome = false;
end
% Non Home Well Performance
perf.correct = perf.correct + 1;
perf.end.poke = perf.end.poke + 1;
if const.home.on == false || ~ishome
perf.record(end+1) = state.sequence_queue(1);
perf.time(end+1) = currtime;
perf.seq(end+1) = N;
state.rcount = state.rcount+1;
perf.crecord(end+1) = state.rcount;
perf.ctime(end+1) = currtime;
end
case 'incorrect'
perf.arecord(end+1) = -maze.home;
perf.atime(end+1) = currtime;
% Home well specific
%if const.home.error && ishome
if ishome
perf.hrecord(end+1) = false;
perf.htime(end+1) = currtime;
unifiedDescription(3) = 1;
end
% Non Home Well Performance
perf.incorrect = perf.incorrect + 1;
nIncorrect = numel(state.sequence)-N+1;
perf.end.poke = perf.end.poke + 1;
if const.home.on == false || ~ishome
perf.record(end+1:end+nIncorrect) = -state.sequence_queue;
perf.time(end+1:end+nIncorrect) = currtime;
perf.seq(end+1:end+nIncorrect) = N;
perf.pseq(end+1) = N;
state.rcount = 0;
perf.crecord(end+1) = state.rcount;
perf.ctime(end+1) = currtime;
end
case 'sequence_correct'
if isequal(const.seq.mode,'normal') || isequal(const.seq.mode,'differentiate')
perf.stime(end+1) = currtime;
perf.srecord(end+1) = true;
elseif isequal(const.seq.mode,'crossoff')
perf.wtime(end+1) = currtime;
perf.wrecord(end+1) = true;
unifiedDescription(4:5) = 1;
end
case 'sequence_incorrect'
if isequal(const.seq.mode,'normal') || isequal(const.seq.mode,'differentiate')
perf.stime(end+1) = currtime;
perf.srecord(end+1) = false;
elseif isequal(const.seq.mode,'crossoff')
perf.wtime(end+1) = currtime;
perf.wrecord(end+1) = false;
unifiedDescription(4:5) = 1;
end
case 'cend'
perf.incorrect = perf.incorrect + 1;
if ~ishome
perf.record(end+1:end+numel(state.sequence_queue)) = -state.sequence_queue;
perf.time(end+1:end+numel(state.sequence_queue)) = currtime;
perf.seq(end+1:end+numel(state.sequence_queue)) = seqN();
end
perf.end.abort = perf.end.abort + 1;
otherwise, error('Unrecognized input');
end
end
% --------------------------------------------------------------------
% Function : debugstop
% Purpose : decides whether to stop based on debug flags
% Input : the type of flag you'd like to stop on here
% --------------------------------------------------------------------
function debugstop(type)
stopnow = false;
if ~isempty(const.app.debug) && contains(const.app.debug,type)
stopnow = true;
end
if stopnow; evalin('caller','keyboard'); end
end
% --------------------------------------------------------------------
% Function : debugmessage
% Purpose : prints message if in debug mode
% Input : inputs that would provide to fprintf
% --------------------------------------------------------------------
function debugmessage(varargin)
varargin{1} = ['\n>>>> DEBUG: ', varargin{1}];
if ~isempty(const.app.debug);cprintf('red',varargin{:});end
end
% ------------ Home Well Func ---------------
% --------------------------------------------------------------------
% Function : selectTrialType()
% Purpose : Decides what type of trial should be used when home
% well is turned on.
% Input : Nothing, it reads state,maze,const,and perf
% --------------------------------------------------------------------
function out = selectTrialType(type)
if nargin == 0; type=[]; end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% HOME / NORMAL TRIAL SELECTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Buffer Trial Type - for right now, the buffer is only for home/normal
bufferpos = mod(state.home.currNstim,const.home.everyNstim)+1;
state.trialbuffer{bufferpos} = state.trialtype{1};
state.bufferpos = mod(state.bufferpos,const.home.everyNstim)+1;
% If request trial type, then perform this switch
if ~isempty(type)
if ischar(type)
switch strtrim(type)
case 'home'
out{1} = sethome();
case 'normal'
out{1} = setnormal();
end
elseif iscell(type)
out = type;
end
else
% If not, use the logical rule to switch trial
if const.home.on
lastmistake_on_homewell = perf.arecord(end) == -5;
if const.home.error && lastmistake_on_homewell
out{1} = sethome();
else
% Decide what to do based on rules
switch const.home.when
case 'intertrial'
switch state.trialtype{1}
case 'home', out{1} = 'normal';
case 'normal', out{1} = 'home';
assert(~isnan(const.home.everyNstim));
if state.home.currNstim >= const.home.everyNstim
out{1} = 'home';
else
out{1} = 'normal';
end
end
case 'error'
if perf.time(end) == currtime && perf.record(end)<0
previous_poke_error = true;
else
previous_poke_error = false;
end
if previous_poke_error
switch state.trialtype{1}
case 'home', out{1} = 'normal';
case 'normal', out{1} = 'home';
end
end
case 'alternate'
%counter_exeeded = state.home.currNstim >= const.home.everyNstim;
end
end
else
out{1} = 'normal';
end
end
if isempty(type) || (iscell(type) && ~isempty(type{2}))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Trial selection for special modes: Home normal have already been picked
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
out{2} = state.trialtype{2};
if isequal(const.seq.mode,'cuememory') && isequal(out{1},'home')
switch out{2}
case {'inbound','cue'}
% Switch mode
out{2} = 'memory';
state.trialtype{2}='memory';
case {'outbound','memory'}
% Switch mode
out{2} = 'cue';
state.trialtype{2}='cue';
end
end
end
switch out{1}
case 'home', sethome();
case 'normal', setnormal();
end
debugmessage('Out_newtrialtype = (%s, %s)\n', out{:});
end
% >>> --------------------------------------------------------------------
% >>> Function : selectTrialType::sethome()
% >>> Purpose : handles all operations associated with setting a home
% >>> trial type.
% >>> Input : Nothing, it reads state,maze,const,and perf
% >>> --------------------------------------------------------------------
function out = sethome()
out = 'home';
state.trialtype{1} = out;
state.allowedcorrect = 0;
% Update state
state.home.currNstim = 0;
sendScQtControlMessage('hometrial=1');
pause(2*const.dur.statescriptExecTime);
end
% >>> --------------------------------------------------------------------
% >>> Function : selectTrialType::setnormal()
% >>> Purpose : handles all operations associated with setting a normal
% >>> trial type.
% >>> Input : Nothing, it reads state,maze,const,and perf
% >>> --------------------------------------------------------------------
function out = setnormal()
out = 'normal';
state.trialtype{1} = out;
state.allowedcorrect = const.train.allowedcorrect;
% Update state
state.home.currNstim = state.home.currNstim + 1;
sendScQtControlMessage('hometrial=0');
pause(2*const.dur.statescriptExecTime);
end
% ------------ Reset Functions ---------------
function executeReset()
% Function resets all flags related to preventing execution of the maze code.
% --- Matlab side ---
blocklockout(false);
currentstimoff('all');
% --- Statescript side ---
sendScQtControlMessage('clear queue')
sendScQtControlMessage('trialstack=0')
sendScQtControlMessage('flashstack=0')
sendScQtControlMessage('block_stack=0')
sendScQtControlMessage('pokeend_stack=0')
end
% ------------ Correct/Incorrect/Skip Decisions ------
% --------------------------------------------------------------------
% Name : potentialTimeExtension
% Purpose : decides whether to send statescript messages about time
% extension et cetera based on DIO patterns.
% Input : MAZE GLOBALS
% --------------------------------------------------------------------
function potentialTimeExtension()
if const.adapt.wm.flag
if any(din(maze.home)) || any( ~din(maze.homeirbeam) )
sendScQtControlMessage('trigger(20)');
end
end
end
% --------------------------------------------------------------------
% Name : inLockout
% Purpose : decides whether we should be in lockout at the moment.
% Input : MAZE GLOBALS
% --------------------------------------------------------------------
function inlockout = inLockout()
% --- Are we in lockout? ---
debugmessage('Lockout decision...');
% (1) Poke timing related lockout condition
pokelockout = false; % Assume we are not
lastPokeCorrect = perf.record(end) > 0 && perf.ptime(end) == perf.time(end); % first condition: last true, second condition:last record time is last poke time (if blocklockout happened this should falsify)
inputExists = sum(din) || sum(zone) ; % DIN REP - 08/14/17 Left off here
anyIsDifferent = any(abs(perf.precord(end)) ~= state.platforms || isnan(perf.precord(end));
firstRun = state.bcount == 0 && state.rcount == 0 && ~( perf.seq(end) > 1 );
if lastPokeCorrect % LAST=CORRECT
debugstop('lockout');
% (A)
lastPokeCorrect_insideLockout = ((currtime-perf.time(end))/1e3)<const.correctpokelockout;
if lastPokeCorrect_insideLockout % if the last poke is for sure correct, and the time difference between now and last is less than a special lockout for correct pokes ...
debugmessage('inside correct lockout window...');
pokelockout=true;
end
% (B)
if inputExists && anyIsDifferent || firstRun % but, if the previous "correct" sequence_queue is equal to the current poke sequence_queue, assume false again
debugmessage('but alternated...');
pokelockout = false;
end
else % LAST=NOT CORRECT
% (A)
lastPokeIncorrect_insideLockout = (currtime/1e3-perf.time(end)/1e3)<const.incorrectpokelockout;
if lastPokeIncorrect_insideLockout % if the difference between now and the last poke is within the lockout, we are
debugmessage('inside incorrect lockout window...');
pokelockout = true;
end
% (B)
if inputExists && anyIsDifferent || firstRun %... % but, if the previous "correct" sequence_queue is equal to the current poke sequence_queue, assume false again
debugmessage('but alternated...');
pokelockout = false;
end
end
inlockout = state.zonedeltaflag || ~inputExists; % second condition: whether
inlockout = inlockout || pokelockout;
end
% --------------------------------------------------------------------
% Function : decideIfSkip
% Purpose : Contains all logic pertaining to decision whether to skip
% processing an input when lockout conditions are not reached.
% Input : Nothing, it reads state,maze,const,and perf
% --------------------------------------------------------------------
function decision = decideIfSkip()
if state.inRestart == true
decision = true;
fprintf('\ntrapped in restart\n');
elseif ~isempty(state.platforms)
decision = true;
warndlg('All inputs on');
else
switch const.seq.mode
case 'alternate'
decision = all(ismember(state.input, maze.irlist));
otherwise
switch state.trialtype{1}
case 'home'
homezone = any(din(maze.home));
% Validate putatitve homepoke?
homepokevalidated = true;
if strcmp(const.home.ensuremethod,'irbeam') && ~any(ismember(state.input,maze.homeirbeam))
homepokevalidated = false;
end
if homezone && homepokevalidated
decision = false;
else
if const.home.error && ~homepokevalidated && ~homezone
decision = false;
else
decision = true;
end
end
otherwise
decision = false;
end
end
end % Guard against non-local well bug
end
% --------------------------------------------------------------------
% Name : decideIfRight
% Purpose : literally what is says. This function decides whether or not
% a poke is correct given the current state of the maze.
% Input : nada, it has access to the global structures about the maze
% % --------------------------------------------------------------------
function decision = decideIfRight()
rules = const.seq.mode;
switch rules
case {'normal','crosson','crossoff','differentiate','cuememory','wtrack'}
if any(isnan(state.sequence_queue))
%keyboard;
%warndlg('instruction is nan in decideIfRight()');
decision = false;
return;
% save('nanerror','const','state','perf','maze');
end
noNans_within_instruction = ~any(isnan(state.sequence_queue));
anyPokes_match_instruction = any(ismember(state.platforms,state.sequence_queue(1)));
anyZones_match_instruction = any(ismember(zone,maze.zones(state.sequence_queue(1))));
decision = noNans_within_instruction && (anyPokes_match_instruction || anyZones_match_instruction);
case 'alternate'
prevPokes = perf.record(~isnan(perf.record)); % indexes all correct non-nan pokes
fprintf('\nNUMELPOKES=%d\n', numel(prevPokes))
if ~isempty(prevPokes) && prevPokes(end)>0
nonIRInput = setdiff(state.input,maze.irlist);
decision = all(~ismember(nonIRInput,prevPokes(end))); % check if poke not in list of
else
decision = true; % if there are not enough pokes to establish a sequence, it's correct
end
otherwise
error('Unhandled mode %s', rules);
end
end
% --------------------------------------------------------------------
% Name : decideIfWrong
% Purpose : handles the decision whether a poke is wrong, given the
% current state of the maze
% Input : nada, it has access to the global structures about the maze
% --------------------------------------------------------------------
function decision = decideIfWrong()
rules = const.seq.mode;
switch rules
case {'normal','crosson','crossoff','differentiate','cuememory','wtrack'}
anyInputs_from_Wells = any(ismember(state.platforms,maze.normal));
decision = anyInputs_from_Wells;
case 'alternate'
prevPokes = perf.record(~isnan(perf.record)); % indexes all correct non-nan pokes
fprintf('\nNUMELPOKES=%d\n', numel(prevPokes))
if ~isempty(prevPokes) && prevPokes(end)>0
nonIRInput = setdiff(state.input,maze.irlist);
decision = ~all(~ismember(nonIRInput,prevPokes(end))); % check if poke not in list of
else
decision = false; % if there are not enough pokes to establish a sequence, it's correct
end
otherwise, error('Unhandled mode %s', rules);
end
end
% ----------- Code Sections
% --------------------------------------------------------------------
% Name : SetParameters
% Purpose :
% Input : nada, it has access to the global structures about the maze
% --------------------------------------------------------------------
function SetParameters()
evalin('caller','global state perf maze const');
rng(sum(clock()));
const.app.profiler = false; % Whether to turn the profiler on in some sections
const.app.debug = [];
const.app.commit = gitcommittag();
const.app.ploton = false;
% ---- Stimuli in sequence ----
const.sequence = 1; % how many goals to have per sequence
const.seqdelay = 0.75; % seconds
const.pokedelay = 0.1; % how many seconds to pause after a poke has been rendered before showing the next stimuli
const.seq.mode = 'differentiate'; % ['normal'|'crosson'|'crossoff'|'simultaneous'|'differentiate'|'alternate' ] , normal mode simply shows the stimuli in sequence upon presentation time, whether that be right after a well, or after a zone-crossing. 'crosson' shows the 1st stimulus at the normal time (which is usually a stimulus continually on), and the second stimulus is shown when the animal crosses out of its current zone. Simulateneous shows stimuli simultaneously but with the same presentation function. Differentiate is a version of simultaneous that uses a different presentation for each stimulus in the sequence.
modeswitch(const.seq.mode);
% ------ Trackers --------
const.ploton = true; % turns plotting on!
if isempty(state)
state.input = nan;
state.sequence_queue = nan(1,const.sequence);
state.sequence = nan(1,const.sequence);
%state.lastpoke = -inf; % previous poke time
%state.lastpokeport = nan; % previous poke port
state.storedrestart = true; % works in conjunction with const.nextstim_zonecross mode
state.gostate = false; % 1 -- maze is executing -- 2 maze is paused (this is controlled by user in statescript in case something happens and the user wants to pause the maze, they can issue a command to this callback script to stop executing temporarily, without destroying its record of ongoing behavior.
state.profilenum = 0;
state.zonedeltaflag = false; %whether waiting for zone delta
state.blocklockout = false;
state.maze.id = nan;
state.maze.onoff = nan;
state.nextstim.portdown = false;
state.pokeend.state = nan; % Tracks the previous END time of a set of poke events -- everytime poking begins, this is set to nan, because the the current time is unknown. When the time become known via a statescript event, it sets to currtime.
state.pokeend.savestate = nan; % Tracks whether an action for poke end is saved. (Not implemented)
state.bcount = 0; % counter for number of consecutive blocklockout runs
state.rcount = 0;
state.seq.shown = 0;
state.afterblocklockout = false;
state.trialtype = {'normal','cue'}; % cell array that holds strings that describe the current trial mode - the first is always homewell versus normal well.
state.trialbuffer = {}; % holds the last N trials, where N is everyNstim (const.home.everyNstim)
state.bufferpos = 0;
state.inRestart = false; % Tracks whether there exists an instance of this callback that is calculating a resartStimulus()
state.timers = timer(); % Storage container for timers that are allowed to run
state.preload_instruction = [];
state.manual_homeir = false; % controls from GUI to manually evoke the home IR beam if the animal is correctly standing but not triggering the beam.
end
% ---- DIO Parameters ----
maze.platforms = 1 : 5; % <-- Labels to denote each platform in the code
maze.leds = [17 23 19 20 21]; % <-- dout of leds
maze.inputs = [1:4, 6]; % <-- din of poke inputs
maze.rewards = [5 7 8 9 10]; % <-- dout of reward wells
maze.zones = 1 : 5; % <-- numbers used to denote each of the zones sent from statescript
maze.home = 5; % <-- numbers used to denote each of the zones sent from statescript
maze.homein = maze.inputs(maze.home);
maze.normal = setdiff(maze.zones,maze.home);
maze.homeirbeam = 13;
maze.trialirbeam = 12; % 14 is dead on the current board
maze.irlist = [maze.homeirbeam, maze.trialirbeam]; % lists out the event-related ir beams
% -------------- HOME WELL -------------------------------------------
const.home.ensuremethod = 'irbeam'; % {zone|irbeam}
const.home.on = true; % Whether or not to activate the homezone
const.home.everyNstim = 1; % How often does the animal have to visit the zone?
const.home.when = 'intertrial'; % intertrial | error
const.home.errorstop = false; % Whether to lockout for erroneous pokes
const.home.multiplier = 0.5; % multiplier for the amount of reward
state.home.currNstim = 0;
const.home.error = false;
const.home.aftererr = true;
const.home.errcnt = 1;
% ---- Durations ----
const.app.stimdoutcheck = true;
const.dur.start = 1; %seconds - startup delay
% (Rest of the durations programmed into the statescript component)
% ---- Statescript consts ---
const.dur.statescriptExecTime = 1e-3; %ms -- used to ensure statescript can see and react to a variable change, before it's undone, via pause()
% if const.sequence>1
% assert(const.incorrectpokelockout>=const.sequence*const.seqdelay);
% assert(const.incorrectpokelockout>=const.pokedelay);
% end
% ---- Specific Modes and Persistent Information -----
const.pokingoff = false; % mode that turns the poke sensing off (to ensure relying purely on zone)
const.zoningoff = true; % mode that turns the zone sensing off that lets animal get milk from zone entrance
% Block lockouts
const.blocklockout_period = 10; % Lockout the maze after incorrects (everything turns off) ... 0 toggles the mode off
const.train.allowedincorrect = 0; % number of incorrects before stimulus restarted
state.allowedincorrect = const.train.allowedincorrect;
const.train.allowedcorrect = 0; % number of corrects before stimulus restarted
state.allowedcorrect = const.train.allowedcorrect;
%state.allowedcorrect = const.train.allowedincorrect;
const.correctpokelockout = 10 ; % seconds to lockout the same well after each poke has been rendered (incorrect)
const.incorrectpokelockout = 10 ; % seconds to lockout the same well after each poke has been rendered (incorrect)
const.train.flash_cuememory = true;
% ------ Adaptive stimulus presentation length -------
% Documentation: This controls adaption of stimulus presentation time.
% The brains of the operation are right now controlled in statescript.
% The light off times are set there and written onto the statescript
% event stack. For home well maze types, this should be written to
% be controlled only at the home well poke and home well positional ir beam
const.adapt.wm = [];
const.adapt.wm.flag = true;
const.adapt.wm.stepdown = 3.5 * 1e-2; %percent to shorten when correct
const.adapt.wm.stepup = 3.5 * 1e-2; %percent to lengthen when incorrect
const.adapt.wm.min = 0.0 * 1e3; % floor of cue time, in statescript time
const.adapt.wm.max = 1 * 1e3; % ceil of cue time, in statescript time,
const.adapt.wm.init = 0.5 * 1e3; % initial value in seconds of cue time. in statescript time
const.adapt.wm.control = 'irbeam'; % none | irbeam , whether or not to use a dedicated ir beam to control the process
const.adapt.wm.controlimp = 'statescript'; % implementation: matlab | statescript
if const.adapt.wm.flag
state.adapt.wm=const.adapt.wm.init;
end
function cycs = cycles(port_sequence, n)
pats = numel(port_sequence) + 1;
cycs = zeros(n,pats*2-1);
for i = 1:pats % forward port_sequence
temp = circshift(port_sequence, i-1);
cycs(:,i) = temp(1:n);
end
for i = 1:pats-1 % backward port_sequence
temp = circshift(port_sequence(end:-1:1), i);
cycs(:,pats+i) = temp(1:n);
end
end
function reps = repeats(ports, n)
reps = repmat(ports', [1 n]);
end
% Maze features
const.info.patterns = {repeats(maze.normal,3), cycles(maze.normal, 2)}; % patterns to break
const.info.distances = mazedistances('2x2');
% Training modes
const.train.distalonly = false;
const.train.weightdistance = false;
const.train.patternbreak = false; % (UNWRITTEN TODO ) mode that attempts to break any cyclic path assumptions of a given animal
const.train.discourage_short_of_pair_first = true; % decides to make the longest distance stimuli of the pair picked first.
const.train.bias_short_pick = false; % increases bias to short distance
const.train.weightperf = false;
const.train.flashremind = false; % toggles whether stimuli reminded via flash
const.train.excprev = false;
% Adaptive blocklockout length
% The way this works, if turned on, is that every subsequent
% blocklockout it is stepped up by the appropriate percentage
% (longer lockout time), and if the animal finally gets something
% correct, it immediately returns the stored value to the minimum.
% We want the animal not to whilly nilly alternate wells. This
% disinventives it.
const.adapt.blocklockout = []; % (NOT FULLY IMPLEMENTconst.blocklockout.restartlastED TODO )
const.adapt.blocklockout.flag = false;
const.adapt.blocklockout.stepup = 60 * 1e-2; %percent to shorten when correct
const.adapt.blocklockout.min = 2 * 1e3; % floor of cue time, in statescript time
const.adapt.blocklockout.max = 20 * 1e3; % ceil of cue time, in statescript time,
if const.adapt.blocklockout.flag
state.adapt.blocklockout=const.adapt.blocklockout.init;
end