Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

figureAsymmetry20190528gin.m 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620
  1. clear all
  2. close all
  3. grating = displayPattern(360/22);
  4. velocity = displayCosine(12.5);
  5. [leftdisk,leftparam] = displayDiskStimuli('left');
  6. [rightdisk,rightparam] = displayDiskStimuli('right');
  7. singledisk = displayDiskStimuli('single');
  8. [diskhandle,diskdata,fitdata] = gainAnalysisDisk('Resolution',50);
  9. variant = 3; % 1 = regular, 2 = rotated, 3 = combined/corrected %%%%% !!!! %%%%%
  10. hd.collage.fg = figure();
  11. hd.collage.fg.Name = 'Sphere manuscript (Suppl Fig); Yoking dependence on stimulus location';
  12. hd.collage.fg.Color = [1 1 1];
  13. hd.collage.fg.Position = [0 40 1070 960];
  14. % Populate the combined figure from previously generated partial figures...
  15. hd.collage.ax(1,1) = copyobj(grating.pattern.ax(1), hd.collage.fg);
  16. hd.collage.ax(1,2) = copyobj(velocity.cosine.ax(1), hd.collage.fg);
  17. hd.collage.ax(1,3) = copyobj(leftdisk.default.ax(1), hd.collage.fg);
  18. hd.collage.ax(1,4) = copyobj(rightdisk.default.ax(1), hd.collage.fg);
  19. hd.collage.ax(1,5) = copyobj(singledisk.default.ax(1), hd.collage.fg);
  20. % hd.collage.ax(2,1) = copyobj(diskdata.gain.ax(7), hd.collage.fg);
  21. hd.collage.ax(2,1) = axes;
  22. for subpanel = 2:4
  23. hd.collage.ax(2,subpanel) = copyobj(diskhandle.gain(variant).ax(subpanel-1), hd.collage.fg);
  24. end
  25. for subpanel = 5:7
  26. hd.collage.ax(2,subpanel) = copyobj(hd.collage.ax(2,subpanel-3), hd.collage.fg);
  27. end
  28. % ...get rid of the partial figures once pillaged...
  29. % close(1:7)
  30. % ...then adjust the size, position and other properties of those objects.
  31. [hd.collage.ax(1,1:5).Units, hd.collage.ax(2,1:7).Units] = deal('pixels');
  32. hd.collage.ax(1,1).Position = [100 850 180 70];
  33. hd.collage.ax(1,2).Position = [130 730 140 55];
  34. hd.collage.ax(1,3).Position = [350 730 200 200];
  35. hd.collage.ax(1,4).Position = hd.collage.ax(1,3).Position + [250 0 0 0];
  36. hd.collage.ax(1,5).Position = hd.collage.ax(1,3).Position + [520 0 0 0];
  37. hd.collage.ax(2,1).Position = [ 50 240 50 160];
  38. hd.collage.ax(2,2).Position = [ 90 280 350 350];
  39. for subpanel = 3:4
  40. hd.collage.ax(2,subpanel).Position = hd.collage.ax(2,subpanel-1).Position + [320 0 0 0];
  41. end
  42. for subpanel = 5:7
  43. hd.collage.ax(2,subpanel).Position = hd.collage.ax(2,subpanel-3).Position - [0 280 0 0];
  44. end
  45. darkgreyscale = (.8:.01:.99)' * [1 1 1];
  46. lightgreyscale = (.9:.01:.99)' * [1 1 1];
  47. [hd.collage.ax(1,3:4).Colormap] = deal(lightgreyscale);
  48. [hd.collage.ax(1,5).Colormap] = deal(darkgreyscale);
  49. % % Shared properties of all axis objects anywhere:
  50. [hd.collage.ax(1,1:5).FontSize, ...
  51. hd.collage.ax(2,1:7).FontSize] = deal(15);
  52. [hd.collage.ax(1,1:5).LabelFontSizeMultiplier, ...
  53. hd.collage.ax(1,1:5).TitleFontSizeMultiplier, ...
  54. hd.collage.ax(2,1:7).LabelFontSizeMultiplier, ...
  55. hd.collage.ax(2,1:7).TitleFontSizeMultiplier] = deal(1);
  56. % Create content of first subpanel of panel b (colorbar):
  57. hd.collage.ax(2,1).CLim = hd.collage.ax(2,2).CLim;
  58. hd.collage.ob(2,1,1) = colorbar;
  59. hd.collage.ob(2,1,1).Label.String = 'OKR gain';
  60. hd.collage.ob(2,1,1).Label.Units = 'normalized';
  61. hd.collage.ob(2,1,1).AxisLocation = 'in';
  62. hd.collage.ax(2,1).Visible = 'off';
  63. hd.collage.ob(2,1,1).Units = 'pixels';
  64. hd.collage.ob(2,1,1).Position(3) = 9;
  65. hd.collage.ob(2,1,1).FontSize = hd.collage.ax(2,1).FontSize;
  66. hd.collage.ob(2,1,1).Label.Position = [-.8 1 1] .* hd.collage.ob(2,1,1).Label.Position;
  67. % Add and format titles to panel a:
  68. for panel = 1
  69. hd.collage.ax(panel,1).Title.String = 'stimulus pattern';
  70. hd.collage.ax(panel,3).Title.String = '----------------------- stimulus crop -----------------------';
  71. hd.collage.ax(panel,3).Title.Interpreter = 'Latex';
  72. % hd.collage.ax(panel,4).Title.String = {'stimulus crop','(right hemisphere)'};
  73. hd.collage.ax(panel,2).XLabel.String = 'time (sec)';
  74. hd.collage.ax(panel,2).YLabel.String = {'velocity','(deg/sec)'};
  75. hd.collage.ax(panel,3).XLabel.String = 'left hemisphere';
  76. hd.collage.ax(panel,4).XLabel.String = 'right hemisphere';
  77. [hd.collage.ax(panel,3).XLabel.Visible, ...
  78. hd.collage.ax(panel,4).XLabel.Visible] = deal('on');
  79. hd.collage.ax(panel,5).Title.String = 'stimulus';
  80. for subpanel = 1:5
  81. hd.collage.ax(panel,subpanel).Title.FontWeight = 'normal';
  82. [hd.collage.ax(panel,subpanel).Title.Units, ...
  83. hd.collage.ax(panel,subpanel).XLabel.Units] = deal('pixels');
  84. end
  85. % hd.collage.ax(panel,1).Title.Position(2) = hd.collage.ax(panel,1).Position(4) - 14;
  86. hd.collage.ax(panel,1).XLabel.Position(2) = hd.collage.ax(panel,1).XLabel.Position(2) + 25;
  87. % hd.collage.ax(panel,2).XLabel.Position(2) = hd.collage.ax(panel,2).XLabel.Position(2) - 65;
  88. hd.collage.ax(panel,2).XLabel.Position(2) = hd.collage.ax(panel,2).XLabel.Position(2) + 25;
  89. hd.collage.ax(panel,3).Title.Position(1) = hd.collage.ax(1,3).Title.Position(1) + 126;
  90. for subpanel = 3:4
  91. hd.collage.ax(panel,subpanel).Title.Position(2) = hd.collage.ax(panel,subpanel).Position(4) - 2;
  92. hd.collage.ax(panel,subpanel).XLabel.Position(2) = hd.collage.ax(panel,subpanel).XLabel.Position(2) + 23;
  93. end
  94. % hd.collage.ax(panel,5).Title.Position(2) = hd.collage.ax(panel,4).Position(4) + 2;
  95. end
  96. % axes(hd.collage.ax(1,3))
  97. % leftline = text(hd.collage.ax(1,3).Title.Position(1) - 50, ...
  98. % hd.collage.ax(1,3).Title.Position(2) + 20, ...
  99. % '-----------------------','Interpreter','Latex','HorizontalAlignment','right','Units','pixels')
  100. % Force visibility of axis ticks on bottom left (sine) plot of panel a:
  101. % hd.collage.ax(1,2).XLim = [-13 13];
  102. hd.collage.ax(1,2).YLim = [-13 13];
  103. % Format titles on top row of panels b to d:
  104. for subpanel = 2:4
  105. hd.collage.ax(2,subpanel).Title.Units = 'pixels';
  106. hd.collage.ax(2,subpanel).Title.Position(2) = hd.collage.ax(2,subpanel).Title.Position(2) - 25;
  107. end
  108. % Correct for odd treatment of indices (or their absence):
  109. hd.collage.ax(2,3).Title.Position(2) = hd.collage.ax(2,3).Title.Position(2) + 9;
  110. % Remove titles from bottom row of panels b to d:
  111. for subpanel = 5:7
  112. hd.collage.ax(2,subpanel).Title.Visible = 'off';
  113. end
  114. % % Make fish on panels b to d more visible by adjusting sphere alpha:
  115. % [hd.collage.ax(
  116. % Adjust camera perspective on the lower row of panels b to d:
  117. [hd.collage.ax(2,5:7).CameraPosition] = deal([19.5 0 0]);
  118. % Display panel lettering:
  119. hd.collage.ax(4,1) = axes;
  120. hd.collage.ax(4,1).Color = 'none';
  121. hd.collage.ax(4,1).Position = [0 0 1 1];
  122. hd.collage.ax(4,1).Units = 'pixels';
  123. hd.collage.ax(4,1).Visible = 'off';
  124. hold on
  125. hd.collage.lt(1) = text(hd.collage.ax(1,1).Position(1) - 85 , ...
  126. hd.collage.ax(1,1).Position(2) + hd.collage.ax(1,1).Position(4) + 20, ...
  127. 'a', 'Units', 'pixels');
  128. hd.collage.lt(2) = text(hd.collage.ax(2,2).Position(1) - 50, ...
  129. hd.collage.ax(2,2).Position(2) + hd.collage.ax(2,2).Position(4) - 10, ...
  130. 'b', 'Units', 'pixels');
  131. hd.collage.lt(3) = text(hd.collage.ax(2,3).Position(1) + 35, ...
  132. hd.collage.ax(2,3).Position(2) + hd.collage.ax(2,3).Position(4) - 10, ...
  133. 'c', 'Units', 'pixels');
  134. hd.collage.lt(4) = text(hd.collage.ax(2,4).Position(1) + 35, ...
  135. hd.collage.ax(2,4).Position(2) + hd.collage.ax(2,4).Position(4) - 10, ...
  136. 'd', 'Units', 'pixels');
  137. hold off
  138. [hd.collage.lt(:).FontSize] = deal(15);
  139. [hd.collage.lt(:).FontWeight] = deal('bold');
  140. if variant == 3
  141. plotAsymmetry(diskdata(variant))
  142. plotYoking(diskdata(variant))
  143. end
  144. mercatorPanel20190520
  145. delete(hd.collage.ax(1,1:2)) % remove panels about basic stimulus pattern
  146. close(1:8)
  147. for subpanel = 3:4
  148. hd.collage.ax(1,subpanel).Position = hd.collage.ax(1,subpanel).Position - [200 0 0 0];
  149. end
  150. hd.collage.ax(1,5).Position = hd.collage.ax(1,5).Position - [100 0 0 0];
  151. % [hd.collage.ax(1:2,1:2).Units,hd.collage.ax(3:5,:).Units] = deal('pixels');
  152. % [hd.collage.ax(1:2,1:2).FontSize,hd.collage.ax(3:5,:).FontSize] = deal(15);
  153. % [hd.collage.ax(1:2,1:2).LabelFontSizeMultiplier, ...
  154. % hd.collage.ax(1:2,1:2).TitleFontSizeMultiplier, ...
  155. % hd.collage.ax(3:5,:).LabelFontSizeMultiplier, ...
  156. % hd.collage.ax(3:5,:).TitleFontSizeMultiplier] = deal(1);
  157. function plotYoking(diskdata)
  158. col.barface = [.2 .8 .4];
  159. col.neutralbarface = .8*[1 1 1];
  160. col.baredge = 'none';
  161. col.errorbar = .3*[1 1 1];
  162. hd.yoke.fg = figure();
  163. hd.yoke.fg.Color = [1 1 1];
  164. hd.yoke.fg.Position = [0 100 1900 390];
  165. hd.yoke.ax(1,1) = axes();
  166. % hd.yoke.ax(1,2) = axes();
  167. [hd.yoke.ax(:,:).Units] = deal('pixels');
  168. hd.yoke.ax(1,1).Position = [100 70 1780 300];
  169. y5a = diskdata.yoking.regular;
  170. y5b = diskdata.yoking.rotated;
  171. y5c = diskdata.yoking.corrected;
  172. y5d = diskdata.yoking.black;
  173. % e5a = mscdisk.botheyes.stdgain;
  174. % e5b = upside.botheyes.stdgain;
  175. e5a = NaN(size(y5a));
  176. e5b = NaN(size(y5b));
  177. e5c = NaN(size(y5c));
  178. e5d = NaN(size(y5d));
  179. % pvalue.asymmetry = signrank(y5a,y5b);
  180. x5a = (1:numel(y5a))' - .24;
  181. x5b = (1:numel(y5b))' - .08;
  182. x5c = (1:numel(y5b))' + .08;
  183. x5d = (1:numel(y5c))' + .24;
  184. axes(hd.yoke.ax(1,1))
  185. hold on
  186. hd.yoke.barregular = bar(x5a,y5a);
  187. hd.yoke.barcontrol = bar(x5b,y5b);
  188. hd.yoke.barcorrect = bar(x5c,y5c);
  189. hd.yoke.barblack = bar(x5d,y5d);
  190. hd.yoke.errorregular = errorbar(x5a,y5a,e5a,'LineStyle','none');
  191. hd.yoke.errorcontrol = errorbar(x5b,y5b,e5b,'LineStyle','none');
  192. hd.yoke.errorcorrect = errorbar(x5c,y5c,e5c,'LineStyle','none');
  193. hd.yoke.errorblack = errorbar(x5d,y5d,e5d,'LineStyle','none');
  194. hd.yoke.explanation = text(29,.351,{['regular (light grey), rotated (dark grey), ', ...
  195. 'corrected (green), reflections screened (black)']}, ...
  196. 'HorizontalAlignment','left');
  197. hold off
  198. % [hd.yoke.ax(1,:).XTick] = deal([]);
  199. hd.yoke.ax(1,1).XTick = mean([x5b,x5c],2);
  200. oldlabel = hd.yoke.ax(1,1).XTickLabel;
  201. for entry = 1:numel(oldlabel)
  202. if entry < 8
  203. newlabel{entry} = ['H',num2str(str2num(oldlabel{entry}))];
  204. else
  205. newlabel{entry} = ['D',num2str(str2num(oldlabel{entry})-7)];
  206. end
  207. end
  208. hd.yoke.ax(1,1).XTickLabel = newlabel;
  209. % hd.yoke.ax(1,1).XTickLabel{1} = '';
  210. hd.yoke.barregular.FaceColor = col.neutralbarface;
  211. hd.yoke.barcontrol.FaceColor = col.neutralbarface*3/4;
  212. hd.yoke.barcorrect.FaceColor = col.barface;
  213. hd.yoke.barblack.FaceColor = col.neutralbarface*1/4;
  214. [hd.yoke.barregular.BarWidth, ...
  215. hd.yoke.barcontrol.BarWidth, ...
  216. hd.yoke.barcorrect.BarWidth, ...
  217. hd.yoke.barblack.BarWidth] = deal(.16);
  218. [hd.yoke.errorregular.Color, ...
  219. hd.yoke.errorcontrol.Color, ...
  220. hd.yoke.errorcorrect.Color, ...
  221. hd.yoke.errorblack.Color] = deal(col.errorbar);
  222. [hd.yoke.errorregular.LineWidth, ...
  223. hd.yoke.errorcontrol.LineWidth, ...
  224. hd.yoke.errorcorrect.LineWidth, ...
  225. hd.yoke.errorblack.LineWidth] = deal(1);
  226. hd.yoke.ax(1,1).XLim = [.5 46];
  227. % hd.yoke.ax(1,1).YLim = hd.yoke.ax(1,2).YLim;
  228. hd.yoke.ax(1,1).YLabel.String = 'yoking index (g_L-g_R)/(g_L+g_R)';
  229. for panel = 1:1
  230. hd.yoke.ax(1,panel).YLabel.Units = 'pixels';
  231. hd.yoke.ax(1,panel).YLabel.Position(1) = hd.yoke.ax(1,panel).YLabel.Position(1) - 20;
  232. end
  233. hd.yoke.ax(1,1).XLabel.String = 'stimulus types';
  234. % hd.yoke.ax(1,2).XLabel.String = 'stimulus types';
  235. [hd.yoke.explanation.FontSize, ...
  236. hd.yoke.pvalue.FontSize] = deal(13);
  237. for panel = 1:1
  238. hd.yoke.ax(1,panel).XLabel.FontSize = 13;
  239. hd.yoke.ax(1,panel).FontSize = 13;
  240. hd.yoke.ax(1,panel).LabelFontSizeMultiplier = 1;
  241. hd.yoke.ax(1,panel).YGrid = 'on';
  242. hd.yoke.ax(1,panel).TickLength = [0 0];
  243. end
  244. hd.yoke.ax(1,1).YMinorGrid = 'on';
  245. end
  246. function plotAsymmetry(diskdata)
  247. col.barface = [.2 .8 .4];
  248. col.neutralbarface = .8*[1 1 1];
  249. col.baredge = 'none';
  250. col.errorbar = .3*[1 1 1];
  251. hd.asymindex.fg = figure();
  252. hd.asymindex.fg.Color = [1 1 1];
  253. hd.asymindex.fg.Position = [0 100 1900 390];
  254. hd.asymindex.ax(1,1) = axes();
  255. % hd.asymindex.ax(1,2) = axes();
  256. [hd.asymindex.ax(:,:).Units] = deal('pixels');
  257. hd.asymindex.ax(1,1).Position = [100 70 1780 300];
  258. y5a = diskdata.asymmetry.A1;
  259. y5b = diskdata.asymmetry.A2;
  260. % e5a = mscdisk.botheyes.stdgain;
  261. % e5b = upside.botheyes.stdgain;
  262. e5a = NaN(size(y5a));
  263. e5b = NaN(size(y5b));
  264. pvalue.asymmetry = signrank(y5a,y5b);
  265. x5a = (1:numel(y5a))' - .2;
  266. x5b = (1:numel(y5b))' + .2;
  267. axes(hd.asymindex.ax(1,1))
  268. hold on
  269. hd.asymindex.barregular = bar(x5a,y5a);
  270. hd.asymindex.barcontrol = bar(x5b,y5b);
  271. hd.asymindex.errorregular = errorbar(x5a,y5a,e5a,'LineStyle','none');
  272. hd.asymindex.errorcontrol = errorbar(x5b,y5b,e5b,'LineStyle','none');
  273. hd.asymindex.explanation = text(24,.351,{'biological asymmetry shown in green'},'HorizontalAlignment','center');
  274. % hd.asymindex.explanation = text(24,.351,{'biological asymmetry shown in green', ...
  275. % '(error bars show s.e.m.)'},'HorizontalAlignment','center');
  276. % hd.asymindex.pvalue = text(36,.351,{['p = ',num2str(pvalue.asymmetry)], ...
  277. % '(Wilcoxon signed-rank test)'},'HorizontalAlignment','center');
  278. hold off
  279. % [hd.asymindex.ax(1,:).XTick] = deal([]);
  280. hd.asymindex.ax(1,1).XTick = mean([x5a,x5b],2);
  281. oldlabel = hd.asymindex.ax(1,1).XTickLabel;
  282. for entry = 1:numel(oldlabel)
  283. if entry < 8
  284. newlabel{entry} = ['H',num2str(str2num(oldlabel{entry}))];
  285. else
  286. newlabel{entry} = ['D',num2str(str2num(oldlabel{entry})-7)];
  287. end
  288. end
  289. hd.asymindex.ax(1,1).XTickLabel = newlabel;
  290. hd.asymindex.barregular.FaceColor = col.neutralbarface;
  291. hd.asymindex.barcontrol.FaceColor = col.barface;
  292. [hd.asymindex.barregular.BarWidth, ...
  293. hd.asymindex.barcontrol.BarWidth] = deal(.35);
  294. [hd.asymindex.errorregular.Color, ...
  295. hd.asymindex.errorcontrol.Color] = deal(col.errorbar);
  296. [hd.asymindex.errorregular.LineWidth, ...
  297. hd.asymindex.errorcontrol.LineWidth] = deal(1);
  298. hd.asymindex.ax(1,1).XLim = [.5 46];
  299. % hd.asymindex.ax(1,1).YLim = hd.asymindex.ax(1,2).YLim;
  300. hd.asymindex.ax(1,1).YLabel.String = 'asymmetries A1 (grey), A2+A3 (green)';
  301. % hd.asymindex.ax(1,1).YLabel.String = 'mean OKR gain (pooled across eyes)';
  302. % hd.asymindex.ax(1,2).YLabel.String = 'mean OKR gain (right eye)';
  303. for panel = 1:1
  304. hd.asymindex.ax(1,panel).YLabel.Units = 'pixels';
  305. hd.asymindex.ax(1,panel).YLabel.Position(1) = hd.asymindex.ax(1,panel).YLabel.Position(1) - 20;
  306. end
  307. hd.asymindex.ax(1,1).XLabel.String = 'stimulus types';
  308. % hd.asymindex.ax(1,2).XLabel.String = 'stimulus types';
  309. [hd.asymindex.explanation.FontSize, ...
  310. hd.asymindex.pvalue.FontSize] = deal(13);
  311. for panel = 1:1
  312. hd.asymindex.ax(1,panel).XLabel.FontSize = 13;
  313. hd.asymindex.ax(1,panel).FontSize = 13;
  314. hd.asymindex.ax(1,panel).LabelFontSizeMultiplier = 1;
  315. hd.asymindex.ax(1,panel).YGrid = 'on';
  316. hd.asymindex.ax(1,panel).TickLength = [0 0];
  317. end
  318. end
  319. function hd = displayPattern(barwidth)
  320. spatialfreq = 180/barwidth;
  321. grid.colour = .3*[1 1 1];
  322. grid.width = 360;
  323. grid.height = 180;
  324. grid.x0 = -180;
  325. grid.y0 = -90;
  326. hd.pattern.fg = figure();
  327. hd.pattern.ax = axes();
  328. hold on
  329. dy = grid.height;
  330. numbar = ceil(grid.width*spatialfreq);
  331. dx = grid.width / spatialfreq / 2;
  332. for barindex = 1:numbar
  333. x0 = grid.x0 + (barindex-1) * dx * 2;
  334. xmax = grid.x0 + grid.width;
  335. y0 = grid.y0;
  336. darkbar(barindex) = patch([x0*[1 1],min(x0+dx,xmax)*[1 1]], ...
  337. [y0,y0+dy,y0+dy,y0], grid.colour);
  338. end
  339. [darkbar(:).EdgeColor] = deal('none');
  340. outline = plot(grid.x0*[1 1 1 1 1] + grid.width*[0 0 1 1 0], ...
  341. grid.y0*[1 1 1 1 1] + grid.height*[0 1 1 0 0], ...
  342. 'Color', grid.colour);
  343. hold off
  344. hd.pattern.ax.PlotBoxAspectRatio = [grid.width, grid.height, grid.height];
  345. % % [hd.pattern.ax.XAxis.Visible, hd.pattern.ax.YAxis.Visible] = deal('off');
  346. hd.pattern.ax.XLim = [-180 180];
  347. hd.pattern.ax.YLim = [-90 90];
  348. hd.pattern.ax.XTick = [-180 180];
  349. hd.pattern.ax.YTick = [-90 0 90];
  350. hd.pattern.ax.XLabel.String = 'azimuth';
  351. hd.pattern.ax.YLabel.String = 'elevation';
  352. end
  353. function hd = displayCosine(maxvelocity)
  354. time = 0:.2:25;
  355. velocity = sin(time*2*pi/10) * maxvelocity;
  356. hd.cosine.fg = figure();
  357. hd.cosine.ax = axes();
  358. hold on
  359. hd.cosine.ob(1) = plot(time([1 end]),[0 0]);
  360. hd.cosine.ob(2) = plot(time,velocity);
  361. hold off
  362. hd.cosine.ob(1).LineStyle = ':';
  363. hd.cosine.ob(1).Color = .0*[1 1 1];
  364. hd.cosine.ob(2).Color = .2*[1 1 1];
  365. hd.cosine.ax.Box = 'off';
  366. hd.cosine.ax.XGrid = 'on';
  367. % hd.cosine.ax.XMinorGrid = 'on';
  368. % hd.cosine.ax.YGrid = 'on';
  369. % hd.cosine.ax.XAxisLocation = 'origin';
  370. hd.cosine.ax.XTick = [0 25];
  371. % hd.cosine.ax.XTick = 25;
  372. % hd.cosine.ax.XTickLabel = hd.cosine.ax.XTick;
  373. hd.cosine.ax.YTick = [-1 1] * maxvelocity;
  374. end
  375. function [handle,data,datafit] = gainAnalysisDisk(varargin) %#ok<FNDEF>
  376. % GAINANALYSIS and all subfunctions were written by Florian Alexander
  377. % Dehmelt in 2018. This is version 20180911, a.k.a. gainAnalysis20180911.m.
  378. % Apologies for the lack of polish (and comments) at this point.
  379. %
  380. % All input arguments are optional:
  381. %
  382. % Resolution - scalar, indicating your choice of resolution for the
  383. % rendering of the spherical surface as part % of the
  384. % figures. Actual data fitting is completely unaffected.
  385. % CamAzimuth - scalar, horizontal angle at which sphere is shown
  386. % CamElevation - scalar, vertical angle at which sphere is shown
  387. % Normalisation - options: nothing, or 'mean gain per hemisphere',
  388. % or 'max gain per hemisphere',
  389. % or 'ratio of mean gains per hemisphere'
  390. %
  391. % Output arguments are:
  392. %
  393. % handle - structure containing handles to all graphics objects
  394. % (figures, axes, plots, decorations)
  395. % data - structure containing pre-processed data, with those fields:
  396. % .left - numerical array of means of OKR gain for the left eye
  397. % .right - numerical array of means of OKR gain for the right eye
  398. % .both - numerical array of mean of OKR gain means across both eyes
  399. % datafit - structure containing the fits to data, with following fields:
  400. % .centre - 2x2 numerical array of azimuths (1st row) and elevation
  401. % angles (2nd row) of the two individial von Mises-Fisher
  402. % functions fit to data (1st column = 1st function, etc.).
  403. % These are NOT the peaks of the actual fit (= sum of both
  404. % functions)! I still have to implement that feature.
  405. % .value - 36x1 numerical array containing the value of the actual fit
  406. % (= sum of both functions) at the locations sampled by data
  407. % .param - 1x9 numerical array containing all best-fit parameters
  408. % (offset, amplitude1, azimuth1, elevation1, concentration1,
  409. % amplitude2, azimuth2, elevation2, concentration2)
  410. %
  411. % The recommended default is calling "hd = gainAnalysis20180911(200);".
  412. %
  413. % To specify camera position before analysis, call the function as follows:
  414. % hd = gainAnalysis20180911(200,'CamAzimuth',-30,'CamElevation',15);
  415. %
  416. % To edit camera position post-hoc, adjust and execute the following lines:
  417. % az = 0; el = 0; % substitute your desired camera coordinates here
  418. % [hd.gain(end).ax(1:6).CameraPosition] = ...
  419. % deal([cosd(az)*cosd(el),sind(az)*cosd(el),sind(el)]/19.0526);
  420. % figure(21)
  421. % MANAGE VARIABLE INPUT
  422. p = inputParser;
  423. valid.resolution = @(x) isnumeric(x) && numel(x)==1 && x>0;
  424. valid.camazimuth = @(x) isnumeric(x) && numel(x)==1;
  425. valid.camelevation = @(x) isnumeric(x) && numel(x)==1;
  426. valid.normalisation = @(x) ischar(x);
  427. % valid.rotation = @(x) ischar(x);
  428. default.resolution = 200;
  429. default.camazimuth = 0;
  430. default.camelevation = 45;
  431. default.normalisation = '';
  432. % default.rotation = 'none';
  433. addOptional(p, 'Resolution', default.resolution, valid.resolution);
  434. addOptional(p, 'CamAzimuth', default.camazimuth, valid.camazimuth);
  435. addOptional(p, 'CamElevation', default.camelevation, valid.camelevation);
  436. addOptional(p, 'Normalisation', default.normalisation, valid.normalisation);
  437. % addOptional(p, 'Rotation', default.rotation, valid.rotation);
  438. parse(p,varargin{:});
  439. resolution = p.Results.Resolution;
  440. cam.azimuth = p.Results.CamAzimuth;
  441. cam.elevation = p.Results.CamElevation;
  442. normalisation = p.Results.Normalisation;
  443. % rotation = p.Results.Rotation;
  444. % GENERAL SETTINGS
  445. warning('off','MATLAB:interp1:UsePCHIP')
  446. % The line above deactivates the following cbrewer warning:
  447. % Warning: INTERP1(...,'CUBIC') will change in a future release. Use INTERP1(...,'PCHIP') instead.
  448. % > In interp1>sanitycheckmethod (line 265)
  449. % In interp1>parseinputs (line 401)
  450. % In interp1 (line 80)
  451. % In interpolate_cbrewer (line 31)
  452. % In cbrewer (line 101)
  453. % In heatmap20180823 (line 21)
  454. % Identify which stimulus entries correspond to the disk-mask stimuli.
  455. datarange = 9:46;
  456. % datarange = 9:44;
  457. % !!! IMPORTANT !!! These are the Excel row numbers, not stimulus types.
  458. % READ DATA
  459. % Query user to manually select the main data regularset. Other files have
  460. % standard names and are expected to be in the same folder.
  461. [mfilefolder,~,~] = fileparts(mfilename('fullpath'));
  462. % [regularset.result,regularset.folder] = uigetfile([mfilefolder,'\*.mat']);
  463. regularset.file = 'result_20180110_125537.mat';
  464. regularset.folder = ['..\..\data\Fig3 default disk data\Data D Series (neue Experimente)\'];
  465. rotatedset.file = 'result_20181001_114504.mat';
  466. rotatedset.folder = ['..\..\data\Fig3 default disk data\Control Data Rotation (arena flip)\'];
  467. blackset.file = 'result_20181026_111904.mat';
  468. blackset.folder = ['..\..\data\Fig3FigSupp4 asymmetry data\Control Data Blocked (one side black)\'];
  469. regularset.structure = load([regularset.folder,regularset.file],'result');
  470. rotatedset.structure = load([rotatedset.folder,rotatedset.file],'result');
  471. blackset.structure = load([blackset.folder,blackset.file],'result');
  472. regularset.result = regularset.structure.result;
  473. rotatedset.result = rotatedset.structure.result;
  474. blackset.result = blackset.structure.result;
  475. % Discard unwanted trials, based on .xlsx documenting manual assessment.
  476. regularset.result = discardTrial(regularset.result,regularset.folder);
  477. rotatedset.result = discardTrial(rotatedset.result,rotatedset.folder);
  478. blackset.result = discardTrial(blackset.result,blackset.folder);
  479. % Pool gain data in various ways (this is a significant subfunction!).
  480. regularset.gain = poolGainData(regularset.result);
  481. rotatedset.gain = poolGainData(rotatedset.result);
  482. blackset.gain = poolGainData(blackset.result);
  483. % READ STIMULUS INFORMATION
  484. regularset.stimcentre = readStimulus(regularset.folder,datarange);
  485. rotatedset.stimcentre = readStimulus(rotatedset.folder,datarange);
  486. blackset.stimcentre = readStimulus(blackset.folder,datarange);
  487. % reordering = [1;2;3;5;4;7;6]; % hemisphere stimuli, U vs. D and L vs. R flipped; now add disks:
  488. % for regularindex = 1:size(regularset.stimcentre,1)
  489. % % Find rotated-arena stimulus appearing at same position relative to fish:
  490. % rotatedindex = find(sum(regularset.stimcentre(regularindex,:)==rotatedset.stimcentre,2)==2);
  491. % reordering = [reordering; rotatedindex]; %#ok<AGROW>
  492. % end
  493. %
  494. % Correction above is unnecessary if stimulus coordinates were already in
  495. % fish-centred coordinates. In this case, do not reorder data:
  496. reordering = 1:45;
  497. % CHOOSE SUBSET OF DATA TO ANALYSE, AND PROCEED TO ANALYSE IT
  498. variantlist = {'regular','rotated','corrected'};
  499. for variant = 1:numel(variantlist)
  500. switch variantlist{variant}
  501. case 'regular'
  502. data(variant).left = regularset.gain.mean.FR{1};
  503. data(variant).right = regularset.gain.mean.FR{2};
  504. % for selection = datarange-1
  505. % gLreg = regularset.gain.mean.FR{1}(selection);
  506. % gRreg = regularset.gain.mean.FR{2}(selection);
  507. % data(variant).yoking.regular(selection) = (gLreg-gRreg)/(gLreg+gRreg);
  508. % end
  509. case 'rotated'
  510. data(variant).left = rotatedset.gain.mean.FR{1}(reordering);
  511. data(variant).right = rotatedset.gain.mean.FR{1}(reordering);
  512. % for selection = datarange-1
  513. % gLrot = rotatedset.gain.mean.FR{1}(reordering(selection));
  514. % gRrot = rotatedset.gain.mean.FR{2}(reordering(selection));
  515. % data(variant).yoking.rotated(selection) = (gLrot-gRrot)/(gLrot+gRrot);
  516. % end
  517. case 'corrected'
  518. % data(variant).left = (regularset.gain.mean.FR{1} + rotatedset.gain.mean.FR{1}) / 2;
  519. % data(variant).right = (regularset.gain.mean.FR{2} + rotatedset.gain.mean.FR{1}) / 2;
  520. data(variant).left = (regularset.gain.mean.FR{1} + rotatedset.gain.mean.FR{1}(reordering)) / 2;
  521. data(variant).right = (regularset.gain.mean.FR{2} + rotatedset.gain.mean.FR{1}(reordering)) / 2;
  522. data(variant).unstimblack.left = blackset.gain.mean.FR{1};
  523. data(variant).unstimblack.right = blackset.gain.mean.FR{2};
  524. % for selection = datarange-1
  525. for selection = 1:(max(datarange)-1)
  526. gLreg = regularset.gain.mean.FR{1}(selection);
  527. gRreg = regularset.gain.mean.FR{2}(selection);
  528. gLrot = rotatedset.gain.mean.FR{1}(reordering(selection));
  529. gRrot = rotatedset.gain.mean.FR{2}(reordering(selection));
  530. gLblk = blackset.gain.mean.FR{1}(selection);
  531. gRblk = blackset.gain.mean.FR{2}(selection);
  532. data(variant).asymmetry.A1(selection) = ((gLreg-gRreg)/(gLreg+gRreg) - (gLrot-gRrot)/(gLrot+gRrot)) / 2;
  533. data(variant).asymmetry.A2(selection) = ((gLreg-gRreg)/(gLreg+gRreg) + (gLrot-gRrot)/(gLrot+gRrot)) / 2;
  534. data(variant).yoking.regular(selection) = (gLreg-gRreg)/(gLreg+gRreg);
  535. data(variant).yoking.rotated(selection) = (gLrot-gRrot)/(gLrot+gRrot);
  536. data(variant).yoking.corrected(selection) = (mean([gLreg,gLrot])-mean([gRreg,gRrot])) / ...
  537. (mean([gLreg,gLrot])+mean([gRreg,gRrot]));
  538. data(variant).yoking.black(selection) = (gLblk-gRblk)/(gLblk+gRblk);
  539. end
  540. end
  541. data(variant).both = [data(variant).left(datarange(1:end/2)-1);data(variant).right(datarange(end/2+1:end)-1)];
  542. % data(variant).both = mean([data(variant).left,data(variant).right],2); % important change! no more averaging
  543. % Create figure(s).
  544. hd.gain(variant).fg = figure();
  545. hd.gain(variant).fg.Name = 'OKR gain';
  546. hd.gain(variant).fg.Color = [1 1 1];
  547. hd.gain(variant).fg.Position = [50 50 1000 650];
  548. hd.gain(variant).fg.Colormap = flipud(cbrewer('div','RdBu',100));
  549. % hd.gain(variant).fg.Colormap = cbrewer('div','PRGn',100);
  550. choicelist = {'left','both','right'};
  551. for choice = 1:numel(choicelist)
  552. switch choicelist{choice}
  553. case 'right'
  554. response = data(variant).right(datarange-1); % -1 is offset by Excel title row
  555. case 'left'
  556. response = data(variant).left(datarange-1);
  557. case 'both'
  558. response = data(variant).both;
  559. otherwise
  560. error(['Input argument must be one of the following strings: ' ...
  561. '''left'', ''right'', or ''both''.'])
  562. end
  563. [x,y,z] = sphere(resolution);
  564. [datafit(choice).matrix,datafit(choice).param] = ...
  565. vonMisesFisherFit(regularset.stimcentre,response,x,y,z);
  566. % set 3D fish parameters
  567. scale = .1;
  568. yaw = 180;
  569. pitch = 0;
  570. roll = 0;
  571. % set decorative arc parameters; compute their cartesian coordinates
  572. equator.base = -1:.01:1;
  573. equator.x = [equator.base, fliplr(equator.base)];
  574. equator.y = [sqrt(1-equator.base.^2), -sqrt(1-equator.base.^2)];
  575. equator.z = 0*ones(1,numel(equator.x));
  576. meridian.x = equator.x;
  577. meridian.y = equator.z;
  578. meridian.z = equator.y;
  579. aura.x = equator.z;
  580. aura.y = equator.y;
  581. aura.z = equator.x;
  582. [sample.x,sample.y,sample.z] = ...
  583. geo2car(regularset.stimcentre(:,1), regularset.stimcentre(:,2), ...
  584. ones(size(regularset.stimcentre,1),1),'CCW');
  585. % plot all of the above onto the correct panel of the existing figure
  586. panel = choice;
  587. figure(hd.gain(variant).fg.Number)
  588. hd.gain(variant).ax(panel) = axes;
  589. hd.gain(variant).ax(panel).Position = [.13 .45 .35 .45] + (panel-1)*[.25 0 0 0];
  590. hold on
  591. hd.gain(variant).ob(panel,1) = surf(x,y,z,datafit(panel).matrix);
  592. shading interp
  593. colour.eye = 0*[1 1 1];
  594. colour.body = 1*[1 1 1];
  595. hd.gain(variant).ob(panel,2:4) = plot3dFish(scale,yaw,pitch,roll,colour);
  596. hd.gain(variant).ob(panel,5) = plot3(equator.x,equator.y,equator.z,'k','LineWidth',2);
  597. hd.gain(variant).ob(panel,6) = plot3(meridian.x,meridian.y,meridian.z,'--k');
  598. hd.gain(variant).ob(panel,7) = plot3(aura.x,aura.y,aura.z,':k');
  599. hd.gain(variant).ob(panel,8) = scatter3(1.03*sample.x,1.03*sample.y,1.03*sample.z, ...
  600. 180*ones(size(sample.x)),response,'Marker','o');
  601. hold off
  602. end
  603. % Apply full formatting to the figure(s). Subfunction included below.
  604. hd = fishbowlFigure(hd,cam,choicelist);
  605. % % Compute yoking/lateralisation index per individual stimulus location.
  606. % convention = 'CCW';
  607. % switch convention
  608. % case 'CCW'
  609. % stim.isleft = stim.azimuth > 0;
  610. % stim.isright = stim.azimuth < 0;
  611. % case 'CW'
  612. % stim.isleft = stim.azimuth < 0;
  613. % stim.isright = stim.azimuth > 0;
  614. % otherwise
  615. % error('Sign convention must either be ''CCW'' or ''CW''.')
  616. % end
  617. end
  618. % Collect output arguments, unless already done (e.g., "datafit").
  619. handle = hd;
  620. end
  621. function stimcentre = readStimulus(datafolder,datarange)
  622. stimulus = 'Stimulus.xlsx';
  623. [~,tableconvention,~] = xlsread([datafolder,stimulus],'C1:C1');
  624. azimuth = xlsread([datafolder,stimulus],['C',num2str(datarange(1)),':C',num2str(datarange(end))]);
  625. elevation = xlsread([datafolder,stimulus],['D',num2str(datarange(1)),':D',num2str(datarange(end))]);
  626. % Stimulus tables may use CW convention; default code uses CCW.
  627. switch tableconvention{:}
  628. case 'azimuth (CCW)'
  629. case 'azimuth (CW)'
  630. azimuth = -azimuth;
  631. otherwise
  632. error(['Field C1 of Stimulus.xlsx should contain either ' ...
  633. 'string ''azimuth (CCW)'' or string ''azimuth (CW)''.'])
  634. end
  635. stimcentre = [azimuth,elevation];
  636. end
  637. function gain = poolGainData(result)
  638. %POOLGAINDATA converts a raw result structure into plottable data by
  639. %pooling OKR gain data in various ways, and storing those results in a new
  640. %structure called "gain".
  641. % Pool data in various ways (e.g., .FR = pooled across all [F]ish and
  642. % all [R]epetitions, but with a separate array for each [E]ye).
  643. for stimtype = 1:size(result,3)
  644. gainpool(stimtype).FER = [result(:,:,stimtype,:).gain];
  645. for eye = 1:size(result,2)
  646. gainpool(stimtype).FR{eye} = [result(:,eye,stimtype,:).gain];
  647. end
  648. for fish = 1:size(result,1)
  649. gainpool(stimtype).ER{fish} = [result(fish,:,stimtype,:).gain];
  650. end
  651. for repetition = 1:size(result,4)
  652. gainpool(stimtype).FE{repetition} = [result(:,:,stimtype,repetition).gain];
  653. end
  654. for fish = 1:size(result,1)
  655. for eye = 1:size(result,2)
  656. gainpool(stimtype).R{fish,eye} = [result(fish,eye,stimtype,:).gain];
  657. end
  658. end
  659. end
  660. % Make sure that in following section, if a stimulus type is not present,
  661. % the array contain a NaN at that position, instead of a zero.
  662. nantemplate = NaN(numel(gainpool),1);
  663. gain.mean .FER = nantemplate;
  664. gain.median.FER = nantemplate;
  665. gain.std .FER = nantemplate;
  666. gain.sem .FER = nantemplate;
  667. for eye = 1:size(result,2)
  668. gain.mean .FR{eye} = nantemplate;
  669. gain.median.FR{eye} = nantemplate;
  670. gain.std .FR{eye} = nantemplate;
  671. gain.sem .FR{eye} = nantemplate;
  672. end
  673. for fish = 1:size(result,1)
  674. gain.mean .ER{fish} = nantemplate;
  675. gain.median.ER{fish} = nantemplate;
  676. gain.std .ER{fish} = nantemplate;
  677. gain.sem .ER{fish} = nantemplate;
  678. end
  679. for fish = 1:size(result,1)
  680. for eye = 1:size(result,2)
  681. gain.mean .R{fish,eye} = nantemplate;
  682. gain.median.R{fish,eye} = nantemplate;
  683. gain.std .R{fish,eye} = nantemplate;
  684. gain.sem .R{fish,eye} = nantemplate;
  685. end
  686. end
  687. % mean, median, stdev, sem across all pooled data, one per stimulus type
  688. for stimtype = 1:numel(gainpool)
  689. selection = gainpool(stimtype).FER;
  690. gain.mean .FER(stimtype) = mean(selection);
  691. gain.median.FER(stimtype) = median(selection);
  692. gain.std .FER(stimtype) = std(selection);
  693. gain.sem .FER(stimtype) = std(selection)/sqrt(numel(selection));
  694. for eye = 1:size(result,2)
  695. selection = gainpool(stimtype).FR{eye};
  696. gain.mean .FR{eye}(stimtype) = mean(selection);
  697. gain.median.FR{eye}(stimtype) = median(selection);
  698. gain.std .FR{eye}(stimtype) = std(selection);
  699. gain.sem .FR{eye}(stimtype) = std(selection)/sqrt(numel(selection));
  700. end
  701. for fish = 1:size(result,1)
  702. selection = gainpool(stimtype).ER{fish};
  703. gain.mean .ER{fish}(stimtype) = mean(selection);
  704. gain.median.ER{fish}(stimtype) = median(selection);
  705. gain.std .ER{fish}(stimtype) = std(selection);
  706. gain.sem .ER{fish}(stimtype) = std(selection)/sqrt(numel(selection));
  707. end
  708. for fish = 1:size(result,1)
  709. for eye = 1:size(result,2)
  710. selection = gainpool(stimtype).R{fish,eye};
  711. gain.mean .R{fish,eye}(stimtype) = mean(selection);
  712. gain.median.R{fish,eye}(stimtype) = median(selection);
  713. gain.std .R{fish,eye}(stimtype) = std(selection);
  714. gain.sem .R{fish,eye}(stimtype) = std(selection)/sqrt(numel(selection));
  715. end
  716. end
  717. end
  718. end
  719. function [fitresult,fitparam] = vonMisesFisherFit(stimcentre,response,x,y,z)
  720. predictor = stimcentre;
  721. % Initialise fit parameters.
  722. offset = 0;
  723. amplitude(1) = 1;
  724. amplitude(2) = 1;
  725. meanazimuth(1) = 70;
  726. meanelevation(1) = 20; % azimuth, elevation
  727. meanazimuth(2) = -70;
  728. meanelevation(2) = 20; % azimuth, elevation
  729. concentration(1) = 2.5;
  730. concentration(2) = 2.5;
  731. initialparam = [offset, ...
  732. amplitude(1), ...
  733. meanazimuth(1), ...
  734. meanelevation(1), ...
  735. concentration(1), ...
  736. amplitude(2), ...
  737. meanazimuth(2), ...
  738. meanelevation(2), ...
  739. concentration(2)];
  740. % Fit.
  741. option.MaxIter = 1e4;
  742. datafit.param = nlinfit(predictor,response,@vonmisesfisher9param,initialparam,option);
  743. datafit.value = vonmisesfisher9param(datafit.param,predictor);
  744. % Identify centres.
  745. azimuth(1) = datafit.param(3);
  746. elevation(1) = datafit.param(4);
  747. azimuth(2) = datafit.param(7);
  748. elevation(2) = datafit.param(8);
  749. for k = 1:2
  750. % azimuth(k) = datafit.param(3); % This was incorrect - same value read twice.
  751. % elevation(k) = datafit.param(4);
  752. uniqueazimuth(k) = mod(azimuth(k)+180,360) - 180;
  753. uniqueelevation(k) = mod(elevation(k)+90,180) - 90;
  754. datafit.centre(:,k) = [uniqueazimuth(k), uniqueelevation(k)];
  755. end
  756. % compute geographic coordinates of sphere, and its colour data values
  757. [azimuth,elevation,radius] = car2geo(x,y,z,'CCW');
  758. predictor = [azimuth(:),elevation(:),radius(:)];
  759. fitresult = reshape(vonmisesfisher9param(datafit.param,predictor),size(radius));
  760. fitparam = datafit.param;
  761. end
  762. function hd = fishbowlFigure(hd,cam,choicelist)
  763. %FISHBOWLFIGURE configures an existing figure of fishbowl plots, adds decorations
  764. %
  765. % HD = fishbowlFigure sets the parameters of an existing fishbowl figure,
  766. % including axis limits, labels, colours, axes object sizes, etc., and
  767. % adds additional decorations such as colorbars.
  768. cmin = min([min(min(hd.gain(end).ob(1,1).CData)), ...
  769. min(min(hd.gain(end).ob(2,1).CData)), ...
  770. min(min(hd.gain(end).ob(3,1).CData)), ...
  771. min(min(hd.gain(end).ob(1,8).CData)), ...
  772. min(min(hd.gain(end).ob(2,8).CData)), ...
  773. min(min(hd.gain(end).ob(3,8).CData))]);
  774. cmax = max([max(max(hd.gain(end).ob(1,1).CData)), ...
  775. max(max(hd.gain(end).ob(2,1).CData)), ...
  776. max(max(hd.gain(end).ob(3,1).CData)), ...
  777. max(max(hd.gain(end).ob(1,8).CData)), ...
  778. max(max(hd.gain(end).ob(2,8).CData)), ...
  779. max(max(hd.gain(end).ob(3,8).CData))]);
  780. clim = max(abs([cmin cmax]))*[-1 1];
  781. for panel = 1:3
  782. switch choicelist{panel}
  783. case 'left'
  784. string.title = 'left eye gain g_L';
  785. case 'right'
  786. string.title = 'right eye gain g_R';
  787. case 'both'
  788. string.title = 'merge stimulated';
  789. % string.title = 'mean = (g_L+ g_R)/2';
  790. end
  791. hd.gain(end).ax(panel).Title.String = string.title;
  792. % title(string.title,'Interpreter','LaTeX')
  793. hd.gain(end).ob(panel,8).MarkerFaceColor = hd.gain(end).ob(panel,8).MarkerEdgeColor;
  794. hd.gain(end).ob(panel,8).MarkerEdgeColor = [0 0 0];
  795. hd.gain(end).ax(panel).CLim = [0 cmax];
  796. % hd.gain(end).ax(panel).CLim = [0 max(max(datafit(choice).matrix))];
  797. % hd.gain(end).ob(panel,1).FaceAlpha = .85;
  798. hd.gain(end).ob(panel,1).FaceAlpha = .75;
  799. hd.gain(end).ob(panel,1).EdgeColor = 'none';
  800. % hd.gain(end).ob(panel,8).Shading = 'flat';
  801. shading interp
  802. axis square
  803. xlabel('x')
  804. ylabel('y')
  805. zlabel('z')
  806. hd.gain(end).ax(panel).XLim = [-1.1 1.1];
  807. hd.gain(end).ax(panel).YLim = [-1.1 1.1];
  808. hd.gain(end).ax(panel).ZLim = [-1.1 1.1];
  809. % hd.gain(end).ax(panel).CameraPosition = [12.2934, -9.0967,8.1315];
  810. cam.radius = 19.0526; % MATLAB automatically reverts to this values while rotating, anyway. Might as well choose it as default.
  811. [cam.x,cam.y,cam.z] = geo2car(cam.azimuth,cam.elevation,cam.radius,'CCW');
  812. hd.gain(end).ax(panel).CameraPosition = [cam.x cam.y cam.z]; % FD20190507: this hides 3rd fish!!!
  813. hd.gain(end).ax(panel).CameraTarget = [0 0 0];
  814. hd.gain(end).ax(panel).CameraUpVector = [0 0 1];
  815. % hd.gain(end).ax(panel).CameraViewAngle = 10.134;
  816. hd.gain(end).ax(panel).CameraViewAngle = 9;
  817. end
  818. hd.gain(end).ax(4) = axes;
  819. hd.gain(end).ax(4).Position = hd.gain(end).ax(1).Position - [.13 -.1 .2 .2];
  820. hd.gain(end).ax(4).CLim = hd.gain(end).ax(1).CLim;
  821. hd.gain(end).ob(4,1) = colorbar;
  822. hd.gain(end).ob(4,1).Label.String = 'OKR gain';
  823. hd.gain(end).ob(4,1).Label.Units = 'normalized';
  824. hd.gain(end).ob(4,1).Label.Position = [-.8 1 1] .* hd.gain(end).ob(4,1).Label.Position;
  825. [hd.gain(end).ob(4,1).Label.FontSize, hd.gain(end).ob(4,1).FontSize, ...
  826. hd.gain(end).ax.FontSize] = deal(14);
  827. [hd.gain(end).ax(1).Title.FontSize, ...
  828. hd.gain(end).ax(2).Title.FontSize, ...
  829. hd.gain(end).ax(3).Title.FontSize] = deal(14);
  830. [hd.gain(end).ax(1).Title.FontWeight, ...
  831. hd.gain(end).ax(2).Title.FontWeight, ...
  832. hd.gain(end).ax(3).Title.FontWeight] = deal('normal');
  833. [hd.gain(end).ax.Visible] = deal('off');
  834. % [hd.gain(end).ax.Title.Visible] = deal('on');
  835. set(([hd.gain(end).ax.Title]),'Visible','on')
  836. set(([hd.gain(end).ax.Title]),'Position',[1 1 .7] .* hd.gain(end).ax(1).Title.Position)
  837. end
  838. function [retained,original] = discardTrial(original,folderstring)
  839. % DISCARDTRIAL removes data obtained from trials consider "poorly fit" etc.
  840. %
  841. % This function takes a "result" structure as its only input argument, and
  842. % requires an XLSX file containing the indices of trials to be discarded.
  843. % It then removes those bad trials, and returns a reduced structure as its
  844. % primary output argument, "retained". For easy access, it also returns its
  845. % original input.
  846. %
  847. % This function can safely be executed multiple times, as "discarded"
  848. % trials are in fact just set to empty, so their array indices are not
  849. % reassigned to other trials.
  850. %
  851. % Written by Florian Alexander Dehmelt, Tuebingen University, in 2018.
  852. % Identify and load an Excel file containing the indices of all unwanted
  853. % trials to be discarded. This file must contain four columns of numbers
  854. % (from left to right: fish no., eye no., stimulus type, and repetition.
  855. % It may contain as many text comments as desired; these will be ignored.
  856. folder.badtrial = folderstring;
  857. % folder.badtrial = 'C:\Users\fdehmelt\Dropbox\ThinkPad Dropbox\MATLAB\201808 SphereAnalysis\Sample Data\D series\';
  858. regularset.badtrial = 'DiscardTrial.xlsx';
  859. badtrial = xlsread([folder.badtrial,regularset.badtrial]);
  860. % The "retained" structure is the same as the "original" one...
  861. retained = original;
  862. % ...except that unwanted trials are overwritten by empty values.
  863. % They thus appear the same way as non-existent trials already did.
  864. for trial = 1:size(badtrial,1)
  865. % Find the position of the bad trial within the structure.
  866. fish = badtrial(trial,1);
  867. eye = badtrial(trial,2);
  868. stimtype = badtrial(trial,3) + 1; % The +1 converts index >=0 to >=1.
  869. repetition = badtrial(trial,4);
  870. % Overwrite all field entries with "[]".
  871. fieldname = fieldnames(retained);
  872. for field = 1:numel(fieldname)
  873. retained(fish,eye,stimtype,repetition).(fieldname{field}) = [];
  874. end
  875. end
  876. end
  877. function [altered,original] = zeroTrial(original,folderstring)
  878. % ZEROTRIAL sets gains to zero based on visual inspection of trials.
  879. %
  880. % This function takes a "result" structure as its only input argument, and
  881. % requires an XLSX file containing the indices of trials to be set to zero.
  882. % It then adjusts those gains, and returns an altered structure as its
  883. % primary output argument, "retained". For easy access, it also returns its
  884. % original input.
  885. %
  886. % This function can safely be executed multiple times, as "altered"
  887. % trials are in fact just set to zero, so their array indices are not
  888. % reassigned to other trials.
  889. %
  890. % Written by Florian Alexander Dehmelt, Tuebingen University, in 2018.
  891. % Identify and load an Excel file containing the indices of all unwanted
  892. % trials to be discarded. This file must contain four columns of numbers
  893. % (from left to right: fish no., eye no., stimulus type, and repetition.
  894. % It may contain as many text comments as desired; these will be ignored.
  895. folder.badtrial = folderstring;
  896. % folder.badtrial = 'C:\Users\fdehmelt\Dropbox\ThinkPad Dropbox\MATLAB\201808 SphereAnalysis\Sample Data\D series\';
  897. regularset.badtrial = 'ZeroTrial.xlsx';
  898. badtrial = xlsread([folder.badtrial,regularset.badtrial]);
  899. % The "altered" structure is the same as the "original" one...
  900. altered = original;
  901. % ...except that gains of visually identified trials are set to zero.
  902. % They thus appear the same way as non-existent trials already did.
  903. for trial = 1:size(badtrial,1)
  904. % Find the position of the bad trial within the structure.
  905. fish = badtrial(trial,1);
  906. eye = badtrial(trial,2);
  907. stimtype = badtrial(trial,3) + 1; % The +1 converts index >=0 to >=1.
  908. repetition = badtrial(trial,4);
  909. % Overwrite the gain field entries with "0", retain all other values.
  910. altered(fish,eye,stimtype,repetition).gain = 0;
  911. end
  912. end
  913. function combinedvalue = vonmisesfisher9param(param,predictor)
  914. %VONMISESFISHER9PARAM Evaluate two overlapping von Mises-Fisher distributions.
  915. %
  916. % VALUE = VONMISESFISHER9PARAM computes the value of two(!) overlapping,
  917. % spherical (p=3) von Mises-Fisher distributions at the chosen location.
  918. %
  919. % It is formatted to match the input/output structure required by the
  920. % built-in non-linear regression function nlinfit.
  921. convention = 'CCW';
  922. dimension = 3;
  923. radius = 1;
  924. if size(predictor,2) == 2
  925. predictor(:,3) = radius*ones(size(predictor(:,1)));
  926. end
  927. [x,y,z] = geo2car(predictor(:,1),predictor(:,2),predictor(:,3),convention);
  928. direction = [x,y,z];
  929. numberofdistributions = 2;
  930. value = NaN(size(predictor,1),numberofdistributions);
  931. for k = 1:numberofdistributions
  932. offset = param(1);
  933. amplitude = param(4*k-2);
  934. meanazimuth = param(4*k-1);
  935. meanelevation = param(4*k);
  936. concentration = param(4*k+1);
  937. [meanx,meany,meanz] = geo2car(meanazimuth,meanelevation,radius,convention);
  938. meandirection = [meanx,meany,meanz];
  939. bessel = concentration / ...
  940. (2*pi * (exp(concentration) - exp(-concentration)));
  941. normalisation = concentration^(dimension/2 - 1) / ...
  942. ((2*pi)^(dimension/2) * bessel);
  943. value(:,k) = normalisation * exp(concentration*meandirection*direction')';
  944. value(:,k) = amplitude * value(:,k) + offset;
  945. % HACK to constrain values
  946. if abs(meanazimuth) > 180 || ...
  947. abs(meanelevation) > 90 || ...
  948. amplitude < 0 || ...
  949. concentration < 0
  950. value(:,k) = 1e10*ones(size(value(:,k)));
  951. end
  952. end
  953. combinedvalue = sum(value,2);
  954. end
  955. function [x,y,z] = geo2car(azimuth,elevation,radius,convention)
  956. switch convention
  957. case 'CCW'
  958. x = radius .* cosd(elevation).*cosd(azimuth);
  959. y = radius .* cosd(elevation).*sind(azimuth);
  960. z = radius .* sind(elevation);
  961. case 'CW'
  962. x = radius .* cosd(elevation).*cosd(azimuth);
  963. y = radius .* cosd(elevation).*-sind(azimuth);
  964. z = radius .* sind(elevation);
  965. otherwise
  966. error('Convention not supported.')
  967. end
  968. end
  969. function [azimuth,elevation,radius] = car2geo(x,y,z,convention)
  970. azimuth = atand(y./x) + 180*((x<0).*(y>=0) - (x<0).*(y<0));
  971. switch convention
  972. case 'CCW'
  973. case 'CW'
  974. azimuth = -azimuth;
  975. otherwise
  976. error('Convention not supported.')
  977. end
  978. radius = sqrt(x.^2+y.^2+z.^2);
  979. elevation = asind(z./radius);
  980. end
  981. function [lefteyehandle,righteyehandle,bodyhandle] = ...
  982. plot3dFish(scale,yaw,pitch,roll,colour)
  983. % This corresponds to version 20170505a, a.k.a. plot3dFish20170505a.m.
  984. set(gcf,'Color',[1 1 1])
  985. resolution = 10;
  986. % colour.eye = .1*[1 1 1];
  987. % colour.body = .7*[1 1 1];
  988. eye.x = scale * (cos(-pi:pi/resolution:3*pi-pi/resolution)');
  989. eye.y = scale * (0.7*sin(0:pi/resolution:4*pi-pi/resolution)');
  990. eye.z = scale * ([.7*ones(2*resolution,1); ...
  991. 1*ones(2*resolution,1)]);
  992. N = 2*resolution;
  993. lefteye.vertices = [eye.x, eye.y, eye.z];
  994. righteye.vertices = [eye.x, -eye.y, eye.z];
  995. lefteye.faces = [[1:2*resolution 1]; ...
  996. [1+2*resolution:4*resolution 1+2*resolution]];
  997. for k = 1:N
  998. lefteye.faces = [lefteye.faces; ...
  999. [mod([k-1 k],N)+1, ...
  1000. mod([k k-1],N)+1+N, ...
  1001. mod(k-1,N)+1, ...
  1002. NaN(1,N-4)]];
  1003. end
  1004. lefteye.facevertexcdata = repmat(colour.eye,[size(lefteye.faces,1) 1]);
  1005. righteye.faces = lefteye.faces;
  1006. righteye.facevertexcdata = lefteye.facevertexcdata;
  1007. lefteyehandle = patch(lefteye);
  1008. righteyehandle = patch(righteye);
  1009. set([lefteyehandle,righteyehandle],'FaceColor','flat','EdgeColor','none')
  1010. mainbody.x = scale * (-.1 + [1.0*cos(-pi:pi/resolution:pi-pi/resolution), ...
  1011. 0.4*cos(-pi:pi/resolution:pi-pi/resolution)]');
  1012. mainbody.y = scale * ([0.7*sin(0:pi/resolution:2*pi-pi/resolution), ...
  1013. 0.2*sin(0:pi/resolution:2*pi-pi/resolution)]');
  1014. mainbody.z = scale * ([1.1*ones(2*resolution,1); ...
  1015. -7*ones(2*resolution,1)]);
  1016. N = 2*resolution;
  1017. body.vertices = [mainbody.x, mainbody.y, mainbody.z];
  1018. body.faces = [[1:2*resolution 1]; ...
  1019. [1+2*resolution:4*resolution 1+2*resolution]];
  1020. for k = 1:N
  1021. body.faces = [body.faces; ...
  1022. [mod([k-1 k],N)+1, ...
  1023. mod([k k-1],N)+1+N, ...
  1024. mod(k-1,N)+1, ...
  1025. NaN(1,N-4)]];
  1026. end
  1027. body.facevertexcdata = repmat(colour.body,[size(body.faces,1) 1]);
  1028. bodyhandle = patch(body);
  1029. set(bodyhandle,'FaceColor','flat','EdgeColor','none')
  1030. rotate(lefteyehandle,[1 0 0],80)
  1031. rotate(lefteyehandle,[0 0 1],-10)
  1032. rotate(righteyehandle,[1 0 0],-80)
  1033. rotate(righteyehandle,[0 0 1],10)
  1034. rotate(bodyhandle,[0 1 0],-90)
  1035. rotate(lefteyehandle,[0 0 1],yaw)
  1036. rotate(righteyehandle,[0 0 1],yaw)
  1037. rotate(bodyhandle,[0 0 1],yaw)
  1038. rotate(lefteyehandle,[0 1 0],pitch)
  1039. rotate(righteyehandle,[0 1 0],pitch)
  1040. rotate(bodyhandle,[0 1 0],pitch)
  1041. rotate(lefteyehandle,[1 0 0],roll)
  1042. rotate(righteyehandle,[1 0 0],roll)
  1043. rotate(bodyhandle,[1 0 0],roll)
  1044. end
  1045. function [hd,stim] = displayDiskStimuli(choice)
  1046. warning('off','MATLAB:interp1:UsePCHIP')
  1047. % The line above deactivates the following cbrewer warning:
  1048. % Warning: INTERP1(...,'CUBIC') will change in a future release. Use INTERP1(...,'PCHIP') instead.
  1049. % > In interp1>sanitycheckmethod (line 265)
  1050. % In interp1>parseinputs (line 401)
  1051. % In interp1 (line 80)
  1052. % In interpolate_cbrewer (line 31)
  1053. % In cbrewer (line 101)
  1054. % In heatmap20180823 (line 21)
  1055. % close all
  1056. % clf
  1057. regularset.folder = '..\..\data\Fig3 default disk data\Data D Series (neue Experimente)\';
  1058. datarange = 9:46;
  1059. % regularset.folder = '..\..\data\Fig5 size data\Data A Series (A-Series)\';
  1060. % datarange = 2:43;
  1061. % datarange = 2:8;
  1062. % regularset.folder = '..\..\data\Fig5 frequency data\Data V Series (V-Serie)\';
  1063. % datarange = 2:43;
  1064. regularset.stimulus = 'Stimulus.xlsx';
  1065. [~,stim.tableconvention,~] = xlsread([regularset.folder,regularset.stimulus],'C1:C1');
  1066. stim.azimuth = xlsread([regularset.folder,regularset.stimulus],['C',num2str(datarange(1)),':C',num2str(datarange(end))]);
  1067. stim.elevation = xlsread([regularset.folder,regularset.stimulus],['D',num2str(datarange(1)),':D',num2str(datarange(end))]);
  1068. % stim.radius = xlsread([regularset.folder,regularset.stimulus],['E',num2str(datarange(1)),':E',num2str(datarange(end))]);
  1069. % stim.azimuth = stim.azimuth(1:2:end);
  1070. % stim.elevation = stim.elevation(1:2:end);
  1071. stim.radius = 40*ones(size(stim.elevation));
  1072. % % [surface.]backgroundSphere
  1073. % % compute background sphere with shading
  1074. % [surface.x,surface.y,surface.z] = sphere(100);
  1075. % reference.geo = [30;20;1]; % desired azimuth, elevation, radius of maximum colour value
  1076. % reference.azim = reference.geo(1);
  1077. % reference.elev = reference.geo(2);
  1078. % reference.rads = ones(size(reference.geo(1)));
  1079. % [reference.x,reference.y,reference.z] = geo2car(reference.azim,reference.elev,reference.rads,'CW');
  1080. % scalarproduct = [surface.x(:),surface.y(:),surface.z(:)] * [reference.x;reference.y;reference.z];
  1081. % surface.c = reshape(scalarproduct,size(surface.x));
  1082. % surface.c = 10.^(surface.c+1);
  1083. % % surface.c = 2.^(surface.c+1);
  1084. % compute disk boundaries
  1085. diskcentre.geo = [stim.azimuth, stim.elevation];
  1086. diskcentre.angle = stim.radius;
  1087. % diskcentre.geo = [ 45 20; ...
  1088. % 30 5; ...
  1089. % 60 -45];
  1090. % diskcentre.angle = 40 * [ones(size(diskcentre.geo,1),1)];
  1091. diskcentre.azim = diskcentre.geo(:,1);
  1092. diskcentre.elev = diskcentre.geo(:,2);
  1093. diskcentre.rads = ones(size(diskcentre.geo(:,1)));
  1094. resolution = .2;
  1095. numdisk = size(diskcentre.geo,1);
  1096. for disk = 1:numdisk
  1097. [boundary.azim{disk}, ...
  1098. boundary.elev{disk}] = diskBoundary(diskcentre.azim(disk), ...
  1099. diskcentre.elev(disk), diskcentre.angle(disk), resolution);
  1100. [boundary.x{disk}, ...
  1101. boundary.y{disk}, ...
  1102. boundary.z{disk}] = geo2car(boundary.azim{disk}, boundary.elev{disk}, ...
  1103. ones(size(boundary.azim{disk})),'CW');
  1104. end
  1105. [diskcentre.x,diskcentre.y,diskcentre.z] = ...
  1106. geo2car(diskcentre.azim,diskcentre.elev,diskcentre.rads,'CW');
  1107. [textcentre.x,textcentre.y,textcentre.z] = ...
  1108. geo2car(diskcentre.azim,.91*diskcentre.elev,1.10*diskcentre.rads,'CW');
  1109. % create figure
  1110. hd.default.fg = figure();
  1111. hd.default.fg.Name = 'This is a sphere.';
  1112. hd.default.fg.Position = [50 50 500 800];
  1113. hd.default.fg.Color = [1 1 1];
  1114. hd.default.ax(1) = axes;
  1115. hd.default.ax(1).Position = [0 0 1 1];
  1116. hold on
  1117. % plot background sphere with shading
  1118. hd.default.globe = displaySphere;
  1119. % hd.default.globe = surf(surface.x,surface.y,surface.z,surface.c);
  1120. % shading flat
  1121. % axis square
  1122. % greyscale = (.8:.01:.99)' * [1 1 1];
  1123. % colormap(greyscale)
  1124. fish.scale = .05;
  1125. fish.yaw = 180;
  1126. fish.pitch = 0;
  1127. fish.roll = 0;
  1128. colour.eye = 0*[1 1 1];
  1129. colour.body = .5*[1 1 1];
  1130. plot3dFish(fish.scale,fish.yaw,fish.pitch,fish.roll,colour);
  1131. hd.default.globe.FaceAlpha = .5;
  1132. cropdisk = 8;
  1133. switch choice
  1134. case 'left'
  1135. disklist = 1 : numdisk/2;
  1136. case 'right'
  1137. disklist = numdisk/2+1 : numdisk;
  1138. case 'single'
  1139. disklist = cropdisk;
  1140. end
  1141. % plot disk boundaries
  1142. % diskcolour = cbrewer('seq','Greens',numdisk*2);
  1143. cbrewergreen = cbrewer('seq','Greens',numdisk);
  1144. diskcolour = repmat(cbrewergreen(round(numdisk/1.4),:),[numdisk,1]);
  1145. % diskcolour = flipud(cbrewer('seq','Greys',numdisk*2));
  1146. % diskcolour = diskcolour(randperm(numdisk),:);
  1147. for disk = disklist
  1148. if sum(disk==cropdisk)
  1149. plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',2,'Color',0*[1 1 1])
  1150. disktext(disk) = text(textcentre.x(disk),textcentre.y(disk),textcentre.z(disk), ...
  1151. ['',num2str(disk)], ...
  1152. 'FontSize', 13, 'HorizontalAlignment','center','VerticalAlignment','middle');
  1153. else
  1154. plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',1,'Color',.6*[1 1 1])
  1155. if sum(disk==disklist)
  1156. disktext(disk) = text(textcentre.x(disk),textcentre.y(disk),textcentre.z(disk), ...
  1157. ['',num2str(disk)], ...
  1158. 'FontSize', 13, 'HorizontalAlignment','center','VerticalAlignment','middle');
  1159. end
  1160. % plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',2,'Color',1*[1 1 1])
  1161. % plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',1,'Color',diskcolour(disk,:))
  1162. % plot3(boundary.x{disk},boundary.y{disk},boundary.z{disk},'-','LineWidth',1,'Color',.2*[1 1 1])
  1163. end
  1164. end
  1165. hold off
  1166. switch choice
  1167. case 'single'
  1168. disktext(cropdisk).Visible = 'off';
  1169. barwidth = 360/22/2;
  1170. % gratingazimuth = -180:barwidth:(180-barwidth);
  1171. gratingazimuth = -180:barwidth:-80;
  1172. for bar = 1:2:numel(gratingazimuth)-1
  1173. % if bar < numel(gratingazimuth)
  1174. azimlimit = gratingazimuth([bar bar+1]);
  1175. % else
  1176. % azimlimit = gratingazimuth([bar 1]);
  1177. % end
  1178. displaySphereBar(azimlimit(1),azimlimit(2),boundary.azim{cropdisk},boundary.elev{cropdisk})
  1179. end
  1180. case {'left','right'}
  1181. disktext(disklist(ceil(end/2))).Position = ...
  1182. disktext(disklist(ceil(end/2))).Position - [0 0 .16];
  1183. end
  1184. % hd.default.ax(1).CameraPosition = [2.3 17 2.2];
  1185. switch choice
  1186. case {'left','single'}
  1187. hd.default.ax(1).CameraPosition = [0 20 0];
  1188. case 'right'
  1189. hd.default.ax(1).CameraPosition = [0 -20 0];
  1190. end
  1191. hd.default.ax(1).CameraViewAngle = 6;
  1192. [hd.default.ax(1).XAxis.Visible, ...
  1193. hd.default.ax(1).YAxis.Visible, ...
  1194. hd.default.ax(1).ZAxis.Visible] = deal('off');
  1195. end
  1196. function globe = displaySphere
  1197. [surface.x,surface.y,surface.z] = sphere(300);
  1198. reference.geo = [30;20;1]; % desired azimuth, elevation, radius of maximum colour value
  1199. reference.azim = reference.geo(1);
  1200. reference.elev = reference.geo(2);
  1201. reference.rads = ones(size(reference.geo(1)));
  1202. [reference.x,reference.y,reference.z] = geo2car(reference.azim,reference.elev,reference.rads,'CW');
  1203. scalarproduct = [surface.x(:),surface.y(:),surface.z(:)] * [reference.x;reference.y;reference.z];
  1204. surface.c = reshape(scalarproduct,size(surface.x));
  1205. surface.c = 10.^(surface.c);
  1206. globe = surf(surface.x,surface.y,surface.z,surface.c);
  1207. shading interp
  1208. axis square
  1209. greyscale = (.8:.01:.99)' * [1 1 1];
  1210. % greyscale = (.9:.01:.99)' * [1 1 1];
  1211. colormap(greyscale)
  1212. end
  1213. function [azimuthOut, elevationOut] = diskBoundary(azimuthIn, elevationIn, angleIn, resolutionIn)
  1214. % The following equation describes orthodromic (or "great circle") distance as an angle:
  1215. % arccos(sin(centre.elevation)*sin(border.elevation) + ...
  1216. % cos(centre.elevation)*cos(border.elevation)*cos(border.azimuth-centre.azimuth)) = angle;
  1217. % This equation is used to compute border.azimuth below.
  1218. centre.azimuth = azimuthIn;
  1219. centre.elevation = elevationIn;
  1220. border.angle = angleIn / 2; % convert solid angle ("diameter") to "radius"
  1221. border.resolution = resolutionIn;
  1222. intended = -90 : border.resolution : 90;
  1223. % limit = cosd([centre.elevation-border.angle centre.elevation+border.angle]);
  1224. % intended = acosd(min(limit) : border.resolution : max(limit));
  1225. % "auxiliary" elevations are explicitly added to guarantee coverage near top/bottom of circle:
  1226. auxiliary = centre.elevation + border.angle*[-1+1e-9 1-1e-9];
  1227. border.elevation = unique([intended,auxiliary]);
  1228. border.azimuth = centre.azimuth + ...
  1229. acosd((cosd(border.angle) - sind(centre.elevation)*sind(border.elevation)) ./ ...
  1230. (cosd(centre.elevation)*cosd(border.elevation)));
  1231. % Eliminate complex elements...
  1232. border.azimuth(imag(border.azimuth)~=0) = NaN; % This statement sometimes fails horribly.
  1233. % border.azimuth(imag(border.azimuth)>1e-5) = NaN; % This statement is silly, but more stable.
  1234. % ...and compute corresponding values for the second half of the boundary.
  1235. border.azimuth = [border.azimuth, 2*centre.azimuth-border.azimuth];
  1236. border.elevation = [border.elevation, border.elevation];
  1237. border.azimuth = mod(border.azimuth+180,360)-168;
  1238. azimuthOut = border.azimuth';
  1239. elevationOut = border.elevation';
  1240. end
  1241. function hd = displaySphereBar(azimlimit1,azimlimit2,boundaryazim,boundaryelev)
  1242. resolution = .01;
  1243. if azimlimit2 < azimlimit1
  1244. error('sdasdsa')
  1245. end
  1246. crop.index = logical((azimlimit1 <= boundaryazim) .* (boundaryazim <= azimlimit2));
  1247. if sum(crop.index) > 0
  1248. crop.azim = boundaryazim(crop.index)';
  1249. crop.elev = boundaryelev(crop.index)';
  1250. crop.azim1 = crop.azim(diff(crop.azim) > 0);
  1251. crop.azim2 = crop.azim(diff(crop.azim) < 0);
  1252. crop.elev1 = crop.elev(diff(crop.azim) > 0);
  1253. crop.elev2 = crop.elev(diff(crop.azim) < 0);
  1254. sequence = @(a,b) a:(sign(b-a)*resolution):b;
  1255. edge.elev1 = sequence(crop.elev1(1), crop.elev2(end));
  1256. edge.elev2 = sequence(crop.elev2(1), crop.elev1(end));
  1257. % edge.elev1 = sequence(crop.elev1(1), crop.elev2(end));
  1258. % edge.elev2 = sequence(crop.elev2(1), crop.elev1(end));
  1259. edge.azim1 = azimlimit1*ones(size(edge.elev1));
  1260. edge.azim2 = azimlimit2*ones(size(edge.elev2));
  1261. azim = [edge.azim1,fliplr(crop.azim2),edge.azim2,fliplr(crop.azim1)];
  1262. elev = [edge.elev1,fliplr(crop.elev2),edge.elev2,fliplr(crop.elev1)];
  1263. rads = 1.009*ones(size(elev));
  1264. [bar.x,bar.y,bar.z] = geo2car(azim,elev,rads,'CW');
  1265. hold on, p = patch(bar.x,bar.y,bar.z,'k'); hold off
  1266. % p.FaceColor = [.2 .8 .4];
  1267. p.FaceColor = .2*[1 1 1];
  1268. p.EdgeColor = 'none';
  1269. % hold on, plot3(bar.x,bar.y,bar.z,'r'); hold off
  1270. end
  1271. end