252 :3) + me.colour2(1:3))/2;
253 end
254 me.isInSetColour = false;
255 end
256
257 % ===================================================================
258 %> @brief
alpha set method
259 %>
260 % ===================================================================
261 function set.alpha(me, value)
262 if me.isSetup; warning('You should set alphaOut to affect drawing...'); end
263 if value<0; value=0;elseif value>1; value=1; end
264 me.alpha = value;
265 if ~me.isInSetColour
266 me.colour = me.colour(1:3); %force
colour to be regenerated
267 if ~isempty(findprop(me,'colour2')) && ~isempty(me.colour2)
268 me.colour2 = [me.colour2(1:3) me.
alpha];
269 end
270 if ~isempty(findprop(me,'baseColour')) && ~isempty(me.baseColour)
271 me.baseColour = [me.baseColour(1:3) me.
alpha];
272 end
273 end
274 end
275
276 % ===================================================================
277 %> @brief
delta Get method
278 %>
delta is the normalised number of pixels per frame to move a stimulus
279 % ===================================================================
280 function value = get.
delta(me)
282 end
283
284 % ===================================================================
285 %> @brief
dX Get method
286 %> X position increment for a given
delta and
angle
287 % ===================================================================
288 function value = get.
dX(me)
289 value = 0;
290 if ~isempty(findprop(me,'directionOut'))
292 elseif ~isempty(findprop(me,'angleOut'))
294 end
295 end
296
297 % ===================================================================
298 %> @brief
dY Get method
299 %> Y position increment for a given
delta and
angle
300 % ===================================================================
301 function value = get.
dY(me)
302 value = 0;
303 if ~isempty(findprop(me,'directionOut'))
305 elseif ~isempty(findprop(me,'angleOut'))
307 end
308 end
309
310 % ===================================================================
312 %>
313 % ===================================================================
316 end
317
318 % ===================================================================
320 %>
321 % ===================================================================
324 end
325
326 % ===================================================================
328 %>
329 % ===================================================================
332 end
333
334 % ===================================================================
336 %>
337 % ===================================================================
340 end
341
342 % ===================================================================
343 %> @brief
reset the various
tick counters for our stimulus
344 %>
345 % ===================================================================
347 global mouseTick %#ok<*GVMIS> %shared across all stimuli
348 if max(me.delayTime) > 0 %delay display a number of frames
349 if length(me.delayTime) == 1
350 me.delayTicks = round(me.delayTime/me.screenVals.ifi);
351 elseif length(me.delayTime) == 2
352 time = randi([me.delayTime(1)*1000 me.delayTime(2)*1000])/1000;
353 me.delayTicks = round(time/me.screenVals.ifi);
354 end
355 else
356 me.delayTicks = 0;
357 end
358 if min(me.offTime) < Inf %delay display a number of frames
359 if length(me.offTime) == 1
360 me.offTicks = round(me.offTime/me.screenVals.ifi);
361 elseif length(me.offTime) == 2
362 time = randi([me.offTime(1)*1000 me.offTime(2)*1000])/1000;
363 me.offTicks = round(time/me.screenVals.ifi);
364 end
365 else
366 me.offTicks = Inf;
367 end
368 mouseTick = 0;
369 me.tick = 0;
370 me.drawTick = 0;
371 end
372
373 % ===================================================================
374 %> @brief get mouse position
375 %> we make sure
this is only called once per animation
tick to
376 %> improve performance and ensure all stimuli that are following
377 %> mouse position have consistent X and Y per frame
update
379 %> PTB screen (useful for mouse override positioning for stimuli)
380 % ===================================================================
382 global mouseTick mouseGlobalX mouseGlobalY
mouseValid
383 if me.tick > mouseTick
384 if ~isempty(me.sM) && isa(me.sM,'screenManager') && me.sM.isOpen
385 [me.mouseX, me.mouseY] = GetMouse(me.sM.win);
386 else
387 [me.mouseX, me.mouseY] = GetMouse;
388 end
389 if me.mouseX > -1 && me.mouseY > -1
390 me.mouseValid = true;
391 else
392 me.mouseValid = false;
393 end
394 mouseTick = me.tick; %set global so no other
object with same
tick number can call
this again
396 mouseGlobalX = me.mouseX; mouseGlobalY = me.mouseY;
397 else
398 if ~isempty(mouseGlobalX) && ~isempty(mouseGlobalY)
399 me.mouseX = mouseGlobalX; me.mouseY = mouseGlobalY;
401 end
402 end
403 end
404
405 % ===================================================================
407 %> @brief Run stimulus in a window to preview it
408 %>
409 %> @param benchmark true|false [optional, default = false]
410 %> @param runtime time to
show stimulus [optional,
default = 2]
412 %> @param forceScreen for a particulr monitor/screen to use
413 %> @param showVBL
show a plot of the VBL times
414 % ===================================================================
415 function
run(me, benchmark, runtime, s, forceScreen, showVBL)
416 arguments(Input)
417 me %self
418 benchmark logical = false
419 runtime double = 2
420 s = []
421 forceScreen double = -1
422 showVBL logical = false
423 end
424
425 if isempty(benchmark); benchmark = false; end
426 if isempty(runtime); runtime = 2; end
427 if isempty(forceScreen); forceScreen = -1; end
428 if isempty(showVBL); showVBL = false; end
429
430 try
431
432 if ~isa(s,'screenManager')
435 s.blend = true;
436 s.disableSyncTests = true;
437 s.visualDebug = true;
438 s.bitDepth = '8bit';
439 end
440
441 oldscreen = s.screen;
442 oldbitdepth = s.bitDepth;
443 oldwindowed = s.windowed;
444 if forceScreen >= 0
445 s.screen = forceScreen;
446 if forceScreen == 0
447 s.bitDepth = '8bit';
448 end
449 end
450 prepareScreen(s);
451
452 if benchmark
453 s.windowed = false;
454 elseif forceScreen > -1
455 if ~isempty(s.windowed) && (length(s.windowed) == 2 || length(s.windowed) == 4)
456 % use existing setting
457 else
459 end
460 end
461
462 if me.
verbose; s.debug = true; end
463
464 if ~s.isOpen; open(s);end
467
468 Priority(MaxPriority(s.win)); %bump our priority to maximum allowed
469
471 if benchmark
472 drawText(s, 'BENCHMARK: screen won''t update properly, see FPS in command window at end.');
473 else
474 drawGrid(s); %
draw degree dot grid
475 drawScreenCenter(s);
476 drawText(s, ['Preview ALL with grid = ±1°; static for 1 seconds, then animate for ' num2str(runtime) ' seconds...'])
477 end
478 if ismethod(me,'resetLog'); resetLog(me); end
479 flip(s);
480 if ~any(strcmpi(me.family,{
'movie',
'revcor'}));
update(me); end
481 if benchmark
482 WaitSecs('YieldSecs',0.25);
483 else
484 WaitSecs('YieldSecs',2);
485 end
486 if runtime < sv.ifi; runtime = sv.ifi; end
487 nFrames = 0;
488 notFinished = true;
489 benchmarkFrames = floor(sv.fps * runtime);
490 vbl = zeros(benchmarkFrames+1,1);
491 startT = GetSecs; lastvbl = startT;
492 while notFinished
493 nFrames = nFrames + 1;
495 if ~benchmark && s.debug; drawGrid(s); end
496 finishDrawing(s); %tell PTB/GPU to
draw
498 if benchmark
499 Screen('Flip',s.win,0,2,2);
500 notFinished = nFrames < benchmarkFrames;
501 else
502 vbl(nFrames) = flip(s, lastvbl + sv.halfifi); %flip the buffer
503 lastvbl = vbl(nFrames);
504 % the calculation needs to take into account the
505 % first and last frame times, so we subtract ifi*2
506 notFinished = lastvbl < ( vbl(1) + ( runtime - (sv.ifi * 2) ) );
507 end
508 end
509 endT = flip(s);
510 if ~benchmark;startT = vbl(1);end
511 diffT = endT - startT;
512 WaitSecs(0.5);
513 vbl = vbl(1:nFrames);
514 if showVBL && ~benchmark
515 figure;
516 plot(diff(vbl)*1e3,'k*');
517 line([0 length(vbl)-1],[sv.ifi*1e3 sv.ifi*1e3],'Color',[0 0 0]);
518 title(sprintf('VBL Times, should be ~%.4f ms',sv.ifi*1e3));
519 ylabel('Time (ms)')
520 xlabel('Frame #')
521 end
522 Priority(0); ShowCursor; ListenChar(0);
523 reset(me); %
reset our stimulus ready
for use again
524 close(s); %close screen
525 s.screen = oldscreen;
526 s.windowed = oldwindowed;
527 s.bitDepth = oldbitdepth;
528 fps = nFrames / diffT;
529 fprintf('\n\n======>>> Stimulus: %s\n',me.fullName);
530 fprintf('======>>> <strong>SPEED</strong> (%i frames in %.3f secs) = <strong>%g</strong> fps\n\n',nFrames, diffT, fps);
531 if ~benchmark;fprintf('\b======>>> First - Last frame time: %.3f\n\n',vbl(end)-startT);end
532 clear s fps benchmark runtime b bb i vbl; %clear up a bit
533 catch ERR
534 try getReport(ERR); end
535 try Priority(0); end
536 if exist('s','var') && isa(s,'screenManager')
537 try close(s); end
538 end
539 clear fps benchmark runtime b bb i; %clear up a bit
540 reset(me); %
reset our stimulus ready for use again
541 rethrow(ERR)
542 end
543 end
544
545 % ===================================================================
546 %> @brief make a GUI properties panel for this object
547 %>
548 % ===================================================================
550 if ~isempty(me.
handles) && isfield(me.
handles, 'root') && isa(me.
handles.root,'matlab.ui.container.Panel')
551 fprintf('---> Panel already open for %s\n', me.
fullName);
552 return
553 end
554
558
559 if ~exist('parent','var')
560 parent = uifigure('Tag','gFig',...
561 'Name', [me.
fullName 'Properties'], ...
562 'Position', [ 10 10 800 500 ],...
563 'MenuBar', 'none', ...
565 'NumberTitle', 'off');
568 end
569
570 bgcolor = [0.95 0.95 0.95];
571 bgcoloredit = [1 1 1];
572 fsmall = 11;
573 fmed = 12;
574 handles.root = uipanel('Parent', parent,...
575 'Units', 'normalized',...
576 'Position', [0 0 1 1],...
578 'TitlePosition','centertop',...
580 'FontSize', fmed,...
581 'FontAngle', 'italic',...
582 'BackgroundColor', [0.94 0.94 0.94]);
583 handles.grid = uigridlayout(
handles.root,[1 3],'ColumnSpacing',2,'RowSpacing',2,'Padding',[2 2 2 2],'BackgroundColor',bgcolor);
584 handles.grid1 = uigridlayout(
handles.grid,'ColumnSpacing',2,'RowSpacing',2,'Padding',[2 2 2 2],'BackgroundColor',bgcolor);
585 handles.grid2 = uigridlayout(
handles.grid,'ColumnSpacing',2,'RowSpacing',2,'Padding',[2 2 2 2],'BackgroundColor',bgcolor);
586 handles.grid.ColumnWidth = {
'1x',
'1x',130};
587 handles.grid1.ColumnWidth = {
'2x',
'1x'};
588 handles.grid2.ColumnWidth = {
'2x',
'1x'};
589
590 idx = {'handles.grid1','handles.grid2','handles.grid3'};
591
592 disableList = 'fullName';
593
594 mc = metaclass(me);
595 pl = string({mc.PropertyList.Name});
596 d1 = {mc.PropertyList.Description};
597 d2 = {mc.PropertyList.DetailedDescription};
598 for i = 1:length(d1)
599 a = regexprep(d1{i},'^\s*>\s*','');
600 a = regexprep(a,'''','`');
601 b = d2{i};
602 if isempty(b)
603 dl{i} = string(a);
604 else
605 b = strsplit(b,'\n');
606 for j = 1:length(b)
607 b{j} = regexprep(b{j},'^\s*>\s*','');
608 b{j} = regexprep(b{j},'''','`');
609 end
610 dl{i} = string([{a} b(:)']);
611 end
612 end
613 pr = findAttributesandType(me,'SetAccess','public','notlogical');
614 pr = sort(pr);
615 igA = {}; igB = {};
616 val = findPropertyDefault(me,'ignorePropertiesUI');
617 if ~isempty(val)
618 igA = val;
619 elseif isprop(me,'ignorePropertiesUI')
620 igA = me.ignorePropertiesUI;
621 end
623 if ischar(igA);igA = strsplit(igA,'|');end
625 excl = [igA igB];
626 eidx = [];
627 for i = 1:length(pr)
628 if matches(pr{i}, excl)
629 eidx = [eidx i];
630 end
631 end
632 pr(eidx) = [];
633 lp = ceil(length(pr)/2);
634
635 pr2 = findAttributesandType(me,'SetAccess','public','logical');
636 pr2 = sort(pr2);
637 eidx = [];
638 for i = 1:length(pr2)
639 if matches(pr2{i},excl)
640 eidx = [eidx i];
641 end
642 end
643 pr2(eidx) = [];
644 lp2 = length(pr2);
645 if lp2 > 0; handles.grid3 = uigridlayout(handles.grid,[lp2 1],'Padding',[1 1 1 1],'BackgroundColor',bgcolor); end
646
647 for i = 1:2
648 for j = 1:lp
649 cur = lp*(i-1)+j;
650 if cur <= length(pr)
651 nm = pr{cur};
652 val = me.(nm);
653 % this gets descriptions
654 ix = find(pl == nm);
655 if ~isempty(ix)
656 desc = dl{ix};
657 else
658 desc = nm;
659 end
660 if ischar(val)
661 if isprop(me,[nm 'List'])
662 if strcmp(me.([nm 'List']),'filerequestor')
663 val = regexprep(val,'\s+',' ');
664 handles.([nm '_char']) = uieditfield(...
665 'Parent',eval(idx{i}),...
666 'Tag',[nm '_char'],...
667 'HorizontalAlignment','center',...
668 'ValueChangedFcn',@me.readPanel,...
669 'Value',val,...
670 'FontName',me.monoFont,...
671 'Tooltip', desc, ...
672 'BackgroundColor',bgcoloredit);
673 if ~isempty(regexpi(nm,disableList,'once'))
674 handles.([nm '_char']).Enable = false;
675 end
676 else
677 txt=findPropertyDefault(me,[nm 'List']);
678 if contains(val,txt)
679 handles.([nm '_list']) = uidropdown(...
680 'Parent',eval(idx{i}),...
681 'Tag',[nm '_list'],...
682 'Items',txt,...
683 'ValueChangedFcn',@me.readPanel,...
684 'Value',val,...
685 'Tooltip', desc, ...
686 'BackgroundColor',bgcolor);
687 if ~isempty(regexpi(nm,disableList,'once'))
688 handles.([nm '_list']).Enable = false;
689 end
690 else
691 handles.([nm '_list']) = uidropdown(...
692 'Parent',eval(idx{i}),...
693 'Tag',[nm '_list'],...
694 'Items',txt,...
695 'Tooltip', desc, ...
696 'ValueChangedFcn',@me.readPanel,...
697 'BackgroundColor',bgcolor);
698 end
699 end
700 else
701 val = regexprep(val,'\s+',' ');
702 handles.([nm '_char']) = uieditfield(...
703 'Parent',eval(idx{i}),...
704 'Tag',[nm '_char'],...
705 'HorizontalAlignment','center',...
706 'ValueChangedFcn',@me.readPanel,...
707 'Value',val,...
708 'Tooltip', desc, ...
709 'BackgroundColor',bgcoloredit);
710 if ~isempty(regexpi(nm,disableList,'once'))
711 handles.([nm '_char']).Enable = false;
712 end
713 end
714 elseif isnumeric(val)
715 val = num2str(val);
716 val = regexprep(val,'\s+',' ');
717 handles.([nm '_num']) = uieditfield('text',...
718 'Parent',eval(idx{i}),...
719 'Tag',[nm '_num'],...
720 'HorizontalAlignment','center',...
721 'Value',val,...
722 'Tooltip', desc, ...
723 'ValueChangedFcn',@me.readPanel,...
724 'FontName',me.monoFont,...
725 'BackgroundColor',bgcoloredit);
726 if ~isempty(regexpi(nm,disableList,'once'))
727 handles.([nm '_num']).Enable = false;
728 end
729 else
730 uilabel('Parent',eval(idx{i}),'Text','','BackgroundColor',bgcolor,'Enable','off');
731 end
732 if isprop(me,[nm 'List'])
733 if strcmp(me.([nm 'List']),'filerequestor')
734 handles.([nm '_button']) = uibutton(...
735 'Parent',eval(idx{i}),...
736 'HorizontalAlignment','left',...
737 'Text','Select file...',...
738 'FontName',me.sansFont,...
739 'Tag',[nm '_button'],...
740 'Tooltip', desc, ...
741 'Icon', [me.paths.root '/ui/images/edit.svg'],...
742 'ButtonPushedFcn', @me.selectFilePanel,...
743 'FontSize', 8);
744 else
745 uilabel(...
746 'Parent',eval(idx{i}),...
747 'HorizontalAlignment','left',...
748 'Text',nm,...
749 'FontName',me.sansFont,...
750 'FontSize', fsmall,...
751 'BackgroundColor',bgcolor);
752 end
753 else
754 uilabel(...
755 'Parent',eval(idx{i}),...
756 'HorizontalAlignment','left',...
757 'Text',nm,...
758 'FontName',me.sansFont,...
759 'FontSize', fsmall,...
760 'BackgroundColor',bgcolor);
761 end
762 else
763 uilabel('Parent',eval(idx{i}),'Text','','BackgroundColor',bgcolor,'Enable','off');
764 end
765 end
766 end
767 for j = 1:lp2
768 nm = pr2{j};
769 val = me.(nm);
770 % this gets descriptions
771 ix = find(pl == nm);
772 if ~isempty(ix)
773 desc = dl{ix};
774 else
775 desc = nm;
776 end
777 if j <= length(pr2)
778 handles.([nm '_bool']) = uicheckbox(...
779 'Parent',eval(idx{end}),...
780 'Tag',[nm '_bool'],...
781 'Text',nm,...
782 'Tooltip', desc, ...
783 'FontName',me.sansFont,...
784 'FontSize', fsmall,...
785 'ValueChangedFcn',@me.readPanel,...
786 'Value',val);
787 end
788 end
789 %handles.readButton = uibutton(...
790 % 'Parent',eval(idx{end}),...
791 % 'Tag','readButton',...%'Callback',@me.readPanel,...
792 % 'Text','Update');
793 me.handles = handles;
794 me.isGUI = true;
795 end
796
797 % ===================================================================
798 %> @brief read values from a GUI properties panel for this object
799 %>
800 % ===================================================================
801 function selectFilePanel(me,varargin)
802 if nargin > 0
803 hin = varargin{1};
804 if ishandle(hin)
805 [f,p] = uigetfile('*.*','Select File:');
806 re = regexp(get(hin,'Tag'),'(.+)_button','tokens','once');
807 hout = me.handles.([re{1} '_char']);
808 if ishandle(hout)
809 set(hout,'Value', [p f]);
810 me.readPanel(hout);
811 end
812 end
813 end
814 end
815
816 % ===================================================================
817 %> @brief read values from a GUI properties panel for this object
818 %>
819 % ===================================================================
820 function readPanel(me,varargin)
821 if isempty(me.handles) || ~(isfield(me.handles, 'root') && isa(me.handles.root,'matlab.ui.container.Panel'))
822 return
823 end
824 if isempty(varargin) || isempty(varargin{1}); return; end
825 source = varargin{1};
826 tag = source.Tag;
827 if isempty(tag); return; end
828 tagName = regexprep(tag,'_.+$','');
829 tagType = regexprep(tag,'^.+_','');
830
831 pList = findAttributes(me,'SetAccess','public'); %our public properties
832
833 if ~any(contains(pList,tagName)); return; end
834
835 switch tagType
836 case 'list'
837 me.(tagName) = source.Value;
838 case 'bool'
839 me.(tagName) = logical(source.Value);
840 case 'num'
841 me.(tagName) = str2num(source.Value);
842 case 'char'
843 me.(tagName) = source.Value;
844 otherwise
845 warning('Can''t set property');
846 end
847
848 if strcmpi(tagName,'name')
849 me.handles.fullName_char.Value = me.fullName;
850 me.handles.root.Title = me.fullName;
851 end
852
853 if strcmpi(tagName,'alpha')
854 me.handles.colour_num.Value = num2str(me.colour, '%g ');
855 if isprop(me,'colour2')
856 me.handles.colour2_num.Value = num2str(me.colour2, '%g ');
857 end
858 if isprop(me,'baseColour')
859 me.handles.baseColour_num.Value = num2str(me.baseColour, '%g ');
860 end
861 end
862
863 if strcmpi(tagName,'alpha2')
864 if isprop(me,'colour2');me.handles.colour2_num.Value = num2str(me.colour2, '%g ');end
865 end
866
867 if strcmpi(tagName,'colour')
868 me.handles.alpha_num.Value = num2str(me.alpha, '%g ');
869 if isprop(me,'correctBaseColour') && me.correctBaseColour
870 me.handles.baseColour_num.Value = num2str(me.baseColour, '%g ');
871 end
872 end
873
874 if strcmpi(tagName,'colour2')
875 if isprop(me,'alpha2');me.handles.alpha2_num.Value = num2str(me.alpha2, '%g ');end
876 if isprop(me,'correctBaseColour') && me.correctBaseColour
877 me.handles.baseColour_num.Value = num2str(me.baseColour, '%g ');
878 end
879 end
880
882 me.checkfilePath();
883 end
884
886 end
887
888 % ===================================================================
889 %> @brief show GUI properties panel for this object
890 %>
891 % ===================================================================
892 function showPanel(me)
893 if isempty(me.handles)
894 return
895 end
896 set(me.handles.root,'Enable','on');
897 set(me.handles.root,'Visible','on');
898 end
899
900 % ===================================================================
901 %> @brief hide GUI properties panel for this object
902 %>
903 % ===================================================================
904 function hidePanel(me)
905 if isempty(me.handles)
906 return
907 end
908 set(me.handles.root,'Enable','off');
909 set(me.handles.root,'Visible','off');
910 end
911
912 % ===================================================================
913 %> @brief close GUI panel for this object
914 %>
915 % ===================================================================
916 function closePanel(me,varargin)
917 if isfield(me.handles,'root') && isgraphics(me.handles.root)
918 delete(me.handles.root);
919 end
920 if isfield(me.handles,'parent') && isgraphics(me.handles.parent,'figure')
921 delete(me.handles.parent)
922 end
923 me.cleanHandles();
924 me.isGUI = false;
925 end
926
927 % ===================================================================
928 %> @fn cleanHandles
929 %> @brief clean any handles
930 %>
931 %> @param
932 %> @return
933 % ===================================================================
934 function cleanHandles(me, ~)
936 me.handles = [];
937 end
938 if isprop(me,'h')
939 me.h = [];
940 end
942 me.isGUI = false;
943 end
944
945 % ===================================================================
946 %> @fn getP
947 %> @brief gets a property copy or original property
948 %>
949 %> When stimuli are run, their properties are copied, so e.g. angle
950 %> is copied to angleOut and this is used during the task. This
951 %> method checks if the copy is available and returns that, otherwise
952 %> return the original.
953 %>
954 %> @param name of property
955 %> @param range of property to return
956 %> @return value of property
957 % ===================================================================
958 function [value, nameOut] = getP(me, name, range)
959 arguments(Input)
960 me
961 name char
962 range = []
963 end
964 arguments(Output)
965 value
966 nameOut char
967 end
968 value = []; nameOut = [];
969 if isprop(me, [name 'Out']) && ~isempty(me.([name 'Out']))
970 nameOut = [name 'Out'];
971 value = me.(nameOut);
972 if ~isempty(range); value = value(range); end
973 elseif isprop(me, name)
974 value = me.(name);
975 nameOut = name;
976 if ~isempty(range); value = value(range); end
977 elseif me.verbose
978 fprintf('!!! Property %s doesn''t exist...\n',name)
979 end
980 end
981
982 % ===================================================================
983 %> @fn setP
984 %> @brief sets a property copy or original property
985 %>
986 %> When stimuli are run, their properties are copied, so e.g. angle
987 %> is copied to angleOut and this is used during the task. This
988 %> method checks if the copy is available and returns that, otherwise
989 %> return the original.
990 %>
991 %> @param name of property
992 %> @param range of property to return
993 %> @return value of property
994 % ===================================================================
995 function setP(me, name, value)
996 arguments(Input)
997 me
998 name char
999 value
1000 end
1001 if isprop(me,[name 'Out'])
1002 me.([name 'Out']) = value;
1003 elseif isprop(me, name)
1004 me.(name) = value;
1005 elseif me.verbose
1006 fprintf('!!! Property %s doesn''t exist...\n',name);
1007 end
1008 end
1009
1010 % ===================================================================
1011 %> @fn updateXY
1012 %> @brief Update only position info, faster and doesn't
reset image etc.
1013 %>
1014 %> @param x X position
1015 %> @param y Y position
1016 %> @param useDegrees where the input is in degrees (true) or pixels (false)
1017 % ===================================================================
1018 function
updateXY(me,x,y,useDegrees)
1019 arguments(Input)
1020 me
1021 x double = NaN
1022 y double = NaN
1023 useDegrees logical = false
1024 end
1025 if ~me.
isSetup; warning(
"updateXY: Need to setup stimulus first!");
return; end
1026 if isnan(x) || isnan(y); return; end
1027 if ~isempty(x) && ~isscalar(x); x = x(1); end; if ~isempty(y) && ~isscalar(y); y = y(1); end
1028 if useDegrees
1029 if ~isempty(x); me.xFinal = me.sM.toPixels(x, 'x'); me.xFinalD = x; me.xPositionOut = x; end
1030 if ~isempty(y); me.yFinal = me.sM.toPixels(y, 'y'); me.yFinalD = y; me.yPositionOut = y; end
1031 else
1032 if ~isempty(x); me.xFinal = x; me.xFinalD = me.sM.toDegrees(x, 'x'); me.xPositionOut = me.xFinalD; end
1033 if ~isempty(y); me.yFinal = y; me.yFinalD = me.sM.toDegrees(y, 'y'); me.yPositionOut = me.yFinalD; end
1034 end
1035 if me.isRect && length(me.mvRect) == 4
1036 me.mvRect=CenterRectOnPointd(me.mvRect, me.xFinal, me.yFinal);
1037 end
1038 end
1039
1040 end %---END PUBLIC METHODS---%
1041
1042 %=======================================================================
1043 methods ( Static ) %----------STATIC METHODS
1044 %=======================================================================
1045
1046 % ===================================================================
1047 %> @brief linear interpolation between two
colour arrays based on a contrast
1048 %>
1049 % ===================================================================
1050 function out =
mixColour(c1, c2, contrast)
1051 arguments(Input)
1052 c1 (1,3) double
1053 c2 (1,3) double
1054 contrast (1,1) double {mustBeNonnegative, mustBeLessThanOrEqual(contrast,1)}
1055 end
1056 out = c1(1:3) * (1 - contrast) + c2(1:3) * contrast;
1057 end
1058
1059 % ===================================================================
1060 %> @brief degrees2radians
1061 %>
1062 % ===================================================================
1063 function r =
d2r(degrees)
1065 r=degrees*(pi/180);
1066 end
1067
1068 % ===================================================================
1069 %> @brief radians2degrees
1070 %>
1071 % ===================================================================
1072 function degrees =
r2d(r)
1074 degrees=r*(180/pi);
1075 end
1076
1077 % ===================================================================
1079 %>
1080 % ===================================================================
1083 distance=sqrt((x2 - x1)^2 + (y2 - y1)^2);
1084 end
1085
1086 % ===================================================================
1088 %>
1089 % ===================================================================
1093 if length(
dX)== 1 && abs(
dX) < 1e-3;
dX = 0; end
1095 if length(
dY)==1 && abs(
dY) < 1e-3;
dY = 0; end
1096 end
1097
1098 end%---END STATIC METHODS---%
1099
1100 %=======================================================================
1101 methods ( Access = protected ) %-------PRIVATE (protected) METHODS-----%
1102 %=======================================================================
1103
1104 % ===================================================================
1106 %> @brief These are transient properties that specify actions during runtime
1107 % ===================================================================
1109 if isempty(me.findprop('doFlash')); me.addprop('doFlash');end
1110 if isempty(me.findprop('doDots')); me.addprop('doDots');end
1111 if isempty(me.findprop('doMotion')); me.addprop('doMotion');end
1112 if isempty(me.findprop('doDrift')); me.addprop('doDrift');end
1113 if isempty(me.findprop('doAnimator')); me.addprop('doAnimator');end
1115 end
1116
1117 % ===================================================================
1119 %> @brief Update transient properties that specify actions during runtime
1120 % ===================================================================
1122 me.doDots = false;
1123 me.doMotion = false;
1124 me.doDrift = false;
1125 me.doFlash = false;
1126 me.doAnimator = false;
1127
1128 if isprop(me, 'tf')
1129 [v,n] =
getP(me,'tf');
1130 if ~isempty(n) && v > 0; me.doDrift = true; end
1131 end
1132
1133 if isprop(me, '
speed')
1135 if ~isempty(n) && v > 0; me.doMotion = true; end
1136 end
1137
1138 if strcmpi(me.
family,'dots'); me.doDots = true; end
1139
1140 if strcmpi(me.
type,'flash'); me.doFlash = true; end
1141
1143 me.doAnimator = true;
1144 end
1145 end
1146
1147 % ===================================================================
1150 %>
1151 % ===================================================================
1155 else
1157 if isprop(me,'direction')
1158 [dx, dy]=pol2cart(me.
d2r(
getP(me,'direction')), sP);
1159 else
1161 end
1162 me.
xFinal = me.xPositionOut + (dx * me.
ppd) + me.
sM.xCenter;
1163 me.
yFinal = me.yPositionOut + (dy * me.
ppd) + me.
sM.yCenter;
1167 end
1169 end
1170
1171 % ===================================================================
1174 %>
delta during animation, so we have to cache these properties to private copies so that
1175 %> when we call the
animate method, it uses the cached versions not the
1176 %> public versions. This method simply copies the properties to their cached
1177 %> equivalents.
1178 % ===================================================================
1183 end
1184
1185 % ===================================================================
1190 % ===================================================================
1192 if isempty(me.
texture); me.
mvRect = [0 0 100 100]; return; end
1193 if isprop(me,'scale')
1194 me.
dstRect = ScaleRect(Screen('Rect',me.
texture(1)), me.scale, me.scale);
1195 else
1197 end
1200 else
1202 end
1206 fprintf('---> %s
setRect = [%.2f %.2f %.2f %.2f] width = %.2f height = %.2f\n',...
1209 end
1210 end
1211
1212 % ===================================================================
1214 %> @brief Converts properties to a structure
1215 %>
1216 %> @param me this instance object
1217 %> @param tmp is whether to use the temporary or permanent properties
1218 %> @return out the structure
1219 % ===================================================================
1221 if ~exist('tmp','var')
1222 tmp = 0; %copy real properties, not temporary ones
1223 end
1224 fn = fieldnames(me);
1225 for j=1:length(fn)
1226 if tmp == 0
1227 out.(fn{j}) = me.(fn{j});
1228 else
1229 out.(fn{j}) = me.([fn{j} 'Out']);
1230 end
1231 end
1232 end
1233
1234 % ===================================================================
1236 %> @brief Finds and removes dynamic properties
1237 %>
1238 %> @param me
1239 %> @return
1240 % ===================================================================
1242 allprops = properties(me);
1243 for i=1:numel(allprops)
1244 m = findprop(me, allprops{i});
1245 if isa(m,'meta.DynamicProperty')
1247 end
1248 end
1256 end
1257
1258 % ===================================================================
1259 %> @fn Delete method
1260 %>
1261 %> @param me
1262 %> @return
1263 % ===================================================================
1267 if Screen(me.
texture, 'WindowKind')~=0 ;try Screen('Close',me.
texture); end; end %
#ok<*TRYNC>
1268 end
1269 end
1270 if isprop(me,'buffertex') && ~isempty(me.buffertex)
1271 if Screen(me.buffertex, 'WindowKind')~=0 ; try Screen('Close',me.buffertex); end; end
1272 end
1273
1275 end
1276
1277 end%---END PRIVATE METHODS---%
1278end
ANIMATIONMANAGER Provides per frame paths for stimuli We integrate dyn4j java physics engine for rigi...
Definition animationManager.m:38
baseStimulus is the superclass for all stimulus objects
Definition baseStimulus.m:3
Property delayTime
Definition baseStimulus.m:71
Property speed
Definition baseStimulus.m:63
function computePosition(in me)
compute xFinal and yFinal (in pixels) taking startPosition, xPosition, yPosition and direction/angle ...
Property mouseValid
is mouse position within screen co-ordinates?
Definition baseStimulus.m:187
Property yFinalD
Definition baseStimulus.m:121
Property delta_
delta cache
Definition baseStimulus.m:205
function getMousePosition(in me)
get mouse position we make sure this is only called once per animation tick to improve performance an...
Property sM
our screen manager
Definition baseStimulus.m:165
Property xFinalD
X and Y position in °
Definition baseStimulus.m:119
Property szPx
computed size in pixels
Definition baseStimulus.m:113
function setAnimationDelta(in me)
Property ppd
pixels per degree (normally inhereted from screenManager)
Definition baseStimulus.m:139
Property xFinal
Definition baseStimulus.m:94
function setDelayTime(in me, in time)
set delayTime
static function d2r(in degrees)
degrees2radians
Property ignorePropertiesUIBase
Which properties to not draw in the UI panel.
Definition baseStimulus.m:229
Property isRect
Definition baseStimulus.m:130
@ readPanelUpdate
triggered when reading from a UI panel,
Definition baseStimulus.m:245
function resetTicks(in me)
reset the various tick counters for our stimulus
Property colour
Definition baseStimulus.m:50
virtual setup(in runObject)
ALL Children must implement these 5 methods!
function getP(in me, in name, in range)
gets a property copy or original property
function updateRuntimeProperties(in me)
Update transient properties that specify actions during runtime.
Property alpha
Alpha (opacity) [0-1], this gets combined with the RGB colour.
Definition baseStimulus.m:53
virtual animate(in runObject)
animate the stimulus, normally called after a draw
static function findDistance(in x1, in y1, in x2, in y2)
findDistance in X and Y coordinates
virtual draw(in runObject)
draw to the screen buffer, ready for flip()
Property handles
handles for the GUI
Definition baseStimulus.m:162
Property texture
Our texture pointer for texture-based stimuli.
Definition baseStimulus.m:159
function setOffTime(in me, in time)
set offTime
Property verbose
Do we log extra details to the command-line?
Definition baseStimulus.m:86
function makePanel(in me, in parent)
make a GUI properties panel for this object
Property isVisible
true or false, whether to draw() this object
Definition baseStimulus.m:77
Property yFinal
Definition baseStimulus.m:98
Property type
stimulus type
Definition baseStimulus.m:23
static function r2d(in r)
radians2degrees
Property animator
animation manager
Definition baseStimulus.m:168
Property startPosition
Definition baseStimulus.m:59
virtual update(in runObject)
function closePanel(in me, in varargin)
close GUI panel for this object
static function mixColour(in c1, in c2, in contrast)
linear interpolation between two colour arrays based on a contrast
Property yPosition
Definition baseStimulus.m:42
Property screenVals
screen settings generated by sM on setup
Definition baseStimulus.m:171
Property isSetup
Definition baseStimulus.m:174
Property offTime
time to turn stimulus off, relative to stimulus onset
Definition baseStimulus.m:74
Property mvRect
Definition baseStimulus.m:102
Property angle
angle in degrees (0 - 360)
Definition baseStimulus.m:66
Property dY
X update which is computed from our speed and angle.
Definition baseStimulus.m:152
Property szD
computed size in °
Definition baseStimulus.m:116
static function updatePosition(in delta, in angle)
updatePosition returns dX and dY given an angle and delta
function removeTmpProperties(in me)
Finds and removes dynamic properties.
function run(in me, in benchmark, in runtime, in s, in forceScreen, in showVBL)
Run stimulus in a window to preview it.
function updateXY(in me, in x, in y, in useDegrees)
Update only position info, faster and doesn't reset image etc.
Property dX_
dX cache
Definition baseStimulus.m:208
virtual reset(in runObject)
Property mouseX
mouse X position
Definition baseStimulus.m:190
Property delta
What our per-frame motion delta is.
Definition baseStimulus.m:146
function hide(in me)
Method to set isVisible=false.
Property dstRect
initial screen rectangle position [LEFT TOP RIGHT BOTTOM]
Definition baseStimulus.m:184
Property family
the stimulus family (grating, dots etc.)
Definition baseStimulus.m:30
function addRuntimeProperties(in me)
These are transient properties that specify actions during runtime.
Property mouseY
mouse Y position
Definition baseStimulus.m:193
function show(in me)
Method to set isVisible=true.
Property dX
X update which is computed from our speed and angle.
Definition baseStimulus.m:149
Property tick
tick updates +1 on each call of draw (even if delay or off is true and no stimulus is drawn,...
Definition baseStimulus.m:133
Property mouseOverride
override X and Y position with mouse input? Useful for RF mapping
Definition baseStimulus.m:80
Property xPosition
Definition baseStimulus.m:38
Property dY_
dY cache
Definition baseStimulus.m:211
function toStructure(in me, in tmp)
Converts properties to a structure.
Show images or directories full of images.
Definition imageStimulus.m:12
function getFonts(in me)
set paths for object
Property fullName
The fullName is the object name combined with its uuid and class name.
Definition optickaCore.m:73
function setPaths(in me)
Sets properties from a structure or normal arguments pairs, ignores invalid or non-allowed properties...
Property paths
storage of various paths
Definition optickaCore.m:66
Property sansFont
sans font
Definition optickaCore.m:83
Property name
object name
Definition optickaCore.m:33
screenManager — manage opening and configuring the PTB screen
Definition screenManager.m:3