-
Notifications
You must be signed in to change notification settings - Fork 0
/
CITest.m
2021 lines (1668 loc) · 86.7 KB
/
CITest.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 = CITest(varargin)
% CITest M-file for CITest.fig
% Last Modified by GUIDE v2.5 03-Nov-2009 15:35:19
%
% Graphical user interface for controlling BEDCS-based psychophysical experiments. A number
% of "experiment types" are currently supported (e.g. threshold, psych. tuning curves), as well as
% various "runtime modes" for running these experiments (e.g. 2-interval forced choice, Bekesy-style.
% tracking), Additional experiment types and runtime modes can be added by creating new GUIs
% in a standard format recognized by CITEST. See "CITest Documentation.docx" for more details.
%
% VERSION HISTORY
% CITest_v01.22 started on January 20, 2015 by SMB. Previous version was 1.21.
% 2015.03.02. Moved code that positions the run-time GUI to the main m-file. Added 'bedcsParam' as
% an argument to the initial run-time GUI call, as this structure contains the BEDCS parameters
% needed to set the reference (fixed) stimulus for the "Two-step Adjust" mode.
% 2015.03.10. Similarly, moved the portion of code that makes sure start level does not exceed max
% level from the run GUIS to the main GUI (for both primary channel w/ 'mainParam' and secondary
% channel w/ 'expParam').
% 2015.04.08. Changed default sigma of primary channel to 0.9 (for pTP and sQP configurations).
% 2015.09.25. Added probe channel to file name, for PTC experiments.
% 3015.05.06. Adjust 'tbase' to larger values if the pulse rate is low enough. This is to avoid an
% excessive number of zeros in the BEDCS pulse table, which crashes the program. (Sad times.)
%
% --- Begin initialization code - DO NOT EDIT --- %
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @CITest_OpeningFcn, ...
'gui_OutputFcn', @CITest_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 CITest is made visible --- %
% Note that variables in ALLCAPS are lab-specific settings managed by CITEST_USERSETTINGS.
function CITest_OpeningFcn(hObject, eventdata, handles, varargin)
% Lab-specific settings and general run-time options %
COMPUTERID = 'BiererLaptop'; %#ok<*NASGU> %% <---- Set this line every time a new CITest version is uploaded %%
VERSION = 1.240;
CITest_UserSettings; % facilitating script to set user-specific run-time variables
% Check and adjust expected directory for BEDCS files %
if ~exist(handles.bedcsinfo.dir,'dir')
maindir = mfilename('fullpath'); maindir = fileparts(maindir);
newdir = fullfile(maindir,handles.bedcsinfo.dir);
handles.bedcsinfo.dir = newdir;
end;
if ~exist(handles.bedcsinfo.dir,'dir') % last chance
error('BEDCS directory was not found. Check the ''bedcsinfo.dir'' setting in CITEST_USERSETTINGS.M.');
end;
% Set some button action, display, and storage defaults %
xypos = get(hObject,'Position'); xypos(1:2) = handles.xydefault.main;
set(hObject,'Position',xypos);
namestr = sprintf('Cochlear Implant Testing - Control Panel (CITest v%.3f)',VERSION);
set(hObject,'Name',namestr);
% starting config codes for each config mode
configStruct = struct('type','sQP','pTP',0.9,'sQP',0.9,'BP',0);
set(handles.config_popup,'UserData',configStruct);
set([handles.start_pushbutton handles.pause_pushbutton],'Enable','Off');
set(handles.expblank_uipanel,'Visible','Off');
set(handles.seebedcs_togglebutton,'ForegroundColor',[.10 .15 .00],'Value',0);
set(handles.seeresults_togglebutton,'ForegroundColor',[0 .5 0],'Value',1);
% Initialize storage structures for electrodes and channels %
achan = 8; % GUI default is for just one channel ..
atype = 'sQP'; asigma = 0.9; % .. in the steered quadrupolar configuration ..
aalpha = 1.0; % .. and with no current steering (GUI figure at opening should reflect this)
chanProfile.onoff = false(1,16); % stores the Channel Selector subGUI field values, one entry per electrode
chanProfile.onoff(achan) = true;
chanProfile.threshold = nan(1,16);
chanProfile.mcl = nan(1,16);
chanProfile.impedance = nan(1,16);
chanProfile.compliance = 333*ones(1,16);
chanProfile.alphatype = 1; % Channel Sel. alpha-related field values; '.alphatype' "1" is "Single Value"
chanProfile.alphaval = aalpha; % '.alphaval' can be a single value or step size for a range of values
chanProfile.alphavector = aalpha; % (dep. on '.alphatype')
chanProfile.stimspec = []; % '.stimspec' keeps track of the stimulus parameters that MCL/THR apply to
chanProfile.stimspec.configtype = atype;
chanProfile.stimspec.configval = asigma;
chanProfile.stimspec.phdur = 99; % these values are just place holders
chanProfile.stimspec.traindur = 999;
chanProfile.stimspec.pulserate = 1000;
handles.chanProfile = chanProfile; % updated with changes to active elec, config, or alpha (main gui) or Ch. Selector
handles.chanProfileHistory = []; % this will track past channel settings as long as CITest is open
% Set up a few other run-time and storage variables % % pulse parameters mostly overwritten in _SetupStim()
pulseSettings = struct('type','','tbase',12*44/49,'tbase_init',12*44/49,'phframes',9,'plsframes',18,'ipframes',75,'numpulses',200,...
'phdur',100,'pulserate',1000,'traindur',200,'dataUI',[],'expgui','');
handles.mainParam = struct('exptype','','expabbr','','expgui','','subtype','','runmode','Manual Level',...
'runabbr','MNL','electrode',8,'configtype',atype,'configcode',asigma,'pulseSettings',pulseSettings,...
'chanSettings',[],'level',[],'minlevel',[],'maxlevel',[]);
handles.expParam = [];
handles.expHandles.uipanel = [];
handles.expHandles.expgui = [];
handles.runnumber = 1;
handles.runHistory = {};
if DEMOMODE
set(handles.demomode_menu,'Checked','On');
else set(handles.demomode_menu,'Checked','Off');
end;
handles.demomode = DEMOMODE;
handles.version = VERSION;
handles.output = hObject;
guidata(hObject, handles);
% Set up external GUIs % % facilitating function to open Results View window and hide it
hresults = CITest_ResultsViewSetup(hObject);
setappdata(hObject,'hrun',[]); % these will store the handle and m-file name of the extenal run-time GUI
setappdata(hObject,'rungui','');
setappdata(hObject,'hactx',[]); % and the BEDCS application
setappdata(hObject,'hresults',hresults);% and the Results View figure; ## no longer keeping the 'hview' axes handle
setappdata(hObject,'runactive',false);
setappdata(hObject,'hchselect',[]); % and the Channel Selector GUI
assignin('base','hctrl',hObject); % this is handy to have in the workspace, for debugging purposes
% Set up keyboard shortcuts when main GUI or Results Window is in scope (run-time subGUI will be set later) %
try
handlset = [hObject hresults];
set(handlset,'WindowKeyPressFcn',{@CITest_KeyPress,hObject});
catch % earlier MATLAB versions don't recognize "Window" key functions
handlset = [hObject hresults handles.start_pushbutton handles.pause_pushbutton];
set(handlset,'KeyPressFcn',{@CITest_KeyPress,hObject});
end;
% Final set up, including pulse phase and rate as well as the file name %
CITest_SetupStimParam(handles.exp_popup,[],handles); % GUIDATA() is called once more
% --- Outputs from this function are returned to the command line.
function varargout = CITest_OutputFcn(hObject, eventdata, handles)
varargout{1} = handles.output;
%%%% CALLBACK FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% --- Make one last parameter check, launch the runtime GUI, and run the selected experiment --- %
function start_pushbutton_Callback(hObject, eventdata, handles)
% Determine the most up-to-date stimulus parameters %
% Note that upon return of Channel Selector, some parameters are updated (via "UserData" property).
expgui = handles.expHandles.expgui;
configStruct = get(handles.config_popup,'UserData');
configtype = configStruct.type;
configval = get(handles.config1_edittext,'UserData');
% store necessary info from various UI fields
chanSettings.electrode = get(handles.activeelec_popup,'UserData'); % (some entries will be overwritten below)
chanSettings.configtype = configtype; % redundancy later with 'mainParam', but that's OK
chanSettings.configval = configval(1);% reiterating that this is a scalar and not a vector
chanSettings.alpha = get(handles.config2_edittext,'UserData');
chanSettings.configcode = []; % set below
chanSettings.impedance = handles.chanProfile.impedance;
chanSettings.compliance = handles.chanProfile.compliance;
chanSettings.threshold = handles.chanProfile.threshold;
chanSettings.mcl = handles.chanProfile.mcl;
% also redundancy with respect to pulse settings
chanSettings.phdur = handles.mainParam.pulseSettings.phdur;
chanSettings.traindur = handles.mainParam.pulseSettings.traindur;
chanSettings.pulserate = handles.mainParam.pulseSettings.pulserate;
chanSettings.ratestr = get(handles.rate_text,'String');
% make sure the threshold and mcl values from Channel Selector are valid
mlmatch = CITest_ChannelSelector('CITest_ChannelSelector_Match',chanSettings,handles.chanProfile);
if ~mlmatch % if config., phase duration, etc don't match, reset threshold and mcl values
chanSettings.threshold = nan(1,16); % (the 'chanProfile' entries are NOT updated, however)
chanSettings.mcl = nan(1,16);
end;
% Interpret configuration and electrode settings to define every channel for the experiment %
if strcmp(configtype,'pTP') || strcmp(configtype,'sQP')
electrode = repmat(chanSettings.electrode,length(chanSettings.alpha),1);
electrode = electrode(:)'; % 'electrode' consists of one row; each entry is a different "channel"
ccrow2 = repmat(chanSettings.alpha,1,length(chanSettings.electrode)); % (elec./alpha combo)
ccrow1 = chanSettings.configval * ones(1,length(ccrow2));
configcode = [ccrow1 ; ccrow2]; % 'configcode' consists of two rows: sigma and alpha ..
else % 'BP' %
electrode = chanSettings.electrode;
ccrow1 = configval * ones(1,length(electrode));
ccrow2 = nan(1,length(electrode));
configcode = [ccrow1 ; ccrow2]; % .. or for BP, channel separation (minus 1 by convention) and undefined
end;
chanSettings.electrode = electrode; % update for _Exp_GetParameters(), below
chanSettings.configcode = configcode;
pulseSettings = handles.mainParam.pulseSettings;
% Determine experiment-specific parameters %
[levelParam, expParam] = feval(expgui,'CITest_Exp_GetParameters',handles,chanSettings,pulseSettings);
if isempty(levelParam) && ischar(expParam)
warnstr = sprintf('One or more level settings are invalid. %s',expParam);
hw = warndlg(warnstr,'Bad Level Setup','modal');
uiwait(hw);
return;
elseif isempty(expParam) || ischar(expParam)
warnstr = sprintf('One or more experiment settings are invalid. %s',expParam);
hw = warndlg(warnstr,'Bad Experiment Setup','modal');
uiwait(hw); % string 'expParam' acts as an error description
return;
elseif length(levelParam.chanidx) < length(electrode);
hw = warndlg('This experiment and run-time mode can support only a subset of defined channels.',...
'Using Fewer Channels','modal');
uiwait(hw); % for some exp/subexps, only one or a subset of defined channels will be used
electrode = electrode(levelParam.chanidx);
configcode = configcode(:,levelParam.chanidx);
end;
% Store level and other parameters in 'mainParam' %
mainParam = handles.mainParam; % start with already stored parameters (includes '.pulseSettings', 'runmode', etc)
mainParam.electrode = electrode;
mainParam.configtype = configtype;
mainParam.configcode = configcode;
mainParam.level = levelParam.value;
mainParam.minlevel = levelParam.minlimit;
mainParam.maxlevel = levelParam.maxlimit;
% for record keeping, also store the "raw" channel settings
chanSettings.electrode = get(handles.activeelec_popup,'UserData');
mainParam.chanSettings = chanSettings; % revert to original electrode vector for this purpose
mainParam.chanSettings.configStruct = configStruct; % add a few other bits as well
mainParam.chanSettings.levelstr = levelParam.valuestring;
mainParam.chanSettings.maxstr = levelParam.maxstring;
% Do a final validity check for electrical current levels %
highind = mainParam.level > mainParam.maxlevel;
if any(highind) % force primary standard (start) level to be no greater than max level
hw = warndlg('One or more levels are too high. Resetting to maximum.','Resetting high values','modal');
uiwait(hw);
if length(mainParam.maxlevel)>1, mainParam.level(highind) = mainParam.maxlevel(highind);
else mainParam.level(highind) = mainParam.maxlevel;
end;
end;
lvlProfile = struct('mcl',chanSettings.mcl,'compliance',chanSettings.compliance);
invalid_levels = CITest_EvaluateLevels(mainParam,lvlProfile,handles.deviceProfile.currentlimit);
if any(invalid_levels) % don't proceed if any channel level is invalid
disp('Run cannot be started because a current limit would be exceeded for the primary channel.');
return;
end;
if isfield(expParam,'electrode2') % if a secondary channel defined (e.g. for masking), certain parameters
highind = expParam.level > expParam.maxlevel; % MUST have been defined in '_Exp_GetParameters'
if any(highind) % force (start) level to be no greater than max level
hw = warndlg('One or more secondary levels are too high. Resetting to maximum.',...
'Resetting high values','modal');
uiwait(hw);
if length(expParam.maxlevel)>1, expParam.level(highind) = expParam.maxlevel(highind);
else expParam.level(highind) = expParam.maxlevel;
end;
end;
chanSettings2 = expParam.chanSettings;
lvlProfile2 = struct('mcl',chanSettings2.mcl,'compliance',chanSettings2.compliance);
chgdur2 = expParam.chgdur; % use phase duration for secondary channel (usually same as for primary)
invalid_levels = CITest_EvaluateLevels(expParam,lvlProfile2,handles.deviceProfile.currentlimit,chgdur2);
if any(invalid_levels) % as with primary channel, don't proceed if any level is invalid
disp('Run cannot be started because a current limit would be exceeded for the secondary channel.');
return;
end;
end; % if ~isempty(expParam) %
if strcmpi(mainParam.configtype,'pTP') || strcmpi(mainParam.configtype,'sQP')
sigmaval = mainParam.configcode(1,1); % make sure configuration parameters are valid, mainly for safety
alphaval = mainParam.configcode(2,:);
if sigmaval<0 || sigmaval>1 || length(unique(mainParam.configcode(1,:)))~=1
disp('Invalid sigma value for the primary channel.');
return; % at present, sigma can't be a variable for primary channel
end;
if any(alphaval<0) || any(alphaval>1)
disp('Invalid alpha value for the primary channel.');
return;
end;
end;
if isfield(expParam,'configtype') && (strcmpi(expParam.configtype,'pTP') || strcmpi(expParam.configtype,'sQP'))
sigmaval = expParam.configcode(1,:);
alphaval = expParam.configcode(2,:);
if any(sigmaval<0) || any(sigmaval>1)
disp('Invalid sigma value for the primary channel.');
return; % sigma CAN be a variable for secondary channel
end;
if any(alphaval<0) || any(alphaval>1)
disp('Invalid alpha value for the primary channel.');
return;
end;
end;
% Transform the stimulus parameters into BEDCS variables and other commands for the subject-interaction GUI %
[bedcsParam, ctrlParam] = feval(expgui,'CITest_Exp_TransformParameters',mainParam,expParam,handles.deviceProfile);
handles.mainParam = mainParam; % store the stimulus and control parameters
handles.expParam = expParam;
handles.bedcsParam = bedcsParam;
handles.ctrlParam = ctrlParam;
guidata(hObject,handles); % give a stamp of approval to all that storage
loadpath = fullfile(handles.bedcsinfo.dir,bedcsParam.bedcsexp);
if isempty(bedcsParam.bedcsexp)
hw = warndlg('The chosen experiment type is not defined.','Experiment Type Undefined','modal');
uiwait(hw); % don't continue if experiment type/subtype is undefined or otherwise invalid
return;
elseif ~exist(loadpath,'file')
warnstr = sprintf('The BEDCS definition file [%s] cannot be found.',bedcsParam.bedcsexp);
hw = warndlg(warnstr,'Experiment Type Undefined','modal');
uiwait(hw); % don't continue if experiment type/subtype is undefined or otherwise invalid
return;
elseif isempty(ctrlParam)
hw = warndlg('There was an error executing the chosen run-time mode.','Run-time Mode Undefined','modal');
uiwait(hw); % same with the run-time mode
return
end;
% Load the BEDCS experiment file and initialize its parameters %
hactx = CITest_OpenApplication(handles); % this also stores 'hactx' via SETAPPDATA()
if ~hactx.Online && ~handles.demomode
fprintf(1,'Initializing BEDCS.');
hactx.Online = 1;
waittime = 0;
while ~hactx.IsCIIReady && waittime<=20
fprintf(1,'.'); % give the program time to come on-line when it's first opened
pause(0.25);
waittime = waittime + .25;
end;
fprintf(1,'\n');
if waittime > 20 % after 20 seconds, give a warning and terminate execution
hw = warndlg('ERROR: BEDCS connection to the implant failed.','BEDCS Error','modal');
uiwait(hw);
hactx.Online = 0;
return;
end;
hactx.ULevelMode = 1;
hactx.Visible = 0;
elseif handles.demomode
hactx.Online = 0;
elseif hactx.Visible == 1 % if BEDCS already open, warn user that it's still open
disp('BEDCS is open, which may cause stability issues.');
end; % if ~hactx && ~demomode
hactx.LoadExpFile(loadpath); % load the BEDCS experiment file and set some common parameters
hactx.DataPath = handles.fileinfo.directory;
tbase_bedcs = hactx.Get_ControlVarVal('tbase');
if char(tbase_bedcs), tbase_bedcs = str2num(tbase_bedcs); end;
tbase = mainParam.pulseSettings.tbase; % check for expected 'tbase' value in BEDCS file (to nearest .01)
if round(tbase_bedcs*100) ~= round(tbase*100)
% hw = warndlg('The BEDCS time base parameter does not match the CITEST setting.','Experiment Error','modal');
% uiwait(hw);
% return;
hactx.Let_ControlVarVal('tbase',mainParam.pulseSettings.tbase);
disp('The time base parameter has been adjusted to accommodate a change in pulse train rate.');
end;
parNames = fieldnames(bedcsParam); % the first field in 'bedcsPars' is always the BEDCS file name
parNames = parNames(2:end);
for i = 1:length(parNames) % set the initial BEDCS stimulus parameters
hactx.Let_ControlVarVal(parNames{i},bedcsParam.(parNames{i}));
end;
pause(0.2);
% Launch the subject interaction / run-time GUI %
[hrun,postype] = feval(ctrlParam.rungui,ctrlParam,bedcsParam,handles.citest,handles.demomode);
if isempty(hrun) % ##2015.09: changed to 'isempty()' because R2014+ represents handles as structures
hw = warndlg('One or more run-time GUI parameters is invalid.','Experiment Error','modal');
uiwait(hw);
return;
end;
xynew = handles.xydefault.(postype);
runpos = get(hrun,'Position'); % re-position run-time GUI depending on desired user (controller or subject)
runpos(1:2) = xynew; % (putting code out here overcomes issue with displaying on a second monitor)
set(hrun,'Position',runpos,'Name','');
setappdata(handles.citest,'hrun',hrun);
setappdata(handles.citest,'rungui',ctrlParam.rungui);
stimInfo.mainParam = mainParam; % push stimulus and other variables to the base workspace
stimInfo.expParam = expParam; % (for debugging and other purposes)
stimInfo.ctrlParam = ctrlParam;
stimInfo.bedcsParam = bedcsParam;
assignin('base','stimInfo',stimInfo);
assignin('base','hrun',hrun);
CITest_UpdateFileName(handles,1); % update displayed file name one last time before the run starts
handles = guidata(hObject);
try % set up "P" and "R" keyboard shortcuts for the run-time GUI
kpstatus = get(hrun,'WindowKeyPressFcn'); % (if keyboard shortcuts are already defined in the run-time GUI,
if isempty(kpstatus) % don't do anything)
set(hrun,'WindowKeyPressFcn',{@CITest_KeyPress,handles.citest});
end;
catch
kpstatus = get(hrun,'KeyPressFcn');
if isempty(kpstatus) % include all uicontrols with an empty keypress property
handlset = get(hrun,'Children')';
handlset = [hrun handlset( ismember(handlset,findobj(hrun,'type','uicontrol','keypressfcn','')) )];
set(handlset,'KeyPressFcn',{@CITest_KeyPress,handles.citest});
end;
end;
hresults = getappdata(handles.citest,'hresults');
hanalysis = findobj(hresults,'tag','pushbutton_analysis');
% Start the experiment with the run-time GUI %
set(hObject,'Enable','Off'); % disable the START button and other ui elements
set([handles.demomode_menu handles.seebedcs_togglebutton],'Enable','Off');
set(hanalysis,'Enable','Inactive');
set(handles.pause_pushbutton,'Enable','On'); % enable the PAUSE button
if ishandle(hrun) && ~getappdata(hrun,'ready')
uiwait(hrun); % if subGUI has a READY button, wait for that
end;
setappdata(handles.citest,'runactive',true); % start stimulation and wait for the run-time GUI to end
runOutput = feval(ctrlParam.rungui,'CITest_RunGUI_Start',hrun);
% Close the run-time GUI %
setappdata(handles.citest,'runactive',false);
fprintf('\n');
if isfield(runOutput,'message')
disp(runOutput.message);
else disp('Closing run-time GUI.');
end;
if ishandle(hrun) % the runtime GUI should normally still be open (no reason to [X] close)
delete(hrun); % if so, close it now
end;
% Analyze and optionally display the results of the experiment %
try
[runResults,runSummary] = feval(expgui,'CITest_Exp_ProcessResults',mainParam,ctrlParam,runOutput);
catch % unformatted 'runOutput' --> formatted 'runResults'
runResults = runOutput;
runSummary = []; % be wary of errors, as each exp's _ProcessResults will be changed often
fprintf(1,'\nThe standard analysis routine encountered an error.\n');
end;
filestr = fullfile(handles.fileinfo.directory,handles.fileinfo.filename);
subjstr = get(handles.subject_edittext,'String');
sesnstr = get(handles.session_edittext,'String');
runInfo = struct('subject',subjstr,'session',str2num(sesnstr),'run',handles.runnumber,...
'experiment',mainParam.exptype,'mode',mainParam.runmode,'date',datestr(now,'yyyy/mm/dd HH:MM'), ...
'user',handles.userid,'software',[],'savedfile',filestr);
runInfo.mfiles = struct('maingui',mfilename('fullpath'),'version',handles.version,'expgui',handles.expHandles.expgui,...
'rungui',ctrlParam.rungui,'custom',handles.customanalysis);
% perform additional custom analysis, if specified in CITEST_USERSETTINGS
[runResults.custom,menuEntry] = CITest_ProcessExtra(runResults,runInfo,stimInfo,handles.customanalysis);
% push the results to the workspace ('stimInfo' already there)
assignin('base','runInfo',runInfo); assignin('base','runResults',runResults);
% Set up additional analysis options and perform post-run clean-up %
if ~isempty(runSummary) % set up Results View summary panel, to be refreshed on tab change
setappdata(hresults,'newSummary',runSummary);
setappdata(hresults,'refresh',true);
else
setappdata(hresults,'refresh',false);
end;
% add analysis routines to context menu on Results View "Analysis" button
hanalysis = findobj(hresults,'tag','pushbutton_analysis');
hmenu = get(hanalysis,'UIContextMenu');
delete(hmenu); % clear current menu set and add the default routine
hmenu = uicontextmenu('Parent',hresults);
uimenu(hmenu,'Label','CITest default','Callback',{@resultsview_default_Callback,expgui,mainParam,ctrlParam,runOutput});
if ~isempty(menuEntry) % add custom routines to analysis menu, if defined in _ProcessExtra() above
for i = 1:length(menuEntry)
if ischar(menuEntry{i})
uimenu(hmenu,'Label',menuEntry{i},'Callback',menuEntry{i});
end;
end;
end;
set(hanalysis,'UIContextMenu',hmenu);
set(hObject,'Enable','On'); % re-enable or re-disable various buttons and features
set([hanalysis handles.demomode_menu handles.seebedcs_togglebutton],'Enable','On');
set(handles.pause_pushbutton,'Enable','Off');
% Save the results of the experiment %
if ~handles.fileinfo.autosave || ~runResults.complete
qsave = questdlg('Save results and experiment info to file?','Request to save','Yes');
if strcmp(qsave,'No')
fprintf(1,'Results not saved to file (but they are available in the workspace).\n\n');
runInfo.savedfile = '';
assignin('base','runInfo',runInfo);
return; % if results aren't saved, run number will not be incremented
end;
end;
if exist(filestr,'file')
savingfile = uiputfile('.mat','File name conflict. Choose a new file name.',filestr);
if savingfile % deal with naming conflicts (unlikely given inclusion of a time stamp!)
filestr = fullfile(handles.fileinfo.directory,savingfile);
runInfo.savedfile = filestr;
assignin('base','runInfo',runInfo);
end;
end;
if ~exist(handles.fileinfo.directory,'dir')
mkdir(handles.fileinfo.directory);
fprintf(1,'Session directory {%s} created.\n',handles.fileinfo.directory);
end;
saveCell = {'runInfo','stimInfo','runResults'};
save(filestr,saveCell{:}); % save results to a uniquely named m-file
fprintf(1,'Results saved to file %s.\n\n',filestr);
% store the basic information about this run, then increment run number
handles.runHistory(handles.runnumber) = {runInfo};
handles.runnumber = handles.runnumber + 1;
set(handles.run_edittext,'String',sprintf('%02.0f',handles.runnumber),'UserData',handles.runnumber);
CITest_UpdateFileName(handles,0); % update file name with new run number for NEXT time; stores 'handles'
% #### end of start_pushbutton_Callback ###############################################################
% --- Handle calls to the Experiment Type popup menu --- %
function exp_popup_Callback(hObject, eventdata, handles)
expchoice = get(hObject,'Value');
if expchoice~=get(hObject,'UserData') % avoid double callback execution in older MATLAB versions
CITest_SetupStimParam(hObject,[],handles);
set(hObject,'UserData',expchoice);
end;
% --- Handle calls to the Experiment Subtype popup menu --- %
% Update the subtype of experiment and initialize the Pulse Settings subGUI tool.
% As of CITest version 1.24, I am only allowing the standard pulse train choice in this menu.
% The main reason is that the time base is reset, and its potential incompatibility with
% pulse rate is not currently checked.
function subexp_popup_Callback(hObject, eventdata, handles)
subtypeStr = get(hObject,'String'); subtypeval = get(hObject,'Value');
if subtypeval ~= 1 % ##2016.06.01: for the time being, only one option is available in the subexp menu
hw = warndlg('Non-standard "sub-experiment" pulse types are not currently supported.','Subexp Non-functioning','modal');
set(hObject,'Value',1);
uiwait(hw);
return;
end;
if strcmp(subtypeStr{subtypeval},handles.mainParam.subtype);
return; % avoid double callback execution in older MATLAB versions
end;
handles.mainParam.subtype = subtypeStr{subtypeval};
expgui = handles.mainParam.expgui;
% determine new pulse type for this experiment subtype
[pulsetype,dataUI,tbase] = feval(expgui,'CITest_Exp_GetPulseInfo',handles,handles.mainParam.subtype);
pulseInput.type = pulsetype; pulseInput.dataUI = dataUI; pulseInput.tbase = tbase;
[pulseUI,tbase] = CITest_PulseOptions([],pulseInput); % initialize UI settings, without opening subGUI
bgcol = get(handles.setuppulse_pushbutton,'BackgroundColor');
set(handles.setuppulse_pushbutton,'BackgroundColor',[.827 .71 .82]);
handles.mainParam.pulseSettings.type = pulsetype;
handles.mainParam.pulseSettings.tbase = tbase;
handles.mainParam.pulseSettings.dataUI = pulseUI;
phdur_text_Callback(handles.phdur_text,[],handles); % this will update new info in 'handles' via GUIDATA
pause(0.1); % just a color flash to signal an update in pulse info
set(handles.setuppulse_pushbutton,'BackgroundColor',bgcol);
% --- Handle calls to the PAUSE pushbutton --- %
% A button press will automatically suspend the run-time GUI until the controller
% makes a choice to continue or stop.
function pause_pushbutton_Callback(hObject, eventdata, handles)
hrun = getappdata(handles.citest,'hrun');
rungui = getappdata(handles.citest,'rungui');
if ~ishandle(hrun) % this shouldn't happen... but just in case
set(hObject,'Enable','Off');
return;
end;
origcolor = get(hObject,'BackgroundColor');
set(hObject,'BackgroundColor','r');
evalstr = cat(2,rungui,'(''CITest_RunGUI_Pause'',hrun);');
eval(evalstr); % evoke utility function in the subGUI's m-file to suspend action
qbutton = questdlg('Continue or stop run?','EXPERIMENT PAUSED','CONTINUE','STOP','CONTINUE');
switch qbutton
case 'STOP'
evalstr = cat(2,rungui,'(''CITest_RunGUI_Stop'',hrun);');
eval(evalstr);
otherwise
evalstr = cat(2,rungui,'(''CITest_RunGUI_Unpause'',hrun);');
eval(evalstr);
end;
set(hObject,'BackgroundColor',origcolor);
% --- Make the Results Window visible during experiment runs --- %
function seeresults_togglebutton_Callback(hObject, eventdata, handles)
hrun = getappdata(handles.citest,'hrun');
if ~ishandle(hrun) % first make sure whether there is an active runtime GUI
setappdata(handles.citest,'runactive',false);
end;
hresults = getappdata(handles.citest,'hresults');
toggleval = get(hObject,'Value');
if toggleval % toggling on allows the Results Window to open during a run
setappdata(hresults,'display',true);
set(hObject,'ForegroundColor',[0 .5 0]);
% reveal figure (even if run is not active, provides access to run history)
set(hresults,'Visible','On');
rhandles = guihandles(hresults); % force current results to be the displayed tab
set(rhandles.buttongroup_tab,'SelectedObject',rhandles.radiobutton_current);
evtData.NewValue = rhandles.radiobutton_current;
CITest_ResultsViewTabs(rhandles.radiobutton_current,evtData);
if getappdata(handles.citest,'runactive')
figure(hrun); % make sure runtime GUI window still has scope (for key presses, etc)
end;
else % toggling off closes the Results Window and prevents it from opening during a run
setappdata(hresults,'display',false);
set(hObject,'ForegroundColor',[.10 .15 .00]);
set(hresults,'Visible','Off');
if getappdata(handles.citest,'runactive')
figure(hrun);
end;
end;
% --- Make the BEDCS application visible --- %
% Starting with BEDCS version 1.18, if the BEDCS application is made visible, it will automatically
% be put OUT of "U-level" mode (i.e. it will assume U Levels have alread been set) in a way that
% can't be altered in MATLAB.
function seebedcs_togglebutton_Callback(hObject, eventdata, handles)
hactx = CITest_OpenApplication(handles);
toggleval = get(hObject,'Value');
if toggleval
if ~handles.demomode
hw = warndlg('WARNING: Making BEDCS visible may crash the experiment.','BEDCS Visible Error','modal');
uiwait(hw);
end;
hactx.Visible = 1; hactx.ULevelMode = 0;
set(hObject,'ForegroundColor',[0 .5 0]);
else
hactx.Visible = 0; hactx.ULevelMode = 1;
set(hObject,'ForegroundColor',[.10 .15 .00]);
end;
% --- Update the top-level directory for saving data --- %
function setdir_pushbutton_Callback(hObject, eventdata, handles)
newdir = uigetdir(handles.fileinfo.directories.top,'Set Top-level Directory');
if ~newdir, return; end;
handles.fileinfo.directories.top = newdir;
[handles.fileinfo.directory, handles.fileinfo.filename] = CITest_CreateFileName(handles,0);
guidata(hObject,handles);
set(handles.filename_text,'String',sprintf('%s\\\n%s',handles.fileinfo.directory,handles.fileinfo.filename));
% --- Handle calls to the subject text field --- %
function subject_edittext_Callback(hObject, eventdata, handles)
inputstr = get(hObject,'String');
if isempty(inputstr)
valid = false;
else
idx = regexpi(inputstr, '\w'); % look for all alphabetic, numeric, and underscore characters
valid = ~isempty(idx);
end;
if ~valid % if invalid string, revert to last valid one ..
newstr = get(hObject,'UserData');
set(hObject,'ForegroundColor','r'); pause(0.2);
set(hObject,'ForegroundColor','k');
else
newstr = inputstr; % .. otherwise accept it
newstr = newstr(idx); % disregard invalid characters
end;
if ~isempty(handles.chanProfileHistory) && ~strcmp(newstr,get(hObject,'UserData'))
query = questdlg('Changing subject ID will reset the channel profile history. Continue anyway?',...
'New Subject or Session','Yes');
if strcmp(query,'Yes')
handles.chanProfileHistory = [];
else
newstr = get(hObject,'UserData');
end;
end;
dirstr0 = handles.fileinfo.directories.top;
dirstrE = '';
if isfield(handles.fileinfo.directories,handles.mainParam.exptype)
dirstrE = handles.fileinfo.directories.(handles.mainParam.exptype);
end;
dirsbj = fullfile(dirstr0,dirstrE,newstr,'');
if ~exist(dirsbj,'dir')
msgbox('A new directory will be created for this subject.','New Subject','modal');
end;
if handles.runnumber ~= 1
msgbox('If run # should be 1, it must be manually reset.','Reset Run #','modal');
end;
% update the displayed and stored strings
set(hObject,'String',newstr,'UserData',newstr);
CITest_UpdateFileName(handles,0); % update handles and the GUI text field that displays the file name
% --- Handle calls to the session and run text fields --- %
function sessrun_edittext_Callback(hObject, eventdata, handles)
inputval = sscanf(get(hObject,'String'),'%d');
if isempty(inputval), valid = false;
else valid = inputval>=1 && inputval<=999;
end;
if ~valid % if invalid string, revert to last valid one ..
newval = get(hObject,'UserData');
set(hObject,'ForegroundColor','r'); pause(0.2);
set(hObject,'ForegroundColor','k');
else
newval = inputval; % .. otherwise accept it
end;
if hObject==handles.session_edittext && ~isempty(handles.chanProfileHistory) && newval~=get(hObject,'UserData')
query = questdlg('Changing session # will reset the channel profile history. Continue anyway?',...
'New Subject or Session','Yes');
if strcmp(query,'Yes')
handles.chanProfileHistory = [];
else
newval = get(hObject,'UserData');
end;
elseif hObject==handles.run_edittext
query = questdlg('Run numbers may become duplicated or out of sequence. Continue anyway?',...
'Change Run Number','Yes');
if strcmp(query,'Yes')
handles.runnumber = newval;
else
newval = get(hObject,'UserData');
end;
end;
if hObject==handles.session_edittext && handles.runnumber ~= 1
msgbox('If run # should be 1, it must be manually reset.','Reset Run #','modal');
end;
% format the displayed string, and update the stored code
set(hObject,'String',sprintf('%02.0f',newval),'UserData',newval);
CITest_UpdateFileName(handles,0); % update handles and the GUI text field that displays the file name
% --- Open the Channel Selection subGUI, for specifiying multiple channels and/or steered channels --- %
function setupactive_pushbutton_Callback(hObject, eventdata, handles)
configStruct = get(handles.config_popup,'UserData');
% store necessary info from various UI fields
chanSettings.electrode = get(handles.activeelec_popup,'UserData'); % (not identical to start button's 'chanSettings')
chanSettings.configtype = configStruct.type;
chanSettings.configval = get(handles.config1_edittext,'UserData');
chanSettings.alpha = get(handles.config2_edittext,'UserData');
chanSettings.phdur = handles.mainParam.pulseSettings.phdur;
chanSettings.traindur = handles.mainParam.pulseSettings.traindur;
chanSettings.pulserate = handles.mainParam.pulseSettings.pulserate;
chanSettings.ratestr = get(handles.rate_text,'String');
% create new Ch. Selector subGUI only if one is not already open
hchselect = getappdata(handles.citest,'hchselect');
if isempty(hchselect) || ~ishandle(hchselect)
hchselect = CITest_ChannelSelector([],'Channel Selector - Primary Channel');
setappdata(handles.citest,'hchselect',hchselect);
end;
try % seed subGUI with last saved channel profile, and wait until it returns
chanProfile = CITest_ChannelSelector('CITest_ChannelSelector_UpdateFields',hchselect,handles.chanProfile,chanSettings);
catch
chanProfile = [];
end;
if isempty(chanProfile) % if empty, Channel Selector was cancelled or (*GASP*) an error occurred
disp('Primary channel settings were not saved.');
if ishandle(hchselect), set(hchselect,'Visible','Off'); end;
return;
end;
if ~strcmpi(chanSettings.configtype,'sQP')
chanProfile.alphatype = 1; % ignore changes to alpha for pTP and BP modes
chanProfile.alphaval = 0.5;
chanProfile.alphavector = 0.5;
end;
% update the active electrode and alpha UI fields (changeable in Ch. Selector)
set(handles.config2_edittext,'String','','UserData',chanProfile.alphavector);
config2_edittext_Callback(handles.config2_edittext,[],handles);
chanvec_old = get(handles.activeelec_popup,'UserData');
chanvec_new = find(chanProfile.onoff);
set(handles.activeelec_popup,'Value',17,'UserData',chanvec_new); % (value = 17 is a safe default)
checkchan = length(chanvec_new)==length(chanvec_old) && all(chanvec_new==chanvec_old);
if checkchan % don't reset start/max levels if channel set didn't change
activeelec_popup_Callback(handles.activeelec_popup,0,handles);
else
activeelec_popup_Callback(handles.activeelec_popup,[],handles);
end;
handles.chanProfile = chanProfile; % overwrite the old profile and backup the new one (for current CITest session)
nProfile = length(handles.chanProfileHistory);
if ~nProfile
handles.chanProfileHistory = chanProfile;
else
handles.chanProfileHistory(nProfile+1) = chanProfile;
end;
guidata(hObject,handles);
% --- Handle calls to the configuration UI components --- %
% Currently, only the partial tripolar and steered quadrupolar configurations are supported.
% Note that the parameter "alpha" is interpreted differently for these two configurations.
% For pTP, alpha = 0.5 is centered on the active electrode; for sQP, alpha = 1.0 is centered.
function config_popup_Callback(hObject, eventdata, handles)
configStr = get(hObject,'String'); % for convenience, store config mode in 'UserData'
configtype = configStr{get(hObject,'Value')};
configStruct = get(hObject,'UserData');
configStruct.type = configtype; % configuration type is changed with no validity checks
set(hObject,'UserData',configStruct);
alphaok = false;
switch configtype % update text label for config code
case 'pTP'
configlabel = 'Sigma'; % current steering is not supported with pTP (alpha = 0.5)
case 'sQP'
configlabel = 'Sigma'; % current steering is supported via the Channel Select menu
alphaok = true;
case 'BP'
configlabel = 'BP sep.';
end;
if alphaok % revert to default alpha values
set(handles.config2_edittext,'String','1.00','UserData',1.0,'Enable','On');
else
set(handles.config2_edittext,'String','0.50','UserData',0.5,'Enable','Off');
end;
% force call to CONFIG1_EDITTEXT to set last valid code
set(handles.configlabel1_text,'String',configlabel); % this also checks active electrode and updates file name
config1_edittext_Callback(handles.config1_edittext, 1, handles);
% The spatial selectivity parameter, either sigma for pTP and sQP or electrode separation for BP %
function config1_edittext_Callback(hObject, eventdata, handles)
configval = get(hObject,'String'); % the unprocessed string appearing in the text field
configStruct = get(handles.config_popup,'UserData');
configtype = configStruct.type;
aelec = get(handles.activeelec_popup,'UserData');
aelec_min = min(aelec); % 'aelec' can be a vector, if multiple electrodes are defined (in Channel Selector)
aelec_max = max(aelec);
switch configtype % process the 'configval' string according to the configuration mode 'configtype'
case 'pTP'
configval = sscanf(configval,'%f');
if isempty(configval), valid = false;
else valid = configval>=0 && configval<=1;
end;
if configval > 0 % make sure flanking electrodes are available
valid = valid && (aelec_min-1)>=1 && (aelec_max+1)<=16;
end;
strformat = '%.2f';
case 'sQP'
configval = sscanf(configval,'%f');
if isempty(configval), valid = false;
else valid = configval>=0 && configval<=1;
end;
if configval > 0 % one extra apical electrode compared to 'pTP' above
valid = valid && (aelec_min-2)>=1 && (aelec_max+1)<=16;
end;
strformat = '%.2f';
case 'BP' % electrode separation is one more than written integer value
configval = sscanf(configval,'%d'); % (e.g. val = 1 for "BP+1" which is a separation of TWO electrodes)
if isempty(configval), valid = false;
else valid = configval>=0 && configval<=6 && (aelec_min-configval-1)>=1;
end; % make sure most apical return electrode is available
strformat = '%+2d';
end;
if ~isempty(eventdata) || ~valid % if callback was evoked by CONFIG_POPUP (see above) OR current value is invalid
configval = configStruct.(configtype); % use instead the last valid value for the current configuration mode
set(hObject,'ForegroundColor','r'); pause(0.2);
set(hObject,'ForegroundColor','k');
end;
% format the displayed string, and update the stored code
set(hObject,'String',sprintf(strformat,configval),'UserData',configval);
% also update the stored value for the "last valid code"
configStruct.(configtype) = configval;
set(handles.config_popup,'UserData',configStruct);
% check active electrode, and if invalid set it to a safe electrode (8)
activeelec_popup_Callback(handles.activeelec_popup,8,handles) % also reset file name and current lvl, with GUIDATA() call
% The steering parameter, alpha %
function config2_edittext_Callback(hObject, eventdata, handles)
inputstr = get(hObject,'String');
newval = sscanf(inputstr,'%f');
if isempty(newval)
valid = false;
else
valid = newval >= 0 && newval <= 1;
end;
if ~valid % if new value is invalid, use instead the last valid value
newval = get(hObject,'UserData');
set(hObject,'ForegroundColor','r'); pause(0.2);
set(hObject,'ForegroundColor','k');
end;
if length(newval) > 1 % vector values of 'alpha' are indicated by the display text "range"
newstr = 'range';
tipstr = sprintf('%d values from %.1f to %.1f',length(newval),newval(1),newval(end));
else
newstr = sprintf('%.2f',newval);
tipstr = '';
end;
set(hObject,'String',newstr,'UserData',newval,'TooltipString',tipstr);
% --- Handle changes to the active electrode popup menu --- %
% Currently, this assumes there are exactly 16 electrode channels.
function activeelec_popup_Callback(hObject, eventdata, handles)
oldelec = get(hObject,'UserData'); % this could be a scalar or vector (the latter if set in Channel Selector)
newval = get(hObject,'Value');
if newval <= 16 % for most cases, the menu entry order is identical to the channel number
newelec = newval;
else % if user chooses the menu entry "multiple", resort to the stored electrode value
newelec = oldelec;
end;
aelec_min = min(newelec); % 'aelec' can be a vector, if multiple electrodes are defined (in Channel Selector)
aelec_max = max(newelec);
configStruct = get(handles.config_popup,'UserData');
configtype = configStruct.type;
configval = get(handles.config1_edittext,'UserData');
switch configtype % acceptable electrode channel depends on configuration
case 'pTP'
if configval>0