1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816 |
- function [hd,area,freq,areastim,freqstim] = figureFreqArea() %#ok<FNDEF>
-
- close all
-
- [freq,~,freqstim] = gainAnalysisFreq;
- [area,~,areastim] = gainAnalysisArea;
- freqoutline = displayStimuli('freq');
- areaoutline = displayStimuli('area');
- freqpattern = displayPattern('freq',freqstim.uniquespatialfreq);
- areapattern = displayPattern('area',freqstim.uniquespatialfreq(4)*ones(1,7));
-
- hd.collage.fg = figure(7);
- hd.collage.fg.Name = 'Sphere manuscript (Fig 7); OKR gain dependence on stimulus size, frequency';
- hd.collage.fg.Color = [1 1 1];
- hd.collage.fg.Position = [0 40 1070 800];
-
-
- % hd.collage.ax(2,1) = axes;
-
-
- % Populate the combined figure from previously generated partial figures...
- hd.collage.ax(1,1) = copyobj(freqpattern.pattern.ax(1), hd.collage.fg);
- hd.collage.ax(1,2) = copyobj(freqoutline.default.ax(1), hd.collage.fg);
- hd.collage.ax(2,1) = copyobj(areapattern.pattern.ax(1), hd.collage.fg);
- hd.collage.ax(2,2) = copyobj(areaoutline.default.ax(1), hd.collage.fg);
- for subpanel = 1:3
- % elements of panel c (location of stimulus centres):
- hd.collage.ax(3,subpanel) = copyobj(area.gain.ax(1), hd.collage.fg);
- % elements of panel d (frequency dependence of gain):
- hd.collage.ax(4,subpanel) = copyobj(freq.gain.ax(1+subpanel), hd.collage.fg);
- % elements of panel e (area dependence of gain):
- hd.collage.ax(5,subpanel) = copyobj(area.gain.ax(1+subpanel), hd.collage.fg);
- end
- % close([1 2 3])
- % ...then adjust the size, position and other properties of those objects.
-
- % Shared properties of all axis objects anywhere:
- [hd.collage.ax(1:2,1:2).Units,hd.collage.ax(3:5,:).Units] = deal('pixels');
- [hd.collage.ax(1:2,1:2).FontSize,hd.collage.ax(3:5,:).FontSize] = deal(15);
- [hd.collage.ax(1:2,1:2).LabelFontSizeMultiplier, ...
- hd.collage.ax(1:2,1:2).TitleFontSizeMultiplier, ...
- hd.collage.ax(3:5,:).LabelFontSizeMultiplier, ...
- hd.collage.ax(3:5,:).TitleFontSizeMultiplier] = deal(1);
- % Positions and sizes within panel a (stimulus frequency):
- hd.collage.ax(1,1).Units = 'pixels';
- hd.collage.ax(1,1).Position = [ 20 600 260 170];
- hd.collage.ax(1,2).Units = 'pixels';
- hd.collage.ax(1,2).Position = [300 600 170 170];
- % Positions and sizes within panel b (stimulus sizes):
- hd.collage.ax(2,1).Units = 'pixels';
- hd.collage.ax(2,1).Position = [540 600 270 170];
- hd.collage.ax(2,2).Units = 'pixels';
- hd.collage.ax(2,2).Position = [820 600 170 170];
- % Positions and sizes within panel c (location of stimulus centres):
- for subpanel = 1:3
- hd.collage.ax(3,subpanel).Position(1) = 30; % x offset
- hd.collage.ax(3,subpanel).Position(2) = 335 - (subpanel-1)*180; % y offset
- hd.collage.ax(3,subpanel).Position(3) = 200; % x width
- hd.collage.ax(3,subpanel).Position(4) = 200; % y height
- end
- hd.collage.ax(3,2).Position(2) = hd.collage.ax(3,2).Position(2) - 8; % shift single y offset
-
- % Positions and sizes within panel d (frequency dependence of gain):
- for subpanel = 1:3
- hd.collage.ax(4,subpanel).Position(1) = hd.collage.ax(3,subpanel).Position(1) + ...
- hd.collage.ax(3,subpanel).Position(3) + ...
- 160 + (subpanel-1)*230; % x offset
- hd.collage.ax(4,subpanel).Position(2) = 350; % y offset
- hd.collage.ax(4,subpanel).Position(3) = 210; % x width
- hd.collage.ax(4,subpanel).Position(4) = 142; % y height
- end
- % Positions and sizes within panel e (area dependence of gain):
- for subpanel = 1:3
- hd.collage.ax(5,subpanel).Position(1) = hd.collage.ax(4,subpanel).Position(1); % x offset
- hd.collage.ax(5,subpanel).Position(2) = 70; % y offset
- hd.collage.ax(5,subpanel).Position(3) = hd.collage.ax(4,subpanel).Position(3); % x width
- hd.collage.ax(5,subpanel).Position(4) = hd.collage.ax(4,subpanel).Position(4); % y height
- end
-
- % Set camera angles for panels a and b:
- [hd.collage.ax(1:2,2).CameraPosition] = deal([-1 -1.5 0]);
- [hd.collage.ax(1:2,2).CameraViewAngle] = deal(60);
-
- % Set camera angles for panel c:
- hd.collage.ax(3,1).CameraPosition = [0.1 0 20];
- hd.collage.ax(3,2).CameraPosition = [20 0 0];
- hd.collage.ax(3,3).CameraPosition = [-16 -9 6];
- [hd.collage.ax(3,:).CameraViewAngle] = deal(9.5);
-
- % Add azimuth and elevation labels to the plots on panel c:
- axes(hd.collage.ax(3,1))
- labelx = [hd.collage.ax(3,1).Children(1:6).XData] * 1.15;
- labely = [hd.collage.ax(3,1).Children(1:6).YData] * 1.15;
- labelz = [hd.collage.ax(3,1).Children(1:6).ZData] * 0;
- stringlist = areastim.azimuth(7*(1:6)); % unexpected order here, fix by quick hack
- stringlist([1 3 4 6]) = stringlist([3 1 6 4]);
- for location = 1:6
- hold on
- hd.collage.tx(3,1,location) = text(1.1*labelx(location), labely(location), labelz(location), ...
- [num2str(stringlist(location),'%+.0f'),'°']);
- hold off
- end
- [hd.collage.tx(3,1,1:3).HorizontalAlignment] = deal('right');
- [hd.collage.tx(3,1,4:6).HorizontalAlignment] = deal('left');
- [hd.collage.tx(3,1,:).VerticalAlignment] = deal('middle');
- axes(hd.collage.ax(3,2))
- labelx = [hd.collage.ax(3,2).Children(1:6).XData] * 0;
- labely = [hd.collage.ax(3,2).Children(1:6).YData] * 1.22;
- labelz = [hd.collage.ax(3,2).Children(1:6).ZData] * 1.22;
- for location = [2 3 5 6]
- hold on
- hd.collage.tx(3,2,location) = text(labelx(location), labely(location), labelz(location), ...
- [num2str(areastim.elevation(7*location),'%+.0f'),'°']);
- hold off
- end
- [hd.collage.tx(3,2,2:3).HorizontalAlignment] = deal('right');
- [hd.collage.tx(3,2,5:6).HorizontalAlignment] = deal('left');
- [hd.collage.tx(3,2,[2 3 5 6]).VerticalAlignment] = deal('middle');
- [hd.collage.tx(3,1,:).FontSize, hd.collage.tx(3,2,[2 3 5 6]).FontSize] = ...
- deal(hd.collage.ax(3,1).FontSize);
-
- % Recolour the background spheres on panels a and b, and remove visible axes:
- greyscale = (.8:.01:.99)' * [1 1 1];
- [hd.collage.ax(1:2,2).Colormap] = deal(greyscale);
- for panel = [1 2]
- for subpanel = [1 2]
- [hd.collage.ax(panel,subpanel).XAxis.Visible, ...
- hd.collage.ax(panel,subpanel).YAxis.Visible, ...
- hd.collage.ax(panel,subpanel).ZAxis.Visible] = deal('off');
- end
- end
-
- % Add and format titles to panels a and b:
- for panel = 1:2
- hd.collage.ax(panel,1).Title.String = 'stimulus pattern';
- hd.collage.ax(panel,2).Title.String = 'stimulus crop';
- for subpanel = 1:2
- hd.collage.ax(panel,subpanel).Title.FontWeight = 'normal';
- hd.collage.ax(panel,subpanel).Title.Units = 'pixels';
- end
- hd.collage.ax(panel,1).Title.Position(2) = hd.collage.ax(panel,1).Position(4) - 14 + 14;
- hd.collage.ax(panel,2).Title.Position(2) = hd.collage.ax(panel,2).Position(4) + 2;
- end
- % Reformat the meridians, equators etc. on panel c:
- for subpanel = 1:3
- [hd.collage.ax(3,subpanel).Children(7:9).LineWidth] = deal(1);
- end
-
- % Add and format titles to panel c:
- hd.collage.ax(3,1).Title.String = 'centre azimuth';
- hd.collage.ax(3,2).Title.String = 'centre elevation';
- hd.collage.ax(3,3).Title.String = 'oblique view';
- for subpanel = 1:3
- hd.collage.ax(3,subpanel).Title.FontWeight = 'normal';
- hd.collage.ax(3,subpanel).Title.Units = 'pixels';
- end
- hd.collage.ax(3,1).Title.Position(2) = hd.collage.ax(3,1).Position(4) - 70;
- hd.collage.ax(3,2).Title.Position(2) = hd.collage.ax(3,2).Position(4) - 70;
- hd.collage.ax(3,3).Title.Position(2) = hd.collage.ax(3,3).Position(4) - 45;
-
- % % Remove redundant x axis labels on panels d and e:
- % for panel = [4 5]
- % for subpanel = [1 3]
- % hd.collage.ax(panel,subpanel).XLabel.String = [];
- % end
- % end
-
- % Remove redundant titles on panel e:
- for panel = 5
- for subpanel = [1 2 3]
- hd.collage.ax(panel,subpanel).Title.String = [];
- end
- end
-
- % Reposition X and Y axis labels on panels d and e:
- for panel = [4 5]
- for subpanel = 1:3
- hd.collage.ax(panel,subpanel).XLabel.Units = 'pixels';
- end
- hd.collage.ax(panel,1).YLabel.Units = 'pixels';
- end
- for subpanel = 1:3
- hd.collage.ax(4,subpanel).XLabel.Position(2) = -33;
- hd.collage.ax(5,subpanel).XLabel.Position(2) = -37;
- end
- hd.collage.ax(4,1).YLabel.Position(1) = -58;
- hd.collage.ax(5,1).YLabel.Position(1) = -46;
-
- % Reformat X axis on panel e:
- for subpanel = 1:3
- hd.collage.ax(5,subpanel).XLim(2) = 1.35;
- hd.collage.ax(5,subpanel).XTick = [.1 1];
- end
-
- % Reposition and reformat titles on panel d:
- for subpanel = 1:3
- hd.collage.ax(4,subpanel).Title.Units = 'pixels';
- hd.collage.ax(4,subpanel).Title.Position(2) = hd.collage.ax(4,subpanel).Position(4) + 25;
- hd.collage.ax(4,subpanel).Title.FontWeight = 'bold';
- end
-
- % Overwrite X axis ticks and tick labels on panel d (frequency dependence of gain):
- [hd.collage.ax(4,:).XTick] = deal([.02 .05 .1]);
- [hd.collage.ax(4,:).XTickLabel] = deal({'0.02','0.05','0.10'});
- % Overwrite Y axis ticks and tick labels on panel d (frequency dependence of gain):
- [hd.collage.ax(4,:).YTick] = deal([0 .05 .1]);
- [hd.collage.ax(4,:).YTickLabel] = deal({'0','0.05','0.10'});
-
- % Display panel lettering:
- hd.collage.ax(6,1) = axes;
- hd.collage.ax(6,1).Color = 'none';
- hd.collage.ax(6,1).Position = [0 0 1 1];
- hd.collage.ax(6,1).Units = 'pixels';
- hd.collage.ax(6,1).Visible = 'off';
- hold on
- hd.collage.lt(1) = text(hd.collage.ax(1,1).Position(1) - 0 , ...
- hd.collage.ax(1,1).Position(2) + hd.collage.ax(1,1).Position(4) + 20, ...
- 'a', 'Units', 'pixels');
- hd.collage.lt(2) = text(hd.collage.ax(2,1).Position(1) - 0, ...
- hd.collage.ax(2,1).Position(2) + hd.collage.ax(2,1).Position(4) + 20, ...
- 'b', 'Units', 'pixels');
- hd.collage.lt(3) = text(hd.collage.ax(3,1).Position(1) - 10, ...
- hd.collage.ax(3,1).Position(2) + hd.collage.ax(3,1).Position(4) - 28 + 30, ...
- 'c', 'Units', 'pixels');
- hd.collage.lt(4) = text(hd.collage.ax(4,1).Position(1) - 85, ...
- hd.collage.ax(4,1).Position(2) + hd.collage.ax(4,1).Position(4) + 30 + 15, ...
- 'd', 'Units', 'pixels');
- hd.collage.lt(5) = text(hd.collage.ax(5,1).Position(1) - 85, ...
- hd.collage.ax(5,1).Position(2) + hd.collage.ax(5,1).Position(4) + 30, ...
- 'e', 'Units', 'pixels');
- hold off
- % [hd.collage.lt(2:5).FontSize] = deal(15);
- % [hd.collage.lt(2:5).FontWeight] = deal('bold');
- [hd.collage.lt(:).FontSize] = deal(15);
- [hd.collage.lt(:).FontWeight] = deal('bold');
-
- end
- function [handle,data,stim] = gainAnalysisArea(varargin)
- % GAINANALYSIS and all subfunctions were written by Florian Alexander
- % Dehmelt in 2018. This is version 20180924, gainAnalysisArea20180924.m.
- % Apologies for the lack of polish (and comments) at this point.
- %
- % All input arguments are optional:
- %
- % Resolution - scalar, indicating your choice of resolution for the
- % rendering of the spherical surface as part % of the
- % figures. Actual data fitting is completely unaffected.
- % CamAzimuth - scalar, horizontal angle at which sphere is shown
- % CamElevation - scalar, vertical angle at which sphere is shown
- % Normalisation - options: nothing, or 'mean gain per hemisphere',
- % or 'max gain per hemisphere',
- % or 'ratio of mean gains per hemisphere'
- %
- % Output arguments are:
- %
- % handle - structure containing handles to all graphics objects
- % (figures, axes, plots, decorations)
- % data - structure containing pre-processed data, with those fields:
- % .left - numerical array of means of OKR gain for the left eye
- % .right - numerical array of means of OKR gain for the right eye
- % .both - numerical array of mean of OKR gain means across both eyes
- % datafit - structure containing the fits to data, with following fields: % removed!
- % .centre - 2x2 numerical array of azimuths (1st row) and elevation
- % angles (2nd row) of the two individial von Mises-Fisher
- % functions fit to data (1st column = 1st function, etc.).
- % These are NOT the peaks of the actual fit (= sum of both
- % functions)! I still have to implement that feature.
- % .value - 36x1 numerical array containing the value of the actual fit
- % (= sum of both functions) at the locations sampled by data
- % .param - 1x9 numerical array containing all best-fit parameters
- % (offset, amplitude1, azimuth1, elevation1, concentration1,
- % amplitude2, azimuth2, elevation2, concentration2)
- %
- % The recommended default is calling "hd = gainAnalysisArea20181217();".
- %
- % To specify camera position before analysis, call the function as follows:
- % hd = gainAnalysisArea20181217(200,'CamAzimuth',-30,'CamElevation',15);
- %
- % To edit camera position post-hoc, adjust and execute the following lines:
- % az = 0; el = 0; % substitute your desired camera coordinates here
- % [hd.gain.ax(1:6).CameraPosition] = ...
- % deal([cosd(az)*cosd(el),sind(az)*cosd(el),sind(el)]/19.0526);
- % figure(21)
- %
- % To normalise gains on each hemisphere, call the function as follows:
- % hd = gainAnalysisArea20181217('Normalisation','mean gain per hemisphere');
-
- % MANAGE VARIABLE INPUT
- p = inputParser;
-
- valid.resolution = @(x) isnumeric(x) && numel(x)==1 && x>0;
- valid.camazimuth = @(x) isnumeric(x) && numel(x)==1;
- valid.camelevation = @(x) isnumeric(x) && numel(x)==1;
- valid.normalisation = @(x) ischar(x);
-
- default.resolution = 200;
- default.camazimuth = -0;
- default.camelevation = 45;
- default.normalisation = '';
-
- addOptional(p, 'Resolution', default.resolution, valid.resolution);
- addOptional(p, 'CamAzimuth', default.camazimuth, valid.camazimuth);
- addOptional(p, 'CamElevation', default.camelevation, valid.camelevation);
- addOptional(p, 'Normalisation', default.normalisation, valid.normalisation);
- parse(p,varargin{:});
-
- resolution = p.Results.Resolution;
- cam.azimuth = p.Results.CamAzimuth;
- cam.elevation = p.Results.CamElevation;
- normalisation = p.Results.Normalisation;
-
- % GENERAL SETTINGS
- warning('off','MATLAB:interp1:UsePCHIP')
- % The line above deactivates the following cbrewer warning:
- % Warning: INTERP1(...,'CUBIC') will change in a future release. Use INTERP1(...,'PCHIP') instead.
- % > In interp1>sanitycheckmethod (line 265)
- % In interp1>parseinputs (line 401)
- % In interp1 (line 80)
- % In interpolate_cbrewer (line 31)
- % In cbrewer (line 101)
- % In heatmap20180823 (line 21)
- % Identify which stimulus entries correspond to the disk-mask stimuli.
- datarange = 2:43;
- % !!! IMPORTANT !!! These are the Excel row numbers, not stimulus types.
-
- % close all
-
-
- % IDENTIFY DATA FOLDER
- file.folder = '..\..\data\Fig5 size data\Data A Series (A-Series)\';
-
-
- % READ STIMULUS INFORMATION
- % file.folder = 'C:\Users\fdehmelt\Dropbox\ThinkPad Dropbox\MATLAB\201808 SphereAnalysis\Sample Data\D series\';
- file.stimulus = 'Stimulus.xlsx';
- [~,stim.tableconvention,~] = xlsread([file.folder,file.stimulus],'C1:C1');
- stim.azimuth = xlsread([file.folder,file.stimulus],['C',num2str(datarange(1)),':C',num2str(datarange(end))]);
- stim.elevation = xlsread([file.folder,file.stimulus],['D',num2str(datarange(1)),':D',num2str(datarange(end))]);
- stim.radius = xlsread([file.folder,file.stimulus],['E',num2str(datarange(1)),':E',num2str(datarange(end))]);
- % Stimulus tables may use CW convention; default code uses CCW.
- switch stim.tableconvention{:}
- case 'azimuth (CCW)'
- case 'azimuth (CW)'
- stim.azimuth = -stim.azimuth;
- otherwise
- error(['Field C1 of Stimulus.xlsx should contain either ' ...
- 'string ''azimuth (CCW)'' or string ''azimuth (CW)''.'])
- end
-
- stimcentre = [stim.azimuth, stim.elevation];
-
-
-
- % READ DATA
- % Query user to manually select the main data file. Other files have
- % standard names and are expected to be in the same folder.
- [mfilefolder,~,~] = fileparts(mfilename('fullpath'));
- % [file.result,file.folder] = uigetfile([mfilefolder,'\*.mat']);
- file.result = 'result_20180228_090646.mat';
- load([file.folder,file.result],'result')
-
- % Discard unwanted trials, based on .xlsx documenting manual assessment.
- result = discardTrial(result,file.folder); %#ok<NODEF>
-
- % % 20181217: optionally, normalise gain values by mean gain across same hemisphere
- % switch normalisation
- % case 'mean gain per hemisphere'
- % meangainposazimuth = mean([result(:,:,stim.azimuth>0,:).gain]);
- % meangainnegazimuth = mean([result(:,:,stim.azimuth<0,:).gain]);
- % result(:,:,stim.azimuth>0,:).gain = result(:,:,stim.azimuth>0,:).gain / meangainposazimuth;
- % result(:,:,stim.azimuth<0,:).gain = result(:,:,stim.azimuth<0,:).gain / meangainnegazimuth;
- % end
-
- % Pool gain data in various ways (this is a significant subfunction!).
- gain = poolGainData(result);
-
- % Select the three types of data to be plotted.
- data.left = gain.mean.FR{1};
- data.right = gain.mean.FR{2};
- % both = gain.mean.FER;
- data.both = mean([data.left,data.right],2);
-
- % 20181217: optionally, normalise gain values by mean gain across same hemisphere
- hemisphere1 = datarange(1:end/2) - 1; % The -1 accounts for the Excel title row.
- hemisphere2 = datarange(end/2+1:end) - 1;
- switch normalisation
- case 'mean gain per hemisphere'
- data.left(hemisphere1) = data.left(hemisphere1) / mean(data.left(hemisphere1));
- data.left(hemisphere2) = data.left(hemisphere2) / mean(data.left(hemisphere2));
- data.right(hemisphere1) = data.right(hemisphere1) / mean(data.right(hemisphere1));
- data.right(hemisphere2) = data.right(hemisphere2) / mean(data.right(hemisphere2));
- data.both(hemisphere1) = data.both(hemisphere1) / mean(data.both(hemisphere1));
- data.both(hemisphere2) = data.both(hemisphere2) / mean(data.both(hemisphere2));
- case 'max gain per hemisphere'
- data.left(hemisphere1) = data.left(hemisphere1) / max(data.left(hemisphere1));
- data.left(hemisphere2) = data.left(hemisphere2) / max(data.left(hemisphere2));
- data.right(hemisphere1) = data.right(hemisphere1) / max(data.right(hemisphere1));
- data.right(hemisphere2) = data.right(hemisphere2) / max(data.right(hemisphere2));
- data.both(hemisphere1) = data.both(hemisphere1) / max(data.both(hemisphere1));
- data.both(hemisphere2) = data.both(hemisphere2) / max(data.both(hemisphere2));
- case 'ratio of mean gains per hemisphere'
- data.left(hemisphere1) = data.left(hemisphere1) / ...
- (mean(data.left(hemisphere1)) / mean(data.left(hemisphere2)));
- data.left(hemisphere2) = data.left(hemisphere2) / ...
- (mean(data.left(hemisphere2) / mean(data.left(hemisphere1))));
- data.right(hemisphere1) = data.right(hemisphere1) / ...
- (mean(data.right(hemisphere1)) / mean(data.right(hemisphere2)));
- data.right(hemisphere2) = data.right(hemisphere2) / ...
- (mean(data.right(hemisphere2)) / mean(data.right(hemisphere1)));
- data.both(hemisphere1) = data.both(hemisphere1) / ...
- (mean(data.both(hemisphere1)) / mean(data.both(hemisphere2)));
- data.both(hemisphere2) = data.both(hemisphere2) / ...
- (mean(data.both(hemisphere2)) / mean(data.both(hemisphere1)));
- end
-
-
- % CHOOSE SUBSET OF DATA TO ANALYSE, AND PROCEED TO ANALYSE IT
- choicelist = {'left','both','right'};
- for choice = 1:numel(choicelist)
-
- switch choicelist{choice}
- case 'right'
- response = data.right(datarange-1); % -1 is offset by Excel title row
- case 'left'
- response = data.left(datarange-1);
- case 'both'
- response = data.both(datarange-1);
- otherwise
- error(['Input argument must be one of the following strings: ' ...
- '''left'', ''right'', or ''both''.'])
- end
- predictor = stimcentre;
- % compute cartesian coordinates of sphere, and its colour data values
- [x,y,z] = sphere(resolution);
- % [x,y,z] = sphere(250);
- [azimuth,elevation,radius] = car2geo(x,y,z,'CCW');
- % predictor = [azimuth(:),elevation(:),radius(:)];
- % datafit.matrix{choice} = reshape(vonmisesfisher9param(datafit.param,predictor),size(radius));
- % set 3D fish parameters
- scale = .1;
- yaw = 180;
- pitch = 0;
- roll = 0;
- % set decorative arc parameters; compute their cartesian coordinates
- equator.base = -1:.01:1;
- equator.x = [equator.base, fliplr(equator.base)];
- equator.y = [sqrt(1-equator.base.^2), -sqrt(1-equator.base.^2)];
- equator.z = 0*ones(1,numel(equator.x));
- meridian.x = equator.x;
- meridian.y = equator.z;
- meridian.z = equator.y;
- aura.x = equator.z;
- aura.y = equator.y;
- aura.z = equator.x;
- [sample.x,sample.y,sample.z] = ...
- geo2car(stimcentre(:,1),stimcentre(:,2),ones(size(stimcentre,1),1),'CCW');
-
-
- [uniquepair,firstoccurrence,~] = unique([stim.azimuth,stim.elevation],'rows');
- % MATLAB reorders unique pairs by azimuth (increasing). This does not
- % usually match the true order in which stimuli were presented! The
- % following two lines recovers this true order, and restores it.
- [~,trueorder] = sort(firstoccurrence);
- uniquepair = uniquepair(trueorder,:);
- stim.uniquecentre.azimuth = uniquepair(:,1);
- stim.uniquecentre.elevation = uniquepair(:,2);
-
- [uniquecentre.x,uniquecentre.y,uniquecentre.z] = ...
- geo2car(uniquepair(:,1),uniquepair(:,2),ones(size(uniquepair,1),1),'CCW');
- stim.uniqueradius = unique(stim.radius);
- stim.uniquesteradian = stim.uniqueradius*(2*pi)/360;
- stim.uniquearea = stim.uniquesteradian/(2*pi);
-
- fontsize = 14;
-
- if choice == 1
-
- panel = 1;
- % hd.location.fg = figure();
- % hd.location.fg.Position = [50 300 1200 300];
- % hd.location.fg.Color = [1 1 1];
- hd.gain.ax(1) = axes;
- hd.gain.ax(1).Position = [.025 .14 .20 .8];
- hold on
- hd.gain.ob(panel,1) = surf(x,y,z);
- hd.gain.ob(panel,1).FaceAlpha = 0*.25;
- hd.gain.ob(panel,1).FaceColor = 1*[1 1 1];
- hd.gain.ob(panel,1).EdgeColor = 'none';
- hd.gain.ob(panel,2:4) = plot3dFish(scale,yaw,pitch,roll);
- hd.gain.ob(panel,5) = plot3(equator.x,equator.y,equator.z,'k','LineWidth',2);
- hd.gain.ob(panel,6) = plot3(meridian.x,meridian.y,meridian.z,'--k');
- hd.gain.ob(panel,7) = plot3(aura.x,aura.y,aura.z,':k');
-
- % colour.centre = [1 0 0; 0 1 0; 0 0 1; 1 1 0; 1 0 1; 0 1 1];
- colour.centre = [1 .447 .741; .85 .325 .098; .929 .694 .125; ...
- .494 .184 .556; .466 .6740 .188; .301 .745 .933];
- colour.purple = cbrewer('seq','Purples',100);
- colour.green = cbrewer('seq','Greens',100);
- colour.centre = [colour.purple(round(end*.8),:); ...
- colour.purple(round(end*.3),:); ...
- colour.purple(round(end*.5),:); ...
- colour.green(round(end*.8),:); ...
- colour.green(round(end*.3),:); ...
- colour.green(round(end*.5),:)];
-
- for origin = 1:numel(uniquecentre.x)
- hd.gain.ob(panel,7+origin) = scatter3(1.03*uniquecentre.x(origin), ...
- 1.03*uniquecentre.y(origin), ...
- 1.03*uniquecentre.z(origin), ...
- 120,'Marker','o');
- hd.gain.ob(panel,7+origin).MarkerFaceColor = colour.centre(origin,:);
- hd.gain.ob(panel,7+origin).MarkerEdgeColor = .6*[1 1 1];
- end
- hold off
- % % hd.gain.ob(panel,8).Color = [1 1 1];
- % hd.gain.ob(panel,1).FaceAlpha = .5;
- % hd.gain.ob(panel,1).FaceColor = hd.gain.ob(panel,1).EdgeColor;
- axis square
-
- hd.gain.ax(panel).XAxis.Visible = 'off';
- hd.gain.ax(panel).YAxis.Visible = 'off';
- hd.gain.ax(panel).ZAxis.Visible = 'off';
- % hd.gain.ax(panel).Title.String = 'stimulus centres';
-
- hd.gain.ax(panel).CameraPosition = [11.5866 -8.9228 9.2809];
- hd.gain.ax(panel).CameraViewAngle = 9.25;
-
- hd.gain.ax(4) = axes;
- hd.gain.ax(4).Position = [0 0 1 1];
- hd.gain.ax(4).Color = 'none';
- hd.gain.ax(4).Visible = 'off';
-
- hd.gain.ob(4,1) = text(.02,.93,'a','Units','normalized');
- hd.gain.ob(4,2) = text(.25,.93,'b','Units','normalized');
- [hd.gain.ob(4,1:2).FontWeight] = deal('bold');
- [hd.gain.ob(4,1:2).FontSize] = deal(14);
-
- end
- input.letter = 'e';
- panel = choice+1;
- croppeddata(:,1) = response(1:7);
- croppeddata(:,2) = response(8:14);
- croppeddata(:,3) = response(15:21);
- croppeddata(:,4) = response(22:28);
- croppeddata(:,5) = response(29:35);
- croppeddata(:,6) = response(36:42);
- hd.gain.ax(panel) = axes;
- hd.gain.ax(panel).Position = [.33+(panel-2)*.22 .26 .20 .55];
- hold on
- for origin = 1:size(croppeddata,2)
- hd.gain.ob(panel,origin) = plot(stim.uniquearea,croppeddata(:,origin));
- hd.gain.ob(panel,origin).Color = colour.centre(origin,:);
- hd.gain.ob(panel,origin).LineWidth = 2;
- end
- hold off
-
- hd.gain.ax(panel).TitleFontWeight = 'normal';
- hd.gain.ax(panel).TitleFontSizeMultiplier = 1;
- hd.gain.ax(panel).XLabel.String = 'stimulus size';
- if choice == 1
- hd.gain.ax(panel).YLabel.String = 'OKR gain';
- hd.gain.ax(panel).YLabel.Units = 'normalized';
- hd.gain.ax(2).YLabel.Position = [-.2 .5 0];
- else
- hd.gain.ax(panel).YAxis.Visible = 'off';
- end
-
- switch choicelist{choice}
- case 'left'
- hd.gain.ax(panel).Title.String = 'left eye';
- case 'right'
- hd.gain.ax(panel).Title.String = 'right eye';
- case 'both'
- % hd.gain.ax(panel).Title.String = 'mean of both eyes';
- hd.gain.ax(panel).Title.String = 'mean';
- end
-
- hd.gain.ax(panel).FontSize = fontsize;
-
- if panel > 1
- hd.gain.ax(panel).XScale = 'log';
- hd.gain.ax(panel).YScale = 'lin';
- hd.gain.ax(panel).XLim = [.9*min(hd.gain.ob(panel,1).XData), ...
- max(hd.gain.ob(panel,1).XData)];
- switch normalisation
- case 'max gain per hemisphere'
- hd.gain.ax(panel).YLim = [0, 1];
- case 'mean gain per hemisphere'
- hd.gain.ax(panel).YLim = [0, max([data.left;data.right;data.both])];
- case 'ratio of mean gains per hemisphere'
- hd.gain.ax(panel).YLim = [0, max([data.left;data.right;data.both])];
- otherwise
- hd.gain.ax(panel).YLim = [0, .5];
- end
- hd.gain.ax(panel).TickLength = [.03 .075];
- hd.gain.ax(panel).YGrid = 'on';
- hd.gain.ax(panel).Title.Units = 'normalized';
- hd.gain.ax(panel).Title.Position = [.5 1.1 0];
- end
-
- end
-
- cam.radius = 19.0526; % MATLAB automatically reverts to this values while rotating, anyway. Might as well choose it as default.
- [cam.x,cam.y,cam.z] = geo2car(cam.azimuth,cam.elevation,cam.radius,'CCW');
- hd.gain.ax(1).CameraPosition = [cam.x cam.y cam.z];
- hd.gain.ax(1).CameraTarget = [0 0 0];
- hd.gain.ax(1).CameraUpVector = [0 0 1];
- % hd.gain.ax(panel).CameraViewAngle = 10.134;
- hd.gain.ax(1).CameraViewAngle = 9;
-
-
- % Collect output arguments, unless already done (e.g., "datafit").
- % data.left = left;
- % data.right = right;
- % data.both = both;
- handle = hd;
-
- end
- function [handle,data,stim] = gainAnalysisFreq(varargin)
- % GAINANALYSIS and all subfunctions were written by Florian Alexander
- % Dehmelt in 2018. This is version 20180927, gainAnalysisFrequency20180924.m.
- % Apologies for the lack of polish (and comments) at this point.
- %
- % All input arguments are optional:
- %
- % Resolution - scalar, indicating your choice of resolution for the
- % rendering of the spherical surface as part % of the
- % figures. Actual data fitting is completely unaffected.
- % CamAzimuth - scalar, horizontal angle at which sphere is shown
- % CamElevation - scalar, vertical angle at which sphere is shown
- % Normalisation - options: nothing, or 'mean gain per hemisphere',
- % or 'max gain per hemisphere',
- % or 'ratio of mean gains per hemisphere'
- %
- % Output arguments are:
- %
- % handle - structure containing handles to all graphics objects
- % (figures, axes, plots, decorations)
- % data - structure containing pre-processed data, with those fields:
- % .left - numerical array of means of OKR gain for the left eye
- % .right - numerical array of means of OKR gain for the right eye
- % .both - numerical array of mean of OKR gain means across both eyes
- % datafit - structure containing the fits to data, with following fields: % removed!
- % .centre - 2x2 numerical array of azimuths (1st row) and elevation
- % angles (2nd row) of the two individial von Mises-Fisher
- % functions fit to data (1st column = 1st function, etc.).
- % These are NOT the peaks of the actual fit (= sum of both
- % functions)! I still have to implement that feature.
- % .value - 36x1 numerical array containing the value of the actual fit
- % (= sum of both functions) at the locations sampled by data
- % .param - 1x9 numerical array containing all best-fit parameters
- % (offset, amplitude1, azimuth1, elevation1, concentration1,
- % amplitude2, azimuth2, elevation2, concentration2)
- %
- % The recommended default is calling "hd = gainAnalysis20180911(200);".
- %
- % To specify camera position before analysis, call the function as follows:
- % hd = gainAnalysis20180911(200,'CamAzimuth',-30,'CamElevation',15);
- %
- % To edit camera position post-hoc, adjust and execute the following lines:
- % az = 0; el = 0; % substitute your desired camera coordinates here
- % [hd.gain.ax(1:6).CameraPosition] = ...
- % deal([cosd(az)*cosd(el),sind(az)*cosd(el),sind(el)]/19.0526);
- % figure(21)
-
- % MANAGE VARIABLE INPUT
- p = inputParser;
-
- valid.resolution = @(x) isnumeric(x) && numel(x)==1 && x>0;
- valid.camazimuth = @(x) isnumeric(x) && numel(x)==1;
- valid.camelevation = @(x) isnumeric(x) && numel(x)==1;
- valid.normalisation = @(x) ischar(x);
-
- default.resolution = 200;
- default.camazimuth = -0;
- default.camelevation = 45;
- default.normalisation = '';
-
- addOptional(p, 'Resolution', default.resolution, valid.resolution);
- addOptional(p, 'CamAzimuth', default.camazimuth, valid.camazimuth);
- addOptional(p, 'CamElevation', default.camelevation, valid.camelevation);
- addOptional(p, 'Normalisation', default.normalisation, valid.normalisation);
- parse(p,varargin{:});
-
- resolution = p.Results.Resolution;
- cam.azimuth = p.Results.CamAzimuth;
- cam.elevation = p.Results.CamElevation;
- normalisation = p.Results.Normalisation;
-
-
- % GENERAL SETTINGS
- warning('off','MATLAB:interp1:UsePCHIP')
- % The line above deactivates the following cbrewer warning:
- % Warning: INTERP1(...,'CUBIC') will change in a future release. Use INTERP1(...,'PCHIP') instead.
- % > In interp1>sanitycheckmethod (line 265)
- % In interp1>parseinputs (line 401)
- % In interp1 (line 80)
- % In interpolate_cbrewer (line 31)
- % In cbrewer (line 101)
- % In heatmap20180823 (line 21)
- % Identify which stimulus entries correspond to the disk-mask stimuli.
- datarange = 2:43;
- % !!! IMPORTANT !!! These are the Excel row numbers, not stimulus types.
-
- % close all
-
-
- % READ DATA
- % Query user to manually select the main data file. Other files have
- % standard names and are expected to be in the same folder.
- [mfilefolder,~,~] = fileparts(mfilename('fullpath'));
- % [file.result,file.folder] = uigetfile([mfilefolder,'\*.mat']);
- file.result = 'result_20180305_124441.mat';
- file.folder = '..\..\data\Fig5 frequency data\Data V Series (V-Serie)\';
- load([file.folder,file.result],'result')
-
- % Discard unwanted trials, based on .xlsx documenting manual assessment.
- result = discardTrial(result,file.folder); %#ok<NODEF>
- result = zeroTrial(result,file.folder); %#ok<NODEF>
-
- % Pool gain data in various ways (this is a significant subfunction!).
- gain = poolGainData(result);
-
- % Select the three types of data to be plotted.
- data.left = gain.mean.FR{1};
- data.right = gain.mean.FR{2};
- % both = gain.mean.FER;
- data.both = mean([data.left,data.right],2);
-
- % 20181217: optionally, normalise gain values by mean gain across same hemisphere
- hemisphere1 = datarange(1:end/2) - 1; % The -1 accounts for the Excel title row.
- hemisphere2 = datarange(end/2+1:end) - 1;
- switch normalisation
- case 'mean gain per hemisphere'
- data.left(hemisphere1) = data.left(hemisphere1) / mean(data.left(hemisphere1));
- data.left(hemisphere2) = data.left(hemisphere2) / mean(data.left(hemisphere2));
- data.right(hemisphere1) = data.right(hemisphere1) / mean(data.right(hemisphere1));
- data.right(hemisphere2) = data.right(hemisphere2) / mean(data.right(hemisphere2));
- data.both(hemisphere1) = data.both(hemisphere1) / mean(data.both(hemisphere1));
- data.both(hemisphere2) = data.both(hemisphere2) / mean(data.both(hemisphere2));
- case 'max gain per hemisphere'
- data.left(hemisphere1) = data.left(hemisphere1) / max(data.left(hemisphere1));
- data.left(hemisphere2) = data.left(hemisphere2) / max(data.left(hemisphere2));
- data.right(hemisphere1) = data.right(hemisphere1) / max(data.right(hemisphere1));
- data.right(hemisphere2) = data.right(hemisphere2) / max(data.right(hemisphere2));
- data.both(hemisphere1) = data.both(hemisphere1) / max(data.both(hemisphere1));
- data.both(hemisphere2) = data.both(hemisphere2) / max(data.both(hemisphere2));
- case 'ratio of mean gains per hemisphere'
- data.left(hemisphere1) = data.left(hemisphere1) / ...
- (mean(data.left(hemisphere1)) / mean(data.left(hemisphere2)));
- data.left(hemisphere2) = data.left(hemisphere2) / ...
- (mean(data.left(hemisphere2) / mean(data.left(hemisphere1))));
- data.right(hemisphere1) = data.right(hemisphere1) / ...
- (mean(data.right(hemisphere1)) / mean(data.right(hemisphere2)));
- data.right(hemisphere2) = data.right(hemisphere2) / ...
- (mean(data.right(hemisphere2)) / mean(data.right(hemisphere1)));
- data.both(hemisphere1) = data.both(hemisphere1) / ...
- (mean(data.both(hemisphere1)) / mean(data.both(hemisphere2)));
- data.both(hemisphere2) = data.both(hemisphere2) / ...
- (mean(data.both(hemisphere2)) / mean(data.both(hemisphere1)));
- end
-
- % READ STIMULUS INFORMATION
- file.stimulus = 'Stimulus.xlsx';
- [~,stim.tableconvention,~] = xlsread([file.folder,file.stimulus],'C1:C1');
- stim.azimuth = xlsread([file.folder,file.stimulus],['C',num2str(datarange(1)),':C',num2str(datarange(end))]);
- stim.elevation = xlsread([file.folder,file.stimulus],['D',num2str(datarange(1)),':D',num2str(datarange(end))]);
- stim.solidangle = xlsread([file.folder,file.stimulus],['E',num2str(datarange(1)),':E',num2str(datarange(end))]);
- stim.spatialfreq = xlsread([file.folder,file.stimulus],['F',num2str(datarange(1)),':F',num2str(datarange(end))]);
- stim.tempfreq = xlsread([file.folder,file.stimulus],['G',num2str(datarange(1)),':G',num2str(datarange(end))]);
- % Stimulus tables may use CW convention; default code uses CCW.
- switch stim.tableconvention{:}
- case 'azimuth (CCW)'
- case 'azimuth (CW)'
- stim.azimuth = -stim.azimuth;
- otherwise
- error(['Field C1 of Stimulus.xlsx should contain either ' ...
- 'string ''azimuth (CCW)'' or string ''azimuth (CW)''.'])
- end
-
- stimcentre = [stim.azimuth, stim.elevation];
-
- % CHOOSE SUBSET OF DATA TO ANALYSE, AND PROCEED TO ANALYSE IT
- choicelist = {'left','both','right'};
- for choice = 1:numel(choicelist)
-
- switch choicelist{choice}
- case 'right'
- response = data.right(datarange-1); % -1 is offset by Excel title row
- case 'left'
- response = data.left(datarange-1);
- case 'both'
- response = data.both(datarange-1);
- otherwise
- error(['Input argument must be one of the following strings: ' ...
- '''left'', ''right'', or ''both''.'])
- end
- predictor = stimcentre;
- % compute cartesian coordinates of sphere, and its colour data values
- [x,y,z] = sphere(resolution);
- % [x,y,z] = sphere(250);
- [azimuth,elevation,radius] = car2geo(x,y,z,'CCW');
- % predictor = [azimuth(:),elevation(:),radius(:)];
- % datafit.matrix{choice} = reshape(vonmisesfisher9param(datafit.param,predictor),size(radius));
- % set 3D fish parameters
- scale = .1;
- yaw = 180;
- pitch = 0;
- roll = 0;
- % set decorative arc parameters; compute their cartesian coordinates
- equator.base = -1:.01:1;
- equator.x = [equator.base, fliplr(equator.base)];
- equator.y = [sqrt(1-equator.base.^2), -sqrt(1-equator.base.^2)];
- equator.z = 0*ones(1,numel(equator.x));
- meridian.x = equator.x;
- meridian.y = equator.z;
- meridian.z = equator.y;
- aura.x = equator.z;
- aura.y = equator.y;
- aura.z = equator.x;
- [sample.x,sample.y,sample.z] = ...
- geo2car(stimcentre(:,1),stimcentre(:,2),ones(size(stimcentre,1),1),'CCW');
-
-
- [uniquepair,firstoccurrence,~] = unique([stim.azimuth,stim.elevation],'rows');
- % MATLAB reorders unique pairs by azimuth (increasing). This does not
- % usually match the true order in which stimuli were presented! The
- % following two lines recovers this true order, and restores it.
- [~,trueorder] = sort(firstoccurrence);
- uniquepair = uniquepair(trueorder,:);
- stim.uniquecentre.azimuth = uniquepair(:,1);
- stim.uniquecentre.elevation = uniquepair(:,2);
-
- [uniquecentre.x,uniquecentre.y,uniquecentre.z] = ...
- geo2car(uniquepair(:,1),uniquepair(:,2),ones(size(uniquepair,1),1),'CCW');
- stim.uniquespatialfreq = unique(stim.spatialfreq);
- stim.uniquetempfreq = unique(stim.tempfreq);
-
- stim.uniquesolidangle = unique(stim.solidangle);
- stim.uniquesteradian = stim.uniquesolidangle*(2*pi)/360;
- stim.uniquearea = stim.uniquesteradian/(2*pi);
-
- fontsize = 14;
-
- if choice == 1
-
- panel = 1;
- hd.location.fg = figure();
- hd.location.fg.Position = [50 300 1200 300];
- hd.location.fg.Color = [1 1 1];
- hd.gain.ax(1) = axes;
- hd.gain.ax(1).Position = [.025 .14 .20 .8];
- hold on
- hd.gain.ob(panel,1) = surf(x,y,z);
- hd.gain.ob(panel,1).FaceAlpha = 0*.25;
- hd.gain.ob(panel,1).FaceColor = 1*[1 1 1];
- hd.gain.ob(panel,1).EdgeColor = 'none';
- hd.gain.ob(panel,2:4) = plot3dFish(scale,yaw,pitch,roll);
- hd.gain.ob(panel,5) = plot3(equator.x,equator.y,equator.z,'k','LineWidth',2);
- hd.gain.ob(panel,6) = plot3(meridian.x,meridian.y,meridian.z,'--k');
- hd.gain.ob(panel,7) = plot3(aura.x,aura.y,aura.z,':k');
-
- % colour.centre = [1 0 0; 0 1 0; 0 0 1; 1 1 0; 1 0 1; 0 1 1];
- colour.centre = [1 .447 .741; .85 .325 .098; .929 .694 .125; ...
- .494 .184 .556; .466 .6740 .188; .301 .745 .933];
- colour.purple = cbrewer('seq','Purples',100);
- colour.green = cbrewer('seq','Greens',100);
- colour.centre = [colour.purple(round(end*.8),:); ...
- colour.purple(round(end*.3),:); ...
- colour.purple(round(end*.5),:); ...
- colour.green(round(end*.8),:); ...
- colour.green(round(end*.3),:); ...
- colour.green(round(end*.5),:)];
-
- for origin = 1:numel(uniquecentre.x)
- hd.gain.ob(panel,7+origin) = scatter3(1.03*uniquecentre.x(origin), ...
- 1.03*uniquecentre.y(origin), ...
- 1.03*uniquecentre.z(origin), ...
- 120,'Marker','o');
- hd.gain.ob(panel,7+origin).MarkerFaceColor = colour.centre(origin,:);
- hd.gain.ob(panel,7+origin).MarkerEdgeColor = .6*[1 1 1];
- end
- hold off
- % % hd.gain.ob(panel,8).Color = [1 1 1];
- % hd.gain.ob(panel,1).FaceAlpha = .5;
- % hd.gain.ob(panel,1).FaceColor = hd.gain.ob(panel,1).EdgeColor;
- axis square
-
- hd.gain.ax(panel).XAxis.Visible = 'off';
- hd.gain.ax(panel).YAxis.Visible = 'off';
- hd.gain.ax(panel).ZAxis.Visible = 'off';
- % hd.gain.ax(panel).Title.String = 'stimulus centres';
-
- hd.gain.ax(panel).CameraPosition = [11.5866 -8.9228 9.2809];
- hd.gain.ax(panel).CameraViewAngle = 9.25;
-
- hd.gain.ax(4) = axes;
- hd.gain.ax(4).Position = [0 0 1 1];
- hd.gain.ax(4).Color = 'none';
- hd.gain.ax(4).Visible = 'off';
-
- hd.gain.ob(4,1) = text(.02,.93,'c','Units','normalized');
- hd.gain.ob(4,2) = text(.25,.93,'d','Units','normalized');
- [hd.gain.ob(4,1:2).FontWeight] = deal('bold');
- [hd.gain.ob(4,1:2).FontSize] = deal(14);
-
- end
- input.letter = 'e';
- panel = choice+1;
- croppeddata(:,1) = response(1:7);
- croppeddata(:,2) = response(8:14);
- croppeddata(:,3) = response(15:21);
- croppeddata(:,4) = response(22:28);
- croppeddata(:,5) = response(29:35);
- croppeddata(:,6) = response(36:42);
- hd.gain.ax(panel) = axes;
- hd.gain.ax(panel).Position = [.33+(panel-2)*.22 .26 .20 .55];
- hold on
- for origin = 1:size(croppeddata,2)
- % hd.gain.ob(panel,origin) = plot(stim.uniquetempfreq,croppeddata(:,origin));
- hd.gain.ob(panel,origin) = plot(stim.uniquespatialfreq,croppeddata(:,origin));
- hd.gain.ob(panel,origin).Color = colour.centre(origin,:);
- hd.gain.ob(panel,origin).LineWidth = 2;
- end
- hold off
-
- hd.gain.ax(panel).TitleFontWeight = 'normal';
- hd.gain.ax(panel).TitleFontSizeMultiplier = 1;
- % hd.gain.ax(panel).XLabel.String = 'temporal frequency';
- hd.gain.ax(panel).XLabel.String = 'spatial frequency';
- if choice == 1
- hd.gain.ax(panel).YLabel.String = 'OKR gain';
- hd.gain.ax(panel).YLabel.Units = 'normalized';
- hd.gain.ax(2).YLabel.Position = [-.2 .5 0];
- else
- hd.gain.ax(panel).YAxis.Visible = 'off';
- end
-
- switch choicelist{choice}
- case 'left'
- hd.gain.ax(panel).Title.String = 'left eye';
- case 'right'
- hd.gain.ax(panel).Title.String = 'right eye';
- case 'both'
- % hd.gain.ax(panel).Title.String = 'mean of both eyes';
- hd.gain.ax(panel).Title.String = 'mean';
- end
-
- hd.gain.ax(panel).FontSize = fontsize;
-
- if panel > 1
- hd.gain.ax(panel).XScale = 'log';
- hd.gain.ax(panel).YScale = 'lin';
- hd.gain.ax(panel).XLim = [.9*min(hd.gain.ob(panel,1).XData), ...
- max(hd.gain.ob(panel,1).XData)];
- switch normalisation
- case 'max gain per hemisphere'
- hd.gain.ax(panel).YLim = [0, 1];
- case 'mean gain per hemisphere'
- hd.gain.ax(panel).YLim = [0, max([data.left;data.right;data.both])];
- case 'ratio of mean gains per hemisphere'
- hd.gain.ax(panel).YLim = [0, max([data.left;data.right;data.both])];
- otherwise
- hd.gain.ax(panel).YLim = [0, max([data.left;data.right;data.both])];
- end
- hd.gain.ax(panel).TickLength = [.03 .075];
- hd.gain.ax(panel).YGrid = 'on';
- hd.gain.ax(panel).Title.Units = 'normalized';
- hd.gain.ax(panel).Title.Position = [.5 1.1 0];
- end
-
- end
-
- cam.radius = 19.0526; % MATLAB automatically reverts to this values while rotating, anyway. Might as well choose it as default.
- [cam.x,cam.y,cam.z] = geo2car(cam.azimuth,cam.elevation,cam.radius,'CCW');
- hd.gain.ax(1).CameraPosition = [cam.x cam.y cam.z];
- hd.gain.ax(1).CameraTarget = [0 0 0];
- hd.gain.ax(1).CameraUpVector = [0 0 1];
- % hd.gain.ax(panel).CameraViewAngle = 10.134;
- hd.gain.ax(1).CameraViewAngle = 9;
-
- % Collect output arguments, unless already done (e.g., "datafit").
- % data.left = left;
- % data.right = right;
- % data.both = both;
- handle = hd;
-
- end
- function gain = poolGainData(result)
- %POOLGAINDATA converts a raw result structure into plottable data by
- %pooling OKR gain data in various ways, and storing those results in a new
- %structure called "gain".
- % Pool data in various ways (e.g., .FR = pooled across all [F]ish and
- % all [R]epetitions, but with a separate array for each [E]ye).
- for stimtype = 1:size(result,3)
- gainpool(stimtype).FER = [result(:,:,stimtype,:).gain];
- for eye = 1:size(result,2)
- gainpool(stimtype).FR{eye} = [result(:,eye,stimtype,:).gain];
- end
- for fish = 1:size(result,1)
- gainpool(stimtype).ER{fish} = [result(fish,:,stimtype,:).gain];
- end
- for repetition = 1:size(result,4)
- gainpool(stimtype).FE{repetition} = [result(:,:,stimtype,repetition).gain];
- end
- for fish = 1:size(result,1)
- for eye = 1:size(result,2)
- gainpool(stimtype).R{fish,eye} = [result(fish,eye,stimtype,:).gain];
- end
- end
-
- end
- % Make sure that in following section, if a stimulus type is not present,
- % the array contain a NaN at that position, instead of a zero.
- nantemplate = NaN(numel(gainpool),1);
- gain.mean .FER = nantemplate;
- gain.median.FER = nantemplate;
- gain.std .FER = nantemplate;
- gain.sem .FER = nantemplate;
- for eye = 1:size(result,2)
- gain.mean .FR{eye} = nantemplate;
- gain.median.FR{eye} = nantemplate;
- gain.std .FR{eye} = nantemplate;
- gain.sem .FR{eye} = nantemplate;
- end
- for fish = 1:size(result,1)
- gain.mean .ER{fish} = nantemplate;
- gain.median.ER{fish} = nantemplate;
- gain.std .ER{fish} = nantemplate;
- gain.sem .ER{fish} = nantemplate;
- end
- for fish = 1:size(result,1)
- for eye = 1:size(result,2)
- gain.mean .R{fish,eye} = nantemplate;
- gain.median.R{fish,eye} = nantemplate;
- gain.std .R{fish,eye} = nantemplate;
- gain.sem .R{fish,eye} = nantemplate;
- end
- end
- % mean, median, stdev, sem across all pooled data, one per stimulus type
- for stimtype = 1:numel(gainpool)
- selection = gainpool(stimtype).FER;
- gain.mean .FER(stimtype) = mean(selection);
- gain.median.FER(stimtype) = median(selection);
- gain.std .FER(stimtype) = std(selection);
- gain.sem .FER(stimtype) = std(selection)/sqrt(numel(selection));
- for eye = 1:size(result,2)
- selection = gainpool(stimtype).FR{eye};
- gain.mean .FR{eye}(stimtype) = mean(selection);
- gain.median.FR{eye}(stimtype) = median(selection);
- gain.std .FR{eye}(stimtype) = std(selection);
- gain.sem .FR{eye}(stimtype) = std(selection)/sqrt(numel(selection));
- end
- for fish = 1:size(result,1)
- selection = gainpool(stimtype).ER{fish};
- gain.mean .ER{fish}(stimtype) = mean(selection);
- gain.median.ER{fish}(stimtype) = median(selection);
- gain.std .ER{fish}(stimtype) = std(selection);
- gain.sem .ER{fish}(stimtype) = std(selection)/sqrt(numel(selection));
- end
- for fish = 1:size(result,1)
- for eye = 1:size(result,2)
- selection = gainpool(stimtype).R{fish,eye};
- gain.mean .R{fish,eye}(stimtype) = mean(selection);
- gain.median.R{fish,eye}(stimtype) = median(selection);
- gain.std .R{fish,eye}(stimtype) = std(selection);
- gain.sem .R{fish,eye}(stimtype) = std(selection)/sqrt(numel(selection));
- end
- end
- end
- end
-
-
- function hd = fishbowlFigure(hd,cam,choicelist)
- %FISHBOWLFIGURE configures an existing figure of fishbowl plots, adds decorations
- %
- % HD = fishbowlFigure sets the parameters of an existing fishbowl figure,
- % including axis limits, labels, colours, axes object sizes, etc., and
- % adds additional decorations such as colorbars.
-
- cminbottomrow = min([min(min(hd.gain.ob(4,5).CData)), ...
- min(min(hd.gain.ob(5,5).CData)), ...
- min(min(hd.gain.ob(6,5).CData))]);
- cmaxbottomrow = max([max(max(hd.gain.ob(4,5).CData)), ...
- max(max(hd.gain.ob(5,5).CData)), ...
- max(max(hd.gain.ob(6,5).CData))]);
- climbottomrow = max(abs([cminbottomrow cmaxbottomrow]))*[-1 1];
-
- cmintoprow = min([min(min(hd.gain.ob(1,1).CData)), ...
- min(min(hd.gain.ob(2,1).CData)), ...
- min(min(hd.gain.ob(3,1).CData)), ...
- min(min(hd.gain.ob(1,8).CData)), ...
- min(min(hd.gain.ob(2,8).CData)), ...
- min(min(hd.gain.ob(3,8).CData))]);
- cmaxtoprow = max([max(max(hd.gain.ob(1,1).CData)), ...
- max(max(hd.gain.ob(2,1).CData)), ...
- max(max(hd.gain.ob(3,1).CData)), ...
- max(max(hd.gain.ob(1,8).CData)), ...
- max(max(hd.gain.ob(2,8).CData)), ...
- max(max(hd.gain.ob(3,8).CData))]);
- climtoprow = max(abs([cmintoprow cmaxtoprow]))*[-1 1];
-
- for panel = 1:3
-
- switch choicelist{panel}
- case 'left'
- string.title = 'left eye gain g_L';
- case 'right'
- string.title = 'right eye gain g_R';
- case 'both'
- string.title = 'mean = (g_L+ g_R)/2';
- end
-
- hd.gain.ax(panel).Title.String = string.title;
- % title(string.title,'Interpreter','LaTeX')
- hd.gain.ob(panel,8).MarkerFaceColor = hd.gain.ob(panel,8).MarkerEdgeColor;
- hd.gain.ob(panel,8).MarkerEdgeColor = [0 0 0];
- hd.gain.ax(panel).CLim = [0 cmaxtoprow];
- % hd.gain.ax(panel).CLim = [0 max(max(datafit.matrix{choice}))];
- hd.gain.ob(panel,1).FaceAlpha = .85;
- hd.gain.ob(panel,1).EdgeColor = 'none';
- % hd.gain.ob(panel,8).Shading = 'flat';
- shading flat
-
- end
- for panel = 4:6
-
- hd.gain.ax(panel).CLim = climbottomrow;
- hd.gain.ob(panel,5).MarkerFaceColor = hd.gain.ob(panel,5).MarkerEdgeColor;
- hd.gain.ob(panel,1).FaceColor = [1 1 1];
- hd.gain.ob(panel,1).FaceAlpha = .85;
- hd.gain.ob(panel,1).EdgeColor = 'none';
- end
- for panel = 1:6
-
- axis square
- xlabel('x')
- ylabel('y')
- zlabel('z')
- hd.gain.ax(panel).XLim = [-1.1 1.1];
- hd.gain.ax(panel).YLim = [-1.1 1.1];
- hd.gain.ax(panel).ZLim = [-1.1 1.1];
- % hd.gain.ax(panel).CameraPosition = [12.2934, -9.0967,8.1315];
- cam.radius = 19.0526; % MATLAB automatically reverts to this values while rotating, anyway. Might as well choose it as default.
- [cam.x,cam.y,cam.z] = geo2car(cam.azimuth,cam.elevation,cam.radius,'CCW');
- hd.gain.ax(panel).CameraPosition = [cam.x cam.y cam.z];
- hd.gain.ax(panel).CameraTarget = [0 0 0];
- hd.gain.ax(panel).CameraUpVector = [0 0 1];
- % hd.gain.ax(panel).CameraViewAngle = 10.134;
- hd.gain.ax(panel).CameraViewAngle = 9;
- end
-
- hd.gain.ax(7) = axes;
- hd.gain.ax(7).Position = hd.gain.ax(1).Position - [.13 -.1 .2 .2];
- hd.gain.ax(7).CLim = hd.gain.ax(1).CLim;
- hd.gain.ob(7,1) = colorbar;
- hd.gain.ob(7,1).Label.String = 'OKR gain';
- hd.gain.ob(7,1).Label.Units = 'normalized';
- hd.gain.ob(7,1).Label.Position = [-.8 1 1] .* hd.gain.ob(7,1).Label.Position;
-
- hd.gain.ax(8) = axes;
- hd.gain.ax(8).Position = hd.gain.ax(4).Position - [.13 -.1 .2 .2];
- hd.gain.ax(8).CLim = hd.gain.ax(4).CLim;
- hd.gain.ob(8,1) = colorbar;
- hd.gain.ob(8,1).Label.String = 'fit - data';
- hd.gain.ob(8,1).Label.Units = 'normalized';
- hd.gain.ob(8,1).Label.Position = [-.8 1 1] .* hd.gain.ob(8,1).Label.Position;
- [hd.gain.ob(7,1).Label.FontSize, hd.gain.ob(7,1).FontSize, ...
- hd.gain.ob(8,1).Label.FontSize, hd.gain.ob(8,1).FontSize, ...
- hd.gain.ax.FontSize] = deal(14);
-
- [hd.gain.ax(1).Title.FontSize, ...
- hd.gain.ax(2).Title.FontSize, ...
- hd.gain.ax(3).Title.FontSize] = deal(14);
-
- [hd.gain.ax(1).Title.FontWeight, ...
- hd.gain.ax(2).Title.FontWeight, ...
- hd.gain.ax(3).Title.FontWeight] = deal('normal');
-
- [hd.gain.ax.Visible] = deal('off');
- % [hd.gain.ax.Title.Visible] = deal('on');
- set(([hd.gain.ax.Title]),'Visible','on')
- set(([hd.gain.ax.Title]),'Position',[1 1 .7] .* hd.gain.ax(1).Title.Position)
-
- end
- function [retained,original] = discardTrial(original,folderstring)
- % DISCARDTRIAL removes data obtained from trials consider "poorly fit" etc.
- %
- % This function takes a "result" structure as its only input argument, and
- % requires an XLSX file containing the indices of trials to be discarded.
- % It then removes those bad trials, and returns a reduced structure as its
- % primary output argument, "retained". For easy access, it also returns its
- % original input.
- %
- % This function can safely be executed multiple times, as "discarded"
- % trials are in fact just set to empty, so their array indices are not
- % reassigned to other trials.
- %
- % Written by Florian Alexander Dehmelt, Tuebingen University, in 2018.
- % Identify and load an Excel file containing the indices of all unwanted
- % trials to be discarded. This file must contain four columns of numbers
- % (from left to right: fish no., eye no., stimulus type, and repetition.
- % It may contain as many text comments as desired; these will be ignored.
- folder.badtrial = folderstring;
- % folder.badtrial = 'C:\Users\fdehmelt\Dropbox\ThinkPad Dropbox\MATLAB\201808 SphereAnalysis\Sample Data\D series\';
- file.badtrial = 'DiscardTrial.xlsx';
- badtrial = xlsread([folder.badtrial,file.badtrial]);
-
- % The "retained" structure is the same as the "original" one...
- retained = original;
-
- % ...except that unwanted trials are overwritten by empty values.
- % They thus appear the same way as non-existent trials already did.
- for trial = 1:size(badtrial,1)
-
- % Find the position of the bad trial within the structure.
- fish = badtrial(trial,1);
- eye = badtrial(trial,2);
- stimtype = badtrial(trial,3) + 1; % The +1 converts index >=0 to >=1.
- repetition = badtrial(trial,4);
-
- % Overwrite all field entries with "[]".
- fieldname = fieldnames(retained);
- for field = 1:numel(fieldname)
- retained(fish,eye,stimtype,repetition).(fieldname{field}) = [];
- end
-
- end
-
- end
- function [altered,original] = zeroTrial(original,folderstring)
- % ZEROTRIAL sets gains to zero based on visual inspection of trials.
- %
- % This function takes a "result" structure as its only input argument, and
- % requires an XLSX file containing the indices of trials to be set to zero.
- % It then adjusts those gains, and returns an altered structure as its
- % primary output argument, "retained". For easy access, it also returns its
- % original input.
- %
- % This function can safely be executed multiple times, as "altered"
- % trials are in fact just set to zero, so their array indices are not
- % reassigned to other trials.
- %
- % Written by Florian Alexander Dehmelt, Tuebingen University, in 2018.
- % Identify and load an Excel file containing the indices of all unwanted
- % trials to be discarded. This file must contain four columns of numbers
- % (from left to right: fish no., eye no., stimulus type, and repetition.
- % It may contain as many text comments as desired; these will be ignored.
- folder.badtrial = folderstring;
- % folder.badtrial = 'C:\Users\fdehmelt\Dropbox\ThinkPad Dropbox\MATLAB\201808 SphereAnalysis\Sample Data\D series\';
- file.badtrial = 'ZeroTrial.xlsx';
- badtrial = xlsread([folder.badtrial,file.badtrial]);
-
- % The "altered" structure is the same as the "original" one...
- altered = original;
-
- % ...except that gains of visually identified trials are set to zero.
- % They thus appear the same way as non-existent trials already did.
- for trial = 1:size(badtrial,1)
-
- % Find the position of the bad trial within the structure.
- fish = badtrial(trial,1);
- eye = badtrial(trial,2);
- stimtype = badtrial(trial,3) + 1; % The +1 converts index >=0 to >=1.
- repetition = badtrial(trial,4);
-
- % Overwrite the gain field entries with "0", retain all other values.
- altered(fish,eye,stimtype,repetition).gain = 0;
-
- end
-
- end
- function combinedvalue = vonmisesfisher9param(param,predictor)
- %VONMISESFISHER9PARAM Evaluate two overlapping von Mises-Fisher distributions.
- %
- % VALUE = VONMISESFISHER9PARAM computes the value of two(!) overlapping,
- % spherical (p=3) von Mises-Fisher distributions at the chosen location.
- %
- % It is formatted to match the input/output structure required by the
- % built-in non-linear regression function nlinfit.
- convention = 'CCW';
- dimension = 3;
- radius = 1;
- if size(predictor,2) == 2
- predictor(:,3) = radius*ones(size(predictor(:,1)));
- end
- [x,y,z] = geo2car(predictor(:,1),predictor(:,2),predictor(:,3),convention);
- direction = [x,y,z];
-
- numberofdistributions = 2;
- value = NaN(size(predictor,1),numberofdistributions);
-
- for k = 1:numberofdistributions
-
- offset = param(1);
- amplitude = param(4*k-2);
- meanazimuth = param(4*k-1);
- meanelevation = param(4*k);
- concentration = param(4*k+1);
-
- [meanx,meany,meanz] = geo2car(meanazimuth,meanelevation,radius,convention);
- meandirection = [meanx,meany,meanz];
- bessel = concentration / ...
- (2*pi * (exp(concentration) - exp(-concentration)));
- normalisation = concentration^(dimension/2 - 1) / ...
- ((2*pi)^(dimension/2) * bessel);
-
- value(:,k) = normalisation * exp(concentration*meandirection*direction')';
- value(:,k) = amplitude * value(:,k) + offset;
-
- % HACK to constrain values
- if abs(meanazimuth) > 180 || ...
- abs(meanelevation) > 90 || ...
- amplitude < 0 || ...
- concentration < 0
-
- value(:,k) = 1e10*ones(size(value(:,k)));
-
- end
- end
-
- combinedvalue = sum(value,2);
-
- end
- function [x,y,z] = geo2car(azimuth,elevation,radius,convention)
-
- switch convention
- case 'CCW'
- x = radius .* cosd(elevation).*cosd(azimuth);
- y = radius .* cosd(elevation).*sind(azimuth);
- z = radius .* sind(elevation);
- case 'CW'
- x = radius .* cosd(elevation).*cosd(azimuth);
- y = radius .* cosd(elevation).*-sind(azimuth);
- z = radius .* sind(elevation);
- otherwise
- error('Convention not supported.')
- end
-
- end
- function [azimuth,elevation,radius] = car2geo(x,y,z,convention)
-
- azimuth = atand(y./x) + 180*((x<0).*(y>=0) - (x<0).*(y<0));
-
- switch convention
- case 'CCW'
- case 'CW'
- azimuth = -azimuth;
- otherwise
- error('Convention not supported.')
- end
-
- radius = sqrt(x.^2+y.^2+z.^2);
- elevation = asind(z./radius);
-
- end
- function [lefteyehandle,righteyehandle,bodyhandle] = ...
- plot3dFish(scale,yaw,pitch,roll)
- % This corresponds to version 20170505a, a.k.a. plot3dFish20170505a.m.
- set(gcf,'Color',[1 1 1])
- resolution = 10;
- eye.colour = .1*[1 1 1];
- mainbody.colour = .7*[1 1 1];
- eye.x = scale * (cos(-pi:pi/resolution:3*pi-pi/resolution)');
- eye.y = scale * (0.7*sin(0:pi/resolution:4*pi-pi/resolution)');
- eye.z = scale * ([.7*ones(2*resolution,1); ...
- 1*ones(2*resolution,1)]);
- N = 2*resolution;
- lefteye.vertices = [eye.x, eye.y, eye.z];
- righteye.vertices = [eye.x, -eye.y, eye.z];
- lefteye.faces = [[1:2*resolution 1]; ...
- [1+2*resolution:4*resolution 1+2*resolution]];
- for k = 1:N
- lefteye.faces = [lefteye.faces; ...
- [mod([k-1 k],N)+1, ...
- mod([k k-1],N)+1+N, ...
- mod(k-1,N)+1, ...
- NaN(1,N-4)]];
- end
- lefteye.facevertexcdata = repmat(eye.colour,[size(lefteye.faces,1) 1]);
- righteye.faces = lefteye.faces;
- righteye.facevertexcdata = lefteye.facevertexcdata;
- lefteyehandle = patch(lefteye);
- righteyehandle = patch(righteye);
- set([lefteyehandle,righteyehandle],'FaceColor','flat','EdgeColor','none')
- mainbody.x = scale * (-.1 + [1.0*cos(-pi:pi/resolution:pi-pi/resolution), ...
- 0.4*cos(-pi:pi/resolution:pi-pi/resolution)]');
- mainbody.y = scale * ([0.7*sin(0:pi/resolution:2*pi-pi/resolution), ...
- 0.2*sin(0:pi/resolution:2*pi-pi/resolution)]');
- mainbody.z = scale * ([1.1*ones(2*resolution,1); ...
- -7*ones(2*resolution,1)]);
- N = 2*resolution;
- body.vertices = [mainbody.x, mainbody.y, mainbody.z];
- body.faces = [[1:2*resolution 1]; ...
- [1+2*resolution:4*resolution 1+2*resolution]];
- for k = 1:N
- body.faces = [body.faces; ...
- [mod([k-1 k],N)+1, ...
- mod([k k-1],N)+1+N, ...
- mod(k-1,N)+1, ...
- NaN(1,N-4)]];
- end
- body.facevertexcdata = repmat(mainbody.colour,[size(body.faces,1) 1]);
- bodyhandle = patch(body);
- set(bodyhandle,'FaceColor','flat','EdgeColor','none')
- rotate(lefteyehandle,[1 0 0],80)
- rotate(lefteyehandle,[0 0 1],-10)
- rotate(righteyehandle,[1 0 0],-80)
- rotate(righteyehandle,[0 0 1],10)
- rotate(bodyhandle,[0 1 0],-90)
-
- rotate(lefteyehandle,[0 0 1],yaw)
- rotate(righteyehandle,[0 0 1],yaw)
- rotate(bodyhandle,[0 0 1],yaw)
-
- rotate(lefteyehandle,[0 1 0],pitch)
- rotate(righteyehandle,[0 1 0],pitch)
- rotate(bodyhandle,[0 1 0],pitch)
-
- rotate(lefteyehandle,[1 0 0],roll)
- rotate(righteyehandle,[1 0 0],roll)
- rotate(bodyhandle,[1 0 0],roll)
- end
- function hd = displayStimuli(typestring)
-
- warning('off','MATLAB:interp1:UsePCHIP')
- % The line above deactivates the following cbrewer warning:
- % Warning: INTERP1(...,'CUBIC') will change in a future release. Use INTERP1(...,'PCHIP') instead.
- % > In interp1>sanitycheckmethod (line 265)
- % In interp1>parseinputs (line 401)
- % In interp1 (line 80)
- % In interpolate_cbrewer (line 31)
- % In cbrewer (line 101)
- % In heatmap20180823 (line 21)
- % close all
- % clf
-
- switch typestring
-
- case 'disk'
- file.folder = '..\..\data\Fig3 default disk data\Data D Series (neue Experimente)\';
- datarange = 9:46;
-
- case 'area'
- file.folder = '..\..\data\Fig5 size data\Data A Series (A-Series)\';
- datarange = 2:43;
- datarange = 2:8;
- case 'freq'
- file.folder = '..\..\data\Fig5 frequency data\Data V Series (V-Serie)\';
- datarange = 2:43;
- datarange = 2;
- otherwise
- error('Argument of displayStimuli.m must be ''disk'', ''area'', or ''freq''.')
-
- end
-
- file.stimulus = 'Stimulus.xlsx';
- [~,stim.tableconvention,~] = xlsread([file.folder,file.stimulus],'C1:C1');
- stim.azimuth = xlsread([file.folder,file.stimulus],['C',num2str(datarange(1)),':C',num2str(datarange(end))]);
- stim.elevation = xlsread([file.folder,file.stimulus],['D',num2str(datarange(1)),':D',num2str(datarange(end))]);
- stim.radius = xlsread([file.folder,file.stimulus],['E',num2str(datarange(1)),':E',num2str(datarange(end))]);
-
- % compute disk boundaries
- diskcentre.geo = [stim.azimuth, stim.elevation];
- diskcentre.angle = stim.radius;
- % diskcentre.geo = [ 45 20; ...
- % 30 5; ...
- % 60 -45];
- % diskcentre.angle = 40 * [ones(size(diskcentre.geo,1),1)];
- diskcentre.azim = diskcentre.geo(:,1);
- diskcentre.elev = diskcentre.geo(:,2);
- diskcentre.rads = ones(size(diskcentre.geo(:,1)));
- resolution = .001;
- numdisk = size(diskcentre.geo,1);
- for disk = 1:numdisk
- [boundary.azim{disk}, ...
- boundary.elev{disk}] = diskBoundary(diskcentre.azim(disk), ...
- diskcentre.elev(disk), diskcentre.angle(disk), resolution);
- [boundary.x{disk}, ...
- boundary.y{disk}, ...
- boundary.z{disk}] = geo2car(boundary.azim{disk}, boundary.elev{disk}, ...
- ones(size(boundary.azim{disk})),'CW');
- end
-
-
- % create figure
- hd.default.fg = figure(1);
- hd.default.fg.Name = 'This is a sphere.';
- hd.default.fg.Position = [50 50 500 800];
- hd.default.fg.Color = [1 1 1];
-
- hd.default.ax(1) = axes;
- hd.default.ax(1).Position = [0 0 1 1];
-
- hold on
- % plot background sphere with shading
- hd.default.globe = displaySphere;
- % plot disk boundaries
- % diskcolour = cbrewer('seq','Greens',numdisk*2);
- diskcolour = flipud(cbrewer('seq','Greys',numdisk*2));
- % diskcolour = diskcolour(randperm(numdisk),:);
- croptext.xpos = [1.27 1.32 1.35 1.34 1.25 0.7 -1.58];
- croptext.ypos = [0 0 0 0 0 0 0];
- croptext.zpos = [.365 .18 .0 -.18 -.45 -.95 -.85];
- for disk = 1:numdisk
- % plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',3,'Color',0*[1 1 1])
- % plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',2,'Color',1*[1 1 1])
- plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',1,'Color',diskcolour(disk,:))
- % plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',1,'Color',.2*[1 1 1])
- croptext.label(disk) = text(croptext.xpos(disk),croptext.ypos(disk),croptext.zpos(disk), ...
- [num2str(diskcentre.angle(disk),'%.1f'),'°'],'FontSize',12);
- end
- hold off
-
- end
- function globe = displaySphere
-
- [surface.x,surface.y,surface.z] = sphere(100);
-
- reference.geo = [30;20;1]; % desired azimuth, elevation, radius of maximum colour value
- reference.azim = reference.geo(1);
- reference.elev = reference.geo(2);
- reference.rads = ones(size(reference.geo(1)));
- [reference.x,reference.y,reference.z] = geo2car(reference.azim,reference.elev,reference.rads,'CW');
- scalarproduct = [surface.x(:),surface.y(:),surface.z(:)] * [reference.x;reference.y;reference.z];
- surface.c = reshape(scalarproduct,size(surface.x));
- surface.c = 10.^(surface.c);
- globe = surf(surface.x,surface.y,surface.z,surface.c);
- shading flat
- axis square
- greyscale = (.8:.01:.99)' * [1 1 1];
- colormap(greyscale)
-
- end
- function [azimuthOut, elevationOut] = diskBoundary(azimuthIn, elevationIn, angleIn, resolutionIn)
- % The following equation describes orthodromic (or "great circle") distance as an angle:
- % arccos(sin(centre.elevation)*sin(border.elevation) + ...
- % cos(centre.elevation)*cos(border.elevation)*cos(border.azimuth-centre.azimuth)) = angle;
- % This equation is used to compute border.azimuth below.
- centre.azimuth = azimuthIn;
- centre.elevation = elevationIn;
- border.angle = angleIn / 2; % convert solid angle ("diameter") to "radius"
- border.resolution = resolutionIn;
- intended = -90 : border.resolution : 90;
- % limit = cosd([centre.elevation-border.angle centre.elevation+border.angle]);
- % intended = acosd(min(limit) : border.resolution : max(limit));
- % "auxiliary" elevations are explicitly added to guarantee coverage near top/bottom of circle:
- auxiliary = centre.elevation + border.angle*[-1 1];
- border.elevation = unique([intended,auxiliary]);
- border.azimuth = centre.azimuth + ...
- acosd((cosd(border.angle) - sind(centre.elevation)*sind(border.elevation)) ./ ...
- (cosd(centre.elevation)*cosd(border.elevation)));
- % Eliminate complex elements...
- border.azimuth(imag(border.azimuth)~=0) = NaN; % This statement sometimes fails horribly.
- % border.azimuth(imag(border.azimuth)>1e-5) = NaN; % This statement is silly, but more stable.
- % ...and compute corresponding values for the second half of the boundary.
- border.azimuth = [border.azimuth, 2*centre.azimuth-border.azimuth];
- border.elevation = [border.elevation, border.elevation];
-
- border.azimuth = mod(border.azimuth+180,360)-180;
-
- azimuthOut = border.azimuth';
- elevationOut = border.elevation';
- end
- function hd = displayPattern(typestring,spatialfreq)
- % typestring = 'freq';
- % switch typestring
- %
- % case 'disk'
- % file.folder = '..\..\data\Fig3 default disk data\Data D Series (neue Experimente)\';
- % datarange = 9:46;
- %
- % case 'area'
- % file.folder = '..\..\data\Fig5 size data\Data A Series (A-Series)\';
- % datarange = 2:43;
- % datarange = 2:8;
- %
- % case 'freq'
- % file.folder = '..\..\data\Fig5 frequency data\Data V Series (V-Serie)\';
- % datarange = 2:43;
- % datarange = 2;
- %
- % otherwise
- % error('Argument of displayStimuli.m must be ''disk'', ''area'', or ''freq''.')
- %
- % end
- % switch typestring
- % case 'area'
- % spatialfreq = 5.5 * ones(1,7);
- % case 'freq'
- % % spatialfreq = [1.5 3 5 7 9 11 20];
- % spatialfreq = logspace(0.18,1.2,7);
- % otherwise
- % error('Argument of displayStimuli.m must be ''area'' or ''freq''.')
- % end
-
- grid.colour = .3*[1 1 1];
- grid.width = 1;
- grid.height = .7;
- grid.xspace = .29;
- grid.yspace = .27 + .25;
- grid.x0 = [-grid.width-grid.xspace/2, +grid.xspace/2, ...
- -grid.width/2*3-grid.xspace, -grid.width/2, +grid.width/2+grid.xspace,...
- -grid.width-grid.xspace/2, +grid.xspace/2];
- grid.y0 = [ [1 1] * (+grid.height/2+grid.yspace), ...
- [1 1 1] * (-grid.height/2), ...
- [1 1] * (-grid.height/2*3-grid.yspace)];
- showazimuthrange = [-180 -135];
- showazimuthfraction = abs(diff(showazimuthrange))/360;
-
- hd.pattern.fg = figure();
- hd.pattern.ax = axes();
- hold on
- dy = grid.height;
- for gridindex = 1:7
- numbar(gridindex) = ceil(grid.width*360*spatialfreq(gridindex)*showazimuthfraction);
- dx = grid.width / (360*spatialfreq(gridindex)*showazimuthfraction) / 2;
- for barindex = 1:numbar(gridindex)
- x0 = grid.x0(gridindex) + (barindex-1) * dx * 2;
- xmax = grid.x0(gridindex) + grid.width;
- y0 = grid.y0(gridindex);
- darkbar(gridindex,barindex) = patch([x0*[1 1],min(x0+dx,xmax)*[1 1]], ...
- [y0,y0+dy,y0+dy,y0], grid.colour);
- end
- outline(gridindex) = plot(grid.x0(gridindex)*[1 1 1 1 1] + grid.width*[0 0 1 1 0], ...
- grid.y0(gridindex)*[1 1 1 1 1] + grid.height*[0 1 1 0 0], ...
- 'Color', grid.colour);
- textlabel(gridindex) = text(grid.x0(gridindex)+grid.width/2, ...
- grid.y0(gridindex)+grid.height/30, ...
- [num2str(spatialfreq(gridindex),'%.2f'),' cpd']);
- textlabel(gridindex).HorizontalAlignment = 'center';
- textlabel(gridindex).VerticalAlignment = 'top';
- textlabel(gridindex).FontSize = 12;
- end
- hold off
- hd.pattern.ax.PlotBoxAspectRatio = [grid.width, grid.height, grid.height];
- [hd.pattern.ax.XAxis.Visible, hd.pattern.ax.YAxis.Visible] = deal('off');
- hd.pattern.ax.XLim = [-1 1] * 2.2;
- % hd.pattern.ax.XLim = [0 .25] * 2.2;
- hd.pattern.ax.YLim = [-1 1] * 1.8;
-
- end
|